/* * TODO: * * 1. button doesn't change dimension after * setstate() * 2. graph widget * 3. input widget * 4. menuhit widget * 5. vslider * 6. keyboard mapping */ #include #include #include #include #include #include #include #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) typedef struct Widget Widget; typedef struct Object Object; enum { Maxstack = 32*1024, Argsmax = 16, }; enum { Wborder = 4, Woutline = 1, Wpad = 8, Wboxw = 16, Wboxh = 16, Wmaxslide = 127, Wsliderw = Wmaxslide + Wboxw, Wsliderh = 16, }; struct Object { Object *next; int type; Rectangle bbox; Point dim; Rectangle r; char *name; char *text, *value; int on, state; Image *graph; int nl; }; Mousectl *mctl; Keyboardctl *kctl; Font *font; Image *col[8]; Object *objects, *lastobj; Biobuf bin; Channel *linechan; int iflag, wflag; enum { Gnl, Gset, Gexit, Gget, Gname, Gresize, Gbutton, Ggraph, Gslider, Gcheckbox, Gswitch, Gtext, Gunknown, }; char *keywords[] = { "nl", "set", "exit", "get", "name", "resize", "button", "graph", "slider", "checkbox", "switch", "text", 0 }; void redrawall(void); /* * Utils */ void goodbye(void) { closedisplay(display); closekeyboard(kctl); closemouse(mctl); threadexitsall(nil); } void xprint(char *fmt, ...) { va_list ap; int nw; va_start(ap, fmt); nw = vfprint(1, fmt, ap); va_end(ap); if(nw <= 0) goodbye(); } Image * color(int col) { return allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, (col << 8) | 0xff); } void flush(void) { flushimage(display, 1); } void missingarg(void) { xprint("error missing argument\n"); } int slideclamp(int x) { x = MAX(x, 0); x = MIN(x, Wmaxslide); return x; } /* * draw utils */ void setlabel(char *label) { int fd; fd = open("/dev/label", OWRITE); if(fd < 0) return; write(fd, label, strlen(label)); close(fd); } void resize(Point p) { int fd; fd = open("/dev/wctl", OWRITE); if(fd < 0) return; fprint(fd, "resize -dx %d -dy %d\n", p.x + 2*Borderwidth, p.y + 2*Borderwidth); close(fd); } /* * Widget logic */ Image * loadgraph(Image *g, char *data) { char *p; int y, i; int w, h; Point a, b; draw(g, g->r, col[0], 0, ZP); w = Dx(g->r); h = Dy(g->r); p = data; for(i=0; iname = strdup(argv[1]); obj->type = type; switch(type){ case Ggraph: if(argc < 4) goto fewarg; obj->dim = Pt(atoi(argv[2]), atoi(argv[3])); obj->graph = allocimage(display, Rpt(ZP, obj->dim), screen->chan, 0, 0xffffffff); break; case Gbutton: obj->text = strdup(argc < 3 ? obj->name : argv[2]); obj->dim = addpt(stringsize(font, obj->text), Pt(2*Wborder, 2*Wborder)); obj->r = insetrect(Rpt(ZP, obj->dim), Wborder); break; case Gtext: obj->text = strdup(argc < 3 ? obj->name : argv[2]); obj->dim = stringsize(font, obj->text); break; case Gslider: obj->dim = Pt(Wsliderw, Wsliderh); obj->r = Rect(0, 0, Wboxw, Wboxh); break; case Gcheckbox: case Gswitch: if(argc < 2) goto fewarg; obj->dim = Pt(Wboxw, Wboxh); obj->value = strdup(argv[2]); break; } return obj; fewarg: missingarg(); return 0; } void redraw(Object *obj) { int on; Rectangle r; on = obj->on; switch(obj->type){ case Ggraph: draw(screen, obj->bbox, obj->graph, 0, ZP); border(screen, obj->bbox, Woutline, col[1], ZP); break; case Gbutton: draw(screen, obj->bbox, col[on], 0, ZP); border(screen, obj->bbox, Woutline, col[1], ZP); string(screen, addpt(obj->r.min, obj->bbox.min), col[!on], ZP, font, obj->text); break; case Gtext: string(screen, obj->bbox.min, col[1], ZP, font, obj->text); break; case Gslider: r = rectaddpt(obj->r, addpt(obj->bbox.min, Pt(obj->state, 0))); draw(screen, obj->bbox, col[0], 0, ZP); border(screen, obj->bbox, Woutline, col[1], ZP); draw(screen, r, col[on], 0, ZP); border(screen, r, Woutline, col[1], ZP); break; case Gcheckbox: case Gswitch: draw(screen, obj->bbox, col[obj->state], 0, ZP); border(screen, obj->bbox, Woutline, col[1], ZP); break; } } void event(Object *obj) { switch(obj->type){ case Gbutton: xprint("%s\n", obj->name); if(strcmp(obj->name, "exit") == 0) goodbye(); break; case Gslider: xprint("%s %d\n", obj->name, obj->state); break; case Gswitch: if(obj->state == 1) xprint("%s %s\n", obj->name, obj->value); break; case Gcheckbox: xprint("%s %d\n", obj->name, obj->state); break; } } void getstate(Object *obj) { switch(obj->type){ case Gslider: case Gcheckbox: case Gswitch: event(obj); break; } } void setstate(Object *obj, char *value) { switch(obj->type){ case Ggraph: loadgraph(obj->graph, value); break; case Gbutton: case Gtext: free(obj->text); obj->text = strdup(value); break; case Gslider: obj->state = slideclamp(atoi(value)); break; case Gswitch: obj->state = !strcmp(value, obj->value); break; case Gcheckbox: obj->state = value[0] == '1'; break; } } void click(Object *obj, Mouse m) { int focus, done, x, y; Object *xobj; focus = ptinrect(m.xy, obj->bbox); done = m.buttons == 0; switch(obj->type){ case Gbutton: if(done && focus) event(obj); obj->on = !done && focus; break; case Gslider: x = m.xy.x - obj->bbox.min.x; y = obj->state; if(obj->on == 0){ if(m.buttons & 4) y += x/2; else if(m.buttons & 1) y -= x/2; else if(m.buttons & 2) y = x; obj->state = slideclamp(y); event(obj); }else{ x = slideclamp(x); if((m.buttons & 2) && obj->state != x){ obj->state = x; event(obj); } } obj->on = !done; break; case Gcheckbox: if(obj->on == 0){ obj->state ^= 1; event(obj); } obj->on = !done; break; case Gswitch: if(obj->on == 0){ for(xobj=objects; xobj; xobj=xobj->next) if(strcmp(obj->name, xobj->name) == 0) xobj->state = 0; obj->state = 1; event(obj); redrawall(); } obj->on = !done; break; } } /* * Layouter */ Point layout(Point p) { Object *obj; int w, aw, h, ah; h = ah = 0; w = aw = 0; p.x += Wpad; p.y += Wpad; for(obj=objects; obj; obj=obj->next){ obj->bbox.min = Pt(p.x + w, p.y + ah); obj->bbox.max = addpt(obj->bbox.min, obj->dim); w = w + obj->dim.x + Wpad; h = MAX(h, obj->dim.y); if(obj->nl == 1 || obj->next == 0){ ah += h + Wpad; aw = MAX(aw, w); w = h = 0; } } return Pt(aw + Wpad, ah + Wpad); } void redrawall(void) { Object *obj; layout(screen->r.min); draw(screen, screen->r, col[0], 0, ZP); for(obj=objects; obj; obj=obj->next) redraw(obj); flush(); } /* * Events */ int parseline(char *s) { char *args[Argsmax]; char **argv; int argc; Object *obj; int k; if(s[0] == '#') return 0; argv = args; argc = tokenize(s, argv, Argsmax); if(argc == 0) return 0; for(k=0; keywords[k]; k++) if(strcmp(keywords[k], argv[0]) == 0) break; switch(k){ case Gnl: if(lastobj != 0) lastobj->nl = 1; return 0; case Gresize: resize(layout(ZP)); return 1; case Gname: if(argc < 2) goto fewarg; setlabel(argv[1]); return 0; case Gget: for(obj=objects; obj; obj=obj->next) if(argc == 1 || strcmp(argv[1], obj->name) == 0) getstate(obj); return 0; case Gset: if(argc < 3) goto fewarg; for(obj=objects; obj; obj=obj->next){ if(strcmp(argv[1], obj->name) != 0) continue; setstate(obj, argv[2]); } return 1; case Gexit: goodbye(); case Gunknown: xprint("error unknown widget %s\n", argv[0]); return 0; default: obj = new(k, argc, argv); if(lastobj != 0) lastobj->next = obj; else objects = obj; lastobj = obj; return 1; } fewarg: missingarg(); return 0; } void mousethread(void *) { enum { Cresize, Cmouse, Ckey, Cline, NCM }; Alt alts[NCM+1]; Rune r; Object *obj; char *line; Mouse m; obj = 0; for(;;){ alts[Cmouse].c = mctl->c; alts[Cmouse].v = &m; alts[Cmouse].op = CHANRCV; alts[Cresize].c = mctl->resizec; alts[Cresize].v = nil; alts[Cresize].op = CHANRCV; alts[Ckey].c = kctl->c; alts[Ckey].v = &r; alts[Ckey].op = CHANRCV; alts[Cline].c = linechan; alts[Cline].v = &line; alts[Cline].op = CHANRCV; alts[NCM].op = CHANEND; switch(alt(alts)){ case Cresize: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); redrawall(); continue; case Ckey: goodbye(); break; case Cmouse: break; case Cline: if(line == 0){ if(iflag != 0) goodbye(); }else{ if(parseline(line) == 1) redrawall(); free(line); } continue; } if(obj == 0){ if(m.buttons == 0) continue; for(obj=objects; obj; obj=obj->next){ if(ptinrect(m.xy, obj->bbox)) break; } } if(obj != 0){ click(obj, m); redraw(obj); flush(); } if(m.buttons == 0) obj = 0; } } void readerthread(void *chan) { char *line; Binit(&bin, 0, OREAD); do{ line = Brdstr(&bin, '\n', 1); sendp(chan, line); }while(line != 0); Bterm(&bin); } void threadmain(int argc, char *argv[]) { ARGBEGIN{ case 'i': iflag = 1; break; case 'w': wflag = 1; break; default: sysfatal("unknown flag"); break; }ARGEND; linechan = chancreate(sizeof(void*), 32); proccreate(readerthread, linechan, Maxstack); if(wflag) newwindow(0); if(initdraw(nil, nil, "gui") < 0) sysfatal("initdraw: %r"); mctl = initmouse("/dev/mouse", display->image); if(mctl == nil) sysfatal("initmouse: %r"); kctl = initkeyboard("/dev/cons"); if(kctl == nil) sysfatal("initkeyboard: %r"); col[0] = color(0xffffff); col[1] = color(0); font = display->defaultfont; redrawall(); threadcreate(mousethread, 0, Maxstack); threadexits(nil); }