From d9a15e56d42e45f021c01d527df67863100840a7 Mon Sep 17 00:00:00 2001 From: Peter Mikkelsen Date: Sat, 17 Feb 2024 16:42:00 +0000 Subject: Implement scroll bar (not clickable yet) and sam-scrolling --- event.c | 23 +++++++++++++++ graphics.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- guifs.h | 8 ++++- guispec.c | 2 +- main.c | 20 +++++++++++-- props.c | 77 +++++++++++++++++++++++++++++++++++++++--------- test.rc | 38 ++++++++++++++---------- 7 files changed, 230 insertions(+), 36 deletions(-) diff --git a/event.c b/event.c index 4182d1c..81c923e 100644 --- a/event.c +++ b/event.c @@ -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; diff --git a/graphics.c b/graphics.c index 9f14058..bbafa72 100644 --- a/graphics.c +++ b/graphics.c @@ -73,14 +73,106 @@ drawcontainer(GuiElement *g) USED(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 * diff --git a/guifs.h b/guifs.h index d66c58d..98f2854 100644 --- a/guifs.h +++ b/guifs.h @@ -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 { diff --git a/guispec.c b/guispec.c index a2f22c1..65e824c 100644 --- a/guispec.c +++ b/guispec.c @@ -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}, diff --git a/main.c b/main.c index aba1b70..98bd3a0 100644 --- a/main.c +++ b/main.c @@ -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); diff --git a/props.c b/props.c index df61782..f31270a 100644 --- a/props.c +++ b/props.c @@ -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 diff --git a/test.rc b/test.rc index 9c04f75..8ee1e20 100755 --- a/test.rc +++ b/test.rc @@ -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' [2]/dev/null & +# printevents 'text field' [2]/dev/null & + +# Reset the textfield contents +echo -n > /mnt/gui/0/props/text wait -- cgit v1.2.3