shithub: widget

Download patch

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
+	}