~amavect/makeu

fdc63c3a21dc2a4df29ba0e9a779c932914f1d35 — amavect 1 year, 9 months ago
Import from https://bitbucket.org/amavect/makeu
14 files changed, 2077 insertions(+), 0 deletions(-)

A LICENSE
A README
A com.c
A com.h
A components.h
A elementile.h
A fs.c
A guipart.c
A hsvmap.c
A makeu.1.man
A makeu.c
A mkfile
A screenpick.c
A slider.c
A  => LICENSE +19 -0
@@ 1,19 @@
Copyright (c) 2018 Amavect

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

A  => README +26 -0
@@ 1,26 @@
Makeu ("make hue") is a color creating tool for 9front.

Features: 
A selectable color field, a color display that doubles as an eyedropper, and a couple sliders.
Made using Libelementile. https://bitbucket.org/amavect/libelementile

TODO: document the filesystem

Install: 
mk install 
mk man 

Contribute: 
Please send love and patches: amavect@gmail.com

Bugs: 
Unoptimized. Libdraw is very slow on non-hw-accelerated systems.
I run on drawterm, in which case I was told that RGBA32 is not hw-accelerated.
Redrawing takes > 16 ms on my system, which is unacceptable.
Runs extremely slow on some systems (shows the redraw operation in motion).
Only known case so far is one user's t61p.
The issue lies in hsvmap.c:/^redraw/ where drawing the color gradients
 takes a long time. I'm not sure how to optimize this effectively, 
 or if the problem lies within devdraw or hardware acceleration.
 When combining all gradients into a static image, redrawing 
 still takes about 20 ms, which is still unacceptable.

A  => com.c +218 -0
@@ 1,218 @@
/* Amavect! */
/* Common functions */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include "com.h"

ulong
unsetalpha(ulong color)
{
	ulong alpha, red, green, blue;
	alpha = (color >> 0*8) & 0xFF;
	if(alpha == 0)
		return 0;
	red = (color >> 3*8) & 0xFF;
	green = (color >> 2*8) & 0xFF;
	blue = (color >> 1*8) & 0xFF;
	red = (ulong)ceil(red * 255.0 / alpha);
	green = (ulong)ceil(green * 255.0 / alpha);
	blue = (ulong)ceil(blue * 255.0 / alpha);
	if(red > 255)
		red = 255;
	if(green > 255)
		green = 255;
	if(blue > 255)
		blue = 255;
	return (red<<3*8) | (green<<2*8) | (blue<<1*8) | (alpha<<0*8);
}

ulong
rgbtocolor(RGB rgb)
{
	return (rgb.red<<24) + (rgb.green<<16) + (rgb.blue<<8) + 0xFF;;
}

RGB
colortorgb(ulong color)
{
	RGB rgb;
	rgb.red = (color>>24) & 0xFF;
	rgb.green = (color>>16) & 0xFF;
	rgb.blue = (color>>8) & 0xFF;
	return rgb;
}

/* stores color to the array of size 3 */
void
colortoarray(uchar col[4], ulong color)
{
	col[0] = (color >> 0) & 0xFF;
	col[1] = (color >> 8) & 0xFF;
	col[2] = (color >> 16) & 0xFF;
	col[3] = (color >> 24) & 0xFF;
}

RGBd
torgbd(RGB rgb)
{
	RGBd rgbd;
	rgbd.red = rgb.red / 255.0;
	rgbd.green = rgb.green / 255.0;
	rgbd.blue = rgb.blue / 255.0;
	return rgbd;
}

RGB
torgb(RGBd rgbd)
{
	RGB rgb;
	/* add 0.5 to round to nearest int */
	rgb.red = (uchar)(rgbd.red * 255 + 0.5);
	rgb.green = (uchar)(rgbd.green * 255 + 0.5);
	rgb.blue = (uchar)(rgbd.blue * 255 + 0.5);
	return rgb;
}

/* clips the point to be the nearest point inside of r (excluding max) */
Point
ptclip(Point p, Rectangle r)
{
	if(p.x > r.max.x - 1)
		p.x = r.max.x - 1;
	else if(p.x < r.min.x)
		p.x = r.min.x;
	if(p.y > r.max.y - 1)
		p.y = r.max.y - 1;
	else if(p.y < r.min.y)
		p.y = r.min.y;
	return p;
}

/* 
 * map HSV to RGB
 * implemented off of wikipedia lol
 */
RGBd
hsvdtorgbd(HSVd hsvd)
{
	RGBd rgbd;
	double c, m, x; /* chroma, m, x */
	c = hsvd.val * hsvd.sat;
	x = c * (1 - fabs(fmod(hsvd.hue, 2) - 1));
	rgbd.red = 0;
	rgbd.green = 0;
	rgbd.blue = 0;
	if(0 <= hsvd.hue && hsvd.hue <= 1){
		rgbd.red = c;
		rgbd.green = x;
	}else if(1 < hsvd.hue && hsvd.hue <= 2){
		rgbd.red = x;
		rgbd.green = c;
	}else if(2 < hsvd.hue && hsvd.hue <= 3){
		rgbd.green = c;
		rgbd.blue = x;
	}else if(3 < hsvd.hue && hsvd.hue <= 4){
		rgbd.green = x;
		rgbd.blue = c;
	}else if(4 < hsvd.hue && hsvd.hue <= 5){
		rgbd.red = x;
		rgbd.blue = c;
	}else if(5 < hsvd.hue && hsvd.hue <= 6){
		rgbd.red = c;
		rgbd.blue = x;
	}
	m = hsvd.val - c;
	rgbd.red += m, rgbd.green += m, rgbd.blue += m;
	return rgbd;
}

HSVd
rgbdtohsvd(RGBd rgbd)
{
	HSVd hsvd;
	double max, min;
	
	if(rgbd.red >= rgbd.green && rgbd.red > rgbd.blue){
		max = rgbd.red;
		if(rgbd.green > rgbd.blue)
			min = rgbd.blue;
		else
			min = rgbd.green;
		hsvd.hue = (rgbd.green - rgbd.blue) / (max - min);
		hsvd.sat = (max - min) / max;
		hsvd.val = max;
		if(hsvd.hue < 0)
			hsvd.hue += 6;
	}else if(rgbd.green >= rgbd.blue && rgbd.green > rgbd.red){
		max = rgbd.green;
		if(rgbd.red > rgbd.blue)
			min = rgbd.blue;
		else
			min = rgbd.red;
		hsvd.hue = 2 + (rgbd.blue - rgbd.red) / (max - min);
		hsvd.sat = (max - min) / max;
		hsvd.val = max;
	}else if(rgbd.blue >= rgbd.red && rgbd.blue > rgbd.green){
		max = rgbd.blue;
		if(rgbd.red > rgbd.green)
			min = rgbd.green;
		else
			min = rgbd.red;
		hsvd.hue = 4 + (rgbd.red - rgbd.green) / (max - min);
		hsvd.sat = (max - min) / max;
		hsvd.val = max;
	}else{ /* r==g==b */
		hsvd.hue = 0;
		hsvd.sat = 0; /* max==min */
		hsvd.val = rgbd.red;
	}
	
	return hsvd;
}

HSVd
rgbtohsvd(RGB rgb)
{
	return rgbdtohsvd(torgbd(rgb));
}

RGB
hsvdtorgb(HSVd hsvd)
{
	return torgb(hsvdtorgbd(hsvd));
}

/* 
 * returns DNotacolor (0xFFFFFF00) if invalid, else the color
 * assumes input str is a valid string
 */
