~pmikkelsen/guifs

d9a15e56d42e45f021c01d527df67863100840a7 — Peter Mikkelsen 3 months ago c51962a front
Implement scroll bar (not clickable yet) and sam-scrolling
7 files changed, 230 insertions(+), 36 deletions(-)

M event.c
M graphics.c
M guifs.h
M guispec.c
M main.c
M props.c
M test.rc
M event.c => event.c +23 -0
@@ 117,6 117,28 @@ mouseevent(Mouse m)
		return 0;

	wlock(&g->lock);

	/* Handle scrolling first */
	if(g->type == Gtextbox && m.buttons&24){ /* TODO: in the future it may apply to other things? */
		PropVal p = getprop(g, Pscroll, 0);
		int lines = ((m.xy.y - g->content.min.y) / font->height);
		if(lines < 1)
			lines = 1;

		if(m.buttons&8)
			p.scroll -= lines;
		else if(m.buttons&16)
			p.scroll += lines;

		if(p.scroll < 0)
			p.scroll = 0;
		
		setprop(g, Pscroll, p, 0);
		wunlock(&g->lock);
		return 1;
	}

	/* Then handle menus */
	Event e;
	MenuSpec *ms = getprop(g, Pmenus, 0).menus;
	if(down >= 1 && down <= 3 && ms->menus[down-1] != nil){


@@ 139,6 161,7 @@ mouseevent(Mouse m)
		return 1;
	}

	/* Now handle general events */
	if(!g->listening){
		wunlock(&g->lock);
		return 0;

M graphics.c => graphics.c +95 -3
@@ 74,13 74,105 @@ drawcontainer(GuiElement *g)
}

void
drawtext(Rectangle rect, Rune *text, Image *fg, int firstline, int *nlinesp, int *endlinep)
{
	Point p = rect.min;
	int w;

	int nlines = 1;
	int newlines = 0;
	int endline = 0;
	int ended = 0;
	int xoffset = 0;

	for(Rune *r = text; *r; r++){
		switch(*r){
		case '\n':
			newlines++;
Lnewline:		if(ended && endline == 0)
				endline = nlines;
			p.x = rect.min.x;
			xoffset = 0;
			if((newlines)-(*r=='\n') >= firstline)
				p.y += font->height;
			nlines++;
			break;
		case '\t':
			w = 8-(xoffset%8);
			xoffset += w;

			w *= stringnwidth(font, " ", 1);
			xoffset += w;
			if(p.x+w > rect.max.x){
				r--;
				goto Lnewline;
			}else
				p.x += w;
			break;
		default:
			xoffset++;
			w = runestringnwidth(font, r, 1);
			if(p.x+w > rect.max.x){
				r--;
				goto Lnewline;
			}
			if(!ended && (p.y+font->height) < rect.max.y){
				if(newlines >= firstline)
					runestringn(screen, p, fg, ZP, font, r, 1);
			}else
				ended = 1;
			p.x += w;
			break;
		}
	}

	if(endline == 0)
		endline = nlines;

	if(nlinesp)
		*nlinesp = nlines;
	if(endlinep)
		*endlinep = endline;
}

