]> git.lizzy.rs Git - plan9front.git/blob - sys/src/libdraw/menuhit.c
acme: fix border size, autoindent undo: imported from plan9port (thanks jxy)
[plan9front.git] / sys / src / libdraw / menuhit.c
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
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
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
70 menusel(Rectangle r, Point p)
71 {
72         if(!ptinrect(p, r))
73                 return -1;
74         return (p.y-r.min.y)/(font->height+Vspacing);
75 }
76
77 static
78 void
79 paintitem(Image *m, 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(m, r, restore, nil, restore->r.min);
90                 return;
91         }
92         if(save)
93                 draw(save, save->r, m, 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(m, r, highlight? high : back, nil, pt);
98         string(m, 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
108 menuscan(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save)
109 {
110         int i;
111
112         paintitem(m, menu, textr, off, lasti, 1, save, nil);
113         for(readmouse(mc); mc->buttons & (1<<(but-1)); readmouse(mc)){
114                 i = menusel(textr, mc->xy);
115                 if(i != -1 && i == lasti)
116                         continue;
117                 paintitem(m, menu, textr, off, lasti, 0, nil, save);
118                 if(i == -1)
119                         return i;
120                 lasti = i;
121                 paintitem(m, menu, textr, off, lasti, 1, save, nil);
122         }
123         return lasti;
124 }
125
126 static void
127 menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn)
128 {
129         int i;
130
131         draw(m, insetrect(textr, Border-Margin), back, nil, ZP);
132         for(i = 0; i<nitemdrawn; i++)
133                 paintitem(m, menu, textr, off, i, 0, nil, nil);
134 }
135
136 static void
137 menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn)
138 {
139         Rectangle r;
140
141         draw(m, scrollr, back, nil, ZP);
142         r.min.x = scrollr.min.x;
143         r.max.x = scrollr.max.x;
144         r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
145         r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
146         if(r.max.y < r.min.y+2)
147                 r.max.y = r.min.y+2;
148         border(m, r, 1, bord, ZP);
149         if(menutxt == 0)
150                 menutxt = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkgreen);   /* border color; BUG? */
151         if(menutxt)
152                 draw(m, insetrect(r, 1), menutxt, nil, ZP);
153 }
154
155 int
156 menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr)
157 {
158         int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
159         int scrolling;
160         Rectangle r, menur, sc, textr, scrollr;
161         Image *b, *save, *backup;
162         Point pt;
163         char *item;
164
165         if(back == nil)
166                 menucolors();
167         sc = screen->clipr;
168         replclipr(screen, 0, screen->r);
169         maxwid = 0;
170         for(nitem = 0;
171             item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
172             nitem++){
173                 i = stringwidth(font, item);
174                 if(i > maxwid)
175                         maxwid = i;
176         }
177         if(menu->lasthit<0 || menu->lasthit>=nitem)
178                 menu->lasthit = 0;
179         screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
180         if(nitem>Maxunscroll || nitem>screenitem){
181                 scrolling = 1;
182                 nitemdrawn = Nscroll;
183                 if(nitemdrawn > screenitem)
184                         nitemdrawn = screenitem;
185                 wid = maxwid + Gap + Scrollwid;
186                 off = menu->lasthit - nitemdrawn/2;
187                 if(off < 0)
188                         off = 0;
189                 if(off > nitem-nitemdrawn)
190                         off = nitem-nitemdrawn;
191                 lasti = menu->lasthit-off;
192         }else{
193                 scrolling = 0;
194                 nitemdrawn = nitem;
195                 wid = maxwid;
196                 off = 0;
197                 lasti = menu->lasthit;
198         }
199         r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
200         r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
201         r = rectaddpt(r, mc->xy);
202         pt = ZP;
203         if(r.max.x>screen->r.max.x)
204                 pt.x = screen->r.max.x-r.max.x;
205         if(r.max.y>screen->r.max.y)
206                 pt.y = screen->r.max.y-r.max.y;
207         if(r.min.x<screen->r.min.x)
208                 pt.x = screen->r.min.x-r.min.x;
209         if(r.min.y<screen->r.min.y)
210                 pt.y = screen->r.min.y-r.min.y;
211         menur = rectaddpt(r, pt);
212         textr.max.x = menur.max.x-Margin;
213         textr.min.x = textr.max.x-maxwid;
214         textr.min.y = menur.min.y+Margin;
215         textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
216         if(scrolling){
217                 scrollr = insetrect(menur, Border);
218                 scrollr.max.x = scrollr.min.x+Scrollwid;
219         }else
220                 scrollr = Rect(0, 0, 0, 0);
221
222         if(scr){
223                 b = allocwindow(scr, menur, Refbackup, DWhite);
224                 if(b == nil)
225                         b = screen;
226                 backup = nil;
227         }else{
228                 b = screen;
229                 backup = allocimage(display, menur, screen->chan, 0, -1);
230                 if(backup)
231                         draw(backup, menur, screen, nil, menur.min);
232         }
233         draw(b, menur, back, nil, ZP);
234         border(b, menur, Blackborder, bord, ZP);
235         save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
236         r = menurect(textr, lasti);
237         if(pt.x || pt.y)
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 }