ulong
strtocolor(char *str)
{
	uchar alpha, chan;
	ulong c;
	int i;
	
	if(strlen(str) != 10)
		return DNotacolor;
	if(str[10] != '\0')
		return DNotacolor;
	if(str[0] != '0' || str[1] != 'x')
		return DNotacolor;
	for(i = 2; i < 10; i++){
		if(!(
			str[i] >= '0' && str[i] <= '9' || 
			str[i] >= 'a' && str[i] <= 'f' ||
			str[i] >= 'A' && str[i] <= 'F'))
			return DNotacolor;
	}
	c = strtoul(str, nil, 0);
	alpha = c & 0xFF;
	for(i = 8; i <= 24; i+=8){
		chan = c >> i & 0xFF;
		if(chan > alpha)
			return DNotacolor;
	}
	return c;
}

A  => com.h +36 -0
@@ 1,36 @@
typedef struct RGBd RGBd;
typedef struct HSVd HSVd;
typedef struct Ratioxy Ratioxy;

struct RGBd{
	double red;
	double green;
	double blue;
};

struct HSVd{
	double hue; /* hue [0,6] */
	double sat; /* saturation [0,1] */
	double val; /* value [0,1] */
};

struct Ratioxy{
	double x;
	double y;
};

/* color funcs */
ulong unsetalpha(ulong);
void colortoarray(uchar col[3], ulong);
RGBd torgbd(RGB);
RGB torgb(RGBd);
RGB colortorgb(ulong);
ulong rgbtocolor(RGB);
RGBd hsvdtorgbd(HSVd);
HSVd rgbdtohsvd(RGBd);
HSVd rgbtohsvd(RGB);
RGB hsvdtorgb(HSVd);

/* misc */
Point ptclip(Point, Rectangle);
ulong strtocolor(char *str);

A  => components.h +68 -0
@@ 1,68 @@
typedef struct Slider Slider;
typedef struct HSVmap HSVmap;
typedef struct Screenpick Screenpick;

struct Slider {
	double val; /* starting value */
	double min; /* minimum value */
	double max; /* maximum value */
	double inc; /* scroll wheel increment */
	int ishoriz; /* is horizontal? */
	char *fmt; /* display format for the current value (a double) */
	void (*onchange)(double); /* passes val on change */
	/* internal */
	Rectangle r;
	Image *bg;
	Image *fg;
	Image *bka;
	Image *wta;
};

struct HSVmap{
	HSVd hsv;
	void (*onchange)(HSVd); /* passes color on change */
	Mousectl *mctl; /* pointer for menu 2 */
	/* internal */
	Ratioxy pos;
	int maptype; /* HS=0, SV=1, HV=2 */
	Rectangle r;
	Image *huex;
	Image *satx;
	Image *saty;
	Image *valy;
	Image *pix;
	Image *circ;
};

struct Screenpick {
	ulong color; /* The selected color, RGB form */
	Mousectl *mctl; /* pointer for cursor changing */
	void (*onchange)(ulong); /* passes color on change */
	/* internal */
	int screenfd; /* /dev/screen */
	Rectangle r; /* current size */
	int state; /* state machine */
	int linesize;
	uchar *linebuf;
	Image* screenline;
	Image* pixel;
};

extern Point hsvmapinit(Elementile*);
extern void hsvmapresize(Elementile*, Rectangle);
extern int hsvmapupdate(Elementile*);
extern int hsvmapmouse(Elementile*, Mouse);
extern int hsvmapkeyboard(Elementile*, Rune);
extern void hsvmapfree(Elementile*);

extern Point screenpickinit(Elementile*);
extern void screenpickresize(Elementile*, Rectangle);
extern int screenpickupdate(Elementile*);
extern int screenpickmouse(Elementile*, Mouse);
extern int screenpickkeyboard(Elementile*, Rune);

extern Point sliderinit(Elementile*);
extern void sliderresize(Elementile*, Rectangle);
extern int sliderupdate(Elementile*);
extern int slidermouse(Elementile*, Mouse);
extern int sliderkeyboard(Elementile*, Rune);

A  => elementile.h +57 -0
@@ 1,57 @@
/* Amavect! */
//#pragma src "."
//#pragma lib "libelementile.a"

typedef struct Elementile Elementile;
typedef struct Guipart Guipart;

struct Elementile {
	void *aux; /* the entity */
	Point (*init)(Elementile*); /* electricity is the essence of consciousness */
	void (*resize)(Elementile*, Rectangle); /* the ground is where we live */
	int (*update)(Elementile*); /* the air we breathe brings us to a new state */
	int (*mouse)(Elementile*, Mouse); /* flow of input is the blood of computation */
	int (*keyboard)(Elementile*, Rune); /* strike the keyboard into flame */
	void (*free)(Elementile*); /* vapor escapes our grasp, leaving nothing in its wake */
};

/*
 * *aux is a pointer to the auxillary/internal state of the Elementile.
 * Spark is initialization. Init any internal state.
 *   Return the minimum size.
 * Earth is resize. Refresh any internal state to the current size given and redraw.
 * Air is update. Validate any state changes and redraw. 
 *   Return 1 if redraw needed, 0 if not.
 * Water is mouse. Use the Mouse to update any state and redraw. 
 *   Return 1 if redraw needed, 0 if not.
 * Fire is keyboard. Use the Rune to update any state and redraw. 
 *   Return 1 if redraw needed, 0 if not.
 * Vapor is free. Free any memory and state.
 */

typedef enum Divtype{
	Vdiv,
	Hdiv
} Divtype;

struct Guipart {
	Divtype vh; /* vertical or horizontal division */
	int w; /* division width = 2*w */
	double d; /* division ratio */
	Elementile *lt; /* left or top next element */
	Elementile *rb; /* right or bottom next element */
	/* internal */
	Point ltmin; /* minimum size of lt */
	Point rbmin; /* minimum size of rb */
	Rectangle ltrect; /* current size of lt */
	Rectangle rbrect; /* current size of rb */
	Elementile *sel; /* selected element, can be nil */
	int state; /* state machine */
};

extern Point guipartinit(Elementile*);
extern void guipartresize(Elementile*, Rectangle);
extern int guipartupdate(Elementile*);
extern int guipartmouse(Elementile*, Mouse);
extern int guipartkeyboard(Elementile*, Rune);
extern void guipartfree(Elementile*);

A  => fs.c +196 -0
@@ 1,196 @@
/* Amavect! */
/* file interface to makeu */
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <draw.h>
#include "com.h"

enum {
	Qroot,
	Qcolor,
};

typedef struct F {
	char *name;
	Qid qid;
	ulong perm;
} F;

char *user;
Channel *colorc;
extern ulong color;

F rootf = {"/", {Qroot, 0, QTDIR}, 0555|DMDIR};
F colorf = {"color", {Qcolor, 0, QTFILE}, 0666};

F *
filebypath(uvlong path)
{
	if(path == Qroot)
		return &rootf;
	else
		return &colorf;
}

void 
fsattach(Req *r)
{
	r->fid->qid = filebypath(Qroot)->qid;
	r->ofcall.qid = r->fid->qid;
	respond(r, nil);
}

char *
fswalk1(Fid *fid, char *name, Qid *qid)
{
	if(fid->qid.path == Qroot){
		if(strcmp(name, colorf.name) == 0){
			*qid = colorf.qid;
			fid->qid = *qid;
			return nil;
		}else if(strcmp(name, "..") == 0){
			*qid = rootf.qid;
			fid->qid = *qid;
			return nil;
		}	
	}
	return "not found";
}

void
fillstat(Dir *d, uvlong path)
{
	F *f;
	
	f = filebypath(path);
	d->qid = f->qid;	/* unique id from server */
	d->mode = f->perm;	/* permissions */
	d->atime = time(0);	/* last read time */
	d->mtime = time(0);	/* last write time */
	d->length = 0;	/* file length */
	d->name = estrdup9p(f->name);	/* last element of path */
	d->uid = estrdup9p(user);	/* owner name */
	d->gid = estrdup9p(user);	/* group name */
	d->muid = estrdup9p(user);	/* last modifier name */
}

