diff options
Diffstat (limited to 'src/libdraw/emenuhit.c')
-rw-r--r-- | src/libdraw/emenuhit.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/src/libdraw/emenuhit.c b/src/libdraw/emenuhit.c new file mode 100644 index 00000000..f596d7a2 --- /dev/null +++ b/src/libdraw/emenuhit.c @@ -0,0 +1,271 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> + +enum +{ + Margin = 4, /* outside to text */ + Border = 2, /* outside to selection boxes */ + Blackborder = 2, /* width of outlining border */ + Vspacing = 2, /* extra spacing between lines of text */ + Maxunscroll = 25, /* maximum #entries before scrolling turns on */ + Nscroll = 20, /* number entries in scrolling part */ + Scrollwid = 14, /* width of scroll bar */ + Gap = 4, /* between text and scroll bar */ +}; + +static Image *menutxt; +static Image *back; +static Image *high; +static Image *bord; +static Image *text; +static Image *htext; + +static +void +menucolors(void) +{ + /* Main tone is greenish, with negative selection */ + back = allocimagemix(display, DPalegreen, DWhite); + high = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DDarkgreen); /* dark green */ + bord = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DMedgreen); /* not as dark green */ + if(back==nil || high==nil || bord==nil) + goto Error; + text = display->black; + htext = back; + return; + + Error: + freeimage(back); + freeimage(high); + freeimage(bord); + back = display->white; + high = display->black; + bord = display->black; + text = display->black; + htext = display->white; +} + +/* + * r is a rectangle holding the text elements. + * return the rectangle, including its black edge, holding element i. + */ +static Rectangle +menurect(Rectangle r, int i) +{ + if(i < 0) + return Rect(0, 0, 0, 0); + r.min.y += (font->height+Vspacing)*i; + r.max.y = r.min.y+font->height+Vspacing; + return insetrect(r, Border-Margin); +} + +/* + * r is a rectangle holding the text elements. + * return the element number containing p. + */ +static int +menusel(Rectangle r, Point p) +{ + r = insetrect(r, Margin); + if(!ptinrect(p, r)) + return -1; + return (p.y-r.min.y)/(font->height+Vspacing); +} + +static +void +paintitem(Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore) +{ + char *item; + Rectangle r; + Point pt; + + if(i < 0) + return; + r = menurect(textr, i); + if(restore){ + draw(screen, r, restore, nil, restore->r.min); + return; + } + if(save) + draw(save, save->r, screen, nil, r.min); + item = menu->item? menu->item[i+off] : (*menu->gen)(i+off); + pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2; + pt.y = textr.min.y+i*(font->height+Vspacing); + draw(screen, r, highlight? high : back, nil, pt); + string(screen, pt, highlight? htext : text, pt, font, item); +} + +/* + * menur is a rectangle holding all the highlightable text elements. + * track mouse while inside the box, return what's selected when button + * is raised, -1 as soon as it leaves box. + * invariant: nothing is highlighted on entry or exit. + */ +static int +menuscan(Menu *menu, int but, Mouse *m, Rectangle textr, int off, int lasti, Image *save) +{ + int i; + + paintitem(menu, textr, off, lasti, 1, save, nil); + flushimage(display, 1); /* in case display->locking is set */ + *m = emouse(); + while(m->buttons & (1<<(but-1))){ + flushimage(display, 1); /* in case display->locking is set */ + *m = emouse(); + i = menusel(textr, m->xy); + if(i != -1 && i == lasti) + continue; + paintitem(menu, textr, off, lasti, 0, nil, save); + if(i == -1) + return i; + lasti = i; + paintitem(menu, textr, off, lasti, 1, save, nil); + } + return lasti; +} + +static void +menupaint(Menu *menu, Rectangle textr, int off, int nitemdrawn) +{ + int i; + + draw(screen, insetrect(textr, Border-Margin), back, nil, ZP); + for(i = 0; i<nitemdrawn; i++) + paintitem(menu, textr, off, i, 0, nil, nil); +} + +static void +menuscrollpaint(Rectangle scrollr, int off, int nitem, int nitemdrawn) +{ + Rectangle r; + + draw(screen, scrollr, back, nil, ZP); + r.min.x = scrollr.min.x; + r.max.x = scrollr.max.x; + r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem; + r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem; + if(r.max.y < r.min.y+2) + r.max.y = r.min.y+2; + border(screen, r, 1, bord, ZP); + if(menutxt == 0) + menutxt = allocimage(display, Rect(0, 0, 1, 1), CMAP8, 1, DDarkgreen); + if(menutxt) + draw(screen, insetrect(r, 1), menutxt, nil, ZP); +} + +int +emenuhit(int but, Mouse *m, Menu *menu) +{ + int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem; + int scrolling; + Rectangle r, menur, sc, textr, scrollr; + Image *b, *save; + Point pt; + char *item; + + if(back == nil) + menucolors(); + sc = screen->clipr; + replclipr(screen, 0, screen->r); + maxwid = 0; + for(nitem = 0; + item = menu->item? menu->item[nitem] : (*menu->gen)(nitem); + nitem++){ + i = stringwidth(font, item); + if(i > maxwid) + maxwid = i; + } + if(menu->lasthit<0 || menu->lasthit>=nitem) + menu->lasthit = 0; + screenitem = (Dy(screen->r)-10)/(font->height+Vspacing); + if(nitem>Maxunscroll || nitem>screenitem){ + scrolling = 1; + nitemdrawn = Nscroll; + if(nitemdrawn > screenitem) + nitemdrawn = screenitem; + wid = maxwid + Gap + Scrollwid; + off = menu->lasthit - nitemdrawn/2; + if(off < 0) + off = 0; + if(off > nitem-nitemdrawn) + off = nitem-nitemdrawn; + lasti = menu->lasthit-off; + }else{ + scrolling = 0; + nitemdrawn = nitem; + wid = maxwid; + off = 0; + lasti = menu->lasthit; + } + r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin); + r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2)); + r = rectaddpt(r, m->xy); + pt = ZP; + if(r.max.x>screen->r.max.x) + pt.x = screen->r.max.x-r.max.x; + if(r.max.y>screen->r.max.y) + pt.y = screen->r.max.y-r.max.y; + if(r.min.x<screen->r.min.x) + pt.x = screen->r.min.x-r.min.x; + if(r.min.y<screen->r.min.y) + pt.y = screen->r.min.y-r.min.y; + menur = rectaddpt(r, pt); + textr.max.x = menur.max.x-Margin; + textr.min.x = textr.max.x-maxwid; + textr.min.y = menur.min.y+Margin; + textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing); + if(scrolling){ + scrollr = insetrect(menur, Border); + scrollr.max.x = scrollr.min.x+Scrollwid; + }else + scrollr = Rect(0, 0, 0, 0); + + b = allocimage(display, menur, screen->chan, 0, 0); + if(b == 0) + b = screen; + draw(b, menur, screen, nil, menur.min); + draw(screen, menur, back, nil, ZP); + border(screen, menur, Blackborder, bord, ZP); + save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1); + r = menurect(textr, lasti); + emoveto(divpt(addpt(r.min, r.max), 2)); + menupaint(menu, textr, off, nitemdrawn); + if(scrolling) + menuscrollpaint(scrollr, off, nitem, nitemdrawn); + while(m->buttons & (1<<(but-1))){ + lasti = menuscan(menu, but, m, textr, off, lasti, save); + if(lasti >= 0) + break; + while(!ptinrect(m->xy, textr) && (m->buttons & (1<<(but-1)))){ + if(scrolling && ptinrect(m->xy, scrollr)){ + noff = ((m->xy.y-scrollr.min.y)*nitem)/Dy(scrollr); + noff -= nitemdrawn/2; + if(noff < 0) + noff = 0; + if(noff > nitem-nitemdrawn) + noff = nitem-nitemdrawn; + if(noff != off){ + off = noff; + menupaint(menu, textr, off, nitemdrawn); + menuscrollpaint(scrollr, off, nitem, nitemdrawn); + } + } + flushimage(display, 1); /* in case display->locking is set */ + *m = emouse(); + } + } + draw(screen, menur, b, nil, menur.min); + if(b != screen) + freeimage(b); + freeimage(save); + replclipr(screen, 0, sc); + flushimage(display, 1); + if(lasti >= 0){ + menu->lasthit = lasti+off; + return menu->lasthit; + } + return -1; +} |