| #include <u.h> | 
 | #include <libc.h> | 
 | #include <draw.h> | 
 | #include <thread.h> | 
 | #include <mouse.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), screen->chan, 1, DDarkgreen);	/* dark green */ | 
 | 	bord = allocimage(display, Rect(0,0,1,1), screen->chan, 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(Image *m, 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(m, r, restore, nil, restore->r.min); | 
 | 		return; | 
 | 	} | 
 | 	if(save) | 
 | 		draw(save, save->r, m, 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(m, r, highlight? high : back, nil, pt); | 
 | 	string(m, 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(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	paintitem(m, menu, textr, off, lasti, 1, save, nil); | 
 | 	for(readmouse(mc); mc->m.buttons & (1<<(but-1)); readmouse(mc)){ | 
 | 		i = menusel(textr, mc->m.xy); | 
 | 		if(i != -1 && i == lasti) | 
 | 			continue; | 
 | 		paintitem(m, menu, textr, off, lasti, 0, nil, save); | 
 | 		if(i == -1) | 
 | 			return i; | 
 | 		lasti = i; | 
 | 		paintitem(m, menu, textr, off, lasti, 1, save, nil); | 
 | 	} | 
 | 	return lasti; | 
 | } | 
 |  | 
 | static void | 
 | menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	draw(m, insetrect(textr, Border-Margin), back, nil, ZP); | 
 | 	for(i = 0; i<nitemdrawn; i++) | 
 | 		paintitem(m, menu, textr, off, i, 0, nil, nil); | 
 | } | 
 |  | 
 | static void | 
 | menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn) | 
 | { | 
 | 	Rectangle r; | 
 |  | 
 | 	draw(m, 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(m, r, 1, bord, ZP); | 
 | 	if(menutxt == 0) | 
 | 		menutxt = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkgreen);	/* border color; BUG? */ | 
 | 	if(menutxt) | 
 | 		draw(m, insetrect(r, 1), menutxt, nil, ZP); | 
 | } | 
 |  | 
 | int | 
 | menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr) | 
 | { | 
 | 	int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem; | 
 | 	int scrolling; | 
 | 	Rectangle r, menur, sc, textr, scrollr; | 
 | 	Image *b, *save, *backup; | 
 | 	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, mc->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); | 
 |  | 
 | 	if(scr){ | 
 | 		b = allocwindow(scr, menur, Refbackup, DWhite); | 
 | 		if(b == nil) | 
 | 			b = screen; | 
 | 		backup = nil; | 
 | 	}else{ | 
 | 		b = screen; | 
 | 		backup = allocimage(display, menur, screen->chan, 0, -1); | 
 | 		if(backup) | 
 | 			draw(backup, menur, screen, nil, menur.min); | 
 | 	} | 
 | 	draw(b, menur, back, nil, ZP); | 
 | 	border(b, menur, Blackborder, bord, ZP); | 
 | 	save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1); | 
 | 	r = menurect(textr, lasti); | 
 | 	moveto(mc, divpt(addpt(r.min, r.max), 2)); | 
 | 	menupaint(b, menu, textr, off, nitemdrawn); | 
 | 	if(scrolling) | 
 | 		menuscrollpaint(b, scrollr, off, nitem, nitemdrawn); | 
 | 	while(mc->m.buttons & (1<<(but-1))){ | 
 | 		lasti = menuscan(b, menu, but, mc, textr, off, lasti, save); | 
 | 		if(lasti >= 0) | 
 | 			break; | 
 | 		while(!ptinrect(mc->m.xy, textr) && (mc->m.buttons & (1<<(but-1)))){ | 
 | 			if(scrolling && ptinrect(mc->m.xy, scrollr)){ | 
 | 				noff = ((mc->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(b, menu, textr, off, nitemdrawn); | 
 | 					menuscrollpaint(b, scrollr, off, nitem, nitemdrawn); | 
 | 				} | 
 | 			} | 
 | 			readmouse(mc); | 
 | 		} | 
 | 	} | 
 | 	if(b != screen) | 
 | 		freeimage(b); | 
 | 	if(backup){ | 
 | 		draw(screen, menur, backup, nil, menur.min); | 
 | 		freeimage(backup); | 
 | 	} | 
 | 	freeimage(save); | 
 | 	replclipr(screen, 0, sc); | 
 | 	flushimage(display, 1); | 
 | 	if(lasti >= 0){ | 
 | 		menu->lasthit = lasti+off; | 
 | 		return menu->lasthit; | 
 | 	} | 
 | 	return -1; | 
 | } |