shithub: picker

Download patch

ref: f0f5833c9acc6cc7037c72e5c738247178f74314
parent: 6fa8382714bde5858e7ae505ee3ac0af127be74b
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Sun Mar 15 20:38:59 EDT 2020

rewrite to use stdin to get colors; support loading themes

--- a/picker.c
+++ b/picker.c
@@ -5,6 +5,8 @@
 #include <memdraw.h>
 #include <mouse.h>
 #include <keyboard.h>
+#include <bio.h>
+#include <plumb.h>
 #include "hsluv.h"
 
 #define MAX(a,b) ((a)>=(b)?(a):(b))
@@ -26,11 +28,14 @@
 typedef struct Space Space;
 
 struct Color {
-	Rectangle r;
+	char *id;
+	Image *i;
 	double v[4];
 	double rgba[4];
-	ulong u;
-	Image *i;
+	Rectangle r;
+	u32int u;
+	int nchan;
+	Color *next, *prev;
 };
 
 struct Space {
@@ -112,8 +117,8 @@
 static char *menu2i[nelem(spaces)+4];
 static Menu menu2 = { .item = menu2i };
 
-static Color *colors;
-static int ncolors, curcolor, nchan;
+static Color *colors, *color, *last;
+static int ncolors;
 static Rectangle srects[3];
 static Space *space;
 static Image *bg;
@@ -125,6 +130,22 @@
 	return D2C(rgba[0])<<24 | D2C(rgba[1])<<16 | D2C(rgba[2])<<8 | D2C(rgba[3]);
 }
 
+#pragma varargck type "©" Color*
+static int
+colorfmt(Fmt *f)
+{
+	char s[16];
+	Color *c;
+
+	c = va_arg(f->args, Color*);
+	if (c->nchan < 4)
+		sprint(s, "%06ux", c->u>>8);
+	else
+		sprint(s, "%08ux", c->u);
+
+	return fmtstrcpy(f, s);
+}
+
 static Image *
 slider(int si, int w)
 {
@@ -146,10 +167,10 @@
 	}
 	b = buf[si];
 
-	memmove(c, colors[curcolor].v, sizeof(c));
+	memmove(c, color->v, sizeof(c));
 	if (space->single) {
 		memset(c, 0, 3*sizeof(double));
-		c[si] = colors[curcolor].v[si];
+		c[si] = color->v[si];
 	}
 	dt = space->max[si] / w;
 	mi = c[si] / dt;
@@ -182,16 +203,11 @@
 	return s;
 }
 
-static int
-color2str(Color *c, char *s)
-{
-	return nchan > 3 ? sprint(s, "%08lux", c->u) : sprint(s, "%06lux", c->u>>8);
-}
-
 static void
 redraw(void)
 {
 	Rectangle r, cr;
+	Color *c;
 	Image *im;
 	int i, colw;
 
@@ -206,7 +222,7 @@
 	r.max.y = r.min.y + Sliderheight;
 
 	/* sliders */
-	for (i = 0; i < nchan; i++) {
+	for (i = 0; i < color->nchan; i++) {
 		srects[i] = r;
 		im = slider(i, Dx(r));
 		draw(screen, r, bg, nil, ZP);
@@ -215,27 +231,27 @@
 		r.max.y += Sliderheight + Offset;
 	}
 
+	/* current color is changed on redraw, always */
+	freeimage(color->i);
+	color->i = nil;
+
 	/* palette */
 	colw = MIN(Sliderheight, (Dx(r)-(ncolors-1)*Offset)/ncolors);
 	cr = r;
 	cr.min.x += (Dx(cr) - colw*ncolors - (ncolors-1)*Offset) / 2;
-	for (i = 0; i < ncolors; i++) {
-		if (i == curcolor) {
-			freeimage(colors[i].i);
-			colors[i].i = nil;
-		}
-		if (colors[i].i == nil) {
-			colors[i].i = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(colors[i].u | 0xff, colors[i].u & 0xff));
-			if (colors[i].i == nil)
+	for (c = colors; c != nil; c = c->next) {
+		if (c->i == nil) {
+			if ((c->i = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(c->u|0xff, c->u&0xff))) == nil)
 				sysfatal("allocimage: %r");
 		}
 
 		cr.max.x = cr.min.x + colw;
 		draw(screen, cr, bg, nil, ZP);
-		draw(screen, cr, colors[i].i, nil, ZP);
-		colors[i].r = insetrect(cr, -3);
-		if (i == curcolor)
-			border(screen, colors[i].r, 2, display->black, ZP);
+		draw(screen, cr, c->i, nil, ZP);
+		border(screen, cr, 1, display->black, ZP);
+		c->r = insetrect(cr, -3);
+		if (c == color)
+			border(screen, c->r, 3, display->black, ZP);
 		cr.min.x += colw + Offset;
 	}
 	r.min.y += Sliderheight + Offset;
@@ -243,12 +259,16 @@
 	/* current color */
 	r.max.y = screen->r.max.y - Offset;
 	draw(screen, r, bg, nil, ZP);
-	draw(screen, r, colors[curcolor].i, nil, ZP);
+	draw(screen, r, color->i, nil, ZP);
 
+	/* current color id */
+	r.min.x += Dx(r)/2 - stringwidth(font, color->id)/2;
+	stringbg(screen, r.min, display->white, ZP, font, color->id, display->black, ZP);
+
 	/* current color in hex */
-	color2str(&colors[curcolor], hex);
-	r.min.x += Dx(r)/2 - stringwidth(font, hex)/2;
-	r.min.y += Dy(r)/2 - font->height/2;
+	r.min.y += font->height;
+	sprint(hex, "%©", color);
+	r.min.x = screen->r.min.x + Dx(screen->r)/2 - stringwidth(font, hex)/2;
 	stringbg(screen, r.min, display->white, ZP, font, hex, display->black, ZP);
 
 	flushimage(display, 1);
@@ -263,7 +283,7 @@
 	print("usage: %s [-e] [-", argv0);
 	for (i = 0; i < nelem(spaces); i++)
 		print("%c", spaces[i].opt);
-	print("] [-a] rrggbb[aa] ...\n");
+	print("]\n");
 
 	threadexitsall("usage");
 }
@@ -293,17 +313,14 @@
 }
 
 static int