void
fsstat(Req *r)
{
	fillstat(&r->d, r->fid->qid.path);
	respond(r, nil);
}

int
rootgen(int n, Dir *d, void *)
{
	if(n >= 1)
		return -1;
	fillstat(d, colorf.qid.path);
	return 0;
}

void
fsopen(Req *r)
{
	uchar *hasread;
	
	if(r->fid->qid.path == Qcolor){
		hasread = emalloc9p(sizeof(uchar));
		*hasread = 0;
		r->fid->aux = hasread;
	}
	respond(r, nil);
}

void
fsread(Req *r)
{
	uvlong path;
	uchar *hasread;
	char buf[12];
	
	path = r->fid->qid.path;
	if(path == Qroot){
		dirread9p(r, rootgen, nil);
		respond(r, nil);
		return;
	}else if(path == Qcolor){
		hasread = r->fid->aux;
		if(*hasread == 1){
			r->ofcall.count = 0;
			respond(r, nil);
			return;
		}
		if(r->ifcall.count < 11){
			respond(r, "read buffer too small");
			return;
		}
		snprint(buf, r->ifcall.count, "0x%08ulX\n", color);
		r->ofcall.count = 11;
		memcpy(r->ofcall.data, buf, 11);
		*hasread += 1;
		respond(r,nil);
	}else{
		respond(r, "what");
	}
}

void
fswrite(Req *r)
{
	char buf[11];
	ulong incolor, n;
	
	if(r->fid->qid.path == Qroot)
		respond(r, "Cannot write to there!");
	if(r->ifcall.count < 10){
		r->ofcall.count = 0;
		respond(r, "color rejected, write buffer too small");
		return;
	}
	r->ofcall.count = r->ifcall.count; /* ignore further bytes */
	n = r->ifcall.count < sizeof(buf) ? r->ifcall.count : sizeof(buf);
	memcpy(buf, r->ifcall.data, n);
	buf[10] = '\0';
	incolor = strtocolor(buf);
	if(incolor == DNotacolor){
		respond(r, "color rejected, not a color");
		return;
	}
	sendul(colorc, incolor);
	respond(r, nil);
}

void
fsdestroyfid(Fid *fid)
{
	free(fid->aux);
}

Srv fs = {
	.attach = fsattach,
	.walk1 = fswalk1,
	.stat = fsstat,
	.open = fsopen,
	.read = fsread,
	.write = fswrite,
	.destroyfid = fsdestroyfid,
};

Channel *
init9pfs(char *mtpt, char *srv, int c9p)
{
	chatty9p = c9p;
	colorc = chancreate(sizeof(ulong), 1);
	user = getuser();
	if(mtpt == nil)
		mtpt = "/mnt/makeu";
	if(srv == nil)
		srv = smprint("makeu.%s.%d", user, getpid());
	threadpostmountsrv(&fs, srv, mtpt, MREPL);
	return colorc;
}

A  => guipart.c +166 -0
@@ 1,166 @@
/* Amavect! */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <elementile.h>

/*
 * Calculate minimum size by the minimum sizes of the children,
 * the division type, and the division width.
 */
Point
guipartinit(Elementile *e)
{
	Guipart *gp;
	Point p;
	
	gp = e->aux;
	gp->sel = nil;
	gp->ltmin = gp->lt->init(gp->lt);
	gp->rbmin = gp->rb->init(gp->rb);
	if(gp->vh == Hdiv){
		p.x = gp->ltmin.x > gp->rbmin.x ? gp->ltmin.x : gp->rbmin.x;
		p.y = gp->ltmin.y + gp->rbmin.y + 2*gp->w;
	}else{
		p.x = gp->ltmin.x + gp->rbmin.x + 2*gp->w;
		p.y = gp->ltmin.y > gp->rbmin.y ? gp->ltmin.y : gp->rbmin.y;
	}
	return p;
}

/*
 * Resize while enforcing the minimum size.
 * Divide rectangle based on horizontal or vertical division.
 * Set lt size by setting max. If lt is too small, increase max.
 * Set rb size by setting min. If rb is too small, increase max.
 * Recurse into subelements.
 */
void
guipartresize(Elementile *e, Rectangle rect)
{
	Guipart *gp;
	int w;
	
	gp = e->aux;
	w = gp->w;
	
	gp->ltrect.min = rect.min;
	gp->rbrect.max = rect.max;
	
	if(gp->vh == Hdiv){
		gp->ltrect.max.x = rect.max.x;
		gp->rbrect.min.x = rect.min.x;
		/* add 0.5 and cast for int rounding */
		gp->ltrect.max.y = (int)(Dy(rect) * gp->d + 0.5) + rect.min.y - w;
		if(Dy(rect) - Dy(gp->ltrect) < gp->rbmin.y)
			gp->ltrect.max.y = rect.max.y - gp->rbmin.y - 2 * w;
		if(Dy(gp->ltrect) < gp->ltmin.y)
			gp->ltrect.max.y = gp->ltrect.min.y + gp->ltmin.y;
		gp->rbrect.min.y = gp->ltrect.max.y + 2 * w;
		if(Dy(gp->rbrect) < gp->rbmin.y)
			gp->rbrect.max.y = gp->rbrect.min.y + gp->rbmin.y;
	}else{
		gp->ltrect.max.y = rect.max.y;
		gp->rbrect.min.y = rect.min.y;
		/* add 0.5 and cast for int rounding */
		gp->ltrect.max.x = (int)(Dx(rect) * gp->d + 0.5) + rect.min.x - w;
		if(Dx(rect) - Dx(gp->ltrect) < gp->rbmin.x)
			gp->ltrect.max.x = rect.max.x - gp->rbmin.x - 2 * w;
		if(Dx(gp->ltrect) < gp->ltmin.x)
			gp->ltrect.max.x = gp->ltrect.min.x + gp->ltmin.x;
		gp->rbrect.min.x = gp->ltrect.max.x + 2 * w;
		if(Dx(gp->rbrect) < gp->rbmin.x)
			gp->rbrect.max.x = gp->rbrect.min.x + gp->rbmin.x;
	}
	
	gp->lt->resize(gp->lt, gp->ltrect);
	gp->rb->resize(gp->rb, gp->rbrect);
}

/*
 * Update all elements.
 */
int
guipartupdate(Elementile *e)
{
	Guipart *gp;
	
	gp = e->aux;
	gp->lt->update(gp->lt);
	gp->rb->update(gp->rb);
	
	return 1;
}

/*
 * The mouse input may be routed to the child Elementile, depending on some state.
 * If currently focused Elementile is not nil, the mouse is passed to it.
 * Then, if no button is pressed, search for a new Elementile.
 * If a different Elementile is found, also send the mouse to it.
 * If no Elementile is found, the selected Elementile becomes nil.
 * 
 * Summary of cases for mouse input to be sent to a member Guielem:
 * mouse entry, exit, movement inside, button presses (anywhere), button releases (anywhere).
 */
int
guipartmouse(Elementile *e, Mouse m)
{
	Guipart *gp;
	int v;
	
	gp = e->aux;
	v = 0;
	
	if(gp->sel != nil)
		v = gp->sel->mouse(gp->sel, m);
	
	if(m.buttons == 0){
		if(ptinrect(m.xy, gp->ltrect)){
			if(gp->sel != gp->lt){
				gp->sel = gp->lt;
				v |= gp->sel->mouse(gp->sel, m);
			}
		}else if(ptinrect(m.xy, gp->rbrect)){
			if(gp->sel != gp->rb){
				gp->sel = gp->rb;
				v |= gp->sel->mouse(gp->sel, m);
			}
		}else{
			gp->sel = nil;
		}
	}
	
	return v;
}

/* 
 * Just send to the currently selected element.
 */
