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