void
drawtextbox(GuiElement *g)
{
	Rune *text = getprop(g, Ptext, 1).text;
	Rune *text = getprop(g, Ptext, 0).text;
	Image *fg = getprop(g, Ptextcolour, 1).colour->image;
	
	Image *scrollbg = getprop(g, Pbordercolour, 1).colour->image;
	Image *scrollfg = getprop(g, Pbackground, 1).colour->image;
	int firstline = getprop(g, Pscroll, 1).scroll;

	int lines = 0;
	for(Rune *r = text; *r; r++)
		lines += (*r) == '\n';

	/* draw the scroll bar background here, then draw the actual bar later */
	Rectangle scrollbar = Rect(g->rect.min.x, g->rect.min.y, g->rect.min.x+ScrollbarWidth, g->rect.max.y);
	Rectangle textrect = g->content;
	textrect.min.x += ScrollbarWidth;

	draw(screen, scrollbar, scrollbg, nil, ZP);

	runestring(screen, g->content.min, fg, ZP, font, text);
	int nlines, endline;
	drawtext(textrect, text, fg, firstline, &nlines, &endline);

	int height = Dy(scrollbar);
	scrollbar.max.x -= 1;
	scrollbar.min.y += (height * (firstline)) / nlines;
	scrollbar.max.y -= (height * (nlines-endline)) / nlines;

	if(scrollbar.min.y > (g->rect.max.y - 2))
		scrollbar.min.y = g->rect.max.y - 2;

	draw(screen, scrollbar, scrollfg, nil, ZP);

	if(firstline > nlines){
		PropVal p;
		p.scroll = nlines - 1;
		setprop(g, Pscroll, p, 0); /* TODO: we don't have the write lock here :) */
	}
}

Colour *

M guifs.h => guifs.h +7 -1
@@ 8,6 8,7 @@ enum {
	Ptext,
	Ptextcolour,
	Pmenus,
	Pscroll,
	Pmax,
};



@@ 38,6 39,10 @@ enum {
	Xmax,
};

enum {
	ScrollbarWidth = 12,
};

typedef struct Colour Colour;
typedef struct Spacing Spacing;
typedef struct MenuSpec MenuSpec;


@@ 70,6 75,7 @@ union PropVal {
	Spacing *spacing;
	MenuSpec *menus;
	int orientation;
	int scroll;
	Rune *text;
};



@@ 77,7 83,7 @@ struct PropSpec {
	char *name;
	PropVal (*def)(int, int);
	char *(*print)(PropVal);
	char *(*parse)(char *, PropVal *);
	char *(*parse)(char *, int, PropVal *);
};