int
guipartkeyboard(Elementile *e, Rune r)
{
	Guipart *gp;
	int v;
	
	gp = e->aux;
	v = 0;
	
	if(gp->sel != nil)
		v = gp->sel->keyboard(gp->sel, r);
	
	return v;
}

void
guipartfree(Elementile *e)
{
	Guipart *gp;
	
	gp = e->aux;
	
	gp->lt->free(gp->lt);
	gp->rb->free(gp->rb);
}

A  => hsvmap.c +363 -0
@@ 1,363 @@
/* Amavect! */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <cursor.h>
#include <thread.h>
#include <keyboard.h>
#include <mouse.h>
#include "elementile.h"
#include "com.h"
#include "components.h"

static
char *buttons2[] = { "HS", "SV", "HV", nil};

static
Menu menu2 = { buttons2 };

/* return ratio of point to boundary. bound of 0,0 1,1 is illegal */
static
Ratioxy
ratioxy(Point p, Rectangle bound)
{
	Ratioxy rxy;
	rxy.x = (p.x - bound.min.x) / (double)(bound.max.x - bound.min.x - 1);
	rxy.y = (p.y - bound.min.y) / (double)(bound.max.y - bound.min.y - 1);
	return rxy;
}

/* get position in relative coordinates, not absolute */
static
Point
ratioxytopoint(Ratioxy rxy, Rectangle bound)
{
	Point p;
	p.x = (int)(rxy.x * (bound.max.x - bound.min.x - 1));
	p.y = (int)(rxy.y * (bound.max.y - bound.min.y - 1));
	return p;
}

/* map x,y to HSV to RGB */
static
HSVd
hsmap(Ratioxy rxy, HSVd hsvd)
{
	hsvd.hue = rxy.x * 6;
	hsvd.sat = rxy.y;
	return hsvd;
}

static
HSVd
svmap(Ratioxy rxy, HSVd hsvd)
{
	hsvd.sat = rxy.x;
	hsvd.val = rxy.y;
	return hsvd;
}

static
HSVd
hvmap(Ratioxy rxy, HSVd hsvd)
{
	hsvd.hue = rxy.x * 6;
	hsvd.val = rxy.y;
	return hsvd;
}

/* map HSV to x,y */
static
Ratioxy
hsmapinv(HSVd hsvd)
{
	Ratioxy rxy;
	rxy.x = hsvd.hue / 6.0;
	rxy.y = hsvd.sat;
	return rxy;
}

static
Ratioxy
svmapinv(HSVd hsvd)
{
	Ratioxy rxy;
	rxy.x = hsvd.sat;
	rxy.y = hsvd.val;
	return rxy;
}

static
Ratioxy
hvmapinv(HSVd hsvd)
{
	Ratioxy rxy;
	rxy.x = hsvd.hue / 6.0;
	rxy.y = hsvd.val;
	return rxy;
}

/* 
 * bugs:
 * rxy to pix conversion depends on precision of the src image
 * very unoptimized
 */
void
redraw(HSVmap *h)
{
	Ratioxy rxy;
	HSVd hsvd;
	uchar c[4] = {0,0,0,0};
	rxy = (Ratioxy){0,0};
	hsvd = h->hsv;
	loadimage(h->pix, h->pix->r, c, sizeof(c));
	switch(h->maptype){
	case 0:
		draw(screen, h->r, h->huex, nil, ZP);
		draw(screen, h->r, h->saty, nil, ZP);
		rxy.y = hsvd.val;
		draw(h->pix, h->pix->r, h->valy, nil, ratioxytopoint(rxy, h->r));
		draw(screen, h->r, h->pix, nil, ZP);
		break;
	case 1:
		rxy.x = hsvd.hue / 6.0;
		draw(h->pix, h->pix->r, h->huex, nil, ratioxytopoint(rxy, h->r));
		draw(screen, h->r, h->pix, nil, ZP);
		draw(screen, h->r, h->satx, nil, ZP);
		draw(screen, h->r, h->valy, nil, ZP);
		break;
	case 2:
		draw(screen, h->r, h->huex, nil, ZP);
		rxy.x = hsvd.sat;
		draw(h->pix, h->pix->r, h->satx, nil, ratioxytopoint(rxy, h->r));
		draw(screen, h->r, h->pix, nil, ZP);
		draw(screen, h->r, h->valy, nil, ZP);
		break;
	}
	draw(screen, h->r, h->circ, nil, mulpt(ratioxytopoint(h->pos, h->r),-1));
}

/*
 * Init unchanging data, return min size.
 */
Point
hsvmapinit(Elementile *e)
{
	HSVmap *h;
	h = e->aux;
	switch(h->maptype){
	case 0:
		h->pos = hsmapinv(h->hsv);
		break;
	case 1:
		h->pos = svmapinv(h->hsv);
		break;
	case 2:
		h->pos = hvmapinv(h->hsv);
		break;
	default:
		h->pos = (Ratioxy){0,0};
		h->maptype = 0;
	}
	h->circ = allocimage(display, Rect(-4,-4,5,5), RGBA32, 0, DTransparent);
	h->pix = allocimage(display, Rect(0,0,1,1), RGBA32, 1, DTransparent);
	if(h->circ == nil || h->pix == nil)
		sysfatal("RIP hsvmap, your images didn't alloc: %r");
	ellipse(h->circ, Pt(0,0), 4, 4, 0, display->black, ZP);
	h->huex = nil;
	h->satx = nil;
	h->saty = nil;
	h->valy = nil;
	return (Point){2, 2};
}

static
void
genmapimage(HSVmap *h)
{
	int xmax, ymax, i;
	uint rbs, cbs; /* row buffer size, column buffer size */
	uchar *rbuf, *cbuf; /* row buffer, column buffer */
	RGB rgb = {255,255,255};
	HSVd hues = {0.0, 1.0, 1.0};
	Ratioxy pd;
	double a;
	Rectangle r;
	
	r = rectsubpt(h->r, h->r.min);
	xmax = r.max.x - 1;
	ymax = r.max.y - 1;
	rbs = 4 * r.max.x;
	cbs = 4 * r.max.y;
	
	freeimage(h->huex);
	freeimage(h->satx);
	freeimage(h->saty);
	freeimage(h->valy);
	
	rbuf = malloc(rbs);
	cbuf = malloc(cbs);
	h->huex = allocimage(display, Rect(0,0,r.max.x,1), RGB24, 1, DNofill);
	h->satx = allocimage(display, Rect(0,0,r.max.x,1), RGBA32, 1, DNofill);
	h->saty = allocimage(display, Rect(0,0,1,r.max.y), RGBA32, 1, DNofill);
	h->valy = allocimage(display, Rect(0,0,1,r.max.y), RGBA32, 1, DNofill);
	if(rbuf == nil || cbuf == nil || 
			h->huex == nil || h->satx == nil || 
			h->saty == nil || h->valy == nil)
		sysfatal("ctorimages failed: %r");
	
	for(i = 0; i < r.max.x; i++){
		pd = ratioxy(Pt(i,ymax), r);
		rgb = hsvdtorgb(hsmap(pd, hues));
		rbuf[3*i] = (uchar)rgb.blue;
		rbuf[3*i+1] = (uchar)rgb.green;
		rbuf[3*i+2] = (uchar)rgb.red;
	}
	loadimage(h->huex, h->huex->r, rbuf, 3 * r.max.x);
	
	for(i = 0; i < r.max.x; i++){
		a = 255.0 * (xmax - i) / xmax; /* invert so S increases with x */
		rbuf[4*i] = (uchar)a;
		rbuf[4*i+1] = (uchar)a;
		rbuf[4*i+2] = (uchar)a;
		rbuf[4*i+3] = (uchar)a;
	}
	loadimage(h->satx, h->satx->r, rbuf, rbs);
	
	for(i = 0; i < r.max.y; i++){
		a = 255.0 * (ymax - i) / ymax; /* invert so S increases with y */
		cbuf[4*i] = (uchar)a;
		cbuf[4*i+1] = (uchar)a;
		cbuf[4*i+2] = (uchar)a;
		cbuf[4*i+3] = (uchar)a;
	}
	loadimage(h->saty, h->valy->r, cbuf, cbs);
	
	for(i = 0; i < r.max.y; i++){
		a = 255.0 * (ymax - i) / ymax; /* invert so V increases with y */
		cbuf[4*i] = (uchar)a;
		cbuf[4*i+1] = 0;
		cbuf[4*i+2] = 0;
		cbuf[4*i+3] = 0;
	}
	loadimage(h->valy, h->valy->r, cbuf, cbs);
	
	free(rbuf);
	free(cbuf);
}

