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