ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 480c476bee3efca10facb5ff2be6863bf112b72a
parent 8c7d6c1077f97dd4ae11a551ccf3a68fa86e21f6
Author: lumidify <nobody@lumidify.org>
Date:   Wed, 22 Jun 2022 19:27:18 +0200

Turn menu entries into regular widgets

This might be slightly more inefficient but makes a lot of things
more convenient, especially when adding keyboard navigation.

Diffstat:
MMakefile | 2+-
Msrc/box.c | 17+++++++----------
Msrc/button.c | 49+++++++++++++++----------------------------------
Msrc/graphics.h | 7++-----
Msrc/graphics_xlib.c | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/grid.c | 31+++++++++++++++++--------------
Msrc/label.c | 6------
Msrc/ltkd.c | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/menu.c | 1659+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/menu.h | 34++++++++++++++++++++++++++--------
Msrc/rect.h | 11++++++++---
Msrc/scrollbar.c | 35+++++++++++------------------------
Msrc/widget.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/widget.h | 62+++++++++++++++++++++++++++++---------------------------------
Mtest2.gui | 53++++++++++++++++++++++++++++++++++++-----------------
15 files changed, 1168 insertions(+), 1172 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,7 +7,7 @@ VERSION = -999-prealpha0 # Note: The stb backend should not be used with untrusted font files. # FIXME: Using DEBUG here doesn't work because it somehow # interferes with a predefined macro, at least on OpenBSD. -DEV = 1 +DEV = 0 SANITIZE = 0 USE_PANGO = 0 diff --git a/src/box.c b/src/box.c @@ -37,7 +37,7 @@ static void ltk_recalculate_box(ltk_widget *self); static void ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget); /* FIXME: Why is sticky unsigned short? */ static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, char **errstr); -static int ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_widget *self, char **errstr); +static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, char **errstr); /* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, char **errstr); */ static void ltk_box_scroll(ltk_widget *self); static int ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event); @@ -119,12 +119,6 @@ static void ltk_box_destroy(ltk_widget *self, int shallow) { ltk_box *box = (ltk_box *)self; ltk_widget *ptr; - char *errstr; - if (self->parent && self->parent->vtable->remove_child) { - self->parent->vtable->remove_child( - self->window, self, self->parent, &errstr - ); - } for (size_t i = 0; i < box->num_widgets; i++) { ptr = box->widgets[i]; ptr->parent = NULL; @@ -269,7 +263,7 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short } static int -ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_widget *self, char **errstr) { +ltk_box_remove(ltk_widget *widget, ltk_widget *self, char **errstr) { ltk_box *box = (ltk_box *)self; int sc_w = box->sc->widget.rect.w; int sc_h = box->sc->widget.rect.h; @@ -284,9 +278,10 @@ ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_widget *self, char ** memmove(box->widgets + i, box->widgets + i + 1, (box->num_widgets - i - 1) * sizeof(ltk_widget *)); box->num_widgets--; - ltk_window_invalidate_rect(window, box->widget.rect); + ltk_window_invalidate_rect(widget->window, box->widget.rect); /* search for new ideal width/height */ /* FIXME: make this all a bit nicer and break the lines better */ + /* FIXME: other part of ideal size not updated */ if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h == box->widget.ideal_h) { box->widget.ideal_h = 0; for (size_t j = 0; j < box->num_widgets; j++) { @@ -339,6 +334,7 @@ ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event) { /* FIXME: configure scrollstep */ int delta = event->button == LTK_BUTTON4 ? -15 : 15; ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0); + ltk_window_fake_motion_event(self->window, event->x, event->y); return 1; } else { return 0; @@ -395,6 +391,7 @@ ltk_box_cmd_remove( char **tokens, size_t num_tokens, char **errstr) { + (void)window; ltk_box *box; ltk_widget *widget; @@ -409,7 +406,7 @@ ltk_box_cmd_remove( return 1; } - return ltk_box_remove(window, widget, (ltk_widget *)box, errstr); + return ltk_box_remove(widget, (ltk_widget *)box, errstr); } /* box <box id> create <orientation> */ diff --git a/src/button.c b/src/button.c @@ -41,7 +41,6 @@ static int ltk_button_mouse_release(ltk_widget *self, ltk_button_event *event); static ltk_button *ltk_button_create(ltk_window *window, const char *id, char *text); static void ltk_button_destroy(ltk_widget *self, int shallow); -static void ltk_button_change_state(ltk_widget *self); static void ltk_button_redraw_surface(ltk_button *button, ltk_surface *s); static struct ltk_widget_vtable vtable = { @@ -52,7 +51,7 @@ static struct ltk_widget_vtable vtable = { .motion_notify = NULL, .mouse_leave = NULL, .mouse_enter = NULL, - .change_state = &ltk_button_change_state, + .change_state = NULL, .get_child_at_pos = NULL, .resize = NULL, .hide = NULL, @@ -61,7 +60,7 @@ static struct ltk_widget_vtable vtable = { .child_size_change = NULL, .remove_child = NULL, .type = LTK_BUTTON, - .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE, + .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE | LTK_ACTIVATABLE_ALWAYS, }; static struct { @@ -133,29 +132,22 @@ ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) { ltk_rect rect = button->widget.rect; int bw = theme.border_width; ltk_color *border = NULL, *fill = NULL; - switch (button->widget.state) { - case LTK_NORMAL: - border = &theme.border; - fill = &theme.fill; - break; - case LTK_HOVER: - border = &theme.border_hover; - fill = &theme.fill_hover; - break; - case LTK_PRESSED: + /* FIXME: HOVERACTIVE STATE */ + if (button->widget.state & LTK_DISABLED) { + border = &theme.border_disabled; + fill = &theme.fill_disabled; + } else if (button->widget.state & LTK_PRESSED) { border = &theme.border_pressed; fill = &theme.fill_pressed; - break; - case LTK_ACTIVE: + } else if (button->widget.state & LTK_HOVER) { + border = &theme.border_hover; + fill = &theme.fill_hover; + } else if (button->widget.state & LTK_ACTIVE) { border = &theme.border_active; fill = &theme.fill_active; - break; - case LTK_DISABLED: - border = &theme.border_disabled; - fill = &theme.fill_disabled; - break; - default: - ltk_fatal("No style found for button!\n"); + } else { + border = &theme.border; + fill = &theme.fill; } rect.x = 0; rect.y = 0; @@ -171,15 +163,10 @@ ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) { button->widget.dirty = 0; } -static void -ltk_button_change_state(ltk_widget *self) { - self->dirty = 1; -} - static int ltk_button_mouse_release(ltk_widget *self, ltk_button_event *event) { ltk_button *button = (ltk_button *)self; - if (self->state == LTK_PRESSED && event->button == LTK_BUTTONL) { + if ((self->state & LTK_PRESSED) && event->button == LTK_BUTTONL && ltk_collide_rect(self->rect, event->x, event->y)) { ltk_queue_event(button->widget.window, LTK_EVENT_BUTTON, button->widget.id, "button_click"); return 1; } @@ -205,12 +192,6 @@ ltk_button_create(ltk_window *window, const char *id, char *text) { static void ltk_button_destroy(ltk_widget *self, int shallow) { (void)shallow; - char *errstr; - if (self->parent && self->parent->vtable->remove_child) { - self->parent->vtable->remove_child( - self->window, self, self->parent, &errstr - ); - } ltk_button *button = (ltk_button *)self; if (!button) { ltk_warn("Tried to destroy NULL button.\n"); diff --git a/src/graphics.h b/src/graphics.h @@ -39,11 +39,6 @@ typedef enum { LTK_BORDER_ALL = 0xF } ltk_border_sides; -/* FIXME: X only supports 16-bit numbers */ -typedef struct { - int x, y; -} ltk_point; - /* typedef struct ltk_surface ltk_surface; */ /* FIXME: graphics context */ @@ -60,7 +55,9 @@ void ltk_surface_draw_rect(ltk_surface *s, ltk_color *c, ltk_rect rect, int line void ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect); /* FIXME: document properly, especiall difference to draw_rect with offsets and line_width */ void ltk_surface_draw_border(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width, ltk_border_sides border_sides); +void ltk_surface_draw_border_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, ltk_rect clip_rect, int line_width, ltk_border_sides border_sides); void ltk_surface_fill_polygon(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints); +void ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints, ltk_rect clip); /* TODO */ /* diff --git a/src/graphics_xlib.c b/src/graphics_xlib.c @@ -128,6 +128,43 @@ ltk_surface_draw_border(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_wi } void +ltk_surface_draw_border_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, ltk_rect clip_rect, int line_width, ltk_border_sides border_sides) { + if (line_width <= 0) + return; + XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel); + int width; + ltk_rect final_rect = ltk_rect_intersect(rect, clip_rect); + if (border_sides & LTK_BORDER_TOP) { + width = rect.y - final_rect.y; + if (width > -line_width) { + width = line_width + width; + XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y, final_rect.w, width); + } + } + if (border_sides & LTK_BORDER_BOTTOM) { + width = (final_rect.y + final_rect.h) - (rect.y + rect.h); + if (width > -line_width) { + width = line_width + width; + XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y + final_rect.h - width, final_rect.w, width); + } + } + if (border_sides & LTK_BORDER_LEFT) { + width = rect.x - final_rect.x; + if (width > -line_width) { + width = line_width + width; + XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y, width, final_rect.h); + } + } + if (border_sides & LTK_BORDER_RIGHT) { + width = (final_rect.x + final_rect.w) - (rect.x + rect.w); + if (width > -line_width) { + width = line_width + width; + XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x + final_rect.w - width, final_rect.y, width, final_rect.h); + } + } +} + +void ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect) { XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel); XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, rect.w, rect.h); @@ -135,7 +172,7 @@ ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect) { void ltk_surface_fill_polygon(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints) { - /* FIXME: maybe make this statis since this won't be threaded anyways? */ + /* FIXME: maybe make this static since this won't be threaded anyways? */ XPoint tmp_points[6]; /* to avoid extra allocations when not necessary */ /* FIXME: this is ugly and inefficient */ XPoint *final_points; @@ -152,7 +189,127 @@ ltk_surface_fill_polygon(ltk_surface *s, ltk_color *c, ltk_point *points, size_t XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel); XFillPolygon(s->renderdata->dpy, s->d, s->renderdata->gc, final_points, (int)npoints, Complex, CoordModeOrigin); if (npoints > 6) - free(final_points); + ltk_free(final_points); +} + +static inline void +swap_ptr(void **ptr1, void **ptr2) { + void *tmp = *ptr1; + *ptr1 = *ptr2; + *ptr2 = tmp; +} + +#define check_size(cond) if (!(cond)) ltk_fatal("Unable to perform polygon clipping. This is a bug, tell lumidify about it.\n") + +/* FIXME: this can probably be optimized */ +/* This is basically Sutherland-Hodgman, but only the special case for clipping rectangles. */ +void +ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints, ltk_rect clip) { + /* FIXME: is this even more efficient? */ + XPoint tmp_points1[12]; /* to avoid extra allocations when not necessary */ + XPoint tmp_points2[12]; + XPoint *points1; + XPoint *points2; + /* FIXME: be a bit smarter about this */ + if (npoints <= 6) { + points1 = tmp_points1; + points2 = tmp_points2; + } else { + /* FIXME: I'm pretty sure there can never be more points than this + since we're only clipping against a rectangle, right? + If I can be sure about that, I can remove all the check_size's below. */ + points1 = ltk_reallocarray(NULL, npoints, sizeof(XPoint) * 2); + points2 = ltk_reallocarray(NULL, npoints, sizeof(XPoint) * 2); + } + + size_t num1 = npoints; + size_t num2 = 0; + for (size_t i = 0; i < npoints; i++) { + points1[i].x = (short)points[i].x; + points1[i].y = (short)points[i].y; + } + + for (size_t i = 0; i < num1; i++) { + XPoint p1 = points1[i]; + XPoint p2 = points1[(i + 1) % num1]; + if (p1.x >= clip.x) { + check_size(num2 < npoints * 2); + points2[num2++] = p1; + if (p2.x < clip.x) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.x = (short)clip.x, .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x - p1.x) / (p2.x - p1.x))}; + } + } else if (p2.x >= clip.x) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.x = (short)clip.x, .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x - p1.x) / (p2.x - p1.x))}; + } + } + num1 = num2; + num2 = 0; + swap_ptr((void**)&points1, (void**)&points2); + + for (size_t i = 0; i < num1; i++) { + XPoint p1 = points1[i]; + XPoint p2 = points1[(i + 1) % num1]; + if (p1.x <= clip.x + clip.w) { + check_size(num2 < npoints * 2); + points2[num2++] = p1; + if (p2.x > clip.x + clip.w) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.x = (short)(clip.x + clip.w), .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x + clip.w - p1.x) / (p2.x - p1.x))}; + } + } else if (p2.x <= clip.x + clip.w) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.x = (short)(clip.x + clip.w), .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x + clip.w - p1.x) / (p2.x - p1.x))}; + } + } + num1 = num2; + num2 = 0; + swap_ptr((void**)&points1, (void**)&points2); + + for (size_t i = 0; i < num1; i++) { + XPoint p1 = points1[i]; + XPoint p2 = points1[(i + 1) % num1]; + if (p1.y >= clip.y) { + check_size(num2 < npoints * 2); + points2[num2++] = p1; + if (p2.y < clip.y) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.y = (short)clip.y, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y - p1.y) / (p2.y - p1.y))}; + } + } else if (p2.y >= clip.y) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.y = (short)clip.y, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y - p1.y) / (p2.y - p1.y))}; + } + } + num1 = num2; + num2 = 0; + swap_ptr((void**)&points1, (void**)&points2); + + for (size_t i = 0; i < num1; i++) { + XPoint p1 = points1[i]; + XPoint p2 = points1[(i + 1) % num1]; + if (p1.y <= clip.y + clip.h) { + check_size(num2 < npoints * 2); + points2[num2++] = p1; + if (p2.y > clip.y + clip.h) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.y = (short)clip.y + clip.h, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y + clip.h - p1.y) / (p2.y - p1.y))}; + } + } else if (p2.y <= clip.y + clip.h) { + check_size(num2 < npoints * 2); + points2[num2++] = (XPoint){.y = (short)clip.y + clip.h, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y + clip.h - p1.y) / (p2.y - p1.y))}; + } + } + + if (num2 > 0) { + XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel); + XFillPolygon(s->renderdata->dpy, s->d, s->renderdata->gc, points2, (int)num2, Complex, CoordModeOrigin); + } + if (npoints > 6) { + ltk_free(points1); + ltk_free(points2); + } } void @@ -284,7 +441,7 @@ renderer_destroy_window(ltk_renderdata *renderdata) { XFreeGC(renderdata->dpy, renderdata->gc); XDestroyWindow(renderdata->dpy, renderdata->xwindow); XCloseDisplay(renderdata->dpy); - free(renderdata); + ltk_free(renderdata); } /* FIXME: this is a completely random collection of properties and should be diff --git a/src/grid.c b/src/grid.c @@ -47,7 +47,7 @@ static void ltk_recalculate_grid(ltk_widget *self); static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget); static int ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid, int row, int column, int row_span, int column_span, unsigned short sticky, char **errstr); -static int ltk_grid_ungrid(ltk_window *window, ltk_widget *widget, ltk_widget *self, char **errstr); +static int ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, char **errstr); static int ltk_grid_find_nearest_column(ltk_grid *grid, int x); static int ltk_grid_find_nearest_row(ltk_grid *grid, int y); static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y); @@ -163,12 +163,6 @@ ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) { static void ltk_grid_destroy(ltk_widget *self, int shallow) { ltk_grid *grid = (ltk_grid *)self; - char *errstr; /* FIXME: unused */ - if (self->parent && self->parent->vtable->remove_child) { - self->parent->vtable->remove_child( - self->window, self, self->parent, &errstr - ); - } ltk_widget *ptr; for (int i = 0; i < grid->rows * grid->columns; i++) { if (grid->widget_grid[i]) { @@ -240,7 +234,7 @@ ltk_recalculate_grid(ltk_widget *self) { currentx += grid->column_widths[i]; } grid->column_pos[grid->columns] = currentx; - int orig_width, orig_height; + /*int orig_width, orig_height;*/ int end_column, end_row; for (i = 0; i < grid->rows; i++) { for (j = 0; j < grid->columns; j++) { @@ -249,8 +243,8 @@ ltk_recalculate_grid(ltk_widget *self) { ltk_widget *ptr = grid->widget_grid[i * grid->columns + j]; if (ptr->row != i || ptr->column != j) continue; - orig_width = ptr->rect.w; - orig_height = ptr->rect.h; + /*orig_width = ptr->rect.w; + orig_height = ptr->rect.h;*/ end_row = i + ptr->row_span; end_column = j + ptr->column_span; if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT) { @@ -259,7 +253,15 @@ ltk_recalculate_grid(ltk_widget *self) { if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) { ptr->rect.h = grid->row_pos[end_row] - grid->row_pos[i]; } - if (orig_width != ptr->rect.w || orig_height != ptr->rect.h) + /* FIXME: Figure out a better system for this - it would be nice to make it more + efficient by not doing anything if nothing changed, but that doesn't work when + this function was called because of a child_size_change. In that case, if a + container widget is nested inside another container widget and another widget + inside the nested container sends a child_size_change but the toplevel container + doesn't change the size of the container, the position/size of the widget at the + bottom of the hierarchy will never be updated. That's why updates are forced + here even if seemingly nothing changed, but there probably is a better way. */ + /*if (orig_width != ptr->rect.w || orig_height != ptr->rect.h)*/ ltk_widget_resize(ptr); if (ptr->sticky & LTK_STICKY_RIGHT) { @@ -342,7 +344,7 @@ ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid, } static int -ltk_grid_ungrid(ltk_window *window, ltk_widget *widget, ltk_widget *self, char **errstr) { +ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, char **errstr) { ltk_grid *grid = (ltk_grid *)self; if (widget->parent != (ltk_widget *)grid) { *errstr = "Widget isn't gridded in given grid.\n"; @@ -354,7 +356,7 @@ ltk_grid_ungrid(ltk_window *window, ltk_widget *widget, ltk_widget *self, char * grid->widget_grid[i * grid->columns + j] = NULL; } } - ltk_window_invalidate_rect(window, grid->widget.rect); + ltk_window_invalidate_rect(widget->window, grid->widget.rect); return 0; } @@ -465,6 +467,7 @@ ltk_grid_cmd_ungrid( char **tokens, size_t num_tokens, char **errstr) { + (void)window; ltk_grid *grid; ltk_widget *widget; if (num_tokens != 4) { @@ -474,7 +477,7 @@ ltk_grid_cmd_ungrid( grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, errstr); widget = ltk_get_widget(tokens[3], LTK_WIDGET, errstr); if (!grid || !widget) return 1; - return ltk_grid_ungrid(window, widget, (ltk_widget *)grid, errstr); + return ltk_grid_ungrid(widget, (ltk_widget *)grid, errstr); } /* grid <grid id> create <rows> <columns> */ diff --git a/src/label.c b/src/label.c @@ -133,12 +133,6 @@ ltk_label_create(ltk_window *window, const char *id, char *text) { static void ltk_label_destroy(ltk_widget *self, int shallow) { (void)shallow; - char *errstr; - if (self->parent && self->parent->vtable->remove_child) { - self->parent->vtable->remove_child( - self->window, self, self->parent, &errstr - ); - } ltk_label *label = (ltk_label *)self; if (!label) { ltk_warn("Tried to destroy NULL label.\n"); diff --git a/src/ltkd.c b/src/ltkd.c @@ -675,9 +675,7 @@ ltk_window_unregister_all_popups(ltk_window *window) { window->popups_locked = 1; for (size_t i = 0; i < window->popups_num; i++) { window->popups[i]->hidden = 1; - if (window->popups[i]->vtable->hide) { - window->popups[i]->vtable->hide(window->popups[i]); - } + ltk_widget_hide(window->popups[i]); } window->popups_num = 0; /* somewhat arbitrary, but should be enough for most cases */ @@ -728,6 +726,10 @@ ltk_ini_handler(void *window, const char *widget, const char *prop, const char * ltk_menu_ini_handler(window, prop, value); } else if (strcmp(widget, "submenu") == 0) { ltk_submenu_ini_handler(window, prop, value); + } else if (strcmp(widget, "menuentry") == 0) { + ltk_menuentry_ini_handler(window, prop, value); + } else if (strcmp(widget, "submenuentry") == 0) { + ltk_menuentry_ini_handler(window, prop, value); } else { return 0; } @@ -745,7 +747,9 @@ ltk_load_theme(ltk_window *window, const char *path) { ltk_label_fill_theme_defaults(window) || ltk_scrollbar_fill_theme_defaults(window) || ltk_menu_fill_theme_defaults(window) || - ltk_submenu_fill_theme_defaults(window)) { + ltk_submenu_fill_theme_defaults(window) || + ltk_menuentry_fill_theme_defaults(window) || + ltk_submenuentry_fill_theme_defaults(window)) { ltk_uninitialize_theme(window); ltk_fatal("Unable to load theme defaults.\n"); } @@ -759,6 +763,8 @@ ltk_uninitialize_theme(ltk_window *window) { ltk_scrollbar_uninitialize_theme(window); ltk_menu_uninitialize_theme(window); ltk_submenu_uninitialize_theme(window); + ltk_menuentry_uninitialize_theme(window); + ltk_submenuentry_uninitialize_theme(window); } static ltk_window * @@ -819,43 +825,71 @@ ltk_destroy_window(ltk_window *window) { ltk_free(window); } -/* FIXME: some widgets should not be allowed to be active or pressed (e.g. containers) */ void ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event) { ltk_widget *old = window->hover_widget; if (old == widget) return; if (old) { - /* set widget to active again if it is actually active */ - if (old == window->active_widget) - old->state = LTK_ACTIVE; - else - old->state = LTK_NORMAL; - ltk_widget_change_state(old); + ltk_widget_state old_state = old->state; + old->state &= ~LTK_HOVER; + ltk_widget_change_state(old, old_state); if (old->vtable->mouse_leave) old->vtable->mouse_leave(old, event); } window->hover_widget = widget; if (widget) { - widget->state = LTK_HOVER; - ltk_widget_change_state(widget); if (widget->vtable->mouse_enter) widget->vtable->mouse_enter(widget, event); + ltk_widget_state old_state = widget->state; + widget->state |= LTK_HOVER; + ltk_widget_change_state(widget, old_state); + if ((widget->vtable->flags & LTK_HOVER_IS_ACTIVE) && widget != window->active_widget) + ltk_window_set_active_widget(window, widget); } } void ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { - if (window->active_widget == widget) + if (window->active_widget == widget) { return; - if (window->active_widget) { - window->active_widget->state = LTK_NORMAL; - ltk_widget_change_state(window->active_widget); } + ltk_widget *old = window->active_widget; + /* Note: this has to be set at the beginning to + avoid infinite recursion in some cases */ window->active_widget = widget; + ltk_widget *common_parent = NULL; if (widget) { - widget->state = LTK_ACTIVE; - ltk_widget_change_state(widget); + ltk_widget *cur = widget; + while (cur) { + if (cur->state & LTK_ACTIVE) { + common_parent = cur; + break; + } + ltk_widget_state old_state = cur->state; + cur->state |= LTK_ACTIVE; + ltk_widget_change_state(cur, old_state); + cur = cur->parent; + } + } + /* FIXME: better variable names; generally make this nicer */ + /* special case if old is parent of new active widget */ + ltk_widget *tmp = common_parent; + while (tmp) { + if (tmp == old) + return; + tmp = tmp->parent; + } + if (old) { + ltk_widget *cur = old; + while (cur) { + if (cur == common_parent) + break; + ltk_widget_state old_state = cur->state; + cur->state &= ~LTK_ACTIVE; + ltk_widget_change_state(cur, old_state); + cur = cur->parent; + } } } @@ -863,17 +897,21 @@ void ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget) { if (window->pressed_widget == widget) return; - if (window->hover_widget && window->hover_widget != widget) { - window->hover_widget->state = LTK_NORMAL; - ltk_widget_change_state(window->hover_widget); - window->hover_widget = NULL; - } - if (window->pressed_widget) + /* FIXME: won't work properly when key navigation is added and enter can be + used to set a widget to pressed while the pointer is still on another + widget */ + /* -> also need generic pressed/released callbacks instead of just mouse_press/leave */ + if (window->pressed_widget) { + ltk_widget_state old_state = window->pressed_widget->state; + window->pressed_widget->state &= ~LTK_PRESSED; + ltk_widget_change_state(window->pressed_widget, old_state); ltk_window_set_active_widget(window, window->pressed_widget); + } window->pressed_widget = widget; if (widget) { - widget->state = LTK_PRESSED; - ltk_widget_change_state(widget); + ltk_widget_state old_state = widget->state; + widget->state |= LTK_PRESSED; + ltk_widget_change_state(widget, old_state); } } @@ -1151,6 +1189,8 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) { err = ltk_menu_cmd(window, tokens, num_tokens, &errstr); } else if (strcmp(tokens[0], "submenu") == 0) { err = ltk_menu_cmd(window, tokens, num_tokens, &errstr); + } else if (strcmp(tokens[0], "menuentry") == 0) { + err = ltk_menuentry_cmd(window, tokens, num_tokens, &errstr); } else if (strcmp(tokens[0], "set-root-widget") == 0) { err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errstr); /* diff --git a/src/menu.c b/src/menu.c @@ -14,6 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/* NOTE: The implementation of menus and menu entries is a collection of ugly hacks. */ + #include <stdio.h> #include <stdlib.h> #include <stdint.h> @@ -41,24 +43,30 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) static struct theme { - int border_width; int pad; - int text_pad; + int arrow_pad; int arrow_size; + int border_width; + int compress_borders; + + ltk_color border; + ltk_color background; + ltk_color scroll_background; + ltk_color scroll_arrow_color; +} menu_theme, submenu_theme; + +static struct entry_theme { + int text_pad; int arrow_pad; + int arrow_size; + int border_width; int compress_borders; - int menu_border_width; /* FIXME: should border_sides actually factor into size calculation? - probably useless and would just make it more complicated */ /* FIXME: allow different values for different states? */ ltk_border_sides border_sides; - ltk_color background; - ltk_color scroll_background; - ltk_color scroll_arrow_color; - ltk_color menu_border; - ltk_color text; ltk_color border; ltk_color fill; @@ -74,95 +82,94 @@ static struct theme { ltk_color text_disabled; ltk_color border_disabled; ltk_color fill_disabled; -} menu_theme, submenu_theme; +} menu_entry_theme, submenu_entry_theme; static void ltk_menu_resize(ltk_widget *self); -static void ltk_menu_change_state(ltk_widget *self); static void ltk_menu_draw(ltk_widget *self, ltk_rect clip); -static void ltk_menu_redraw_surface(ltk_menu *menu, ltk_surface *s); static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret); static void ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step); static void ltk_menu_scroll_callback(void *data); static void stop_scrolling(ltk_menu *menu); -static size_t get_entry_at_point(ltk_menu *menu, int x, int y, ltk_rect *entry_rect_ret); +static ltk_widget *ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y); static int set_scroll_timer(ltk_menu *menu, int x, int y); -static int ltk_menu_mouse_release(ltk_widget *self, ltk_button_event *event); static int ltk_menu_mouse_press(ltk_widget *self, ltk_button_event *event); static void ltk_menu_hide(ltk_widget *self); -static void popup_active_menu(ltk_menu *menu, ltk_rect r); -static void unpopup_active_entry(ltk_menu *menu); -static void handle_hover(ltk_menu *menu, int x, int y); +static void popup_active_menu(ltk_menuentry *e); +static void unpopup_active_entry(ltk_menuentry *e); static int ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event); static int ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event); static int ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event); static ltk_menu *ltk_menu_create(ltk_window *window, const char *id, int is_submenu); -static ltk_menuentry *insert_entry(ltk_menu *menu, size_t idx); -static void recalc_menu_size(ltk_menu *menu); +static void recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget); static void shrink_entries(ltk_menu *menu); static size_t get_entry_with_id(ltk_menu *menu, const char *id); static void ltk_menu_destroy(ltk_widget *self, int shallow); -static ltk_menuentry *ltk_menu_insert_entry(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, size_t idx, char **errstr); -static ltk_menuentry *ltk_menu_add_entry(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, char **errstr); -static ltk_menuentry *ltk_menu_insert_submenu(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, size_t idx, char **errstr); -static ltk_menuentry *ltk_menu_add_submenu(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, char **errstr); -static int ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, int shallow, char **errstr); -static int ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, int shallow, char **errstr); -static int ltk_menu_remove_all_entries(ltk_menu *menu, int shallow, char **errstr); -static int ltk_menu_detach_submenu_from_entry_id(ltk_menu *menu, const char *id, char **errstr); -static int ltk_menu_detach_submenu_from_entry_index(ltk_menu *menu, size_t idx, char **errstr); -static int ltk_menu_disable_entry_index(ltk_menu *menu, size_t idx, char **errstr); -static int ltk_menu_disable_entry_id(ltk_menu *menu, const char *id, char **errstr); -static int ltk_menu_disable_all_entries(ltk_menu *menu, char **errstr); -static int ltk_menu_enable_entry_index(ltk_menu *menu, size_t idx, char **errstr); -static int ltk_menu_enable_entry_id(ltk_menu *menu, const char *id, char **errstr); -static int ltk_menu_enable_all_entries(ltk_menu *menu, char **errstr); +static ltk_menuentry *ltk_menuentry_create(ltk_window *window, const char *id, const char *text); +static void ltk_menuentry_draw(ltk_widget *self, ltk_rect clip); +static void ltk_menuentry_destroy(ltk_widget *self, int shallow); +static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state); +static int ltk_menuentry_mouse_release(ltk_widget *self, ltk_button_event *event); +static void ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry); +static void ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, char **errstr); +static void ltk_menuentry_detach_submenu(ltk_menuentry *e); + +static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, char **errstr); + +#define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_MENU && ((ltk_menu *)e->widget.parent)->is_submenu) static struct ltk_widget_vtable vtable = { .key_press = NULL, .key_release = NULL, .mouse_press = &ltk_menu_mouse_press, .motion_notify = &ltk_menu_motion_notify, - .mouse_release = &ltk_menu_mouse_release, + .mouse_release = NULL, .mouse_enter = &ltk_menu_mouse_enter, .mouse_leave = &ltk_menu_mouse_leave, - .get_child_at_pos = NULL, + .get_child_at_pos = &ltk_menu_get_child_at_pos, .resize = &ltk_menu_resize, - .change_state = &ltk_menu_change_state, + .change_state = NULL, .hide = &ltk_menu_hide, .draw = &ltk_menu_draw, .destroy = &ltk_menu_destroy, + .child_size_change = &recalc_ideal_menu_size, + .remove_child = &ltk_menu_remove_child, + .type = LTK_MENU, + .flags = LTK_NEEDS_REDRAW, +}; + +static struct ltk_widget_vtable entry_vtable = { + .key_press = NULL, + .key_release = NULL, + .mouse_press = NULL, + .motion_notify = NULL, + .mouse_release = &ltk_menuentry_mouse_release, + .mouse_enter = NULL, + .mouse_leave = NULL, + .get_child_at_pos = NULL, + .resize = NULL, + .change_state = &ltk_menuentry_change_state, + .hide = NULL, + .draw = &ltk_menuentry_draw, + .destroy = &ltk_menuentry_destroy, .child_size_change = NULL, .remove_child = NULL, - .type = LTK_MENU, - .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE, + .type = LTK_MENUENTRY, + .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_HOVER_IS_ACTIVE, }; +/* FIXME: standardize menuentry vs. menu_entry */ + static ltk_theme_parseinfo menu_parseinfo[] = { - {"border-width", THEME_INT, {.i = &menu_theme.border_width}, {.i = 2}, 0, MAX_MENU_BORDER_WIDTH, 0}, {"pad", THEME_INT, {.i = &menu_theme.pad}, {.i = 0}, 0, MAX_MENU_PAD, 0}, - {"text-pad", THEME_INT, {.i = &menu_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, - {"arrow-size", THEME_INT, {.i = &menu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, {"arrow-pad", THEME_INT, {.i = &menu_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, + {"arrow-size", THEME_INT, {.i = &menu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, + {"border-width", THEME_INT, {.i = &menu_theme.border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0}, {"compress-borders", THEME_BOOL, {.b = &menu_theme.compress_borders}, {.b = 1}, 0, 0, 0}, - {"border-sides", THEME_BORDERSIDES, {.border = &menu_theme.border_sides}, {.border = LTK_BORDER_ALL}, 0, 0, 0}, - {"menu-border-width", THEME_INT, {.i = &menu_theme.menu_border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0}, - {"background", THEME_COLOR, {.color = &menu_theme.background}, {.color = "#555555"}, 0, 0, 0}, + {"border", THEME_COLOR, {.color = &menu_theme.border}, {.color = "#000000"}, 0, 0, 0}, + {"background", THEME_COLOR, {.color = &menu_theme.background}, {.color = "#000000"}, 0, 0, 0}, {"scroll-background", THEME_COLOR, {.color = &menu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0}, {"scroll-arrow-color", THEME_COLOR, {.color = &menu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0}, - {"text", THEME_COLOR, {.color = &menu_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"border", THEME_COLOR, {.color = &menu_theme.border}, {.color = "#339999"}, 0, 0, 0}, - {"fill", THEME_COLOR, {.color = &menu_theme.fill}, {.color = "#113355"}, 0, 0, 0}, - {"text-pressed", THEME_COLOR, {.color = &menu_theme.text_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"border-pressed", THEME_COLOR, {.color = &menu_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"fill-pressed", THEME_COLOR, {.color = &menu_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, - {"text-active", THEME_COLOR, {.color = &menu_theme.text_active}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"border-active", THEME_COLOR, {.color = &menu_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"fill-active", THEME_COLOR, {.color = &menu_theme.fill_active}, {.color = "#738194"}, 0, 0, 0}, - {"text-disabled", THEME_COLOR, {.color = &menu_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"border-disabled", THEME_COLOR, {.color = &menu_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"fill-disabled", THEME_COLOR, {.color = &menu_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, - {"menu-border", THEME_COLOR, {.color = &menu_theme.menu_border}, {.color = "#000000"}, 0, 0, 0}, }; static int menu_parseinfo_sorted = 0; @@ -181,31 +188,53 @@ ltk_menu_uninitialize_theme(ltk_window *window) { ltk_theme_uninitialize(window, menu_parseinfo, LENGTH(menu_parseinfo)); } +static ltk_theme_parseinfo menu_entry_parseinfo[] = { + {"text-pad", THEME_INT, {.i = &menu_entry_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, + {"arrow-pad", THEME_INT, {.i = &menu_entry_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, + {"arrow-size", THEME_INT, {.i = &menu_entry_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, + {"border-width", THEME_INT, {.i = &menu_entry_theme.border_width}, {.i = 2}, 0, MAX_MENU_BORDER_WIDTH, 0}, + {"border-sides", THEME_BORDERSIDES, {.border = &menu_entry_theme.border_sides}, {.border = LTK_BORDER_ALL}, 0, 0, 0}, + {"compress-borders", THEME_BOOL, {.b = &menu_entry_theme.compress_borders}, {.b = 1}, 0, 0, 0}, + {"text", THEME_COLOR, {.color = &menu_entry_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"border", THEME_COLOR, {.color = &menu_entry_theme.border}, {.color = "#339999"}, 0, 0, 0}, + {"fill", THEME_COLOR, {.color = &menu_entry_theme.fill}, {.color = "#113355"}, 0, 0, 0}, + {"text-pressed", THEME_COLOR, {.color = &menu_entry_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0}, + {"border-pressed", THEME_COLOR, {.color = &menu_entry_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"fill-pressed", THEME_COLOR, {.color = &menu_entry_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, + {"text-active", THEME_COLOR, {.color = &menu_entry_theme.text_active}, {.color = "#000000"}, 0, 0, 0}, + {"border-active", THEME_COLOR, {.color = &menu_entry_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"fill-active", THEME_COLOR, {.color = &menu_entry_theme.fill_active}, {.color = "#738194"}, 0, 0, 0}, + {"text-disabled", THEME_COLOR, {.color = &menu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"border-disabled", THEME_COLOR, {.color = &menu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"fill-disabled", THEME_COLOR, {.color = &menu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, +}; +static int menu_entry_parseinfo_sorted = 0; + +int +ltk_menuentry_ini_handler(ltk_window *window, const char *prop, const char *value) { + return ltk_theme_handle_value(window, "menu-entry", prop, value, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo), &menu_entry_parseinfo_sorted); +} + +int +ltk_menuentry_fill_theme_defaults(ltk_window *window) { + return ltk_theme_fill_defaults(window, "menu-entry", menu_entry_parseinfo, LENGTH(menu_entry_parseinfo)); +} + +void +ltk_menuentry_uninitialize_theme(ltk_window *window) { + ltk_theme_uninitialize(window, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo)); +} + static ltk_theme_parseinfo submenu_parseinfo[] = { - {"border-width", THEME_INT, {.i = &submenu_theme.border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0}, - {"pad", THEME_INT, {.i = &submenu_theme.pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, - {"text-pad", THEME_INT, {.i = &submenu_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, - {"arrow-size", THEME_INT, {.i = &submenu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, + {"pad", THEME_INT, {.i = &submenu_theme.pad}, {.i = 0}, 0, MAX_MENU_PAD, 0}, {"arrow-pad", THEME_INT, {.i = &submenu_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, - {"compress-borders", THEME_BOOL, {.b = &submenu_theme.compress_borders}, {.b = 0}, 0, 0, 0}, - {"border-sides", THEME_BORDERSIDES, {.border = &submenu_theme.border_sides}, {.border = LTK_BORDER_NONE}, 0, 0, 0}, - {"menu-border-width", THEME_INT, {.i = &submenu_theme.menu_border_width}, {.i = 1}, 0, MAX_MENU_BORDER_WIDTH, 0}, - {"background", THEME_COLOR, {.color = &submenu_theme.background}, {.color = "#555555"}, 0, 0, 0}, + {"arrow-size", THEME_INT, {.i = &submenu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, + {"border-width", THEME_INT, {.i = &submenu_theme.border_width}, {.i = 1}, 0, MAX_MENU_BORDER_WIDTH, 0}, + {"compress-borders", THEME_BOOL, {.b = &submenu_theme.compress_borders}, {.b = 1}, 0, 0, 0}, + {"border", THEME_COLOR, {.color = &submenu_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"background", THEME_COLOR, {.color = &submenu_theme.background}, {.color = "#000000"}, 0, 0, 0}, {"scroll-background", THEME_COLOR, {.color = &submenu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0}, {"scroll-arrow-color", THEME_COLOR, {.color = &submenu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0}, - {"text", THEME_COLOR, {.color = &submenu_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"border", THEME_COLOR, {.color = &submenu_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"fill", THEME_COLOR, {.color = &submenu_theme.fill}, {.color = "#113355"}, 0, 0, 0}, - {"text-pressed", THEME_COLOR, {.color = &submenu_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0}, - {"border-pressed", THEME_COLOR, {.color = &submenu_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"fill-pressed", THEME_COLOR, {.color = &submenu_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, - {"text-active", THEME_COLOR, {.color = &submenu_theme.text_active}, {.color = "#000000"}, 0, 0, 0}, - {"border-active", THEME_COLOR, {.color = &submenu_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"fill-active", THEME_COLOR, {.color = &submenu_theme.fill_active}, {.color = "#113355"}, 0, 0, 0}, - {"text-disabled", THEME_COLOR, {.color = &submenu_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"border-disabled", THEME_COLOR, {.color = &submenu_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, - {"fill-disabled", THEME_COLOR, {.color = &submenu_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, - {"menu-border", THEME_COLOR, {.color = &submenu_theme.menu_border}, {.color = "#FFFFFF"}, 0, 0, 0}, }; static int submenu_parseinfo_sorted = 0; @@ -224,40 +253,122 @@ ltk_submenu_uninitialize_theme(ltk_window *window) { ltk_theme_uninitialize(window, submenu_parseinfo, LENGTH(submenu_parseinfo)); } +static ltk_theme_parseinfo submenu_entry_parseinfo[] = { + {"text-pad", THEME_INT, {.i = &submenu_entry_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, + {"arrow-pad", THEME_INT, {.i = &submenu_entry_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, + {"arrow-size", THEME_INT, {.i = &submenu_entry_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, + {"border-width", THEME_INT, {.i = &submenu_entry_theme.border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0}, + {"border-sides", THEME_BORDERSIDES, {.border = &submenu_entry_theme.border_sides}, {.border = LTK_BORDER_NONE}, 0, 0, 0}, + {"compress-borders", THEME_BOOL, {.b = &submenu_entry_theme.compress_borders}, {.b = 0}, 0, 0, 0}, + {"text", THEME_COLOR, {.color = &submenu_entry_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"border", THEME_COLOR, {.color = &submenu_entry_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"fill", THEME_COLOR, {.color = &submenu_entry_theme.fill}, {.color = "#113355"}, 0, 0, 0}, + {"text-pressed", THEME_COLOR, {.color = &submenu_entry_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0}, + {"border-pressed", THEME_COLOR, {.color = &submenu_entry_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"fill-pressed", THEME_COLOR, {.color = &submenu_entry_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, + {"text-active", THEME_COLOR, {.color = &submenu_entry_theme.text_active}, {.color = "#000000"}, 0, 0, 0}, + {"border-active", THEME_COLOR, {.color = &submenu_entry_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"fill-active", THEME_COLOR, {.color = &submenu_entry_theme.fill_active}, {.color = "#738194"}, 0, 0, 0}, + {"text-disabled", THEME_COLOR, {.color = &submenu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"border-disabled", THEME_COLOR, {.color = &submenu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, + {"fill-disabled", THEME_COLOR, {.color = &submenu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, +}; +static int submenu_entry_parseinfo_sorted = 0; + +int +ltk_submenuentry_ini_handler(ltk_window *window, const char *prop, const char *value) { + return ltk_theme_handle_value(window, "submenu-entry", prop, value, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo), &submenu_entry_parseinfo_sorted); +} + +int +ltk_submenuentry_fill_theme_defaults(ltk_window *window) { + return ltk_theme_fill_defaults(window, "submenu-entry", submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo)); +} + +void +ltk_submenuentry_uninitialize_theme(ltk_window *window) { + ltk_theme_uninitialize(window, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo)); +} + static void -ltk_menu_resize(ltk_widget *self) { - ltk_menu *menu = (ltk_menu *)self; - double x_old = menu->x_scroll_offset; - double y_old = menu->y_scroll_offset; - int max_x, max_y; - ltk_menu_get_max_scroll_offset(menu, &max_x, &max_y); - if (menu->x_scroll_offset > max_x) - menu->x_scroll_offset = max_x; - if (menu->y_scroll_offset > max_y) - menu->y_scroll_offset = max_y; - if (fabs(x_old - menu->x_scroll_offset) < 0.01 || - fabs(y_old - menu->y_scroll_offset) < 0.01) { - menu->widget.dirty = 1; - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); +ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) { + ltk_menuentry *e = (ltk_menuentry *)self; + int in_submenu = IN_SUBMENU(e); + int submenus_opened = self->parent && self->parent->vtable->type == LTK_MENU && ((ltk_menu *)self->parent)->popup_submenus; + if (!(self->state & (LTK_ACTIVE | LTK_PRESSED))) { + /* Note: This only has to take care of the submenu that is the direct child + of e because ltk_window_set_active_widget already calls change_state for + the whole hierarchy */ + unpopup_active_entry(e); + } else if ((self->state & LTK_PRESSED) && !(old_state & LTK_PRESSED) && submenus_opened) { + ((ltk_menu *)self->parent)->popup_submenus = 0; + } else if (((self->state & LTK_PRESSED) || + ((self->state & LTK_ACTIVE) && (in_submenu || submenus_opened))) && + e->submenu && e->submenu->widget.hidden) { + printf("popup: %s, %d, %d, %d\n", self->id, submenus_opened, self->state, self->parent->hidden); + popup_active_menu(e); + if (self->parent && self->parent->vtable->type == LTK_MENU) + ((ltk_menu *)self->parent)->popup_submenus = 1; } } static void -ltk_menu_change_state(ltk_widget *self) { - ltk_menu *menu = (ltk_menu *)self; - if (self->state != LTK_PRESSED && menu->pressed_entry < menu->num_entries) { - menu->pressed_entry = SIZE_MAX; - self->dirty = 1; - ltk_window_invalidate_rect(self->window, self->rect); - } - if (self->state == LTK_NORMAL && menu->active_entry < menu->num_entries) { - ltk_menuentry *e = &menu->entries[menu->active_entry]; - if (!e->submenu || e->submenu->widget.hidden) { - menu->active_entry = SIZE_MAX; - self->dirty = 1; - ltk_window_invalidate_rect(self->window, self->rect); - } +ltk_menuentry_draw(ltk_widget *self, ltk_rect clip) { + /* FIXME: figure out how hidden should work */ + if (self->hidden) + return; + ltk_menuentry *entry = (ltk_menuentry *)self; + int in_submenu = IN_SUBMENU(entry); + struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme; + ltk_color *text, *border, *fill; + if (self->state & LTK_DISABLED) { + text = &t->text_disabled; + border = &t->border_disabled; + fill = &t->fill_disabled; + } else if (self->state & LTK_PRESSED) { + text = &t->text_pressed; + border = &t->border_pressed; + fill = &t->fill_pressed; + } else if (self->state & LTK_HOVERACTIVE) { + text = &t->text_active; + border = &t->border_active; + fill = &t->fill_active; + } else { + text = &t->text; + border = &t->border; + fill = &t->fill; + } + ltk_rect rect = self->rect; + ltk_rect clip_final = ltk_rect_intersect(clip, rect); + if (clip_final.w <= 0 || clip_final.h <= 0) + return; + ltk_surface_fill_rect(self->window->surface, fill, clip_final); + + ltk_surface *s; + int text_w, text_h; + ltk_text_line_get_size(entry->text_line, &text_w, &text_h); + if (!ltk_surface_cache_get_surface(entry->text_surface_key, &s) || self->dirty) { + ltk_surface_fill_rect(s, fill, (ltk_rect){0, 0, text_w, text_h}); + ltk_text_line_draw(entry->text_line, s, text, 0, 0); + self->dirty = 0; + } + int text_x = rect.x + t->text_pad + t->border_width; + int text_y = rect.y + t->text_pad + t->border_width; + ltk_rect text_clip = ltk_rect_intersect(clip, (ltk_rect){text_x, text_y, text_w, text_h}); + ltk_surface_copy( + s, self->window->surface, + (ltk_rect){text_clip.x - text_x, text_clip.y - text_y, text_clip.w, text_clip.h}, text_clip.x, text_clip.y + ); + + if (in_submenu && entry->submenu) { + ltk_point arrow_points[] = { + {rect.x + rect.w - t->arrow_pad - t->border_width, rect.y + rect.h / 2}, + {rect.x + rect.w - t->arrow_pad - t->border_width - t->arrow_size, rect.y + rect.h / 2 - t->arrow_size / 2}, + {rect.x + rect.w - t->arrow_pad - t->border_width - t->arrow_size, rect.y + rect.h / 2 + t->arrow_size / 2} + }; + ltk_surface_fill_polygon_clipped(self->window->surface, text, arrow_points, LENGTH(arrow_points), clip_final); } + ltk_surface_draw_border_clipped(self->window->surface, border, rect, clip_final, t->border_width, t->border_sides); } static void @@ -267,185 +378,110 @@ ltk_menu_draw(ltk_widget *self, ltk_rect clip) { ltk_menu *menu = (ltk_menu *)self; ltk_rect rect = self->rect; ltk_rect clip_final = ltk_rect_intersect(clip, rect); - ltk_surface *s; - if (!ltk_surface_cache_get_surface(self->surface_key, &s) || self->dirty) - ltk_menu_redraw_surface(menu, s); - ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y); + struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; + ltk_surface_fill_rect(self->window->surface, &t->background, self->rect); + for (size_t i = 0; i < menu->num_entries; i++) { + /* FIXME: I guess it could be improved *slightly* by making the clip rect + smaller when scrollarrows are shown */ + /* draw active entry after others so it isn't hidden with compress_borders */ + if ((menu->entries[i]->widget.state & (LTK_ACTIVE | LTK_PRESSED | LTK_HOVER)) && i < menu->num_entries - 1) { + ltk_menuentry_draw(&menu->entries[i + 1]->widget, clip_final); + ltk_menuentry_draw(&menu->entries[i]->widget, clip_final); + i++; + } else { + ltk_widget *widget = &menu->entries[i]->widget; + ltk_menuentry_draw(&menu->entries[i]->widget, clip_final); + } + } + + /* FIXME: active, pressed states */ + int sz = t->arrow_size + t->arrow_pad * 2; + int ww = self->rect.w; + int wh = self->rect.h; + int wx = self->rect.x; + int wy = self->rect.y; + int mbw = t->border_width; + /* FIXME: handle pathological case where rect is so small that this still draws outside */ + if (rect.w < (int)self->ideal_w) { + ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, sz, wh - mbw * 2}); + ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + ww - sz - mbw, wy + mbw, sz, wh - mbw * 2}); + ltk_point arrow_points[3] = { + {wx + t->arrow_pad + mbw, wy + wh / 2}, + {wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 - t->arrow_size / 2}, + {wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 + t->arrow_size / 2} + }; + ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + arrow_points[0] = (ltk_point){wx + ww - t->arrow_pad - mbw, wy + wh / 2}; + arrow_points[1] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 - t->arrow_size / 2}; + arrow_points[2] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 + t->arrow_size / 2}; + ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + } + if (rect.h < (int)self->ideal_h) { + ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz}); + ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz}); + ltk_point arrow_points[3] = { + {wx + ww / 2, wy + t->arrow_pad + mbw}, + {wx + ww / 2 - t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size}, + {wx + ww / 2 + t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size} + }; + ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + arrow_points[0] = (ltk_point){wx + ww / 2, wy + wh - t->arrow_pad - mbw}; + arrow_points[1] = (ltk_point){wx + ww / 2 - t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size}; + arrow_points[2] = (ltk_point){wx + ww / 2 + t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size}; + ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + } + ltk_surface_draw_border(self->window->surface, &t->border, rect, mbw, LTK_BORDER_ALL); + + self->dirty = 0; } -/* FIXME: glitches when drawing text with stb backend while scrolling */ + static void -ltk_menu_redraw_surface(ltk_menu *menu, ltk_surface *s) { - ltk_rect rect = menu->widget.rect; - int ideal_w = menu->widget.ideal_w, ideal_h = menu->widget.ideal_h; +ltk_menu_resize(ltk_widget *self) { + ltk_menu *menu = (ltk_menu *)self; + int max_x, max_y; + ltk_menu_get_max_scroll_offset(menu, &max_x, &max_y); + if (menu->x_scroll_offset > max_x) + menu->x_scroll_offset = max_x; + if (menu->y_scroll_offset > max_y) + menu->y_scroll_offset = max_y; + + ltk_rect rect = self->rect; struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; + struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme; + int ideal_w = self->ideal_w, ideal_h = self->ideal_h; int arrow_size = t->arrow_pad * 2 + t->arrow_size; int start_x = rect.w < ideal_w ? arrow_size : 0; int start_y = rect.h < ideal_h ? arrow_size : 0; - start_x += t->menu_border_width; - start_y += t->menu_border_width; - int real_w = rect.w - start_x * 2; - int real_h = rect.h - start_y * 2; + start_x += t->border_width; + start_y += t->border_width; - int offset_x = (int)menu->x_scroll_offset; - int offset_y = (int)menu->y_scroll_offset; + int mbw = t->border_width; + int cur_abs_x = -(int)menu->x_scroll_offset + rect.x + start_x + t->pad; + int cur_abs_y = -(int)menu->y_scroll_offset + rect.y + start_y + t->pad; + printf("%d, %d\n", self->rect.x, self->rect.w); - ltk_surface_fill_rect(s, &t->background, (ltk_rect){0, 0, rect.w, rect.h}); - int text_w, text_h; - ltk_color *text, *border, *fill; - int cur_abs_x = 0, cur_abs_y = 0; - if (menu->is_submenu) - cur_abs_y = t->pad; - else - cur_abs_x = t->pad; - int overlap = t->compress_borders ? t->border_width - t->pad : 0; - int bw_advance = t->compress_borders ? t->border_width : t->border_width * 2; - int mbw = t->menu_border_width; for (size_t i = 0; i < menu->num_entries; i++) { - ltk_menuentry *e = &menu->entries[i]; - ltk_text_line_get_size(e->text, &text_w, &text_h); - if (menu->is_submenu) { - if (cur_abs_y + t->border_width * 2 + t->text_pad * 2 + text_h <= offset_y) { - /* FIXME: ugly because repeated further down */ - cur_abs_y += bw_advance + t->text_pad * 2 + text_h + t->pad; - continue; - } else if (cur_abs_y >= offset_y + real_h) { - break; - } - } else { - if (cur_abs_x + t->border_width * 2 + t->text_pad * 2 + text_w <= offset_x) { - cur_abs_x += bw_advance + t->text_pad * 2 + text_w + t->pad; - continue; - } else if (cur_abs_x >= offset_x + real_w) { - break; - } - } - /* FIXME: allow different border_sides for different states */ - if (e->disabled) { - text = &t->text_disabled; - border = &t->border_disabled; - fill = &t->fill_disabled; - } else if (menu->pressed_entry == i) { - text = &t->text_pressed; - border = &t->border_pressed; - fill = &t->fill_pressed; - } else if (menu->active_entry == i) { - text = &t->text_active; - border = &t->border_active; - fill = &t->fill_active; - } else { - text = &t->text; - border = &t->border; - fill = &t->fill; - } - /* FIXME: how well-defined is it to give X drawing commands - with parts outside of the actual pixmap? */ - /* FIXME: optimize drawing (avoid drawing pixels multiple times) */ - int draw_x = cur_abs_x - offset_x + start_x; - int draw_y = cur_abs_y - offset_y + start_y; - int last_special = i > 0 && (menu->active_entry == i - 1 || menu->pressed_entry == i - 1); + ltk_menuentry *e = menu->entries[i]; + e->widget.rect.x = cur_abs_x; + e->widget.rect.y = cur_abs_y; if (menu->is_submenu) { - int extra_size = e->submenu ? t->arrow_pad * 2 + t->arrow_size : 0; - int height = MAX(text_h + t->text_pad * 2, extra_size) + t->border_width * 2; - ltk_rect r; - if (last_special && overlap > 0) { - r = (ltk_rect){ - draw_x + overlap, - draw_y + t->pad, /* t->pad is the same as t->border_width - overlap */ - ideal_w - t->pad * 2 - mbw * 2, - height - overlap - }; - } else { - r = (ltk_rect){draw_x + t->pad, draw_y, ideal_w - t->pad * 2, height}; - } - ltk_surface_fill_rect(s, fill, r); - ltk_text_line_draw( - e->text, s, text, - draw_x + t->pad + t->border_width + t->text_pad, - draw_y + height / 2 - text_h / 2 - ); - if (e->submenu) { - ltk_point arrow_points[3] = { - {draw_x + ideal_w - t->pad - t->arrow_pad, draw_y + height / 2}, - {draw_x + ideal_w - t->pad - t->arrow_pad - t->arrow_size, draw_y + height / 2 - t->arrow_size / 2}, - {draw_x + ideal_w - t->pad - t->arrow_pad - t->arrow_size, draw_y + height / 2 + t->arrow_size / 2} - }; - ltk_surface_fill_polygon(s, text, arrow_points, 3); - } - if (last_special && overlap > 0) { - ltk_surface_draw_border(s, border, r, t->border_width, t->border_sides & ~LTK_BORDER_TOP); - if (t->border_sides & LTK_BORDER_TOP) - ltk_surface_draw_border(s, border, r, t->pad, LTK_BORDER_TOP); - } else { - ltk_surface_draw_border(s, border, r, t->border_width, t->border_sides); - } - cur_abs_y += bw_advance + t->text_pad * 2 + text_h + t->pad; + e->widget.rect.w = ideal_w - 2 * t->pad - 2 * mbw; + e->widget.rect.h = e->widget.ideal_h; + cur_abs_y += e->widget.ideal_h + t->pad; + if (et->compress_borders) + cur_abs_y -= et->border_width; } else { - ltk_rect r; - if (last_special && overlap > 0) { - r = (ltk_rect){ - draw_x + overlap, - draw_y + t->pad, - t->text_pad * 2 + t->border_width * 2 - overlap + text_w, - ideal_h - t->pad * 2 - mbw * 2 - }; - } else { - r = (ltk_rect){draw_x, draw_y + t->pad, t->text_pad * 2 + t->border_width * 2 + text_w, ideal_h - t->pad * 2}; - } - ltk_surface_fill_rect(s, fill, r); - /* FIXME: should the text be bottom-aligned in case different - entries have different text height? */ - ltk_text_line_draw( - e->text, s, text, - draw_x + t->border_width + t->text_pad, - draw_y + t->pad + t->border_width + t->text_pad - ); - if (last_special && overlap > 0) { - ltk_surface_draw_border(s, border, r, t->border_width, t->border_sides & ~LTK_BORDER_LEFT); - if (t->border_sides & LTK_BORDER_LEFT) - ltk_surface_draw_border(s, border, r, t->pad, LTK_BORDER_LEFT); - } else { - ltk_surface_draw_border(s, border, r, t->border_width, t->border_sides); - } - cur_abs_x += bw_advance + t->text_pad * 2 + text_w + t->pad; + e->widget.rect.w = e->widget.ideal_w; + e->widget.rect.h = ideal_h - 2 * t->pad - 2 * mbw; + cur_abs_x += e->widget.ideal_w + t->pad; + if (et->compress_borders) + cur_abs_x -= et->border_width; } } - /* FIXME: active, pressed states */ - int sz = t->arrow_size + t->arrow_pad * 2; - int ww = menu->widget.rect.w; - int wh = menu->widget.rect.h; - if (rect.w < ideal_w) { - ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){mbw, mbw, sz, wh - mbw * 2}); - ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){ww - sz - mbw, mbw, sz, wh - mbw * 2}); - ltk_point arrow_points[3] = { - {t->arrow_pad + mbw, wh / 2}, - {t->arrow_pad + mbw + t->arrow_size, wh / 2 - t->arrow_size / 2}, - {t->arrow_pad + mbw + t->arrow_size, wh / 2 + t->arrow_size / 2} - }; - ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); - arrow_points[0] = (ltk_point){ww - t->arrow_pad - mbw, wh / 2}; - arrow_points[1] = (ltk_point){ww - t->arrow_pad - mbw - t->arrow_size, wh / 2 - t->arrow_size / 2}; - arrow_points[2] = (ltk_point){ww - t->arrow_pad - mbw - t->arrow_size, wh / 2 + t->arrow_size / 2}; - ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); - } - if (rect.h < ideal_h) { - ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){mbw, mbw, ww - mbw * 2, sz}); - ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){mbw, wh - sz - mbw, ww - mbw * 2, sz}); - ltk_point arrow_points[3] = { - {ww / 2, t->arrow_pad + mbw}, - {ww / 2 - t->arrow_size / 2, t->arrow_pad + mbw + t->arrow_size}, - {ww / 2 + t->arrow_size / 2, t->arrow_pad + mbw + t->arrow_size} - }; - ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); - arrow_points[0] = (ltk_point){ww / 2, wh - t->arrow_pad - mbw}; - arrow_points[1] = (ltk_point){ww / 2 - t->arrow_size / 2, wh - t->arrow_pad - mbw - t->arrow_size}; - arrow_points[2] = (ltk_point){ww / 2 + t->arrow_size / 2, wh - t->arrow_pad - mbw - t->arrow_size}; - ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); - } - ltk_surface_draw_border(s, &t->menu_border, (ltk_rect){0, 0, ww, wh}, mbw, LTK_BORDER_ALL); - - menu->widget.dirty = 0; + self->dirty = 1; + ltk_window_invalidate_rect(self->window, self->rect); } static void @@ -485,8 +521,9 @@ ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step) { if (menu->y_scroll_offset > max_scroll_y) menu->y_scroll_offset = max_scroll_y; /* FIXME: sensible epsilon? */ - if (fabs(x_old - menu->x_scroll_offset) < 0.01 || - fabs(y_old - menu->y_scroll_offset) < 0.01) { + if (fabs(x_old - menu->x_scroll_offset) > 0.01 || + fabs(y_old - menu->y_scroll_offset) > 0.01) { + ltk_menu_resize(&menu->widget); menu->widget.dirty = 1; ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); } @@ -503,8 +540,6 @@ ltk_menu_scroll_callback(void *data) { ); } -/* FIXME: HANDLE mouse scroll wheel! */ - static void stop_scrolling(ltk_menu *menu) { menu->scroll_top_hover = 0; @@ -516,62 +551,30 @@ stop_scrolling(ltk_menu *menu) { } /* FIXME: should ideal_w, ideal_h just be int? */ -static size_t -get_entry_at_point(ltk_menu *menu, int x, int y, ltk_rect *entry_rect_ret) { +static ltk_widget * +ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) { + ltk_menu *menu = (ltk_menu *)self; struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; int arrow_size = t->arrow_size + t->arrow_pad * 2; - int mbw = t->menu_border_width; - int start_x = menu->widget.rect.x + mbw, end_x = menu->widget.rect.x + menu->widget.rect.w - mbw; - int start_y = menu->widget.rect.y + mbw, end_y = menu->widget.rect.y + menu->widget.rect.h - mbw; - if (menu->widget.rect.w < (int)menu->widget.ideal_w) { + int mbw = t->border_width; + int start_x = self->rect.x + mbw, end_x = self->rect.x + self->rect.w - mbw; + int start_y = self->rect.y + mbw, end_y = self->rect.y + self->rect.h - mbw; + if (self->rect.w < (int)self->ideal_w) { start_x += arrow_size; end_x -= arrow_size; } - if (menu->widget.rect.h < (int)menu->widget.ideal_h) { + if (self->rect.h < (int)self->ideal_h) { start_y += arrow_size; end_y -= arrow_size; } if (!ltk_collide_rect((ltk_rect){start_x, start_y, end_x - start_x, end_y - start_y}, x, y)) - return SIZE_MAX; + return NULL; - int bw_sub = t->compress_borders ? t->border_width : 0; - int cur_x = start_x - (int)menu->x_scroll_offset + t->pad; - int cur_y = start_y - (int)menu->y_scroll_offset + t->pad; - /* FIXME: could be optimized a bit */ for (size_t i = 0; i < menu->num_entries; i++) { - ltk_menuentry *e = &menu->entries[i]; - int text_w, text_h; - ltk_text_line_get_size(e->text, &text_w, &text_h); - if (menu->is_submenu) { - int extra_size = e->submenu ? t->arrow_pad * 2 + t->arrow_size : 0; - int w = (int)menu->widget.ideal_w - t->pad * 2; - int h = MAX(text_h + t->text_pad * 2, extra_size) + t->border_width * 2; - if (x >= cur_x && x <= cur_x + w && y >= cur_y && y <= cur_y + h) { - if (entry_rect_ret) { - entry_rect_ret->x = cur_x; - entry_rect_ret->y = cur_y; - entry_rect_ret->w = w; - entry_rect_ret->h = h; - } - return i; - } - cur_y += h - bw_sub + t->pad; - } else { - int w = text_w + t->text_pad * 2 + t->border_width * 2; - int h = (int)menu->widget.ideal_h - t->pad * 2; - if (x >= cur_x && x <= cur_x + w && y >= cur_y && y <= cur_y + h) { - if (entry_rect_ret) { - entry_rect_ret->x = cur_x; - entry_rect_ret->y = cur_y; - entry_rect_ret->w = w; - entry_rect_ret->h = h; - } - return i; - } - cur_x += w - bw_sub + t->pad; - } + if (ltk_collide_rect(menu->entries[i]->widget.rect, x, y)) + return &menu->entries[i]->widget; } - return SIZE_MAX; + return NULL; } /* FIXME: make sure timers are always destroyed when widget is destroyed */ @@ -610,54 +613,44 @@ set_scroll_timer(ltk_menu *menu, int x, int y) { } static int -ltk_menu_mouse_release(ltk_widget *self, ltk_button_event *event) { - ltk_menu *menu = (ltk_menu *)self; - size_t idx = get_entry_at_point(menu, event->x, event->y, NULL); - if (idx < menu->num_entries && idx == menu->pressed_entry) { - ltk_window_unregister_all_popups(self->window); - /* FIXME: give menu id and entry id */ - ltk_queue_event(self->window, LTK_EVENT_MENU, menu->entries[idx].id, "menu_entry_click"); - } - if (menu->pressed_entry < menu->num_entries && idx < menu->num_entries) - menu->active_entry = menu->pressed_entry; - else if (idx < menu->num_entries) - menu->active_entry = idx; - menu->pressed_entry = SIZE_MAX; - self->dirty = 1; +ltk_menuentry_mouse_release(ltk_widget *self, ltk_button_event *event) { + (void)event; + ltk_menuentry *e = (ltk_menuentry *)self; + int in_submenu = IN_SUBMENU(e); + int keep_popup = self->parent && self->parent->vtable->type == LTK_MENU && ((ltk_menu *)self->parent)->popup_submenus; + /* FIXME: problem when scrolling because actual shown rect may not be entire rect */ + if ((self->state & LTK_PRESSED) && event->button == LTK_BUTTONL && ltk_collide_rect(self->rect, event->x, event->y)) { + if (in_submenu || !keep_popup) { + ltk_window_unregister_all_popups(self->window); + } + ltk_queue_event(self->window, LTK_EVENT_MENU, self->id, "menu_entry_click"); + } return 1; } static int ltk_menu_mouse_press(ltk_widget *self, ltk_button_event *event) { ltk_menu *menu = (ltk_menu *)self; - size_t idx; /* FIXME: configure scroll step */ switch (event->button) { - case LTK_BUTTONL: - idx = get_entry_at_point(menu, event->x, event->y, NULL); - if (idx < menu->num_entries) { - menu->pressed_entry = idx; - self->dirty = 1; - } - break; case LTK_BUTTON4: ltk_menu_scroll(menu, 1, 0, 0, 0, 10); - handle_hover(menu, event->x, event->y); + ltk_window_fake_motion_event(self->window, event->x, event->y); break; case LTK_BUTTON5: ltk_menu_scroll(menu, 0, 1, 0, 0, 10); - handle_hover(menu, event->x, event->y); + ltk_window_fake_motion_event(self->window, event->x, event->y); break; case LTK_BUTTON6: ltk_menu_scroll(menu, 0, 0, 1, 0, 10); - handle_hover(menu, event->x, event->y); + ltk_window_fake_motion_event(self->window, event->x, event->y); break; case LTK_BUTTON7: ltk_menu_scroll(menu, 0, 0, 0, 1, 10); - handle_hover(menu, event->x, event->y); + ltk_window_fake_motion_event(self->window, event->x, event->y); break; default: - break; + return 0; } return 1; } @@ -665,167 +658,156 @@ ltk_menu_mouse_press(ltk_widget *self, ltk_button_event *event) { static void ltk_menu_hide(ltk_widget *self) { ltk_menu *menu = (ltk_menu *)self; - menu->active_entry = menu->pressed_entry = SIZE_MAX; if (menu->scroll_timer_id >= 0) ltk_unregister_timer(menu->scroll_timer_id); menu->scroll_bottom_hover = menu->scroll_top_hover = 0; menu->scroll_left_hover = menu->scroll_right_hover = 0; ltk_window_unregister_popup(self->window, self); ltk_window_invalidate_rect(self->window, self->rect); - /* when hiding, also update parent so it doesn't show the - entry as selected anymore */ - if (self->parent && self->parent->vtable->type == LTK_MENU) { - ltk_menu_change_state(self->parent); + /* FIXME: this is really ugly/hacky */ + if (menu->unpopup_submenus_on_hide && self->parent && self->parent->vtable->type == LTK_MENUENTRY && + self->parent->parent && self->parent->parent->vtable->type == LTK_MENU) { + printf("hide: %s\n", self->id); + ((ltk_menu *)self->parent->parent)->popup_submenus = 0; } + menu->unpopup_submenus_on_hide = 1; } -/* FIXME: don't require passing rect */ +/* FIXME: hacky because entries need to know about their parents to be able to properly position the popup */ static void -popup_active_menu(ltk_menu *menu, ltk_rect r) { - size_t idx = menu->active_entry; - if (idx >= menu->num_entries) +popup_active_menu(ltk_menuentry *e) { + if (!e->submenu) return; - int win_w = menu->widget.window->rect.w; - int win_h = menu->widget.window->rect.h; - if (menu->entries[idx].submenu) { - ltk_menu *submenu = menu->entries[idx].submenu; - int ideal_w = submenu->widget.ideal_w + 2; - int ideal_h = submenu->widget.ideal_h; - int x_final = 0, y_final = 0, w_final = ideal_w, h_final = ideal_h; - if (menu->is_submenu) { - int space_left = menu->widget.rect.x; - int space_right = win_w - (menu->widget.rect.x + menu->widget.rect.w); - int x_right = menu->widget.rect.x + menu->widget.rect.w; - int x_left = menu->widget.rect.x - ideal_w; - if (menu->was_opened_left) { - if (x_left >= 0) { - x_final = x_left; - submenu->was_opened_left = 1; - } else if (space_right >= ideal_w) { - x_final = x_right; - submenu->was_opened_left = 0; - } else { - x_final = 0; - if (win_w < ideal_w) - w_final = win_w; - submenu->was_opened_left = 1; - } + int in_submenu = 0, was_opened_left = 0; + ltk_rect menu_rect = e->widget.rect; + ltk_rect entry_rect = e->widget.rect; + if (e->widget.parent && e->widget.parent->vtable->type == LTK_MENU) { + ltk_menu *menu = (ltk_menu *)e->widget.parent; + in_submenu = menu->is_submenu; + was_opened_left = menu->was_opened_left; + menu_rect = menu->widget.rect; + } + int win_w = e->widget.window->rect.w; + int win_h = e->widget.window->rect.h; + ltk_menu *submenu = e->submenu; + int ideal_w = submenu->widget.ideal_w; + int ideal_h = submenu->widget.ideal_h; + int x_final = 0, y_final = 0, w_final = ideal_w, h_final = ideal_h; + if (in_submenu) { + int space_left = menu_rect.x; + int space_right = win_w - (menu_rect.x + menu_rect.w); + int x_right = menu_rect.x + menu_rect.w; + int x_left = menu_rect.x - ideal_w; + if (submenu_theme.compress_borders) { + x_right -= submenu_theme.border_width; + x_left += submenu_theme.border_width; + } + if (was_opened_left) { + if (x_left >= 0) { + x_final = x_left; + submenu->was_opened_left = 1; + } else if (space_right >= ideal_w) { + x_final = x_right; + submenu->was_opened_left = 0; } else { - if (space_right >= ideal_w) { - x_final = x_right; - submenu->was_opened_left = 0; - } else if (space_left >= ideal_w) { - x_final = x_left; - submenu->was_opened_left = 1; - } else { - x_final = win_w - ideal_w; - if (x_final < 0) { - x_final = 0; - w_final = win_w; - } - submenu->was_opened_left = 0; - } - } - /* subtract padding and border width so the actual entries are at the right position */ - y_final = r.y - submenu_theme.pad - submenu_theme.menu_border_width; - if (y_final + ideal_h > win_h) - y_final = win_h - ideal_h; - if (y_final < 0) { - y_final = 0; - h_final = win_h; + x_final = 0; + if (win_w < ideal_w) + w_final = win_w; + submenu->was_opened_left = 1; } } else { - int space_top = menu->widget.rect.y; - int space_bottom = win_h - (menu->widget.rect.y + menu->widget.rect.h); - int y_top = menu->widget.rect.y - ideal_h; - int y_bottom = menu->widget.rect.y + menu->widget.rect.h; - if (space_top > space_bottom) { - y_final = y_top; - if (y_final < 0) { - y_final = 0; - h_final = menu->widget.rect.y; - } + if (space_right >= ideal_w) { + x_final = x_right; + submenu->was_opened_left = 0; + } else if (space_left >= ideal_w) { + x_final = x_left; + submenu->was_opened_left = 1; } else { - y_final = y_bottom; - if (space_bottom < ideal_h) - h_final = space_bottom; + x_final = win_w - ideal_w; + if (x_final < 0) { + x_final = 0; + w_final = win_w; + } + submenu->was_opened_left = 0; } - /* FIXME: maybe threshold so there's always at least a part of - the menu contents shown (instead of maybe just a few pixels) */ - /* pathological case where window is way too small */ - if (h_final <= 0) { + } + /* subtract padding and border width so the actual entries are at the right position */ + y_final = entry_rect.y - submenu_theme.pad - submenu_theme.border_width; + if (y_final + ideal_h > win_h) + y_final = win_h - ideal_h; + if (y_final < 0) { + y_final = 0; + h_final = win_h; + } + } else { + int space_top = menu_rect.y; + int space_bottom = win_h - (menu_rect.y + menu_rect.h); + int y_top = menu_rect.y - ideal_h; + int y_bottom = menu_rect.y + menu_rect.h; + if (menu_theme.compress_borders) { + y_top += menu_theme.border_width; + y_bottom -= menu_theme.border_width; + } + if (space_top > space_bottom) { + y_final = y_top; + if (y_final < 0) { y_final = 0; - h_final = win_h; - } - x_final = r.x; - if (x_final + ideal_w > win_w) - x_final = win_w - ideal_w; - if (x_final < 0) { - x_final = 0; - w_final = win_w; + h_final = menu_rect.y; } + } else { + y_final = y_bottom; + if (space_bottom < ideal_h) + h_final = space_bottom; + } + /* FIXME: maybe threshold so there's always at least a part of + the menu contents shown (instead of maybe just a few pixels) */ + /* pathological case where window is way too small */ + if (h_final <= 0) { + y_final = 0; + h_final = win_h; + } + x_final = entry_rect.x; + if (x_final + ideal_w > win_w) + x_final = win_w - ideal_w; + if (x_final < 0) { + x_final = 0; + w_final = win_w; } - /* reset everything just in case */ - submenu->x_scroll_offset = submenu->y_scroll_offset = 0; - submenu->active_entry = submenu->pressed_entry = SIZE_MAX; - submenu->scroll_top_hover = submenu->scroll_bottom_hover = 0; - submenu->scroll_left_hover = submenu->scroll_right_hover = 0; - submenu->widget.rect.x = x_final; - submenu->widget.rect.y = y_final; - submenu->widget.rect.w = w_final; - submenu->widget.rect.h = h_final; - ltk_surface_cache_request_surface_size(submenu->widget.surface_key, w_final, h_final); - submenu->widget.dirty = 1; - submenu->widget.hidden = 0; - ltk_window_register_popup(menu->widget.window, (ltk_widget *)submenu); - ltk_window_invalidate_rect(submenu->widget.window, submenu->widget.rect); - } -} - -static void -unpopup_active_entry(ltk_menu *menu) { - if (menu->active_entry >= menu->num_entries) - return; - ltk_menu *cur_menu = menu->entries[menu->active_entry].submenu; - menu->active_entry = SIZE_MAX; - while (cur_menu) { - ltk_menu *tmp = NULL; - if (cur_menu->active_entry < cur_menu->num_entries) - tmp = cur_menu->entries[cur_menu->active_entry].submenu; - ltk_menu_hide((ltk_widget *)cur_menu); - cur_menu = tmp; } + /* reset everything just in case */ + submenu->x_scroll_offset = submenu->y_scroll_offset = 0; + submenu->scroll_top_hover = submenu->scroll_bottom_hover = 0; + submenu->scroll_left_hover = submenu->scroll_right_hover = 0; + submenu->widget.rect.x = x_final; + submenu->widget.rect.y = y_final; + submenu->widget.rect.w = w_final; + submenu->widget.rect.h = h_final; + submenu->widget.dirty = 1; + submenu->widget.hidden = 0; + submenu->popup_submenus = 0; + submenu->unpopup_submenus_on_hide = 1; + ltk_menu_resize(&submenu->widget); + ltk_window_register_popup(e->widget.window, (ltk_widget *)submenu); + ltk_window_invalidate_rect(submenu->widget.window, submenu->widget.rect); } static void -handle_hover(ltk_menu *menu, int x, int y) { - if (set_scroll_timer(menu, x, y) || menu->pressed_entry < menu->num_entries) - return; - ltk_rect r; - size_t idx = get_entry_at_point(menu, x, y, &r); - if (idx >= menu->num_entries) - return; - ltk_menu *cur_submenu = menu->active_entry < menu->num_entries ? menu->entries[menu->active_entry].submenu : NULL; - if (idx != menu->active_entry) { - unpopup_active_entry(menu); - menu->active_entry = idx; - menu->widget.dirty = 1; - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); - popup_active_menu(menu, r); - } else if (cur_submenu && cur_submenu->widget.hidden) { - popup_active_menu(menu, r); +unpopup_active_entry(ltk_menuentry *e) { + if (e->submenu && !e->submenu->widget.hidden) { + e->submenu->unpopup_submenus_on_hide = 0; + ltk_widget_hide(&e->submenu->widget); } } static int ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event) { - handle_hover((ltk_menu *)self, event->x, event->y); + set_scroll_timer((ltk_menu *)self, event->x, event->y); return 1; } static int ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event) { - handle_hover((ltk_menu *)self, event->x, event->y); + set_scroll_timer((ltk_menu *)self, event->x, event->y); return 1; } @@ -846,317 +828,269 @@ ltk_menu_create(ltk_window *window, const char *id, int is_submenu) { menu->entries = NULL; menu->num_entries = menu->num_alloc = 0; - menu->pressed_entry = menu->active_entry = SIZE_MAX; menu->x_scroll_offset = menu->y_scroll_offset = 0; menu->is_submenu = is_submenu; menu->was_opened_left = 0; menu->scroll_timer_id = -1; menu->scroll_top_hover = menu->scroll_bottom_hover = 0; menu->scroll_left_hover = menu->scroll_right_hover = 0; + menu->popup_submenus = 0; + menu->unpopup_submenus_on_hide = 1; /* FIXME: hide widget by default so recalc doesn't cause unnecessary redrawing */ - recalc_menu_size(menu); + recalc_ideal_menu_size(&menu->widget, NULL); return menu; } -static ltk_menuentry * -insert_entry(ltk_menu *menu, size_t idx) { +static int +insert_entry(ltk_menu *menu, ltk_menuentry *e, size_t idx) { if (idx > menu->num_entries) - return NULL; + return 1; if (menu->num_entries == menu->num_alloc) { menu->num_alloc = ideal_array_size(menu->num_alloc, menu->num_entries + 1); - menu->entries = ltk_reallocarray(menu->entries, menu->num_alloc, sizeof(ltk_menuentry)); + menu->entries = ltk_reallocarray(menu->entries, menu->num_alloc, sizeof(ltk_menuentry *)); } memmove( menu->entries + idx + 1, menu->entries + idx, - sizeof(ltk_menuentry) * (menu->num_entries - idx) + sizeof(ltk_menuentry *) * (menu->num_entries - idx) ); - if (menu->active_entry >= idx && menu->active_entry < menu->num_entries) - menu->active_entry++; - if (menu->pressed_entry >= idx && menu->pressed_entry < menu->num_entries) - menu->pressed_entry++; menu->num_entries++; - return &menu->entries[idx]; + menu->entries[idx] = e; + return 0; } -/* FIXME: handle child_size_change - what if something added while menu shown? - -> I guess the scroll arrows will just be added when that's the case - it's - kind of pointless to spend time implementing the logic for recalculating - all submenu positions and sizes when it's such a corner case */ static void -recalc_menu_size(ltk_menu *menu) { +recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget) { + ltk_menu *menu = (ltk_menu *)self; + /* If widget with size change is submenu, it doesn't affect this menu */ + if (widget && widget->vtable->type == LTK_MENU) { + ltk_widget_resize(widget); + return; + } struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; - menu->widget.ideal_w = menu->widget.ideal_h = t->pad + t->menu_border_width * 2; + struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme; + unsigned int old_w = menu->widget.ideal_w, old_h = menu->widget.ideal_h; + menu->widget.ideal_w = menu->widget.ideal_h = t->pad + t->border_width * 2; ltk_menuentry *e; - int text_w, text_h, bw; for (size_t i = 0; i < menu->num_entries; i++) { - e = &menu->entries[i]; - ltk_text_line_get_size(e->text, &text_w, &text_h); - bw = t->border_width * 2; - if (t->compress_borders && i != 0) - bw = t->border_width; + e = menu->entries[i]; if (menu->is_submenu) { - int extra_size = e->submenu ? t->arrow_pad * 2 + t->arrow_size : 0; - menu->widget.ideal_w = - MAX(text_w + extra_size + (t->pad + t->text_pad + t->border_width + t->menu_border_width) * 2, (int)menu->widget.ideal_w); - menu->widget.ideal_h += MAX(text_h + t->text_pad * 2, extra_size) + bw + t->pad; + menu->widget.ideal_w = MAX((t->pad + t->border_width) * 2 + (int)e->widget.ideal_w, (int)menu->widget.ideal_w); + menu->widget.ideal_h += e->widget.ideal_h + t->pad; + if (et->compress_borders && i != 0) + menu->widget.ideal_h -= et->border_width; } else { - menu->widget.ideal_h = - MAX(text_h + (t->pad + t->text_pad + t->border_width + t->menu_border_width) * 2, (int)menu->widget.ideal_h); - menu->widget.ideal_w += text_w + t->text_pad * 2 + bw + t->pad; + menu->widget.ideal_w += e->widget.ideal_w + t->pad; + if (et->compress_borders && i != 0) + menu->widget.ideal_w -= et->border_width; + menu->widget.ideal_h = MAX((t->pad + t->border_width) * 2 + (int)e->widget.ideal_h, (int)menu->widget.ideal_h); } } - if (menu->widget.parent && menu->widget.parent->vtable->child_size_change) { + if ((old_w != menu->widget.ideal_w || old_h != menu->widget.ideal_h) && + menu->widget.parent && menu->widget.parent->vtable->child_size_change) { menu->widget.parent->vtable->child_size_change(menu->widget.parent, (ltk_widget *)menu); + } else { + ltk_menu_resize(self); } menu->widget.dirty = 1; if (!menu->widget.hidden) ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); } +static void +ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry) { + int in_submenu = IN_SUBMENU(entry); + struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme; + int extra_size = (in_submenu && entry->submenu) ? t->arrow_pad * 2 + t->arrow_size : 0; + + int text_w, text_h; + ltk_text_line_get_size(entry->text_line, &text_w, &text_h); + entry->widget.ideal_w = text_w + extra_size + (t->text_pad + t->border_width) * 2; + entry->widget.ideal_h = MAX(text_h + t->text_pad * 2, extra_size) + t->border_width * 2; + /* FIXME: only call if something changed */ + if (entry->widget.parent && entry->widget.parent->vtable->child_size_change) { + entry->widget.parent->vtable->child_size_change(entry->widget.parent, (ltk_widget *)entry); + } +} + static ltk_menuentry * -ltk_menu_insert_entry(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, size_t idx, char **errstr) { - if (submenu && submenu->widget.parent) { - *errstr = "Submenu already part of other menu.\n"; - return NULL; +ltk_menuentry_create(ltk_window *window, const char *id, const char *text) { + ltk_menuentry *e = ltk_malloc(sizeof(ltk_menuentry)); + e->text_line = ltk_text_line_create(window->text_context, window->theme->font_size, (char *)text, 0, -1); + e->submenu = NULL; + int w, h; + ltk_text_line_get_size(e->text_line, &w, &h); + e->text_surface_key = ltk_surface_cache_get_unnamed_key(window->surface_cache, w, h); + ltk_fill_widget_defaults(&e->widget, id, window, &entry_vtable, 5, 5); + /* Note: This is only set as a dummy value! The actual ideal size can't + be set until it is part of a menu because it needs to know which + theme it should use */ + ltk_menuentry_recalc_ideal_size(e); + e->widget.dirty = 1; + return e; +} + +static void +ltk_menuentry_destroy(ltk_widget *self, int shallow) { + ltk_menuentry *e = (ltk_menuentry *)self; + /* FIXME: should be in widget destroy function */ + ltk_free(e->widget.id); + ltk_text_line_destroy(e->text_line); + ltk_surface_cache_release_key(e->text_surface_key); + /* FIXME: function to call when parent is destroyed */ + /* also function to call when parent added */ + /* also function to call when child destroyed */ + if (e->submenu) { + e->submenu->widget.parent = NULL; + if (!shallow) { + ltk_menu_destroy(&e->submenu->widget, shallow); + } } - ltk_menuentry *e = insert_entry(menu, idx); - if (!e) { - *errstr = "Unable to insert menu entry at given index.\n"; - return NULL; + ltk_free(e); +} + +static void +ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx, char **errstr) { + if (entry->widget.parent) { + *errstr = "Entry already part of other menu.\n"; + return; } - e->id = ltk_strdup(id); - ltk_window *w = menu->widget.window; - /* FIXME: pass const text */ - e->text = ltk_text_line_create(w->text_context, w->theme->font_size, (char *)text, 0, -1); - e->submenu = submenu; - if (submenu) - submenu->widget.parent = (ltk_widget *)menu; - e->disabled = 0; - recalc_menu_size(menu); + if (insert_entry(menu, entry, idx)) { + *errstr = "Illegal index.\n"; + return; + } + entry->widget.parent = &menu->widget; + ltk_menuentry_recalc_ideal_size(entry); + recalc_ideal_menu_size(&menu->widget, NULL); menu->widget.dirty = 1; - return e; } -static ltk_menuentry * -ltk_menu_add_entry(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, char **errstr) { - return ltk_menu_insert_entry(menu, id, text, submenu, menu->num_entries, errstr); +static void +ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry, char **errstr) { + ltk_menu_insert_entry(menu, entry, menu->num_entries, errstr); } /* FIXME: maybe allow any menu and just change is_submenu (also need to recalculate size then) */ -static ltk_menuentry * -ltk_menu_insert_submenu(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, size_t idx, char **errstr) { +static void +ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, char **errstr) { if (!submenu->is_submenu) { *errstr = "Not a submenu.\n"; - return NULL; + return; + } else if (e->submenu) { + *errstr = "Menu entry already has attached submenu.\n"; + return; } - return ltk_menu_insert_entry(menu, id, text, submenu, idx, errstr); + e->submenu = submenu; + ltk_menuentry_recalc_ideal_size(e); + e->widget.dirty = 1; + if (submenu) { + submenu->widget.hidden = 1; + submenu->widget.parent = &e->widget; + } + if (!e->widget.hidden) + ltk_window_invalidate_rect(e->widget.window, e->widget.rect); } -static ltk_menuentry * -ltk_menu_add_submenu(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, char **errstr) { - return ltk_menu_insert_submenu(menu, id, text, submenu, menu->num_entries, errstr); -} +/* FIXME: hide all entries when menu hidden? */ static void shrink_entries(ltk_menu *menu) { size_t new_alloc = ideal_array_size(menu->num_alloc, menu->num_entries); if (new_alloc != menu->num_alloc) { - menu->entries = ltk_reallocarray(menu->entries, new_alloc, sizeof(ltk_menuentry)); + menu->entries = ltk_reallocarray(menu->entries, new_alloc, sizeof(ltk_menuentry *)); menu->num_alloc = new_alloc; } } static int -ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, int shallow, char **errstr) { +ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, char **errstr) { if (idx >= menu->num_entries) { *errstr = "Invalid menu entry index.\n"; return 1; } - ltk_menuentry *e = &menu->entries[idx]; - ltk_free(e->id); - ltk_text_line_destroy(e->text); - if (e->submenu) { - e->submenu->widget.parent = NULL; - if (!shallow) - ltk_menu_destroy((ltk_widget *)e->submenu, shallow); - } + menu->entries[idx]->widget.parent = NULL; + ltk_menuentry_recalc_ideal_size(menu->entries[idx]); memmove( menu->entries + idx, menu->entries + idx + 1, - sizeof(ltk_menuentry) * (menu->num_entries - idx - 1) + sizeof(ltk_menuentry *) * (menu->num_entries - idx - 1) ); menu->num_entries--; shrink_entries(menu); - recalc_menu_size(menu); + recalc_ideal_menu_size(&menu->widget, NULL); return 0; } +static size_t +get_entry_with_id(ltk_menu *menu, const char *id) { + for (size_t i = 0; i < menu->num_entries; i++) { + if (!strcmp(id, menu->entries[i]->widget.id)) + return i; + } + return SIZE_MAX; +} + static int -ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, int shallow, char **errstr) { +ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, char **errstr) { size_t idx = get_entry_with_id(menu, id); if (idx >= menu->num_entries) { *errstr = "Invalid menu entry id.\n"; return 1; } - ltk_menu_remove_entry_index(menu, idx, shallow, errstr); + ltk_menu_remove_entry_index(menu, idx, errstr); return 0; } static int -ltk_menu_remove_all_entries(ltk_menu *menu, int shallow, char **errstr) { - (void)errstr; /* FIXME: why is errstr given at all? */ +ltk_menu_remove_child(ltk_widget *child, ltk_widget *self, char **errstr) { + ltk_menu *menu = (ltk_menu *)self; for (size_t i = 0; i < menu->num_entries; i++) { - ltk_menuentry *e = &menu->entries[i]; - ltk_free(e->id); - ltk_text_line_destroy(e->text); - if (e->submenu) { - e->submenu->widget.parent = NULL; - if (!shallow) - ltk_menu_destroy((ltk_widget *)e->submenu, shallow); + if (&menu->entries[i]->widget == child) { + return ltk_menu_remove_entry_index(menu, i, errstr); } } - menu->num_entries = menu->num_alloc = 0; - ltk_free(menu->entries); - menu->entries = NULL; - recalc_menu_size(menu); - return 0; + *errstr = "Widget not contained in menu.\n"; + return 1; } -/* FIXME: how to get rid of duplicate IDs? */ - -static size_t -get_entry_with_id(ltk_menu *menu, const char *id) { +static void +ltk_menu_remove_all_entries(ltk_menu *menu) { for (size_t i = 0; i < menu->num_entries; i++) { - if (!strcmp(id, menu->entries[i].id)) - return i; + menu->entries[i]->widget.parent = NULL; + ltk_menuentry_recalc_ideal_size(menu->entries[i]); } - return SIZE_MAX; + menu->num_entries = menu->num_alloc = 0; + ltk_free(menu->entries); + menu->entries = NULL; + recalc_ideal_menu_size(&menu->widget, NULL); } /* FIXME: unregister from window popups? */ -static int -ltk_menu_detach_submenu_from_entry_id(ltk_menu *menu, const char *id, char **errstr) { - size_t idx = get_entry_with_id(menu, id); - if (idx >= menu->num_entries) { - *errstr = "Invalid menu entry id.\n"; - return 1; - } - /* FIXME: error if submenu already NULL? */ - menu->entries[idx].submenu = NULL; - recalc_menu_size(menu); - return 0; -} - -static int -ltk_menu_detach_submenu_from_entry_index(ltk_menu *menu, size_t idx, char **errstr) { - if (idx >= menu->num_entries) { - *errstr = "Invalid menu entry index.\n"; - return 1; - } - menu->entries[idx].submenu = NULL; - recalc_menu_size(menu); - return 0; -} - -static int -ltk_menu_disable_entry_index(ltk_menu *menu, size_t idx, char **errstr) { - if (idx >= menu->num_entries) { - *errstr = "Invalid menu entry index.\n"; - return 1; - } - menu->entries[idx].disabled = 1; - menu->widget.dirty = 1; - if (!menu->widget.hidden) - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); - return 0; -} - -static int -ltk_menu_disable_entry_id(ltk_menu *menu, const char *id, char **errstr) { - size_t idx = get_entry_with_id(menu, id); - if (idx >= menu->num_entries) { - *errstr = "Invalid menu entry id.\n"; - return 1; - } - menu->entries[idx].disabled = 1; - menu->widget.dirty = 1; - if (!menu->widget.hidden) - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); - return 0; -} - -static int -ltk_menu_disable_all_entries(ltk_menu *menu, char **errstr) { - (void)errstr; - for (size_t i = 0; i < menu->num_entries; i++) { - menu->entries[i].disabled = 1; - } - menu->widget.dirty = 1; - if (!menu->widget.hidden) - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); - return 0; -} - -static int -ltk_menu_enable_entry_index(ltk_menu *menu, size_t idx, char **errstr) { - if (idx >= menu->num_entries) { - *errstr = "Invalid menu entry index.\n"; - return 1; - } - menu->entries[idx].disabled = 0; - menu->widget.dirty = 1; - if (!menu->widget.hidden) - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); - return 0; -} - -static int -ltk_menu_enable_entry_id(ltk_menu *menu, const char *id, char **errstr) { - size_t idx = get_entry_with_id(menu, id); - if (idx >= menu->num_entries) { - *errstr = "Invalid menu entry id.\n"; - return 1; - } - menu->entries[idx].disabled = 0; - menu->widget.dirty = 1; - if (!menu->widget.hidden) - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); - return 0; -} - -static int -ltk_menu_enable_all_entries(ltk_menu *menu, char **errstr) { - (void)errstr; - for (size_t i = 0; i < menu->num_entries; i++) { - menu->entries[i].disabled = 0; - } - menu->widget.dirty = 1; - if (!menu->widget.hidden) - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); - return 0; +static void +ltk_menuentry_detach_submenu(ltk_menuentry *e) { + if (e->submenu) + e->submenu->widget.parent = NULL; + e->submenu = NULL; + ltk_menuentry_recalc_ideal_size(e); } static void ltk_menu_destroy(ltk_widget *self, int shallow) { ltk_menu *menu = (ltk_menu *)self; - char *errstr; - if (self->parent && self->parent->vtable->remove_child) { - self->parent->vtable->remove_child( - self->window, self, self->parent, &errstr - ); - } if (!menu) { ltk_warn("Tried to destroy NULL menu.\n"); return; } - /* FIXME: this should be generic part of widget */ - ltk_surface_cache_release_key(self->surface_key); if (menu->scroll_timer_id >= 0) ltk_unregister_timer(menu->scroll_timer_id); - ltk_menu_remove_all_entries(menu, shallow, NULL); + if (!shallow) { + for (size_t i = 0; i < menu->num_entries; i++) { + ltk_menuentry_destroy(&menu->entries[i]->widget, shallow); + } + } + ltk_menu_remove_all_entries(menu); ltk_window_unregister_popup(self->window, self); /* FIXME: what to do on error here? */ /* FIXME: maybe unregister popup in ltk_remove_widget? */ @@ -1194,7 +1128,7 @@ ltk_menu_cmd_create( return 0; } -/* menu <menu id> insert-entry <entry id> <entry text> <index> */ +/* menu <menu id> insert-entry <entry widget id> <index> */ static int ltk_menu_cmd_insert_entry( ltk_window *window, @@ -1203,228 +1137,46 @@ ltk_menu_cmd_insert_entry( char **errstr) { (void)window; ltk_menu *menu; + ltk_menuentry *e; const char *errstr_num; - if (num_tokens != 6) { - *errstr = "Invalid number of arguments.\n"; - return 1; - } - /* FIXME: actually use this errstr */ - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { - *errstr = "Invalid widget ID.\n"; - return 1; - } - size_t idx = (size_t)ltk_strtonum(tokens[5], 0, (long long)menu->num_entries, &errstr_num); - if (errstr_num) { - *errstr = "Invalid index.\n"; - return 1; - } - if (!ltk_menu_insert_entry(menu, tokens[3], tokens[4], NULL, idx, errstr)) - return 1; - - return 0; -} - -/* menu <menu id> add-entry <entry id> <entry text> */ -static int -ltk_menu_cmd_add_entry( - ltk_window *window, - char **tokens, - size_t num_tokens, - char **errstr) { - (void)window; - ltk_menu *menu; if (num_tokens != 5) { *errstr = "Invalid number of arguments.\n"; return 1; } + /* FIXME: actually use this errstr */ menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); if (!menu) { *errstr = "Invalid widget ID.\n"; return 1; } - if (!ltk_menu_add_entry(menu, tokens[3], tokens[4], NULL, errstr)) - return 1; - - return 0; -} - -/* menu <menu id> insert-submenu <entry id> <entry text> <submenu id> <index> */ -static int -ltk_menu_cmd_insert_submenu( - ltk_window *window, - char **tokens, - size_t num_tokens, - char **errstr) { - (void)window; - ltk_menu *menu, *submenu; - const char *errstr_num; - if (num_tokens != 7) { - *errstr = "Invalid number of arguments.\n"; - return 1; - } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - submenu = (ltk_menu *)ltk_get_widget(tokens[5], LTK_MENU, errstr); - if (!menu || !submenu) { - *errstr = "Invalid widget ID.\n"; - return 1; - } - size_t idx = (size_t)ltk_strtonum(tokens[6], 0, (long long)menu->num_entries, &errstr_num); - if (errstr_num) { - *errstr = "Invalid index.\n"; - return 1; - } - if (!ltk_menu_insert_submenu(menu, tokens[3], tokens[4], submenu, idx, errstr)) - return 1; - - return 0; -} - -/* menu <menu id> add-submenu <entry id> <entry text> <submenu id> */ -static int -ltk_menu_cmd_add_submenu( - ltk_window *window, - char **tokens, - size_t num_tokens, - char **errstr) { - (void)window; - ltk_menu *menu, *submenu; - if (num_tokens != 6) { - *errstr = "Invalid number of arguments.\n"; - return 1; - } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - submenu = (ltk_menu *)ltk_get_widget(tokens[5], LTK_MENU, errstr); - if (!menu || !submenu) { - *errstr = "Invalid widget ID.\n"; - return 1; - } - if (!ltk_menu_add_submenu(menu, tokens[3], tokens[4], submenu, errstr)) - return 1; - - return 0; -} - -/* menu <menu id> remove-entry-index <entry index> [shallow|deep] */ -static int -ltk_menu_cmd_remove_entry_index( - ltk_window *window, - char **tokens, - size_t num_tokens, - char **errstr) { - (void)window; - ltk_menu *menu; - const char *errstr_num; - if (num_tokens != 4 && num_tokens != 5) { - *errstr = "Invalid number of arguments.\n"; - return 1; - } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { + e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, errstr); + if (!e) { *errstr = "Invalid widget ID.\n"; return 1; } - size_t idx = (size_t)ltk_strtonum(tokens[3], 0, (long long)menu->num_entries, &errstr_num); + size_t idx = (size_t)ltk_strtonum(tokens[5], 0, (long long)menu->num_entries, &errstr_num); if (errstr_num) { *errstr = "Invalid index.\n"; return 1; } - int shallow = 1; - if (num_tokens == 5) { - if (!strcmp(tokens[4], "shallow")) { - /* NOP */ - } else if (!strcmp(tokens[4], "deep")) { - shallow = 0; - } else { - *errstr = "Invalid shallow specifier.\n"; - return 1; - } - } - if (!ltk_menu_remove_entry_index(menu, idx, shallow, errstr)) + *errstr = NULL; + ltk_menu_insert_entry(menu, e, idx, errstr); + if (*errstr) return 1; return 0; } -/* menu <menu id> remove-entry-id <entry id> [shallow|deep] */ +/* menu <menu id> add-entry <entry widget id> */ static int -ltk_menu_cmd_remove_entry_id( - ltk_window *window, - char **tokens, - size_t num_tokens, - char **errstr) { - (void)window; - ltk_menu *menu; - if (num_tokens != 4 && num_tokens != 5) { - *errstr = "Invalid number of arguments.\n"; - return 1; - } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { - *errstr = "Invalid widget ID.\n"; - return 1; - } - int shallow = 1; - if (num_tokens == 5) { - if (!strcmp(tokens[4], "shallow")) { - /* NOP */ - } else if (!strcmp(tokens[4], "deep")) { - shallow = 0; - } else { - *errstr = "Invalid shallow specifier.\n"; - return 1; - } - } - if (!ltk_menu_remove_entry_id(menu, tokens[3], shallow, errstr)) - return 1; - - return 0; -} - -/* menu <menu id> remove-all-entries [shallow|deep] */ -static int -ltk_menu_cmd_remove_all_entries( - ltk_window *window, - char **tokens, - size_t num_tokens, - char **errstr) { - (void)window; - ltk_menu *menu; - if (num_tokens != 3 && num_tokens != 4) { - *errstr = "Invalid number of arguments.\n"; - return 1; - } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { - *errstr = "Invalid widget ID.\n"; - return 1; - } - int shallow = 1; - if (num_tokens == 4) { - if (!strcmp(tokens[3], "shallow")) { - /* NOP */ - } else if (!strcmp(tokens[3], "deep")) { - shallow = 0; - } else { - *errstr = "Invalid shallow specifier.\n"; - return 1; - } - } - if (!ltk_menu_remove_all_entries(menu, shallow, errstr)) - return 1; - - return 0; -} - -/* menu <menu id> detach-submenu-from-entry-id <entry id> */ -static int -ltk_menu_cmd_detach_submenu_from_entry_id( +ltk_menu_cmd_add_entry( ltk_window *window, char **tokens, size_t num_tokens, char **errstr) { (void)window; ltk_menu *menu; + ltk_menuentry *e; if (num_tokens != 4) { *errstr = "Invalid number of arguments.\n"; return 1; @@ -1434,47 +1186,22 @@ ltk_menu_cmd_detach_submenu_from_entry_id( *errstr = "Invalid widget ID.\n"; return 1; } - - if (!ltk_menu_detach_submenu_from_entry_id(menu, tokens[3], errstr)) - return 1; - - return 0; -} - -/* menu <menu id> detach-submenu-from-entry-index <entry index> */ -static int -ltk_menu_cmd_detach_submenu_from_entry_index( - ltk_window *window, - char **tokens, - size_t num_tokens, - char **errstr) { - (void)window; - ltk_menu *menu; - const char *errstr_num; - if (num_tokens != 4) { - *errstr = "Invalid number of arguments.\n"; - return 1; - } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { + e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, errstr); + if (!e) { *errstr = "Invalid widget ID.\n"; return 1; } - size_t idx = (size_t)ltk_strtonum(tokens[3], 0, (long long)menu->num_entries, &errstr_num); - if (errstr_num) { - *errstr = "Invalid index.\n"; - return 1; - } - - if (!ltk_menu_detach_submenu_from_entry_index(menu, idx, errstr)) + *errstr = NULL; + ltk_menu_add_entry(menu, e, errstr); + if (*errstr) return 1; return 0; } -/* menu <menu id> enable-entry-index <entry index> */ +/* menu <menu id> remove-entry-index <entry index> */ static int -ltk_menu_cmd_enable_entry_index( +ltk_menu_cmd_remove_entry_index( ltk_window *window, char **tokens, size_t num_tokens, @@ -1496,16 +1223,15 @@ ltk_menu_cmd_enable_entry_index( *errstr = "Invalid index.\n"; return 1; } - - if (!ltk_menu_enable_entry_index(menu, idx, errstr)) + if (!ltk_menu_remove_entry_index(menu, idx, errstr)) return 1; return 0; } -/* menu <menu id> enable-entry-id <entry id> */ +/* menu <menu id> remove-entry-id <entry id> */ static int -ltk_menu_cmd_enable_entry_id( +ltk_menu_cmd_remove_entry_id( ltk_window *window, char **tokens, size_t num_tokens, @@ -1521,16 +1247,15 @@ ltk_menu_cmd_enable_entry_id( *errstr = "Invalid widget ID.\n"; return 1; } - - if (!ltk_menu_enable_entry_id(menu, tokens[3], errstr)) + if (!ltk_menu_remove_entry_id(menu, tokens[3], errstr)) return 1; return 0; } -/* menu <menu id> enable-all-entries */ +/* menu <menu id> remove-all-entries */ static int -ltk_menu_cmd_enable_all_entries( +ltk_menu_cmd_remove_all_entries( ltk_window *window, char **tokens, size_t num_tokens, @@ -1546,97 +1271,94 @@ ltk_menu_cmd_enable_all_entries( *errstr = "Invalid widget ID.\n"; return 1; } - - if (!ltk_menu_enable_all_entries(menu, errstr)) - return 1; + ltk_menu_remove_all_entries(menu); return 0; } -/* menu <menu id> disable-entry-index <entry index> */ +/* menuentry <id> create <text> */ static int -ltk_menu_cmd_disable_entry_index( +ltk_menuentry_cmd_create( ltk_window *window, char **tokens, size_t num_tokens, char **errstr) { - (void)window; - ltk_menu *menu; - const char *errstr_num; + ltk_menuentry *e; if (num_tokens != 4) { *errstr = "Invalid number of arguments.\n"; return 1; } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { - *errstr = "Invalid widget ID.\n"; - return 1; - } - size_t idx = (size_t)ltk_strtonum(tokens[3], 0, (long long)menu->num_entries, &errstr_num); - if (errstr_num) { - *errstr = "Invalid index.\n"; + if (!ltk_widget_id_free(tokens[1])) { + *errstr = "Widget ID already taken.\n"; return 1; } - - if (!ltk_menu_disable_entry_index(menu, idx, errstr)) - return 1; + e = ltk_menuentry_create(window, tokens[1], tokens[3]); + ltk_set_widget((ltk_widget *)e, tokens[1]); return 0; } -/* menu <menu id> disable-entry-id <entry id> */ +/* menuentry <menuentry id> attach-submenu <submenu id> */ static int -ltk_menu_cmd_disable_entry_id( +ltk_menuentry_cmd_attach_submenu( ltk_window *window, char **tokens, size_t num_tokens, char **errstr) { (void)window; - ltk_menu *menu; + ltk_menuentry *e; + ltk_menu *submenu; if (num_tokens != 4) { *errstr = "Invalid number of arguments.\n"; return 1; } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { - *errstr = "Invalid widget ID.\n"; + e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, errstr); + if (!e) { + *errstr = "Invalid entry widget ID.\n"; + return 1; + } + submenu = (ltk_menu *)ltk_get_widget(tokens[3], LTK_MENU, errstr); + if (!submenu) { + *errstr = "Invalid submenu widget ID.\n"; return 1; } - if (!ltk_menu_disable_entry_id(menu, tokens[3], errstr)) + *errstr = NULL; + ltk_menuentry_attach_submenu(e, submenu, errstr); + if (*errstr) return 1; return 0; } -/* menu <menu id> disable-all-entries */ +/* menuentry <menuentry id> detach-submenu */ static int -ltk_menu_cmd_disable_all_entries( +ltk_menuentry_cmd_detach_submenu( ltk_window *window, char **tokens, size_t num_tokens, char **errstr) { (void)window; - ltk_menu *menu; + ltk_menuentry *e; if (num_tokens != 3) { *errstr = "Invalid number of arguments.\n"; return 1; } - menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr); - if (!menu) { + e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, errstr); + if (!e) { *errstr = "Invalid widget ID.\n"; return 1; } - if (!ltk_menu_disable_all_entries(menu, errstr)) - return 1; + ltk_menuentry_detach_submenu(e); return 0; } +/* FIXME: sort out menu/submenu - it's weird right now */ /* FIXME: binary search for command handler */ /* FIXME: distinguish between menu/submenu in commands other than create? */ -/* menu <menu id> <command> ... */ +/* [sub]menu <menu id> <command> ... */ int ltk_menu_cmd( ltk_window *window, @@ -1653,32 +1375,37 @@ ltk_menu_cmd( return ltk_menu_cmd_insert_entry(window, tokens, num_tokens, errstr); } else if (strcmp(tokens[2], "add-entry") == 0) { return ltk_menu_cmd_add_entry(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "insert-submenu") == 0) { - return ltk_menu_cmd_insert_submenu(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "add-submenu") == 0) { - return ltk_menu_cmd_add_submenu(window, tokens, num_tokens, errstr); } else if (strcmp(tokens[2], "remove-entry-index") == 0) { return ltk_menu_cmd_remove_entry_index(window, tokens, num_tokens, errstr); } else if (strcmp(tokens[2], "remove-entry-id") == 0) { return ltk_menu_cmd_remove_entry_id(window, tokens, num_tokens, errstr); } else if (strcmp(tokens[2], "remove-all-entries") == 0) { return ltk_menu_cmd_remove_all_entries(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "detach-submenu-from-entry-id") == 0) { - return ltk_menu_cmd_detach_submenu_from_entry_id(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "detach-submenu-from-entry-index") == 0) { - return ltk_menu_cmd_detach_submenu_from_entry_index(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "disable-entry-index") == 0) { - return ltk_menu_cmd_disable_entry_index(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "disable-entry-id") == 0) { - return ltk_menu_cmd_disable_entry_id(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "disable-all-entries") == 0) { - return ltk_menu_cmd_disable_all_entries(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "enable-entry-index") == 0) { - return ltk_menu_cmd_enable_entry_index(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "enable-entry-id") == 0) { - return ltk_menu_cmd_enable_entry_id(window, tokens, num_tokens, errstr); - } else if (strcmp(tokens[2], "enable-all-entries") == 0) { - return ltk_menu_cmd_enable_all_entries(window, tokens, num_tokens, errstr); + } else { + *errstr = "Invalid command.\n"; + return 1; + } + + return 0; +} + +/* menuentry <menuentry id> <command> ... */ +int +ltk_menuentry_cmd( + ltk_window *window, + char **tokens, + size_t num_tokens, + char **errstr) { + if (num_tokens < 3) { + *errstr = "Invalid number of arguments.\n"; + return 1; + } + if (strcmp(tokens[2], "create") == 0) { + return ltk_menuentry_cmd_create(window, tokens, num_tokens, errstr); + } else if (strcmp(tokens[2], "attach-submenu") == 0) { + return ltk_menuentry_cmd_attach_submenu(window, tokens, num_tokens, errstr); + } else if (strcmp(tokens[2], "detach-submenu") == 0) { + return ltk_menuentry_cmd_detach_submenu(window, tokens, num_tokens, errstr); } else { *errstr = "Invalid command.\n"; return 1; diff --git a/src/menu.h b/src/menu.h @@ -21,33 +21,37 @@ #include "text.h" #include "widget.h" -/* TODO: implement scrolling */ - typedef struct ltk_menuentry ltk_menuentry; typedef struct { ltk_widget widget; - ltk_menuentry *entries; + ltk_menuentry **entries; size_t num_entries; size_t num_alloc; - size_t pressed_entry; - size_t active_entry; double x_scroll_offset; double y_scroll_offset; int scroll_timer_id; char is_submenu; char was_opened_left; + /* FIXME: better names */ + char popup_submenus; + char unpopup_submenus_on_hide; char scroll_top_hover; char scroll_bottom_hover; char scroll_left_hover; char scroll_right_hover; } ltk_menu; +/* FIXME: maybe need to set entire widget hierarchy to hover state so menu entry + is also hover when nested widget is hover? */ + struct ltk_menuentry { - char *id; - ltk_text_line *text; + ltk_widget widget; + /* FIXME: I guess if the regular label got the ability to + change its color, a label could just be used instead of this */ + ltk_text_line *text_line; + ltk_surface_cache_key *text_surface_key; ltk_menu *submenu; - int disabled; }; int ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value); @@ -57,6 +61,13 @@ int ltk_submenu_ini_handler(ltk_window *window, const char *prop, const char *va int ltk_submenu_fill_theme_defaults(ltk_window *window); void ltk_submenu_uninitialize_theme(ltk_window *window); +int ltk_menuentry_ini_handler(ltk_window *window, const char *prop, const char *value); +int ltk_menuentry_fill_theme_defaults(ltk_window *window); +void ltk_menuentry_uninitialize_theme(ltk_window *window); +int ltk_submenuentry_ini_handler(ltk_window *window, const char *prop, const char *value); +int ltk_submenuentry_fill_theme_defaults(ltk_window *window); +void ltk_submenuentry_uninitialize_theme(ltk_window *window); + int ltk_menu_cmd( ltk_window *window, char **tokens, @@ -64,4 +75,11 @@ int ltk_menu_cmd( char **errstr ); +int ltk_menuentry_cmd( + ltk_window *window, + char **tokens, + size_t num_tokens, + char **errstr +); + #endif /* _LTK_MENU_H_ */ diff --git a/src/rect.h b/src/rect.h @@ -14,8 +14,13 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef _LTK_RECT_H_ -#define _LTK_RECT_H_ +#ifndef LTK_RECT_H +#define LTK_RECT_H + +/* FIXME: X only supports 16-bit numbers */ +typedef struct { + int x, y; +} ltk_point; typedef struct { int x; @@ -29,4 +34,4 @@ ltk_rect ltk_rect_relative(ltk_rect parent, ltk_rect child); ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2); int ltk_collide_rect(ltk_rect rect, int x, int y); -#endif /* _LTK_RECT_H_ */ +#endif /* LTK_RECT_H */ diff --git a/src/scrollbar.c b/src/scrollbar.c @@ -53,7 +53,8 @@ static struct ltk_widget_vtable vtable = { .child_size_change = NULL, .remove_child = NULL, .type = LTK_UNKNOWN, /* FIXME */ - .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE, + /* FIXME: need different activatable state so arrow keys don't move onto scrollbar */ + .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE | LTK_ACTIVATABLE_ALWAYS, }; static struct { @@ -131,27 +132,19 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) { ltk_scrollbar *scrollbar = (ltk_scrollbar *)self; ltk_color *bg = NULL, *fg = NULL; ltk_rect rect = scrollbar->widget.rect; - switch (scrollbar->widget.state) { /* FIXME: proper theme for hover */ - case LTK_NORMAL: - bg = &theme.bg_normal; - fg = &theme.fg_normal; - break; - case LTK_PRESSED: + if (self->state & LTK_DISABLED) { + bg = &theme.bg_disabled; + fg = &theme.fg_disabled; + } else if (self->state & LTK_PRESSED) { bg = &theme.bg_normal; fg = &theme.fg_pressed; - break; - case LTK_HOVER: - case LTK_ACTIVE: + } else if (self->state & LTK_HOVERACTIVE) { bg = &theme.bg_normal; fg = &theme.fg_active; - break; - case LTK_DISABLED: - bg = &theme.bg_disabled; - fg = &theme.fg_disabled; - break; - default: - ltk_fatal("No style found for current scrollbar state.\n"); + } else { + bg = &theme.bg_normal; + fg = &theme.fg_normal; } ltk_surface *s; ltk_surface_cache_get_surface(self->surface_key, &s); @@ -221,7 +214,7 @@ static int ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) { ltk_scrollbar *sc = (ltk_scrollbar *)self; int delta; - if (self->state != LTK_PRESSED) { + if (!(self->state & LTK_PRESSED)) { return 1; } if (sc->orient == LTK_HORIZONTAL) @@ -256,12 +249,6 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback static void ltk_scrollbar_destroy(ltk_widget *self, int shallow) { (void)shallow; - char *errstr; - if (self->parent && self->parent->vtable->remove_child) { - self->parent->vtable->remove_child( - self->window, self, self->parent, &errstr - ); - } ltk_surface_cache_release_key(self->surface_key); ltk_free(self); } diff --git a/src/widget.c b/src/widget.c @@ -1,10 +1,5 @@ /* FIXME: store coordinates relative to parent widget */ /* FIXME: Destroy function for widget to destroy pixmap! */ -/* FIXME/NOTE: maybe it would be better to do some sort of - inheritance where the generic widget destroy function is - called before the specific function for each widget type - so each widget doesn't have to manually remove itself from - its parent */ /* * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org> * @@ -47,11 +42,12 @@ ltk_destroy_widget_hash(void) { hash_locked = 1; khint_t k; ltk_widget *ptr; + char *errstr; for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) { if (kh_exist(widget_hash, k)) { ptr = kh_value(widget_hash, k); ltk_free((char *)kh_key(widget_hash, k)); - ptr->vtable->destroy(ptr, 1); + ltk_widget_destroy(ptr, 1, &errstr); } } kh_destroy(widget, widget_hash); @@ -105,6 +101,47 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window, widget->hidden = 0; } +void +ltk_widget_hide(ltk_widget *widget) { + if (widget->vtable->hide) + widget->vtable->hide(widget); + widget->hidden = 1; + /* remove hover state */ + /* FIXME: this needs to call change_state but that might cause issues */ + ltk_widget *hover = widget->window->hover_widget; + while (hover) { + if (hover == widget) { + widget->window->hover_widget->state &= ~LTK_HOVER; + widget->window->hover_widget = NULL; + break; + } + hover = hover->parent; + } + ltk_widget *pressed = widget->window->pressed_widget; + while (pressed) { + if (pressed == widget) { + widget->window->pressed_widget->state &= ~LTK_PRESSED; + widget->window->pressed_widget = NULL; + break; + } + pressed = pressed->parent; + } + ltk_widget *active = widget->window->active_widget; + /* if current active widget is child, set active widget to widget above in hierarchy */ + int set_next = 0; + while (active) { + if (active == widget) { + set_next = 1; + } else if (set_next && (active->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { + ltk_window_set_active_widget(active->window, active); + break; + } + active = active->parent; + } + if (set_next && !active) + ltk_window_set_active_widget(active->window, NULL); +} + /* FIXME: Maybe pass the new width as arg here? That would make a bit more sense */ void @@ -112,18 +149,20 @@ ltk_widget_resize(ltk_widget *widget) { /* FIXME: should surface maybe be resized first? */ if (widget->vtable->resize) widget->vtable->resize(widget); - if (!widget->vtable->flags & LTK_NEEDS_SURFACE) + if (!(widget->vtable->flags & LTK_NEEDS_SURFACE)) return; ltk_surface_cache_request_surface_size(widget->surface_key, widget->rect.w, widget->rect.h); widget->dirty = 1; } void -ltk_widget_change_state(ltk_widget *widget) { +ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) { if (widget->vtable->change_state) - widget->vtable->change_state(widget); - if (widget->vtable->flags & LTK_NEEDS_REDRAW) + widget->vtable->change_state(widget, old_state); + if (widget->vtable->flags & LTK_NEEDS_REDRAW) { + widget->dirty = 1; ltk_window_invalidate_rect(widget->window, widget->rect); + } } static ltk_widget * @@ -148,25 +187,51 @@ get_hover_popup(ltk_window *window, int x, int y) { return NULL; } +static int +is_parent(ltk_widget *parent, ltk_widget *child) { + while (child && child != parent) { + child = child->parent; + } + return child != NULL; +} + /* FIXME: This is still weird. */ void ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { ltk_widget *widget = get_hover_popup(window, event->x, event->y); + int check_hide = 0; if (!widget) { - ltk_window_unregister_all_popups(window); widget = window->root_widget; + check_hide = 1; } - if (!widget) + if (!widget) { + ltk_window_unregister_all_popups(window); return; + } ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y); + /* FIXME: need to add more flags for more fine-grained control + -> also, should the widget still get mouse_press even if state doesn't change? */ + if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { + ltk_window_unregister_all_popups(window); + } + + /* FIXME: this doesn't make much sense if the popups aren't a + hierarchy (right now, they're just menus, so that's always + a hierarchy */ + /* don't hide popups if they are children of the now pressed widget */ + if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0]))) + ltk_window_unregister_all_popups(window); + int first = 1; while (cur_widget) { int handled = 0; if (cur_widget->state != LTK_DISABLED) { + /* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled) + get mouse press, but they are only set to pressed if they are activatable */ if (cur_widget->vtable->mouse_press) handled = cur_widget->vtable->mouse_press(cur_widget, event); /* set first non-disabled widget to pressed widget */ - if (first && event->button == LTK_BUTTONL) { + if (first && event->button == LTK_BUTTONL && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { ltk_window_set_pressed_widget(window, cur_widget); first = 0; } @@ -179,17 +244,26 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { } void +ltk_window_fake_motion_event(ltk_window *window, int x, int y) { + ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y}; + ltk_window_motion_notify_event(window, &e); +} + +void ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) { - ltk_widget *widget = get_hover_popup(window, event->x, event->y); - if (!widget) - widget = window->pressed_widget; + ltk_widget *widget = window->pressed_widget; + if (!widget) { + widget = get_hover_popup(window, event->x, event->y); + widget = get_widget_under_pointer(widget, event->x, event->y); + } + /* FIXME: loop up to top of hierarchy if not handled */ if (widget && widget->vtable->mouse_release) widget->vtable->mouse_release(widget, event); if (event->button == LTK_BUTTONL) { ltk_window_set_pressed_widget(window, NULL); /* send motion notify to widget under pointer */ - ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = event->x, .y = event->y}; - ltk_window_motion_notify_event(window, &e); + /* FIXME: only when not collide with rect */ + ltk_window_fake_motion_event(window, event->x, event->y); } } @@ -221,7 +295,7 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) { /* set first non-disabled widget to hover widget */ /* FIXME: should enter/leave event be sent to parent when moving from/to widget nested in parent? */ - if (first) { + if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { ltk_window_set_hover_widget(window, cur_widget, event); first = 0; } @@ -231,6 +305,8 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) { else break; } + if (first) + ltk_window_set_hover_widget(window, NULL, event); } int @@ -287,10 +363,9 @@ ltk_widget_destroy(ltk_widget *widget, int shallow, char **errstr) { /* widget->parent->remove_child should never be NULL because of the fact that the widget is set as parent, but let's just check anyways... */ int err = 0; - /* FIXME: why is window passed here? */ if (widget->parent && widget->parent->vtable->remove_child) { err = widget->parent->vtable->remove_child( - widget->window, widget, widget->parent, errstr + widget, widget->parent, errstr ); } widget->vtable->destroy(widget, shallow); diff --git a/src/widget.h b/src/widget.h @@ -14,8 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef _LTK_WIDGET_H_ -#define _LTK_WIDGET_H_ +#ifndef LTK_WIDGET_H +#define LTK_WIDGET_H #include "rect.h" #include "event.h" @@ -31,6 +31,7 @@ typedef enum { LTK_GRABS_INPUT = 4, LTK_NEEDS_REDRAW = 8, LTK_NEEDS_SURFACE = 16, /* FIXME: let widgets handle this themselves */ + LTK_HOVER_IS_ACTIVE = 32, } ltk_widget_flags; typedef enum { @@ -46,11 +47,13 @@ typedef enum { } ltk_orientation; typedef enum { - LTK_NORMAL, - LTK_HOVER, - LTK_PRESSED, - LTK_ACTIVE, - LTK_DISABLED + /* FIXME: maybe remove normal or set it to 0 */ + LTK_NORMAL = 1, + LTK_HOVER = 2, + LTK_PRESSED = 4, + LTK_ACTIVE = 8, + LTK_HOVERACTIVE = 2 | 8, + LTK_DISABLED = 16, } ltk_widget_state; typedef enum { @@ -63,6 +66,7 @@ typedef enum { LTK_WIDGET, LTK_BOX, LTK_MENU, + LTK_MENUENTRY, LTK_NUM_WIDGETS } ltk_widget_type; @@ -97,19 +101,19 @@ struct ltk_widget { /* FIXME: just give the structs for the actual event type here instead of the generic ltk_event */ struct ltk_widget_vtable { - void (*key_press) (struct ltk_widget *, ltk_event *); - void (*key_release) (struct ltk_widget *, ltk_event *); - int (*mouse_press) (struct ltk_widget *, ltk_button_event *); - int (*mouse_release) (struct ltk_widget *, ltk_button_event *); - int (*motion_notify) (struct ltk_widget *, ltk_motion_event *); - int (*mouse_leave) (struct ltk_widget *, ltk_motion_event *); - int (*mouse_enter) (struct ltk_widget *, ltk_motion_event *); - - void (*resize) (struct ltk_widget *); - void (*hide) (struct ltk_widget *); - void (*draw) (struct ltk_widget *, ltk_rect); - void (*change_state) (struct ltk_widget *); - void (*destroy) (struct ltk_widget *, int); + void (*key_press)(struct ltk_widget *, ltk_event *); + void (*key_release)(struct ltk_widget *, ltk_event *); + int (*mouse_press)(struct ltk_widget *, ltk_button_event *); + int (*mouse_release)(struct ltk_widget *, ltk_button_event *); + int (*motion_notify)(struct ltk_widget *, ltk_motion_event *); + int (*mouse_leave)(struct ltk_widget *, ltk_motion_event *); + int (*mouse_enter)(struct ltk_widget *, ltk_motion_event *); + + void (*resize)(struct ltk_widget *); + void (*hide)(struct ltk_widget *); + void (*draw)(struct ltk_widget *, ltk_rect); + void (*change_state)(struct ltk_widget *, ltk_widget_state); + void (*destroy)(struct ltk_widget *, int); struct ltk_widget *(*nearest_child)(struct ltk_widget *self, ltk_rect rect); struct ltk_widget *(*nearest_child_left)(struct ltk_widget *self, ltk_rect rect); @@ -120,33 +124,25 @@ struct ltk_widget_vtable { struct ltk_widget *(*prev_child)(struct ltk_widget *self, ltk_widget *child); struct ltk_widget *(*first_child)(struct ltk_widget *self); - /* FIXME: make menu entries actual widgets so this isn't needed anymore */ - int (*move_active_left)(struct ltk_widget *self, ltk_rect *rect_ret); - int (*move_active_right)(struct ltk_widget *self, ltk_rect *rect_ret); - int (*move_active_above)(struct ltk_widget *self, ltk_rect *rect_ret); - int (*move_active_below)(struct ltk_widget *self, ltk_rect *rect_ret); - int (*move_active_next)(struct ltk_widget *self, ltk_rect *rect_ret); - int (*move_active_prev)(struct ltk_widget *self, ltk_rect *rect_ret); - int (*move_active_first)(struct ltk_widget *self, ltk_rect *rect_ret); - void (*child_size_change) (struct ltk_widget *, struct ltk_widget *); - /* FIXME: why does this take window? */ - int (*remove_child) (struct ltk_window *, struct ltk_widget *, struct ltk_widget *, char **); + int (*remove_child)(struct ltk_widget *, struct ltk_widget *, char **); struct ltk_widget *(*get_child_at_pos)(struct ltk_widget *, int x, int y); ltk_widget_type type; ltk_widget_flags flags; }; +void ltk_widget_hide(ltk_widget *widget); int ltk_widget_destroy(ltk_widget *widget, int shallow, char **errstr); int ltk_widget_destroy_cmd(struct ltk_window *window, char **tokens, size_t num_tokens, char **errstr); void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, struct ltk_window *window, struct ltk_widget_vtable *vtable, int w, int h); -void ltk_widget_change_state(ltk_widget *widget); +void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state); /* FIXME: move to separate window.h */ void ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event); void ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event); void ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event); +void ltk_window_fake_motion_event(ltk_window *window, int x, int y); int ltk_widget_id_free(const char *id); ltk_widget *ltk_get_widget(const char *id, ltk_widget_type type, char **errstr); void ltk_set_widget(ltk_widget *widget, const char *id); @@ -155,4 +151,4 @@ void ltk_widgets_cleanup(); void ltk_widgets_init(); void ltk_widget_resize(ltk_widget *widget); -#endif /* _LTK_WIDGET_H_ */ +#endif /* LTK_WIDGET_H */ diff --git a/test2.gui b/test2.gui @@ -3,23 +3,42 @@ grid grd1 set-row-weight 1 1 grid grd1 set-column-weight 0 1 set-root-widget grd1 menu menu1 create -menu menu1 add-entry entry1 "Menu Entry" -menu menu1 add-entry entrya1 "Menu Entry 2" +menuentry entry1 create "Entry 1" +menuentry entry2 create "Entry 2" +menuentry entry3 create "Entry 3" +menuentry entry4 create "Entry 4" +menuentry entry5 create "Entry 5" +menuentry entry6 create "Entry 6" +menuentry entry7 create "Entry 7" +menuentry entry8 create "Entry 8" +menuentry entry9 create "Entry 9" +menuentry entry10 create "Entry 10" +menuentry entry11 create "Entry 11" +menuentry entry12 create "Entry 12" +menuentry entry13 create "Entry 13" +menuentry entry14 create "Entry 14" +menuentry entry15 create "Entry 15" +menuentry entry16 create "Entry 16" +menu menu1 add-entry entry1 +menu menu1 add-entry entry2 +menu menu1 add-entry entry12 submenu submenu1 create -menu submenu1 add-entry entry2 "Submenu Entry 1" -menu submenu1 add-entry entry6 "Submenu Entry 2" -menu submenu1 add-entry entry7 "Submenu Entry 3" -menu submenu1 add-entry entry8 "Submenu Entry 4" -menu submenu1 add-entry entry9 "Submenu Entry 5" -menu submenu1 add-entry entry10 "Submenu Entry 6" -menu submenu1 add-entry entry11 "Submenu Entry 7" -menu submenu1 add-entry entry12 "Submenu Entry 8" -menu submenu1 add-entry entry13 "Submenu Entry 9" -menu menu1 add-submenu entry3 "Submenu" submenu1 +menu submenu1 add-entry entry3 +menu submenu1 add-entry entry4 +menu submenu1 add-entry entry5 +menu submenu1 add-entry entry6 +menu submenu1 add-entry entry7 +menu submenu1 add-entry entry8 +menu submenu1 add-entry entry9 +menu submenu1 add-entry entry10 +menu submenu1 add-entry entry11 +menuentry entry12 attach-submenu submenu1 submenu submenu2 create -menu submenu2 add-entry entry4 "Submenu Entry" -menu submenu1 add-submenu entry5 "Submenu" submenu2 +menu submenu2 add-entry entry13 +menu submenu2 add-entry entry15 +menu submenu1 add-entry entry14 +menuentry entry14 attach-submenu submenu2 submenu submenu3 create -menu submenu3 add-entry entrya3 "Submenu Entry" -menu submenu2 add-submenu entrya2 "Submenu" submenu3 -grid grd1 add menu1 0 0 1 1 w +menu submenu3 add-entry entry16 +menuentry entry15 attach-submenu submenu3 +grid grd1 add menu1 0 0 1 1 ew