/* 
 * Generate the HS map and static parts.
 * Uses compositing tricks for speed instead of calculating each pixel.
 * Note that this introduces a 1-bit-per-channel maximum error
 *  between the visual and the chosen color.
 * Nobody will notice, right? :D
 */
void
hsvmapresize(Elementile *e, Rectangle r)
{
	HSVmap *h;
	h = e->aux;
	h->r = r;
	genmapimage(h);
	redraw(h);
}

/*
 * Update the current position to match the color.
 */

int
hsvmapupdate(Elementile *e)
{
	HSVmap *h;
	h = e->aux;
	switch(h->maptype){
	case 0:
		h->pos = hsmapinv(h->hsv);
		break;
	case 1:
		h->pos = svmapinv(h->hsv);
		break;
	case 2:
		h->pos = hvmapinv(h->hsv);
		break;
	default:
		break;
	}
	redraw(h);
	return 1;
}

/*
 * Update the current position to the mouse,
 *  and set the color to an opaque one at the current position.
 * Note that some colors are the same in low-saturation areas, 
 *  so don't calculate the position based on color.
 * Redraw.
 */
int
hsvmapmouse(Elementile *e, Mouse m)
{
	HSVmap *h;
	h = e->aux;
	if(m.buttons & 1){
		h->pos = ratioxy(ptclip(m.xy, h->r), h->r);
		switch(h->maptype){
		case 0:
			h->hsv = hsmap(h->pos, h->hsv);
			break;
		case 1:
			h->hsv = svmap(h->pos, h->hsv);
			break;
		case 2:
			h->hsv = hvmap(h->pos, h->hsv);
			break;
		}
		redraw(h);
		h->onchange(h->hsv);
		return 1;
	}
	if(m.buttons == 2){
		switch(menuhit(2, h->mctl, &menu2, nil)){
		case 0:
			h->maptype = 0;
			h->pos = hsmapinv(h->hsv);
			break;
		case 1:
			h->maptype = 1;
			h->pos = svmapinv(h->hsv);
			break;
		case 2:
			h->maptype = 2;
			h->pos = hvmapinv(h->hsv);
			break;
		default:
			break;
		}
		redraw(h);
		return 1;
	}
	return 0;
}

/*
 * insert functionality here
 */
int
hsvmapkeyboard(Elementile*, Rune)
{
	return 0;
}

void
hsvmapfree(Elementile *e)
{
	HSVmap *h;
	h = e->aux;
	freeimage(h->huex);
	freeimage(h->satx);
	freeimage(h->saty);
	freeimage(h->valy);
	freeimage(h->pix);
}

A  => makeu.1.man +57 -0
@@ 1,57 @@
.TH MAKEU 1
.SH NAME
makeu \- make hues and create colors
.SH SYNOPSIS
.PP
.B makeu
[
.B -p
] [
.B -c
.I 0xRRGGBBAA
]
.SH DESCRIPTION
.I Makeu
is a tool for creating colors. It consists of a selectable color field, a color display that doubles as an eyedropper tool, and 7 sliders that change red, green, blue, hue, saturation, value, and alpha.
.PP
On the color field, pressing mouse 1 will set the hue, saturation, or value corresponding to that location depending on the graph type. Pressing mouse 2 over the color field will bring up a menu to select a HS graph, SV graph, or HV graph.
.PP
The color display shows the color visually and in hexadecimal after multiplying alpha. On the color display, holding mouse 1 and dragging anywhere on the screen (similar to
.IR lens (1))
will select the color below the cursor. On letting go of mouse 1,
.I makeu
will update its color to the selected color.
.PP
Pressing mouse 1 on a slider will update the corresponding value. The scroll wheel increments and decrements the values, as well.
.PP
In any panel, mouse 3 will open up the menu, which has 2 entries:
.TF Send
.TP
.B Send
Output the hex color in the form 0xRRGGBBAA and exit.
.TP
.B Exit
Exit and output nothing.
.PD
.PP
.I Makeu
has 2 options:
.TF -c
.TP
.B -c
Color. 
.I Makeu
will start with the given color.
.TP
.B  -p
Persistent. 
.I Makeu
will not exit when Send is chosen.
.SH SOURCE
.B https://bitbucket.org/amavect/makeu
.br
.SH SEE ALSO
.IR colors (1), 
.IR paint (1), 
.IR allocimage (2),
.IR color (6)

A  => makeu.c +422 -0
@@ 1,422 @@
/* Amavect! */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <cursor.h>
#include <thread.h>
#include <keyboard.h>
#include <mouse.h>
#include "elementile.h"
#include "com.h"
#include "components.h"

extern Channel* init9pfs(char*, char*, int);

void mapevent(HSVd);
void pickevent(ulong);
void redevent(double);
void greenevent(double);
void blueevent(double);
void hueevent(double);
void satevent(double);
void valevent(double);
void alphaevent(double);

int pflag = 0;

/* 
 * Alternative color models are needed because they do not map
 * injectively to each other.
 */
ulong color = 0xFFFFFFFF; /* premultiplied color */
RGB rgb = {0xFF, 0xFF, 0xFF}; /* non-premultiplied color */
ulong alpha = 0xFF;
HSVd hsvd = {1.0, 0.0, 1.0};

HSVmap hsva = {{1.0, 0.0, 1.0}, mapevent, nil};
Screenpick spa = {0xFFFFFFFF, nil, pickevent};
Slider ra = {0xFF, 0, 0xFF, 1, 0, "%2φ", redevent};
Slider ga = {0xFF, 0, 0xFF, 1, 0, "%2φ", greenevent};
Slider ba = {0xFF, 0, 0xFF, 1, 0, "%2φ", blueevent};
Slider ha = {0, 0, 6, 1.0/255.0, 0, "%1.2f", hueevent};
Slider sa = {0, 0, 1, 1.0/255.0, 0, "%1.2f", satevent};
Slider va = {0, 0, 1, 1.0/255.0, 0, "%1.2f", valevent};
Slider aa = {0xFF, 0, 0xFF, 1, 1, "%2φ", alphaevent};

Elementile hsvmp = {&hsva, hsvmapinit, hsvmapresize, hsvmapupdate, hsvmapmouse, hsvmapkeyboard, hsvmapfree};
Elementile scrpk = {&spa, screenpickinit, screenpickresize, screenpickupdate, screenpickmouse, screenpickkeyboard};
Elementile rsldr = {&ra, sliderinit, sliderresize, sliderupdate, slidermouse, sliderkeyboard};
Elementile gsldr = {&ga, sliderinit, sliderresize, sliderupdate, slidermouse, sliderkeyboard};
Elementile bsldr = {&ba, sliderinit, sliderresize, sliderupdate, slidermouse, sliderkeyboard};
Elementile hsldr = {&ha, sliderinit, sliderresize, sliderupdate, slidermouse, sliderkeyboard};
Elementile ssldr = {&sa, sliderinit, sliderresize, sliderupdate, slidermouse, sliderkeyboard};
Elementile vsldr = {&va, sliderinit, sliderresize, sliderupdate, slidermouse, sliderkeyboard};
Elementile asldr = {&aa, sliderinit, sliderresize, sliderupdate, slidermouse, sliderkeyboard};

