ref: 5409ad54228242e20aed847acba4d10582b1191c
author: Tevo <estevan.cps@gmail.com>
date: Sat Jan 9 20:27:40 EST 2021
Basic prototype, text label, button
--- /dev/null
+++ b/CONVENTIONS
@@ -1,0 +1,7 @@
+• widgets that encapsulate other widgets:
+ - take ownership (responsible for freeing, etc)
+ - are responsible for ensuring their subwidgets don't stomp
+all over the screen
+• widgets should make sure global state is consistent with how it was when their functions were invoked (i.e. if you touch screen->clipr, restore it before returning, etc)
+• Widget->draw returns size of widget
+• event handling functions return 1 for when they handled the event themselves and 0 when they didn't
--- /dev/null
+++ b/TODO
@@ -1,0 +1,3 @@
+• figure out a way to reduce boilerplate for each widget
+• not sure if pulling every base widget dependency along the library is a great idea, maybe split the headers (<widget.h>, then <widget/textbox.h>, <widget/button.h>, etc)?
+• figure out widget-generated event handling
--- /dev/null
+++ b/cmd/factory/factory.c
@@ -1,0 +1,82 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <String.h>
+#include <widget.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s", argv0);
+ exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Keyboardctl *kbctl;
+ Mousectl *mctl;
+ Button *root;
+ Widgetctl *wctl;
+ Widgetmsg *msg;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if(initdraw(nil, nil, "widget factory") < 0)
+ sysfatal("initdraw: %r");
+
+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+
+ if((kbctl = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");
+
+ root = newtextbutton(nil, "hello, world!");
+
+ if((wctl = initwidget(screen, kbctl, mctl, root)) == nil)
+ sysfatal("initwidget: %r");
+
+ enum
+ {
+ MESSAGE, RESIZE
+ };
+
+ Alt chans[] =
+ {
+ { wctl->c, &msg, CHANRCV },
+ { wctl->resizec, nil, CHANRCV },
+
+ { nil, nil, CHANEND }
+ };
+
+ for(;;)
+ {
+ switch(alt(chans))
+ {
+ case MESSAGE:
+ print("got message for %d!\n", msg->what);
+ free(msg);
+ break;
+ case RESIZE:
+ if(getwindow(display, Refnone) < 0)
+ sysfatal("getwindow: cannot resize: %r");
+ /* FIXME most users shouldn't need to call this directly */
+ redrawwidget(root, screen, screen->r);
+ break;
+ }
+ flushimage(display, 1);
+ }
+
+end:
+ closemouse(mctl);
+ closekeyboard(kbctl);
+ freewidget(root);
+
+ exits(0);
+}
--- /dev/null
+++ b/cmd/factory/mkfile
@@ -1,0 +1,11 @@
+</$objtype/mkfile
+
+TARG=factory
+
+OFILES=\
+ factory.$O
+
+HFILES=\
+ /sys/include/widget.h
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/cmd/mkfile
@@ -1,0 +1,14 @@
+BIN=/$objtype/bin/widget
+
+FOLDERS=\
+ factory
+
+all:V:
+
+%:V: $FOLDERS $BIN
+ for(f in $FOLDERS) @{
+ cd $f; mk $stem
+ }
+
+$BIN:
+ mkdir -p $target
--- /dev/null
+++ b/libwidget/base.c
@@ -1,0 +1,131 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <String.h>
+
+#include <widget.h>
+#include "w-internal.h"
+
+void (*werror)(char*, ...) = sysfatal;
+
+int
+nextid(void)
+{
+ static int curid;
+ return curid++;
+}
+
+void
+widgetmain(Widgetctl* ctl)
+{
+ Mouse mouse;
+ Rune rune;
+
+ Alt chans[] =
+ {
+ { ctl->mouse->c, &mouse, CHANRCV },
+ { ctl->kbd->c, &rune, CHANRCV },
+
+ { nil, nil, CHANEND }
+ };
+
+ enum
+ {
+ MOUSE, KEYBOARD
+ };
+
+ for(;;)
+ {
+ while(ctl->root == nil)
+ yield();
+
+ switch(alt(chans))
+ {
+ case MOUSE:
+ mouseevent(ctl->root, ctl->image, ctl->image->r, mouse, ctl->c);
+ break;
+ case KEYBOARD:
+ kbdevent(ctl->root, ctl->image, ctl->image->r, rune, ctl->c);
+ break;
+ }
+ flushimage(ctl->image->display, 1);
+ }
+}
+
+Widgetctl*
+initwidget(Image *img, Keyboardctl *kbd, Mousectl *mouse, Widget *root)
+{
+ Widgetctl *ctl;
+
+ if((ctl = malloc(sizeof(*ctl))) == nil)
+ return nil;
+
+ ctl->image = img;
+ ctl->mouse = mouse;
+ ctl->root = root;
+ ctl->kbd = kbd;
+ ctl->c = chancreate(sizeof(Widgetmsg), 16);
+ ctl->resizec = mouse->resizec;
+
+ threadcreate((void(*)(void*))widgetmain, ctl, 16384);
+
+ redrawwidget(root, img, img->r);
+ flushimage(img->display, 1);
+
+ return ctl;
+}
+
+/* TODO set clipr */
+Point
+redrawwidget(Widget *w, Image *dst, Rectangle r)
+{
+ return w->redraw(w, dst, r);
+}
+
+int
+kbdevent(Widget *w, Image *img, Rectangle rect, Rune r, Channel *c)
+{
+ if(w->kbdevent != nil)
+ return w->kbdevent(w, img, rect, r, c);
+ return 0;
+}
+
+int
+mouseevent(Widget *w, Image *img, Rectangle rect, Mouse m, Channel *c)
+{
+ if(w->mouseevent != nil)
+ return w->mouseevent(w, img, rect, m, c);
+ return 0;
+}
+
+void
+freewidget(Widget *w)
+{
+ w->cleanup(w);
+}
+
+void
+wdefaults(Widget *w)
+{
+ w->id = nextid();
+ w->bg = display->white;
+ w->fg = display->black;
+ w->kbdevent = nil;
+ w->mouseevent = nil;
+ w->cleanup = (void(*)(Widget*))free;
+}
+
+Widgetmsg*
+newmsg(Widget* w, u32int what)
+{
+ Widgetmsg *msg;
+
+ msg = emalloc(sizeof(*msg));
+ msg->sender = w;
+ msg->what = what;
+
+ return msg;
+}
--- /dev/null
+++ b/libwidget/base.h
@@ -1,0 +1,60 @@
+#pragma lib "libwidget.a"
+
+typedef struct Widget Widget;
+typedef struct Widgetctl Widgetctl;
+typedef struct Widgetmsg Widgetmsg;
+
+struct Widget
+{
+ int id;
+ char *kind;
+
+ void *aux; /* for the user */
+
+ Image *bg, *fg;
+
+ Point (*redraw)(Widget*, Image*, Rectangle);
+
+ /* can be nil if the widget doesn't take events */
+ int (*kbdevent)(Widget*, Image*, Rectangle, Rune, Channel* /*(Widgetmsg*)*/);
+ int (*mouseevent)(Widget*, Image*, Rectangle, Mouse, Channel* /*(Widgetmsg*)*/);
+
+ void (*cleanup)(Widget*);
+};
+
+struct Widgetctl
+{
+ Channel *c; /* chan(Widgetmsg*)[16] */
+ Channel *resizec;
+ Widget *root;
+
+ Keyboardctl *kbd;
+ Mousectl *mouse;
+
+ Image *image;
+};
+
+struct Widgetmsg
+{
+ Widget *sender;
+ u32int what;
+};
+
+Widgetmsg* newmsg(Widget*, u32int what);
+
+#define C2I(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
+
+extern void (*werror)(char*, ...);
+
+Widgetctl* initwidget(Image*, Keyboardctl*, Mousectl*, Widget *root);
+
+void wdefaults(Widget*);
+
+int nextid(void);
+
+Point redrawwidget(Widget*, Image*, Rectangle);
+
+int kbdevent(Widget*, Image*, Rectangle, Rune, Channel* /*(Widgetmsg*)*/);
+int mouseevent(Widget*, Image*, Rectangle, Mouse, Channel* /*(Widgetmsg*)*/);
+
+void freewidget(Widget*);
--- /dev/null
+++ b/libwidget/button.c
@@ -1,0 +1,118 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <String.h>
+
+#include <widget.h>
+#include "w-internal.h"
+
+static char *btnkind = "Button";
+static Image *lightblue, *darkblue;
+
+int
+isbutton(Widget *w)
+{
+ return strcmp(w->kind, btnkind) == 0;
+}
+
+Point
+btnredraw(Widget *w, Image *dst, Rectangle r)
+{
+ Image *tmp;
+ Button *btn;
+ Point btsz, pos, sz;
+ Rectangle conrect;
+
+ if(!isbutton(w))
+ werror("btnredraw: not a button");
+
+ btn = (Button*)w;
+ tmp = allocimage(dst->display, r, RGBA32, 0, DTransparent);
+ sz = redrawwidget(btn->content, tmp, r);
+
+ pos = btsz = subpt(r.max, r.min);
+ pos = divpt(pos, 2);
+ pos = subpt(pos, divpt(sz, 2));
+
+ conrect = Rpt(pos, subpt(r.max, pos));
+
+ draw(dst, r, btn->pressed ? btn->fg : btn->bg, nil, ZP);
+ draw(dst, conrect, tmp, nil, ZP);
+
+ freeimage(tmp);
+
+ return btsz;
+}
+
+int
+btnmouse(Widget *w, Image *dst, Rectangle rect, Mouse m, Channel *chan)
+{
+ Button *btn;
+ int pressed;
+ Widgetmsg *msg;
+
+ if(!isbutton(w))
+ werror("btndraw: not a button");
+
+ btn = (Button*)w;
+ if((pressed = m.buttons & 1) != btn->pressed)
+ {
+ if(pressed)
+ {
+ msg = newmsg(btn, M_BUTTON_PRESSED);
+ send(chan, &msg);
+ }
+ btn->pressed = pressed;
+ btnredraw(btn, dst, rect);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+btnfree(Widget *w)
+{
+ Button *btn;
+
+ if(!isbutton(w))
+ werror("btnfree: not a button");
+
+ btn = (Button*)w;
+ freewidget(btn->content);
+ free(btn);
+}
+
+Button*
+newbutton(Widget *w)
+{
+ Button *btn;
+
+ if(lightblue == nil)
+ lightblue = allocimagemix(display, DPalebluegreen, DWhite);
+
+ if(darkblue == nil)
+ darkblue = allocimagemix(display, DPurpleblue, DPalebluegreen);
+
+ btn = emalloc(sizeof(*btn));
+ wdefaults(btn);
+ btn->bg = lightblue;
+ btn->fg = darkblue;
+ btn->kind = btnkind;
+ btn->redraw = btnredraw;
+ btn->cleanup = btnfree;
+ btn->content = w;
+
+ btn->mouseevent = btnmouse;
+
+ return btn;
+}
+
+Button*
+newtextbutton(Font *f, char *content)
+{
+ return newbutton(newtextbox(0, 0, f, content));
+}
--- /dev/null
+++ b/libwidget/button.h
@@ -1,0 +1,17 @@
+
+typedef struct Button Button;
+
+struct Button
+{
+ Widget;
+
+ Widget *content;
+ int pressed;
+};
+
+int isbutton(Widget*);
+
+Button* newbutton(Widget*);
+Button* newtextbutton(Font*, char *content);
+
+static const u32int M_BUTTON_PRESSED = C2I('b', 't', 'n', 'p');
--- /dev/null
+++ b/libwidget/mkfile
@@ -1,0 +1,23 @@
+</$objtype/mkfile
+
+LIB=/$objtype/lib/libwidget.a
+
+OFILES=\
+ base.$O \
+ textbox.$O \
+ button.$O
+
+HDR=widget.h
+
+HCOMP=\
+ base.h \
+ textbox.h \
+ button.h
+
+HFILES=\
+ /sys/include/$HDR
+
+</sys/src/cmd/mksyslib
+
+/sys/include/$HDR: $HCOMP
+ cat $prereq >$target
--- /dev/null
+++ b/libwidget/textbox.c
@@ -1,0 +1,76 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <String.h>
+
+#include <widget.h>
+#include "w-internal.h"
+
+static char* tbkind = "Textbox";
+
+int
+istextbox(Widget *w)
+{
+ return strcmp(w->kind, tbkind) == 0;
+}
+
+Point
+tbredraw(Widget *w, Image *dst, Rectangle r)
+{
+ Textbox *tb;
+ Point ret;
+
+ if(!istextbox(w))
+ werror("tbredraw: not a textbox");
+
+ tb = (Textbox*)w;
+ s_terminate(tb->content);
+ tb->lastpt = string(dst, r.min, tb->fg, ZP, tb->font, s_to_c(tb->content));
+ ret = subpt(tb->lastpt, r.min);
+ ret.y += tb->font->height;
+
+ return ret;
+}
+
+void
+tbfree(Widget *w)
+{
+ Textbox *tb;
+
+ if(!istextbox(w))
+ werror("tbfree: not a textbox");
+
+ tb = (Textbox*)w;
+ s_free(tb->content);
+ free(tb);
+}
+
+Textbox*
+newtextbox(int selectable, int editable, Font *f, char *content)
+{
+ Textbox *tb;
+
+ tb = emalloc(sizeof(*tb));
+ wdefaults(tb);
+ tb->kind = tbkind;
+ tb->redraw = tbredraw;
+ tb->cleanup = tbfree;
+
+ tb->selectable = selectable;
+ tb->editable = editable;
+
+ if(f != nil)
+ tb->font = f;
+ else
+ tb->font = font;
+
+ if(content != nil)
+ tb->content = s_copy(content);
+ else
+ tb->content = s_new();
+
+ return tb;
+}
--- /dev/null
+++ b/libwidget/textbox.h
@@ -1,0 +1,17 @@
+
+typedef struct Textbox Textbox;
+
+struct Textbox
+{
+ Widget;
+
+ Font *font;
+ String *content;
+ Point lastpt;
+ int selectable;
+ int editable;
+};
+
+int istextbox(Widget*);
+
+Textbox* newtextbox(int selectable, int editable, Font*, char *content);
--- /dev/null
+++ b/libwidget/w-internal.h
@@ -1,0 +1,11 @@
+static void*
+emalloc(ulong sz)
+{
+ void *p;
+
+ p = malloc(sz);
+ if(p == nil)
+ werror("malloc: %r");
+ setmalloctag(p, getcallerpc(&sz));
+ return p;
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,10 @@
+FOLDERS=\
+ libwidget \
+ cmd
+
+all:V:
+
+%:V: $FOLDERS
+ for(f in $FOLDERS) @{
+ cd $f; mk $stem
+ }