gl-ui.c 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. // Copyright (C) 2004-2022 Artifex Software, Inc.
  2. //
  3. // This file is part of MuPDF.
  4. //
  5. // MuPDF is free software: you can redistribute it and/or modify it under the
  6. // terms of the GNU Affero General Public License as published by the Free
  7. // Software Foundation, either version 3 of the License, or (at your option)
  8. // any later version.
  9. //
  10. // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
  11. // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. // details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
  17. //
  18. // Alternative licensing terms are available from the licensor.
  19. // For commercial licensing, see <https://www.artifex.com/> or contact
  20. // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
  21. // CA 94129, USA, for further information.
  22. #include "gl-app.h"
  23. #include <string.h>
  24. #include <stdlib.h>
  25. #include <stdio.h>
  26. #ifndef FREEGLUT
  27. /* freeglut extension no-ops */
  28. void glutExit(void) {}
  29. void glutMouseWheelFunc(void *fn) {}
  30. void glutInitErrorFunc(void *fn) {}
  31. void glutInitWarningFunc(void *fn) {}
  32. #define glutSetOption(X,Y)
  33. #endif
  34. enum
  35. {
  36. /* Default UI sizes */
  37. DEFAULT_UI_FONTSIZE = 15,
  38. DEFAULT_UI_BASELINE = 14,
  39. DEFAULT_UI_LINEHEIGHT = 18,
  40. DEFAULT_UI_GRIDSIZE = DEFAULT_UI_LINEHEIGHT + 6,
  41. };
  42. struct ui ui;
  43. #if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
  44. void ui_set_clipboard(const char *buf)
  45. {
  46. glutSetClipboard(GLUT_PRIMARY, buf);
  47. glutSetClipboard(GLUT_CLIPBOARD, buf);
  48. }
  49. const char *ui_get_clipboard(void)
  50. {
  51. return glutGetClipboard(GLUT_CLIPBOARD);
  52. }
  53. #else
  54. static char *clipboard_buffer = NULL;
  55. void ui_set_clipboard(const char *buf)
  56. {
  57. fz_free(ctx, clipboard_buffer);
  58. clipboard_buffer = fz_strdup(ctx, buf);
  59. }
  60. const char *ui_get_clipboard(void)
  61. {
  62. return clipboard_buffer;
  63. }
  64. #endif
  65. static const char *ogl_error_string(GLenum code)
  66. {
  67. #define CASE(E) case E: return #E; break
  68. switch (code)
  69. {
  70. /* glGetError */
  71. CASE(GL_NO_ERROR);
  72. CASE(GL_INVALID_ENUM);
  73. CASE(GL_INVALID_VALUE);
  74. CASE(GL_INVALID_OPERATION);
  75. CASE(GL_OUT_OF_MEMORY);
  76. CASE(GL_STACK_UNDERFLOW);
  77. CASE(GL_STACK_OVERFLOW);
  78. default: return "(unknown)";
  79. }
  80. #undef CASE
  81. }
  82. static int has_ARB_texture_non_power_of_two = 1;
  83. static GLint max_texture_size = 8192;
  84. void ui_init_draw(void)
  85. {
  86. }
  87. static unsigned int next_power_of_two(unsigned int n)
  88. {
  89. --n;
  90. n |= n >> 1;
  91. n |= n >> 2;
  92. n |= n >> 4;
  93. n |= n >> 8;
  94. n |= n >> 16;
  95. return ++n;
  96. }
  97. void ui_texture_from_pixmap(struct texture *tex, fz_pixmap *pix)
  98. {
  99. if (!tex->id)
  100. glGenTextures(1, &tex->id);
  101. glBindTexture(GL_TEXTURE_2D, tex->id);
  102. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  103. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  104. tex->x = pix->x;
  105. tex->y = pix->y;
  106. tex->w = pix->w;
  107. tex->h = pix->h;
  108. if (has_ARB_texture_non_power_of_two)
  109. {
  110. if (tex->w > max_texture_size || tex->h > max_texture_size)
  111. fz_warn(ctx, "texture size (%d x %d) exceeds implementation limit (%d)", tex->w, tex->h, max_texture_size);
  112. glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  113. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->w, tex->h, 0, pix->n == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, pix->samples);
  114. tex->s = 1;
  115. tex->t = 1;
  116. }
  117. else
  118. {
  119. int w2 = next_power_of_two(tex->w);
  120. int h2 = next_power_of_two(tex->h);
  121. if (w2 > max_texture_size || h2 > max_texture_size)
  122. fz_warn(ctx, "texture size (%d x %d) exceeds implementation limit (%d)", w2, h2, max_texture_size);
  123. glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  124. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w2, h2, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  125. glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex->w, tex->h, pix->n == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, pix->samples);
  126. tex->s = (float) tex->w / w2;
  127. tex->t = (float) tex->h / h2;
  128. }
  129. }
  130. void ui_draw_image(struct texture *tex, float x, float y)
  131. {
  132. glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  133. glEnable(GL_BLEND);
  134. glBindTexture(GL_TEXTURE_2D, tex->id);
  135. glEnable(GL_TEXTURE_2D);
  136. glBegin(GL_TRIANGLE_STRIP);
  137. {
  138. glColor4f(1, 1, 1, 1);
  139. glTexCoord2f(0, tex->t);
  140. glVertex2f(x + tex->x, y + tex->y + tex->h);
  141. glTexCoord2f(0, 0);
  142. glVertex2f(x + tex->x, y + tex->y);
  143. glTexCoord2f(tex->s, tex->t);
  144. glVertex2f(x + tex->x + tex->w, y + tex->y + tex->h);
  145. glTexCoord2f(tex->s, 0);
  146. glVertex2f(x + tex->x + tex->w, y + tex->y);
  147. }
  148. glEnd();
  149. glDisable(GL_TEXTURE_2D);
  150. glDisable(GL_BLEND);
  151. }
  152. void glColorHex(unsigned int hex)
  153. {
  154. float r = ((hex>>16)&0xff) / 255.0f;
  155. float g = ((hex>>8)&0xff) / 255.0f;
  156. float b = ((hex)&0xff) / 255.0f;
  157. glColor3f(r, g, b);
  158. }
  159. void ui_draw_bevel_imp(fz_irect area, unsigned ot, unsigned it, unsigned ib, unsigned ob)
  160. {
  161. glColorHex(ot);
  162. glRectf(area.x0, area.y0, area.x1-1, area.y0+1);
  163. glRectf(area.x0, area.y0+1, area.x0+1, area.y1-1);
  164. glColorHex(ob);
  165. glRectf(area.x1-1, area.y0, area.x1, area.y1);
  166. glRectf(area.x0, area.y1-1, area.x1-1, area.y1);
  167. glColorHex(it);
  168. glRectf(area.x0+1, area.y0+1, area.x1-2, area.y0+2);
  169. glRectf(area.x0+1, area.y0+2, area.x0+2, area.y1-2);
  170. glColorHex(ib);
  171. glRectf(area.x1-2, area.y0+1, area.x1-1, area.y1-1);
  172. glRectf(area.x0+1, area.y1-2, area.x1-2, area.y1-1);
  173. }
  174. void ui_draw_bevel(fz_irect area, int depressed)
  175. {
  176. if (depressed)
  177. ui_draw_bevel_imp(area, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4);
  178. else
  179. ui_draw_bevel_imp(area, UI_COLOR_BEVEL_4, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1);
  180. }
  181. void ui_draw_ibevel(fz_irect area, int depressed)
  182. {
  183. if (depressed)
  184. ui_draw_bevel_imp(area, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4);
  185. else
  186. ui_draw_bevel_imp(area, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1);
  187. }
  188. void ui_draw_bevel_rect(fz_irect area, unsigned int fill, int depressed)
  189. {
  190. ui_draw_bevel(area, depressed);
  191. glColorHex(fill);
  192. glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2);
  193. }
  194. void ui_draw_ibevel_rect(fz_irect area, unsigned int fill, int depressed)
  195. {
  196. ui_draw_ibevel(area, depressed);
  197. glColorHex(fill);
  198. glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2);
  199. }
  200. #if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
  201. static void on_keyboard(int key, int x, int y)
  202. #else
  203. static void on_keyboard(unsigned char key, int x, int y)
  204. #endif
  205. {
  206. #ifdef __APPLE__
  207. /* Apple's GLUT has swapped DELETE and BACKSPACE */
  208. if (key == 8)
  209. key = 127;
  210. else if (key == 127)
  211. key = 8;
  212. #endif
  213. ui.x = x;
  214. ui.y = y;
  215. ui.key = key;
  216. ui.mod = glutGetModifiers();
  217. ui.plain = !(ui.mod & ~GLUT_ACTIVE_SHIFT);
  218. run_main_loop();
  219. ui.key = ui.plain = 0;
  220. ui_invalidate(); // TODO: leave this to caller
  221. }
  222. static void on_special(int key, int x, int y)
  223. {
  224. ui.x = x;
  225. ui.y = y;
  226. ui.key = 0;
  227. switch (key)
  228. {
  229. case GLUT_KEY_INSERT: ui.key = KEY_INSERT; break;
  230. #ifdef GLUT_KEY_DELETE
  231. case GLUT_KEY_DELETE: ui.key = KEY_DELETE; break;
  232. #endif
  233. case GLUT_KEY_RIGHT: ui.key = KEY_RIGHT; break;
  234. case GLUT_KEY_LEFT: ui.key = KEY_LEFT; break;
  235. case GLUT_KEY_DOWN: ui.key = KEY_DOWN; break;
  236. case GLUT_KEY_UP: ui.key = KEY_UP; break;
  237. case GLUT_KEY_PAGE_UP: ui.key = KEY_PAGE_UP; break;
  238. case GLUT_KEY_PAGE_DOWN: ui.key = KEY_PAGE_DOWN; break;
  239. case GLUT_KEY_HOME: ui.key = KEY_HOME; break;
  240. case GLUT_KEY_END: ui.key = KEY_END; break;
  241. case GLUT_KEY_F1: ui.key = KEY_F1; break;
  242. case GLUT_KEY_F2: ui.key = KEY_F2; break;
  243. case GLUT_KEY_F3: ui.key = KEY_F3; break;
  244. case GLUT_KEY_F4: ui.key = KEY_F4; break;
  245. case GLUT_KEY_F5: ui.key = KEY_F5; break;
  246. case GLUT_KEY_F6: ui.key = KEY_F6; break;
  247. case GLUT_KEY_F7: ui.key = KEY_F7; break;
  248. case GLUT_KEY_F8: ui.key = KEY_F8; break;
  249. case GLUT_KEY_F9: ui.key = KEY_F9; break;
  250. case GLUT_KEY_F10: ui.key = KEY_F10; break;
  251. case GLUT_KEY_F11: ui.key = KEY_F11; break;
  252. case GLUT_KEY_F12: ui.key = KEY_F12; break;
  253. }
  254. if (ui.key)
  255. {
  256. ui.mod = glutGetModifiers();
  257. ui.plain = !(ui.mod & ~GLUT_ACTIVE_SHIFT);
  258. run_main_loop();
  259. ui.key = ui.plain = 0;
  260. ui_invalidate(); // TODO: leave this to caller
  261. }
  262. }
  263. static void on_wheel(int wheel, int direction, int x, int y)
  264. {
  265. ui.scroll_x = wheel == 1 ? direction : 0;
  266. ui.scroll_y = wheel == 0 ? direction : 0;
  267. ui.mod = glutGetModifiers();
  268. run_main_loop();
  269. ui_invalidate(); // TODO: leave this to caller
  270. ui.scroll_x = ui.scroll_y = 0;
  271. }
  272. static void on_mouse(int button, int action, int x, int y)
  273. {
  274. ui.x = x;
  275. ui.y = y;
  276. if (action == GLUT_DOWN)
  277. {
  278. switch (button)
  279. {
  280. case GLUT_LEFT_BUTTON:
  281. ui.down_x = x;
  282. ui.down_y = y;
  283. ui.down = 1;
  284. break;
  285. case GLUT_MIDDLE_BUTTON:
  286. ui.middle_x = x;
  287. ui.middle_y = y;
  288. ui.middle = 1;
  289. break;
  290. case GLUT_RIGHT_BUTTON:
  291. ui.right_x = x;
  292. ui.right_y = y;
  293. ui.right = 1;
  294. break;
  295. case 3: on_wheel(0, 1, x, y); break;
  296. case 4: on_wheel(0, -1, x, y); break;
  297. case 5: on_wheel(1, 1, x, y); break;
  298. case 6: on_wheel(1, -1, x, y); break;
  299. }
  300. }
  301. else if (action == GLUT_UP)
  302. {
  303. switch (button)
  304. {
  305. case GLUT_LEFT_BUTTON: ui.down = 0; break;
  306. case GLUT_MIDDLE_BUTTON: ui.middle = 0; break;
  307. case GLUT_RIGHT_BUTTON: ui.right = 0; break;
  308. }
  309. }
  310. ui.mod = glutGetModifiers();
  311. run_main_loop();
  312. ui_invalidate(); // TODO: leave this to caller
  313. }
  314. static void on_motion(int x, int y)
  315. {
  316. ui.x = x;
  317. ui.y = y;
  318. ui_invalidate();
  319. }
  320. static void on_passive_motion(int x, int y)
  321. {
  322. ui.x = x;
  323. ui.y = y;
  324. ui_invalidate();
  325. }
  326. static void on_reshape(int w, int h)
  327. {
  328. ui.window_w = w;
  329. ui.window_h = h;
  330. }
  331. static void on_display(void)
  332. {
  333. run_main_loop();
  334. }
  335. static void on_error(const char *fmt, va_list ap)
  336. {
  337. #ifdef _WIN32
  338. char buf[1000];
  339. fz_vsnprintf(buf, sizeof buf, fmt, ap);
  340. MessageBoxA(NULL, buf, "MuPDF GLUT Error", MB_ICONERROR);
  341. #else
  342. fprintf(stderr, "GLUT error: ");
  343. vfprintf(stderr, fmt, ap);
  344. fprintf(stderr, "\n");
  345. #endif
  346. }
  347. static void on_warning(const char *fmt, va_list ap)
  348. {
  349. fprintf(stderr, "GLUT warning: ");
  350. vfprintf(stderr, fmt, ap);
  351. fprintf(stderr, "\n");
  352. }
  353. static void on_timer(int timer_id)
  354. {
  355. if (reloadrequested)
  356. {
  357. reload();
  358. ui_invalidate();
  359. reloadrequested = 0;
  360. }
  361. glutTimerFunc(500, on_timer, 0);
  362. }
  363. void ui_init_dpi(float override_scale)
  364. {
  365. ui.scale = 1;
  366. if (override_scale)
  367. {
  368. ui.scale = override_scale;
  369. }
  370. else
  371. {
  372. int wmm = glutGet(GLUT_SCREEN_WIDTH_MM);
  373. int wpx = glutGet(GLUT_SCREEN_WIDTH);
  374. int hmm = glutGet(GLUT_SCREEN_HEIGHT_MM);
  375. int hpx = glutGet(GLUT_SCREEN_HEIGHT);
  376. if (wmm > 0 && hmm > 0)
  377. {
  378. float ppi = ((wpx * 254) / wmm + (hpx * 254) / hmm) / 20;
  379. if (ppi >= 288) ui.scale = 3;
  380. else if (ppi >= 192) ui.scale = 2;
  381. else if (ppi >= 144) ui.scale = 1.5f;
  382. }
  383. }
  384. ui.fontsize = DEFAULT_UI_FONTSIZE * ui.scale;
  385. ui.baseline = DEFAULT_UI_BASELINE * ui.scale;
  386. ui.lineheight = DEFAULT_UI_LINEHEIGHT * ui.scale;
  387. ui.gridsize = DEFAULT_UI_GRIDSIZE * ui.scale;
  388. ui.padsize = 2 * ui.scale;
  389. }
  390. void ui_init(int w, int h, const char *title)
  391. {
  392. #ifdef FREEGLUT
  393. glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
  394. #endif
  395. glutInitErrorFunc(on_error);
  396. glutInitWarningFunc(on_warning);
  397. glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
  398. glutInitWindowSize(w, h);
  399. glutCreateWindow(title);
  400. glutTimerFunc(500, on_timer, 0);
  401. glutReshapeFunc(on_reshape);
  402. glutDisplayFunc(on_display);
  403. #if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
  404. glutKeyboardExtFunc(on_keyboard);
  405. #else
  406. fz_warn(ctx, "This version of MuPDF has been built WITHOUT clipboard or unicode input support!");
  407. fz_warn(ctx, "Please file a complaint with your friendly local distribution manager.");
  408. glutKeyboardFunc(on_keyboard);
  409. #endif
  410. glutSpecialFunc(on_special);
  411. glutMouseFunc(on_mouse);
  412. glutMotionFunc(on_motion);
  413. glutPassiveMotionFunc(on_passive_motion);
  414. glutMouseWheelFunc(on_wheel);
  415. has_ARB_texture_non_power_of_two = glutExtensionSupported("GL_ARB_texture_non_power_of_two");
  416. if (!has_ARB_texture_non_power_of_two)
  417. fz_warn(ctx, "OpenGL implementation does not support non-power of two texture sizes");
  418. glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
  419. ui_init_fonts();
  420. ui.overlay_list = glGenLists(1);
  421. }
  422. void ui_finish(void)
  423. {
  424. pdf_drop_annot(ctx, ui.selected_annot);
  425. glDeleteLists(ui.overlay_list, 1);
  426. ui_finish_fonts();
  427. glutExit();
  428. }
  429. void ui_invalidate(void)
  430. {
  431. glutPostRedisplay();
  432. }
  433. void ui_begin(void)
  434. {
  435. ui.hot = NULL;
  436. ui.cavity = ui.cavity_stack;
  437. ui.cavity->x0 = 0;
  438. ui.cavity->y0 = 0;
  439. ui.cavity->x1 = ui.window_w;
  440. ui.cavity->y1 = ui.window_h;
  441. ui.layout = ui.layout_stack;
  442. ui.layout->side = ALL;
  443. ui.layout->fill = BOTH;
  444. ui.layout->anchor = NW;
  445. ui.layout->padx = 0;
  446. ui.layout->pady = 0;
  447. ui.cursor = GLUT_CURSOR_INHERIT;
  448. ui.overlay = 0;
  449. glViewport(0, 0, ui.window_w, ui.window_h);
  450. glClear(GL_COLOR_BUFFER_BIT);
  451. glMatrixMode(GL_PROJECTION);
  452. glLoadIdentity();
  453. glOrtho(0, ui.window_w, ui.window_h, 0, -1, 1);
  454. glMatrixMode(GL_MODELVIEW);
  455. glLoadIdentity();
  456. }
  457. void ui_end(void)
  458. {
  459. int code;
  460. if (ui.overlay)
  461. glCallList(ui.overlay_list);
  462. if (ui.cursor != ui.last_cursor)
  463. {
  464. glutSetCursor(ui.cursor);
  465. ui.last_cursor = ui.cursor;
  466. }
  467. code = glGetError();
  468. if (code != GL_NO_ERROR)
  469. fz_warn(ctx, "glGetError: %s", ogl_error_string(code));
  470. if (!ui.active && (ui.down || ui.middle || ui.right))
  471. ui.active = "dummy";
  472. if ((ui.grab_down && !ui.down) || (ui.grab_middle && !ui.middle) || (ui.grab_right && !ui.right))
  473. {
  474. ui.grab_down = ui.grab_middle = ui.grab_right = 0;
  475. ui.active = NULL;
  476. }
  477. if (ui.active)
  478. {
  479. if (ui.active != ui.focus)
  480. ui.focus = NULL;
  481. if (!ui.grab_down && !ui.grab_middle && !ui.grab_right)
  482. {
  483. ui.grab_down = ui.down;
  484. ui.grab_middle = ui.middle;
  485. ui.grab_right = ui.right;
  486. }
  487. }
  488. glutSwapBuffers();
  489. }
  490. /* Widgets */
  491. int ui_mouse_inside(fz_irect area)
  492. {
  493. if (ui.x >= area.x0 && ui.x < area.x1 && ui.y >= area.y0 && ui.y < area.y1)
  494. return 1;
  495. return 0;
  496. }
  497. fz_irect ui_pack_layout(int slave_w, int slave_h, enum side side, enum fill fill, enum anchor anchor, int padx, int pady)
  498. {
  499. fz_irect parcel, slave;
  500. int parcel_w, parcel_h;
  501. int anchor_x, anchor_y;
  502. switch (side)
  503. {
  504. default:
  505. case ALL:
  506. parcel.x0 = ui.cavity->x0 + padx;
  507. parcel.x1 = ui.cavity->x1 - padx;
  508. parcel.y0 = ui.cavity->y0 + pady;
  509. parcel.y1 = ui.cavity->y1 - pady;
  510. ui.cavity->x0 = ui.cavity->x1;
  511. ui.cavity->y0 = ui.cavity->y1;
  512. break;
  513. case T:
  514. parcel.x0 = ui.cavity->x0 + padx;
  515. parcel.x1 = ui.cavity->x1 - padx;
  516. parcel.y0 = ui.cavity->y0 + pady;
  517. parcel.y1 = ui.cavity->y0 + pady + slave_h;
  518. ui.cavity->y0 = parcel.y1 + pady;
  519. break;
  520. case B:
  521. parcel.x0 = ui.cavity->x0 + padx;
  522. parcel.x1 = ui.cavity->x1 - padx;
  523. parcel.y0 = ui.cavity->y1 - pady - slave_h;
  524. parcel.y1 = ui.cavity->y1 - pady;
  525. ui.cavity->y1 = parcel.y0 - pady;
  526. break;
  527. case L:
  528. parcel.x0 = ui.cavity->x0 + padx;
  529. parcel.x1 = ui.cavity->x0 + padx + slave_w;
  530. parcel.y0 = ui.cavity->y0 + pady;
  531. parcel.y1 = ui.cavity->y1 - pady;
  532. ui.cavity->x0 = parcel.x1 + padx;
  533. break;
  534. case R:
  535. parcel.x0 = ui.cavity->x1 - padx - slave_w;
  536. parcel.x1 = ui.cavity->x1 - padx;
  537. parcel.y0 = ui.cavity->y0 + pady;
  538. parcel.y1 = ui.cavity->y1 - pady;
  539. ui.cavity->x1 = parcel.x0 - padx;
  540. break;
  541. }
  542. parcel_w = parcel.x1 - parcel.x0;
  543. parcel_h = parcel.y1 - parcel.y0;
  544. if (fill & X)
  545. slave_w = parcel_w;
  546. if (fill & Y)
  547. slave_h = parcel_h;
  548. anchor_x = parcel_w - slave_w;
  549. anchor_y = parcel_h - slave_h;
  550. switch (anchor)
  551. {
  552. default:
  553. case CENTER:
  554. slave.x0 = parcel.x0 + anchor_x / 2;
  555. slave.y0 = parcel.y0 + anchor_y / 2;
  556. break;
  557. case N:
  558. slave.x0 = parcel.x0 + anchor_x / 2;
  559. slave.y0 = parcel.y0;
  560. break;
  561. case NE:
  562. slave.x0 = parcel.x0 + anchor_x;
  563. slave.y0 = parcel.y0;
  564. break;
  565. case E:
  566. slave.x0 = parcel.x0 + anchor_x;
  567. slave.y0 = parcel.y0 + anchor_y / 2;
  568. break;
  569. case SE:
  570. slave.x0 = parcel.x0 + anchor_x;
  571. slave.y0 = parcel.y0 + anchor_y;
  572. break;
  573. case S:
  574. slave.x0 = parcel.x0 + anchor_x / 2;
  575. slave.y0 = parcel.y0 + anchor_y;
  576. break;
  577. case SW:
  578. slave.x0 = parcel.x0;
  579. slave.y0 = parcel.y0 + anchor_y;
  580. break;
  581. case W:
  582. slave.x0 = parcel.x0;
  583. slave.y0 = parcel.y0 + anchor_y / 2;
  584. break;
  585. case NW:
  586. slave.x0 = parcel.x0;
  587. slave.y0 = parcel.y0;
  588. break;
  589. }
  590. slave.x1 = slave.x0 + slave_w;
  591. slave.y1 = slave.y0 + slave_h;
  592. return slave;
  593. }
  594. fz_irect ui_pack(int slave_w, int slave_h)
  595. {
  596. return ui_pack_layout(slave_w, slave_h, ui.layout->side, ui.layout->fill, ui.layout->anchor, ui.layout->padx, ui.layout->pady);
  597. }
  598. int ui_available_width(void)
  599. {
  600. return ui.cavity->x1 - ui.cavity->x0 - ui.layout->padx * 2;
  601. }
  602. int ui_available_height(void)
  603. {
  604. return ui.cavity->y1 - ui.cavity->y0 - ui.layout->pady * 2;
  605. }
  606. void ui_pack_push(fz_irect cavity)
  607. {
  608. *(++ui.cavity) = cavity;
  609. ++ui.layout;
  610. ui.layout->side = ALL;
  611. ui.layout->fill = BOTH;
  612. ui.layout->anchor = NW;
  613. ui.layout->padx = 0;
  614. ui.layout->pady = 0;
  615. }
  616. void ui_pack_pop(void)
  617. {
  618. --ui.cavity;
  619. --ui.layout;
  620. }
  621. void ui_layout(enum side side, enum fill fill, enum anchor anchor, int padx, int pady)
  622. {
  623. ui.layout->side = side;
  624. ui.layout->fill = fill;
  625. ui.layout->anchor = anchor;
  626. ui.layout->padx = padx;
  627. ui.layout->pady = pady;
  628. }
  629. void ui_panel_begin(int w, int h, int padx, int pady, int opaque)
  630. {
  631. fz_irect area = ui_pack(w, h);
  632. if (opaque)
  633. {
  634. glColorHex(UI_COLOR_PANEL);
  635. glRectf(area.x0, area.y0, area.x1, area.y1);
  636. }
  637. area.x0 += padx; area.y0 += pady;
  638. area.x1 -= padx; area.y1 -= pady;
  639. ui_pack_push(area);
  640. }
  641. void ui_panel_end(void)
  642. {
  643. ui_pack_pop();
  644. }
  645. void ui_dialog_begin(int w, int h)
  646. {
  647. fz_irect area;
  648. int x, y;
  649. w += 24 + 4;
  650. h += 24 + 4;
  651. if (w > ui.window_w) w = ui.window_w - 20;
  652. if (h > ui.window_h) h = ui.window_h - 20;
  653. x = (ui.window_w-w)/2;
  654. y = (ui.window_h-h)/3;
  655. area = fz_make_irect(x, y, x+w, y+h);
  656. ui_draw_bevel_rect(area, UI_COLOR_PANEL, 0);
  657. area = fz_expand_irect(area, -14);
  658. ui_pack_push(area);
  659. }
  660. void ui_dialog_end(void)
  661. {
  662. ui_pack_pop();
  663. }
  664. void ui_spacer(void)
  665. {
  666. ui_pack(ui.lineheight / 2, ui.lineheight / 2);
  667. }
  668. void ui_label(const char *fmt, ...)
  669. {
  670. char buf[512];
  671. struct line lines[20];
  672. int avail, used, n;
  673. fz_irect area;
  674. va_list ap;
  675. va_start(ap, fmt);
  676. fz_vsnprintf(buf, sizeof buf, fmt, ap);
  677. va_end(ap);
  678. avail = ui_available_width();
  679. n = ui_break_lines(buf, lines, nelem(lines), avail, &used);
  680. area = ui_pack(used, n * ui.lineheight);
  681. glColorHex(UI_COLOR_TEXT_FG);
  682. ui_draw_lines(area.x0, area.y0, lines, n);
  683. }
  684. int ui_button(const char *label)
  685. {
  686. return ui_button_aux(label, 0);
  687. }
  688. int ui_button_aux(const char *label, int flags)
  689. {
  690. int width = ui_measure_string(label);
  691. fz_irect area = ui_pack(width + 20, ui.gridsize);
  692. int text_x = area.x0 + ((area.x1 - area.x0) - width) / 2;
  693. int pressed = 0;
  694. int disabled = (flags & 1);
  695. if (!disabled)
  696. {
  697. if (ui_mouse_inside(area))
  698. {
  699. ui.hot = label;
  700. if (!ui.active && ui.down)
  701. ui.active = label;
  702. }
  703. pressed = (ui.hot == label && ui.active == label && ui.down);
  704. }
  705. ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed);
  706. glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
  707. ui_draw_string(text_x + pressed, area.y0+3 + pressed, label);
  708. return !disabled && ui.hot == label && ui.active == label && !ui.down;
  709. }
  710. int ui_checkbox(const char *label, int *value)
  711. {
  712. return ui_checkbox_aux(label, value, 0);
  713. }
  714. int ui_checkbox_aux(const char *label, int *value, int flags)
  715. {
  716. int width = ui_measure_string(label);
  717. fz_irect area = ui_pack(13 + 4 + width, ui.lineheight);
  718. fz_irect mark = { area.x0, area.y0 + ui.baseline-12, area.x0 + 13, area.y0 + ui.baseline+1 };
  719. int pressed = 0;
  720. int disabled = (flags & 1);
  721. glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
  722. ui_draw_string(mark.x1 + 4, area.y0, label);
  723. if (!disabled)
  724. {
  725. if (ui_mouse_inside(area))
  726. {
  727. ui.hot = label;
  728. if (!ui.active && ui.down)
  729. ui.active = label;
  730. }
  731. if (ui.hot == label && ui.active == label && !ui.down)
  732. *value = !*value;
  733. pressed = (ui.hot == label && ui.active == label && ui.down);
  734. }
  735. ui_draw_bevel_rect(mark, (disabled || pressed) ? UI_COLOR_PANEL : UI_COLOR_TEXT_BG, 1);
  736. if (*value)
  737. {
  738. float ax = mark.x0+2 + 1, ay = mark.y0+2 + 3;
  739. float bx = mark.x0+2 + 4, by = mark.y0+2 + 5;
  740. float cx = mark.x0+2 + 8, cy = mark.y0+2 + 1;
  741. glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
  742. glBegin(GL_TRIANGLE_STRIP);
  743. glVertex2f(ax, ay); glVertex2f(ax, ay+3);
  744. glVertex2f(bx, by); glVertex2f(bx, by+3);
  745. glVertex2f(cx, cy); glVertex2f(cx, cy+3);
  746. glEnd();
  747. }
  748. return !disabled && ui.hot == label && ui.active == label && !ui.down;
  749. }
  750. int ui_slider(int *value, int min, int max, int width)
  751. {
  752. static int start_value = 0;
  753. fz_irect area = ui_pack(width, ui.lineheight);
  754. int m = 6;
  755. int w = area.x1 - area.x0 - m * 2;
  756. int h = area.y1 - area.y0;
  757. fz_irect gutter = { area.x0, area.y0+h/2-2, area.x1, area.y0+h/2+2 };
  758. fz_irect thumb;
  759. int x;
  760. if (ui_mouse_inside(area))
  761. {
  762. ui.hot = value;
  763. if (!ui.active && ui.down)
  764. {
  765. ui.active = value;
  766. start_value = *value;
  767. }
  768. }
  769. if (ui.active == value)
  770. {
  771. if (ui.y < area.y0 || ui.y > area.y1)
  772. *value = start_value;
  773. else
  774. {
  775. float v = (float)(ui.x - (area.x0+m)) / w;
  776. *value = fz_clamp(min + v * (max - min), min, max);
  777. }
  778. }
  779. x = ((*value - min) * w) / (max - min);
  780. thumb = fz_make_irect(area.x0+m + x-m, area.y0, area.x0+m + x+m, area.y1);
  781. ui_draw_bevel(gutter, 1);
  782. ui_draw_bevel_rect(thumb, UI_COLOR_BUTTON, 0);
  783. return *value != start_value && ui.active == value && !ui.down;
  784. }
  785. void ui_splitter(int *start, int *v, int min, int max, enum side side)
  786. {
  787. fz_irect area = { 0 };
  788. if (side == L || side == R)
  789. area = ui_pack(4, 0);
  790. else if (side == T || side == B)
  791. area = ui_pack(0, 4);
  792. if (ui_mouse_inside(area))
  793. {
  794. ui.hot = v;
  795. if (!ui.active && ui.down)
  796. {
  797. ui.active = v;
  798. *start = *v;
  799. }
  800. }
  801. if (ui.active == v)
  802. {
  803. // how we slide the splitter coords depends on the packing direction
  804. switch (ui.layout->side)
  805. {
  806. default:
  807. case L: *v = fz_clampi(*start + (ui.x - ui.down_x), min, max); break;
  808. case R: *v = fz_clampi(*start + (ui.down_x - ui.x), min, max); break;
  809. case B: *v = fz_clampi(*start + (ui.down_y - ui.y), min, max); break;
  810. case T: *v = fz_clampi(*start + (ui.y - ui.down_y), min, max); break;
  811. }
  812. }
  813. if (ui.hot == v || ui.active == v)
  814. {
  815. if (side == L || side == R)
  816. ui.cursor = GLUT_CURSOR_LEFT_RIGHT;
  817. else if (side == T || side == B)
  818. ui.cursor = GLUT_CURSOR_UP_DOWN;
  819. }
  820. if (side == R)
  821. {
  822. glColorHex(UI_COLOR_PANEL);
  823. glRectf(area.x0+0, area.y0, area.x0+2, area.y1);
  824. glColorHex(UI_COLOR_BEVEL_2);
  825. glRectf(area.x0+2, area.y0, area.x0+3, area.y1);
  826. glColorHex(UI_COLOR_BEVEL_1);
  827. glRectf(area.x0+3, area.y0, area.x0+4, area.y1);
  828. }
  829. else if (side == L)
  830. {
  831. glColorHex(UI_COLOR_BEVEL_4);
  832. glRectf(area.x0+0, area.y0, area.x0+1, area.y1);
  833. glColorHex(UI_COLOR_BEVEL_3);
  834. glRectf(area.x0+1, area.y0, area.x0+3, area.y1);
  835. glColorHex(UI_COLOR_PANEL);
  836. glRectf(area.x0+2, area.y0, area.x0+4, area.y1);
  837. }
  838. else if (side == T)
  839. {
  840. glColorHex(UI_COLOR_BEVEL_4);
  841. glRectf(area.x0, area.y0+0, area.x1, area.y0+1);
  842. glColorHex(UI_COLOR_BEVEL_3);
  843. glRectf(area.x0, area.y0+1, area.x1, area.y0+2);
  844. glColorHex(UI_COLOR_PANEL);
  845. glRectf(area.x0, area.y0+2, area.x1, area.y0+4);
  846. }
  847. else if (side == B)
  848. {
  849. glColorHex(UI_COLOR_PANEL);
  850. glRectf(area.x0, area.y0+0, area.x1, area.y0+2);
  851. glColorHex(UI_COLOR_BEVEL_2);
  852. glRectf(area.x0, area.y0+2, area.x1, area.y0+3);
  853. glColorHex(UI_COLOR_BEVEL_1);
  854. glRectf(area.x0, area.y0+3, area.x1, area.y0+4);
  855. }
  856. }
  857. void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max, int *sticky)
  858. {
  859. static float start_top = 0; /* we can only drag in one scrollbar at a time, so static is safe */
  860. float top;
  861. int total_h = y1 - y0;
  862. int thumb_h = fz_maxi(x1 - x0, total_h * page_size / max);
  863. int avail_h = total_h - thumb_h;
  864. max -= page_size;
  865. if (max <= 0)
  866. {
  867. *value = 0;
  868. glColorHex(UI_COLOR_SCROLLBAR);
  869. glRectf(x0, y0, x1, y1);
  870. return;
  871. }
  872. if (sticky)
  873. {
  874. if (*sticky <= -1)
  875. *value = 0;
  876. else if (*sticky >= 1)
  877. *value = max;
  878. }
  879. top = (float) *value * avail_h / max;
  880. if (ui.down && !ui.active)
  881. {
  882. if (ui.x >= x0 && ui.x < x1 && ui.y >= y0 && ui.y < y1)
  883. {
  884. if (ui.y < y0 + top)
  885. {
  886. ui.active = "pgup";
  887. *value -= page_size;
  888. }
  889. else if (ui.y >= y0 + top + thumb_h)
  890. {
  891. ui.active = "pgdn";
  892. *value += page_size;
  893. }
  894. else
  895. {
  896. ui.hot = value;
  897. ui.active = value;
  898. start_top = top;
  899. }
  900. }
  901. }
  902. if (ui.active == value)
  903. {
  904. *value = (start_top + ui.y - ui.down_y) * max / avail_h;
  905. }
  906. if (*value < 0)
  907. *value = 0;
  908. else if (*value > max)
  909. *value = max;
  910. if (sticky)
  911. {
  912. if (*sticky == 0 && *value == 0)
  913. *sticky = -1;
  914. else if (*sticky == 0 && *value == max)
  915. *sticky = 1;
  916. else if (*sticky <= -1 && *value != 0)
  917. *sticky = 0;
  918. else if (*sticky >= 1 && *value != max)
  919. *sticky = 0;
  920. }
  921. top = (float) *value * avail_h / max;
  922. glColorHex(UI_COLOR_SCROLLBAR);
  923. glRectf(x0, y0, x1, y1);
  924. ui_draw_ibevel_rect(fz_make_irect(x0, y0+top, x1, y0+top+thumb_h), UI_COLOR_BUTTON, 0);
  925. }
  926. void ui_tree_begin(struct list *list, int count, int req_w, int req_h, int is_tree)
  927. {
  928. static int start_scroll_y = 0; /* we can only drag in one list at a time, so static is safe */
  929. fz_irect outer_area = ui_pack(req_w, req_h);
  930. fz_irect area = { outer_area.x0+2, outer_area.y0+2, outer_area.x1-2, outer_area.y1-2 };
  931. int max_scroll_y = count * ui.lineheight - (area.y1-area.y0);
  932. if (max_scroll_y > 0)
  933. area.x1 -= 16;
  934. if (ui_mouse_inside(area))
  935. {
  936. ui.hot = list;
  937. if (!ui.active && ui.middle)
  938. {
  939. ui.active = list;
  940. start_scroll_y = list->scroll_y;
  941. }
  942. }
  943. /* middle button dragging */
  944. if (ui.active == list)
  945. list->scroll_y = start_scroll_y + (ui.middle_y - ui.y) * 5;
  946. /* scroll wheel events */
  947. if (ui.hot == list)
  948. list->scroll_y -= ui.scroll_y * ui.lineheight * 3;
  949. /* keyboard keys */
  950. if (ui.hot == list && ui.key == KEY_HOME)
  951. list->scroll_y = 0;
  952. if (ui.hot == list && ui.key == KEY_END)
  953. list->scroll_y = max_scroll_y;
  954. if (ui.hot == list && ui.key == KEY_PAGE_UP)
  955. list->scroll_y -= ((area.y1 - area.y0) / ui.lineheight) * ui.lineheight;
  956. if (ui.hot == list && ui.key == KEY_PAGE_DOWN)
  957. list->scroll_y += ((area.y1 - area.y0) / ui.lineheight) * ui.lineheight;
  958. /* clamp scrolling to client area */
  959. if (list->scroll_y >= max_scroll_y)
  960. list->scroll_y = max_scroll_y;
  961. if (list->scroll_y < 0)
  962. list->scroll_y = 0;
  963. ui_draw_bevel_rect(outer_area, UI_COLOR_TEXT_BG, 1);
  964. if (max_scroll_y > 0)
  965. {
  966. ui_scrollbar(area.x1, area.y0, area.x1+16, area.y1,
  967. &list->scroll_y, area.y1-area.y0, count * ui.lineheight, NULL);
  968. }
  969. list->is_tree = is_tree;
  970. list->area = area;
  971. list->item_y = area.y0 - list->scroll_y;
  972. glScissor(list->area.x0, ui.window_h-list->area.y1, list->area.x1-list->area.x0, list->area.y1-list->area.y0);
  973. glEnable(GL_SCISSOR_TEST);
  974. }
  975. int ui_tree_item(struct list *list, const void *id, const char *label, int selected, int depth, int is_branch, int *is_open)
  976. {
  977. fz_irect area = { list->area.x0, list->item_y, list->area.x1, list->item_y + ui.lineheight };
  978. int x_handle, x_item;
  979. x_item = ui.lineheight / 4;
  980. x_item += depth * ui.lineheight;
  981. x_handle = x_item;
  982. if (list->is_tree)
  983. x_item += ui_measure_character(0x25BC) + ui.lineheight / 4;
  984. /* only process visible items */
  985. if (area.y1 >= list->area.y0 && area.y0 <= list->area.y1)
  986. {
  987. if (ui_mouse_inside(list->area) && ui_mouse_inside(area))
  988. {
  989. if (list->is_tree && ui.x < area.x0 + x_item)
  990. {
  991. ui.hot = is_open;
  992. }
  993. else
  994. ui.hot = id;
  995. if (!ui.active && ui.down)
  996. {
  997. if (list->is_tree && ui.hot == is_open)
  998. *is_open = !*is_open;
  999. ui.active = ui.hot;
  1000. }
  1001. }
  1002. if (ui.active == id || selected)
  1003. {
  1004. glColorHex(UI_COLOR_TEXT_SEL_BG);
  1005. glRectf(area.x0, area.y0, area.x1, area.y1);
  1006. glColorHex(UI_COLOR_TEXT_SEL_FG);
  1007. }
  1008. else
  1009. {
  1010. glColorHex(UI_COLOR_TEXT_FG);
  1011. }
  1012. ui_draw_string(area.x0 + x_item, area.y0, label);
  1013. if (list->is_tree && is_branch)
  1014. ui_draw_character(area.x0 + x_handle, area.y0,
  1015. *is_open ? 0x25BC : 0x25B6);
  1016. }
  1017. list->item_y += ui.lineheight;
  1018. /* trigger on mouse up */
  1019. return ui.active == id && !ui.down;
  1020. }
  1021. void ui_list_begin(struct list *list, int count, int req_w, int req_h)
  1022. {
  1023. ui_tree_begin(list, count, req_w, req_h, 0);
  1024. }
  1025. int ui_list_item(struct list *list, const void *id, const char *label, int selected)
  1026. {
  1027. return ui_tree_item(list, id, label, selected, 0, 0, NULL);
  1028. }
  1029. void ui_tree_end(struct list *list)
  1030. {
  1031. glDisable(GL_SCISSOR_TEST);
  1032. }
  1033. void ui_list_end(struct list *list)
  1034. {
  1035. ui_tree_end(list);
  1036. }
  1037. void ui_label_with_scrollbar(char *text, int width, int height, int *scroll, int *sticky)
  1038. {
  1039. struct line lines[500];
  1040. fz_irect area;
  1041. int n;
  1042. area = ui_pack(width, height);
  1043. n = ui_break_lines(text, lines, nelem(lines), area.x1-area.x0 - 16, NULL);
  1044. if (n > (area.y1-area.y0) / ui.lineheight)
  1045. {
  1046. if (ui_mouse_inside(area))
  1047. {
  1048. *scroll -= ui.scroll_y * ui.lineheight * 3;
  1049. if (ui.scroll_y != 0 && sticky)
  1050. *sticky = 0;
  1051. }
  1052. ui_scrollbar(area.x1-16, area.y0, area.x1, area.y1,
  1053. scroll, area.y1-area.y0, n * ui.lineheight, sticky);
  1054. }
  1055. else
  1056. *scroll = 0;
  1057. glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0-16, area.y1-area.y0);
  1058. glEnable(GL_SCISSOR_TEST);
  1059. glColorHex(UI_COLOR_TEXT_FG);
  1060. ui_draw_lines(area.x0, area.y0 - *scroll, lines, n);
  1061. glDisable(GL_SCISSOR_TEST);
  1062. }
  1063. int ui_popup(const void *id, const char *label, int is_button, int count)
  1064. {
  1065. return ui_popup_aux(id, label, is_button, count, 0);
  1066. }
  1067. int ui_popup_aux(const void *id, const char *label, int is_button, int count, int flags)
  1068. {
  1069. int width = ui_measure_string(label);
  1070. fz_irect area = ui_pack(width + 22 + 6, ui.gridsize);
  1071. fz_irect menu_area;
  1072. int pressed = 0;
  1073. int disabled = (flags & 1);
  1074. if (!disabled)
  1075. {
  1076. if (ui_mouse_inside(area))
  1077. {
  1078. ui.hot = id;
  1079. if (!ui.active && ui.down)
  1080. ui.active = id;
  1081. }
  1082. pressed = (ui.active == id);
  1083. }
  1084. if (is_button)
  1085. {
  1086. ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed);
  1087. glColorHex(disabled? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
  1088. ui_draw_string(area.x0 + 6+pressed, area.y0+3+pressed, label);
  1089. glBegin(GL_TRIANGLES);
  1090. glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9);
  1091. glVertex2f(area.x1+pressed-8, area.y0+pressed+9);
  1092. glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14);
  1093. glEnd();
  1094. }
  1095. else
  1096. {
  1097. fz_irect arrow = { area.x1-22, area.y0+2, area.x1-2, area.y1-2 };
  1098. ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1);
  1099. glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
  1100. ui_draw_string(area.x0 + 6, area.y0+3, label);
  1101. ui_draw_ibevel_rect(arrow, UI_COLOR_BUTTON, pressed);
  1102. glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
  1103. glBegin(GL_TRIANGLES);
  1104. glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9);
  1105. glVertex2f(area.x1+pressed-8, area.y0+pressed+9);
  1106. glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14);
  1107. glEnd();
  1108. }
  1109. if (pressed)
  1110. {
  1111. ui.overlay = 1;
  1112. glNewList(ui.overlay_list, GL_COMPILE);
  1113. /* Area inside the border line */
  1114. menu_area.x0 = area.x0+1;
  1115. menu_area.x1 = area.x1-1; // TODO: width of submenu
  1116. if (area.y1+2 + count * ui.lineheight < ui.window_h)
  1117. {
  1118. menu_area.y0 = area.y1+2;
  1119. menu_area.y1 = menu_area.y0 + count * ui.lineheight;
  1120. }
  1121. else
  1122. {
  1123. menu_area.y1 = area.y0-2;
  1124. menu_area.y0 = menu_area.y1 - count * ui.lineheight;
  1125. }
  1126. glColorHex(UI_COLOR_TEXT_FG);
  1127. glRectf(menu_area.x0-1, menu_area.y0-1, menu_area.x1+1, menu_area.y1+1);
  1128. glColorHex(UI_COLOR_TEXT_BG);
  1129. glRectf(menu_area.x0, menu_area.y0, menu_area.x1, menu_area.y1);
  1130. ui_pack_push(menu_area);
  1131. ui_layout(T, X, NW, 0, 0);
  1132. }
  1133. return pressed;
  1134. }
  1135. int ui_popup_item(const char *title)
  1136. {
  1137. return ui_popup_item_aux(title, 0);
  1138. }
  1139. int ui_popup_item_aux(const char *title, int flags)
  1140. {
  1141. fz_irect area = ui_pack(0, ui.lineheight);
  1142. int disabled = (flags & 1);
  1143. if (!disabled && ui_mouse_inside(area))
  1144. {
  1145. ui.hot = title;
  1146. glColorHex(UI_COLOR_TEXT_SEL_BG);
  1147. glRectf(area.x0, area.y0, area.x1, area.y1);
  1148. glColorHex(UI_COLOR_TEXT_SEL_FG);
  1149. ui_draw_string(area.x0 + 4, area.y0, title);
  1150. }
  1151. else
  1152. {
  1153. glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
  1154. ui_draw_string(area.x0 + 4, area.y0, title);
  1155. }
  1156. return !disabled && ui.hot == title && !ui.down;
  1157. }
  1158. void ui_popup_end(void)
  1159. {
  1160. glEndList();
  1161. ui_pack_pop();
  1162. }
  1163. int ui_select(const void *id, const char *current, const char *options[], int n)
  1164. {
  1165. return ui_select_aux(id, current, options, n, 0);
  1166. }
  1167. int ui_select_aux(const void *id, const char *current, const char *options[], int n, int flags)
  1168. {
  1169. int i, choice = -1;
  1170. if (ui_popup_aux(id, current, 0, n, flags))
  1171. {
  1172. for (i = 0; i < n; ++i)
  1173. if (ui_popup_item_aux(options[i], flags))
  1174. choice = i;
  1175. ui_popup_end();
  1176. }
  1177. return choice;
  1178. }
  1179. void ui_select_annot(pdf_annot *annot)
  1180. {
  1181. pdf_drop_annot(ctx, ui.selected_annot);
  1182. ui.selected_annot = annot;
  1183. }