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 b610b280bbdbda3dba8f8fc3e67961f26857da43
parent 99773bbc2bcaea288c5d8b657bdff74ae69e216a
Author: lumidify <nobody@lumidify.org>
Date:   Wed, 16 Aug 2023 22:11:10 +0200

Add double/triple-click; add explicit scroll event

Diffstat:
Msrc/array.h | 65++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/box.c | 16++++++++--------
Msrc/entry.c | 2++
Msrc/event.h | 10++++++++--
Msrc/event_xlib.c | 187++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/eventdefs.h | 18+++++++++++++-----
Msrc/grid.c | 1+
Msrc/ltkd.c | 11++++++++---
Msrc/menu.c | 46+++++++++++++++++++++-------------------------
Msrc/proto_types.h | 45++++++++++++++++++++++++++++-----------------
Msrc/scrollbar.c | 2+-
Msrc/widget.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/widget.h | 3+++
13 files changed, 394 insertions(+), 123 deletions(-)

diff --git a/src/array.h b/src/array.h @@ -30,27 +30,34 @@ #include "util.h" #include "memory.h" -#define LTK_ARRAY_INIT_DECL_BASE(name, type, storage) \ -typedef struct { \ - type *buf; \ - size_t buf_size; \ - size_t len; \ -} ltk_array_##name; \ - \ -storage ltk_array_##name *ltk_array_create_##name(size_t initial_len); \ -storage type ltk_array_pop_##name(ltk_array_##name *ar); \ -storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len); \ -storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len); \ -storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size); \ -storage void ltk_array_destroy_##name(ltk_array_##name *ar); \ -storage void ltk_array_clear_##name(ltk_array_##name *ar); \ -storage void ltk_array_append_##name(ltk_array_##name *ar, type elem); \ -storage void ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)); \ -storage type ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index); \ -storage void ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e); +/* FIXME: make this work on more compilers? */ +#if (defined(__GNUC__) || defined(__clang__)) +#define LTK_UNUSED_FUNC __attribute__((unused)) +#else +#define LTK_UNUSED_FUNC +#endif + +#define LTK_ARRAY_INIT_DECL_BASE(name, type, storage) \ +typedef struct { \ + type *buf; \ + size_t buf_size; \ + size_t len; \ +} ltk_array_##name; \ + \ +LTK_UNUSED_FUNC storage ltk_array_##name *ltk_array_create_##name(size_t initial_len); \ +LTK_UNUSED_FUNC storage type ltk_array_pop_##name(ltk_array_##name *ar); \ +LTK_UNUSED_FUNC storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len); \ +LTK_UNUSED_FUNC storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len); \ +LTK_UNUSED_FUNC storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size); \ +LTK_UNUSED_FUNC storage void ltk_array_destroy_##name(ltk_array_##name *ar); \ +LTK_UNUSED_FUNC storage void ltk_array_clear_##name(ltk_array_##name *ar); \ +LTK_UNUSED_FUNC storage void ltk_array_append_##name(ltk_array_##name *ar, type elem); \ +LTK_UNUSED_FUNC storage void ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)); \ +LTK_UNUSED_FUNC storage type ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index); \ +LTK_UNUSED_FUNC storage void ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e); #define LTK_ARRAY_INIT_IMPL_BASE(name, type, storage) \ -storage ltk_array_##name * \ +LTK_UNUSED_FUNC storage ltk_array_##name * \ ltk_array_create_##name(size_t initial_len) { \ if (initial_len == 0) \ ltk_fatal("Array length is zero\n"); \ @@ -61,7 +68,7 @@ ltk_array_create_##name(size_t initial_len) { \ return ar; \ } \ \ -storage type \ +LTK_UNUSED_FUNC storage type \ ltk_array_pop_##name(ltk_array_##name *ar) { \ if (ar->len == 0) \ ltk_fatal("Array empty; cannot pop.\n"); \ @@ -70,7 +77,7 @@ ltk_array_pop_##name(ltk_array_##name *ar) { \ } \ \ /* FIXME: having this function in the public interface is ugly */ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) { \ if (index > ar->len) \ ltk_fatal("Array index out of bounds\n"); \ @@ -82,7 +89,7 @@ ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) { (ar->len - len - index) * sizeof(type)); \ } \ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len) { \ ltk_array_prepare_gap_##name(ar, index, len); \ for (size_t i = 0; i < len; i++) { \ @@ -90,20 +97,20 @@ ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t l } \ } \ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_append_##name(ltk_array_##name *ar, type elem) { \ if (ar->len == ar->buf_size) \ ltk_array_resize_##name(ar, ar->len + 1); \ ar->buf[ar->len++] = elem; \ } \ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_clear_##name(ltk_array_##name *ar) { \ ar->len = 0; \ ltk_array_resize_##name(ar, 1); \ } \ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_resize_##name(ltk_array_##name *ar, size_t len) { \ size_t new_size = ideal_array_size(ar->buf_size, len); \ if (new_size != ar->buf_size) { \ @@ -113,7 +120,7 @@ ltk_array_resize_##name(ltk_array_##name *ar, size_t len) { \ } \ } \ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_destroy_##name(ltk_array_##name *ar) { \ if (!ar) \ return; \ @@ -121,7 +128,7 @@ ltk_array_destroy_##name(ltk_array_##name *ar) { \ ltk_free(ar); \ } \ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)) { \ if (!ar) \ return; \ @@ -131,14 +138,14 @@ ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)) ltk_array_destroy_##name(ar); \ } \ \ -storage type \ +LTK_UNUSED_FUNC storage type \ ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index) { \ if (index >= ar->len) \ ltk_fatal("Index out of bounds.\n"); \ return ar->buf[index]; \ } \ \ -storage void \ +LTK_UNUSED_FUNC storage void \ ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e) { \ if (index >= ar->len) \ ltk_fatal("Index out of bounds.\n"); \ diff --git a/src/box.c b/src/box.c @@ -40,7 +40,7 @@ static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, uns static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err); /* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, ltk_error *err); */ static void ltk_box_scroll(ltk_widget *self); -static int ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event); +static int ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event); static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y); static void ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r); @@ -65,7 +65,8 @@ static struct ltk_widget_vtable vtable = { .remove_child = &ltk_box_remove, .key_press = NULL, .key_release = NULL, - .mouse_press = &ltk_box_mouse_press, + .mouse_press = NULL, + .mouse_scroll = &ltk_box_mouse_scroll, .mouse_release = NULL, .motion_notify = NULL, .get_child_at_pos = &ltk_box_get_child_at_pos, @@ -471,19 +472,18 @@ ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) { } static int -ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event) { +ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) { ltk_box *box = (ltk_box *)self; - /* FIXME: combine multiple events into one for efficiency */ - if (event->button == LTK_BUTTON4 || event->button == LTK_BUTTON5) { + if (event->dy) { + /* FIXME: horizontal scrolling, etc. */ /* FIXME: configure scrollstep */ - int delta = event->button == LTK_BUTTON4 ? -15 : 15; + int delta = event->dy * -15; ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0); ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y); ltk_window_fake_motion_event(self->window, glob.x, glob.y); return 1; - } else { - return 0; } + return 0; } /* box <box id> add <widget id> [sticky] */ diff --git a/src/entry.c b/src/entry.c @@ -53,6 +53,8 @@ static int ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event); static int ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event); static int ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event); +/* FIXME: give entire key event, not just text */ +/* FIXME: also allow binding key release, not just press */ typedef void (*cb_func)(ltk_entry *, char *, size_t); /* FIXME: configure mouse actions, e.g. select-word-under-pointer, move-cursor-to-pointer */ diff --git a/src/event.h b/src/event.h @@ -12,6 +12,12 @@ typedef struct { typedef struct { ltk_event_type type; int x, y; + int dx, dy; +} ltk_scroll_event; + +typedef struct { + ltk_event_type type; + int x, y; } ltk_motion_event; typedef struct { @@ -43,6 +49,7 @@ typedef struct { typedef union { ltk_event_type type; ltk_button_event button; + ltk_scroll_event scroll; ltk_motion_event motion; ltk_key_event key; ltk_configure_event configure; @@ -52,10 +59,9 @@ typedef union { #include "ltk.h" -int ltk_events_pending(ltk_renderdata *renderdata); void ltk_events_cleanup(void); /* WARNING: Text returned in key and keyboard events must be copied before calling this function again! */ -void ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event); +int ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event); void ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event); #endif /* LTK_EVENT_H */ diff --git a/src/event_xlib.c b/src/event_xlib.c @@ -13,11 +13,35 @@ static char *text = NULL; static size_t text_alloc = 0; static char *cur_kbd = NULL; - -int -ltk_events_pending(ltk_renderdata *renderdata) { - return XPending(renderdata->dpy); -} +/* FIXME: support more buttons? + -> What even makes sense here? Mice can support a bunch + of buttons, but what is sensible here? Just adding + support for higher button numbers would cause problems + when adding other backends (e.g. SDL) that might do + things completely differently. */ +/* FIXME: support touch events? */ +/* times of last button press/release, + used to implement double/triple-click */ +static Time last_button_press[] = {0, 0, 0}; +static Time last_button_release[] = {0, 0, 0}; +/* positions of last press/release so double/triple-click is + only generated when the position is near enough */ +static struct point { + int x; + int y; +} press_pos[] = {{0, 0}, {0, 0}, {0, 0}}; +static struct point release_pos[] = {{0, 0}, {0, 0}, {0, 0}}; +/* stores whether the last button press already was + a double-click to decide if a triple-click should + be generated (same for release) */ +static int was_2press[] = {0, 0, 0}; +static int was_2release[] = {0, 0, 0}; +/* Used to store special next event - currently just + used to implement double/triple-click because the + actual double/triple-click/release event is + generated in addition to the regular press/release */ +static int next_event_valid = 0; +static ltk_button_event next_event; void ltk_events_cleanup(void) { @@ -34,10 +58,6 @@ get_button(unsigned int button) { case Button1: return LTK_BUTTONL; case Button2: return LTK_BUTTONM; case Button3: return LTK_BUTTONR; - case 4: return LTK_BUTTON4; - case 5: return LTK_BUTTON5; - case 6: return LTK_BUTTON6; - case 7: return LTK_BUTTON7; default: return LTK_BUTTONL; /* FIXME: what to do here? */ } } @@ -154,35 +174,138 @@ ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event) { XkbFreeKeyboard(desc, XkbAllComponentsMask, True); } -void -ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) { +#define DISTSQ(x0, y0, x1, y1) (((x1) - (x0)) * ((x1) - (x0)) + ((y1) - (y0)) * ((y1) - (y0))) +/* return value 0 means valid event returned, + 1 means no events pending, + 2 means event discarded (need to call again) */ +static int +next_event_base(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) { + if (next_event_valid) { + next_event_valid = 0; + *event = (ltk_event){.button = next_event}; + return 0; + } XEvent xevent; + if (!XPending(renderdata->dpy)) + return 1; XNextEvent(renderdata->dpy, &xevent); if (renderdata->xkb_supported && xevent.type == renderdata->xkb_event_type) { ltk_generate_keyboard_event(renderdata, event); - return; + return 0; } *event = (ltk_event){.type = LTK_UNKNOWN_EVENT}; if (XFilterEvent(&xevent, None)) - return; + return 2; + int button = 0; switch (xevent.type) { case ButtonPress: - *event = (ltk_event){.button = { - .type = LTK_BUTTONPRESS_EVENT, - .button = get_button(xevent.xbutton.button), - .x = xevent.xbutton.x, - .y = xevent.xbutton.y - }}; + button = xevent.xbutton.button; + /* FIXME: are the buttons really always defined as exactly these values? */ + if (button >= 1 && button <= 3) { + if (xevent.xbutton.time - last_button_press[button] <= DOUBLECLICK_TIME && + DISTSQ(press_pos[button].x, press_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) { + if (was_2press[button]) { + /* reset so normal press is sent again next time */ + was_2press[button] = 0; + last_button_press[button] = 0; + next_event = (ltk_button_event){ + .type = LTK_3BUTTONPRESS_EVENT, + .button = get_button(button), + .x = xevent.xbutton.x, + .y = xevent.xbutton.y + }; + } else { + was_2press[button] = 1; + last_button_press[button] = xevent.xbutton.time; + next_event = (ltk_button_event){ + .type = LTK_2BUTTONPRESS_EVENT, + .button = get_button(button), + .x = xevent.xbutton.x, + .y = xevent.xbutton.y + }; + } + next_event_valid = 1; + } else { + last_button_press[button] = xevent.xbutton.time; + } + *event = (ltk_event){.button = { + .type = LTK_BUTTONPRESS_EVENT, + .button = get_button(button), + .x = xevent.xbutton.x, + .y = xevent.xbutton.y + }}; + press_pos[button].x = xevent.xbutton.x; + press_pos[button].y = xevent.xbutton.y; + } else if (button >= 4 && button <= 7) { + /* FIXME: compress multiple scroll events into one */ + *event = (ltk_event){.scroll = { + .type = LTK_SCROLL_EVENT, + .x = xevent.xbutton.x, + .y = xevent.xbutton.y, + .dx = 0, + .dy = 0 + }}; + switch (button) { + case 4: + event->scroll.dy = 1; + break; + case 5: + event->scroll.dy = -1; + break; + case 6: + event->scroll.dx = -1; + break; + case 7: + event->scroll.dx = 1; + break; + } + } else { + return 2; + } break; case ButtonRelease: - *event = (ltk_event){.button = { - .type = LTK_BUTTONRELEASE_EVENT, - .button = get_button(xevent.xbutton.button), - .x = xevent.xbutton.x, - .y = xevent.xbutton.y - }}; + button = xevent.xbutton.button; + if (button >= 1 && button <= 3) { + if (xevent.xbutton.time - last_button_release[button] <= DOUBLECLICK_TIME && + DISTSQ(release_pos[button].x, release_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) { + if (was_2release[button]) { + /* reset so normal release is sent again next time */ + was_2release[button] = 0; + last_button_release[button] = 0; + next_event = (ltk_button_event){ + .type = LTK_3BUTTONRELEASE_EVENT, + .button = get_button(button), + .x = xevent.xbutton.x, + .y = xevent.xbutton.y + }; + } else { + was_2release[button] = 1; + last_button_release[button] = xevent.xbutton.time; + next_event = (ltk_button_event){ + .type = LTK_2BUTTONRELEASE_EVENT, + .button = get_button(button), + .x = xevent.xbutton.x, + .y = xevent.xbutton.y + }; + } + next_event_valid = 1; + } else { + last_button_release[button] = xevent.xbutton.time; + } + *event = (ltk_event){.button = { + .type = LTK_BUTTONRELEASE_EVENT, + .button = get_button(button), + .x = xevent.xbutton.x, + .y = xevent.xbutton.y + }}; + release_pos[button].x = xevent.xbutton.x; + release_pos[button].y = xevent.xbutton.y; + } else { + return 2; + } break; case MotionNotify: + /* FIXME: compress motion events */ *event = (ltk_event){.motion = { .type = LTK_MOTION_EVENT, .x = xevent.xmotion.x, @@ -216,13 +339,27 @@ ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) .w = xevent.xexpose.width, .h = xevent.xexpose.height }}; + } else { + return 2; } break; case ClientMessage: if ((Atom)xevent.xclient.data.l[0] == renderdata->wm_delete_msg) *event = (ltk_event){.type = LTK_WINDOWCLOSE_EVENT}; + else + return 2; break; default: break; } + return 0; +} + +int +ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) { + int ret = 0; + while ((ret = next_event_base(renderdata, lang_index, event)) == 2) { + /* NOP */ + } + return ret; } diff --git a/src/eventdefs.h b/src/eventdefs.h @@ -1,10 +1,23 @@ #ifndef LTK_EVENTDEFS_H #define LTK_EVENTDEFS_H +/* FIXME: add to config */ +#define DOUBLECLICK_TIME 250 +/* square of distance to make calculation simpler */ +#define DOUBLECLICK_DISTSQ 25 +/* FIXME: reduce amount of scroll/motion events */ + typedef enum { LTK_UNKNOWN_EVENT, /* FIXME: a bit weird */ LTK_BUTTONPRESS_EVENT, LTK_BUTTONRELEASE_EVENT, + /* double-click/release */ + LTK_2BUTTONPRESS_EVENT, + LTK_2BUTTONRELEASE_EVENT, + /* triple-click/release */ + LTK_3BUTTONPRESS_EVENT, + LTK_3BUTTONRELEASE_EVENT, + LTK_SCROLL_EVENT, LTK_MOTION_EVENT, LTK_KEYPRESS_EVENT, LTK_KEYRELEASE_EVENT, @@ -20,11 +33,6 @@ typedef enum { LTK_BUTTONL, LTK_BUTTONM, LTK_BUTTONR, - /* FIXME: dedicated scroll event */ - LTK_BUTTON4, - LTK_BUTTON5, - LTK_BUTTON6, - LTK_BUTTON7 } ltk_button_type; /* FIXME: just steal the definitions from X when using Xlib so no conversion is necessary? */ diff --git a/src/grid.c b/src/grid.c @@ -72,6 +72,7 @@ static struct ltk_widget_vtable vtable = { .child_size_change = &ltk_grid_child_size_change, .remove_child = &ltk_grid_ungrid, .mouse_press = NULL, + .mouse_scroll = NULL, .mouse_release = NULL, .motion_notify = NULL, .get_child_at_pos = &ltk_grid_get_child_at_pos, diff --git a/src/ltkd.c b/src/ltkd.c @@ -450,10 +450,8 @@ ltk_mainloop(ltk_window *window) { /* value of tv doesn't really matter anymore here because the necessary framerate-limiting delay is already done */ wretval = select(sock_state.maxfd + 1, NULL, &wfds, NULL, &tv); - while (ltk_events_pending(window->renderdata)) { - ltk_next_event(window->renderdata, window->cur_kbd, &event); + while (!ltk_next_event(window->renderdata, window->cur_kbd, &event)) ltk_handle_event(window, &event); - } if (rretval > 0 || (sock_write_available && wretval > 0)) { if (FD_ISSET(sock_state.listenfd, &rfds)) { @@ -1222,9 +1220,16 @@ ltk_handle_event(ltk_window *window, ltk_event *event) { ltk_window_key_release_event(window, &event->key); break; case LTK_BUTTONPRESS_EVENT: + case LTK_2BUTTONPRESS_EVENT: + case LTK_3BUTTONPRESS_EVENT: ltk_window_mouse_press_event(window, &event->button); break; + case LTK_SCROLL_EVENT: + ltk_window_mouse_scroll_event(window, &event->scroll); + break; case LTK_BUTTONRELEASE_EVENT: + case LTK_2BUTTONRELEASE_EVENT: + case LTK_3BUTTONRELEASE_EVENT: ltk_window_mouse_release_event(window, &event->button); break; case LTK_MOTION_EVENT: diff --git a/src/menu.c b/src/menu.c @@ -16,6 +16,10 @@ /* NOTE: The implementation of menus and menu entries is a collection of ugly hacks. */ +/* FIXME: parent is pressed when scroll arrows pressed */ +/* -> this is because the pressed handling checks if the widget is activatable, then goes to the parent, + but the child isn't geometrically in the parent here, so that's weird */ + #include <stdio.h> #include <stdlib.h> #include <stdint.h> @@ -94,7 +98,7 @@ static void ltk_menu_scroll_callback(void *data); static void stop_scrolling(ltk_menu *menu); 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_press(ltk_widget *self, ltk_button_event *event); +static int ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event); static void ltk_menu_hide(ltk_widget *self); static void popup_active_menu(ltk_menuentry *e); static void unpopup_active_entry(ltk_menuentry *e); @@ -136,7 +140,8 @@ static ltk_widget *ltk_menuentry_get_child(ltk_widget *self); static struct ltk_widget_vtable vtable = { .key_press = NULL, .key_release = NULL, - .mouse_press = &ltk_menu_mouse_press, + .mouse_press = NULL, + .mouse_scroll = &ltk_menu_mouse_scroll, .motion_notify = &ltk_menu_motion_notify, .mouse_release = NULL, .mouse_enter = &ltk_menu_mouse_enter, @@ -646,7 +651,8 @@ ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) { /* FIXME: make sure timers are always destroyed when widget is destroyed */ static int set_scroll_timer(ltk_menu *menu, int x, int y) { - if (!ltk_collide_rect(menu->widget.lrect, x, y)) + /* this check probably isn't necessary, but whatever */ + if (x < 0 || y < 0 || x >= menu->widget.lrect.w || y >= menu->widget.lrect.h) return 0; int t = 0, b = 0, l = 0,r = 0; struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; @@ -694,30 +700,20 @@ ltk_menuentry_release(ltk_widget *self) { } static int -ltk_menu_mouse_press(ltk_widget *self, ltk_button_event *event) { +ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) { ltk_menu *menu = (ltk_menu *)self; - /* FIXME: configure scroll step */ ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y); - switch (event->button) { - case LTK_BUTTON4: - ltk_menu_scroll(menu, 1, 0, 0, 0, 10); - ltk_window_fake_motion_event(self->window, glob.x, glob.y); - break; - case LTK_BUTTON5: - ltk_menu_scroll(menu, 0, 1, 0, 0, 10); - ltk_window_fake_motion_event(self->window, glob.x, glob.y); - break; - case LTK_BUTTON6: - ltk_menu_scroll(menu, 0, 0, 1, 0, 10); - ltk_window_fake_motion_event(self->window, glob.x, glob.y); - break; - case LTK_BUTTON7: - ltk_menu_scroll(menu, 0, 0, 0, 1, 10); - ltk_window_fake_motion_event(self->window, glob.x, glob.y); - break; - default: - return 0; - } + /* FIXME: configure scroll step */ + /* FIXME: fix the interface for ltk_menu_scroll */ + if (event->dx > 0) + ltk_menu_scroll(menu, 0, 0, 0, 1, event->dx * 10); + else if (event->dx < 0) + ltk_menu_scroll(menu, 0, 0, 1, 0, -event->dx * 10); + if (event->dy > 0) + ltk_menu_scroll(menu, 1, 0, 0, 0, event->dy * 10); + else if (event->dy < 0) + ltk_menu_scroll(menu, 0, 1, 0, 0, -event->dy * 10); + ltk_window_fake_motion_event(self->window, glob.x, glob.y); return 1; } diff --git a/src/proto_types.h b/src/proto_types.h @@ -24,23 +24,34 @@ /* P == protocol; W == widget */ -#define LTK_PEVENT_MOUSEPRESS 0 -#define LTK_PEVENT_MOUSERELEASE 1 -#define LTK_PEVENT_MOUSEMOTION 2 -#define LTK_PEVENT_KEYPRESS 3 -#define LTK_PEVENT_KEYRELEASE 4 -#define LTK_PEVENT_CONFIGURE 5 -#define LTK_PEVENT_STATECHANGE 6 - -#define LTK_PEVENTMASK_NONE (UINT32_C(0)) -#define LTK_PEVENTMASK_MOUSEPRESS (UINT32_C(1) << LTK_PEVENT_MOUSEPRESS) -#define LTK_PEVENTMASK_MOUSERELEASE (UINT32_C(1) << LTK_PEVENT_MOUSERELEASE) -#define LTK_PEVENTMASK_MOUSEMOTION (UINT32_C(1) << LTK_PEVENT_MOUSEMOTION) -#define LTK_PEVENTMASK_KEYPRESS (UINT32_C(1) << LTK_PEVENT_KEYPRESS) -#define LTK_PEVENTMASK_KEYRELEASE (UINT32_C(1) << LTK_PEVENT_KEYRELEASE) -#define LTK_PEVENTMASK_CONFIGURE (UINT32_C(1) << LTK_PEVENT_CONFIGURE) -#define LTK_PEVENTMASK_EXPOSE (UINT32_C(1) << LTK_PEVENT_EXPOSE) -#define LTK_PEVENTMASK_STATECHANGE (UINT32_C(1) << LTK_PEVENT_STATECHANGE) +#define LTK_PEVENT_MOUSEPRESS 0 +#define LTK_PEVENT_2MOUSEPRESS 1 +#define LTK_PEVENT_3MOUSEPRESS 2 +#define LTK_PEVENT_MOUSERELEASE 3 +#define LTK_PEVENT_2MOUSERELEASE 4 +#define LTK_PEVENT_3MOUSERELEASE 5 +#define LTK_PEVENT_MOUSEMOTION 6 +#define LTK_PEVENT_MOUSESCROLL 7 +#define LTK_PEVENT_KEYPRESS 8 +#define LTK_PEVENT_KEYRELEASE 9 +#define LTK_PEVENT_CONFIGURE 10 +#define LTK_PEVENT_STATECHANGE 11 + +/* FIXME: standardize names - internally, buttonpress is used, here it's mousepress... */ +#define LTK_PEVENTMASK_NONE (UINT32_C(0)) +#define LTK_PEVENTMASK_MOUSEPRESS (UINT32_C(1) << LTK_PEVENT_MOUSEPRESS) +#define LTK_PEVENTMASK_2MOUSEPRESS (UINT32_C(1) << LTK_PEVENT_2MOUSEPRESS) +#define LTK_PEVENTMASK_3MOUSEPRESS (UINT32_C(1) << LTK_PEVENT_3MOUSEPRESS) +#define LTK_PEVENTMASK_MOUSERELEASE (UINT32_C(1) << LTK_PEVENT_MOUSERELEASE) +#define LTK_PEVENTMASK_2MOUSERELEASE (UINT32_C(1) << LTK_PEVENT_2MOUSERELEASE) +#define LTK_PEVENTMASK_3MOUSERELEASE (UINT32_C(1) << LTK_PEVENT_3MOUSERELEASE) +#define LTK_PEVENTMASK_MOUSEMOTION (UINT32_C(1) << LTK_PEVENT_MOUSEMOTION) +#define LTK_PEVENTMASK_KEYPRESS (UINT32_C(1) << LTK_PEVENT_KEYPRESS) +#define LTK_PEVENTMASK_KEYRELEASE (UINT32_C(1) << LTK_PEVENT_KEYRELEASE) +#define LTK_PEVENTMASK_CONFIGURE (UINT32_C(1) << LTK_PEVENT_CONFIGURE) +#define LTK_PEVENTMASK_EXPOSE (UINT32_C(1) << LTK_PEVENT_EXPOSE) +#define LTK_PEVENTMASK_STATECHANGE (UINT32_C(1) << LTK_PEVENT_STATECHANGE) +#define LTK_PEVENTMASK_MOUSESCROLL (UINT32_C(1) << LTK_PEVENT_MOUSESCROLL) #define LTK_PWEVENT_MENUENTRY_PRESS 0 #define LTK_PWEVENTMASK_MENUENTRY_NONE (UINT32_C(0)) diff --git a/src/scrollbar.c b/src/scrollbar.c @@ -165,7 +165,7 @@ static int ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) { ltk_scrollbar *sc = (ltk_scrollbar *)self; int max_pos; - if (event->button != LTK_BUTTONL) + if (event->button != LTK_BUTTONL || event->type != LTK_BUTTONPRESS_EVENT) return 0; int ex = event->x, ey = event->y; ltk_rect handle_rect = handle_get_rect(sc); diff --git a/src/widget.c b/src/widget.c @@ -469,14 +469,47 @@ is_parent(ltk_widget *parent, ltk_widget *child) { /* FIXME: fix global and local coordinates! */ static int -queue_mouse_event(ltk_widget *widget, char *type, uint32_t mask, int x, int y) { +queue_mouse_event(ltk_widget *widget, ltk_event_type type, int x, int y) { + uint32_t mask; + char *typename; + switch (type) { + case LTK_MOTION_EVENT: + mask = LTK_PEVENTMASK_MOUSEMOTION; + typename = "mousemotion"; + break; + case LTK_2BUTTONPRESS_EVENT: + mask = LTK_PEVENTMASK_2MOUSEPRESS; + typename = "2mousepress"; + break; + case LTK_3BUTTONPRESS_EVENT: + mask = LTK_PEVENTMASK_3MOUSEPRESS; + typename = "3mousepress"; + break; + case LTK_BUTTONRELEASE_EVENT: + mask = LTK_PEVENTMASK_MOUSERELEASE; + typename = "mouserelease"; + break; + case LTK_2BUTTONRELEASE_EVENT: + mask = LTK_PEVENTMASK_2MOUSERELEASE; + typename = "2mouserelease"; + break; + case LTK_3BUTTONRELEASE_EVENT: + mask = LTK_PEVENTMASK_3MOUSERELEASE; + typename = "3mouserelease"; + break; + case LTK_BUTTONPRESS_EVENT: + default: + mask = LTK_PEVENTMASK_MOUSEPRESS; + typename = "mousepress"; + break; + } int lock_client = -1; for (size_t i = 0; i < widget->masks_num; i++) { if (widget->event_masks[i].lmask & mask) { ltk_queue_sock_write_fmt( widget->event_masks[i].client, "eventl %s widget %s %d %d %d %d\n", - widget->id, type, x, y, x, y + widget->id, typename, x, y, x, y /* x - widget->rect.x, y - widget->rect.y */ ); lock_client = widget->event_masks[i].client; @@ -484,7 +517,37 @@ queue_mouse_event(ltk_widget *widget, char *type, uint32_t mask, int x, int y) { ltk_queue_sock_write_fmt( widget->event_masks[i].client, "event %s widget %s %d %d %d %d\n", - widget->id, type, x, y, x, y + widget->id, typename, x, y, x, y + /* x - widget->rect.x, y - widget->rect.y */ + ); + } + } + if (lock_client >= 0) { + if (ltk_handle_lock_client(widget->window, lock_client)) + return 1; + } + return 0; +} + +/* FIXME: global/local coords (like above) */ +static int +queue_scroll_event(ltk_widget *widget, int x, int y, int dx, int dy) { + uint32_t mask = LTK_PEVENTMASK_MOUSESCROLL; + int lock_client = -1; + for (size_t i = 0; i < widget->masks_num; i++) { + if (widget->event_masks[i].lmask & mask) { + ltk_queue_sock_write_fmt( + widget->event_masks[i].client, + "eventl %s widget %s %d %d %d %d %d %d\n", + widget->id, "mousescroll", x, y, x, y, dx, dy + /* x - widget->rect.x, y - widget->rect.y */ + ); + lock_client = widget->event_masks[i].client; + } else if (widget->event_masks[i].mask & mask) { + ltk_queue_sock_write_fmt( + widget->event_masks[i].client, + "event %s widget %s %d %d %d %d %d %d\n", + widget->id, "mousescroll", x, y, x, y, dx, dy /* x - widget->rect.x, y - widget->rect.y */ ); } @@ -1011,6 +1074,8 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0]))) ltk_window_unregister_all_popups(window); + /* FIXME: popups don't always have their children geometrically contained within parents, + so this won't work properly in all cases */ int first = 1; while (cur_widget) { int handled = 0; @@ -1020,13 +1085,13 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { 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 (queue_mouse_event(cur_widget, "mousepress", LTK_PEVENTMASK_MOUSEPRESS, event->x, event->y)) + if (queue_mouse_event(cur_widget, event->type, event->x, event->y)) handled = 1; else if (cur_widget->vtable->mouse_press) handled = cur_widget->vtable->mouse_press(cur_widget, event); /* set first non-disabled widget to pressed widget */ /* FIXME: use config values for all_activatable */ - if (first && event->button == LTK_BUTTONL && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { + if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { ltk_window_set_pressed_widget(window, cur_widget, 0); first = 0; } @@ -1039,6 +1104,35 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { } void +ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_event *event) { + /* FIXME: should it first be sent to pressed widget? */ + ltk_widget *widget = get_hover_popup(window, event->x, event->y); + if (!widget) + widget = window->root_widget; + if (!widget) + return; + int orig_x = event->x, orig_y = event->y; + ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y); + /* FIXME: same issue with popups like in mouse_press above */ + while (cur_widget) { + int handled = 0; + ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y); + event->x = local.x; + event->y = local.y; + if (cur_widget->state != LTK_DISABLED) { + if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy)) + handled = 1; + else if (cur_widget->vtable->mouse_scroll) + handled = cur_widget->vtable->mouse_scroll(cur_widget, event); + } + if (!handled) + cur_widget = cur_widget->parent; + else + break; + } +} + +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); @@ -1048,17 +1142,18 @@ void ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) { ltk_widget *widget = window->pressed_widget; int orig_x = event->x, orig_y = event->y; + /* FIXME: why does this only take pressed widget and popups into account? */ if (!widget) { widget = get_hover_popup(window, event->x, event->y); widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y); } /* FIXME: loop up to top of hierarchy if not handled */ - if (widget && queue_mouse_event(widget, "mouserelease", LTK_PEVENTMASK_MOUSERELEASE, event->x, event->y)) { + if (widget && queue_mouse_event(widget, event->type, event->x, event->y)) { /* NOP */ } else if (widget && widget->vtable->mouse_release) { widget->vtable->mouse_release(widget, event); } - if (event->button == LTK_BUTTONL) { + if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) { int release = 0; if (window->pressed_widget) { ltk_rect prect = window->pressed_widget->lrect; @@ -1104,7 +1199,7 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) { event->x = local.x; event->y = local.y; if (cur_widget->state != LTK_DISABLED) { - if (queue_mouse_event(cur_widget, "mousemotion", LTK_PEVENTMASK_MOUSEMOTION, event->x, event->y)) + if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y)) handled = 1; else if (cur_widget->vtable->motion_notify) handled = cur_widget->vtable->motion_notify(cur_widget, event); diff --git a/src/widget.h b/src/widget.h @@ -119,8 +119,10 @@ struct ltk_widget { struct ltk_widget_vtable { int (*key_press)(struct ltk_widget *, ltk_key_event *); int (*key_release)(struct ltk_widget *, ltk_key_event *); + /* press/release also receive double/triple-click/release */ int (*mouse_press)(struct ltk_widget *, ltk_button_event *); int (*mouse_release)(struct ltk_widget *, ltk_button_event *); + int (*mouse_scroll)(struct ltk_widget *, ltk_scroll_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 *); @@ -168,6 +170,7 @@ void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state); void ltk_window_key_press_event(ltk_window *window, ltk_key_event *event); void ltk_window_key_release_event(ltk_window *window, ltk_key_event *event); void ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event); +void ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_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);