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