struct Prop {

M guispec.c => guispec.c +1 -1
@@ 7,7 7,7 @@
#include "guifs.h"

int containerprops[] = {Porientation};
int textboxprops[] = {Ptext, Ptextcolour};
int textboxprops[] = {Ptext, Ptextcolour, Pscroll};

GuiSpec guispecs[Gmax] = {
	[Gcontainer] = { "container",	drawcontainer,	layoutcontainer,	0,	nelem(containerprops),	containerprops},

M main.c => main.c +18 -2
@@ 322,6 322,10 @@ Lend:
void
fsstat(Req *r)
{
	GuiElement *g = r->fid->aux;
	PropVal p;
	char *buf;

	r->d.qid = r->fid->qid;
	r->d.uid = estrdup9p(username);
	r->d.gid = estrdup9p(username);


@@ 335,6 339,12 @@ fsstat(Req *r)
		r->d.name = estrdup9p("/");
		r->d.mode = 0555|DMDIR;
		break;
	case Qprop:
		p = getprop(g, QID_PROP(r->fid->qid), 1);
		buf = propspecs[QID_PROP(r->fid->qid)].print(p);
		r->d.length = strlen(buf);
		free(buf);
		break;
	}

	respond(r, nil);


@@ 417,9 427,14 @@ proptreegen(int n, Dir *d, void *aux)

	if(!done){
		PropSpec spec = propspecs[g->props[n].tag];
		PropVal p = getprop(g, g->props[n].tag, 0);
		char *buf = spec.print(p);

		d->mode = 0666;
		d->name = estrdup9p(spec.name);
		d->qid = g->props[n].qid;
		d->length = strlen(buf);
		free(buf);
	}
	runlock(&g->lock);



@@ 459,6 474,7 @@ Lgotevent:			currentsize = g->currentevents ? strlen(g->currentevents) : 0;
				eventsize = strlen(event);

				wlock(&g->lock);
				g->qevent.vers++;
				g->currentevents = erealloc(g->currentevents, currentsize+eventsize+1);
				memcpy(g->currentevents+currentsize, event, eventsize);
				g->currentevents[currentsize+eventsize] = 0;


@@ 544,12 560,12 @@ fswrite(Req *r)
		{
			int tag = QID_PROP(r->fid->qid);
			PropSpec spec = propspecs[tag];
			PropVal val;
			PropVal val = getprop(g, tag, 1);

			char *buf = emalloc(r->ifcall.count + 1);
			buf[r->ifcall.count] = 0;
			memcpy(buf, r->ifcall.data, r->ifcall.count);
			err = spec.parse(buf, &val);
			err = spec.parse(buf, r->ifcall.offset, &val);
			if(err == nil)
				setprop(g, tag, val, 1);
			free(buf);

M props.c => props.c +64 -13
@@ 10,6 10,7 @@
#include "guifs.h"

#define Eparse "could not parse property"
#define Eoffset "cannot parse this property when writing at a non-zero offset"

int
allspace(char *r)


@@ 120,6 121,17 @@ defmenus(int gtag, int ptag)
	return v;
}

PropVal
defscroll(int gtag, int ptag)
{
	USED(gtag);
	USED(ptag);

	PropVal v;
	v.scroll = 0;
	return v;
}

char *
printcolour(PropVal p)
{


@@ 196,20 208,31 @@ printmenus(PropVal p)
}

char *
parsecolour(char *str, PropVal *p)
printscroll(PropVal p)
{
	return smprint("%d\n", p.scroll);
}

char *
parsecolour(char *str, int offset, PropVal *p)
{
	if(offset != 0)
		return Eoffset;

	char *r;
	ulong c = strtoul(str, &r, 16);
	if((r - str) != 8 || !allspace(r))
		return Eparse;
	(*p).colour = mkcolour(c);
	p->colour = mkcolour(c);
	return nil;
}

char *
parsespacing(char *str, PropVal *p)
parsespacing(char *str, int offset, PropVal *p)
{
	USED(p);
	if(offset != 0)
		return Eoffset;

	char *fields[5];
	int spacings[4];



@@ 240,36 263,50 @@ parsespacing(char *str, PropVal *p)
		s->left = spacings[3];
		break;
	}
	(*p).spacing = s;
	p->spacing = s;

	return nil;
}

char *
parseorientation(char *str, PropVal *p)
parseorientation(char *str, int offset, PropVal *p)
{
	if(offset != 0)
		return Eoffset;

	if(strncmp(str, "horizontal", 10) == 0 && allspace(str+10))
		(*p).orientation = Horizontal;
		p->orientation = Horizontal;
	else if(strncmp(str, "vertical", 8) == 0 && allspace(str+8))
		(*p).orientation = Vertical;
		p->orientation = Vertical;
	else
		return Eparse;
	return nil;
}

char *
parsetext(char *str, PropVal *p)
parsetext(char *str, int offset, PropVal *p)
{
	Rune *rstr = runesmprint("%s", str);
	char *old = smprint("%S", p->text);
	long oldlen = strlen(old);

	if(offset > oldlen)
		return Eparse;

	old[offset] = 0;
	Rune *rstr = runesmprint("%s%s", old, str);
	free(old);
	if(rstr == nil)
		sysfatal("runesmprint failed");
	(*p).text = rstr;
	p->text = rstr;
	return nil;
}

char *
parsemenus(char *str, PropVal *p)
parsemenus(char *str, int offset, PropVal *p)
{
	if(offset != 0)
		return Eoffset;

	char *err = nil;
	int n = 0;
	for(int i = 0; str[i] != 0; i++)


@@ 327,7 364,6 @@ parsemenus(char *str, PropVal *p)
			}
		}
		spec->menus[which]->item[count] = nil;
		print("count: %d\n", count);
	}

Lend:


@@ 335,6 371,20 @@ Lend:
	return err;
}

char *
parsescroll(char *str, int offset, PropVal *p)
{
	if(offset != 0)
		return Eoffset;

	char *r;
	ulong s = strtoul(str, &r, 10);
	if(!allspace(r))
		return Eparse;
	p->scroll = (int)s;
	return nil;
}

