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