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