Guipart parts[8];
Elementile tree[8];

Guipart parts[8] = {
	{Vdiv, 1, 1.0, &hsvmp, &tree[1]},
	{Hdiv, 1, 0.3, &scrpk, &tree[2]},
	{Hdiv, 1, 0.95, &tree[3], &asldr},
	{Hdiv, 1, 0.5, &tree[4], &tree[6]},
	{Vdiv, 1, 1.0/3.0, &rsldr, &tree[5]},
	{Vdiv, 1, 0.5, &gsldr, &bsldr},
	{Vdiv, 1, 1.0/3.0, &hsldr, &tree[7]},
	{Vdiv, 1, 0.5, &ssldr, &vsldr}
};

Elementile tree[8] = {
	{&parts[0], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard},
	{&parts[1], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard},
	{&parts[2], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard},
	{&parts[3], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard},
	{&parts[4], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard},
	{&parts[5], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard},
	{&parts[6], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard},
	{&parts[7], guipartinit, guipartresize, guipartupdate, guipartmouse, guipartkeyboard}
};

Elementile *root = &tree[0];

char *buttons3[] = {"Send", "Exit", nil};
Menu menu3 = {buttons3};

#pragma varargck type "φ" double
/* 
 * lazy man's double to hex fmt
 * φ for φloat
 */
int
φfmt(Fmt *f)
{
	double d;
	char fmt[64];
	d = va_arg(f->args, double);
	snprint(fmt, 64, "%%%dlX", f->width); //right justification
	return fmtprint(f, fmt, (long)d);
}

/* resolve functions consolidate differences of global color models */
void
resolvecolor(void)
{
	rgb = colortorgb(color);
	alpha = color & 0xFF;
	hsvd = rgbtohsvd(rgb);
}

void
resolvergba(void)
{
	color = setalpha(rgbtocolor(rgb), alpha);
	hsvd = rgbtohsvd(rgb);
}

void
resolvehsvd(void)
{
	rgb = hsvdtorgb(hsvd);
	color = setalpha(rgbtocolor(rgb), alpha);
}

/* common event actions */
void
setmap(void)
{
	hsva.hsv = hsvd;
	hsvmp.update(&hsvmp);
}

void
setpick(void)
{
	spa.color = color;
	scrpk.update(&scrpk);
}

void
sethsvsliders(void)
{
	ha.val = hsvd.hue;
	hsldr.update(&hsldr);
	sa.val = hsvd.sat;
	ssldr.update(&ssldr);
	va.val = hsvd.val;
	vsldr.update(&vsldr);
}

void
setrgbsliders(void)
{
	ra.val = rgb.red;
	rsldr.update(&rsldr);
	ga.val = rgb.green;
	gsldr.update(&gsldr);
	ba.val = rgb.blue;
	bsldr.update(&bsldr);
}

void
setsliders(void)
{
	setrgbsliders();
	sethsvsliders();
}

/* events */
void
mapevent(HSVd h)
{
	hsvd = h;
	resolvehsvd();
	setpick();
	setsliders();
}

void
pickevent(ulong c)
{
	color = c;
	resolvecolor();
	setmap();
	setsliders();
	aa.val = alpha;
	asldr.update(&asldr);
}

void
allevent(ulong c)
{
	color = c;
	pickevent(c);
	setpick();
}

void
commonrgb(void)
{
	resolvergba();
	setmap();
	setpick();
	sethsvsliders();
}

void
redevent(double val)
{
	rgb.red = (ulong)(val + 0.5);
	commonrgb();
}

void
greenevent(double val)
{
	rgb.green = (ulong)(val + 0.5);
	commonrgb();
}

void
blueevent(double val)
{
	rgb.blue = (ulong)(val + 0.5);
	commonrgb();
}

void
commonhsv(void)
{
	resolvehsvd();
	setmap();
	setpick();
	setrgbsliders();
}

void
hueevent(double val)
{
	hsvd.hue = val;
	commonhsv();
}

void
satevent(double val)
{
	hsvd.sat = val;
	commonhsv();
}

void
valevent(double val)
{
	hsvd.val = val;
	commonhsv();
}

void
alphaevent(double val)
{
	alpha = (ulong)(val + 0.5);
	resolvergba();
	setpick();
}


vlong startt, stopt;
#define STARTTIME() startt = nsec()
#define STOPTIME() stopt = nsec(); \
	fprint(2, "%lld\n", stopt-startt)

void
dogetwindow(void)
{
	if(getwindow(display, Refnone) < 0)
		sysfatal("Cannot reconnect to display: %r");
	draw(screen, screen->r, display->black, nil, ZP);
}

void
usage(void)
{
	fprint(2, "usage: %s [-p] [-c 0xRRGGBBAA]\n", argv0);
	threadexitsall("usage");
}


void
threadmain(int argc, char **argv)
{
	Keyboardctl *kctl;
	Rune r;
	Mousectl *mctl;
	Mouse m;
	ulong incolor;
	Channel *fsc;
	char *mtpt, *srv;
	int c9p;
	
	mtpt = nil;
	srv = nil;
	c9p = 0;
	incolor = 0xFFFFFFFF;
	ARGBEGIN{
	case 'p':
		pflag++;
		break;
	case 'c':
		incolor = strtocolor(EARGF(usage()));
		if(incolor == DNotacolor)
			incolor = 0xFFFFFFFF;
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	case 's':
		srv = EARGF(usage());
		break;
	case 'D':
		c9p++;
		break;
	default:
		usage();
	}ARGEND;

	fmtinstall(L'φ', φfmt);
	
	if(initdraw(nil, nil, argv0) < 0)
		sysfatal("%r");
	if((mctl = initmouse(nil, screen)) == nil)
		sysfatal("%r");
	if((kctl = initkeyboard(nil)) == nil)
		sysfatal("%r");
	fsc = init9pfs(mtpt, srv, c9p);
	spa.mctl = mctl;
	hsva.mctl = mctl;
	
	color = incolor;
	alpha = color & 0xFF;
	rgb = colortorgb(unsetalpha(incolor));
	hsvd = rgbtohsvd(rgb);
	
	hsva.hsv = hsvd;
	spa.color = color;
	ra.val = rgb.red;
	ga.val = rgb.green;
	ba.val = rgb.blue;
	ha.val = hsvd.hue;
	sa.val = hsvd.sat;
	va.val = hsvd.val;
	aa.val = alpha;
	
	root->init(root);
	dogetwindow();
	root->resize(root, screen->r);
	flushimage(display, 1);
	
	enum { MOUSE, RESIZE, KEYS, FS, NONE };
	Alt alts[] = {
		[MOUSE] =  {mctl->c, &m, CHANRCV},
		[RESIZE] =  {mctl->resizec, nil, CHANRCV},
		[KEYS] = {kctl->c, &r, CHANRCV},
		[FS] = {fsc, &incolor, CHANRCV},
		[NONE] =  {nil, nil, CHANEND}
	};
	
	/* DEBUG frame counter */
	/*
	Rectangle rekt;
	ulong frames = 0;
	char ftext[10];
	*/
	STARTTIME();
	for(;;){
		/* DEBUG frame counter */
		/*
		snprint(ftext, sizeof(ftext), "%uld", (ulong)frames);
		rekt = Rpt(screen->r.min, addpt(screen->r.min, stringsize(display->defaultfont, ftext)));
		draw(screen, rekt, display->white, nil, ZP);
		string(screen, screen->r.min, display->black, ZP, display->defaultfont, ftext);
		frames++;
		flushimage(display, 1);
		*/
		
		switch(alt(alts)){
		case MOUSE:
			if(m.buttons == 4){
				switch(menuhit(3, mctl, &menu3, nil)){
				case 0:
					print("0x%08ulX\n", color);
					if(pflag == 0)
						threadexitsall(nil);
					break;
				case 1:
					threadexitsall(nil);
					break;
				}
				break;
			}
			if(root->mouse(root, m) == 1)
				flushimage(display, 1);
			break;
		case RESIZE:
			dogetwindow();
			root->resize(root, screen->r);
			flushimage(display, 1);
			break;
		case KEYS:
			if(r == Kdel)
				threadexitsall(nil);
			break;
		case FS:
			if(incolor == color)
				break;
			allevent(incolor);
			flushimage(display, 1);
			break;
		case NONE:
			print("I'm a woodchuck, not a woodchucker! (thanks for playing)\n");
			break;
		}
	}
}

