Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/platform/gl/gl-ui.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/platform/gl/gl-ui.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1357 @@ +// Copyright (C) 2004-2022 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "gl-app.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#ifndef FREEGLUT +/* freeglut extension no-ops */ +void glutExit(void) {} +void glutMouseWheelFunc(void *fn) {} +void glutInitErrorFunc(void *fn) {} +void glutInitWarningFunc(void *fn) {} +#define glutSetOption(X,Y) +#endif + +enum +{ + /* Default UI sizes */ + DEFAULT_UI_FONTSIZE = 15, + DEFAULT_UI_BASELINE = 14, + DEFAULT_UI_LINEHEIGHT = 18, + DEFAULT_UI_GRIDSIZE = DEFAULT_UI_LINEHEIGHT + 6, +}; + +struct ui ui; + +#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6) + +void ui_set_clipboard(const char *buf) +{ + glutSetClipboard(GLUT_PRIMARY, buf); + glutSetClipboard(GLUT_CLIPBOARD, buf); +} + +const char *ui_get_clipboard(void) +{ + return glutGetClipboard(GLUT_CLIPBOARD); +} + +#else + +static char *clipboard_buffer = NULL; + +void ui_set_clipboard(const char *buf) +{ + fz_free(ctx, clipboard_buffer); + clipboard_buffer = fz_strdup(ctx, buf); +} + +const char *ui_get_clipboard(void) +{ + return clipboard_buffer; +} + +#endif + +static const char *ogl_error_string(GLenum code) +{ +#define CASE(E) case E: return #E; break + switch (code) + { + /* glGetError */ + CASE(GL_NO_ERROR); + CASE(GL_INVALID_ENUM); + CASE(GL_INVALID_VALUE); + CASE(GL_INVALID_OPERATION); + CASE(GL_OUT_OF_MEMORY); + CASE(GL_STACK_UNDERFLOW); + CASE(GL_STACK_OVERFLOW); + default: return "(unknown)"; + } +#undef CASE +} + +static int has_ARB_texture_non_power_of_two = 1; +static GLint max_texture_size = 8192; + +void ui_init_draw(void) +{ +} + +static unsigned int next_power_of_two(unsigned int n) +{ + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + return ++n; +} + +void ui_texture_from_pixmap(struct texture *tex, fz_pixmap *pix) +{ + if (!tex->id) + glGenTextures(1, &tex->id); + glBindTexture(GL_TEXTURE_2D, tex->id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + tex->x = pix->x; + tex->y = pix->y; + tex->w = pix->w; + tex->h = pix->h; + + if (has_ARB_texture_non_power_of_two) + { + if (tex->w > max_texture_size || tex->h > max_texture_size) + fz_warn(ctx, "texture size (%d x %d) exceeds implementation limit (%d)", tex->w, tex->h, max_texture_size); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->w, tex->h, 0, pix->n == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, pix->samples); + tex->s = 1; + tex->t = 1; + } + else + { + int w2 = next_power_of_two(tex->w); + int h2 = next_power_of_two(tex->h); + if (w2 > max_texture_size || h2 > max_texture_size) + fz_warn(ctx, "texture size (%d x %d) exceeds implementation limit (%d)", w2, h2, max_texture_size); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w2, h2, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex->w, tex->h, pix->n == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, pix->samples); + tex->s = (float) tex->w / w2; + tex->t = (float) tex->h / h2; + } +} + +void ui_draw_image(struct texture *tex, float x, float y) +{ + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glBindTexture(GL_TEXTURE_2D, tex->id); + glEnable(GL_TEXTURE_2D); + glBegin(GL_TRIANGLE_STRIP); + { + glColor4f(1, 1, 1, 1); + glTexCoord2f(0, tex->t); + glVertex2f(x + tex->x, y + tex->y + tex->h); + glTexCoord2f(0, 0); + glVertex2f(x + tex->x, y + tex->y); + glTexCoord2f(tex->s, tex->t); + glVertex2f(x + tex->x + tex->w, y + tex->y + tex->h); + glTexCoord2f(tex->s, 0); + glVertex2f(x + tex->x + tex->w, y + tex->y); + } + glEnd(); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); +} + +void glColorHex(unsigned int hex) +{ + float r = ((hex>>16)&0xff) / 255.0f; + float g = ((hex>>8)&0xff) / 255.0f; + float b = ((hex)&0xff) / 255.0f; + glColor3f(r, g, b); +} + +void ui_draw_bevel_imp(fz_irect area, unsigned ot, unsigned it, unsigned ib, unsigned ob) +{ + glColorHex(ot); + glRectf(area.x0, area.y0, area.x1-1, area.y0+1); + glRectf(area.x0, area.y0+1, area.x0+1, area.y1-1); + glColorHex(ob); + glRectf(area.x1-1, area.y0, area.x1, area.y1); + glRectf(area.x0, area.y1-1, area.x1-1, area.y1); + glColorHex(it); + glRectf(area.x0+1, area.y0+1, area.x1-2, area.y0+2); + glRectf(area.x0+1, area.y0+2, area.x0+2, area.y1-2); + glColorHex(ib); + glRectf(area.x1-2, area.y0+1, area.x1-1, area.y1-1); + glRectf(area.x0+1, area.y1-2, area.x1-2, area.y1-1); +} + +void ui_draw_bevel(fz_irect area, int depressed) +{ + if (depressed) + ui_draw_bevel_imp(area, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4); + else + ui_draw_bevel_imp(area, UI_COLOR_BEVEL_4, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1); +} + +void ui_draw_ibevel(fz_irect area, int depressed) +{ + if (depressed) + ui_draw_bevel_imp(area, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4); + else + ui_draw_bevel_imp(area, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1); +} + +void ui_draw_bevel_rect(fz_irect area, unsigned int fill, int depressed) +{ + ui_draw_bevel(area, depressed); + glColorHex(fill); + glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2); +} + +void ui_draw_ibevel_rect(fz_irect area, unsigned int fill, int depressed) +{ + ui_draw_ibevel(area, depressed); + glColorHex(fill); + glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2); +} + +#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6) +static void on_keyboard(int key, int x, int y) +#else +static void on_keyboard(unsigned char key, int x, int y) +#endif +{ +#ifdef __APPLE__ + /* Apple's GLUT has swapped DELETE and BACKSPACE */ + if (key == 8) + key = 127; + else if (key == 127) + key = 8; +#endif + ui.x = x; + ui.y = y; + ui.key = key; + ui.mod = glutGetModifiers(); + ui.plain = !(ui.mod & ~GLUT_ACTIVE_SHIFT); + run_main_loop(); + ui.key = ui.plain = 0; + ui_invalidate(); // TODO: leave this to caller +} + +static void on_special(int key, int x, int y) +{ + ui.x = x; + ui.y = y; + ui.key = 0; + + switch (key) + { + case GLUT_KEY_INSERT: ui.key = KEY_INSERT; break; +#ifdef GLUT_KEY_DELETE + case GLUT_KEY_DELETE: ui.key = KEY_DELETE; break; +#endif + case GLUT_KEY_RIGHT: ui.key = KEY_RIGHT; break; + case GLUT_KEY_LEFT: ui.key = KEY_LEFT; break; + case GLUT_KEY_DOWN: ui.key = KEY_DOWN; break; + case GLUT_KEY_UP: ui.key = KEY_UP; break; + case GLUT_KEY_PAGE_UP: ui.key = KEY_PAGE_UP; break; + case GLUT_KEY_PAGE_DOWN: ui.key = KEY_PAGE_DOWN; break; + case GLUT_KEY_HOME: ui.key = KEY_HOME; break; + case GLUT_KEY_END: ui.key = KEY_END; break; + case GLUT_KEY_F1: ui.key = KEY_F1; break; + case GLUT_KEY_F2: ui.key = KEY_F2; break; + case GLUT_KEY_F3: ui.key = KEY_F3; break; + case GLUT_KEY_F4: ui.key = KEY_F4; break; + case GLUT_KEY_F5: ui.key = KEY_F5; break; + case GLUT_KEY_F6: ui.key = KEY_F6; break; + case GLUT_KEY_F7: ui.key = KEY_F7; break; + case GLUT_KEY_F8: ui.key = KEY_F8; break; + case GLUT_KEY_F9: ui.key = KEY_F9; break; + case GLUT_KEY_F10: ui.key = KEY_F10; break; + case GLUT_KEY_F11: ui.key = KEY_F11; break; + case GLUT_KEY_F12: ui.key = KEY_F12; break; + } + + if (ui.key) + { + ui.mod = glutGetModifiers(); + ui.plain = !(ui.mod & ~GLUT_ACTIVE_SHIFT); + run_main_loop(); + ui.key = ui.plain = 0; + ui_invalidate(); // TODO: leave this to caller + } +} + +static void on_wheel(int wheel, int direction, int x, int y) +{ + ui.scroll_x = wheel == 1 ? direction : 0; + ui.scroll_y = wheel == 0 ? direction : 0; + ui.mod = glutGetModifiers(); + run_main_loop(); + ui_invalidate(); // TODO: leave this to caller + ui.scroll_x = ui.scroll_y = 0; +} + +static void on_mouse(int button, int action, int x, int y) +{ + ui.x = x; + ui.y = y; + if (action == GLUT_DOWN) + { + switch (button) + { + case GLUT_LEFT_BUTTON: + ui.down_x = x; + ui.down_y = y; + ui.down = 1; + break; + case GLUT_MIDDLE_BUTTON: + ui.middle_x = x; + ui.middle_y = y; + ui.middle = 1; + break; + case GLUT_RIGHT_BUTTON: + ui.right_x = x; + ui.right_y = y; + ui.right = 1; + break; + case 3: on_wheel(0, 1, x, y); break; + case 4: on_wheel(0, -1, x, y); break; + case 5: on_wheel(1, 1, x, y); break; + case 6: on_wheel(1, -1, x, y); break; + } + } + else if (action == GLUT_UP) + { + switch (button) + { + case GLUT_LEFT_BUTTON: ui.down = 0; break; + case GLUT_MIDDLE_BUTTON: ui.middle = 0; break; + case GLUT_RIGHT_BUTTON: ui.right = 0; break; + } + } + ui.mod = glutGetModifiers(); + run_main_loop(); + ui_invalidate(); // TODO: leave this to caller +} + +static void on_motion(int x, int y) +{ + ui.x = x; + ui.y = y; + ui_invalidate(); +} + +static void on_passive_motion(int x, int y) +{ + ui.x = x; + ui.y = y; + ui_invalidate(); +} + +static void on_reshape(int w, int h) +{ + ui.window_w = w; + ui.window_h = h; +} + +static void on_display(void) +{ + run_main_loop(); +} + +static void on_error(const char *fmt, va_list ap) +{ +#ifdef _WIN32 + char buf[1000]; + fz_vsnprintf(buf, sizeof buf, fmt, ap); + MessageBoxA(NULL, buf, "MuPDF GLUT Error", MB_ICONERROR); +#else + fprintf(stderr, "GLUT error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +#endif +} + +static void on_warning(const char *fmt, va_list ap) +{ + fprintf(stderr, "GLUT warning: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +} + +static void on_timer(int timer_id) +{ + if (reloadrequested) + { + reload(); + ui_invalidate(); + reloadrequested = 0; + } + glutTimerFunc(500, on_timer, 0); +} + +void ui_init_dpi(float override_scale) +{ + ui.scale = 1; + + if (override_scale) + { + ui.scale = override_scale; + } + else + { + int wmm = glutGet(GLUT_SCREEN_WIDTH_MM); + int wpx = glutGet(GLUT_SCREEN_WIDTH); + int hmm = glutGet(GLUT_SCREEN_HEIGHT_MM); + int hpx = glutGet(GLUT_SCREEN_HEIGHT); + if (wmm > 0 && hmm > 0) + { + float ppi = ((wpx * 254) / wmm + (hpx * 254) / hmm) / 20; + if (ppi >= 288) ui.scale = 3; + else if (ppi >= 192) ui.scale = 2; + else if (ppi >= 144) ui.scale = 1.5f; + } + } + + ui.fontsize = DEFAULT_UI_FONTSIZE * ui.scale; + ui.baseline = DEFAULT_UI_BASELINE * ui.scale; + ui.lineheight = DEFAULT_UI_LINEHEIGHT * ui.scale; + ui.gridsize = DEFAULT_UI_GRIDSIZE * ui.scale; + ui.padsize = 2 * ui.scale; +} + +void ui_init(int w, int h, const char *title) +{ +#ifdef FREEGLUT + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS); +#endif + + glutInitErrorFunc(on_error); + glutInitWarningFunc(on_warning); + glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); + glutInitWindowSize(w, h); + glutCreateWindow(title); + + glutTimerFunc(500, on_timer, 0); + glutReshapeFunc(on_reshape); + glutDisplayFunc(on_display); +#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6) + glutKeyboardExtFunc(on_keyboard); +#else + fz_warn(ctx, "This version of MuPDF has been built WITHOUT clipboard or unicode input support!"); + fz_warn(ctx, "Please file a complaint with your friendly local distribution manager."); + glutKeyboardFunc(on_keyboard); +#endif + glutSpecialFunc(on_special); + glutMouseFunc(on_mouse); + glutMotionFunc(on_motion); + glutPassiveMotionFunc(on_passive_motion); + glutMouseWheelFunc(on_wheel); + + has_ARB_texture_non_power_of_two = glutExtensionSupported("GL_ARB_texture_non_power_of_two"); + if (!has_ARB_texture_non_power_of_two) + fz_warn(ctx, "OpenGL implementation does not support non-power of two texture sizes"); + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); + + ui_init_fonts(); + + ui.overlay_list = glGenLists(1); +} + +void ui_finish(void) +{ + pdf_drop_annot(ctx, ui.selected_annot); + glDeleteLists(ui.overlay_list, 1); + ui_finish_fonts(); + glutExit(); +} + +void ui_invalidate(void) +{ + glutPostRedisplay(); +} + +void ui_begin(void) +{ + ui.hot = NULL; + + ui.cavity = ui.cavity_stack; + ui.cavity->x0 = 0; + ui.cavity->y0 = 0; + ui.cavity->x1 = ui.window_w; + ui.cavity->y1 = ui.window_h; + + ui.layout = ui.layout_stack; + ui.layout->side = ALL; + ui.layout->fill = BOTH; + ui.layout->anchor = NW; + ui.layout->padx = 0; + ui.layout->pady = 0; + + ui.cursor = GLUT_CURSOR_INHERIT; + + ui.overlay = 0; + + glViewport(0, 0, ui.window_w, ui.window_h); + glClear(GL_COLOR_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, ui.window_w, ui.window_h, 0, -1, 1); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +void ui_end(void) +{ + int code; + + if (ui.overlay) + glCallList(ui.overlay_list); + + if (ui.cursor != ui.last_cursor) + { + glutSetCursor(ui.cursor); + ui.last_cursor = ui.cursor; + } + + code = glGetError(); + if (code != GL_NO_ERROR) + fz_warn(ctx, "glGetError: %s", ogl_error_string(code)); + + if (!ui.active && (ui.down || ui.middle || ui.right)) + ui.active = "dummy"; + + if ((ui.grab_down && !ui.down) || (ui.grab_middle && !ui.middle) || (ui.grab_right && !ui.right)) + { + ui.grab_down = ui.grab_middle = ui.grab_right = 0; + ui.active = NULL; + } + + if (ui.active) + { + if (ui.active != ui.focus) + ui.focus = NULL; + if (!ui.grab_down && !ui.grab_middle && !ui.grab_right) + { + ui.grab_down = ui.down; + ui.grab_middle = ui.middle; + ui.grab_right = ui.right; + } + } + + glutSwapBuffers(); +} + +/* Widgets */ + +int ui_mouse_inside(fz_irect area) +{ + if (ui.x >= area.x0 && ui.x < area.x1 && ui.y >= area.y0 && ui.y < area.y1) + return 1; + return 0; +} + +fz_irect ui_pack_layout(int slave_w, int slave_h, enum side side, enum fill fill, enum anchor anchor, int padx, int pady) +{ + fz_irect parcel, slave; + int parcel_w, parcel_h; + int anchor_x, anchor_y; + + switch (side) + { + default: + case ALL: + parcel.x0 = ui.cavity->x0 + padx; + parcel.x1 = ui.cavity->x1 - padx; + parcel.y0 = ui.cavity->y0 + pady; + parcel.y1 = ui.cavity->y1 - pady; + ui.cavity->x0 = ui.cavity->x1; + ui.cavity->y0 = ui.cavity->y1; + break; + case T: + parcel.x0 = ui.cavity->x0 + padx; + parcel.x1 = ui.cavity->x1 - padx; + parcel.y0 = ui.cavity->y0 + pady; + parcel.y1 = ui.cavity->y0 + pady + slave_h; + ui.cavity->y0 = parcel.y1 + pady; + break; + case B: + parcel.x0 = ui.cavity->x0 + padx; + parcel.x1 = ui.cavity->x1 - padx; + parcel.y0 = ui.cavity->y1 - pady - slave_h; + parcel.y1 = ui.cavity->y1 - pady; + ui.cavity->y1 = parcel.y0 - pady; + break; + case L: + parcel.x0 = ui.cavity->x0 + padx; + parcel.x1 = ui.cavity->x0 + padx + slave_w; + parcel.y0 = ui.cavity->y0 + pady; + parcel.y1 = ui.cavity->y1 - pady; + ui.cavity->x0 = parcel.x1 + padx; + break; + case R: + parcel.x0 = ui.cavity->x1 - padx - slave_w; + parcel.x1 = ui.cavity->x1 - padx; + parcel.y0 = ui.cavity->y0 + pady; + parcel.y1 = ui.cavity->y1 - pady; + ui.cavity->x1 = parcel.x0 - padx; + break; + } + + parcel_w = parcel.x1 - parcel.x0; + parcel_h = parcel.y1 - parcel.y0; + + if (fill & X) + slave_w = parcel_w; + if (fill & Y) + slave_h = parcel_h; + + anchor_x = parcel_w - slave_w; + anchor_y = parcel_h - slave_h; + + switch (anchor) + { + default: + case CENTER: + slave.x0 = parcel.x0 + anchor_x / 2; + slave.y0 = parcel.y0 + anchor_y / 2; + break; + case N: + slave.x0 = parcel.x0 + anchor_x / 2; + slave.y0 = parcel.y0; + break; + case NE: + slave.x0 = parcel.x0 + anchor_x; + slave.y0 = parcel.y0; + break; + case E: + slave.x0 = parcel.x0 + anchor_x; + slave.y0 = parcel.y0 + anchor_y / 2; + break; + case SE: + slave.x0 = parcel.x0 + anchor_x; + slave.y0 = parcel.y0 + anchor_y; + break; + case S: + slave.x0 = parcel.x0 + anchor_x / 2; + slave.y0 = parcel.y0 + anchor_y; + break; + case SW: + slave.x0 = parcel.x0; + slave.y0 = parcel.y0 + anchor_y; + break; + case W: + slave.x0 = parcel.x0; + slave.y0 = parcel.y0 + anchor_y / 2; + break; + case NW: + slave.x0 = parcel.x0; + slave.y0 = parcel.y0; + break; + } + + slave.x1 = slave.x0 + slave_w; + slave.y1 = slave.y0 + slave_h; + + return slave; +} + +fz_irect ui_pack(int slave_w, int slave_h) +{ + return ui_pack_layout(slave_w, slave_h, ui.layout->side, ui.layout->fill, ui.layout->anchor, ui.layout->padx, ui.layout->pady); +} + +int ui_available_width(void) +{ + return ui.cavity->x1 - ui.cavity->x0 - ui.layout->padx * 2; +} + +int ui_available_height(void) +{ + return ui.cavity->y1 - ui.cavity->y0 - ui.layout->pady * 2; +} + +void ui_pack_push(fz_irect cavity) +{ + *(++ui.cavity) = cavity; + ++ui.layout; + ui.layout->side = ALL; + ui.layout->fill = BOTH; + ui.layout->anchor = NW; + ui.layout->padx = 0; + ui.layout->pady = 0; +} + +void ui_pack_pop(void) +{ + --ui.cavity; + --ui.layout; +} + +void ui_layout(enum side side, enum fill fill, enum anchor anchor, int padx, int pady) +{ + ui.layout->side = side; + ui.layout->fill = fill; + ui.layout->anchor = anchor; + ui.layout->padx = padx; + ui.layout->pady = pady; +} + +void ui_panel_begin(int w, int h, int padx, int pady, int opaque) +{ + fz_irect area = ui_pack(w, h); + if (opaque) + { + glColorHex(UI_COLOR_PANEL); + glRectf(area.x0, area.y0, area.x1, area.y1); + } + area.x0 += padx; area.y0 += pady; + area.x1 -= padx; area.y1 -= pady; + ui_pack_push(area); +} + +void ui_panel_end(void) +{ + ui_pack_pop(); +} + +void ui_dialog_begin(int w, int h) +{ + fz_irect area; + int x, y; + w += 24 + 4; + h += 24 + 4; + if (w > ui.window_w) w = ui.window_w - 20; + if (h > ui.window_h) h = ui.window_h - 20; + x = (ui.window_w-w)/2; + y = (ui.window_h-h)/3; + area = fz_make_irect(x, y, x+w, y+h); + ui_draw_bevel_rect(area, UI_COLOR_PANEL, 0); + area = fz_expand_irect(area, -14); + ui_pack_push(area); +} + +void ui_dialog_end(void) +{ + ui_pack_pop(); +} + +void ui_spacer(void) +{ + ui_pack(ui.lineheight / 2, ui.lineheight / 2); +} + +void ui_label(const char *fmt, ...) +{ + char buf[512]; + struct line lines[20]; + int avail, used, n; + fz_irect area; + va_list ap; + + va_start(ap, fmt); + fz_vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + + avail = ui_available_width(); + n = ui_break_lines(buf, lines, nelem(lines), avail, &used); + area = ui_pack(used, n * ui.lineheight); + glColorHex(UI_COLOR_TEXT_FG); + ui_draw_lines(area.x0, area.y0, lines, n); +} + +int ui_button(const char *label) +{ + return ui_button_aux(label, 0); +} + +int ui_button_aux(const char *label, int flags) +{ + int width = ui_measure_string(label); + fz_irect area = ui_pack(width + 20, ui.gridsize); + int text_x = area.x0 + ((area.x1 - area.x0) - width) / 2; + int pressed = 0; + int disabled = (flags & 1); + + if (!disabled) + { + if (ui_mouse_inside(area)) + { + ui.hot = label; + if (!ui.active && ui.down) + ui.active = label; + } + + pressed = (ui.hot == label && ui.active == label && ui.down); + } + ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed); + glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG); + ui_draw_string(text_x + pressed, area.y0+3 + pressed, label); + + return !disabled && ui.hot == label && ui.active == label && !ui.down; +} + +int ui_checkbox(const char *label, int *value) +{ + return ui_checkbox_aux(label, value, 0); +} + +int ui_checkbox_aux(const char *label, int *value, int flags) +{ + int width = ui_measure_string(label); + fz_irect area = ui_pack(13 + 4 + width, ui.lineheight); + fz_irect mark = { area.x0, area.y0 + ui.baseline-12, area.x0 + 13, area.y0 + ui.baseline+1 }; + int pressed = 0; + int disabled = (flags & 1); + + glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG); + ui_draw_string(mark.x1 + 4, area.y0, label); + + if (!disabled) + { + if (ui_mouse_inside(area)) + { + ui.hot = label; + if (!ui.active && ui.down) + ui.active = label; + } + + if (ui.hot == label && ui.active == label && !ui.down) + *value = !*value; + + pressed = (ui.hot == label && ui.active == label && ui.down); + } + ui_draw_bevel_rect(mark, (disabled || pressed) ? UI_COLOR_PANEL : UI_COLOR_TEXT_BG, 1); + if (*value) + { + float ax = mark.x0+2 + 1, ay = mark.y0+2 + 3; + float bx = mark.x0+2 + 4, by = mark.y0+2 + 5; + float cx = mark.x0+2 + 8, cy = mark.y0+2 + 1; + glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG); + glBegin(GL_TRIANGLE_STRIP); + glVertex2f(ax, ay); glVertex2f(ax, ay+3); + glVertex2f(bx, by); glVertex2f(bx, by+3); + glVertex2f(cx, cy); glVertex2f(cx, cy+3); + glEnd(); + } + + return !disabled && ui.hot == label && ui.active == label && !ui.down; +} + +int ui_slider(int *value, int min, int max, int width) +{ + static int start_value = 0; + fz_irect area = ui_pack(width, ui.lineheight); + int m = 6; + int w = area.x1 - area.x0 - m * 2; + int h = area.y1 - area.y0; + fz_irect gutter = { area.x0, area.y0+h/2-2, area.x1, area.y0+h/2+2 }; + fz_irect thumb; + int x; + + if (ui_mouse_inside(area)) + { + ui.hot = value; + if (!ui.active && ui.down) + { + ui.active = value; + start_value = *value; + } + } + + if (ui.active == value) + { + if (ui.y < area.y0 || ui.y > area.y1) + *value = start_value; + else + { + float v = (float)(ui.x - (area.x0+m)) / w; + *value = fz_clamp(min + v * (max - min), min, max); + } + } + + x = ((*value - min) * w) / (max - min); + thumb = fz_make_irect(area.x0+m + x-m, area.y0, area.x0+m + x+m, area.y1); + + ui_draw_bevel(gutter, 1); + ui_draw_bevel_rect(thumb, UI_COLOR_BUTTON, 0); + + return *value != start_value && ui.active == value && !ui.down; +} + +void ui_splitter(int *start, int *v, int min, int max, enum side side) +{ + fz_irect area = { 0 }; + + if (side == L || side == R) + area = ui_pack(4, 0); + else if (side == T || side == B) + area = ui_pack(0, 4); + + if (ui_mouse_inside(area)) + { + ui.hot = v; + if (!ui.active && ui.down) + { + ui.active = v; + *start = *v; + } + } + + if (ui.active == v) + { + // how we slide the splitter coords depends on the packing direction + switch (ui.layout->side) + { + default: + case L: *v = fz_clampi(*start + (ui.x - ui.down_x), min, max); break; + case R: *v = fz_clampi(*start + (ui.down_x - ui.x), min, max); break; + case B: *v = fz_clampi(*start + (ui.down_y - ui.y), min, max); break; + case T: *v = fz_clampi(*start + (ui.y - ui.down_y), min, max); break; + } + } + + if (ui.hot == v || ui.active == v) + { + if (side == L || side == R) + ui.cursor = GLUT_CURSOR_LEFT_RIGHT; + else if (side == T || side == B) + ui.cursor = GLUT_CURSOR_UP_DOWN; + } + + if (side == R) + { + glColorHex(UI_COLOR_PANEL); + glRectf(area.x0+0, area.y0, area.x0+2, area.y1); + glColorHex(UI_COLOR_BEVEL_2); + glRectf(area.x0+2, area.y0, area.x0+3, area.y1); + glColorHex(UI_COLOR_BEVEL_1); + glRectf(area.x0+3, area.y0, area.x0+4, area.y1); + } + else if (side == L) + { + glColorHex(UI_COLOR_BEVEL_4); + glRectf(area.x0+0, area.y0, area.x0+1, area.y1); + glColorHex(UI_COLOR_BEVEL_3); + glRectf(area.x0+1, area.y0, area.x0+3, area.y1); + glColorHex(UI_COLOR_PANEL); + glRectf(area.x0+2, area.y0, area.x0+4, area.y1); + } + else if (side == T) + { + glColorHex(UI_COLOR_BEVEL_4); + glRectf(area.x0, area.y0+0, area.x1, area.y0+1); + glColorHex(UI_COLOR_BEVEL_3); + glRectf(area.x0, area.y0+1, area.x1, area.y0+2); + glColorHex(UI_COLOR_PANEL); + glRectf(area.x0, area.y0+2, area.x1, area.y0+4); + } + else if (side == B) + { + glColorHex(UI_COLOR_PANEL); + glRectf(area.x0, area.y0+0, area.x1, area.y0+2); + glColorHex(UI_COLOR_BEVEL_2); + glRectf(area.x0, area.y0+2, area.x1, area.y0+3); + glColorHex(UI_COLOR_BEVEL_1); + glRectf(area.x0, area.y0+3, area.x1, area.y0+4); + } +} + +void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max, int *sticky) +{ + static float start_top = 0; /* we can only drag in one scrollbar at a time, so static is safe */ + float top; + + int total_h = y1 - y0; + int thumb_h = fz_maxi(x1 - x0, total_h * page_size / max); + int avail_h = total_h - thumb_h; + + max -= page_size; + + if (max <= 0) + { + *value = 0; + glColorHex(UI_COLOR_SCROLLBAR); + glRectf(x0, y0, x1, y1); + return; + } + + if (sticky) + { + if (*sticky <= -1) + *value = 0; + else if (*sticky >= 1) + *value = max; + } + + top = (float) *value * avail_h / max; + + if (ui.down && !ui.active) + { + if (ui.x >= x0 && ui.x < x1 && ui.y >= y0 && ui.y < y1) + { + if (ui.y < y0 + top) + { + ui.active = "pgup"; + *value -= page_size; + } + else if (ui.y >= y0 + top + thumb_h) + { + ui.active = "pgdn"; + *value += page_size; + } + else + { + ui.hot = value; + ui.active = value; + start_top = top; + } + } + } + + if (ui.active == value) + { + *value = (start_top + ui.y - ui.down_y) * max / avail_h; + } + + if (*value < 0) + *value = 0; + else if (*value > max) + *value = max; + + if (sticky) + { + if (*sticky == 0 && *value == 0) + *sticky = -1; + else if (*sticky == 0 && *value == max) + *sticky = 1; + else if (*sticky <= -1 && *value != 0) + *sticky = 0; + else if (*sticky >= 1 && *value != max) + *sticky = 0; + } + + top = (float) *value * avail_h / max; + + glColorHex(UI_COLOR_SCROLLBAR); + glRectf(x0, y0, x1, y1); + ui_draw_ibevel_rect(fz_make_irect(x0, y0+top, x1, y0+top+thumb_h), UI_COLOR_BUTTON, 0); +} + +void ui_tree_begin(struct list *list, int count, int req_w, int req_h, int is_tree) +{ + static int start_scroll_y = 0; /* we can only drag in one list at a time, so static is safe */ + + fz_irect outer_area = ui_pack(req_w, req_h); + fz_irect area = { outer_area.x0+2, outer_area.y0+2, outer_area.x1-2, outer_area.y1-2 }; + + int max_scroll_y = count * ui.lineheight - (area.y1-area.y0); + + if (max_scroll_y > 0) + area.x1 -= 16; + + if (ui_mouse_inside(area)) + { + ui.hot = list; + if (!ui.active && ui.middle) + { + ui.active = list; + start_scroll_y = list->scroll_y; + } + } + + /* middle button dragging */ + if (ui.active == list) + list->scroll_y = start_scroll_y + (ui.middle_y - ui.y) * 5; + + /* scroll wheel events */ + if (ui.hot == list) + list->scroll_y -= ui.scroll_y * ui.lineheight * 3; + + /* keyboard keys */ + if (ui.hot == list && ui.key == KEY_HOME) + list->scroll_y = 0; + if (ui.hot == list && ui.key == KEY_END) + list->scroll_y = max_scroll_y; + if (ui.hot == list && ui.key == KEY_PAGE_UP) + list->scroll_y -= ((area.y1 - area.y0) / ui.lineheight) * ui.lineheight; + if (ui.hot == list && ui.key == KEY_PAGE_DOWN) + list->scroll_y += ((area.y1 - area.y0) / ui.lineheight) * ui.lineheight; + + /* clamp scrolling to client area */ + if (list->scroll_y >= max_scroll_y) + list->scroll_y = max_scroll_y; + if (list->scroll_y < 0) + list->scroll_y = 0; + + ui_draw_bevel_rect(outer_area, UI_COLOR_TEXT_BG, 1); + if (max_scroll_y > 0) + { + ui_scrollbar(area.x1, area.y0, area.x1+16, area.y1, + &list->scroll_y, area.y1-area.y0, count * ui.lineheight, NULL); + } + + list->is_tree = is_tree; + list->area = area; + list->item_y = area.y0 - list->scroll_y; + + glScissor(list->area.x0, ui.window_h-list->area.y1, list->area.x1-list->area.x0, list->area.y1-list->area.y0); + glEnable(GL_SCISSOR_TEST); +} + +int ui_tree_item(struct list *list, const void *id, const char *label, int selected, int depth, int is_branch, int *is_open) +{ + fz_irect area = { list->area.x0, list->item_y, list->area.x1, list->item_y + ui.lineheight }; + int x_handle, x_item; + + x_item = ui.lineheight / 4; + x_item += depth * ui.lineheight; + x_handle = x_item; + if (list->is_tree) + x_item += ui_measure_character(0x25BC) + ui.lineheight / 4; + + /* only process visible items */ + if (area.y1 >= list->area.y0 && area.y0 <= list->area.y1) + { + if (ui_mouse_inside(list->area) && ui_mouse_inside(area)) + { + if (list->is_tree && ui.x < area.x0 + x_item) + { + ui.hot = is_open; + } + else + ui.hot = id; + if (!ui.active && ui.down) + { + if (list->is_tree && ui.hot == is_open) + *is_open = !*is_open; + ui.active = ui.hot; + } + } + + if (ui.active == id || selected) + { + glColorHex(UI_COLOR_TEXT_SEL_BG); + glRectf(area.x0, area.y0, area.x1, area.y1); + glColorHex(UI_COLOR_TEXT_SEL_FG); + } + else + { + glColorHex(UI_COLOR_TEXT_FG); + } + + ui_draw_string(area.x0 + x_item, area.y0, label); + if (list->is_tree && is_branch) + ui_draw_character(area.x0 + x_handle, area.y0, + *is_open ? 0x25BC : 0x25B6); + } + + list->item_y += ui.lineheight; + + /* trigger on mouse up */ + return ui.active == id && !ui.down; +} + +void ui_list_begin(struct list *list, int count, int req_w, int req_h) +{ + ui_tree_begin(list, count, req_w, req_h, 0); +} + +int ui_list_item(struct list *list, const void *id, const char *label, int selected) +{ + return ui_tree_item(list, id, label, selected, 0, 0, NULL); +} + +void ui_tree_end(struct list *list) +{ + glDisable(GL_SCISSOR_TEST); +} + +void ui_list_end(struct list *list) +{ + ui_tree_end(list); +} + +void ui_label_with_scrollbar(char *text, int width, int height, int *scroll, int *sticky) +{ + struct line lines[500]; + fz_irect area; + int n; + + area = ui_pack(width, height); + n = ui_break_lines(text, lines, nelem(lines), area.x1-area.x0 - 16, NULL); + if (n > (area.y1-area.y0) / ui.lineheight) + { + if (ui_mouse_inside(area)) + { + *scroll -= ui.scroll_y * ui.lineheight * 3; + if (ui.scroll_y != 0 && sticky) + *sticky = 0; + } + ui_scrollbar(area.x1-16, area.y0, area.x1, area.y1, + scroll, area.y1-area.y0, n * ui.lineheight, sticky); + } + else + *scroll = 0; + + glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0-16, area.y1-area.y0); + glEnable(GL_SCISSOR_TEST); + glColorHex(UI_COLOR_TEXT_FG); + ui_draw_lines(area.x0, area.y0 - *scroll, lines, n); + glDisable(GL_SCISSOR_TEST); +} + +int ui_popup(const void *id, const char *label, int is_button, int count) +{ + return ui_popup_aux(id, label, is_button, count, 0); +} + +int ui_popup_aux(const void *id, const char *label, int is_button, int count, int flags) +{ + int width = ui_measure_string(label); + fz_irect area = ui_pack(width + 22 + 6, ui.gridsize); + fz_irect menu_area; + int pressed = 0; + int disabled = (flags & 1); + + if (!disabled) + { + if (ui_mouse_inside(area)) + { + ui.hot = id; + if (!ui.active && ui.down) + ui.active = id; + } + + pressed = (ui.active == id); + } + + if (is_button) + { + ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed); + glColorHex(disabled? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG); + ui_draw_string(area.x0 + 6+pressed, area.y0+3+pressed, label); + glBegin(GL_TRIANGLES); + glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9); + glVertex2f(area.x1+pressed-8, area.y0+pressed+9); + glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14); + glEnd(); + } + else + { + fz_irect arrow = { area.x1-22, area.y0+2, area.x1-2, area.y1-2 }; + ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1); + glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG); + ui_draw_string(area.x0 + 6, area.y0+3, label); + ui_draw_ibevel_rect(arrow, UI_COLOR_BUTTON, pressed); + + glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG); + glBegin(GL_TRIANGLES); + glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9); + glVertex2f(area.x1+pressed-8, area.y0+pressed+9); + glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14); + glEnd(); + } + + if (pressed) + { + ui.overlay = 1; + + glNewList(ui.overlay_list, GL_COMPILE); + + /* Area inside the border line */ + menu_area.x0 = area.x0+1; + menu_area.x1 = area.x1-1; // TODO: width of submenu + if (area.y1+2 + count * ui.lineheight < ui.window_h) + { + menu_area.y0 = area.y1+2; + menu_area.y1 = menu_area.y0 + count * ui.lineheight; + } + else + { + menu_area.y1 = area.y0-2; + menu_area.y0 = menu_area.y1 - count * ui.lineheight; + } + + glColorHex(UI_COLOR_TEXT_FG); + glRectf(menu_area.x0-1, menu_area.y0-1, menu_area.x1+1, menu_area.y1+1); + glColorHex(UI_COLOR_TEXT_BG); + glRectf(menu_area.x0, menu_area.y0, menu_area.x1, menu_area.y1); + + ui_pack_push(menu_area); + ui_layout(T, X, NW, 0, 0); + } + + return pressed; +} + +int ui_popup_item(const char *title) +{ + return ui_popup_item_aux(title, 0); +} + +int ui_popup_item_aux(const char *title, int flags) +{ + fz_irect area = ui_pack(0, ui.lineheight); + int disabled = (flags & 1); + + if (!disabled && ui_mouse_inside(area)) + { + ui.hot = title; + glColorHex(UI_COLOR_TEXT_SEL_BG); + glRectf(area.x0, area.y0, area.x1, area.y1); + glColorHex(UI_COLOR_TEXT_SEL_FG); + ui_draw_string(area.x0 + 4, area.y0, title); + } + else + { + glColorHex(disabled ? UI_COLOR_TEXT_GRAY : UI_COLOR_TEXT_FG); + ui_draw_string(area.x0 + 4, area.y0, title); + } + + return !disabled && ui.hot == title && !ui.down; +} + +void ui_popup_end(void) +{ + glEndList(); + ui_pack_pop(); +} + +int ui_select(const void *id, const char *current, const char *options[], int n) +{ + return ui_select_aux(id, current, options, n, 0); +} + +int ui_select_aux(const void *id, const char *current, const char *options[], int n, int flags) +{ + int i, choice = -1; + if (ui_popup_aux(id, current, 0, n, flags)) + { + for (i = 0; i < n; ++i) + if (ui_popup_item_aux(options[i], flags)) + choice = i; + ui_popup_end(); + } + return choice; +} + +void ui_select_annot(pdf_annot *annot) +{ + pdf_drop_annot(ctx, ui.selected_annot); + ui.selected_annot = annot; +}
