menuhit.c 7.1 KB

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