plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

menuhit.c (7278B)


      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 	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
     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
    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->m.buttons & (1<<(but-1)); readmouse(mc)){
    115 		i = menusel(textr, mc->m.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
    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
    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
    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->m.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->m.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->m.xy, textr) && (mc->m.buttons & (1<<(but-1)))){
    247 			if(scrolling && ptinrect(mc->m.xy, scrollr)){
    248 				noff = ((mc->m.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 }