plan9fox/sys/src/libdraw/emenuhit.c
cinap_lenrek b5659c0977 libdraw: avoid moving cursor if we dont have to for menuhit
depending on the font, poping the menu would move your cursor
one pixel down each time (due to division). this is annoying
when using a trackpoint and trying to repeat some operation
over 9000 times.

the cursor should only be moved when the menu is repositioned
to contain it on the screen.
2013-06-18 23:23:41 +02:00

270 lines
7 KiB
C

#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)
{
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);
if(pt.x || pt.y)
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);
if(lasti >= 0){
menu->lasthit = lasti+off;
return menu->lasthit;
}
return -1;
}