xref: /plan9/sys/src/libdraw/menuhit.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
17dd7cddfSDavid du Colombier #include <u.h>
27dd7cddfSDavid du Colombier #include <libc.h>
37dd7cddfSDavid du Colombier #include <draw.h>
47dd7cddfSDavid du Colombier #include <thread.h>
57dd7cddfSDavid du Colombier #include <mouse.h>
67dd7cddfSDavid du Colombier 
77dd7cddfSDavid du Colombier enum
87dd7cddfSDavid du Colombier {
97dd7cddfSDavid du Colombier 	Margin = 4,		/* outside to text */
107dd7cddfSDavid du Colombier 	Border = 2,		/* outside to selection boxes */
117dd7cddfSDavid du Colombier 	Blackborder = 2,	/* width of outlining border */
127dd7cddfSDavid du Colombier 	Vspacing = 2,		/* extra spacing between lines of text */
137dd7cddfSDavid du Colombier 	Maxunscroll = 25,	/* maximum #entries before scrolling turns on */
147dd7cddfSDavid du Colombier 	Nscroll = 20,		/* number entries in scrolling part */
157dd7cddfSDavid du Colombier 	Scrollwid = 14,		/* width of scroll bar */
167dd7cddfSDavid du Colombier 	Gap = 4,			/* between text and scroll bar */
177dd7cddfSDavid du Colombier };
187dd7cddfSDavid du Colombier 
197dd7cddfSDavid du Colombier static	Image	*menutxt;
207dd7cddfSDavid du Colombier static	Image	*back;
217dd7cddfSDavid du Colombier static	Image	*high;
227dd7cddfSDavid du Colombier static	Image	*bord;
237dd7cddfSDavid du Colombier static	Image	*text;
247dd7cddfSDavid du Colombier static	Image	*htext;
257dd7cddfSDavid du Colombier 
267dd7cddfSDavid du Colombier static
277dd7cddfSDavid du Colombier void
menucolors(void)287dd7cddfSDavid du Colombier menucolors(void)
297dd7cddfSDavid du Colombier {
307dd7cddfSDavid du Colombier 	/* Main tone is greenish, with negative selection */
317dd7cddfSDavid du Colombier 	back = allocimagemix(display, DPalegreen, DWhite);
32*9a747e4fSDavid du Colombier 	high = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkgreen);	/* dark green */
33*9a747e4fSDavid du Colombier 	bord = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedgreen);	/* not as dark green */
347dd7cddfSDavid du Colombier 	if(back==nil || high==nil || bord==nil)
357dd7cddfSDavid du Colombier 		goto Error;
367dd7cddfSDavid du Colombier 	text = display->black;
377dd7cddfSDavid du Colombier 	htext = back;
387dd7cddfSDavid du Colombier 	return;
397dd7cddfSDavid du Colombier 
407dd7cddfSDavid du Colombier     Error:
417dd7cddfSDavid du Colombier 	freeimage(back);
427dd7cddfSDavid du Colombier 	freeimage(high);
437dd7cddfSDavid du Colombier 	freeimage(bord);
447dd7cddfSDavid du Colombier 	back = display->white;
457dd7cddfSDavid du Colombier 	high = display->black;
467dd7cddfSDavid du Colombier 	bord = display->black;
477dd7cddfSDavid du Colombier 	text = display->black;
487dd7cddfSDavid du Colombier 	htext = display->white;
497dd7cddfSDavid du Colombier }
507dd7cddfSDavid du Colombier 
517dd7cddfSDavid du Colombier /*
527dd7cddfSDavid du Colombier  * r is a rectangle holding the text elements.
537dd7cddfSDavid du Colombier  * return the rectangle, including its black edge, holding element i.
547dd7cddfSDavid du Colombier  */
557dd7cddfSDavid du Colombier static Rectangle
menurect(Rectangle r,int i)567dd7cddfSDavid du Colombier menurect(Rectangle r, int i)
577dd7cddfSDavid du Colombier {
587dd7cddfSDavid du Colombier 	if(i < 0)
597dd7cddfSDavid du Colombier 		return Rect(0, 0, 0, 0);
607dd7cddfSDavid du Colombier 	r.min.y += (font->height+Vspacing)*i;
617dd7cddfSDavid du Colombier 	r.max.y = r.min.y+font->height+Vspacing;
627dd7cddfSDavid du Colombier 	return insetrect(r, Border-Margin);
637dd7cddfSDavid du Colombier }
647dd7cddfSDavid du Colombier 
657dd7cddfSDavid du Colombier /*
667dd7cddfSDavid du Colombier  * r is a rectangle holding the text elements.
677dd7cddfSDavid du Colombier  * return the element number containing p.
687dd7cddfSDavid du Colombier  */
697dd7cddfSDavid du Colombier static int
menusel(Rectangle r,Point p)707dd7cddfSDavid du Colombier menusel(Rectangle r, Point p)
717dd7cddfSDavid du Colombier {
727dd7cddfSDavid du Colombier 	r = insetrect(r, Margin);
737dd7cddfSDavid du Colombier 	if(!ptinrect(p, r))
747dd7cddfSDavid du Colombier 		return -1;
757dd7cddfSDavid du Colombier 	return (p.y-r.min.y)/(font->height+Vspacing);
767dd7cddfSDavid du Colombier }
777dd7cddfSDavid du Colombier 
787dd7cddfSDavid du Colombier static
797dd7cddfSDavid du Colombier void
paintitem(Image * m,Menu * menu,Rectangle textr,int off,int i,int highlight,Image * save,Image * restore)807dd7cddfSDavid du Colombier paintitem(Image *m, Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
817dd7cddfSDavid du Colombier {
827dd7cddfSDavid du Colombier 	char *item;
837dd7cddfSDavid du Colombier 	Rectangle r;
847dd7cddfSDavid du Colombier 	Point pt;
857dd7cddfSDavid du Colombier 
867dd7cddfSDavid du Colombier 	if(i < 0)
877dd7cddfSDavid du Colombier 		return;
887dd7cddfSDavid du Colombier 	r = menurect(textr, i);
897dd7cddfSDavid du Colombier 	if(restore){
907dd7cddfSDavid du Colombier 		draw(m, r, restore, nil, restore->r.min);
917dd7cddfSDavid du Colombier 		return;
927dd7cddfSDavid du Colombier 	}
937dd7cddfSDavid du Colombier 	if(save)
947dd7cddfSDavid du Colombier 		draw(save, save->r, m, nil, r.min);
957dd7cddfSDavid du Colombier 	item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
967dd7cddfSDavid du Colombier 	pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
977dd7cddfSDavid du Colombier 	pt.y = textr.min.y+i*(font->height+Vspacing);
987dd7cddfSDavid du Colombier 	draw(m, r, highlight? high : back, nil, pt);
997dd7cddfSDavid du Colombier 	string(m, pt, highlight? htext : text, pt, font, item);
1007dd7cddfSDavid du Colombier }
1017dd7cddfSDavid du Colombier 
1027dd7cddfSDavid du Colombier /*
1037dd7cddfSDavid du Colombier  * menur is a rectangle holding all the highlightable text elements.
1047dd7cddfSDavid du Colombier  * track mouse while inside the box, return what's selected when button
1057dd7cddfSDavid du Colombier  * is raised, -1 as soon as it leaves box.
1067dd7cddfSDavid du Colombier  * invariant: nothing is highlighted on entry or exit.
1077dd7cddfSDavid du Colombier  */
1087dd7cddfSDavid du Colombier static int
menuscan(Image * m,Menu * menu,int but,Mousectl * mc,Rectangle textr,int off,int lasti,Image * save)1097dd7cddfSDavid du Colombier menuscan(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save)
1107dd7cddfSDavid du Colombier {
1117dd7cddfSDavid du Colombier 	int i;
1127dd7cddfSDavid du Colombier 
1137dd7cddfSDavid du Colombier 	paintitem(m, menu, textr, off, lasti, 1, save, nil);
1147dd7cddfSDavid du Colombier 	for(readmouse(mc); mc->buttons & (1<<(but-1)); readmouse(mc)){
1157dd7cddfSDavid du Colombier 		i = menusel(textr, mc->xy);
1167dd7cddfSDavid du Colombier 		if(i != -1 && i == lasti)
1177dd7cddfSDavid du Colombier 			continue;
1187dd7cddfSDavid du Colombier 		paintitem(m, menu, textr, off, lasti, 0, nil, save);
1197dd7cddfSDavid du Colombier 		if(i == -1)
1207dd7cddfSDavid du Colombier 			return i;
1217dd7cddfSDavid du Colombier 		lasti = i;
1227dd7cddfSDavid du Colombier 		paintitem(m, menu, textr, off, lasti, 1, save, nil);
1237dd7cddfSDavid du Colombier 	}
1247dd7cddfSDavid du Colombier 	return lasti;
1257dd7cddfSDavid du Colombier }
1267dd7cddfSDavid du Colombier 
1277dd7cddfSDavid du Colombier static void
menupaint(Image * m,Menu * menu,Rectangle textr,int off,int nitemdrawn)1287dd7cddfSDavid du Colombier menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn)
1297dd7cddfSDavid du Colombier {
1307dd7cddfSDavid du Colombier 	int i;
1317dd7cddfSDavid du Colombier 
1327dd7cddfSDavid du Colombier 	draw(m, insetrect(textr, Border-Margin), back, nil, ZP);
1337dd7cddfSDavid du Colombier 	for(i = 0; i<nitemdrawn; i++)
1347dd7cddfSDavid du Colombier 		paintitem(m, menu, textr, off, i, 0, nil, nil);
1357dd7cddfSDavid du Colombier }
1367dd7cddfSDavid du Colombier 
1377dd7cddfSDavid du Colombier static void
menuscrollpaint(Image * m,Rectangle scrollr,int off,int nitem,int nitemdrawn)1387dd7cddfSDavid du Colombier menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn)
1397dd7cddfSDavid du Colombier {
1407dd7cddfSDavid du Colombier 	Rectangle r;
1417dd7cddfSDavid du Colombier 
1427dd7cddfSDavid du Colombier 	draw(m, scrollr, back, nil, ZP);
1437dd7cddfSDavid du Colombier 	r.min.x = scrollr.min.x;
1447dd7cddfSDavid du Colombier 	r.max.x = scrollr.max.x;
1457dd7cddfSDavid du Colombier 	r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
1467dd7cddfSDavid du Colombier 	r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
1477dd7cddfSDavid du Colombier 	if(r.max.y < r.min.y+2)
1487dd7cddfSDavid du Colombier 		r.max.y = r.min.y+2;
1497dd7cddfSDavid du Colombier 	border(m, r, 1, bord, ZP);
1507dd7cddfSDavid du Colombier 	if(menutxt == 0)
151*9a747e4fSDavid du Colombier 		menutxt = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkgreen);	/* border color; BUG? */
1527dd7cddfSDavid du Colombier 	if(menutxt)
1537dd7cddfSDavid du Colombier 		draw(m, insetrect(r, 1), menutxt, nil, ZP);
1547dd7cddfSDavid du Colombier }
1557dd7cddfSDavid du Colombier 
1567dd7cddfSDavid du Colombier int
menuhit(int but,Mousectl * mc,Menu * menu,Screen * scr)1577dd7cddfSDavid du Colombier menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr)
1587dd7cddfSDavid du Colombier {
1597dd7cddfSDavid du Colombier 	int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
1607dd7cddfSDavid du Colombier 	int scrolling;
1617dd7cddfSDavid du Colombier 	Rectangle r, menur, sc, textr, scrollr;
1627dd7cddfSDavid du Colombier 	Image *b, *save, *backup;
1637dd7cddfSDavid du Colombier 	Point pt;
1647dd7cddfSDavid du Colombier 	char *item;
1657dd7cddfSDavid du Colombier 
1667dd7cddfSDavid du Colombier 	if(back == nil)
1677dd7cddfSDavid du Colombier 		menucolors();
1687dd7cddfSDavid du Colombier 	sc = screen->clipr;
1697dd7cddfSDavid du Colombier 	replclipr(screen, 0, screen->r);
1707dd7cddfSDavid du Colombier 	maxwid = 0;
1717dd7cddfSDavid du Colombier 	for(nitem = 0;
1727dd7cddfSDavid du Colombier 	    item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
1737dd7cddfSDavid du Colombier 	    nitem++){
1747dd7cddfSDavid du Colombier 		i = stringwidth(font, item);
1757dd7cddfSDavid du Colombier 		if(i > maxwid)
1767dd7cddfSDavid du Colombier 			maxwid = i;
1777dd7cddfSDavid du Colombier 	}
1787dd7cddfSDavid du Colombier 	if(menu->lasthit<0 || menu->lasthit>=nitem)
1797dd7cddfSDavid du Colombier 		menu->lasthit = 0;
1807dd7cddfSDavid du Colombier 	screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
1817dd7cddfSDavid du Colombier 	if(nitem>Maxunscroll || nitem>screenitem){
1827dd7cddfSDavid du Colombier 		scrolling = 1;
1837dd7cddfSDavid du Colombier 		nitemdrawn = Nscroll;
1847dd7cddfSDavid du Colombier 		if(nitemdrawn > screenitem)
1857dd7cddfSDavid du Colombier 			nitemdrawn = screenitem;
1867dd7cddfSDavid du Colombier 		wid = maxwid + Gap + Scrollwid;
1877dd7cddfSDavid du Colombier 		off = menu->lasthit - nitemdrawn/2;
1887dd7cddfSDavid du Colombier 		if(off < 0)
1897dd7cddfSDavid du Colombier 			off = 0;
1907dd7cddfSDavid du Colombier 		if(off > nitem-nitemdrawn)
1917dd7cddfSDavid du Colombier 			off = nitem-nitemdrawn;
1927dd7cddfSDavid du Colombier 		lasti = menu->lasthit-off;
1937dd7cddfSDavid du Colombier 	}else{
1947dd7cddfSDavid du Colombier 		scrolling = 0;
1957dd7cddfSDavid du Colombier 		nitemdrawn = nitem;
1967dd7cddfSDavid du Colombier 		wid = maxwid;
1977dd7cddfSDavid du Colombier 		off = 0;
1987dd7cddfSDavid du Colombier 		lasti = menu->lasthit;
1997dd7cddfSDavid du Colombier 	}
2007dd7cddfSDavid du Colombier 	r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
2017dd7cddfSDavid du Colombier 	r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
2027dd7cddfSDavid du Colombier 	r = rectaddpt(r, mc->xy);
2037dd7cddfSDavid du Colombier 	pt = ZP;
2047dd7cddfSDavid du Colombier 	if(r.max.x>screen->r.max.x)
2057dd7cddfSDavid du Colombier 		pt.x = screen->r.max.x-r.max.x;
2067dd7cddfSDavid du Colombier 	if(r.max.y>screen->r.max.y)
2077dd7cddfSDavid du Colombier 		pt.y = screen->r.max.y-r.max.y;
2087dd7cddfSDavid du Colombier 	if(r.min.x<screen->r.min.x)
2097dd7cddfSDavid du Colombier 		pt.x = screen->r.min.x-r.min.x;
2107dd7cddfSDavid du Colombier 	if(r.min.y<screen->r.min.y)
2117dd7cddfSDavid du Colombier 		pt.y = screen->r.min.y-r.min.y;
2127dd7cddfSDavid du Colombier 	menur = rectaddpt(r, pt);
2137dd7cddfSDavid du Colombier 	textr.max.x = menur.max.x-Margin;
2147dd7cddfSDavid du Colombier 	textr.min.x = textr.max.x-maxwid;
2157dd7cddfSDavid du Colombier 	textr.min.y = menur.min.y+Margin;
2167dd7cddfSDavid du Colombier 	textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
2177dd7cddfSDavid du Colombier 	if(scrolling){
2187dd7cddfSDavid du Colombier 		scrollr = insetrect(menur, Border);
2197dd7cddfSDavid du Colombier 		scrollr.max.x = scrollr.min.x+Scrollwid;
2207dd7cddfSDavid du Colombier 	}else
2217dd7cddfSDavid du Colombier 		scrollr = Rect(0, 0, 0, 0);
2227dd7cddfSDavid du Colombier 
2237dd7cddfSDavid du Colombier 	if(scr){
2247dd7cddfSDavid du Colombier 		b = allocwindow(scr, menur, Refbackup, DWhite);
2257dd7cddfSDavid du Colombier 		if(b == nil)
2267dd7cddfSDavid du Colombier 			b = screen;
2277dd7cddfSDavid du Colombier 		backup = nil;
2287dd7cddfSDavid du Colombier 	}else{
2297dd7cddfSDavid du Colombier 		b = screen;
2307dd7cddfSDavid du Colombier 		backup = allocimage(display, menur, screen->chan, 0, -1);
2317dd7cddfSDavid du Colombier 		if(backup)
2327dd7cddfSDavid du Colombier 			draw(backup, menur, screen, nil, menur.min);
2337dd7cddfSDavid du Colombier 	}
2347dd7cddfSDavid du Colombier 	draw(b, menur, back, nil, ZP);
2357dd7cddfSDavid du Colombier 	border(b, menur, Blackborder, bord, ZP);
2367dd7cddfSDavid du Colombier 	save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
2377dd7cddfSDavid du Colombier 	r = menurect(textr, lasti);
2387dd7cddfSDavid du Colombier 	moveto(mc, divpt(addpt(r.min, r.max), 2));
2397dd7cddfSDavid du Colombier 	menupaint(b, menu, textr, off, nitemdrawn);
2407dd7cddfSDavid du Colombier 	if(scrolling)
2417dd7cddfSDavid du Colombier 		menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
2427dd7cddfSDavid du Colombier 	while(mc->buttons & (1<<(but-1))){
2437dd7cddfSDavid du Colombier 		lasti = menuscan(b, menu, but, mc, textr, off, lasti, save);
2447dd7cddfSDavid du Colombier 		if(lasti >= 0)
2457dd7cddfSDavid du Colombier 			break;
2467dd7cddfSDavid du Colombier 		while(!ptinrect(mc->xy, textr) && (mc->buttons & (1<<(but-1)))){
2477dd7cddfSDavid du Colombier 			if(scrolling && ptinrect(mc->xy, scrollr)){
2487dd7cddfSDavid du Colombier 				noff = ((mc->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
2497dd7cddfSDavid du Colombier 				noff -= nitemdrawn/2;
2507dd7cddfSDavid du Colombier 				if(noff < 0)
2517dd7cddfSDavid du Colombier 					noff = 0;
2527dd7cddfSDavid du Colombier 				if(noff > nitem-nitemdrawn)
2537dd7cddfSDavid du Colombier 					noff = nitem-nitemdrawn;
2547dd7cddfSDavid du Colombier 				if(noff != off){
2557dd7cddfSDavid du Colombier 					off = noff;
2567dd7cddfSDavid du Colombier 					menupaint(b, menu, textr, off, nitemdrawn);
2577dd7cddfSDavid du Colombier 					menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
2587dd7cddfSDavid du Colombier 				}
2597dd7cddfSDavid du Colombier 			}
2607dd7cddfSDavid du Colombier 			readmouse(mc);
2617dd7cddfSDavid du Colombier 		}
2627dd7cddfSDavid du Colombier 	}
2637dd7cddfSDavid du Colombier 	if(b != screen)
2647dd7cddfSDavid du Colombier 		freeimage(b);
2657dd7cddfSDavid du Colombier 	if(backup){
2667dd7cddfSDavid du Colombier 		draw(screen, menur, backup, nil, menur.min);
2677dd7cddfSDavid du Colombier 		freeimage(backup);
2687dd7cddfSDavid du Colombier 	}
2697dd7cddfSDavid du Colombier 	freeimage(save);
2707dd7cddfSDavid du Colombier 	replclipr(screen, 0, sc);
2717dd7cddfSDavid du Colombier 	flushimage(display, 1);
2727dd7cddfSDavid du Colombier 	if(lasti >= 0){
2737dd7cddfSDavid du Colombier 		menu->lasthit = lasti+off;
2747dd7cddfSDavid du Colombier 		return menu->lasthit;
2757dd7cddfSDavid du Colombier 	}
2767dd7cddfSDavid du Colombier 	return -1;
2777dd7cddfSDavid du Colombier }
278