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