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:
M | Makefile | | | 2 | +- |
M | src/box.c | | | 17 | +++++++---------- |
M | src/button.c | | | 49 | +++++++++++++++---------------------------------- |
M | src/graphics.h | | | 7 | ++----- |
M | src/graphics_xlib.c | | | 163 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
M | src/grid.c | | | 31 | +++++++++++++++++-------------- |
M | src/label.c | | | 6 | ------ |
M | src/ltkd.c | | | 94 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
M | src/menu.c | | | 1659 | +++++++++++++++++++++++++++++++++---------------------------------------------- |
M | src/menu.h | | | 34 | ++++++++++++++++++++++++++-------- |
M | src/rect.h | | | 11 | ++++++++--- |
M | src/scrollbar.c | | | 35 | +++++++++++------------------------ |
M | src/widget.c | | | 117 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------- |
M | src/widget.h | | | 62 | +++++++++++++++++++++++++++++--------------------------------- |
M | test2.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 = <k_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 = <k_menu_mouse_press,
.motion_notify = <k_menu_motion_notify,
- .mouse_release = <k_menu_mouse_release,
+ .mouse_release = NULL,
.mouse_enter = <k_menu_mouse_enter,
.mouse_leave = <k_menu_mouse_leave,
- .get_child_at_pos = NULL,
+ .get_child_at_pos = <k_menu_get_child_at_pos,
.resize = <k_menu_resize,
- .change_state = <k_menu_change_state,
+ .change_state = NULL,
.hide = <k_menu_hide,
.draw = <k_menu_draw,
.destroy = <k_menu_destroy,
+ .child_size_change = &recalc_ideal_menu_size,
+ .remove_child = <k_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 = <k_menuentry_mouse_release,
+ .mouse_enter = NULL,
+ .mouse_leave = NULL,
+ .get_child_at_pos = NULL,
+ .resize = NULL,
+ .change_state = <k_menuentry_change_state,
+ .hide = NULL,
+ .draw = <k_menuentry_draw,
+ .destroy = <k_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