URI: 
       tTurn menu entries into regular widgets - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 480c476bee3efca10facb5ff2be6863bf112b72a
   DIR parent 8c7d6c1077f97dd4ae11a551ccf3a68fa86e21f6
  HTML 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(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       t@@ -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
        
   DIR diff --git a/src/box.c b/src/box.c
       t@@ -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);
       t@@ -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;
       t@@ -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;
       t@@ -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++) {
       t@@ -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;
       t@@ -395,6 +391,7 @@ ltk_box_cmd_remove(
            char **tokens,
            size_t num_tokens,
            char **errstr) {
       +        (void)window;
                ltk_box *box;
                ltk_widget *widget;
        
       t@@ -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> */
   DIR diff --git a/src/button.c b/src/button.c
       t@@ -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 = {
       t@@ -52,7 +51,7 @@ static struct ltk_widget_vtable vtable = {
                .motion_notify = NULL,
                .mouse_leave = NULL,
                .mouse_enter = NULL,
       -        .change_state = &ltk_button_change_state,
       +        .change_state = NULL,
                .get_child_at_pos = NULL,
                .resize = NULL,
                .hide = NULL,
       t@@ -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 {
       t@@ -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;
       t@@ -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;
                }
       t@@ -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");
   DIR diff --git a/src/graphics.h b/src/graphics.h
       t@@ -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 */
       t@@ -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 */
        /*
   DIR diff --git a/src/graphics_xlib.c b/src/graphics_xlib.c
       t@@ -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);
       t@@ -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;
       t@@ -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
       t@@ -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
   DIR diff --git a/src/grid.c b/src/grid.c
       t@@ -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);
       t@@ -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]) {
       t@@ -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++) {
       t@@ -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) {
       t@@ -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) {
       t@@ -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";
       t@@ -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;
        }
       t@@ -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) {
       t@@ -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> */
   DIR diff --git a/src/label.c b/src/label.c
       t@@ -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");
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -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 */
       t@@ -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;
                }
       t@@ -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");
                }
       t@@ -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 *
       t@@ -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;
       +                }
                }
        }
        
       t@@ -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);
                }
        }
        
       t@@ -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);
        /*
   DIR diff --git a/src/menu.c b/src/menu.c
       t@@ -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>
       t@@ -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;
       t@@ -74,95 +82,94 @@ static struct theme {
                ltk_color text_disabled;
                ltk_color border_disabled;
                ltk_color fill_disabled;
       -} menu_theme, submenu_theme;
       +} menu_entry_theme, submenu_entry_theme;
        
        static void ltk_menu_resize(ltk_widget *self);
       -static void ltk_menu_change_state(ltk_widget *self);
        static void ltk_menu_draw(ltk_widget *self, ltk_rect clip);
       -static void ltk_menu_redraw_surface(ltk_menu *menu, ltk_surface *s);
        static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret);
        static void ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step);
        static void ltk_menu_scroll_callback(void *data);
        static void stop_scrolling(ltk_menu *menu);
       -static size_t get_entry_at_point(ltk_menu *menu, int x, int y, ltk_rect *entry_rect_ret);
       +static ltk_widget *ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y);
        static int set_scroll_timer(ltk_menu *menu, int x, int y);
       -static int ltk_menu_mouse_release(ltk_widget *self, ltk_button_event *event);
        static int ltk_menu_mouse_press(ltk_widget *self, ltk_button_event *event);
        static void ltk_menu_hide(ltk_widget *self);
       -static void popup_active_menu(ltk_menu *menu, ltk_rect r);
       -static void unpopup_active_entry(ltk_menu *menu);
       -static void handle_hover(ltk_menu *menu, int x, int y);
       +static void popup_active_menu(ltk_menuentry *e);
       +static void unpopup_active_entry(ltk_menuentry *e);
        static int ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event);
        static int ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event);
        static int ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event);
        static ltk_menu *ltk_menu_create(ltk_window *window, const char *id, int is_submenu);
       -static ltk_menuentry *insert_entry(ltk_menu *menu, size_t idx);
       -static void recalc_menu_size(ltk_menu *menu);
       +static void recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget);
        static void shrink_entries(ltk_menu *menu);
        static size_t get_entry_with_id(ltk_menu *menu, const char *id);
        static void ltk_menu_destroy(ltk_widget *self, int shallow);
        
       -static ltk_menuentry *ltk_menu_insert_entry(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, size_t idx, char **errstr);
       -static ltk_menuentry *ltk_menu_add_entry(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, char **errstr);
       -static ltk_menuentry *ltk_menu_insert_submenu(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, size_t idx, char **errstr);
       -static ltk_menuentry *ltk_menu_add_submenu(ltk_menu *menu, const char *id, const char *text, ltk_menu *submenu, char **errstr);
       -static int ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, int shallow, char **errstr);
       -static int ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, int shallow, char **errstr);
       -static int ltk_menu_remove_all_entries(ltk_menu *menu, int shallow, char **errstr);
       -static int ltk_menu_detach_submenu_from_entry_id(ltk_menu *menu, const char *id, char **errstr);
       -static int ltk_menu_detach_submenu_from_entry_index(ltk_menu *menu, size_t idx, char **errstr);
       -static int ltk_menu_disable_entry_index(ltk_menu *menu, size_t idx, char **errstr);
       -static int ltk_menu_disable_entry_id(ltk_menu *menu, const char *id, char **errstr);
       -static int ltk_menu_disable_all_entries(ltk_menu *menu, char **errstr);
       -static int ltk_menu_enable_entry_index(ltk_menu *menu, size_t idx, char **errstr);
       -static int ltk_menu_enable_entry_id(ltk_menu *menu, const char *id, char **errstr);
       -static int ltk_menu_enable_all_entries(ltk_menu *menu, char **errstr);
       +static ltk_menuentry *ltk_menuentry_create(ltk_window *window, const char *id, const char *text);
       +static void ltk_menuentry_draw(ltk_widget *self, ltk_rect clip);
       +static void ltk_menuentry_destroy(ltk_widget *self, int shallow);
       +static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state);
       +static int ltk_menuentry_mouse_release(ltk_widget *self, ltk_button_event *event);
       +static void ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry);
       +static void ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, char **errstr);
       +static void ltk_menuentry_detach_submenu(ltk_menuentry *e);
       +
       +static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, char **errstr);
       +
       +#define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_MENU && ((ltk_menu *)e->widget.parent)->is_submenu)
        
        static struct ltk_widget_vtable vtable = {
                .key_press = NULL,
                .key_release = NULL,
                .mouse_press = &ltk_menu_mouse_press,
                .motion_notify = &ltk_menu_motion_notify,
       -        .mouse_release = &ltk_menu_mouse_release,
       +        .mouse_release = NULL,
                .mouse_enter = &ltk_menu_mouse_enter,
                .mouse_leave = &ltk_menu_mouse_leave,
       -        .get_child_at_pos = NULL,
       +        .get_child_at_pos = &ltk_menu_get_child_at_pos,
                .resize = &ltk_menu_resize,
       -        .change_state = &ltk_menu_change_state,
       +        .change_state = NULL,
                .hide = &ltk_menu_hide,
                .draw = &ltk_menu_draw,
                .destroy = &ltk_menu_destroy,
       +        .child_size_change = &recalc_ideal_menu_size,
       +        .remove_child = &ltk_menu_remove_child,
       +        .type = LTK_MENU,
       +        .flags = LTK_NEEDS_REDRAW,
       +};
       +
       +static struct ltk_widget_vtable entry_vtable = {
       +        .key_press = NULL,
       +        .key_release = NULL,
       +        .mouse_press = NULL,
       +        .motion_notify = NULL,
       +        .mouse_release = &ltk_menuentry_mouse_release,
       +        .mouse_enter = NULL,
       +        .mouse_leave = NULL,
       +        .get_child_at_pos = NULL,
       +        .resize = NULL,
       +        .change_state = &ltk_menuentry_change_state,
       +        .hide = NULL,
       +        .draw = &ltk_menuentry_draw,
       +        .destroy = &ltk_menuentry_destroy,
                .child_size_change = NULL,
                .remove_child = NULL,
       -        .type = LTK_MENU,
       -        .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE,
       +        .type = LTK_MENUENTRY,
       +        .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_HOVER_IS_ACTIVE,
        };
        
       +/* FIXME: standardize menuentry vs. menu_entry */
       +
        static ltk_theme_parseinfo menu_parseinfo[] = {
       -        {"border-width", THEME_INT, {.i = &menu_theme.border_width}, {.i = 2}, 0, MAX_MENU_BORDER_WIDTH, 0},
                {"pad", THEME_INT, {.i = &menu_theme.pad}, {.i = 0}, 0, MAX_MENU_PAD, 0},
       -        {"text-pad", THEME_INT, {.i = &menu_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0},
       -        {"arrow-size", THEME_INT, {.i = &menu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0},
                {"arrow-pad", THEME_INT, {.i = &menu_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0},
       +        {"arrow-size", THEME_INT, {.i = &menu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0},
       +        {"border-width", THEME_INT, {.i = &menu_theme.border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0},
                {"compress-borders", THEME_BOOL, {.b = &menu_theme.compress_borders}, {.b = 1}, 0, 0, 0},
       -        {"border-sides", THEME_BORDERSIDES, {.border = &menu_theme.border_sides}, {.border = LTK_BORDER_ALL}, 0, 0, 0},
       -        {"menu-border-width", THEME_INT, {.i = &menu_theme.menu_border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0},
       -        {"background", THEME_COLOR, {.color = &menu_theme.background}, {.color = "#555555"}, 0, 0, 0},
       +        {"border", THEME_COLOR, {.color = &menu_theme.border}, {.color = "#000000"}, 0, 0, 0},
       +        {"background", THEME_COLOR, {.color = &menu_theme.background}, {.color = "#000000"}, 0, 0, 0},
                {"scroll-background", THEME_COLOR, {.color = &menu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0},
                {"scroll-arrow-color", THEME_COLOR, {.color = &menu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0},
       -        {"text", THEME_COLOR, {.color = &menu_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0},
       -        {"border", THEME_COLOR, {.color = &menu_theme.border}, {.color = "#339999"}, 0, 0, 0},
       -        {"fill", THEME_COLOR, {.color = &menu_theme.fill}, {.color = "#113355"}, 0, 0, 0},
       -        {"text-pressed", THEME_COLOR, {.color = &menu_theme.text_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
       -        {"border-pressed", THEME_COLOR, {.color = &menu_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
       -        {"fill-pressed", THEME_COLOR, {.color = &menu_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
       -        {"text-active", THEME_COLOR, {.color = &menu_theme.text_active}, {.color = "#FFFFFF"}, 0, 0, 0},
       -        {"border-active", THEME_COLOR, {.color = &menu_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
       -        {"fill-active", THEME_COLOR, {.color = &menu_theme.fill_active}, {.color = "#738194"}, 0, 0, 0},
       -        {"text-disabled", THEME_COLOR, {.color = &menu_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
       -        {"border-disabled", THEME_COLOR, {.color = &menu_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
       -        {"fill-disabled", THEME_COLOR, {.color = &menu_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
       -        {"menu-border", THEME_COLOR, {.color = &menu_theme.menu_border}, {.color = "#000000"}, 0, 0, 0},
        };
        static int menu_parseinfo_sorted = 0;
        
       t@@ -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;
        
       t@@ -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
       t@@ -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
       t@@ -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);
                }
       t@@ -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;
       t@@ -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 */
       t@@ -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;
        }
       t@@ -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;
        }
        
       t@@ -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? */
       t@@ -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,
       t@@ -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;
       t@@ -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,
       t@@ -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,
       t@@ -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,
       t@@ -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,
       t@@ -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;
   DIR diff --git a/src/menu.h b/src/menu.h
       t@@ -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);
       t@@ -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,
       t@@ -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_ */
   DIR diff --git a/src/rect.h b/src/rect.h
       t@@ -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;
       t@@ -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 */
   DIR diff --git a/src/scrollbar.c b/src/scrollbar.c
       t@@ -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 {
       t@@ -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);
       t@@ -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)
       t@@ -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);
        }
   DIR diff --git a/src/widget.c b/src/widget.c
       t@@ -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>
         *
       t@@ -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);
       t@@ -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
       t@@ -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 *
       t@@ -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;
                                }
       t@@ -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);
                }
        }
        
       t@@ -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;
                                }
       t@@ -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
       t@@ -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);
   DIR diff --git a/src/widget.h b/src/widget.h
       t@@ -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"
       t@@ -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 {
       t@@ -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 {
       t@@ -63,6 +66,7 @@ typedef enum {
                LTK_WIDGET,
                LTK_BOX,
                LTK_MENU,
       +        LTK_MENUENTRY,
                LTK_NUM_WIDGETS
        } ltk_widget_type;
        
       t@@ -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);
       t@@ -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);
       t@@ -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 */
   DIR diff --git a/test2.gui b/test2.gui
       t@@ -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