ltk

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

commit bf6d516dc0a01db18fb93ddb4f153a454b36700b
parent 994e235d9939788238dd2c66cdefb4cc4ade1bce
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:
MMakefile | 11++++++-----
Msrc/box.c | 71+++++++++++++++++++----------------------------------------------------
Msrc/button.c | 19++++++++++++++-----
Msrc/grid.c | 59++++++++++-------------------------------------------------
Msrc/label.c | 1+
Msrc/ltk.h | 2++
Msrc/ltkd.c | 73++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/menu.c | 52+++++++++++++++++++++++++++++++++-------------------
Msrc/scrollbar.c | 32++++++++++++++++----------------
Msrc/widget.c | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/widget.h | 20++++++++++++--------
11 files changed, 241 insertions(+), 227 deletions(-)

diff --git a/Makefile b/Makefile @@ -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) @@ -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` diff --git a/src/box.c b/src/box.c @@ -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, @@ -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, @@ -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( diff --git a/src/button.c b/src/button.c @@ -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); @@ -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, @@ -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; @@ -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}, @@ -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; @@ -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; } diff --git a/src/grid.c b/src/grid.c @@ -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, @@ -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, @@ -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] */ diff --git a/src/label.c b/src/label.c @@ -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, diff --git a/src/ltk.h b/src/ltk.h @@ -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); @@ -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); diff --git a/src/ltkd.c b/src/ltkd.c @@ -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 */ @@ -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; @@ -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) @@ -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; @@ -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) diff --git a/src/menu.c b/src/menu.c @@ -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); @@ -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, @@ -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 @@ -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 */ @@ -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; @@ -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; @@ -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 */ @@ -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; diff --git a/src/scrollbar.c b/src/scrollbar.c @@ -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" @@ -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 = { @@ -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, @@ -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; @@ -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; @@ -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) { @@ -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; } @@ -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 * diff --git a/src/widget.c b/src/widget.c @@ -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 diff --git a/src/widget.h b/src/widget.h @@ -38,6 +38,7 @@ typedef enum { typedef enum { LTK_NORMAL, + LTK_HOVER, LTK_PRESSED, LTK_ACTIVE, LTK_DISABLED @@ -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 *); @@ -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; @@ -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);