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 */
glutExit(void)9 void glutExit(void) {}
glutMouseWheelFunc(void * fn)10 void glutMouseWheelFunc(void *fn) {}
glutInitErrorFunc(void * fn)11 void glutInitErrorFunc(void *fn) {}
glutInitWarningFunc(void * fn)12 void glutInitWarningFunc(void *fn) {}
13 #define glutSetOption(X,Y)
14 #endif
15
16 enum
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
25 struct ui ui;
26
27 #if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
28
ui_set_clipboard(const char * buf)29 void ui_set_clipboard(const char *buf)
30 {
31 glutSetClipboard(GLUT_PRIMARY, buf);
32 glutSetClipboard(GLUT_CLIPBOARD, buf);
33 }
34
ui_get_clipboard(void)35 const char *ui_get_clipboard(void)
36 {
37 return glutGetClipboard(GLUT_CLIPBOARD);
38 }
39
40 #else
41
42 static char *clipboard_buffer = NULL;
43
ui_set_clipboard(const char * buf)44 void ui_set_clipboard(const char *buf)
45 {
46 fz_free(ctx, clipboard_buffer);
47 clipboard_buffer = fz_strdup(ctx, buf);
48 }
49
ui_get_clipboard(void)50 const char *ui_get_clipboard(void)
51 {
52 return clipboard_buffer;
53 }
54
55 #endif
56
ogl_error_string(GLenum code)57 static 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
75 static int has_ARB_texture_non_power_of_two = 1;
76 static GLint max_texture_size = 8192;
77
ui_init_draw(void)78 void ui_init_draw(void)
79 {
80 }
81
next_power_of_two(unsigned int n)82 static 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
ui_texture_from_pixmap(struct texture * tex,fz_pixmap * pix)93 void 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
ui_draw_image(struct texture * tex,float x,float y)129 void 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
glColorHex(unsigned int hex)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
ui_draw_bevel_imp(fz_irect area,unsigned ot,unsigned it,unsigned ib,unsigned ob)160 void 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
ui_draw_bevel(fz_irect area,int depressed)176 void 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
ui_draw_ibevel(fz_irect area,int depressed)184 void 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
ui_draw_bevel_rect(fz_irect area,unsigned int fill,int depressed)192 void 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
ui_draw_ibevel_rect(fz_irect area,unsigned int fill,int depressed)199 void 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)
on_keyboard(int key,int x,int y)207 static void on_keyboard(int key, int x, int y)
208 #else
209 static 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
on_special(int key,int x,int y)229 static 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
on_wheel(int wheel,int direction,int x,int y)273 static 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
on_mouse(int button,int action,int x,int y)283 static 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
on_motion(int x,int y)326 static void on_motion(int x, int y)
327 {
328 ui.x = x;
329 ui.y = y;
330 ui_invalidate();
331 }
332
on_passive_motion(int x,int y)333 static void on_passive_motion(int x, int y)
334 {
335 ui.x = x;
336 ui.y = y;
337 ui_invalidate();
338 }
339
on_reshape(int w,int h)340 static void on_reshape(int w, int h)
341 {
342 ui.window_w = w;
343 ui.window_h = h;
344 }
345
on_display(void)346 static void on_display(void)
347 {
348 run_main_loop();
349 }
350
on_error(const char * fmt,va_list ap)351 static void on_error(const char *fmt, va_list ap)
352 {
353 #ifdef _WIN32
354 char buf[1000];
355 fz_vsnprintf(buf, sizeof buf, fmt, ap);
356 MessageBoxA(NULL, buf, "MuPDF GLUT Error", MB_ICONERROR);
357 #else
358 fprintf(stderr, "GLUT error: ");
359 vfprintf(stderr, fmt, ap);
360 fprintf(stderr, "\n");
361 #endif
362 }
363
on_warning(const char * fmt,va_list ap)364 static void on_warning(const char *fmt, va_list ap)
365 {
366 fprintf(stderr, "GLUT warning: ");
367 vfprintf(stderr, fmt, ap);
368 fprintf(stderr, "\n");
369 }
370
on_timer(int timer_id)371 static void on_timer(int timer_id)
372 {
373 if (reloadrequested)
374 {
375 reload();
376 ui_invalidate();
377 reloadrequested = 0;
378 }
379 glutTimerFunc(500, on_timer, 0);
380 }
381
ui_init(int w,int h,const char * title)382 void ui_init(int w, int h, const char *title)
383 {
384 float ui_scale;
385
386 #ifdef FREEGLUT
387 glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
388 #endif
389
390 glutInitErrorFunc(on_error);
391 glutInitWarningFunc(on_warning);
392 glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
393 glutInitWindowSize(w, h);
394 glutCreateWindow(title);
395
396 glutTimerFunc(500, on_timer, 0);
397 glutReshapeFunc(on_reshape);
398 glutDisplayFunc(on_display);
399 #if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
400 glutKeyboardExtFunc(on_keyboard);
401 #else
402 fz_warn(ctx, "This version of MuPDF has been built WITHOUT clipboard or unicode input support!");
403 fz_warn(ctx, "Please file a complaint with your friendly local distribution manager.");
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
ui_finish(void)443 void ui_finish(void)
444 {
445 glDeleteLists(ui.overlay_list, 1);
446 ui_finish_fonts();
447 glutExit();
448 }
449
ui_invalidate(void)450 void ui_invalidate(void)
451 {
452 glutPostRedisplay();
453 }
454
ui_begin(void)455 void 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
ui_end(void)487 void 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
ui_mouse_inside(fz_irect area)530 int 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
ui_pack_layout(int slave_w,int slave_h,enum side side,enum fill fill,enum anchor anchor,int padx,int pady)537 fz_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
ui_pack(int slave_w,int slave_h)642 fz_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
ui_available_width(void)647 int ui_available_width(void)
648 {
649 return ui.cavity->x1 - ui.cavity->x0 - ui.layout->padx * 2;
650 }
651
ui_available_height(void)652 int ui_available_height(void)
653 {
654 return ui.cavity->y1 - ui.cavity->y0 - ui.layout->pady * 2;
655 }
656
ui_pack_push(fz_irect cavity)657 void 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
ui_pack_pop(void)668 void ui_pack_pop(void)
669 {
670 --ui.cavity;
671 --ui.layout;
672 }
673
ui_layout(enum side side,enum fill fill,enum anchor anchor,int padx,int pady)674 void 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
ui_panel_begin(int w,int h,int padx,int pady,int opaque)683 void 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
ui_panel_end(void)696 void ui_panel_end(void)
697 {
698 ui_pack_pop();
699 }
700
ui_dialog_begin(int w,int h)701 void 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
ui_dialog_end(void)717 void ui_dialog_end(void)
718 {
719 ui_pack_pop();
720 }
721
ui_spacer(void)722 void ui_spacer(void)
723 {
724 ui_pack(ui.lineheight / 2, ui.lineheight / 2);
725 }
726
ui_label(const char * fmt,...)727 void 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
ui_button(const char * label)746 int ui_button(const char *label)
747 {
748 return ui_button_aux(label, 0);
749 }
750
ui_button_aux(const char * label,int flags)751 int ui_button_aux(const char *label, int flags)
752 {
753 int width = ui_measure_string(label);
754 fz_irect area = ui_pack(width + 20, ui.gridsize);
755 int text_x = area.x0 + ((area.x1 - area.x0) - width) / 2;
756 int pressed = 0;
757 int disabled = (flags & 1);
758
759 if (!disabled)
760 {
761 if (ui_mouse_inside(area))
762 {
763 ui.hot = label;
764 if (!ui.active && ui.down)
765 ui.active = label;
766 }
767
768 pressed = (ui.hot == label && ui.active == label && ui.down);
769 }
770 ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed);
771 glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
772 ui_draw_string(text_x + pressed, area.y0+3 + pressed, label);
773
774 return !disabled && ui.hot == label && ui.active == label && !ui.down;
775 }
776
ui_checkbox(const char * label,int * value)777 int ui_checkbox(const char *label, int *value)
778 {
779 return ui_checkbox_aux(label, value, 0);
780 }
781
ui_checkbox_aux(const char * label,int * value,int flags)782 int ui_checkbox_aux(const char *label, int *value, int flags)
783 {
784 int width = ui_measure_string(label);
785 fz_irect area = ui_pack(13 + 4 + width, ui.lineheight);
786 fz_irect mark = { area.x0, area.y0 + ui.baseline-12, area.x0 + 13, area.y0 + ui.baseline+1 };
787 int pressed = 0;
788 int disabled = (flags & 1);
789
790 glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
791 ui_draw_string(mark.x1 + 4, area.y0, label);
792
793 if (!disabled)
794 {
795 if (ui_mouse_inside(area))
796 {
797 ui.hot = label;
798 if (!ui.active && ui.down)
799 ui.active = label;
800 }
801
802 if (ui.hot == label && ui.active == label && !ui.down)
803 *value = !*value;
804
805 pressed = (ui.hot == label && ui.active == label && ui.down);
806 }
807 ui_draw_bevel_rect(mark, (disabled || pressed) ? UI_COLOR_PANEL : UI_COLOR_TEXT_BG, 1);
808 if (*value)
809 {
810 float ax = mark.x0+2 + 1, ay = mark.y0+2 + 3;
811 float bx = mark.x0+2 + 4, by = mark.y0+2 + 5;
812 float cx = mark.x0+2 + 8, cy = mark.y0+2 + 1;
813 glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
814 glBegin(GL_TRIANGLE_STRIP);
815 glVertex2f(ax, ay); glVertex2f(ax, ay+3);
816 glVertex2f(bx, by); glVertex2f(bx, by+3);
817 glVertex2f(cx, cy); glVertex2f(cx, cy+3);
818 glEnd();
819 }
820
821 return !disabled && ui.hot == label && ui.active == label && !ui.down;
822 }
823
ui_slider(int * value,int min,int max,int width)824 int ui_slider(int *value, int min, int max, int width)
825 {
826 static int start_value = 0;
827 fz_irect area = ui_pack(width, ui.lineheight);
828 int m = 6;
829 int w = area.x1 - area.x0 - m * 2;
830 int h = area.y1 - area.y0;
831 fz_irect gutter = { area.x0, area.y0+h/2-2, area.x1, area.y0+h/2+2 };
832 fz_irect thumb;
833 int x;
834
835 if (ui_mouse_inside(area))
836 {
837 ui.hot = value;
838 if (!ui.active && ui.down)
839 {
840 ui.active = value;
841 start_value = *value;
842 }
843 }
844
845 if (ui.active == value)
846 {
847 if (ui.y < area.y0 || ui.y > area.y1)
848 *value = start_value;
849 else
850 {
851 float v = (float)(ui.x - (area.x0+m)) / w;
852 *value = fz_clamp(min + v * (max - min), min, max);
853 }
854 }
855
856 x = ((*value - min) * w) / (max - min);
857 thumb = fz_make_irect(area.x0+m + x-m, area.y0, area.x0+m + x+m, area.y1);
858
859 ui_draw_bevel(gutter, 1);
860 ui_draw_bevel_rect(thumb, UI_COLOR_BUTTON, 0);
861
862 return *value != start_value && ui.active == value && !ui.down;
863 }
864
ui_splitter(int * x,int min,int max,enum side side)865 void ui_splitter(int *x, int min, int max, enum side side)
866 {
867 static int start_x = 0;
868 fz_irect area = ui_pack(4, 0);
869
870 if (ui_mouse_inside(area))
871 {
872 ui.hot = x;
873 if (!ui.active && ui.down)
874 {
875 ui.active = x;
876 start_x = *x;
877 }
878 }
879
880 if (ui.active == x)
881 *x = fz_clampi(start_x + (ui.x - ui.down_x), min, max);
882
883 if (ui.hot == x || ui.active == x)
884 ui.cursor = GLUT_CURSOR_LEFT_RIGHT;
885
886 if (side == L)
887 {
888 glColorHex(UI_COLOR_BEVEL_4);
889 glRectf(area.x0+0, area.y0, area.x0+2, area.y1);
890 glColorHex(UI_COLOR_BEVEL_3);
891 glRectf(area.x0+2, area.y0, area.x0+3, area.y1);
892 glColorHex(UI_COLOR_PANEL);
893 glRectf(area.x0+3, area.y0, area.x0+4, area.y1);
894 }
895 if (side == R)
896 {
897 glColorHex(UI_COLOR_PANEL);
898 glRectf(area.x0, area.y0, area.x0+2, area.y1);
899 glColorHex(UI_COLOR_BEVEL_2);
900 glRectf(area.x0+2, area.y0, area.x0+3, area.y1);
901 glColorHex(UI_COLOR_BEVEL_1);
902 glRectf(area.x0+3, area.y0, area.x0+4, area.y1);
903 }
904 }
905
ui_scrollbar(int x0,int y0,int x1,int y1,int * value,int page_size,int max)906 void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max)
907 {
908 static float start_top = 0; /* we can only drag in one scrollbar at a time, so static is safe */
909 float top;
910
911 int total_h = y1 - y0;
912 int thumb_h = fz_maxi(x1 - x0, total_h * page_size / max);
913 int avail_h = total_h - thumb_h;
914
915 max -= page_size;
916
917 if (max <= 0)
918 {
919 *value = 0;
920 glColorHex(UI_COLOR_SCROLLBAR);
921 glRectf(x0, y0, x1, y1);
922 return;
923 }
924
925 top = (float) *value * avail_h / max;
926
927 if (ui.down && !ui.active)
928 {
929 if (ui.x >= x0 && ui.x < x1 && ui.y >= y0 && ui.y < y1)
930 {
931 if (ui.y < y0 + top)
932 {
933 ui.active = "pgdn";
934 *value -= page_size;
935 }
936 else if (ui.y >= y0 + top + thumb_h)
937 {
938 ui.active = "pgup";
939 *value += page_size;
940 }
941 else
942 {
943 ui.hot = value;
944 ui.active = value;
945 start_top = top;
946 }
947 }
948 }
949
950 if (ui.active == value)
951 {
952 *value = (start_top + ui.y - ui.down_y) * max / avail_h;
953 }
954
955 if (*value < 0)
956 *value = 0;
957 else if (*value > max)
958 *value = max;
959
960 top = (float) *value * avail_h / max;
961
962 glColorHex(UI_COLOR_SCROLLBAR);
963 glRectf(x0, y0, x1, y1);
964 ui_draw_ibevel_rect(fz_make_irect(x0, y0+top, x1, y0+top+thumb_h), UI_COLOR_BUTTON, 0);
965 }
966
ui_tree_begin(struct list * list,int count,int req_w,int req_h,int is_tree)967 void ui_tree_begin(struct list *list, int count, int req_w, int req_h, int is_tree)
968 {
969 static int start_scroll_y = 0; /* we can only drag in one list at a time, so static is safe */
970
971 fz_irect outer_area = ui_pack(req_w, req_h);
972 fz_irect area = { outer_area.x0+2, outer_area.y0+2, outer_area.x1-2, outer_area.y1-2 };
973
974 int max_scroll_y = count * ui.lineheight - (area.y1-area.y0);
975
976 if (max_scroll_y > 0)
977 area.x1 -= 16;
978
979 if (ui_mouse_inside(area))
980 {
981 ui.hot = list;
982 if (!ui.active && ui.middle)
983 {
984 ui.active = list;
985 start_scroll_y = list->scroll_y;
986 }
987 }
988
989 /* middle button dragging */
990 if (ui.active == list)
991 list->scroll_y = start_scroll_y + (ui.middle_y - ui.y) * 5;
992
993 /* scroll wheel events */
994 if (ui.hot == list)
995 list->scroll_y -= ui.scroll_y * ui.lineheight * 3;
996
997 /* clamp scrolling to client area */
998 if (list->scroll_y >= max_scroll_y)
999 list->scroll_y = max_scroll_y;
1000 if (list->scroll_y < 0)
1001 list->scroll_y = 0;
1002
1003 ui_draw_bevel_rect(outer_area, UI_COLOR_TEXT_BG, 1);
1004 if (max_scroll_y > 0)
1005 {
1006 ui_scrollbar(area.x1, area.y0, area.x1+16, area.y1,
1007 &list->scroll_y, area.y1-area.y0, count * ui.lineheight);
1008 }
1009
1010 list->is_tree = is_tree;
1011 list->area = area;
1012 list->item_y = area.y0 - list->scroll_y;
1013
1014 glScissor(list->area.x0, ui.window_h-list->area.y1, list->area.x1-list->area.x0, list->area.y1-list->area.y0);
1015 glEnable(GL_SCISSOR_TEST);
1016 }
1017
ui_tree_item(struct list * list,const void * id,const char * label,int selected,int depth,int is_branch,int * is_open)1018 int ui_tree_item(struct list *list, const void *id, const char *label, int selected, int depth, int is_branch, int *is_open)
1019 {
1020 fz_irect area = { list->area.x0, list->item_y, list->area.x1, list->item_y + ui.lineheight };
1021 int x_handle, x_item;
1022
1023 x_item = ui.lineheight / 4;
1024 x_item += depth * ui.lineheight;
1025 x_handle = x_item;
1026 if (list->is_tree)
1027 x_item += ui_measure_character(0x25BC) + ui.lineheight / 4;
1028
1029 /* only process visible items */
1030 if (area.y1 >= list->area.y0 && area.y0 <= list->area.y1)
1031 {
1032 if (ui_mouse_inside(list->area) && ui_mouse_inside(area))
1033 {
1034 if (list->is_tree && ui.x < area.x0 + x_item)
1035 {
1036 ui.hot = is_open;
1037 }
1038 else
1039 ui.hot = id;
1040 if (!ui.active && ui.down)
1041 {
1042 if (list->is_tree && ui.hot == is_open)
1043 *is_open = !*is_open;
1044 ui.active = ui.hot;
1045 }
1046 }
1047
1048 if (ui.active == id || selected)
1049 {
1050 glColorHex(UI_COLOR_TEXT_SEL_BG);
1051 glRectf(area.x0, area.y0, area.x1, area.y1);
1052 glColorHex(UI_COLOR_TEXT_SEL_FG);
1053 }
1054 else
1055 {
1056 glColorHex(UI_COLOR_TEXT_FG);
1057 }
1058
1059 ui_draw_string(area.x0 + x_item, area.y0, label);
1060 if (list->is_tree && is_branch)
1061 ui_draw_character(area.x0 + x_handle, area.y0,
1062 *is_open ? 0x25BC : 0x25B6);
1063 }
1064
1065 list->item_y += ui.lineheight;
1066
1067 /* trigger on mouse up */
1068 return ui.active == id && !ui.down;
1069 }
1070
ui_list_begin(struct list * list,int count,int req_w,int req_h)1071 void ui_list_begin(struct list *list, int count, int req_w, int req_h)
1072 {
1073 ui_tree_begin(list, count, req_w, req_h, 0);
1074 }
1075
ui_list_item(struct list * list,const void * id,const char * label,int selected)1076 int ui_list_item(struct list *list, const void *id, const char *label, int selected)
1077 {
1078 return ui_tree_item(list, id, label, selected, 0, 0, NULL);
1079 }
1080
ui_tree_end(struct list * list)1081 void ui_tree_end(struct list *list)
1082 {
1083 glDisable(GL_SCISSOR_TEST);
1084 }
1085
ui_list_end(struct list * list)1086 void ui_list_end(struct list *list)
1087 {
1088 ui_tree_end(list);
1089 }
1090
ui_label_with_scrollbar(char * text,int width,int height,int * scroll)1091 void ui_label_with_scrollbar(char *text, int width, int height, int *scroll)
1092 {
1093 struct line lines[500];
1094 fz_irect area;
1095 int n;
1096
1097 area = ui_pack(width, height);
1098 n = ui_break_lines(text, lines, nelem(lines), area.x1-area.x0 - 16, NULL);
1099 if (n > (area.y1-area.y0) / ui.lineheight)
1100 {
1101 if (ui_mouse_inside(area))
1102 *scroll -= ui.scroll_y * ui.lineheight * 3;
1103 ui_scrollbar(area.x1-16, area.y0, area.x1, area.y1, scroll, area.y1-area.y0, n * ui.lineheight);
1104 }
1105 else
1106 *scroll = 0;
1107
1108 glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0-16, area.y1-area.y0);
1109 glEnable(GL_SCISSOR_TEST);
1110 glColorHex(UI_COLOR_TEXT_FG);
1111 ui_draw_lines(area.x0, area.y0 - *scroll, lines, n);
1112 glDisable(GL_SCISSOR_TEST);
1113 }
1114
ui_popup(const void * id,const char * label,int is_button,int count)1115 int ui_popup(const void *id, const char *label, int is_button, int count)
1116 {
1117 return ui_popup_aux(id, label, is_button, count, 0);
1118 }
1119
ui_popup_aux(const void * id,const char * label,int is_button,int count,int flags)1120 int ui_popup_aux(const void *id, const char *label, int is_button, int count, int flags)
1121 {
1122 int width = ui_measure_string(label);
1123 fz_irect area = ui_pack(width + 22 + 6, ui.gridsize);
1124 fz_irect menu_area;
1125 int pressed = 0;
1126 int disabled = (flags & 1);
1127
1128 if (!disabled)
1129 {
1130 if (ui_mouse_inside(area))
1131 {
1132 ui.hot = id;
1133 if (!ui.active && ui.down)
1134 ui.active = id;
1135 }
1136
1137 pressed = (ui.active == id);
1138 }
1139
1140 if (is_button)
1141 {
1142 ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed);
1143 glColorHex(disabled? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
1144 ui_draw_string(area.x0 + 6+pressed, area.y0+3+pressed, label);
1145 glBegin(GL_TRIANGLES);
1146 glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9);
1147 glVertex2f(area.x1+pressed-8, area.y0+pressed+9);
1148 glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14);
1149 glEnd();
1150 }
1151 else
1152 {
1153 fz_irect arrow = { area.x1-22, area.y0+2, area.x1-2, area.y1-2 };
1154 ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1);
1155 glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
1156 ui_draw_string(area.x0 + 6, area.y0+3, label);
1157 ui_draw_ibevel_rect(arrow, UI_COLOR_BUTTON, pressed);
1158
1159 glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
1160 glBegin(GL_TRIANGLES);
1161 glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9);
1162 glVertex2f(area.x1+pressed-8, area.y0+pressed+9);
1163 glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14);
1164 glEnd();
1165 }
1166
1167 if (pressed)
1168 {
1169 ui.overlay = 1;
1170
1171 glNewList(ui.overlay_list, GL_COMPILE);
1172
1173 /* Area inside the border line */
1174 menu_area.x0 = area.x0+1;
1175 menu_area.x1 = area.x1-1; // TODO: width of submenu
1176 if (area.y1+2 + count * ui.lineheight < ui.window_h)
1177 {
1178 menu_area.y0 = area.y1+2;
1179 menu_area.y1 = menu_area.y0 + count * ui.lineheight;
1180 }
1181 else
1182 {
1183 menu_area.y1 = area.y0-2;
1184 menu_area.y0 = menu_area.y1 - count * ui.lineheight;
1185 }
1186
1187 glColorHex(UI_COLOR_TEXT_FG);
1188 glRectf(menu_area.x0-1, menu_area.y0-1, menu_area.x1+1, menu_area.y1+1);
1189 glColorHex(UI_COLOR_TEXT_BG);
1190 glRectf(menu_area.x0, menu_area.y0, menu_area.x1, menu_area.y1);
1191
1192 ui_pack_push(menu_area);
1193 ui_layout(T, X, NW, 0, 0);
1194 }
1195
1196 return pressed;
1197 }
1198
ui_popup_item(const char * title)1199 int ui_popup_item(const char *title)
1200 {
1201 return ui_popup_item_aux(title, 0);
1202 }
1203
ui_popup_item_aux(const char * title,int flags)1204 int ui_popup_item_aux(const char *title, int flags)
1205 {
1206 fz_irect area = ui_pack(0, ui.lineheight);
1207 int disabled = (flags & 1);
1208
1209 if (!disabled && ui_mouse_inside(area))
1210 {
1211 ui.hot = title;
1212 glColorHex(UI_COLOR_TEXT_SEL_BG);
1213 glRectf(area.x0, area.y0, area.x1, area.y1);
1214 glColorHex(UI_COLOR_TEXT_SEL_FG);
1215 ui_draw_string(area.x0 + 4, area.y0, title);
1216 }
1217 else
1218 {
1219 glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG);
1220 ui_draw_string(area.x0 + 4, area.y0, title);
1221 }
1222
1223 return !disabled && ui.hot == title && !ui.down;
1224 }
1225
ui_popup_end(void)1226 void ui_popup_end(void)
1227 {
1228 glEndList();
1229 ui_pack_pop();
1230 }
1231
ui_select(const void * id,const char * current,const char * options[],int n)1232 int ui_select(const void *id, const char *current, const char *options[], int n)
1233 {
1234 return ui_select_aux(id, current, options, n, 0);
1235 }
1236
ui_select_aux(const void * id,const char * current,const char * options[],int n,int flags)1237 int ui_select_aux(const void *id, const char *current, const char *options[], int n, int flags)
1238 {
1239 int i, choice = -1;
1240 if (ui_popup_aux(id, current, 0, n, flags))
1241 {
1242 for (i = 0; i < n; ++i)
1243 if (ui_popup_item_aux(options[i], flags))
1244 choice = i;
1245 ui_popup_end();
1246 }
1247 return choice;
1248 }
1249