-printcolor(void)
+printcolor(Color *c)
 {
+	char s[64];
 	int n;
-	char buf[16];
 
-	if (nchan < 4)
-		n = sprint(buf, "%d %06lux\n", curcolor, colors[curcolor].u>>8);
-	else
-		n = sprint(buf, "%d %08lux\n", curcolor, colors[curcolor].u);
+	n = snprint(s, sizeof(s), "%s\t%©\n", c->id, c);
 
-	return write(1, buf, n) == n ? 0 : -1;
+	return write(1, s, n) == n ? 0 : -1;
 }
 
 static int
@@ -310,18 +327,25 @@
 str2color(char *s, Color *c)
 {
 	vlong v;
-	char *e;
-	int i;
+	char *a[3], *e;
+	int i, n;
 
-	if (strlen(s) != nchan*2) {
-		werrstr("wrong number of components");
+	if ((n = tokenize(s, a, nelem(a))) < 2) {
+		werrstr("columns: %d", n);
 		return -1;
 	}
-	if ((v = strtoll(s, &e, 16)) == 0 && (e == s || *e || v < 0)) {
-		werrstr("invalid color");
+	n = strlen(a[1]);
+	if (n != 6 && n != 8) {
+		werrstr("components: %d", n);
 		return -1;
 	}
-	if (nchan < 4) {
+	c->nchan = n == 6 ? 3 : 4;
+	if ((v = strtoll(a[1], &e, 16)) == 0 && (e == a[1] || *e || v < 0 || (n == 6 && v > 0xffffff) || (n == 8 && v > 0xffffffff))) {
+		werrstr("color: '%s'", a[1]);
+		return -1;
+	}
+	c->id = strdup(a[0]);
+	if (c->nchan < 4) {
 		v <<= 8;
 		v |= 0xff;
 	}
@@ -336,11 +360,89 @@
 	return 0;
 }
 
+static void
+readcolors(Biobuf *b)
+{
+	Color *c, *new;
+	char *s;
+	int i, n;
+
+	new = nil;
+	for (i = 1; (s = Brdstr(b, '\n', 1)) != nil; i++) {
+		if (new == nil)
+			new = calloc(1, sizeof(Color));
+
+		n = str2color(s, new);
+		free(s);
+
+		if (n != 0) {
+			fprint(2, "%d: %r\n", i);
+			continue;
+		}
+
+		for (c = colors; c != nil; c = c->next) {
+			if (strcmp(c->id, new->id) == 0) {
+				free(c->id);
+				new->prev = c->prev;
+				new->next = c->next;
+				memmove(c, new, sizeof(*c));
+				break;
+			}
+		}
+
+		if (c != nil)
+			continue;
+
+		if (last != nil)
+			last->next = new;
+		new->prev = last;
+		last = new;
+
+		if (colors == nil)
+			colors = new;
+		new = nil;
+		ncolors++;
+	}
+}
+
+static void
+loadtheme(char *filename)
+{
+	Biobuf *b;
+
+	if ((b = Bopen(filename, OREAD)) != nil) {
+		lockdisplay(display);
+		readcolors(b);
+		unlockdisplay(display);
+		Bterm(b);
+	}
+}
+
+static void
+plumbproc(void *)
+{
+	int f;
+	Plumbmsg *m;
+
+	threadsetname("plumb");
+	if ((f = plumbopen("picker", OREAD)) >= 0) {
+		while ((m = plumbrecv(f)) != nil) {
+			loadtheme(m->data);
+			redraw();
+			plumbfree(m);
+		}
+	}
+
+	threadexits(nil);
+}
+
 void
 threadmain(int argc, char **argv)
 {
 	Mousectl *mctl;
 	Keyboardctl *kctl;
+	Biobuf *b;
+	Color *c;
 	Rune r;
 	Mouse m;
 	Alt a[Numchan+1] = {
@@ -349,18 +451,15 @@
 		[Cresize] = { nil, nil, CHANRCV },
 		{ nil, nil, CHANEND },
 	};
-	Color *c;
 	int i, once;
 	ulong u;
 	char buf[16];
 
+	fmtinstall(L'©', colorfmt);
+
 	space = &spaces[0];
-	nchan = 3;
 	once = 0;
 	ARGBEGIN{
-	case 'a':
-		nchan = 4;
-		break;
 	case 'e':
 		once = 1;
 		break;
@@ -379,19 +478,18 @@
 		break;
 	}ARGEND
 
-	ncolors = argc;
-	if (ncolors < 1) {
-		fprint(2, "no colors specified\n");
+	if (argc > 1)
 		usage();
-	}
-	colors = calloc(ncolors, sizeof(Color));
-	for (i = 0; i < ncolors; i++) {
-		if (str2color(argv[i], &colors[i]) != 0) {
-			fprint(2, "'%s': %r\n", argv[i]);
-			usage();
-		}
-	}
 
+	b = argc == 1 ? Bopen(argv[0], OREAD) : Bfdopen(0, OREAD);
+	if (b == nil)
+		sysfatal("no colors: %r");
+	readcolors(b);
+	if (ncolors < 1)
+		sysfatal("no colors");
+	Bterm(b);
+	color = colors;
+
 	for (i = 0; i < nelem(spaces); i++)
 		menu2i[i] = spaces[i].name;
 	menu2i[i++] = "snarf";
@@ -409,15 +507,14 @@
 	a[Cresize].c = mctl->resizec;
 	display->locking = 1;
 	unlockdisplay(display);
-
-	if (nchan > 3)
-		loadbg();
-
+	loadbg();
 	redraw();
 
+	proccreate(plumbproc, nil, mainstacksize);
+
 	for (;;) {
 next:
-		c = &colors[curcolor];
+		c = color;
 
 		switch (alt(a)) {
 		case -1:
@@ -426,11 +523,13 @@
 		case Ckey:
 			switch (r) {
 			case Kleft:
-				curcolor = MAX(0, curcolor-1);
+				if (c->prev != nil)
+					color = c->prev;
 				redraw();
 				break;
 			case Kright:
-				curcolor = MIN(ncolors-1, curcolor+1);
+				if (c->next != nil)
+					color = c->next;
 				redraw();
 				break;
 			case Kdel:
@@ -441,27 +540,29 @@
 		case Cmouse:
 			if (m.buttons == 1) {
 				m.xy.x = MAX(screen->r.min.x, MIN(screen->r.max.x, m.xy.x));
-				for (i = 0; i < nchan; i++) {
+				for (i = 0; i < c->nchan; i++) {
 					Rectangle r = srects[i];
 					r.max.x += 1;
-					if (ptinrect(m.xy, r)) {
-						c->v[i] = MIN(space->max[i], (double)(m.xy.x - r.min.x) * space->max[i]/(double)(Dx(r)-1));
-						c->rgba[3] = c->v[3];
+					if (!ptinrect(m.xy, r))
+						continue;
+
+					c->v[i] = MIN(space->max[i], (double)(m.xy.x - r.min.x) * space->max[i]/(double)(Dx(r)-1));
+					c->rgba[3] = c->v[3];
 changed:
-						space->torgb(c->v, c->rgba);
-						u = rgba2u(c->rgba);
-						if (c->u != u) {
-							c->u = u;
-							if (!once)
-								printcolor();
-						}
-						redraw();
-						goto next;
+					space->torgb(c->v, c->rgba);
+					u = rgba2u(c->rgba);
+					if (c->u != u) {
+						c->u = u;
+						if (!once)
+							printcolor(c);
 					}
+					redraw();
+					goto next;
 				}
-				for (i = 0; i < ncolors; i++) {
-					if (ptinrect(m.xy, colors[i].r)) {
-						curcolor = i;
+
+				for (c = colors; c != nil; c = c->next) {
+					if (ptinrect(m.xy, c->r)) {
+						color = c;
 						redraw();
 						goto next;
 					}
@@ -482,17 +583,14 @@
 					if (i == nelem(spaces)) {
 						write(f, hex, strlen(hex));
 					} else {
-						for (i = 0; i < ncolors; i++) {
-							write(f, buf, color2str(&colors[i], buf));
-							if (i != ncolors-1)
-								write(f, " ", 1);
-						}
+						for (c = colors; c != nil; c = c->next)
+							fprint(f, "%s\t%©\n", c->id, c);
 					}
 					close(f);
 				}
 			} else if (m.buttons == 2) {
 				strcpy(buf, hex);
-				if (enter(nchan < 4 ? "rgb:" : "rgba:", buf, sizeof(buf), mctl, kctl, nil) > 0) {
+				if (enter(c->nchan < 4 ? "rgb:" : "rgba:", buf, sizeof(buf), mctl, kctl, nil) > 0) {
 					u = c->u;
 					if (str2color(buf, c) == 0 && c->u != u) {
 						c->u = ~c->u; /* just for the update to kick in */
@@ -511,13 +609,8 @@
 
 end:
 	if (once) {
-		for (i = 0; i < ncolors; i++) {
-			if (nchan < 4)
-				print("%06lux ", colors[i].u>>8);
-			else
-				print("%08lux ", colors[i].u);
-		}
-		print("\n");
+		for (c = colors; c != nil; c = c->next)
+			print("%s\t%©\n", c->id, c);
 	}
 
 	threadexitsall(nil);
--- a/picker.man
+++ b/picker.man
@@ -6,11 +6,10 @@
 [
 .I -e
 ] [
-.I -a
-] [
 .I -slr
+] [
+.I FILE
 ]
-COLOR ...
 .SH DESCRIPTION
 .I Picker
 is a tool designed to mainly be used by other programs in order to
@@ -17,11 +16,8 @@
 change a color palette dynamically, showing changes in real time.
 Colors are supplied as command line argument, each is encoded as
 .I RGB
-(or
+or
 .I RGBA
-if
-.I -a
-option was used)
 in hex form, i.e.
 .I ff0000
 for red in
@@ -32,6 +28,15 @@
 .I RGBA
 mode.
 .PP
+If the filename wasn't specified,
+.I picker
+expects data to be supplied on its standard input.  The format in both
+cases is the same:
+.I id
+of the color first, then hex value of the color as the second column.
+.I Id
+is used for color updates written to stdout.
+.PP
 Different color spaces are accessible through right button menu, or as
 command line arguments:
 .TP
@@ -44,18 +49,34 @@
 .B -r
 RGB.
 .PP
-Switching between palette can be done by either a mouse click, or
-left/right arrows on the keyboard. With the middle button click you
-can enter color as
+Switching between colors in the palette can be done by either a mouse
+click, or left/right arrows on the keyboard.  With the middle button
+click you can enter color as
 .I RGB(A)
 manually.
 .PP
-For each change of a color, picker writes to stdout its index and the
-new color value, separated by a single space.  Pass
+For each change of a color, picker writes to stdout its
+.I id
+and the new color value, separated by a single space.  Pass
 .I -e
 option if you want
 .I picker
-to print all the colors only once you exit the program, as a single
-line.
+to print all the colors only once you exit the program.
+.SH PLUMBING
+Themes can be loaded into a running
+.I picker
+through plumber rule. The following assumes themes have
+.I ".theme"
+as its extension.
+.EX
+type	is	text
+data	matches	'[a-zA-Z¡-￿0-9_\-.,/]+'
+data	matches	'([a-zA-Z¡-￿0-9_\-.,/]+)\.theme'
+arg	isfile	$0
+data	set	$file
+plumb	to	picker
+plumb	start window picker -e $file
+.EE
+.PP
 .SH SOURCE
 https://github.com/ftrvxmtrx/picker
binary files a/picker.png b/picker.png differ