URI: 
       tClean up mouse event handling a bit; bring back hover state - 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 bf6d516dc0a01db18fb93ddb4f153a454b36700b
   DIR parent 994e235d9939788238dd2c66cdefb4cc4ade1bce
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu, 16 Jun 2022 10:47:44 +0200
       
       Clean up mouse event handling a bit; bring back hover state
       
       Of course, everything is still hopelessly broken.
       
       Diffstat:
         M Makefile                            |      11 ++++++-----
         M src/box.c                           |      71 +++++++++----------------------
         M src/button.c                        |      19 ++++++++++++++-----
         M src/grid.c                          |      59 ++++++-------------------------
         M src/label.c                         |       1 +
         M src/ltk.h                           |       2 ++
         M src/ltkd.c                          |      73 +++++++++++++++----------------
         M src/menu.c                          |      52 ++++++++++++++++++++-----------
         M src/scrollbar.c                     |      32 ++++++++++++++++----------------
         M src/widget.c                        |     128 ++++++++++++++++++++++---------
         M src/widget.h                        |      20 ++++++++++++--------
       
       11 files changed, 241 insertions(+), 227 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       t@@ -7,14 +7,15 @@ 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 = 0
       +DEV = 1
       +SANITIZE = 0
        USE_PANGO = 0
        
        # Note: this macro magic for debugging and pango rendering seems ugly; it should probably be changed
        
        # debug
       -DEV_CFLAGS_1 = -fsanitize=address -g -Wall -Wextra -pedantic
       -DEV_LDFLAGS_1 = -fsanitize=address
       +DEV_CFLAGS_1 = -g -Wall -Wextra -pedantic
       +SANITIZE_FLAGS_1 = -fsanitize=address
        # don't include default flags when debugging so possible
        # optimization flags don't interfere with it
        DEV_CFLAGS_0 = $(CFLAGS)
       t@@ -29,8 +30,8 @@ EXTRA_CFLAGS_1 = `pkg-config --cflags pangoxft`
        EXTRA_LDFLAGS_1 = `pkg-config --libs pangoxft`
        
        EXTRA_OBJ = $(EXTRA_OBJ_$(USE_PANGO))
       -EXTRA_CFLAGS = $(DEV_CFLAGS_$(DEV)) $(EXTRA_CFLAGS_$(USE_PANGO))
       -EXTRA_LDFLAGS = $(DEV_LDFLAGS_$(DEV)) $(EXTRA_LDFLAGS_$(USE_PANGO))
       +EXTRA_CFLAGS = $(SANITIZE_FLAGS_$(SANITIZE)) $(DEV_CFLAGS_$(DEV)) $(EXTRA_CFLAGS_$(USE_PANGO))
       +EXTRA_LDFLAGS = $(SANITIZE_FLAGS_$(SANITIZE)) $(DEV_LDFLAGS_$(DEV)) $(EXTRA_LDFLAGS_$(USE_PANGO))
        
        LTK_CFLAGS = $(EXTRA_CFLAGS) -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -std=c99 `pkg-config --cflags x11 fontconfig xext` -D_POSIX_C_SOURCE=200809L
        LTK_LDFLAGS = $(EXTRA_LDFLAGS) -lm `pkg-config --libs x11 fontconfig xext`
   DIR diff --git a/src/box.c b/src/box.c
       t@@ -40,10 +40,8 @@ static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, uns
        static int ltk_box_remove(ltk_window *window, 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_event(ltk_box *box, int x, int y, ltk_event *event, void (*handler)(ltk_widget *, ltk_event *));
       -static int ltk_box_mouse_press(ltk_widget *self, ltk_event *event);
       -static int ltk_box_mouse_release(ltk_widget *self, ltk_event *event);
       -static int ltk_box_motion_notify(ltk_widget *self, ltk_event *event);
       +static int ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event);
       +static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y);
        
        static struct ltk_widget_vtable vtable = {
                .change_state = NULL,
       t@@ -56,8 +54,9 @@ static struct ltk_widget_vtable vtable = {
                .key_press = NULL,
                .key_release = NULL,
                .mouse_press = &ltk_box_mouse_press,
       -        .mouse_release = &ltk_box_mouse_release,
       -        .motion_notify = &ltk_box_motion_notify,
       +        .mouse_release = NULL,
       +        .motion_notify = NULL,
       +        .get_child_at_pos = &ltk_box_get_child_at_pos,
                .mouse_leave = NULL,
                .mouse_enter = NULL,
                .needs_redraw = 0,
       t@@ -321,64 +320,32 @@ ltk_box_scroll(ltk_widget *self) {
                ltk_window_invalidate_rect(box->widget.window, box->widget.rect);
        }
        
       -static int
       -ltk_box_mouse_event(ltk_box *box, int x, int y, ltk_event *event, void (*handler)(ltk_widget *, ltk_event *)) {
       -        ltk_widget *widget;
       -
       -        if (ltk_collide_rect(box->sc->widget.rect, x, y)) {
       -                handler((ltk_widget *)box->sc, event);
       -                return 0;
       -        }
       -
       -        /* FIXME: check only the currently visible items */
       +static ltk_widget *
       +ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) {
       +        ltk_box *box = (ltk_box *)self;
       +        if (ltk_collide_rect(box->sc->widget.rect, x, y))
       +                return (ltk_widget *)box->sc;
                for (size_t i = 0; i < box->num_widgets; i++) {
       -                widget = box->widgets[i];
       -                if (ltk_collide_rect(widget->rect, x, y)) {
       -                        handler(widget, event);
       -                        return 0;
       -                }
       +                if (ltk_collide_rect(box->widgets[i]->rect, x, y))
       +                        return box->widgets[i];
                }
       -        return 0;
       +        return NULL;
        }
        
        static int
       -ltk_box_mouse_press(ltk_widget *self, ltk_event *event) {
       +ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event) {
                ltk_box *box = (ltk_box *)self;
                /* FIXME: combine multiple events into one for efficiency */
       -        /* FIXME: fix this whole state handling */
       -        if (event->button.button == LTK_BUTTON4 || event->button.button == LTK_BUTTON5) {
       -                ltk_widget *widget;
       -                int default_handler = 1;
       -                for (size_t i = 0; i < box->num_widgets; i++) {
       -                        widget = box->widgets[i];
       -                        if (ltk_collide_rect(widget->rect, event->button.x, event->button.y)) {
       -                                if (widget->vtable->mouse_press)
       -                                        default_handler = widget->vtable->mouse_press(widget, event);
       -                        }
       -                }
       +        if (event->button == LTK_BUTTON4 || event->button == LTK_BUTTON5) {
                        /* FIXME: configure scrollstep */
       -                if (default_handler) {
       -                        int delta = event->button.button == LTK_BUTTON4 ? -15 : 15;
       -                        ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0);
       -                }
       -                return 0;
       +                int delta = event->button == LTK_BUTTON4 ? -15 : 15;
       +                ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0);
       +                return 1;
                } else {
       -                return ltk_box_mouse_event(box, event->button.x, event->button.y, event, &ltk_widget_mouse_press_event);
       +                return 0;
                }
        }
        
       -static int
       -ltk_box_mouse_release(ltk_widget *self, ltk_event *event) {
       -        ltk_box *box = (ltk_box *)self;
       -        return ltk_box_mouse_event(box, event->button.x, event->button.y, event, &ltk_widget_mouse_release_event);
       -}
       -
       -static int
       -ltk_box_motion_notify(ltk_widget *self, ltk_event *event) {
       -        ltk_box *box = (ltk_box *)self;
       -        return ltk_box_mouse_event(box, event->motion.x, event->motion.y, event, &ltk_widget_motion_notify_event);
       -}
       -
        /* box <box id> add <widget id> [sticky] */
        static int
        ltk_box_cmd_add(
   DIR diff --git a/src/button.c b/src/button.c
       t@@ -37,7 +37,7 @@
        #define MAX_BUTTON_PADDING 500
        
        static void ltk_button_draw(ltk_widget *self, ltk_rect clip);
       -static int ltk_button_mouse_release(ltk_widget *self, ltk_event *event);
       +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);
       t@@ -53,6 +53,7 @@ static struct ltk_widget_vtable vtable = {
                .mouse_leave = NULL,
                .mouse_enter = NULL,
                .change_state = &ltk_button_change_state,
       +        .get_child_at_pos = NULL,
                .resize = NULL,
                .hide = NULL,
                .draw = &ltk_button_draw,
       t@@ -75,6 +76,9 @@ static struct {
                ltk_color border_pressed;
                ltk_color fill_pressed;
        
       +        ltk_color border_hover;
       +        ltk_color fill_hover;
       +
                ltk_color border_active;
                ltk_color fill_active;
        
       t@@ -84,12 +88,14 @@ static struct {
        
        static ltk_theme_parseinfo parseinfo[] = {
                {"border", THEME_COLOR, {.color = &theme.border}, {.color = "#339999"}, 0, 0, 0},
       +        {"border-hover", THEME_COLOR, {.color = &theme.border_hover}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"border-active", THEME_COLOR, {.color = &theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"border-disabled", THEME_COLOR, {.color = &theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"border-pressed", THEME_COLOR, {.color = &theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"border-width", THEME_INT, {.i = &theme.border_width}, {.i = 2}, 0, MAX_BUTTON_BORDER_WIDTH, 0},
                {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#113355"}, 0, 0, 0},
       -        {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#738194"}, 0, 0, 0},
       +        {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#738194"}, 0, 0, 0},
       +        {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#113355"}, 0, 0, 0},
                {"fill-disabled", THEME_COLOR, {.color = &theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
                {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
                {"pad", THEME_INT, {.i = &theme.pad}, {.i = 5}, 0, MAX_BUTTON_PADDING, 0},
       t@@ -133,6 +139,10 @@ ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) {
                        border = &theme.border;
                        fill = &theme.fill;
                        break;
       +        case LTK_HOVER:
       +                border = &theme.border_hover;
       +                fill = &theme.fill_hover;
       +                break;
                case LTK_PRESSED:
                        border = &theme.border_pressed;
                        fill = &theme.fill_pressed;
       t@@ -167,11 +177,10 @@ ltk_button_change_state(ltk_widget *self) {
                self->dirty = 1;
        }
        
       -/* FIXME: only when pressed button was actually this one */
        static int
       -ltk_button_mouse_release(ltk_widget *self, ltk_event *event) {
       +ltk_button_mouse_release(ltk_widget *self, ltk_button_event *event) {
                ltk_button *button = (ltk_button *)self;
       -        if (event->button.button == LTK_BUTTONL) {
       +        if (self->state == LTK_PRESSED && event->button == LTK_BUTTONL) {
                        ltk_queue_event(button->widget.window, LTK_EVENT_BUTTON, button->widget.id, "button_click");
                        return 1;
                }
   DIR diff --git a/src/grid.c b/src/grid.c
       t@@ -50,9 +50,7 @@ static int 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);
        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 int ltk_grid_mouse_press(ltk_widget *self, ltk_event *event);
       -static int ltk_grid_mouse_release(ltk_widget *self, ltk_event *event);
       -static int ltk_grid_motion_notify(ltk_widget *self, ltk_event *event);
       +static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y);
        
        static struct ltk_widget_vtable vtable = {
                .draw = &ltk_grid_draw,
       t@@ -62,9 +60,10 @@ static struct ltk_widget_vtable vtable = {
                .change_state = NULL,
                .child_size_change = &ltk_grid_child_size_change,
                .remove_child = &ltk_grid_ungrid,
       -        .mouse_press = &ltk_grid_mouse_press,
       -        .mouse_release = &ltk_grid_mouse_release,
       -        .motion_notify = &ltk_grid_motion_notify,
       +        .mouse_press = NULL,
       +        .mouse_release = NULL,
       +        .motion_notify = NULL,
       +        .get_child_at_pos = &ltk_grid_get_child_at_pos,
                .mouse_leave = NULL,
                .mouse_enter = NULL,
                .key_press = NULL,
       t@@ -383,55 +382,17 @@ ltk_grid_find_nearest_row(ltk_grid *grid, int y) {
                return -1;
        }
        
       -static int
       -ltk_grid_mouse_press(ltk_widget *self, ltk_event *event) {
       -        ltk_grid *grid = (ltk_grid *)self;
       -        int x = event->button.x;
       -        int y = event->button.y;
       -        int row = ltk_grid_find_nearest_row(grid, y);
       -        int column = ltk_grid_find_nearest_column(grid, x);
       -        if (row == -1 || column == -1)
       -                return 0;
       -        ltk_widget *ptr = grid->widget_grid[row * grid->columns + column];
       -        if (ptr && ltk_collide_rect(ptr->rect, x, y)) {
       -                ltk_widget_mouse_press_event(ptr, event);
       -                return 0;
       -        }
       -        return 0;
       -}
       -
       -static int
       -ltk_grid_mouse_release(ltk_widget *self, ltk_event *event) {
       -        ltk_grid *grid = (ltk_grid *)self;
       -        int x = event->button.x;
       -        int y = event->button.y;
       -        int row = ltk_grid_find_nearest_row(grid, y);
       -        int column = ltk_grid_find_nearest_column(grid, x);
       -        if (row == -1 || column == -1)
       -                return 0;
       -        ltk_widget *ptr = grid->widget_grid[row * grid->columns + column];
       -        if (ptr && ltk_collide_rect(ptr->rect, x, y)) {
       -                ltk_widget_mouse_release_event(ptr, event);
       -                return 0;
       -        }
       -        return 0;
       -}
       -
       -static int
       -ltk_grid_motion_notify(ltk_widget *self, ltk_event *event) {
       +static ltk_widget *
       +ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) {
                ltk_grid *grid = (ltk_grid *)self;
       -        int x = event->motion.x;
       -        int y = event->motion.y;
                int row = ltk_grid_find_nearest_row(grid, y);
                int column = ltk_grid_find_nearest_column(grid, x);
                if (row == -1 || column == -1)
                        return 0;
                ltk_widget *ptr = grid->widget_grid[row * grid->columns + column];
       -        if (ptr && ltk_collide_rect(ptr->rect, x, y)) {
       -                ltk_widget_motion_notify_event(ptr, event);
       -                return 0;
       -        }
       -        return 0;
       +        if (ptr && ltk_collide_rect(ptr->rect, x, y))
       +                return ptr;
       +        return NULL;
        }
        
        /* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> [sticky] */
   DIR diff --git a/src/label.c b/src/label.c
       t@@ -49,6 +49,7 @@ static struct ltk_widget_vtable vtable = {
                .change_state = NULL,
                .child_size_change = NULL,
                .remove_child = NULL,
       +        .get_child_at_pos = NULL,
                .key_press = NULL,
                .key_release = NULL,
                .mouse_press = NULL,
   DIR diff --git a/src/ltk.h b/src/ltk.h
       t@@ -59,6 +59,7 @@ struct ltk_window {
                ltk_text_context *text_context;
                ltk_surface *surface;
                ltk_widget *root_widget;
       +        ltk_widget *hover_widget;
                ltk_widget *active_widget;
                ltk_widget *pressed_widget;
                void (*other_event) (struct ltk_window *, ltk_event *event);
       t@@ -89,6 +90,7 @@ struct ltk_window_theme {
        
        void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
        void ltk_queue_event(ltk_window *window, ltk_userevent_type type, const char *id, const char *data);
       +void ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event);
        void ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget);
        void ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget);
        void ltk_quit(ltk_window *window);
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -674,10 +674,10 @@ void
        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]);
                        }
       -                window->popups[i]->hidden = 1;
                }
                window->popups_num = 0;
                /* somewhat arbitrary, but should be enough for most cases */
       t@@ -784,6 +784,7 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
                renderer_set_window_properties(window->renderdata, &window->theme->bg, &window->theme->fg, window->theme->border_width);
        
                window->root_widget = NULL;
       +        window->hover_widget = NULL;
                window->active_widget = NULL;
                window->pressed_widget = NULL;
        
       t@@ -818,6 +819,31 @@ 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);
       +                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);
       +        }
       +}
       +
        void
        ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
                if (window->active_widget == widget)
       t@@ -833,19 +859,17 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
                }
        }
        
       -/* FIXME: Should pressed widget also be set as active widget? */
        void
        ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget) {
                if (window->pressed_widget == widget)
                        return;
       -        if (window->active_widget && window->active_widget != widget) {
       -                window->active_widget->state = LTK_NORMAL;
       -                ltk_widget_change_state(window->active_widget);
       -        }
       -        if (window->pressed_widget) {
       -                window->pressed_widget->state = LTK_ACTIVE;
       -                ltk_widget_change_state(window->pressed_widget);
       +        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)
       +                ltk_window_set_active_widget(window, window->pressed_widget);
                window->pressed_widget = widget;
                if (widget) {
                        widget->state = LTK_PRESSED;
       t@@ -853,46 +877,21 @@ ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget) {
                }
        }
        
       -static ltk_widget *
       -get_hover_popup(ltk_window *window, int x, int y) {
       -        for (size_t i = window->popups_num; i-- > 0;) {
       -                if (ltk_collide_rect(window->popups[i]->rect, x, y))
       -                        return window->popups[i];
       -        }
       -        return NULL;
       -}
       -
        static void
        ltk_handle_event(ltk_window *window, ltk_event *event) {
       -        ltk_widget *hover_popup;
       -        ltk_widget *root_widget = window->root_widget;
                switch (event->type) {
                case LTK_KEYPRESS_EVENT:
                        break;
                case LTK_KEYRELEASE_EVENT:
                        break;
                case LTK_BUTTONPRESS_EVENT:
       -                hover_popup = get_hover_popup(window, event->button.x, event->button.y);
       -                if (hover_popup) {
       -                        ltk_widget_mouse_press_event(hover_popup, event);
       -                } else if (root_widget) {
       -                        ltk_window_unregister_all_popups(window);
       -                        ltk_widget_mouse_press_event(root_widget, event);
       -                }
       +                ltk_window_mouse_press_event(window, &event->button);
                        break;
                case LTK_BUTTONRELEASE_EVENT:
       -                hover_popup = get_hover_popup(window, event->button.x, event->button.y);
       -                if (hover_popup)
       -                        ltk_widget_mouse_release_event(hover_popup, event);
       -                else if (root_widget)
       -                        ltk_widget_mouse_release_event(root_widget, event);
       +                ltk_window_mouse_release_event(window, &event->button);
                        break;
                case LTK_MOTION_EVENT:
       -                hover_popup = get_hover_popup(window, event->motion.x, event->motion.y);
       -                if (hover_popup)
       -                        ltk_widget_motion_notify_event(hover_popup, event);
       -                else if (root_widget)
       -                        ltk_widget_motion_notify_event(root_widget, event);
       +                ltk_window_motion_notify_event(window, &event->motion);
                        break;
                default:
                        if (window->other_event)
   DIR diff --git a/src/menu.c b/src/menu.c
       t@@ -86,15 +86,15 @@ 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 int set_scroll_timer(ltk_menu *menu, int x, int y);
       -static int ltk_menu_mouse_release(ltk_widget *self, ltk_event *event);
       -static int ltk_menu_mouse_press(ltk_widget *self, ltk_event *event);
       +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 int ltk_menu_motion_notify(ltk_widget *self, ltk_event *event);
       -static int ltk_menu_mouse_enter(ltk_widget *self, ltk_event *event);
       -static int ltk_menu_mouse_leave(ltk_widget *self, ltk_event *event);
       +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);
       t@@ -126,6 +126,7 @@ static struct ltk_widget_vtable vtable = {
                .mouse_release = &ltk_menu_mouse_release,
                .mouse_enter = &ltk_menu_mouse_enter,
                .mouse_leave = &ltk_menu_mouse_leave,
       +        .get_child_at_pos = NULL,
                .resize = &ltk_menu_resize,
                .change_state = &ltk_menu_change_state,
                .hide = &ltk_menu_hide,
       t@@ -250,6 +251,14 @@ ltk_menu_change_state(ltk_widget *self) {
                        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);
       +                }
       +        }
        }
        
        static void
       t@@ -602,9 +611,9 @@ set_scroll_timer(ltk_menu *menu, int x, int y) {
        }
        
        static int
       -ltk_menu_mouse_release(ltk_widget *self, ltk_event *event) {
       +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->button.x, event->button.y, NULL);
       +        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 */
       t@@ -620,13 +629,13 @@ ltk_menu_mouse_release(ltk_widget *self, ltk_event *event) {
        }
        
        static int
       -ltk_menu_mouse_press(ltk_widget *self, ltk_event *event) {
       +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.button) {
       +        switch (event->button) {
                case LTK_BUTTONL:
       -                idx = get_entry_at_point(menu, event->button.x, event->button.y, NULL);
       +                idx = get_entry_at_point(menu, event->x, event->y, NULL);
                        if (idx < menu->num_entries) {
                                menu->pressed_entry = idx;
                                self->dirty = 1;
       t@@ -634,19 +643,19 @@ ltk_menu_mouse_press(ltk_widget *self, ltk_event *event) {
                        break;
                case LTK_BUTTON4:
                        ltk_menu_scroll(menu, 1, 0, 0, 0, 10);
       -                handle_hover(menu, event->button.x, event->button.y);
       +                handle_hover(menu, event->x, event->y);
                        break;
                case LTK_BUTTON5:
                        ltk_menu_scroll(menu, 0, 1, 0, 0, 10);
       -                handle_hover(menu, event->button.x, event->button.y);
       +                handle_hover(menu, event->x, event->y);
                        break;
                case LTK_BUTTON6:
                        ltk_menu_scroll(menu, 0, 0, 1, 0, 10);
       -                handle_hover(menu, event->button.x, event->button.y);
       +                handle_hover(menu, event->x, event->y);
                        break;
                case LTK_BUTTON7:
                        ltk_menu_scroll(menu, 0, 0, 0, 1, 10);
       -                handle_hover(menu, event->button.x, event->button.y);
       +                handle_hover(menu, event->x, event->y);
                        break;
                default:
                        break;
       t@@ -664,6 +673,11 @@ ltk_menu_hide(ltk_widget *self) {
                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: don't require passing rect */
       t@@ -805,19 +819,19 @@ handle_hover(ltk_menu *menu, int x, int y) {
        }
        
        static int
       -ltk_menu_motion_notify(ltk_widget *self, ltk_event *event) {
       -        handle_hover((ltk_menu *)self, event->motion.x, event->motion.y);
       +ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event) {
       +        handle_hover((ltk_menu *)self, event->x, event->y);
                return 1;
        }
        
        static int
       -ltk_menu_mouse_enter(ltk_widget *self, ltk_event *event) {
       -        handle_hover((ltk_menu *)self, event->motion.x, event->motion.y);
       +ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event) {
       +        handle_hover((ltk_menu *)self, event->x, event->y);
                return 1;
        }
        
        static int
       -ltk_menu_mouse_leave(ltk_widget *self, ltk_event *event) {
       +ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event) {
                (void)event;
                stop_scrolling((ltk_menu *)self);
                return 1;
   DIR diff --git a/src/scrollbar.c b/src/scrollbar.c
       t@@ -21,9 +21,6 @@
        #include <string.h>
        #include <stdarg.h>
        
       -#include <X11/Xlib.h>
       -#include <X11/Xutil.h>
       -
        #include "event.h"
        #include "memory.h"
        #include "color.h"
       t@@ -37,8 +34,8 @@
        #define MAX_SCROLLBAR_WIDTH 100 /* completely arbitrary */
        
        static void ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip);
       -static int ltk_scrollbar_mouse_press(ltk_widget *self, ltk_event *event);
       -static int ltk_scrollbar_motion_notify(ltk_widget *self, ltk_event *event);
       +static int ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event);
       +static int ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event);
        static void ltk_scrollbar_destroy(ltk_widget *self, int shallow);
        
        static struct ltk_widget_vtable vtable = {
       t@@ -50,6 +47,7 @@ static struct ltk_widget_vtable vtable = {
                .mouse_press = &ltk_scrollbar_mouse_press,
                .mouse_release = NULL,
                .motion_notify = &ltk_scrollbar_motion_notify,
       +        .get_child_at_pos = NULL,
                .mouse_leave = NULL,
                .mouse_enter = NULL,
                .child_size_change = NULL,
       t@@ -135,6 +133,7 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) {
                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;
       t@@ -143,6 +142,7 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) {
                        bg = &theme.bg_normal;
                        fg = &theme.fg_pressed;
                        break;
       +        case LTK_HOVER:
                case LTK_ACTIVE:
                        bg = &theme.bg_normal;
                        fg = &theme.fg_active;
       t@@ -165,12 +165,12 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) {
        }
        
        static int
       -ltk_scrollbar_mouse_press(ltk_widget *self, ltk_event *event) {
       +ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) {
                ltk_scrollbar *sc = (ltk_scrollbar *)self;
                int max_pos;
       -        if (event->button.button != LTK_BUTTONL)
       +        if (event->button != LTK_BUTTONL)
                        return 0;
       -        int ex = event->button.x, ey = event->button.y;
       +        int ex = event->x, ey = event->y;
                ltk_rect handle_rect = get_handle_rect(sc);
                if (sc->orient == LTK_HORIZONTAL) {
                        if (ex < handle_rect.x || ex > handle_rect.x + handle_rect.w) {
       t@@ -188,8 +188,8 @@ ltk_scrollbar_mouse_press(ltk_widget *self, ltk_event *event) {
                else if (sc->cur_pos > max_pos)
                        sc->cur_pos = max_pos;
                sc->callback(sc->callback_data);
       -        sc->last_mouse_x = event->button.x;
       -        sc->last_mouse_y = event->button.y;
       +        sc->last_mouse_x = event->x;
       +        sc->last_mouse_y = event->y;
                return 1;
        }
        
       t@@ -219,20 +219,20 @@ ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) {
        }
        
        static int
       -ltk_scrollbar_motion_notify(ltk_widget *self, ltk_event *event) {
       +ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) {
                ltk_scrollbar *sc = (ltk_scrollbar *)self;
                int delta;
                if (self->state != LTK_PRESSED) {
                        return 1;
                }
                if (sc->orient == LTK_HORIZONTAL)
       -                delta = event->button.x - sc->last_mouse_x;
       +                delta = event->x - sc->last_mouse_x;
                else
       -                delta = event->button.y - sc->last_mouse_y;
       +                delta = event->y - sc->last_mouse_y;
                ltk_scrollbar_scroll(self, delta, 1);
       -        sc->last_mouse_x = event->button.x;
       -        sc->last_mouse_y = event->button.y;
       -        return 0;
       +        sc->last_mouse_x = event->x;
       +        sc->last_mouse_y = event->y;
       +        return 1;
        }
        
        ltk_scrollbar *
   DIR diff --git a/src/widget.c b/src/widget.c
       t@@ -126,55 +126,111 @@ ltk_widget_change_state(ltk_widget *widget) {
                        ltk_window_invalidate_rect(widget->window, widget->rect);
        }
        
       +static ltk_widget *
       +get_widget_under_pointer(ltk_widget *widget, int x, int y) {
       +        ltk_widget *next = NULL;
       +        while (widget && widget->vtable->get_child_at_pos) {
       +                next = widget->vtable->get_child_at_pos(widget, x, y);
       +                if (!next)
       +                        break;
       +                else
       +                        widget = next;
       +        }
       +        return widget;
       +}
       +
       +static ltk_widget *
       +get_hover_popup(ltk_window *window, int x, int y) {
       +        for (size_t i = window->popups_num; i-- > 0;) {
       +                if (ltk_collide_rect(window->popups[i]->rect, x, y))
       +                        return window->popups[i];
       +        }
       +        return NULL;
       +}
       +
       +/* FIXME: This is still weird. */
        void
       -ltk_widget_mouse_press_event(ltk_widget *widget, ltk_event *event) {
       -        if (!widget || widget->state == LTK_DISABLED)
       +ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
       +        ltk_widget *widget = get_hover_popup(window, event->x, event->y);
       +        if (!widget) {
       +                ltk_window_unregister_all_popups(window);
       +                widget = window->root_widget;
       +        }
       +        if (!widget)
                        return;
       -        int default_handler = 1;
       -        if (widget->vtable->mouse_press)
       -                default_handler = widget->vtable->mouse_press(widget, event);
       -        if (default_handler) {
       -                if (event->button.button == LTK_BUTTONL)
       -                        ltk_window_set_pressed_widget(widget->window, widget);
       +        ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y);
       +        int first = 1;
       +        while (cur_widget) {
       +                int handled = 0;
       +                if (cur_widget->state != LTK_DISABLED) {
       +                        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) {
       +                                ltk_window_set_pressed_widget(window, cur_widget);
       +                                first = 0;
       +                        }
       +                }
       +                if (!handled)
       +                        cur_widget = cur_widget->parent;
       +                else
       +                        break;
                }
        }
        
        void
       -ltk_widget_mouse_release_event(ltk_widget *widget, ltk_event *event) {
       -        if (!widget || widget->state == LTK_DISABLED)
       -                return;
       -        if (widget->vtable->mouse_release)
       +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;
       +        if (widget && widget->vtable->mouse_release)
                        widget->vtable->mouse_release(widget, event);
       -        ltk_window_set_pressed_widget(widget->window, NULL);
       +        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);
       +        }
        }
        
        void
       -ltk_widget_motion_notify_event(ltk_widget *widget, ltk_event *event) {
       -        /* FIXME: THIS WHOLE STATE HANDLING IS STILL PARTIALLY BROKEN */
       -        /* FIXME: need to bring back hover state to make enter/leave work properly */
       -        /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
       -        /* (especially once keyboard navigation is added) */
       -        /* Also, enter/leave should probably be called for all in hierarchy */
       -        int set_active = 1;
       -        if (widget->window->pressed_widget && widget->window->pressed_widget->vtable->motion_notify) {
       -                widget->window->pressed_widget->vtable->motion_notify(widget->window->pressed_widget, event);
       -                set_active = 0;
       -        } else if (widget && widget->state != LTK_DISABLED) {
       -                /* FIXME: because only the bottom widget of the hierarchy is stored,
       -                   this *really* does not work properly! */
       -                if (widget != widget->window->active_widget) {
       -                        if (widget->window->active_widget && widget->window->active_widget->vtable->mouse_leave) {
       -                                widget->window->active_widget->vtable->mouse_leave(widget->window->active_widget, event);
       -                        }
       -                        if (widget->vtable->mouse_enter) {
       -                                widget->vtable->mouse_enter(widget, event);
       +ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) {
       +        ltk_widget *widget = get_hover_popup(window, event->x, event->y);
       +        if (!widget) {
       +                widget = window->pressed_widget;
       +                if (widget) {
       +                        if (widget->vtable->motion_notify)
       +                                widget->vtable->motion_notify(widget, event);
       +                        return;
       +                }
       +                widget = window->root_widget;
       +        }
       +        if (!widget)
       +                return;
       +        if (!ltk_collide_rect(widget->rect, event->x, event->y)) {
       +                ltk_window_set_hover_widget(widget->window, NULL, event);
       +                return;
       +        }
       +        ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y);
       +        int first = 1;
       +        while (cur_widget) {
       +                int handled = 0;
       +                if (cur_widget->state != LTK_DISABLED) {
       +                        if (cur_widget->vtable->motion_notify)
       +                                handled = cur_widget->vtable->motion_notify(cur_widget, 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) {
       +                                ltk_window_set_hover_widget(window, cur_widget, event);
       +                                first = 0;
                                }
                        }
       -                if (widget->vtable->motion_notify)
       -                        set_active = widget->vtable->motion_notify(widget, event);
       +                if (!handled)
       +                        cur_widget = cur_widget->parent;
       +                else
       +                        break;
                }
       -        if (set_active)
       -                ltk_window_set_active_widget(widget->window, widget);
        }
        
        int
   DIR diff --git a/src/widget.h b/src/widget.h
       t@@ -38,6 +38,7 @@ typedef enum {
        
        typedef enum {
                LTK_NORMAL,
       +        LTK_HOVER,
                LTK_PRESSED,
                LTK_ACTIVE,
                LTK_DISABLED
       t@@ -89,11 +90,11 @@ struct ltk_widget {
        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_event *);
       -        int (*mouse_release) (struct ltk_widget *, ltk_event *);
       -        int (*motion_notify) (struct ltk_widget *, ltk_event *);
       -        int (*mouse_leave) (struct ltk_widget *, ltk_event *);
       -        int (*mouse_enter) (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 *);
       t@@ -102,7 +103,9 @@ struct ltk_widget_vtable {
                void (*destroy) (struct ltk_widget *, int);
        
                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 **);
       +        struct ltk_widget *(*get_child_at_pos)(struct ltk_widget *, int x, int y);
        
                ltk_widget_type type;
                char needs_redraw;
       t@@ -114,9 +117,10 @@ int ltk_widget_destroy_cmd(struct ltk_window *window, char **tokens, size_t num_
        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_mouse_press_event(ltk_widget *widget, ltk_event *event);
       -void ltk_widget_mouse_release_event(ltk_widget *widget, ltk_event *event);
       -void ltk_widget_motion_notify_event(ltk_widget *widget, ltk_event *event);
       +/* 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);
        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);