PropVal
getprop(GuiElement *g, int tag, int lock)
{


@@ 378,6 428,7 @@ PropSpec propspecs[Pmax] = {
	[Ptext] = {"text",	deftext,	printtext,	parsetext},
	[Ptextcolour] = {"textcolour",	defcolour,	printcolour,	parsecolour},
	[Pmenus] = {"menus",	defmenus,	printmenus,	parsemenus},
	[Pscroll] = {"scroll",	defscroll,	printscroll,	parsescroll},
};

int baseprops[nbaseprops] = {Pbackground, Pborder, Pmargin, Ppadding, Pbordercolour, Pmenus};
\ No newline at end of file

M test.rc => test.rc +22 -16
@@ 5,12 5,12 @@ delay=0.1
dir=/mnt/gui

# split the window vertically into two, and make the top one a textbox
echo vertical >> $dir/props/orientation
echo vertical > $dir/props/orientation
textdir=$dir/`{cat $dir/clone}
echo textbox >> $textdir/type
echo textbox > $textdir/type

fn show {
	echo -n $* >> $textdir/props/text
	echo $* >> $textdir/props/text
}

dir=$dir/`{cat $dir/clone}


@@ 18,7 18,7 @@ dir=$dir/`{cat $dir/clone}
colours=(FF0000 00FF00 0000FF FFFF00 00FFFF FF00FF FFFFFF 333333)
for(colour in $colours){
	dir=$dir/`{cat $dir/clone}			# Create a new sub element in the gui (for now, always a "container")
	echo $colour^FF >> $dir/props/background	# Set the background
	echo $colour^FF > $dir/props/background	# Set the background
	show 'setting background of '^$dir^' to 0x'^$colour
	sleep $delay					# Wait a bit
}


@@ 26,39 26,41 @@ for(colour in $colours){
# Now do the same, but don't nest the elements
for(colour in $colours){
	subdir=$dir/`{cat $dir/clone}
	echo $colour^FF >> $subdir/props/background
	echo $colour^FF > $subdir/props/background
	show 'setting background of '^$subdir^' to 0x'^$colour
	sleep $delay
}

# Add some padding to all elements
for(f in `{walk /mnt/gui/ | grep 'padding$'}){
	echo 10 >> $f
	show 'echo 10 >> '^$f
	echo 10 > $f
	show 'echo 10 > '^$f
	sleep $delay
}

# Add a border to the innermost elements
for(f in `{walk /mnt/gui | grep $dir'/[0-9]+/props/border$'}){
	echo 8888CCFF >> $f^colour
	echo 5 >> $f
	show 'echo 5 >> '^$f
	echo 8888CCFF > $f^colour
	echo 5 > $f
	show 'echo 5 > '^$f
	sleep $delay
}

# Add some margin to the innermost elements
for(f in `{walk /mnt/gui | grep $dir'/[0-9]+/props/margin'}){
	echo 5 >> $f
	show 'echo 5 >> '^$f
	echo 5 > $f
	show 'echo 5 > '^$f
	sleep $delay
}

# Make the inner container vertical
echo vertical >> $dir/props/orientation
show 'echo vertical >> '^$dir/props/orientation
echo vertical > $dir/props/orientation
show 'echo vertical > '^$dir/props/orientation

nl='
'
fn printevents {
	while(event = `''{read}){
	while(event = `''{read | tr -d $nl}){
		show $1': '$event
	}
}


@@ 70,7 72,11 @@ for(f in `{walk /mnt/gui | grep $dir'/[0-9]+/event'}){

# Create a right-click menu on the text field
echo 'R/this is a/right click/menu' >> /mnt/gui/0/props/menus

# Also attach an event printer to the text field
printevents 'text field' </mnt/gui/0/event >[2]/dev/null &
# printevents 'text field' </mnt/gui/0/event >[2]/dev/null &

# Reset the textfield contents
echo -n > /mnt/gui/0/props/text

wait