summaryrefslogtreecommitdiff
path: root/platform/gl
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2018-02-14 15:10:29 +0100
committerRobin Watts <robin.watts@artifex.com>2018-06-22 16:48:46 +0100
commitbde703f83b83232f830426b45957b10e4e44043d (patch)
tree27da098843ec9e42879bc0a4fdce7d3ace81815f /platform/gl
parenta313e64f9a1912e1c4a1102eaf345749ca8e26cb (diff)
downloadmupdf-bde703f83b83232f830426b45957b10e4e44043d.tar.xz
gl: Add layout packer and various widgets.
* Move window setup and swap buffer calls into ui_begin/end. * Rearrange source files. * Simplify grab logic: Make it more robust in the face of user errors, such as setting ui.active to NULL explicitly. * Copy text to primary selection as well.
Diffstat (limited to 'platform/gl')
-rw-r--r--platform/gl/gl-app.h105
-rw-r--r--platform/gl/gl-font.c28
-rw-r--r--platform/gl/gl-input.c51
-rw-r--r--platform/gl/gl-main.c811
-rw-r--r--platform/gl/gl-ui.c855
5 files changed, 1144 insertions, 706 deletions
diff --git a/platform/gl/gl-app.h b/platform/gl/gl-app.h
index 2aea2aed..6efb1ff7 100644
--- a/platform/gl/gl-app.h
+++ b/platform/gl/gl-app.h
@@ -6,6 +6,7 @@ int win_open_file(char *buf, int len);
#include "mupdf/fitz.h"
#include "mupdf/ucdn.h"
+#include "mupdf/pdf.h" /* for pdf specifics and forms */
#ifndef __APPLE__
#include <GL/freeglut.h>
@@ -13,7 +14,7 @@ int win_open_file(char *buf, int len);
#include <GLUT/glut.h>
#endif
-extern fz_context *ctx;
+/* UI */
enum
{
@@ -55,33 +56,62 @@ enum
KEY_F12,
};
+enum side { ALL, T, R, B, L };
+enum fill { NONE = 0, X = 1, Y = 2, BOTH = 3 };
+enum anchor { CENTER, N, NE, E, SE, S, SW, W, NW };
+
+struct layout
+{
+ enum side side;
+ enum fill fill;
+ enum anchor anchor;
+ int padx, pady;
+};
+
struct ui
{
+ int window_w, window_h;
+
int x, y;
- int down, middle, right;
+ int down, down_x, down_y;
+ int middle, middle_x, middle_y;
+ int right, right_x, right_y;
+
int scroll_x, scroll_y;
int key, mod, plain;
- void *hot, *active, *focus;
+ int grab_down, grab_middle, grab_right;
+ const void *hot, *active, *focus;
int fontsize;
int baseline;
int lineheight;
+
+ struct layout *layout;
+ fz_irect *cavity;
+ struct layout layout_stack[32];
+ fz_irect cavity_stack[32];
};
extern struct ui ui;
+void ui_init(int w, int h, const char *title);
+void ui_quit(void);
+void ui_invalidate(void);
+void ui_finish(void);
+
void ui_set_clipboard(const char *buf);
const char *ui_get_clipboard(void);
-void ui_init_fonts(fz_context *ctx, float pixelsize);
-void ui_finish_fonts(fz_context *ctx);
-float ui_measure_character(fz_context *ctx, int ucs);
-void ui_begin_text(fz_context *ctx);
-float ui_draw_character(fz_context *ctx, int ucs, float x, float y);
-void ui_end_text(fz_context *ctx);
-float ui_draw_string(fz_context *ctx, float x, float y, const char *str);
-float ui_measure_string(fz_context *ctx, char *str);
+void ui_init_fonts(float pixelsize);
+void ui_finish_fonts(void);
+float ui_measure_character(int ucs);
+void ui_begin_text(void);
+float ui_draw_character(int ucs, float x, float y);
+void ui_end_text(void);
+
+float ui_draw_string(float x, float y, const char *str);
+float ui_measure_string(const char *str);
struct texture
{
@@ -90,12 +120,63 @@ struct texture
float s, t;
};
+void ui_texture_from_pixmap(struct texture *tex, fz_pixmap *pix);
void ui_draw_image(struct texture *tex, float x, float y);
+enum
+{
+ UI_INPUT_CANCEL = -1,
+ UI_INPUT_ACCEPT = 1,
+ UI_INPUT_CONTINUE = 0,
+};
+
struct input
{
char text[256];
char *end, *p, *q;
};
-int ui_input(int x0, int y0, int x1, int y1, struct input *input);
+struct list
+{
+ fz_irect area;
+ int scroll_y;
+ int item_y;
+};
+
+void ui_begin(void);
+void ui_end(void);
+
+int ui_mouse_inside(fz_irect *area);
+
+void ui_layout(enum side side, enum fill fill, enum anchor anchor, int padx, int pady);
+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 ui_pack(int slave_w, int slave_h);
+void ui_pack_push(fz_irect cavity);
+void ui_pack_pop(void);
+
+void ui_panel_begin(int w, int h, int opaque);
+void ui_panel_end(void);
+
+void ui_spacer(void);
+void ui_label(const char *fmt, ...);
+int ui_button(const char *label);
+void ui_checkbox(const char *label, int *value);
+void ui_slider(float *value, float min, float max, int width);
+
+void ui_input_init(struct input *input, const char *text);
+int ui_input(struct input *input, int width);
+void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max);
+
+void ui_list_begin(struct list *list, int count, int req_w, int req_h);
+int ui_list_item(struct list *list, void *id, int indent, const char *label, int selected);
+void ui_list_end(struct list *list);
+
+/* App */
+
+extern fz_context *ctx;
+extern pdf_document *pdf;
+extern pdf_page *page;
+extern fz_matrix page_ctm, page_inv_ctm;
+extern int page_x_ofs, page_y_ofs;
+
+void run_main_loop(void);
diff --git a/platform/gl/gl-font.c b/platform/gl/gl-font.c
index 44f42c3c..08dd773a 100644
--- a/platform/gl/gl-font.c
+++ b/platform/gl/gl-font.c
@@ -76,7 +76,7 @@ static void clear_font_cache(void)
g_cache_row_h = 0;
}
-void ui_init_fonts(fz_context *ctx, float pixelsize)
+void ui_init_fonts(float pixelsize)
{
const unsigned char *data;
int size;
@@ -98,7 +98,7 @@ void ui_init_fonts(fz_context *ctx, float pixelsize)
g_font_size = pixelsize;
}
-void ui_finish_fonts(fz_context *ctx)
+void ui_finish_fonts(void)
{
clear_font_cache();
fz_drop_font(ctx, g_font);
@@ -249,21 +249,21 @@ static float ui_draw_glyph(fz_font *font, int gid, float x, float y)
return fz_advance_glyph(ctx, font, gid, 0) * g_font_size;
}
-float ui_measure_character(fz_context *ctx, int ucs)
+float ui_measure_character(int ucs)
{
fz_font *font;
int gid = fz_encode_character_with_fallback(ctx, g_font, ucs, 0, 0, &font);
return fz_advance_glyph(ctx, font, gid, 0) * g_font_size;
}
-float ui_draw_character(fz_context *ctx, int ucs, float x, float y)
+float ui_draw_character(int ucs, float x, float y)
{
fz_font *font;
int gid = fz_encode_character_with_fallback(ctx, g_font, ucs, 0, 0, &font);
return ui_draw_glyph(font, gid, x, y);
}
-void ui_begin_text(fz_context *ctx)
+void ui_begin_text(void)
{
glBindTexture(GL_TEXTURE_2D, g_cache_tex);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -272,44 +272,40 @@ void ui_begin_text(fz_context *ctx)
glBegin(GL_QUADS);
}
-void ui_end_text(fz_context *ctx)
+void ui_end_text(void)
{
glEnd();
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
}
-float ui_draw_string(fz_context *ctx, float x, float y, const char *str)
+float ui_draw_string(float x, float y, const char *str)
{
int ucs;
- ui_begin_text(ctx);
+ ui_begin_text();
while (*str)
{
str += fz_chartorune(&ucs, str);
- x += ui_draw_character(ctx, ucs, x, y);
+ x += ui_draw_character(ucs, x, y);
}
- ui_end_text(ctx);
+ ui_end_text();
return x;
}
-float ui_measure_string(fz_context *ctx, char *str)
+float ui_measure_string(const char *str)
{
int ucs;
float x = 0;
- ui_begin_text(ctx);
-
while (*str)
{
str += fz_chartorune(&ucs, str);
- x += ui_measure_character(ctx, ucs);
+ x += ui_measure_character(ucs);
}
- ui_end_text(ctx);
-
return x;
}
diff --git a/platform/gl/gl-input.c b/platform/gl/gl-input.c
index db42cace..ad68a692 100644
--- a/platform/gl/gl-input.c
+++ b/platform/gl/gl-input.c
@@ -5,13 +5,13 @@
static void draw_string_part(float x, float y, const char *s, const char *e)
{
int c;
- ui_begin_text(ctx);
+ ui_begin_text();
while (s < e)
{
s += fz_chartorune(&c, s);
- x += ui_draw_character(ctx, c, x, y + ui.baseline);
+ x += ui_draw_character(c, x, y + ui.baseline);
}
- ui_end_text(ctx);
+ ui_end_text();
}
static float measure_string_part(const char *s, const char *e)
@@ -21,7 +21,7 @@ static float measure_string_part(const char *s, const char *e)
while (s < e)
{
s += fz_chartorune(&c, s);
- w += ui_measure_character(ctx, c);
+ w += ui_measure_character(c);
}
return w;
}
@@ -32,7 +32,7 @@ static char *find_string_location(char *s, char *e, float w, float x)
while (s < e)
{
int n = fz_chartorune(&c, s);
- float cw = ui_measure_character(ctx, c);
+ float cw = ui_measure_character(c);
if (w + (cw / 2) >= x)
return s;
w += cw;
@@ -212,9 +212,11 @@ static int ui_input_key(struct input *input)
}
break;
case KEY_ESCAPE:
- return -1;
+ ui.focus = NULL;
+ return UI_INPUT_CANCEL;
case KEY_ENTER:
- return 1;
+ ui.focus = NULL;
+ return UI_INPUT_ACCEPT;
case KEY_BACKSPACE:
if (input->p != input->q)
ui_input_delete_selection(input);
@@ -279,59 +281,66 @@ static int ui_input_key(struct input *input)
}
break;
}
- return 0;
+ return UI_INPUT_CONTINUE;
+}
+
+void ui_input_init(struct input *input, const char *text)
+{
+ fz_strlcpy(input->text, text, sizeof input->text);
+ input->end = input->text + strlen(input->text);
+ input->p = input->q = input->text;
}
-int ui_input(int x0, int y0, int x1, int y1, struct input *input)
+int ui_input(struct input *input, int width)
{
+ fz_irect area;
float px, qx;
char *p, *q;
int state;
- if (ui.x >= x0 && ui.x < x1 && ui.y >= y0 && ui.y < y1)
+ area = ui_pack(width, ui.lineheight + 4);
+
+ if (ui_mouse_inside(&area))
{
ui.hot = input;
if (!ui.active && ui.down)
{
- input->p = find_string_location(input->text, input->end, x0 + 2, ui.x);
+ input->p = find_string_location(input->text, input->end, area.x0 + 2, ui.x);
ui.active = input;
}
}
if (ui.active == input)
{
- input->q = find_string_location(input->text, input->end, x0 + 2, ui.x);
+ input->q = find_string_location(input->text, input->end, area.x0 + 2, ui.x);
ui.focus = input;
}
- if (!ui.focus)
- ui.focus = input;
-
if (ui.focus == input)
state = ui_input_key(input);
else
state = 0;
glColor4f(0, 0, 0, 1);
- glRectf(x0, y0, x1, y1);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
glColor4f(1, 1, 1, 1);
- glRectf(x0+1, y0+1, x1-1, y1-1);
+ glRectf(area.x0+1, area.y0+1, area.x1-1, area.y1-1);
p = input->p < input->q ? input->p : input->q;
q = input->p > input->q ? input->p : input->q;
- px = x0 + 2 + measure_string_part(input->text, p);
+ px = area.x0 + 2 + measure_string_part(input->text, p);
qx = px + measure_string_part(p, q);
- if (ui.focus)
+ if (ui.focus == input)
{
glColor4f(0.6f, 0.6f, 1.0f, 1.0f);
- glRectf(px, y0 + 2, qx+1, y1 - 2);
+ glRectf(px, area.y0 + 2, qx+1, area.y1 - 2);
}
glColor4f(0, 0, 0, 1);
- draw_string_part(x0 + 2, y0 + 2, input->text, input->end);
+ draw_string_part(area.x0 + 2, area.y0 + 2, input->text, input->end);
return state;
}
diff --git a/platform/gl/gl-main.c b/platform/gl/gl-main.c
index 994e7479..67f69f16 100644
--- a/platform/gl/gl-main.c
+++ b/platform/gl/gl-main.c
@@ -1,7 +1,5 @@
#include "gl-app.h"
-#include "mupdf/pdf.h" /* for pdf specifics and forms */
-
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -10,13 +8,12 @@
#include <unistd.h> /* for fork and exec */
#endif
-#ifndef FREEGLUT
-/* freeglut extension no-ops */
-void glutExit(void) {}
-void glutMouseWheelFunc(void *fn) {}
-void glutInitErrorFunc(void *fn) {}
-void glutInitWarningFunc(void *fn) {}
-#endif
+fz_context *ctx = NULL;
+pdf_document *pdf = NULL;
+pdf_page *page = NULL;
+int page_x_ofs = 0;
+int page_y_ofs = 0;
+fz_matrix page_ctm, page_inv_ctm;
enum
{
@@ -28,31 +25,8 @@ enum
DEFAULT_LAYOUT_W = 450,
DEFAULT_LAYOUT_H = 600,
DEFAULT_LAYOUT_EM = 12,
-
- /* Default UI sizes */
- DEFAULT_UI_FONTSIZE = 15,
- DEFAULT_UI_BASELINE = 14,
- DEFAULT_UI_LINEHEIGHT = 18,
};
-struct ui ui;
-fz_context *ctx = NULL;
-
-/* OpenGL capabilities */
-static int has_ARB_texture_non_power_of_two = 1;
-static GLint max_texture_size = 8192;
-
-static void ui_begin(void)
-{
- ui.hot = NULL;
-}
-
-static void ui_end(void)
-{
- if (!ui.down && !ui.middle && !ui.right)
- ui.active = NULL;
-}
-
static void open_browser(const char *uri)
{
#ifdef _WIN32
@@ -76,55 +50,6 @@ static void open_browser(const char *uri)
#endif
}
-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
-}
-
-void ogl_assert(fz_context *ctx, const char *msg)
-{
- int code = glGetError();
- if (code != GL_NO_ERROR) {
- fz_warn(ctx, "glGetError(%s): %s", msg, ogl_error_string(code));
- }
-}
-
-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);
-}
-
static const int zoom_list[] = { 18, 24, 36, 54, 72, 96, 120, 144, 180, 216, 288 };
static int zoom_in(int oldres)
@@ -160,9 +85,8 @@ static int layout_use_doc_css = 1;
static const char *title = "MuPDF/GL";
static fz_document *doc = NULL;
-static fz_page *page = NULL;
+static fz_page *fzpage = NULL;
static fz_stext_page *text = NULL;
-static pdf_document *pdf = NULL;
static fz_outline *outline = NULL;
static fz_link *links = NULL;
@@ -173,18 +97,12 @@ static int scroll_x = 0, scroll_y = 0;
static int canvas_x = 0, canvas_w = 100;
static int canvas_y = 0, canvas_h = 100;
-static struct texture annot_tex[256];
-static int annot_count = 0;
-
-static int window_w = 1, window_h = 1;
+static int outline_w = 260;
static int oldinvert = 0, currentinvert = 0;
static int oldpage = 0, currentpage = 0;
static float oldzoom = DEFRES, currentzoom = DEFRES;
static float oldrotate = 0, currentrotate = 0;
-static fz_matrix page_ctm, page_inv_ctm;
-static int loaded = 0;
-static int window = 0;
static int isfullscreen = 0;
static int showoutline = 0;
@@ -192,7 +110,6 @@ static int showlinks = 0;
static int showsearch = 0;
static int showinfo = 0;
static int showhelp = 0;
-static int doquit = 0;
struct mark
{
@@ -215,17 +132,6 @@ static int search_hit_page = -1;
static int search_hit_count = 0;
static fz_rect search_hit_bbox[5000];
-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;
-}
-
static void update_title(void)
{
static char buf[256];
@@ -238,42 +144,6 @@ static void update_title(void)
glutSetIconTitle(buf);
}
-void 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 load_page(void)
{
fz_rect rect;
@@ -287,55 +157,42 @@ void load_page(void)
text = NULL;
fz_drop_link(ctx, links);
links = NULL;
- fz_drop_page(ctx, page);
- page = NULL;
+ fz_drop_page(ctx, fzpage);
+ fzpage = NULL;
+
+ fzpage = fz_load_page(ctx, doc, currentpage);
+ if (pdf)
+ page = (pdf_page*)fzpage;
+
+ links = fz_load_links(ctx, fzpage);
+ text = fz_new_stext_page_from_page(ctx, fzpage, NULL);
- page = fz_load_page(ctx, doc, currentpage);
- links = fz_load_links(ctx, page);
- text = fz_new_stext_page_from_page(ctx, page, NULL);
/* compute bounds here for initial window size */
- fz_bound_page(ctx, page, &rect);
+ fz_bound_page(ctx, fzpage, &rect);
fz_transform_rect(&rect, &page_ctm);
- fz_round_rect(&irect, &rect);
+ fz_irect_from_rect(&irect, &rect);
page_tex.w = irect.x1 - irect.x0;
page_tex.h = irect.y1 - irect.y0;
-
- loaded = 1;
}
void render_page(void)
{
- fz_annot *annot;
fz_pixmap *pix;
- if (!loaded)
- load_page();
+ fz_scale(&page_ctm, currentzoom / 72, currentzoom / 72);
+ fz_pre_rotate(&page_ctm, -currentrotate);
+ fz_invert_matrix(&page_inv_ctm, &page_ctm);
- pix = fz_new_pixmap_from_page_contents(ctx, page, &page_ctm, fz_device_rgb(ctx), 0);
+ pix = fz_new_pixmap_from_page(ctx, fzpage, &page_ctm, fz_device_rgb(ctx), 0);
if (currentinvert)
{
fz_invert_pixmap(ctx, pix);
fz_gamma_pixmap(ctx, pix, 1 / 1.4f);
}
- texture_from_pixmap(&page_tex, pix);
+ ui_texture_from_pixmap(&page_tex, pix);
fz_drop_pixmap(ctx, pix);
-
- annot_count = 0;
- for (annot = fz_first_annot(ctx, page); annot; annot = fz_next_annot(ctx, annot))
- {
- pix = fz_new_pixmap_from_annot(ctx, annot, &page_ctm, fz_device_rgb(ctx), 1);
- texture_from_pixmap(&annot_tex[annot_count++], pix);
- fz_drop_pixmap(ctx, pix);
- if (annot_count >= nelem(annot_tex))
- {
- fz_warn(ctx, "too many annotations to display!");
- break;
- }
- }
-
- loaded = 0;
}
static struct mark save_mark()
@@ -426,201 +283,72 @@ static void pop_future(void)
push_history();
}
-static void ui_label_draw(int x0, int y0, int x1, int y1, const char *text)
-{
- glColor4f(1, 1, 1, 1);
- glRectf(x0, y0, x1, y1);
- glColor4f(0, 0, 0, 1);
- ui_draw_string(ctx, x0 + 2, y0 + 2 + ui.baseline, text);
-}
-
-static void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max)
+static int count_outline(fz_outline *node)
{
- static float saved_top = 0;
- static int saved_ui_y = 0;
- 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;
- glColor4f(0.6f, 0.6f, 0.6f, 1.0f);
- glRectf(x0, y0, x1, y1);
- return;
- }
-
- top = (float) *value * avail_h / max;
-
- if (ui.down && !ui.active)
+ int n = 0;
+ while (node)
{
- if (ui.x >= x0 && ui.x < x1 && ui.y >= y0 && ui.y < y1)
+ if (node->page >= 0)
{
- if (ui.y < top)
- {
- ui.active = "pgdn";
- *value -= page_size;
- }
- else if (ui.y >= top + thumb_h)
- {
- ui.active = "pgup";
- *value += page_size;
- }
- else
- {
- ui.hot = value;
- ui.active = value;
- saved_top = top;
- saved_ui_y = ui.y;
- }
+ n += 1;
+ if (node->down)
+ n += count_outline(node->down);
}
- }
-
- if (ui.active == value)
- {
- *value = (saved_top + ui.y - saved_ui_y) * max / avail_h;
- }
-
- if (*value < 0)
- *value = 0;
- else if (*value > max)
- *value = max;
-
- top = (float) *value * avail_h / max;
-
- glColor4f(0.6f, 0.6f, 0.6f, 1.0f);
- glRectf(x0, y0, x1, y1);
- glColor4f(0.8f, 0.8f, 0.8f, 1.0f);
- glRectf(x0, top, x1, top + thumb_h);
-}
-
-static int measure_outline_height(fz_outline *node)
-{
- int h = 0;
- while (node)
- {
- h += ui.lineheight;
- if (node->down)
- h += measure_outline_height(node->down);
node = node->next;
}
- return h;
+ return n;
}
-static int do_outline_imp(fz_outline *node, int end, int x0, int x1, int x, int y)
+static void do_outline_imp(struct list *list, int end, fz_outline *node, int depth)
{
- int h = 0;
- int p = currentpage;
- int n = end;
+ int selected;
while (node)
{
- p = node->page;
+ int p = node->page;
if (p >= 0)
{
- if (ui.x >= x0 && ui.x < x1 && ui.y >= y + h && ui.y < y + h + ui.lineheight)
- {
- ui.hot = node;
- if (!ui.active && ui.down)
- {
- ui.active = node;
- jump_to_page_xy(p, node->x, node->y);
- glutPostRedisplay(); /* we changed the current page, so force a redraw */
- }
- }
-
- n = end;
+ int n = end;
if (node->next && node->next->page >= 0)
- {
n = node->next->page;
- }
- if (currentpage == p || (currentpage > p && currentpage < n))
- {
- glColor4f(0.9f, 0.9f, 0.9f, 1.0f);
- glRectf(x0, y + h, x1, y + h + ui.lineheight);
- }
- }
- glColor4f(0, 0, 0, 1);
- ui_draw_string(ctx, x, y + h + ui.baseline, node->title);
- h += ui.lineheight;
- if (node->down)
- h += do_outline_imp(node->down, n, x0, x1, x + ui.lineheight, y + h);
+ selected = (currentpage == p || (currentpage > p && currentpage < n));
+ if (ui_list_item(list, node, depth * ui.lineheight, node->title, selected))
+ jump_to_page_xy(p, node->x, node->y);
+ if (node->down)
+ do_outline_imp(list, n, node->down, depth + 1);
+ }
node = node->next;
}
- return h;
}
-static void do_outline(fz_outline *node, int outline_w)
+static void do_outline(fz_outline *node)
{
- static char *id = "outline";
- static int outline_scroll_y = 0;
- static int saved_outline_scroll_y = 0;
- static int saved_ui_y = 0;
-
- int outline_h;
- int total_h;
-
- outline_w -= ui.lineheight;
- outline_h = window_h;
- total_h = measure_outline_height(outline);
-
- if (ui.x >= 0 && ui.x < outline_w && ui.y >= 0 && ui.y < outline_h)
- {
- ui.hot = id;
- if (!ui.active && ui.middle)
- {
- ui.active = id;
- saved_ui_y = ui.y;
- saved_outline_scroll_y = outline_scroll_y;
- }
- }
-
- if (ui.active == id)
- outline_scroll_y = saved_outline_scroll_y + (saved_ui_y - ui.y) * 5;
-
- if (ui.hot == id)
- outline_scroll_y -= ui.scroll_y * ui.lineheight * 3;
-
- ui_scrollbar(outline_w, 0, outline_w+ui.lineheight, outline_h, &outline_scroll_y, outline_h, total_h);
-
- glScissor(0, 0, outline_w, outline_h);
- glEnable(GL_SCISSOR_TEST);
-
- glColor4f(1, 1, 1, 1);
- glRectf(0, 0, outline_w, outline_h);
-
- do_outline_imp(outline, fz_count_pages(ctx, doc), 0, outline_w, 10, -outline_scroll_y);
-
- glDisable(GL_SCISSOR_TEST);
+ static struct list list = {};
+ ui_layout(L, BOTH, NW, 0, 0);
+ ui_list_begin(&list, count_outline(node), outline_w, 0);
+ do_outline_imp(&list, fz_count_pages(ctx, doc), node, 1);
+ ui_list_end(&list);
}
-static void do_links(fz_link *link, int xofs, int yofs)
+static void do_links(fz_link *link)
{
- fz_rect r;
- float x, y;
+ fz_rect bounds;
+ fz_irect area;
float link_x, link_y;
- x = ui.x;
- y = ui.y;
-
- xofs -= page_tex.x;
- yofs -= page_tex.y;
-
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
while (link)
{
- r = link->rect;
- fz_transform_rect(&r, &page_ctm);
+ bounds = link->rect;
+ fz_transform_rect(&bounds, &page_ctm);
+ fz_irect_from_rect(&area, &bounds);
+ fz_translate_irect(&area, page_x_ofs, page_y_ofs);
- if (x >= xofs + r.x0 && x < xofs + r.x1 && y >= yofs + r.y0 && y < yofs + r.y1)
+ if (ui_mouse_inside(&area))
{
ui.hot = link;
if (!ui.active && ui.down)
@@ -635,7 +363,7 @@ static void do_links(fz_link *link, int xofs, int yofs)
glColor4f(0, 0, 1, 0.2f);
else
glColor4f(0, 0, 1, 0.1f);
- glRectf(xofs + r.x0, yofs + r.y0, xofs + r.x1, yofs + r.y1);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
}
if (ui.active == link && !ui.down)
@@ -651,7 +379,6 @@ static void do_links(fz_link *link, int xofs, int yofs)
jump_to_page_xy(p, link_x, link_y);
else
fz_warn(ctx, "cannot find link destination '%s'", link->uri);
- glutPostRedisplay(); /* we changed the current page, so force a redraw */
}
}
}
@@ -714,50 +441,44 @@ static void do_page_selection(int x0, int y0, int x1, int y1)
#endif
ui_set_clipboard(s);
fz_free(ctx, s);
- glutPostRedisplay();
}
}
}
-static void do_search_hits(int xofs, int yofs)
+static void do_search_hits(void)
{
- fz_rect r;
+ fz_rect bounds;
+ fz_irect area;
int i;
- xofs -= page_tex.x;
- yofs -= page_tex.y;
-
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
for (i = 0; i < search_hit_count; ++i)
{
- r = search_hit_bbox[i];
-
- fz_transform_rect(&r, &page_ctm);
+ bounds = search_hit_bbox[i];
+ fz_transform_rect(&bounds, &page_ctm);
+ fz_irect_from_rect(&area, &bounds);
+ fz_translate_irect(&area, page_x_ofs, page_y_ofs);
glColor4f(1, 0, 0, 0.4f);
- glRectf(xofs + r.x0, yofs + r.y0, xofs + r.x1, yofs + r.y1);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
}
glDisable(GL_BLEND);
}
-static void do_forms(float xofs, float yofs)
+static void do_forms(void)
{
static int do_forms_tag = 0;
pdf_ui_event event;
fz_point p;
- int i;
-
- for (i = 0; i < annot_count; ++i)
- ui_draw_image(&annot_tex[i], xofs - page_tex.x, yofs - page_tex.y);
if (!pdf || search_active)
return;
- p.x = xofs - page_tex.x + ui.x;
- p.y = xofs - page_tex.x + ui.y;
+ p.x = page_x_ofs + ui.x;
+ p.y = page_y_ofs + ui.y;
fz_transform_point(&p, &page_inv_ctm);
if (ui.down && !ui.active)
@@ -771,7 +492,6 @@ static void do_forms(float xofs, float yofs)
ui.active = &do_forms_tag;
pdf_update_page(ctx, (pdf_page*)page);
render_page();
- glutPostRedisplay();
}
}
else if (ui.active == &do_forms_tag && !ui.down)
@@ -784,7 +504,6 @@ static void do_forms(float xofs, float yofs)
{
pdf_update_page(ctx, (pdf_page*)page);
render_page();
- glutPostRedisplay();
}
}
}
@@ -814,8 +533,8 @@ static void shrinkwrap(void)
{
int screen_w = glutGet(GLUT_SCREEN_WIDTH) - SCREEN_FURNITURE_W;
int screen_h = glutGet(GLUT_SCREEN_HEIGHT) - SCREEN_FURNITURE_H;
- int w = page_tex.w + canvas_x;
- int h = page_tex.h + canvas_y;
+ int w = page_tex.w + (showoutline ? outline_w : 0);
+ int h = page_tex.h;
if (screen_w > 0 && w > screen_w)
w = screen_w;
if (screen_h > 0 && h > screen_h)
@@ -867,6 +586,7 @@ static void load_document(void)
static void reload(void)
{
load_document();
+ load_page();
render_page();
update_title();
}
@@ -876,10 +596,6 @@ static void toggle_outline(void)
if (outline)
{
showoutline = !showoutline;
- if (showoutline)
- canvas_x = ui.lineheight * 16;
- else
- canvas_x = 0;
if (canvas_w == page_tex.w && canvas_h == page_tex.h)
shrinkwrap();
}
@@ -963,11 +679,6 @@ static void smart_move_forward(void)
}
}
-static void quit(void)
-{
- doquit = 1;
-}
-
static void clear_search(void)
{
search_hit_page = -1;
@@ -977,7 +688,7 @@ static void clear_search(void)
static void do_app(void)
{
if (ui.key == KEY_F4 && ui.mod == GLUT_ACTIVE_ALT)
- quit();
+ glutLeaveMainLoop();
if (ui.down || ui.middle || ui.right || ui.key)
showinfo = showhelp = 0;
@@ -992,7 +703,7 @@ static void do_app(void)
case 'L': showlinks = !showlinks; break;
case 'i': showinfo = !showinfo; break;
case 'r': reload(); break;
- case 'q': quit(); break;
+ case 'q': glutLeaveMainLoop(); break;
case 'I': currentinvert = !currentinvert; break;
case 'f': toggle_fullscreen(); break;
@@ -1050,6 +761,7 @@ static void do_app(void)
clear_search();
search_dir = 1;
showsearch = 1;
+ ui.focus = &search_input;
search_input.p = search_input.text;
search_input.q = search_input.end;
break;
@@ -1057,6 +769,7 @@ static void do_app(void)
clear_search();
search_dir = -1;
showsearch = 1;
+ ui.focus = &search_input;
search_input.p = search_input.text;
search_input.q = search_input.end;
break;
@@ -1072,7 +785,6 @@ static void do_app(void)
if (search_needle)
search_active = 1;
}
- glutPostRedisplay();
break;
case 'n':
search_dir = 1;
@@ -1086,7 +798,6 @@ static void do_app(void)
if (search_needle)
search_active = 1;
}
- glutPostRedisplay();
break;
}
@@ -1110,7 +821,7 @@ static int do_info_line(int x, int y, char *label, char *text)
{
char buf[512];
fz_snprintf(buf, sizeof buf, "%s: %s", label, text);
- ui_draw_string(ctx, x, y, buf);
+ ui_draw_string(x, y, buf);
return y + ui.lineheight;
}
@@ -1170,8 +881,8 @@ static void do_info(void)
static int do_help_line(int x, int y, char *label, char *text)
{
- ui_draw_string(ctx, x, y, label);
- ui_draw_string(ctx, x+100, y, text);
+ ui_draw_string(x, y, label);
+ ui_draw_string(x+100, y, text);
return y + ui.lineheight;
}
@@ -1240,20 +951,20 @@ static void do_canvas(void)
static int saved_scroll_y = 0;
static int saved_ui_x = 0;
static int saved_ui_y = 0;
+ fz_irect area;
+ int state;
- float x, y;
+ ui_layout(ALL, BOTH, NW, 0, 0);
+ ui_pack_push(area = ui_pack(0, 0));
+ glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0, area.y1-area.y0);
+ glEnable(GL_SCISSOR_TEST);
- if (oldpage != currentpage || oldzoom != currentzoom || oldrotate != currentrotate || oldinvert != currentinvert)
- {
- render_page();
- update_title();
- oldpage = currentpage;
- oldzoom = currentzoom;
- oldrotate = currentrotate;
- oldinvert = currentinvert;
- }
+ canvas_x = area.x0;
+ canvas_y = area.y0;
+ canvas_w = area.x1 - area.x0;
+ canvas_h = area.y1 - area.y0;
- if (ui.x >= canvas_x && ui.x < canvas_x + canvas_w && ui.y >= canvas_y && ui.y < canvas_y + canvas_h)
+ if (ui_mouse_inside(&area))
{
ui.hot = doc;
if (!ui.active && ui.middle)
@@ -1281,51 +992,76 @@ static void do_canvas(void)
if (page_tex.w <= canvas_w)
{
scroll_x = 0;
- x = canvas_x + (canvas_w - page_tex.w) / 2;
+ page_x_ofs = canvas_x + (canvas_w - page_tex.w) / 2;
}
else
{
scroll_x = fz_clamp(scroll_x, 0, page_tex.w - canvas_w);
- x = canvas_x - scroll_x;
+ page_x_ofs = canvas_x - scroll_x;
}
if (page_tex.h <= canvas_h)
{
scroll_y = 0;
- y = canvas_y + (canvas_h - page_tex.h) / 2;
+ page_y_ofs = canvas_y + (canvas_h - page_tex.h) / 2;
}
else
{
scroll_y = fz_clamp(scroll_y, 0, page_tex.h - canvas_h);
- y = canvas_y - scroll_y;
+ page_y_ofs = canvas_y - scroll_y;
}
- ui_draw_image(&page_tex, x - page_tex.x, y - page_tex.y);
-
- do_forms(x, y);
+ page_x_ofs -= page_tex.x;
+ page_y_ofs -= page_tex.y;
+ ui_draw_image(&page_tex, page_x_ofs, page_y_ofs);
- if (!search_active)
+ if (search_active)
{
- do_links(links, x, y);
- do_page_selection(x, y, x+page_tex.w, y+page_tex.h);
- if (search_hit_page == currentpage && search_hit_count > 0)
- do_search_hits(x, y);
+ ui_layout(T, X, NW, 2, 2);
+ ui_label("Searching page %d of %d.", search_page + 1, fz_count_pages(ctx, doc));
}
-}
+ else
+ {
+ do_forms();
+ do_links(links);
+ do_page_selection(page_x_ofs, page_y_ofs, page_x_ofs+page_tex.w, page_y_ofs+page_tex.h);
-static void run_main_loop(void)
-{
- glViewport(0, 0, window_w, window_h);
- glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
+ if (search_hit_page == currentpage && search_hit_count > 0)
+ do_search_hits();
+ }
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrtho(0, window_w, window_h, 0, -1, 1);
+ if (showsearch)
+ {
+ ui_layout(T, X, NW, 0, 0);
+ state = ui_input(&search_input, 0);
+ if (state == UI_INPUT_CANCEL)
+ {
+ showsearch = 0;
+ }
+ else if (state == UI_INPUT_ACCEPT)
+ {
+ showsearch = 0;
+ search_page = -1;
+ if (search_needle)
+ {
+ fz_free(ctx, search_needle);
+ search_needle = NULL;
+ }
+ if (search_input.end > search_input.text)
+ {
+ search_needle = fz_strdup(ctx, search_input.text);
+ search_active = 1;
+ search_page = currentpage;
+ }
+ }
+ }
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
+ ui_pack_pop();
+ glDisable(GL_SCISSOR_TEST);
+}
+void run_main_loop(void)
+{
ui_begin();
if (search_active)
@@ -1368,17 +1104,22 @@ static void run_main_loop(void)
do_app();
- if (doquit)
+ if (oldpage != currentpage)
{
- glutDestroyWindow(window);
-#ifdef __APPLE__
- exit(1); /* GLUT on MacOS keeps running even with no windows */
-#endif
- return;
+ load_page();
+ update_title();
+ }
+ if (oldpage != currentpage || oldzoom != currentzoom || oldrotate != currentrotate || oldinvert != currentinvert)
+ {
+ render_page();
+ oldpage = currentpage;
+ oldzoom = currentzoom;
+ oldrotate = currentrotate;
+ oldinvert = currentinvert;
}
- canvas_w = window_w - canvas_x;
- canvas_h = window_h - canvas_y;
+ if (showoutline)
+ do_outline(outline);
do_canvas();
@@ -1387,207 +1128,9 @@ static void run_main_loop(void)
else if (showhelp)
do_help();
- if (showoutline)
- do_outline(outline, canvas_x);
-
- if (showsearch)
- {
- int state = ui_input(canvas_x, 0, canvas_x + canvas_w, ui.lineheight+4, &search_input);
- if (state == -1)
- {
- ui.focus = NULL;
- showsearch = 0;
- glutPostRedisplay();
- }
- else if (state == 1)
- {
- ui.focus = NULL;
- showsearch = 0;
- search_page = -1;
- if (search_needle)
- {
- fz_free(ctx, search_needle);
- search_needle = NULL;
- }
- if (search_input.end > search_input.text)
- {
- search_needle = fz_strdup(ctx, search_input.text);
- search_active = 1;
- search_page = currentpage;
- }
- glutPostRedisplay();
- }
- }
-
- if (search_active)
- {
- char buf[256];
- sprintf(buf, "Searching page %d of %d.", search_page + 1, fz_count_pages(ctx, doc));
- ui_label_draw(canvas_x, 0, canvas_x + canvas_w, ui.lineheight+4, buf);
- }
-
ui_end();
-
- glutSwapBuffers();
-
- ogl_assert(ctx, "swap buffers");
-}
-
-#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.key = key;
- ui.mod = glutGetModifiers();
- ui.plain = !(ui.mod & ~GLUT_ACTIVE_SHIFT);
- run_main_loop();
- ui.key = ui.mod = ui.plain = 0;
-}
-
-static void on_special(int key, int x, int 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.mod = ui.plain = 0;
- }
-}
-
-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;
- run_main_loop();
- 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;
- switch (button)
- {
- case GLUT_LEFT_BUTTON: ui.down = (action == GLUT_DOWN); break;
- case GLUT_MIDDLE_BUTTON: ui.middle = (action == GLUT_DOWN); break;
- case GLUT_RIGHT_BUTTON: ui.right = (action == GLUT_DOWN); break;
- case 3: if (action == GLUT_DOWN) on_wheel(0, 1, x, y); break;
- case 4: if (action == GLUT_DOWN) on_wheel(0, -1, x, y); break;
- case 5: if (action == GLUT_DOWN) on_wheel(1, 1, x, y); break;
- case 6: if (action == GLUT_DOWN) on_wheel(1, -1, x, y); break;
- }
- run_main_loop();
-}
-
-static void on_motion(int x, int y)
-{
- ui.x = x;
- ui.y = y;
- glutPostRedisplay();
-}
-
-static void on_reshape(int w, int h)
-{
- showinfo = 0;
- showhelp = 0;
- window_w = w;
- 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");
-}
-
-#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
-
-void ui_set_clipboard(const char *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 void usage(const char *argv0)
{
fprintf(stderr, "mupdf-gl version %s\n", FZ_VERSION);
@@ -1654,74 +1197,28 @@ int main(int argc, char **argv)
else
title = filename;
- /* Init MuPDF */
-
ctx = fz_new_context(NULL, NULL, 0);
fz_register_document_handlers(ctx);
-
if (layout_css)
{
fz_buffer *buf = fz_read_file(ctx, layout_css);
fz_set_user_css(ctx, fz_string_from_buffer(ctx, buf));
fz_drop_buffer(ctx, buf);
}
-
fz_set_use_document_css(ctx, layout_use_doc_css);
load_document();
load_page();
- /* Init IMGUI */
-
- memset(&ui, 0, sizeof ui);
-
- search_input.p = search_input.text;
- search_input.q = search_input.p;
- search_input.end = search_input.p;
-
- /* Init GLUT */
-
- glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
-
- glutInitErrorFunc(on_error);
- glutInitWarningFunc(on_warning);
- glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
- glutInitWindowSize(page_tex.w, page_tex.h);
- window = glutCreateWindow(title);
-
- glutReshapeFunc(on_reshape);
- glutDisplayFunc(on_display);
-#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
- glutKeyboardExtFunc(on_keyboard);
-#else
- glutKeyboardFunc(on_keyboard);
-#endif
- glutSpecialFunc(on_special);
- glutMouseFunc(on_mouse);
- glutMotionFunc(on_motion);
- glutPassiveMotionFunc(on_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.fontsize = DEFAULT_UI_FONTSIZE;
- ui.baseline = DEFAULT_UI_BASELINE;
- ui.lineheight = DEFAULT_UI_LINEHEIGHT;
-
- ui_init_fonts(ctx, ui.fontsize);
+ ui_init(page_tex.w, page_tex.h, title);
+ ui_input_init(&search_input, "");
render_page();
update_title();
glutMainLoop();
- ui_finish_fonts(ctx);
-
- glutExit();
+ ui_finish();
#ifndef NDEBUG
if (fz_atoi(getenv("FZ_DEBUG_STORE")))
@@ -1730,7 +1227,7 @@ int main(int argc, char **argv)
fz_drop_stext_page(ctx, text);
fz_drop_link(ctx, links);
- fz_drop_page(ctx, page);
+ fz_drop_page(ctx, fzpage);
fz_drop_outline(ctx, outline);
fz_drop_document(ctx, doc);
fz_drop_context(ctx);
diff --git a/platform/gl/gl-ui.c b/platform/gl/gl-ui.c
new file mode 100644
index 00000000..df65ab2a
--- /dev/null
+++ b/platform/gl/gl-ui.c
@@ -0,0 +1,855 @@
+#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,
+};
+
+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);
+}
+
+#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_invalidate(); // TODO: leave this to caller
+ ui.key = ui.mod = ui.plain = 0;
+}
+
+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_invalidate(); // TODO: leave this to caller
+ ui.key = ui.mod = ui.plain = 0;
+ }
+}
+
+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;
+ 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;
+ }
+ }
+ 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_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");
+}
+
+void ui_init(int w, int h, const char *title)
+{
+ glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
+
+ glutInitErrorFunc(on_error);
+ glutInitWarningFunc(on_warning);
+ glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
+ glutInitWindowSize(w, h);
+ glutCreateWindow(title);
+
+ glutReshapeFunc(on_reshape);
+ glutDisplayFunc(on_display);
+#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
+ glutKeyboardExtFunc(on_keyboard);
+#else
+ glutKeyboardFunc(on_keyboard);
+#endif
+ glutSpecialFunc(on_special);
+ glutMouseFunc(on_mouse);
+ glutMotionFunc(on_motion);
+ glutPassiveMotionFunc(on_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);
+
+ memset(&ui, 0, sizeof ui);
+
+ ui.fontsize = DEFAULT_UI_FONTSIZE;
+ ui.baseline = DEFAULT_UI_BASELINE;
+ ui.lineheight = DEFAULT_UI_LINEHEIGHT;
+
+ ui_init_fonts(ui.fontsize);
+}
+
+void ui_finish(void)
+{
+ 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;
+
+ glViewport(0, 0, ui.window_w, ui.window_h);
+ glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
+ 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 = 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.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;
+ }
+ }
+
+ 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;
+ }
+
+ 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);
+}
+
+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 opaque)
+{
+ fz_irect area = ui_pack(w, h);
+ if (opaque)
+ {
+ fz_irect total = {
+ area.x0 - ui.layout->padx,
+ area.y0 - ui.layout->pady,
+ area.x1 + ui.layout->padx,
+ area.y1 + ui.layout->pady
+ };
+ glColor4f(0.8f, 0.8f, 0.8f, 1);
+ glRectf(total.x0, total.y0, total.x1, total.y1);
+ }
+ ui_pack_push(area);
+}
+
+void ui_panel_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];
+ int width;
+ fz_irect area;
+ va_list ap;
+
+ va_start(ap, fmt);
+ fz_vsnprintf(buf, sizeof buf, fmt, ap);
+ va_end(ap);
+
+ width = ui_measure_string(buf);
+ area = ui_pack(width, ui.lineheight);
+ glColor4f(0, 0, 0, 1);
+ ui_draw_string(area.x0, area.y0 + ui.baseline, buf);
+}
+
+int ui_button(const char *label)
+{
+ int width = ui_measure_string(label);
+ fz_irect area = ui_pack(width + 12, ui.lineheight + 6);
+ int text_x = area.x0 + ((area.x1 - area.x0) - width) / 2;
+
+ if (ui_mouse_inside(&area))
+ {
+ ui.hot = label;
+ if (!ui.active && ui.down)
+ ui.active = label;
+ }
+
+ glColor4f(0, 0, 0, 1);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
+
+ if (ui.hot == label && ui.active == label && ui.down)
+ glColor4f(0, 0, 0, 1);
+ else
+ glColor4f(1, 1, 1, 1);
+ glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2);
+
+ if (ui.hot == label && ui.active == label && ui.down)
+ glColor4f(1, 1, 1, 1);
+ else
+ glColor4f(0, 0, 0, 1);
+ ui_draw_string(text_x, area.y0 + 3 + ui.baseline, label);
+
+ return ui.hot == label && ui.active == label && !ui.down;
+}
+
+void ui_checkbox(const char *label, int *value)
+{
+ int width = ui_measure_string(label);
+ fz_irect area = ui_pack(width + ui.baseline - 3 + 4, ui.lineheight);
+ fz_irect mark = { area.x0, area.y0 + 3, area.x0 + ui.baseline - 3, area.y0 + ui.baseline };
+
+ glColor4f(0, 0, 0, 1);
+ ui_draw_string(mark.x1 + 4, area.y0 + ui.baseline, label);
+
+ glColor4f(0, 0, 0, 1);
+ glRectf(mark.x0, mark.y0, mark.x1, mark.y1);
+
+ 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;
+
+ glColor4f(1, 1, 1, 1);
+ if (ui.hot == label && ui.active == label && ui.down)
+ glRectf(mark.x0+2, mark.y0+2, mark.x1-2, mark.y1-2);
+ else
+ glRectf(mark.x0+1, mark.y0+1, mark.x1-1, mark.y1-1);
+
+ if (*value)
+ {
+ glColor4f(0, 0, 0, 1);
+ glRectf(mark.x0+3, mark.y0+3, mark.x1-3, mark.y1-3);
+ }
+}
+
+void ui_slider(float *value, float min, float max, int width)
+{
+ fz_irect area = ui_pack(width, ui.lineheight);
+ static float start_value = 0;
+ float w = area.x1 - area.x0 - 4;
+ char buf[50];
+
+ 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+2)) / (area.x1-area.x0-4);
+ *value = fz_clamp(min + v * (max - min), min, max);
+ }
+ }
+
+ glColor4f(0.4, 0.4, 0.4, 1);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
+ glColor4f(0.7, 0.7, 0.7, 1);
+ glRectf(area.x0+2, area.y0+2, area.x0+2 + (*value - min) / (max - min) * w, area.y1 - 2);
+
+ glColor4f(1, 1, 1, 1);
+ fz_snprintf(buf, sizeof buf, "%0.2f", *value);
+ w = ui_measure_string(buf);
+ ui_draw_string(area.x0 + ((area.x1-area.x0) - w) / 2, area.y0 + ui.baseline, buf);
+}
+
+void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max)
+{
+ 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 - 4;
+ 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;
+ glColor4f(0.6f, 0.6f, 0.6f, 1.0f);
+ glRectf(x0, y0, x1, y1);
+ return;
+ }
+
+ 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 = "pgdn";
+ *value -= page_size;
+ }
+ else if (ui.y >= y0 + top + thumb_h)
+ {
+ ui.active = "pgup";
+ *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;
+
+ top = (float) *value * avail_h / max;
+
+ glColor4f(0.6f, 0.6f, 0.6f, 1.0f);
+ glRectf(x0, y0, x1, y1);
+ glColor4f(0.8f, 0.8f, 0.8f, 1.0f);
+ glRectf(x0+2, y0+2 + top, x1-2, y0+2 + top + thumb_h);
+}
+
+void ui_list_begin(struct list *list, int count, int req_w, int req_h)
+{
+ static int start_scroll_y = 0; /* we can only drag in one list at a time, so static is safe */
+
+ fz_irect area = ui_pack(req_w, req_h);
+
+ int max_scroll_y = count * ui.lineheight - (area.y1-area.y0);
+
+ if (max_scroll_y > 0)
+ area.x1 -= ui.lineheight;
+
+ 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;
+
+ /* 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;
+
+ if (max_scroll_y > 0)
+ {
+ ui_scrollbar(area.x1, area.y0, area.x1+ui.lineheight, area.y1,
+ &list->scroll_y, area.y1-area.y0, count * ui.lineheight);
+ }
+
+ list->area = area;
+ list->item_y = area.y0 - list->scroll_y;
+
+ glColor4f(1, 1, 1, 1);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
+
+ glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0, area.y1-area.y0);
+ glEnable(GL_SCISSOR_TEST);
+}
+
+int ui_list_item(struct list *list, void *id, int indent, const char *label, int selected)
+{
+ fz_irect area = { list->area.x0, list->item_y, list->area.x1, list->item_y + ui.lineheight };
+
+ /* 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))
+ {
+ ui.hot = id;
+ if (!ui.active && ui.down)
+ ui.active = id;
+ }
+
+ if (selected)
+ {
+ glColor4f(0.9f, 0.9f, 0.9f, 1);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
+ }
+
+ glColor4f(0, 0, 0, 1);
+ ui_draw_string(area.x0 + indent, area.y0 + ui.baseline, label);
+ }
+
+ list->item_y += ui.lineheight;
+
+ /* trigger on first mouse down */
+ return ui.active == id;
+}
+
+void ui_list_end(struct list *list)
+{
+ glDisable(GL_SCISSOR_TEST);
+}