A  => mkfile +41 -0
@@ 1,41 @@
</$objtype/mkfile

MAN=/sys/man
BIN=/$objtype/bin
HFILES=com.h components.h elementile.h
OFILES=com.$O guipart.$O makeu.$O hsvmap.$O screenpick.$O slider.$O fs.$O
TARG=$BIN/makeu
MANFILES=$MAN/1/makeu

default:V: all

all:V: $O.out

install:V: $TARG

allall:V:
	for(objtype in $CPUS)
		mk all

installall:V:
	for(objtype in $CPUS)
		mk install

clean:V:
	rm -f *.[$OS] [$OS].out $CLEANFILES

man:V: $MANFILES

$TARG: $O.out
	cp $prereq $target

$O.out: $OFILES
	$LD $LDFLAGS -o $target $prereq

%.$O: $HFILES

%.$O: %.c
	$CC $CFLAGS $stem.c

$MAN/([0-9])/([0-9A-Za-z]+):R: \2.\1.man
	cp $prereq $target

A  => screenpick.c +219 -0
@@ 1,219 @@
/* Amavect! */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <cursor.h>
#include <thread.h>
#include <keyboard.h>
#include <mouse.h>
#include "elementile.h"
#include "com.h"
#include "components.h"

static
Cursor icursethee = {
	{-7, -7},
	{0x07, 0xC0, 0x1F, 0xF0, 0x3F, 0xF8, 0x7F, 0xFC,
	 0x7B, 0xBC, 0xF0, 0x1E, 0xF8, 0x3E, 0xF8, 0x3E,
	 0xF8, 0x3E, 0xF0, 0x1E, 0x7B, 0xBC, 0x7F, 0xFC,
	 0x3F, 0xF8, 0x1F, 0xF0, 0x07, 0xC0, 0x00, 0x00, },
	{0x00, 0x00, 0x07, 0xC0, 0x1F, 0xF0, 0x39, 0x38,
	 0x30, 0x18, 0x60, 0x0C, 0x60, 0x0C, 0x70, 0x1C,
	 0x60, 0x0C, 0x60, 0x0C, 0x30, 0x18, 0x39, 0x38,
	 0x1F, 0xF0, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x00, }
};

/* Returns the big endian color of the 1x1 pixel image */
static
ulong
getpixelcolor(Screenpick *sp)
{
	uchar data[4];
	Image *pixel;
	pixel = sp->pixel;
	unloadimage(pixel, pixel->r, data, sizeof(data));
	return (data[3]<<24) | (data[2]<<16) | (data[1]<<8) | 0xFF;
}

/* Regenerate image */
static
void
redraw(Screenpick *sp)
{
	char hex[9];
	Point strsize, pfont, pfont2;
	Rectangle r, r2, r3;
	int i, dx, dy;
	
	r = sp->r;
	dx = Dx(r), dy = Dy(r);
	snprint(hex, 9, "%08ulX", sp->color);
	strsize = stringsize(display->defaultfont, hex);
	pfont = addpt(divpt(subpt(subpt(r.max, r.min), strsize), 2), r.min);
	pfont2 = pfont;
	pfont.y -= 3*strsize.y/2;
	pfont2.y -= strsize.y/2;
	
	draw(screen, r, display->black, nil, ZP);
	for(i = 0; i < 2; i++){
		r2.min.x = dx*i/2 + r.min.x;
		r2.min.y = dy/2 + r.min.y;
		r2.max.x = r2.min.x + dx/4;
		r2.max.y = r2.min.y + dy/4;
		r3.min = r2.max;
		r3.max.x = dx*(i+1)/2 + r.min.x;
		r3.max.y = r.max.y;
		draw(screen, r2, display->white, nil, ZP);
		draw(screen, r3, display->white, nil, ZP);
	}
	draw(screen, r, sp->pixel, nil, ZP);
	string(screen, pfont, display->black, ZP, display->defaultfont, hex);
	string(screen, pfont2, display->white, ZP, display->defaultfont, hex);
}

/*
 * Open /dev/screen for reading. This is done for optimization by reading one line at a time.
 * Then, calculate minimum size using the default font.
 */
Point
screenpickinit(Elementile *e)
{
	Screenpick *sp;
	Point strsize;
	char hex[9];
	sp = e->aux;
	sp->state = 0;
	sp->screenfd = open("/dev/screen", OREAD);
	if(sp->screenfd < 0)
		sysfatal("%r");
	sp->screenline = nil;
	sp->linesize = 0;
	sp->linebuf = nil;
	sp->pixel = allocimage(display, Rect(0,0,1,1), RGBA32, 1, sp->color);
	if(sp->pixel == nil)
		sysfatal("download more ram plz: %r");
	snprint(hex, sizeof(hex), "%08ulX", setalpha(sp->color, sp->color & 0xFF));
	strsize = stringsize(display->defaultfont, hex);
	strsize.y *= 4;
	return strsize;
}

/*
 * Update the size, refresh screenline in case of screen resolution resize, and redraw.
 */
void
screenpickresize(Elementile *e, Rectangle r)
{
	Screenpick *sp;
	ulong chantype;
	char chanstr[12], minxstr[12], minystr[12], maxxstr[12], maxystr[12];
	Rectangle rect;
	
	sp = e->aux;
	sp->r = r;
	
	freeimage(sp->screenline);
	free(sp->linebuf);
	
	/* 
	 * Read screenfd directly so a single scanline can be read.
	 * Potential for a crash if /dev/screen is bind-ed with a malicious file.
	 * Potential for usability on named images (draw(3)) or other /dev/wsys/window.
	 * Need a second opinion.
	 */
	if(pread(sp->screenfd, chanstr, 12, 0) < 12)
		sysfatal("%r");
	if(pread(sp->screenfd, minxstr, 12, 12) < 12)
		sysfatal("%r");
	if(pread(sp->screenfd, minystr, 12, 2*12) < 12)
		sysfatal("%r");
	if(pread(sp->screenfd, maxxstr, 12, 3*12) < 12)
		sysfatal("%r");
	if(pread(sp->screenfd, maxystr, 12, 4*12) < 12)
		sysfatal("%r");
	chanstr[11] = 0;
	minxstr[11] = 0;
	minystr[11] = 0;
	maxxstr[11] = 0;
	maxystr[11] = 0;
	chantype = strtochan(chanstr);
	rect.min.x = (int)strtol(minxstr, nil, 0);
	rect.min.y = (int)strtol(minystr, nil, 0);
	rect.max.x = (int)strtol(maxxstr, nil, 0);
	rect.max.y = (int)strtol(maxystr, nil, 0);
	
	sp->screenline = allocimage(display, Rect(0,0,rect.max.x,1), chantype, 0, DWhite);
	sp->linesize = bytesperline(rect, chantodepth(chantype));
	sp->linebuf = malloc(sp->linesize);
	if(sp->screenline == nil || sp->linebuf == nil)
		sysfatal("download more ram plz: %r");
	
	redraw(sp);
}

/*
 * Load and redraw the current color.
 */
