xref: /plan9-contrib/sys/src/libdraw/menuhit.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <mouse.h>
6 
7 enum
8 {
9 	Margin = 4,		/* outside to text */
10 	Border = 2,		/* outside to selection boxes */
11 	Blackborder = 2,	/* width of outlining border */
12 	Vspacing = 2,		/* extra spacing between lines of text */
13 	Maxunscroll = 25,	/* maximum #entries before scrolling turns on */
14 	Nscroll = 20,		/* number entries in scrolling part */
15 	Scrollwid = 14,		/* width of scroll bar */
16 	Gap = 4,			/* between text and scroll bar */
17 };
18 
19 static	Image	*menutxt;
20 static	Image	*back;
21 static	Image	*high;
22 static	Image	*bord;
23 static	Image	*text;
24 static	Image	*htext;
25 
26 static
27 void
menucolors(void)28 menucolors(void)
29 {
30 	/* Main tone is greenish, with negative selection */
31 	back = allocimagemix(display, DPalegreen, DWhite);
32 	high = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkgreen);	/* dark green */
33 	bord = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedgreen);	/* not as dark green */
34 	if(back==nil || high==nil || bord==nil)
35 		goto Error;
36 	text = display->black;
37 	htext = back;
38 	return;
39 
40     Error:
41 	freeimage(back);
42 	freeimage(high);
43 	freeimage(bord);
44 	back = display->white;
45 	high = display->black;
46 	bord = display->black;
47 	text = display->black;
48 	htext = display->white;
49 }
50 
51 /*
52  * r is a rectangle holding the text elements.
53  * return the rectangle, including its black edge, holding element i.
54  */
55 static Rectangle
menurect(Rectangle r,int i)56 menurect(Rectangle r, int i)
57 {
58 	if(i < 0)
59 		return Rect(0, 0, 0, 0);
60 	r.min.y += (font->height+Vspacing)*i;
61 	r.max.y = r.min.y+font->height+Vspacing;
62 	return insetrect(r, Border-Margin);
63 }
64 
65 /*
66  * r is a rectangle holding the text elements.
67  * return the element number containing p.
68  */
69 static int
menusel(Rectangle r,Point p)70 menusel(Rectangle r, Point p)
71 {
72 	r = insetrect(r, Margin);
73 	if(!ptinrect(p, r))
74 		return -1;
75 	return (p.y-r.min.y)/(font->height+Vspacing);
76 }
77 
78 static
79 void
paintitem(Image * m,Menu * menu,Rectangle textr,int off,int i,int highlight,Image * save,Image * restore)80 paintitem(Image *m, Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
81 {
82 	char *item;
83 	Rectangle r;
84 	Point pt;
85 
86 	if(i < 0)
87 		return;
88 	r = menurect(textr, i);
89 	if(restore){
90 		draw(m, r, restore, nil, restore->r.min);
91 		return;
92 	}
93 	if(save)
94 		draw(save, save->r, m, nil, r.min);
95 	item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
96 	pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
97 	pt.y = textr.min.y+i*(font->height+Vspacing);
98 	draw(m, r, highlight? high : back, nil, pt);
99 	string(m, pt, highlight? htext : text, pt, font, item);
100 }
101 
102 /*
103  * menur is a rectangle holding all the highlightable text elements.
104  * track mouse while inside the box, return what's selected when button
105  * is raised, -1 as soon as it leaves box.
106  * invariant: nothing is highlighted on entry or exit.
107  */
108 static int
menuscan(Image * m,Menu * menu,int but,Mousectl * mc,Rectangle textr,int off,int lasti,Image * save)109 menuscan(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save)
110 {
111 	int i;
112 
113 	paintitem(m, menu, textr, off, lasti, 1, save, nil);
114 	for(readmouse(mc); mc->buttons & (1<<(but-1)); readmouse(mc)){
115 		i = menusel(textr, mc->xy);
116 		if(i != -1 && i == lasti)
117 			continue;
118 		paintitem(m, menu, textr, off, lasti, 0, nil, save);
119 		if(i == -1)
120 			return i;
121 		lasti = i;
122 		paintitem(m, menu, textr, off, lasti, 1, save, nil);
123 	}
124 	return lasti;
125 }
126 
127 static void
menupaint(Image * m,Menu * menu,Rectangle textr,int off,int nitemdrawn)128 menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn)
129 {
130 	int i;
131 
132 	draw(m, insetrect(textr, Border-Margin), back, nil, ZP);
133 	for(i = 0; i<nitemdrawn; i++)
134 		paintitem(m, menu, textr, off, i, 0, nil, nil);
135 }
136 
137 static void
menuscrollpaint(Image * m,Rectangle scrollr,int off,int nitem,int nitemdrawn)138 menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn)
139 {
140 	Rectangle r;
141 
142 	draw(m, scrollr, back, nil, ZP);
143 	r.min.x = scrollr.min.x;
144 	r.max.x = scrollr.max.x;
145 	r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
146 	r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
147 	if(r.max.y < r.min.y+2)
148 		r.max.y = r.min.y+2;
149 	border(m, r, 1, bord, ZP);
150 	if(menutxt == 0)
151 		menutxt = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkgreen);	/* border color; BUG? */
152 	if(menutxt)
153 		draw(m, insetrect(r, 1), menutxt, nil, ZP);
154 }
155 
156 int
menuhit(int but,Mousectl * mc,Menu * menu,Screen * scr)157 menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr)
158 {
159 	int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
160 	int scrolling;
161 	Rectangle r, menur, sc, textr, scrollr;
162 	Image *b, *save, *backup;
163 	Point pt;
164 	char *item;
165 
166 	if(back == nil)
167 		menucolors();
168 	sc = screen->clipr;
169 	replclipr(screen, 0, screen->r);
170 	maxwid = 0;
171 	for(nitem = 0;
172 	    item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
173 	    nitem++){
174 		i = stringwidth(font, item);
175 		if(i > maxwid)
176 			maxwid = i;
177 	}
178 	if(menu->lasthit<0 || menu->lasthit>=nitem)
179 		menu->lasthit = 0;
180 	screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
181 	if(nitem>Maxunscroll || nitem>screenitem){
182 		scrolling = 1;
183 		nitemdrawn = Nscroll;
184 		if(nitemdrawn > screenitem)
185 			nitemdrawn = screenitem;
186 		wid = maxwid + Gap + Scrollwid;
187 		off = menu->lasthit - nitemdrawn/2;
188 		if(off < 0)
189 			off = 0;
190 		if(off > nitem-nitemdrawn)
191 			off = nitem-nitemdrawn;
192 		lasti = menu->lasthit-off;
193 	}else{
194 		scrolling = 0;
195 		nitemdrawn = nitem;
196 		wid = maxwid;
197 		off = 0;
198 		lasti = menu->lasthit;
199 	}
200 	r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
201 	r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
202 	r = rectaddpt(r, mc->xy);
203 	pt = ZP;
204 	if(r.max.x>screen->r.max.x)
205 		pt.x = screen->r.max.x-r.max.x;
206 	if(r.max.y>screen->r.max.y)
207 		pt.y = screen->r.max.y-r.max.y;
208 	if(r.min.x<screen->r.min.x)
209 		pt.x = screen->r.min.x-r.min.x;
210 	if(r.min.y<screen->r.min.y)
211 		pt.y = screen->r.min.y-r.min.y;
212 	menur = rectaddpt(r, pt);
213 	textr.max.x = menur.max.x-Margin;
214 	textr.min.x = textr.max.x-maxwid;
215 	textr.min.y = menur.min.y+Margin;
216 	textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
217 	if(scrolling){
218 		scrollr = insetrect(menur, Border);
219 		scrollr.max.x = scrollr.min.x+Scrollwid;
220 	}else
221 		scrollr = Rect(0, 0, 0, 0);
222 
223 	if(scr){
224 		b = allocwindow(scr, menur, Refbackup, DWhite);
225 		if(b == nil)
226 			b = screen;
227 		backup = nil;
228 	}else{
229 		b = screen;
230 		backup = allocimage(display, menur, screen->chan, 0, -1);
231 		if(backup)
232 			draw(backup, menur, screen, nil, menur.min);
233 	}
234 	draw(b, menur, back, nil, ZP);
235 	border(b, menur, Blackborder, bord, ZP);
236 	save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
237 	r = menurect(textr, lasti);
238 	moveto(mc, divpt(addpt(r.min, r.max), 2));
239 	menupaint(b, menu, textr, off, nitemdrawn);
240 	if(scrolling)
241 		menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
242 	while(mc->buttons & (1<<(but-1))){
243 		lasti = menuscan(b, menu, but, mc, textr, off, lasti, save);
244 		if(lasti >= 0)
245 			break;
246 		while(!ptinrect(mc->xy, textr) && (mc->buttons & (1<<(but-1)))){
247 			if(scrolling && ptinrect(mc->xy, scrollr)){
248 				noff = ((mc->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
249 				noff -= nitemdrawn/2;
250 				if(noff < 0)
251 					noff = 0;
252 				if(noff > nitem-nitemdrawn)
253 					noff = nitem-nitemdrawn;
254 				if(noff != off){
255 					off = noff;
256 					menupaint(b, menu, textr, off, nitemdrawn);
257 					menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
258 				}
259 			}
260 			readmouse(mc);
261 		}
262 	}
263 	if(b != screen)
264 		freeimage(b);
265 	if(backup){
266 		draw(screen, menur, backup, nil, menur.min);
267 		freeimage(backup);
268 	}
269 	freeimage(save);
270 	replclipr(screen, 0, sc);
271 	flushimage(display, 1);
272 	if(lasti >= 0){
273 		menu->lasthit = lasti+off;
274 		return menu->lasthit;
275 	}
276 	return -1;
277 }
278