int
screenpickupdate(Elementile *e)
{
	Screenpick *sp;
	uchar col[4];
	sp = e->aux;
	colortoarray(col, sp->color);
	loadimage(sp->pixel, sp->pixel->r, col, 4);
	redraw(sp);
	return 1;
}

/*
 * Read a single scanline (for speed) and load it into the new pixel. 
 * However, do not send an update until the mouse is released.
 * When the mouse is released, get the pixel at that x location.
 */
int
screenpickmouse(Elementile *e, Mouse m)
{
	Screenpick *sp;
	Rectangle newpixr;
	Image *pixel, *screenline;
	sp = e->aux;
again:	
	switch(sp->state){
	case 0:
		if(m.buttons & 1){
			setcursor(sp->mctl, &icursethee);
			sp->state = 1;
			goto again;
		}
		break;
	case 1:
		pread(sp->screenfd, sp->linebuf, sp->linesize, m.xy.y*sp->linesize+12*5);
		pixel = sp->pixel;
		screenline = sp->screenline;
		newpixr = sp->r;
		newpixr.min = addpt(newpixr.min, Pt(Dx(newpixr)/2,Dy(newpixr)/2));
		loadimage(screenline, screenline->r, sp->linebuf, sp->linesize);
		draw(pixel, pixel->r, screenline, nil, Pt(m.xy.x, 0));
		draw(screen, newpixr, pixel, nil, ZP);
		if(m.buttons == 0){
			setcursor(sp->mctl, nil);
			sp->color = getpixelcolor(sp);
			redraw(sp);
			sp->onchange(sp->color);
			sp->state = 0;
		}
		return 1;
		break;
	}
	return 0;
}

/*
 * insert functionality here
 */
int
screenpickkeyboard(Elementile*, Rune)
{
	return 0;
}

A  => slider.c +189 -0
@@ 1,189 @@
/* Amavect! */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <cursor.h>
#include <thread.h>
#include <keyboard.h>
#include <mouse.h>
#include "elementile.h"
#include "com.h"
#include "components.h"

static
Point
rotpt(Point p)
{
	int t;
	t = p.x;
	p.x = -p.y;
	p.y = t;
	return p;
}

static
void
redraw(Slider *sl)
{
	Rectangle r;
	char txt[32];
	Point pfont;
	int off;
	r = sl->r;
	draw(screen, sl->r, sl->bg, nil, ZP);
	if(sl->ishoriz){
		off = (int)(Dx(r) * sl->val / (sl->max - sl->min));
		draw(screen, r, sl->bka, nil, Pt(-off, 0));
		draw(screen, r, sl->wta, nil, Pt(-off, -Dy(r)));
	}else{
		off = Dy(r) - (int)(Dy(r) * sl->val / (sl->max - sl->min));
		draw(screen, r, sl->bka, nil, Pt(0, -off));
		draw(screen, r, sl->wta, nil, Pt(-Dx(r), -off));
	}
	snprint(txt, 64, sl->fmt, sl->val);
	pfont = stringsize(display->defaultfont, txt);
	pfont.x = (Dx(r) - pfont.x)/2 + r.min.x;
	pfont.y = (Dy(r) - pfont.y)/2 + r.min.y;
	string(screen, pfont, display->black, ZP, display->defaultfont, txt);
}

/*
 * Allocate the images and return the minimum size based on the defaultfont.
 */
Point
sliderinit(Elementile *e)
{
	Slider *sl;
	int n;
	Point pfont;
	char txt[32];
	sl = e->aux;
	sl->bg = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0xAAAAAAFF);
	sl->fg = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0x333333FF);
	if(sl->bg == nil || sl->fg == nil)
		sysfatal("get more ram dude: %r");
	sl->bka = nil;
	sl->wta = nil;
	n = snprint(txt, 64, sl->fmt, sl->val);
	pfont = stringsize(display->defaultfont, txt);
	pfont.x = pfont.x + 2*pfont.x/n;
	pfont.y *= 2;
	return pfont;
}

/*
 * Set the new Rectangle, regenerate indicator arrows, and redraw.
 */
void
sliderresize(Elementile *e, Rectangle r)
{
	Slider *sl;
	int n, i;
	char txt[32];
	Point pfont;
	Point ptb[3] = {{1,0},{0,1},{0,-1}};
	Point ptw[3] = {{-1,0},{0,1},{0,-1}};
	Rectangle rb, rw;
	
	sl = e->aux;
	sl->r = r;
	
	freeimage(sl->bka);
	freeimage(sl->wta);
	n = snprint(txt, 64, sl->fmt, sl->val);
	pfont = stringsize(display->defaultfont, txt);
	if(sl->ishoriz){
		pfont.x /= n;
		pfont.y = (Dy(r) - pfont.y)/2;
		for(i = 0; i < nelem(ptb); i++){
			ptb[i] = rotpt(ptb[i]);
			ptw[i] = rotpt(ptw[i]);
			ptb[i].x *= pfont.x;
			ptw[i].x *= pfont.x;
			ptb[i].y *= pfont.y;
			ptw[i].y *= pfont.y;
		}
		rb = Rect(-pfont.x, 0, pfont.x, pfont.y);
		rw = Rect(-pfont.x, -pfont.y, pfont.x, 0);
	}else{
		pfont.x = (Dx(r) - pfont.x)/2;
		pfont.y /= 2;
		for(i = 0; i < nelem(ptb); i++){
			ptb[i].x *= pfont.x;
			ptw[i].x *= pfont.x;
			ptb[i].y *= pfont.y;
			ptw[i].y *= pfont.y;
		}
		rb = Rect(0, -pfont.y, pfont.x, pfont.y);
		rw = Rect(-pfont.x, -pfont.y, 0, pfont.y);
	}
	sl->bka = allocimage(display, rb, RGBA32, 0, DTransparent);
	sl->wta = allocimage(display, rw, RGBA32, 0, DTransparent);
	if(sl->bka == nil || sl->wta == nil)
		sysfatal("get more ram bruh: %r");
	fillpoly(sl->bka, ptb, nelem(ptb), 1, display->black, ZP);
	fillpoly(sl->wta, ptw, nelem(ptw), 1, display->white, ZP);
	redraw(sl);
}

/*
 * Sanitize value and then redraw.
 */
int
sliderupdate(Elementile *e)
{
	Slider *sl;
	sl = e->aux;
	if(sl->val < sl->min)
		sl->val = sl->min;
	else if(sl->val > sl->max)
		sl->val = sl->max;
	redraw(sl);
	return 1;
}

/*
 * Take mouse 1 and scroll wheels, but do not go above max or below min.
 */
int
slidermouse(Elementile *e, Mouse m)
{
	Slider *sl;
	Rectangle rect;
	Point pt;
	sl = e->aux;
	if(m.buttons == 1){
		rect = sl->r;
		pt = ptclip(m.xy, rect);
		if(sl->ishoriz)
			sl->val = (sl->max - sl->min) * (pt.x - rect.min.x) / (rect.max.x - rect.min.x - 1);
		else
			sl->val = (sl->max - sl->min) * (rect.max.y - 1 - pt.y) / (rect.max.y - rect.min.y - 1);
		redraw(sl);
		sl->onchange(sl->val);
	}else if(m.buttons == 8){
		sl->val += sl->inc;
		if(sl->val > sl->max)
			sl->val = sl->max;
		redraw(sl);
		sl->onchange(sl->val);
	}else if(m.buttons == 16){
		sl->val -= sl->inc;
		if(sl->val < sl->min)
			sl->val = sl->min;
		redraw(sl);
		sl->onchange(sl->val);
	}
	if(m.buttons != 0)
		return 1;
	return 0;
}

/*
 * Insert functionality here.
 */
int
sliderkeyboard(Elementile*, Rune)
{
	return 0;
}