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 6b058581e1cb02dd6d4775d8ab3f008bcc87829c
parent 610c7a836cb111096f1ae31d5190a2964ba25310
Author: lumidify <nobody@lumidify.org>
Date:   Sat, 23 Jul 2022 13:08:01 +0200

Implement key events and mappings

It's probably still very buggy.

Diffstat:
A.ltk/ltk.cfg | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M.ltk/theme.ini | 1-
MMakefile | 13+++++++++----
Msrc/box.c | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/button.c | 35++++++++++++++++-------------------
Asrc/config.c | 710+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/config.h | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/event.h | 57+++++++++++----------------------------------------------
Msrc/event_xlib.c | 148++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Asrc/eventdefs.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/graphics.h | 3++-
Msrc/graphics_xlib.c | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/grid.c | 211++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/label.c | 26++++++++++++++------------
Msrc/ltk.h | 7++++++-
Msrc/ltkd.c | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/memory.c | 35+++++++++++++++++++++++++++++++++++
Msrc/memory.h | 19++++++++++++++-----
Msrc/menu.c | 470++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/menu.h | 1+
Msrc/scrollbar.c | 46+++++++++++++++++++++++++---------------------
Msrc/theme.h | 2--
Msrc/util.c | 10++++++++++
Msrc/util.h | 11+++++++++++
Msrc/widget.c | 635+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/widget.h | 50+++++++++++++++++++++++++++++++++++++++-----------
Asrc/widget_config.h | 7+++++++
Msrc/xlib_shared.h | 6++++++
Mtest.gui | 7++++++-
Mtest.sh | 4----
Mtest2.gui | 1+
Atest3.gui | 13+++++++++++++
Atest3.sh | 20++++++++++++++++++++
Mtestbox.sh | 2+-
34 files changed, 2850 insertions(+), 384 deletions(-)

diff --git a/.ltk/ltk.cfg b/.ltk/ltk.cfg @@ -0,0 +1,65 @@ +[general] +explicit-focus = true +all-activatable = true +# In future: +# text-editor = ... +# line-editor = ... + +[key-binding] +# In future: +# bind edit-text-external ... +# bind edit-line-external ... +bind-keypress move-next sym tab +bind-keypress move-prev sym tab mods shift +bind-keypress move-next text n +bind-keypress move-prev text p +bind-keypress move-left sym left +bind-keypress move-right sym right +bind-keypress move-up sym up +bind-keypress move-down sym down +bind-keypress move-left text h +bind-keypress move-right text l +bind-keypress move-up text k +bind-keypress move-down text j +bind-keypress focus-active sym return +# FIXME: Test that this works properly once widgets that need +# focus are added - remove-popups should be called if escape +# wasn't handled already by unfocus-active. +bind-keypress unfocus-active sym escape +bind-keypress remove-popups sym escape +bind-keypress set-pressed sym return #flags run-always +bind-keyrelease unset-pressed sym return #flags run-always +# alternative: rawtext instead of text to ignore mapping + +# default mapping (just to silence warnings) +[key-mapping] +language = "English (US)" + +[key-mapping] +language = "German" +map "z" "y" +map "y" "z" +map "Z" "Y" +map "Y" "Z" +map "Ö" ":" +map "_" "?" +map "-" "/" +map "ä" "'" + +[key-mapping] +language = "Urdu (Pakistan)" +map "ج" "j" +map "ک" "k" +map "ح" "h" +map "ل" "l" +map "ن" "n" +map "پ" "p" + +[key-mapping] +language = "Hindi (Bolnagri)" +map "ज" "j" +map "क" "k" +map "ह" "h" +map "ल" "l" +map "न" "n" +map "प" "p" diff --git a/.ltk/theme.ini b/.ltk/theme.ini @@ -1,6 +1,5 @@ [window] font-size = 15 -border-width = 0 bg = #000000 fg = #FFFFFF font = Liberation Mono diff --git a/Makefile b/Makefile @@ -8,6 +8,7 @@ VERSION = -999-prealpha0 # FIXME: Using DEBUG here doesn't work because it somehow # interferes with a predefined macro, at least on OpenBSD. DEV = 0 +MEMDEBUG = 0 SANITIZE = 0 USE_PANGO = 0 @@ -15,7 +16,7 @@ USE_PANGO = 0 # debug DEV_CFLAGS_1 = -g -Wall -Wextra -pedantic -SANITIZE_FLAGS_1 = -fsanitize=address +SANITIZE_FLAGS_1 = -g -fsanitize=address # don't include default flags when debugging so possible # optimization flags don't interfere with it DEV_CFLAGS_0 = $(CFLAGS) @@ -33,7 +34,7 @@ EXTRA_OBJ = $(EXTRA_OBJ_$(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_CFLAGS = $(EXTRA_CFLAGS) -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -DMEMDEBUG=$(MEMDEBUG) -std=c99 `pkg-config --cflags x11 fontconfig xext` -D_POSIX_C_SOURCE=200809L LTK_LDFLAGS = $(EXTRA_LDFLAGS) -lm `pkg-config --libs x11 fontconfig xext` OBJ = \ @@ -55,7 +56,8 @@ OBJ = \ src/graphics_xlib.o \ src/surface_cache.o \ src/event_xlib.o \ - src/err.c \ + src/err.o \ + src/config.o \ $(EXTRA_OBJ) # Note: This could be improved so a change in a header only causes the .c files @@ -83,9 +85,12 @@ HDR = \ src/surface_cache.h \ src/macros.h \ src/event.h \ + src/eventdefs.h \ src/xlib_shared.h \ src/err.h \ - src/proto_types.h + src/proto_types.h \ + src/config.h \ + src/widget_config.h all: src/ltkd src/ltkc diff --git a/src/box.c b/src/box.c @@ -30,7 +30,7 @@ #include "scrollbar.h" #include "box.h" -static void ltk_box_draw(ltk_widget *self, ltk_rect clip); +static void ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); static ltk_box *ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient); static void ltk_box_destroy(ltk_widget *self, int shallow); static void ltk_recalculate_box(ltk_widget *self); @@ -42,6 +42,18 @@ static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, 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 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); + +static ltk_widget *ltk_box_prev_child(ltk_widget *self, ltk_widget *child); +static ltk_widget *ltk_box_next_child(ltk_widget *self, ltk_widget *child); +static ltk_widget *ltk_box_first_child(ltk_widget *self); +static ltk_widget *ltk_box_last_child(ltk_widget *self); + +static ltk_widget *ltk_box_nearest_child(ltk_widget *self, ltk_rect rect); +static ltk_widget *ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget); static struct ltk_widget_vtable vtable = { .change_state = NULL, @@ -59,6 +71,16 @@ static struct ltk_widget_vtable vtable = { .get_child_at_pos = &ltk_box_get_child_at_pos, .mouse_leave = NULL, .mouse_enter = NULL, + .prev_child = &ltk_box_prev_child, + .next_child = &ltk_box_next_child, + .first_child = &ltk_box_first_child, + .last_child = &ltk_box_last_child, + .nearest_child = &ltk_box_nearest_child, + .nearest_child_left = &ltk_box_nearest_child_left, + .nearest_child_right = &ltk_box_nearest_child_right, + .nearest_child_above = &ltk_box_nearest_child_above, + .nearest_child_below = &ltk_box_nearest_child_below, + .ensure_rect_shown = &ltk_box_ensure_rect_shown, .type = LTK_WIDGET_BOX, .flags = 0, }; @@ -87,17 +109,22 @@ static int ltk_box_cmd_create( ltk_error *err); static void -ltk_box_draw(ltk_widget *self, ltk_rect clip) { +ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { ltk_box *box = (ltk_box *)self; ltk_widget *ptr; - ltk_rect real_clip = ltk_rect_intersect(box->widget.rect, clip); + ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip); for (size_t i = 0; i < box->num_widgets; i++) { ptr = box->widgets[i]; /* FIXME: Maybe continue immediately if widget is obviously outside of clipping rect */ - ptr->vtable->draw(ptr, real_clip); + ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, real_clip)); } - box->sc->widget.vtable->draw((ltk_widget *)box->sc, real_clip); + box->sc->widget.vtable->draw( + (ltk_widget *)box->sc, s, + x + box->sc->widget.lrect.x, + y + box->sc->widget.lrect.y, + ltk_rect_relative(box->sc->widget.lrect, real_clip) + ); } static ltk_box * @@ -107,28 +134,55 @@ ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) { ltk_fill_widget_defaults(&box->widget, id, window, &vtable, 0, 0); box->sc = ltk_scrollbar_create(window, orient, &ltk_box_scroll, box); + box->sc->widget.parent = &box->widget; box->widgets = NULL; box->num_alloc = 0; box->num_widgets = 0; box->orient = orient; + if (orient == LTK_HORIZONTAL) + box->widget.ideal_h = box->sc->widget.ideal_h; + else + box->widget.ideal_w = box->sc->widget.ideal_w; + ltk_recalculate_box(&box->widget); return box; } static void +ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r) { + ltk_box *box = (ltk_box *)self; + int delta = 0; + if (box->orient == LTK_HORIZONTAL) { + if (r.x + r.w > self->lrect.w && r.w <= self->lrect.w) + delta = r.x - (self->lrect.w - r.w); + else if (r.x < 0 || r.w > self->lrect.w) + delta = r.x; + } else { + if (r.y + r.h > self->lrect.h && r.h <= self->lrect.h) + delta = r.y - (self->lrect.h - r.h); + else if (r.y < 0 || r.h > self->lrect.h) + delta = r.y; + } + if (delta) + ltk_scrollbar_scroll(&box->sc->widget, delta, 0); +} + +static void ltk_box_destroy(ltk_widget *self, int shallow) { ltk_box *box = (ltk_box *)self; ltk_widget *ptr; + ltk_error err; for (size_t i = 0; i < box->num_widgets; i++) { ptr = box->widgets[i]; ptr->parent = NULL; if (!shallow) - ptr->vtable->destroy(ptr, shallow); + ltk_widget_destroy(ptr, shallow, &err); } ltk_free(box->widgets); - ltk_remove_widget(self->id); - ltk_free(self->id); - box->sc->widget.vtable->destroy((ltk_widget *)box->sc, 0); + box->sc->widget.parent = NULL; + /* FIXME: this is a bit weird because sc isn't a normal widget + (it isn't in the widget hash) */ + ltk_widget_destroy(&box->sc->widget, 0, &err); ltk_free(box); } @@ -140,47 +194,52 @@ static void ltk_recalculate_box(ltk_widget *self) { ltk_box *box = (ltk_box *)self; ltk_widget *ptr; - ltk_rect *sc_rect = &box->sc->widget.rect; - int offset = box->orient == LTK_HORIZONTAL ? box->widget.rect.x : box->widget.rect.y; - int cur_pos = offset; + ltk_rect *sc_rect = &box->sc->widget.lrect; + int cur_pos = 0; + if (box->orient == LTK_HORIZONTAL) + sc_rect->h = box->sc->widget.ideal_h; + else + sc_rect->w = box->sc->widget.ideal_w; for (size_t i = 0; i < box->num_widgets; i++) { ptr = box->widgets[i]; if (box->orient == LTK_HORIZONTAL) { - ptr->rect.x = cur_pos - box->sc->cur_pos; + ptr->lrect.x = cur_pos - box->sc->cur_pos; if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) - ptr->rect.h = box->widget.rect.h - sc_rect->h; + ptr->lrect.h = box->widget.lrect.h - sc_rect->h; if (ptr->sticky & LTK_STICKY_TOP) - ptr->rect.y = box->widget.rect.y; + ptr->lrect.y = 0; else if (ptr->sticky & LTK_STICKY_BOTTOM) - ptr->rect.y = box->widget.rect.y + box->widget.rect.h - ptr->rect.h - sc_rect->h; + ptr->lrect.y = box->widget.lrect.h - ptr->lrect.h - sc_rect->h; else - ptr->rect.y = box->widget.rect.y + (box->widget.rect.h - ptr->rect.h) / 2; - ltk_widget_resize(ptr); - cur_pos += ptr->rect.w; + ptr->lrect.y = (box->widget.lrect.h - ptr->lrect.h) / 2; + cur_pos += ptr->lrect.w; } else { - ptr->rect.y = cur_pos - box->sc->cur_pos; + ptr->lrect.y = cur_pos - box->sc->cur_pos; if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT) - ptr->rect.w = box->widget.rect.w - sc_rect->w; + ptr->lrect.w = box->widget.lrect.w - sc_rect->w; if (ptr->sticky & LTK_STICKY_LEFT) - ptr->rect.x = box->widget.rect.x; + ptr->lrect.x = 0; else if (ptr->sticky & LTK_STICKY_RIGHT) - ptr->rect.x = box->widget.rect.x + box->widget.rect.w - ptr->rect.w - sc_rect->w; + ptr->lrect.x = box->widget.lrect.w - ptr->lrect.w - sc_rect->w; else - ptr->rect.x = box->widget.rect.x + (box->widget.rect.w - ptr->rect.w) / 2; - ltk_widget_resize(ptr); - cur_pos += ptr->rect.h; + ptr->lrect.x = (box->widget.lrect.w - ptr->lrect.w) / 2; + cur_pos += ptr->lrect.h; } + ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect); + ltk_widget_resize(ptr); } - ltk_scrollbar_set_virtual_size(box->sc, cur_pos - offset); + ltk_scrollbar_set_virtual_size(box->sc, cur_pos); if (box->orient == LTK_HORIZONTAL) { - sc_rect->x = box->widget.rect.x; - sc_rect->y = box->widget.rect.y + box->widget.rect.h - sc_rect->h; - sc_rect->w = box->widget.rect.w; + sc_rect->x = 0; + sc_rect->y = box->widget.lrect.h - sc_rect->h; + sc_rect->w = box->widget.lrect.w; } else { - sc_rect->x = box->widget.rect.x + box->widget.rect.w - sc_rect->w; - sc_rect->y = box->widget.rect.y; - sc_rect->h = box->widget.rect.h; + sc_rect->x = box->widget.lrect.w - sc_rect->w; + sc_rect->y = 0; + sc_rect->h = box->widget.lrect.h; } + *sc_rect = ltk_rect_intersect(*sc_rect, (ltk_rect){0, 0, box->widget.lrect.w, box->widget.lrect.h}); + box->sc->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, *sc_rect); ltk_widget_resize((ltk_widget *)box->sc); } @@ -206,12 +265,12 @@ ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) { could also set all widgets even if they don't have any sticky settings, but there'd probably be some catch as well. */ /* FIXME: the same comment as in grid.c applies */ - int orig_w = widget->rect.w; - int orig_h = widget->rect.h; - widget->rect.w = widget->ideal_w; - widget->rect.h = widget->ideal_h; - int sc_w = box->sc->widget.rect.w; - int sc_h = box->sc->widget.rect.h; + int orig_w = widget->lrect.w; + int orig_h = widget->lrect.h; + widget->lrect.w = widget->ideal_w; + widget->lrect.h = widget->ideal_h; + int sc_w = box->sc->widget.lrect.w; + int sc_h = box->sc->widget.lrect.h; if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h > box->widget.ideal_h) { box->widget.ideal_h = widget->ideal_h + sc_h; size_changed = 1; @@ -224,7 +283,7 @@ ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) { box->widget.parent->vtable->child_size_change(box->widget.parent, (ltk_widget *)box); else ltk_recalculate_box((ltk_widget *)box); - if (orig_w != widget->rect.w || orig_h != widget->rect.h) + if (orig_w != widget->lrect.w || orig_h != widget->lrect.h) ltk_widget_resize(widget); } @@ -241,8 +300,8 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short box->widgets = new; } - int sc_w = box->sc->widget.rect.w; - int sc_h = box->sc->widget.rect.h; + int sc_w = box->sc->widget.lrect.w; + int sc_h = box->sc->widget.lrect.h; box->widgets[box->num_widgets++] = widget; if (box->orient == LTK_HORIZONTAL) { @@ -257,7 +316,7 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short widget->parent = (ltk_widget *)box; widget->sticky = sticky; ltk_box_child_size_change((ltk_widget *)box, widget); - ltk_window_invalidate_rect(window, box->widget.rect); + ltk_window_invalidate_widget_rect(window, &box->widget); return 0; } @@ -265,8 +324,8 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err) { ltk_box *box = (ltk_box *)self; - int sc_w = box->sc->widget.rect.w; - int sc_h = box->sc->widget.rect.h; + int sc_w = box->sc->widget.lrect.w; + int sc_h = box->sc->widget.lrect.h; if (widget->parent != (ltk_widget *)box) { err->type = ERR_WIDGET_NOT_IN_CONTAINER; return 1; @@ -278,7 +337,7 @@ ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err) { memmove(box->widgets + i, box->widgets + i + 1, (box->num_widgets - i - 1) * sizeof(ltk_widget *)); box->num_widgets--; - ltk_window_invalidate_rect(widget->window, box->widget.rect); + ltk_window_invalidate_widget_rect(widget->window, &box->widget); /* search for new ideal width/height */ /* FIXME: make this all a bit nicer and break the lines better */ /* FIXME: other part of ideal size not updated */ @@ -307,20 +366,105 @@ ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err) { return 1; } +/* FIXME: maybe come up with a more efficient method */ +static ltk_widget * +ltk_box_nearest_child(ltk_widget *self, ltk_rect rect) { + ltk_box *box = (ltk_box *)self; + ltk_widget *minw = NULL; + int min_dist = INT_MAX; + int cx = rect.x + rect.w / 2; + int cy = rect.y + rect.h / 2; + ltk_rect r; + int dist; + for (size_t i = 0; i < box->num_widgets; i++) { + r = box->widgets[i]->lrect; + dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy); + if (dist < min_dist) { + min_dist = dist; + minw = box->widgets[i]; + } + } + return minw; +} + +static ltk_widget * +ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget) { + ltk_box *box = (ltk_box *)self; + if (box->orient == LTK_VERTICAL) + return NULL; + return ltk_box_prev_child(self, widget); +} + +static ltk_widget * +ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget) { + ltk_box *box = (ltk_box *)self; + if (box->orient == LTK_VERTICAL) + return NULL; + return ltk_box_next_child(self, widget); +} + +static ltk_widget * +ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget) { + ltk_box *box = (ltk_box *)self; + if (box->orient == LTK_HORIZONTAL) + return NULL; + return ltk_box_prev_child(self, widget); +} + +static ltk_widget * +ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget) { + ltk_box *box = (ltk_box *)self; + if (box->orient == LTK_HORIZONTAL) + return NULL; + return ltk_box_next_child(self, widget); +} + +static ltk_widget * +ltk_box_prev_child(ltk_widget *self, ltk_widget *child) { + ltk_box *box = (ltk_box *)self; + for (size_t i = box->num_widgets; i-- > 0;) { + if (box->widgets[i] == child) + return i > 0 ? box->widgets[i-1] : NULL; + } + return NULL; +} + +static ltk_widget * +ltk_box_next_child(ltk_widget *self, ltk_widget *child) { + ltk_box *box = (ltk_box *)self; + for (size_t i = 0; i < box->num_widgets; i++) { + if (box->widgets[i] == child) + return i < box->num_widgets - 1 ? box->widgets[i+1] : NULL; + } + return NULL; +} + +static ltk_widget * +ltk_box_first_child(ltk_widget *self) { + ltk_box *box = (ltk_box *)self; + return box->num_widgets > 0 ? box->widgets[0] : NULL; +} + +static ltk_widget * +ltk_box_last_child(ltk_widget *self) { + ltk_box *box = (ltk_box *)self; + return box->num_widgets > 0 ? box->widgets[box->num_widgets-1] : NULL; +} + static void ltk_box_scroll(ltk_widget *self) { ltk_box *box = (ltk_box *)self; ltk_recalculate_box(self); - ltk_window_invalidate_rect(box->widget.window, box->widget.rect); + ltk_window_invalidate_widget_rect(box->widget.window, &box->widget); } 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)) + if (ltk_collide_rect(box->sc->widget.crect, x, y)) return (ltk_widget *)box->sc; for (size_t i = 0; i < box->num_widgets; i++) { - if (ltk_collide_rect(box->widgets[i]->rect, x, y)) + if (ltk_collide_rect(box->widgets[i]->crect, x, y)) return box->widgets[i]; } return NULL; @@ -334,7 +478,8 @@ ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event) { /* FIXME: configure scrollstep */ int delta = event->button == LTK_BUTTON4 ? -15 : 15; ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0); - ltk_window_fake_motion_event(self->window, event->x, event->y); + 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; diff --git a/src/button.c b/src/button.c @@ -37,8 +37,8 @@ #define MAX_BUTTON_BORDER_WIDTH 100 #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_button_event *event); +static void ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip); +static int ltk_button_release(ltk_widget *self); static ltk_button *ltk_button_create(ltk_window *window, const char *id, char *text); static void ltk_button_destroy(ltk_widget *self, int shallow); @@ -48,7 +48,8 @@ static struct ltk_widget_vtable vtable = { .key_press = NULL, .key_release = NULL, .mouse_press = NULL, - .mouse_release = &ltk_button_mouse_release, + .mouse_release = NULL, + .release = &ltk_button_release, .motion_notify = NULL, .mouse_leave = NULL, .mouse_enter = NULL, @@ -119,20 +120,22 @@ ltk_button_uninitialize_theme(ltk_window *window) { /* FIXME: only keep text in surface to avoid large surface */ static void -ltk_button_draw(ltk_widget *self, ltk_rect clip) { +ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { ltk_button *button = (ltk_button *)self; - ltk_rect rect = button->widget.rect; - ltk_rect clip_final = ltk_rect_intersect(clip, rect); + ltk_rect lrect = self->lrect; + ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); + if (clip_final.w <= 0 || clip_final.h <= 0) + return; ltk_surface *s; - ltk_surface_cache_request_surface_size(button->key, self->rect.w, self->rect.h); + ltk_surface_cache_request_surface_size(button->key, lrect.w, lrect.h); if (!ltk_surface_cache_get_surface(button->key, &s) || self->dirty) ltk_button_redraw_surface(button, s); - ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y); + ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y); } static void ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) { - ltk_rect rect = button->widget.rect; + ltk_rect rect = button->widget.lrect; int bw = theme.border_width; ltk_color *border = NULL, *fill = NULL; /* FIXME: HOVERACTIVE STATE */ @@ -167,12 +170,9 @@ ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) { } static int -ltk_button_mouse_release(ltk_widget *self, ltk_button_event *event) { - if ((self->state & LTK_PRESSED) && event->button == LTK_BUTTONL && ltk_collide_rect(self->rect, event->x, event->y)) { - ltk_queue_specific_event(self, "button", LTK_PWEVENTMASK_BUTTON_PRESS, "press"); - return 1; - } - return 0; +ltk_button_release(ltk_widget *self) { + ltk_queue_specific_event(self, "button", LTK_PWEVENTMASK_BUTTON_PRESS, "press"); + return 1; } static ltk_button * @@ -183,9 +183,9 @@ ltk_button_create(ltk_window *window, const char *id, char *text) { button->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1); int text_w, text_h; ltk_text_line_get_size(button->tl, &text_w, &text_h); + ltk_fill_widget_defaults(&button->widget, id, window, &vtable, button->widget.ideal_w, button->widget.ideal_h); button->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2; button->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2; - ltk_fill_widget_defaults(&button->widget, id, window, &vtable, button->widget.ideal_w, button->widget.ideal_h); button->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, button->widget.ideal_w, button->widget.ideal_h); button->widget.dirty = 1; @@ -202,9 +202,6 @@ ltk_button_destroy(ltk_widget *self, int shallow) { } ltk_surface_cache_release_key(button->key); ltk_text_line_destroy(button->tl); - ltk_remove_widget(self->id); - ltk_remove_widget(button->widget.id); - ltk_free(button->widget.id); ltk_free(button); } diff --git a/src/config.c b/src/config.c @@ -0,0 +1,710 @@ +/* FIXME: This really is horrible. */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <limits.h> + +#include "util.h" +#include "memory.h" +#include "config.h" + +ltk_config *global_config = NULL; + +enum toktype { + STRING, + SECTION, + EQUALS, + NEWLINE, + ERROR, + END +}; + +#if 0 +static const char * +toktype_str(enum toktype type) { + switch (type) { + case STRING: + return "string"; + break; + case SECTION: + return "section"; + break; + case EQUALS: + return "equals"; + break; + case NEWLINE: + return "newline"; + break; + case ERROR: + return "error"; + break; + case END: + return "end of file"; + break; + default: + return "unknown"; + } +} +#endif + +struct token { + char *text; + size_t len; + enum toktype type; + size_t line; /* line in original input */ + size_t line_offset; /* offset from start of line */ +}; + +struct lexstate { + const char *filename; + char *text; + size_t len; /* length of text */ + size_t cur; /* current byte position */ + size_t cur_line; /* current line */ + size_t line_start; /* byte offset of start of current line */ +}; + +static struct token +next_token(struct lexstate *s) { + char c; + struct token tok; + int finished = 0; + while (1) { + if (s->cur >= s->len) + return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; + while (isspace(c = s->text[s->cur])) { + s->cur++; + if (c == '\n') { + struct token tok = (struct token){s->text + s->cur - 1, 1, NEWLINE, s->cur_line, s->cur - s->line_start}; + s->cur_line++; + s->line_start = s->cur; + return tok; + } + if (s->cur >= s->len) + return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; + } + + switch (s->text[s->cur]) { + case '#': + s->cur++; + while (s->cur < s->len && s->text[s->cur] != '\n') + s->cur++; + continue; + case '[': + s->cur++; + tok = (struct token){s->text + s->cur, 0, SECTION, s->cur_line, s->cur - s->line_start + 1}; + finished = 0; + while (s->cur < s->len) { + char c = s->text[s->cur]; + if (c == '\n') { + break; + } else if (c == ']') { + s->cur++; + finished = 1; + break; + } else { + tok.len++; + } + s->cur++; + } + if (!finished) { + tok.text = "Unfinished section name"; + tok.len = strlen("Unfinished section name"); + tok.type = ERROR; + } + break; + case '=': + tok = (struct token){s->text + s->cur, 1, EQUALS, s->cur_line, s->cur - s->line_start + 1}; + s->cur++; + break; + case '"': + /* FIXME: error if next char is not whitespace or end */ + s->cur++; + tok = (struct token){s->text + s->cur, 0, STRING, s->cur_line, s->cur - s->line_start + 1}; + size_t shift = 0, bs = 0; + finished = 0; + while (s->cur < s->len) { + char c = s->text[s->cur]; + if (c == '\n') { + break; + } else if (c == '\\') { + shift += bs; + tok.len += bs; + bs = (bs + 1) % 2; + } else if (c == '"') { + if (bs) { + shift++; + tok.len++; + bs = 0; + } else { + s->cur++; + finished = 1; + break; + } + } else { + tok.len++; + } + s->text[s->cur - shift] = s->text[s->cur]; + s->cur++; + } + if (!finished) { + tok.text = "Unfinished string"; + tok.len = strlen("Unfinished string"); + tok.type = ERROR; + } + break; + default: + tok = (struct token){s->text + s->cur, 1, STRING, s->cur_line, s->cur - s->line_start + 1}; + s->cur++; + while (s->cur < s->len) { + char c = s->text[s->cur]; + if (isspace(c) || c == '=') { + break; + } else if (c == '"') { + tok.text = "Unexpected start of string"; + tok.len = strlen("Unexpected start of string"); + tok.type = ERROR; + tok.line_offset = s->cur - s->line_start + 1; + } else if (c == '[' || c == ']') { + tok.text = "Unexpected start or end of section name"; + tok.len = strlen("Unexpected start or end of section name"); + tok.type = ERROR; + tok.line_offset = s->cur - s->line_start + 1; + } + tok.len++; + s->cur++; + } + } + return tok; + } +} + +/* FIXME: optimize - just copy from ledit; actually support all keysyms */ +static int +parse_keysym(char *text, size_t len, ltk_keysym *sym_ret) { + if (str_array_equal("left", text, len)) + *sym_ret = LTK_KEY_LEFT; + else if (str_array_equal("right", text, len)) + *sym_ret = LTK_KEY_RIGHT; + else if (str_array_equal("up", text, len)) + *sym_ret = LTK_KEY_UP; + else if (str_array_equal("down", text, len)) + *sym_ret = LTK_KEY_DOWN; + else if (str_array_equal("backspace", text, len)) + *sym_ret = LTK_KEY_BACKSPACE; + else if (str_array_equal("delete", text, len)) + *sym_ret = LTK_KEY_DELETE; + else if (str_array_equal("space", text, len)) + *sym_ret = LTK_KEY_SPACE; + else if (str_array_equal("return", text, len)) + *sym_ret = LTK_KEY_RETURN; + else if (str_array_equal("tab", text, len)) + *sym_ret = LTK_KEY_TAB; + else if (str_array_equal("escape", text, len)) + *sym_ret = LTK_KEY_ESCAPE; + else + return 1; + return 0; +} + +#undef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static int +parse_modmask(char *modmask_str, size_t len, ltk_mod_type *mask_ret) { + size_t cur = 0; + *mask_ret = 0; + while (cur < len) { + if (str_array_equal("shift", modmask_str + cur, MIN(5, len - cur))) { + cur += 5; + *mask_ret |= LTK_MOD_SHIFT; + } else if (str_array_equal("ctrl", modmask_str + cur, MIN(4, len - cur))) { + cur += 4; + *mask_ret |= LTK_MOD_CTRL; + } else if (str_array_equal("alt", modmask_str + cur, MIN(3, len - cur))) { + cur += 3; + *mask_ret |= LTK_MOD_ALT; + } else if (str_array_equal("super", modmask_str + cur, MIN(5, len - cur))) { + cur += 5; + *mask_ret |= LTK_MOD_SUPER; + } else if (str_array_equal("any", modmask_str + cur, MIN(3, len - cur))) { + cur += 3; + *mask_ret = UINT_MAX; + } else { + return 1; + } + if (cur < len && modmask_str[cur] != '|') + return 1; + else + cur++; + } + return 0; +} + +static int +parse_flags(char *text, size_t len, ltk_key_binding_flags *flags_ret) { + if (str_array_equal("run-always", text, len)) + *flags_ret = LTK_KEY_BINDING_RUN_ALWAYS; + else + return 1; + return 0; +} + +static int +parse_keypress_binding(struct lexstate *s, struct token *tok, ltk_keypress_binding *binding_ret, char **errstr) { + ltk_keypress_binding b = {NULL, NULL, NULL, LTK_KEY_NONE, LTK_MOD_NONE, LTK_KEY_BINDING_NOFLAGS}; + *tok = next_token(s); + char *msg = NULL; + if (tok->type != STRING) { + msg = "Invalid token type"; + goto error; + } + b.callback = ltk_get_key_func(tok->text, tok->len); + if (!b.callback) { + msg = "Invalid function specification"; + goto error; + } + struct token prevtok; + int text_init = 0, rawtext_init = 0, sym_init = 0, mods_init = 0, flags_init = 0; + while (1) { + *tok = next_token(s); + if (tok->type == NEWLINE || tok->type == END) + break; + prevtok = *tok; + *tok = next_token(s); + if (prevtok.type != STRING) { + msg = "Invalid token type"; + *tok = prevtok; + goto error; + } else if (tok->type != STRING) { + msg = "Invalid token type"; + goto error; + } else if (str_array_equal("text", prevtok.text, prevtok.len)) { + if (rawtext_init) { + msg = "Rawtext already specified"; + goto error; + } else if (sym_init) { + msg = "Keysym already specified"; + goto error; + } else if (text_init) { + msg = "Duplicate text specification"; + goto error; + } + b.text = ltk_strndup(tok->text, tok->len); + text_init = 1; + } else if (str_array_equal("rawtext", prevtok.text, prevtok.len)) { + if (text_init) { + msg = "Text already specified"; + goto error; + } else if (sym_init) { + msg = "Keysym already specified"; + goto error; + } else if (rawtext_init) { + msg = "Duplicate rawtext specification"; + goto error; + } + b.rawtext = ltk_strndup(tok->text, tok->len); + rawtext_init = 1; + } else if (str_array_equal("sym", prevtok.text, prevtok.len)) { + if (text_init) { + msg = "Text already specified"; + goto error; + } else if (rawtext_init) { + msg = "Rawtext already specified"; + goto error; + } else if (sym_init) { + msg = "Duplicate keysym specification"; + goto error; + } else if (parse_keysym(tok->text, tok->len, &b.sym)) { + msg = "Invalid keysym specification"; + goto error; + } + sym_init = 1; + } else if (str_array_equal("mods", prevtok.text, prevtok.len)) { + if (mods_init) { + msg = "Duplicate mods specification"; + goto error; + } else if (parse_modmask(tok->text, tok->len, &b.mods)) { + msg = "Invalid mods specification"; + goto error; + } + mods_init = 1; + } else if (str_array_equal("flags", prevtok.text, prevtok.len)) { + if (flags_init) { + msg = "Duplicate flags specification"; + goto error; + } else if (parse_flags(tok->text, tok->len, &b.flags)) { + msg = "Invalid flags specification"; + goto error; + } + flags_init = 1; + } else { + msg = "Invalid keyword"; + *tok = prevtok; + goto error; + } + }; + if (!text_init && !rawtext_init && !sym_init) { + msg = "One of text, rawtext, and sym must be initialized"; + goto error; + } + *binding_ret = b; + return 0; +error: + free(b.text); + free(b.rawtext); + if (msg) { + *errstr = ltk_print_fmt( + "%s, line %zu, offset %zu: %s", s->filename, tok->line, tok->line_offset, msg + ); + } else { + *errstr = NULL; + } + return 1; +} + +static void +push_keypress(ltk_config *c, ltk_keypress_binding b) { + if (c->keys.press_alloc == c->keys.press_len) { + c->keys.press_alloc = ideal_array_size(c->keys.press_alloc, c->keys.press_len + 1); + c->keys.press_bindings = ltk_reallocarray(c->keys.press_bindings, c->keys.press_alloc, sizeof(ltk_keypress_binding)); + } + c->keys.press_bindings[c->keys.press_len] = b; + c->keys.press_len++; +} + +static void +push_keyrelease(ltk_config *c, ltk_keyrelease_binding b) { + if (c->keys.release_alloc == c->keys.release_len) { + c->keys.release_alloc = ideal_array_size(c->keys.release_alloc, c->keys.release_len + 1); + c->keys.release_bindings = ltk_reallocarray(c->keys.release_bindings, c->keys.release_alloc, sizeof(ltk_keyrelease_binding)); + } + c->keys.release_bindings[c->keys.release_len] = b; + c->keys.release_len++; +} + +static int +parse_keybinding(struct lexstate *s, ltk_config *c, struct token *tok, char **errstr) { + char *msg = NULL; + *tok = next_token(s); + if (tok->type == SECTION || tok->type == END) { + return -1; + } else if (tok->type == NEWLINE) { + return 0; /* empty line */ + } else if (tok->type != STRING) { + msg = "Invalid token type"; + goto error; + } else if (str_array_equal("bind-keypress", tok->text, tok->len)) { + ltk_keypress_binding b; + if (parse_keypress_binding(s, tok, &b, errstr)) + return 1; + push_keypress(c, b); + } else if (str_array_equal("bind-keyrelease", tok->text, tok->len)) { + ltk_keypress_binding b; + if (parse_keypress_binding(s, tok, &b, errstr)) + return 1; + if (b.text || b.rawtext) { + free(b.text); + free(b.rawtext); + msg = "Text and rawtext may only be specified for keypress bindings"; + goto error; + } + push_keyrelease(c, (ltk_keyrelease_binding){b.callback, b.sym, b.mods, b.flags}); + } else { + msg = "Invalid statement"; + goto error; + } + return 0; +error: + if (msg) { + *errstr = ltk_print_fmt( + "%s, line %zu, offset %zu: %s", s->filename, tok->line, tok->line_offset, msg + ); + } + return 1; +} + +static void +push_lang_mapping(ltk_config *c) { + if (c->keys.mappings_alloc == c->keys.mappings_len) { + c->keys.mappings_alloc = ideal_array_size(c->keys.mappings_alloc, c->keys.mappings_len + 1); + c->keys.mappings = ltk_reallocarray(c->keys.mappings, c->keys.mappings_alloc, sizeof(ltk_language_mapping)); + } + c->keys.mappings[c->keys.mappings_len].lang = NULL; + c->keys.mappings[c->keys.mappings_len].mappings = NULL; + c->keys.mappings[c->keys.mappings_len].mappings_alloc = 0; + c->keys.mappings[c->keys.mappings_len].mappings_len = 0; + c->keys.mappings_len++; +} + +static void +push_text_mapping(ltk_config *c, char *text1, size_t len1, char *text2, size_t len2) { + if (c->keys.mappings_len == 0) + return; /* I guess just fail silently... */ + ltk_language_mapping *m = &c->keys.mappings[c->keys.mappings_len - 1]; + if (m->mappings_alloc == m->mappings_len) { + m->mappings_alloc = ideal_array_size(m->mappings_alloc, m->mappings_len + 1); + m->mappings = ltk_reallocarray(m->mappings, m->mappings_alloc, sizeof(ltk_keytext_mapping)); + } + m->mappings[m->mappings_len].from = ltk_strndup(text1, len1); + m->mappings[m->mappings_len].to = ltk_strndup(text2, len2); + m->mappings_len++; +} + +static void +destroy_config(ltk_config *c) { + for (size_t i = 0; i < c->keys.press_len; i++) { + ltk_free(c->keys.press_bindings[i].text); + ltk_free(c->keys.press_bindings[i].rawtext); + } + ltk_free(c->keys.press_bindings); + ltk_free(c->keys.release_bindings); + for (size_t i = 0; i < c->keys.mappings_len; i++) { + ltk_free(c->keys.mappings[i].lang); + for (size_t j = 0; j < c->keys.mappings[i].mappings_len; j++) { + ltk_free(c->keys.mappings[i].mappings[j].from); + ltk_free(c->keys.mappings[i].mappings[j].to); + } + ltk_free(c->keys.mappings[i].mappings); + } + ltk_free(c->keys.mappings); + ltk_free(c); +} + +void +ltk_config_cleanup(void) { + if (global_config) + destroy_config(global_config); + global_config = NULL; +} + +ltk_config * +ltk_config_get(void) { + return global_config; +} + +int +ltk_config_get_language_index(char *lang, size_t *idx_ret) { + if (!global_config) + return 1; + for (size_t i = 0; i < global_config->keys.mappings_len; i++) { + if (!strcmp(lang, global_config->keys.mappings[i].lang)) { + *idx_ret = i; + return 0; + } + } + return 1; +} + +ltk_language_mapping * +ltk_config_get_language_mapping(size_t idx) { + if (!global_config || idx >= global_config->keys.mappings_len) + return NULL; + return &global_config->keys.mappings[idx]; +} + +/* WARNING: errstr must be freed! */ +/* FIXME: make ltk_load_file give size_t; handle errors there (copy from ledit) */ +static int +load_from_text(const char *filename, char *file_contents, size_t len, char **errstr) { + ltk_config *config = ltk_malloc(sizeof(ltk_config)); + config->keys.press_bindings = NULL; + config->keys.release_bindings = NULL; + config->keys.mappings = NULL; + config->keys.press_alloc = config->keys.press_len = 0; + config->keys.release_alloc = config->keys.release_len = 0; + config->keys.mappings_alloc = config->keys.mappings_len = 0; + config->general.explicit_focus = 0; + config->general.all_activatable = 0; + + struct lexstate s = {filename, file_contents, len, 0, 1, 0}; + struct token tok = next_token(&s); + int start_of_line = 1; + char *msg = NULL; + struct token secttok; + while (tok.type != END) { + switch (tok.type) { + case SECTION: + if (!start_of_line) { + msg = "Section can only start at new line"; + goto error; + } + secttok = tok; + tok = next_token(&s); + if (tok.type != NEWLINE && tok.type != END) { + msg = "Section must be alone on line"; + goto error; + } + /* FIXME: generalize (at least once more options are added) */ + if (str_array_equal("general", secttok.text, secttok.len)) { + struct token prev1tok, prev2tok; + while (1) { + tok = next_token(&s); + if (tok.type == SECTION || tok.type == END) + break; + else if (tok.type == NEWLINE) + continue; + prev2tok = tok; + tok = next_token(&s); + prev1tok = tok; + tok = next_token(&s); + if (prev2tok.type != STRING || prev1tok.type != EQUALS || tok.type != STRING) { + msg = "Invalid assignment statement"; + goto error; + } + if (str_array_equal("explicit-focus", prev2tok.text, prev2tok.len)) { + if (str_array_equal("true", tok.text, tok.len)) { + config->general.explicit_focus = 1; + } else if (str_array_equal("false", tok.text, tok.len)) { + config->general.explicit_focus = 0; + } else { + msg = "Invalid boolean setting"; + goto error; + } + } else if (str_array_equal("all-activatable", prev2tok.text, prev2tok.len)) { + if (str_array_equal("true", tok.text, tok.len)) { + config->general.all_activatable = 1; + } else if (str_array_equal("false", tok.text, tok.len)) { + config->general.all_activatable = 0; + } else { + msg = "Invalid boolean setting"; + goto error; + } + } else { + msg = "Invalid setting"; + goto error; + } + tok = next_token(&s); + if (tok.type == END) { + break; + } else if (tok.type != NEWLINE) { + msg = "Invalid assignment statement"; + goto error; + } + start_of_line = 1; + } + } else if (str_array_equal("key-binding", secttok.text, secttok.len)) { + int ret = 0; + while (1) { + if ((ret = parse_keybinding(&s, config, &tok, errstr)) > 0) { + goto errornomsg; + } else if (ret < 0) { + start_of_line = 1; + break; + } + } + } else if (str_array_equal("key-mapping", secttok.text, secttok.len)) { + int lang_init = 0; + push_lang_mapping(config); + struct token prev1tok, prev2tok; + while (1) { + tok = next_token(&s); + if (tok.type == SECTION || tok.type == END) + break; + else if (tok.type == NEWLINE) + continue; + prev2tok = tok; + tok = next_token(&s); + prev1tok = tok; + tok = next_token(&s); + if (prev2tok.type != STRING) { + msg = "Invalid statement in language mapping"; + goto error; + } + if (str_array_equal("language", prev2tok.text, prev2tok.len)) { + if (prev1tok.type != EQUALS || tok.type != STRING) { + msg = "Invalid language assignment"; + goto error; + } else if (lang_init) { + msg = "Language already set"; + goto error; + } + config->keys.mappings[config->keys.mappings_len - 1].lang = ltk_strndup(tok.text, tok.len); + lang_init = 1; + } else if (str_array_equal("map", prev2tok.text, prev2tok.len)) { + if (prev1tok.type != STRING || tok.type != STRING) { + msg = "Invalid map statement"; + goto error; + } + push_text_mapping(config, prev1tok.text, prev1tok.len, tok.text, tok.len); + } else { + msg = "Invalid statement in language mapping"; + goto error; + } + tok = next_token(&s); + if (tok.type == END) { + break; + } else if (tok.type != NEWLINE) { + msg = "Invalid statement in language mapping"; + goto error; + } + start_of_line = 1; + } + if (!lang_init) { + msg = "Language not set for language mapping"; + goto error; + } + } else { + msg = "Invalid section"; + goto error; + } + break; + case NEWLINE: + start_of_line = 1; + break; + default: + msg = "Invalid token"; + goto error; + break; + } + } + global_config = config; + return 0; +error: + if (msg) { + *errstr = ltk_print_fmt( + "%s, line %zu, offset %zu: %s", filename, tok.line, tok.line_offset, msg + ); + } +errornomsg: + destroy_config(config); + return 1; +} + +int +ltk_config_parsefile(const char *filename, char **errstr) { + unsigned long len = 0; + char *file_contents = ltk_read_file(filename, &len); + if (!file_contents) { + *errstr = ltk_print_fmt("Unable to open file \"%s\"", filename); + return 1; + } + int ret = load_from_text(filename, file_contents, len, errstr); + ltk_free(file_contents); + return ret; +} + +const char *default_config = "[general]\n" +"explicit-focus = true\n" +"all-activatable = true\n" +"[key-binding]\n" +"bind-keypress move-next sym tab\n" +"bind-keypress move-prev sym tab mods shift\n" +"bind-keypress move-next text n\n" +"bind-keypress move-prev text p\n" +"bind-keypress focus-active sym return\n" +"bind-keypress unfocus-active sym escape\n" +"bind-keypress set-pressed sym return flags run-always\n" +"bind-keyrelease unset-pressed sym return flags run-always\n" +"[key-mapping]\n" +"language = \"English (US)\"\n"; + +/* FIXME: improve this configuration */ +int +ltk_config_load_default(char **errstr) { + char *config_copied = ltk_strdup(default_config); + int ret = load_from_text("<default config>", config_copied, strlen(config_copied), errstr); + free(config_copied); + return ret; +} diff --git a/src/config.h b/src/config.h @@ -0,0 +1,69 @@ +#ifndef LTK_CONFIG_H +#define LTK_CONFIG_H + +#include <stddef.h> + +#include "eventdefs.h" +#include "widget_config.h" + +typedef enum{ + LTK_KEY_BINDING_NOFLAGS = 0, + LTK_KEY_BINDING_RUN_ALWAYS = 1, +} ltk_key_binding_flags; + +typedef struct { + char *text; + char *rawtext; + /* FIXME: forward declaration to avoid having to pull everything in for these definitions */ + ltk_key_callback *callback; + ltk_keysym sym; + ltk_mod_type mods; + ltk_key_binding_flags flags; +} ltk_keypress_binding; + +typedef struct { + ltk_key_callback *callback; + ltk_keysym sym; + ltk_mod_type mods; + ltk_key_binding_flags flags; +} ltk_keyrelease_binding; + +typedef struct{ + char *from; + char *to; +} ltk_keytext_mapping; + +typedef struct { + char *lang; + ltk_keytext_mapping *mappings; + size_t mappings_alloc, mappings_len; +} ltk_language_mapping; + +/* FIXME: generic array */ +typedef struct { + ltk_keypress_binding *press_bindings; + ltk_keyrelease_binding *release_bindings; + ltk_language_mapping *mappings; + size_t press_alloc, press_len; + size_t release_alloc, release_len; + size_t mappings_alloc, mappings_len; +} ltk_keys_config; + +typedef struct { + char explicit_focus; + char all_activatable; +} ltk_general_config; + +typedef struct { + ltk_keys_config keys; + ltk_general_config general; +} ltk_config; + +void ltk_config_cleanup(void); +ltk_config *ltk_config_get(void); +int ltk_config_get_language_index(char *lang, size_t *idx_ret); +ltk_language_mapping *ltk_config_get_language_mapping(size_t idx); +int ltk_config_parsefile(const char *filename, char **errstr); +int ltk_config_load_default(char **errstr); + +#endif /* LTK_CONFIG_H */ diff --git a/src/event.h b/src/event.h @@ -1,30 +1,7 @@ #ifndef LTK_EVENT_H #define LTK_EVENT_H -typedef enum { - LTK_UNKNOWN_EVENT, /* FIXME: a bit weird */ - LTK_BUTTONPRESS_EVENT, - LTK_BUTTONRELEASE_EVENT, - LTK_MOTION_EVENT, - LTK_KEYPRESS_EVENT, - LTK_KEYRELEASE_EVENT, - LTK_CONFIGURE_EVENT, - LTK_EXPOSE_EVENT, - LTK_WINDOWCLOSE_EVENT -} ltk_event_type; - -/* FIXME: button mask also in motion */ - -typedef enum { - LTK_BUTTONL, - LTK_BUTTONM, - LTK_BUTTONR, - /* FIXME: dedicated scroll event */ - LTK_BUTTON4, - LTK_BUTTON5, - LTK_BUTTON6, - LTK_BUTTON7 -} ltk_button_type; +#include "eventdefs.h" typedef struct { ltk_event_type type; @@ -37,29 +14,8 @@ typedef struct { int x, y; } ltk_motion_event; -/* FIXME: just steal the definitions from X when using Xlib so no conversion is necessary? */ -typedef enum { - LTK_KEY_NONE = 0, - LTK_KEY_LEFT, - LTK_KEY_RIGHT, - LTK_KEY_UP, - LTK_KEY_DOWN, - LTK_KEY_BACKSPACE, - LTK_KEY_DELETE, - LTK_KEY_SPACE, - LTK_KEY_RETURN -} ltk_keysym; - -typedef enum { - LTK_MOD_CTRL, - LTK_MOD_SHIFT, - LTK_MOD_ALT, - LTK_MOD_SUPER -} ltk_mod_type; - typedef struct { ltk_event_type type; - int x, y; ltk_mod_type modmask; ltk_keysym sym; char *text; @@ -68,6 +24,11 @@ typedef struct { typedef struct { ltk_event_type type; + char *new_kbd; +} ltk_keyboard_event; + +typedef struct { + ltk_event_type type; int x, y; int w, h; } ltk_configure_event; @@ -86,11 +47,15 @@ typedef union { ltk_key_event key; ltk_configure_event configure; ltk_expose_event expose; + ltk_keyboard_event keyboard; } ltk_event; #include "ltk.h" int ltk_events_pending(ltk_renderdata *renderdata); -void ltk_next_event(ltk_renderdata *renderdata, ltk_event *event); +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); +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 @@ -1,11 +1,33 @@ +#include <stdio.h> + +#include <X11/XKBlib.h> +#include <X11/extensions/XKBrules.h> + +#include "memory.h" #include "graphics.h" #include "xlib_shared.h" +#include "config.h" + +#define TEXT_INITIAL_SIZE 128 + +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); } +void +ltk_events_cleanup(void) { + ltk_free(text); + ltk_free(cur_kbd); + cur_kbd = NULL; + text = NULL; + text_alloc = 0; +} + static ltk_button_type get_button(unsigned int button) { switch (button) { @@ -20,11 +42,127 @@ get_button(unsigned int button) { } } +/* FIXME: make an actual exhaustive list here */ +static ltk_keysym +get_keysym(KeySym sym) { + switch (sym) { + case XK_Left: return LTK_KEY_LEFT; + case XK_Right: return LTK_KEY_RIGHT; + case XK_Up: return LTK_KEY_UP; + case XK_Down: return LTK_KEY_DOWN; + case XK_BackSpace: return LTK_KEY_BACKSPACE; + case XK_space: return LTK_KEY_SPACE; + case XK_Return: return LTK_KEY_RETURN; + case XK_Delete: return LTK_KEY_DELETE; + /* FIXME: what other weird keys like this exist? */ + /* why is it ISO_Left_Tab when shift is pressed? */ + /* I mean, it makes sense, but I find it to be weird, + and I'm not sure how standardized it is */ + case XK_ISO_Left_Tab: case XK_Tab: return LTK_KEY_TAB; + case XK_Escape: return LTK_KEY_ESCAPE; + default: return LTK_KEY_NONE; + } +} + +/* FIXME: properly implement modifiers - see SDL and GTK */ +static ltk_mod_type +get_modmask(unsigned int state) { + ltk_mod_type t = 0; + if (state & ControlMask) + t |= LTK_MOD_CTRL; + if (state & ShiftMask) + t |= LTK_MOD_SHIFT; + if (state & Mod1Mask) + t |= LTK_MOD_ALT; + if (state & Mod4Mask) + t |= LTK_MOD_SUPER; + return t; +} +#ifdef X_HAVE_UTF8_STRING +#define LOOKUP_STRING_FUNC Xutf8LookupString +#else +#define LOOKUP_STRING_FUNC XmbLookupString +#endif + +static ltk_event +process_key(ltk_renderdata *renderdata, size_t lang_index, XEvent *event, ltk_event_type type) { + ltk_language_mapping *map = ltk_config_get_language_mapping(lang_index); + /* FIXME: see comment in keys.c in ledit repository */ + if (!text) { + text = ltk_malloc(TEXT_INITIAL_SIZE); + text_alloc = TEXT_INITIAL_SIZE; + } + unsigned int state = event->xkey.state; + event->xkey.state &= ~ControlMask; + KeySym sym; + int len = 0; + Status status; + if (renderdata->xic && type == LTK_KEYPRESS_EVENT) { + len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status); + if (status == XBufferOverflow) { + text_alloc = ideal_array_size(text_alloc, len + 1); + text = ltk_realloc(text, text_alloc); + len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status); + } + } else { + /* FIXME: anything equivalent to XBufferOverflow here? */ + len = XLookupString(&event->xkey, text, text_alloc - 1, &sym, NULL); + status = XLookupBoth; + } + text[len >= (int)text_alloc ? (int)text_alloc - 1 : len] = '\0'; + char *key_text = (status == XLookupChars || status == XLookupBoth) ? text : NULL; + char *mapped = key_text; + /* FIXME: BINARY SEARCH! */ + if (key_text && map) { + for (size_t i = 0; i < map->mappings_len; i++) { + if (!strcmp(key_text, map->mappings[i].from)) { + mapped = map->mappings[i].to; + break; + } + } + } + return (ltk_event){.key = { + .type = type, + .modmask = get_modmask(state), + .sym = (status == XLookupKeySym || status == XLookupBoth) ? get_keysym(sym) : LTK_KEY_NONE, + .text = key_text, + .mapped = mapped + }}; +} + void -ltk_next_event(ltk_renderdata *renderdata, ltk_event *event) { +ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event) { + XkbStateRec s; + XkbGetState(renderdata->dpy, XkbUseCoreKbd, &s); + XkbDescPtr desc = XkbGetKeyboard( + renderdata->dpy, XkbAllComponentsMask, XkbUseCoreKbd + ); + char *group = XGetAtomName( + renderdata->dpy, desc->names->groups[s.group] + ); + ltk_free(cur_kbd); + /* just so the interface is the same for all events and the + caller doesn't have to free the contained string(s) */ + cur_kbd = ltk_strdup(group); + *event = (ltk_event){.keyboard = { + .type = LTK_KEYBOARDCHANGE_EVENT, + .new_kbd = cur_kbd + }}; + XFree(group); + XkbFreeKeyboard(desc, XkbAllComponentsMask, True); +} + +void +ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) { XEvent xevent; XNextEvent(renderdata->dpy, &xevent); + if (renderdata->xkb_supported && xevent.type == renderdata->xkb_event_type) { + ltk_generate_keyboard_event(renderdata, event); + return; + } *event = (ltk_event){.type = LTK_UNKNOWN_EVENT}; + if (XFilterEvent(&xevent, None)) + return; switch (xevent.type) { case ButtonPress: *event = (ltk_event){.button = { @@ -49,6 +187,12 @@ ltk_next_event(ltk_renderdata *renderdata, ltk_event *event) { .y = xevent.xmotion.y }}; break; + case KeyPress: + *event = process_key(renderdata, lang_index, &xevent, LTK_KEYPRESS_EVENT); + break; + case KeyRelease: + *event = process_key(renderdata, lang_index, &xevent, LTK_KEYRELEASE_EVENT); + break; case ConfigureNotify: *event = (ltk_event){.configure = { .type = LTK_CONFIGURE_EVENT, @@ -76,5 +220,7 @@ ltk_next_event(ltk_renderdata *renderdata, ltk_event *event) { if ((Atom)xevent.xclient.data.l[0] == renderdata->wm_delete_msg) *event = (ltk_event){.type = LTK_WINDOWCLOSE_EVENT}; break; + default: + break; } } diff --git a/src/eventdefs.h b/src/eventdefs.h @@ -0,0 +1,53 @@ +#ifndef LTK_EVENTDEFS_H +#define LTK_EVENTDEFS_H + +typedef enum { + LTK_UNKNOWN_EVENT, /* FIXME: a bit weird */ + LTK_BUTTONPRESS_EVENT, + LTK_BUTTONRELEASE_EVENT, + LTK_MOTION_EVENT, + LTK_KEYPRESS_EVENT, + LTK_KEYRELEASE_EVENT, + LTK_CONFIGURE_EVENT, + LTK_EXPOSE_EVENT, + LTK_WINDOWCLOSE_EVENT, + LTK_KEYBOARDCHANGE_EVENT, +} ltk_event_type; + +/* FIXME: button mask also in motion */ + +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? */ +typedef enum { + LTK_KEY_NONE = 0, + LTK_KEY_LEFT, + LTK_KEY_RIGHT, + LTK_KEY_UP, + LTK_KEY_DOWN, + LTK_KEY_BACKSPACE, + LTK_KEY_DELETE, + LTK_KEY_SPACE, + LTK_KEY_RETURN, + LTK_KEY_TAB, + LTK_KEY_ESCAPE, +} ltk_keysym; + +typedef enum { + LTK_MOD_NONE = 0, + LTK_MOD_CTRL = 1, + LTK_MOD_SHIFT = 2, + LTK_MOD_ALT = 4, + LTK_MOD_SUPER = 8 +} ltk_mod_type; + +#endif /* LTK_EVENTDEFS_H */ diff --git a/src/graphics.h b/src/graphics.h @@ -75,9 +75,10 @@ XftDraw *ltk_surface_get_xft_draw(ltk_surface *s); Drawable ltk_surface_get_drawable(ltk_surface *s); #endif +void renderer_set_imspot(ltk_renderdata *renderdata, int x, int y); ltk_renderdata *renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned int h); void renderer_destroy_window(ltk_renderdata *renderdata); -void renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg, ltk_color *border, unsigned int border_width); +void renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg); /* FIXME: this is kind of out of place */ void renderer_swap_buffers(ltk_renderdata *renderdata); /* FIXME: this is just for the socket name and is a bit weird */ diff --git a/src/graphics_xlib.c b/src/graphics_xlib.c @@ -14,10 +14,14 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <stdio.h> +#include <stdint.h> + #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/extensions/Xdbe.h> -#include <stdint.h> +#include <X11/XKBlib.h> +#include <X11/extensions/XKBrules.h> #include "color.h" #include "rect.h" @@ -351,6 +355,83 @@ ltk_surface_get_drawable(ltk_surface *s) { return s->d; } +/* FIXME: move this to a file where it makes more sense */ +/* blatantly stolen from st */ +static void ximinstantiate(Display *dpy, XPointer client, XPointer call); +static void ximdestroy(XIM xim, XPointer client, XPointer call); +static int xicdestroy(XIC xim, XPointer client, XPointer call); +static int ximopen(ltk_renderdata *renderdata, Display *dpy); + +static void +ximdestroy(XIM xim, XPointer client, XPointer call) { + (void)xim; + (void)call; + ltk_renderdata *renderdata = (ltk_renderdata *)client; + renderdata->xim = NULL; + XRegisterIMInstantiateCallback( + renderdata->dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)renderdata + ); + XFree(renderdata->spotlist); +} + +static int +xicdestroy(XIC xim, XPointer client, XPointer call) { + (void)xim; + (void)call; + ltk_renderdata *renderdata = (ltk_renderdata *)client; + renderdata->xic = NULL; + return 1; +} + +static int +ximopen(ltk_renderdata *renderdata, Display *dpy) { + XIMCallback imdestroy = { .client_data = (XPointer)renderdata, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = (XPointer)renderdata, .callback = xicdestroy }; + + renderdata->xim = XOpenIM(dpy, NULL, NULL, NULL); + if (renderdata->xim == NULL) + return 0; + + if (XSetIMValues(renderdata->xim, XNDestroyCallback, &imdestroy, NULL)) + ltk_warn("XSetIMValues: Could not set XNDestroyCallback.\n"); + + renderdata->spotlist = XVaCreateNestedList(0, XNSpotLocation, &renderdata->spot, NULL); + + if (renderdata->xic == NULL) { + renderdata->xic = XCreateIC( + renderdata->xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, renderdata->xwindow, + XNDestroyCallback, &icdestroy, NULL + ); + } + if (renderdata->xic == NULL) + ltk_warn("XCreateIC: Could not create input context.\n"); + + return 1; +} + +static void +ximinstantiate(Display *dpy, XPointer client, XPointer call) { + (void)call; + ltk_renderdata *renderdata = (ltk_renderdata *)client; + if (ximopen(renderdata, dpy)) { + XUnregisterIMInstantiateCallback( + dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)renderdata + ); + } +} + +void +renderer_set_imspot(ltk_renderdata *renderdata, int x, int y) { + if (renderdata->xic == NULL) + return; + /* FIXME! */ + renderdata->spot.x = x; + renderdata->spot.y = y; + XSetICValues(renderdata->xic, XNPreeditAttributes, renderdata->spotlist, NULL); +} + ltk_renderdata * renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) { XSetWindowAttributes attrs; @@ -430,15 +511,48 @@ renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned title, NULL, None, NULL, 0, NULL ); XSetWMProtocols(renderdata->dpy, renderdata->xwindow, &renderdata->wm_delete_msg, 1); + + renderdata->xim = NULL; + renderdata->xic = NULL; + if (!ximopen(renderdata, renderdata->dpy)) { + XRegisterIMInstantiateCallback( + renderdata->dpy, NULL, NULL, NULL, + ximinstantiate, (XPointer)renderdata + ); + } + XClearWindow(renderdata->dpy, renderdata->xwindow); XMapRaised(renderdata->dpy, renderdata->xwindow); + renderdata->xkb_supported = 1; + renderdata->xkb_event_type = 0; + if (!XkbQueryExtension(renderdata->dpy, 0, &renderdata->xkb_event_type, NULL, &major, &minor)) { + ltk_warn("XKB not supported.\n"); + renderdata->xkb_supported = 0; + } else { + /* This should select the events when the keyboard mapping changes. + * When e.g. 'setxkbmap us' is executed, two events are sent, but I + * haven't figured out how to change that. When the xkb layout + * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'), + * this issue does not occur because only a state event is sent. */ + XkbSelectEvents( + renderdata->dpy, XkbUseCoreKbd, + XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask + ); + XkbSelectEventDetails( + renderdata->dpy, XkbUseCoreKbd, XkbStateNotify, + XkbAllStateComponentsMask, XkbGroupStateMask + ); + } + return renderdata; } void renderer_destroy_window(ltk_renderdata *renderdata) { XFreeGC(renderdata->dpy, renderdata->gc); + if (renderdata->spotlist) + XFree(renderdata->spotlist); XDestroyWindow(renderdata->dpy, renderdata->xwindow); XCloseDisplay(renderdata->dpy); ltk_free(renderdata); @@ -447,10 +561,8 @@ renderer_destroy_window(ltk_renderdata *renderdata) { /* FIXME: this is a completely random collection of properties and should be changed to a more sensible list */ void -renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg, ltk_color *border, unsigned int border_width) { - XSetWindowBorder(renderdata->dpy, renderdata->xwindow, border->xcolor.pixel); +renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg) { XSetWindowBackground(renderdata->dpy, renderdata->xwindow, bg->xcolor.pixel); - XSetWindowBorderWidth(renderdata->dpy, renderdata->xwindow, border_width); } void diff --git a/src/grid.c b/src/grid.c @@ -39,7 +39,7 @@ static void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight); static void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight); -static void ltk_grid_draw(ltk_widget *self, ltk_rect clip); +static void ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); static ltk_grid *ltk_grid_create(ltk_window *window, const char *id, int rows, int columns); static void ltk_grid_destroy(ltk_widget *self, int shallow); @@ -52,6 +52,17 @@ static int ltk_grid_find_nearest_column(ltk_grid *grid, int x); static int ltk_grid_find_nearest_row(ltk_grid *grid, int y); static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y); +static ltk_widget *ltk_grid_prev_child(ltk_widget *self, ltk_widget *child); +static ltk_widget *ltk_grid_next_child(ltk_widget *self, ltk_widget *child); +static ltk_widget *ltk_grid_first_child(ltk_widget *self); +static ltk_widget *ltk_grid_last_child(ltk_widget *self); + +static ltk_widget *ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect); +static ltk_widget *ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget); + static struct ltk_widget_vtable vtable = { .draw = &ltk_grid_draw, .destroy = &ltk_grid_destroy, @@ -68,6 +79,15 @@ static struct ltk_widget_vtable vtable = { .mouse_enter = NULL, .key_press = NULL, .key_release = NULL, + .prev_child = &ltk_grid_prev_child, + .next_child = &ltk_grid_next_child, + .first_child = &ltk_grid_first_child, + .last_child = &ltk_grid_last_child, + .nearest_child = &ltk_grid_nearest_child, + .nearest_child_left = &ltk_grid_nearest_child_left, + .nearest_child_right = &ltk_grid_nearest_child_right, + .nearest_child_above = &ltk_grid_nearest_child_above, + .nearest_child_below = &ltk_grid_nearest_child_below, .type = LTK_WIDGET_GRID, .flags = 0, }; @@ -111,14 +131,15 @@ ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight) { } static void -ltk_grid_draw(ltk_widget *self, ltk_rect clip) { +ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { ltk_grid *grid = (ltk_grid *)self; int i; + ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip); for (i = 0; i < grid->rows * grid->columns; i++) { if (!grid->widget_grid[i]) continue; ltk_widget *ptr = grid->widget_grid[i]; - ptr->vtable->draw(ptr, clip); + ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, real_clip)); } } @@ -164,6 +185,7 @@ static void ltk_grid_destroy(ltk_widget *self, int shallow) { ltk_grid *grid = (ltk_grid *)self; ltk_widget *ptr; + ltk_error err; for (int i = 0; i < grid->rows * grid->columns; i++) { if (grid->widget_grid[i]) { ptr = grid->widget_grid[i]; @@ -176,7 +198,7 @@ ltk_grid_destroy(ltk_widget *self, int shallow) { grid->widget_grid[r * grid->columns + c] = NULL; } } - ptr->vtable->destroy(ptr, shallow); + ltk_widget_destroy(ptr, shallow, &err); } } } @@ -187,8 +209,6 @@ ltk_grid_destroy(ltk_widget *self, int shallow) { ltk_free(grid->column_weights); ltk_free(grid->row_pos); ltk_free(grid->column_pos); - ltk_remove_widget(self->id); - ltk_free(self->id); ltk_free(grid); } @@ -213,10 +233,10 @@ ltk_recalculate_grid(ltk_widget *self) { } } if (total_row_weight > 0) { - height_unit = (float) (grid->widget.rect.h - height_static) / (float) total_row_weight; + height_unit = (float) (grid->widget.lrect.h - height_static) / (float) total_row_weight; } if (total_column_weight > 0) { - width_unit = (float) (grid->widget.rect.w - width_static) / (float) total_column_weight; + width_unit = (float) (grid->widget.lrect.w - width_static) / (float) total_column_weight; } for (i = 0; i < grid->rows; i++) { grid->row_pos[i] = currenty; @@ -238,20 +258,18 @@ ltk_recalculate_grid(ltk_widget *self) { int end_column, end_row; for (i = 0; i < grid->rows; i++) { for (j = 0; j < grid->columns; j++) { - if (!grid->widget_grid[i * grid->columns + j]) - continue; ltk_widget *ptr = grid->widget_grid[i * grid->columns + j]; - if (ptr->row != i || ptr->column != j) + if (!ptr || ptr->row != i || ptr->column != j) continue; - /*orig_width = ptr->rect.w; - orig_height = ptr->rect.h;*/ + /*orig_width = ptr->lrect.w; + orig_height = ptr->lrect.h;*/ end_row = i + ptr->row_span; end_column = j + ptr->column_span; if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT) { - ptr->rect.w = grid->column_pos[end_column] - grid->column_pos[j]; + ptr->lrect.w = grid->column_pos[end_column] - grid->column_pos[j]; } if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) { - ptr->rect.h = grid->row_pos[end_row] - grid->row_pos[i]; + ptr->lrect.h = grid->row_pos[end_row] - grid->row_pos[i]; } /* FIXME: Figure out a better system for this - it would be nice to make it more efficient by not doing anything if nothing changed, but that doesn't work when @@ -261,24 +279,25 @@ ltk_recalculate_grid(ltk_widget *self) { doesn't change the size of the container, the position/size of the widget at the bottom of the hierarchy will never be updated. That's why updates are forced here even if seemingly nothing changed, but there probably is a better way. */ - /*if (orig_width != ptr->rect.w || orig_height != ptr->rect.h)*/ + /*if (orig_width != ptr->lrect.w || orig_height != ptr->lrect.h)*/ ltk_widget_resize(ptr); if (ptr->sticky & LTK_STICKY_RIGHT) { - ptr->rect.x = grid->column_pos[end_column] - ptr->rect.w; + ptr->lrect.x = grid->column_pos[end_column] - ptr->lrect.w; } else if (ptr->sticky & LTK_STICKY_LEFT) { - ptr->rect.x = grid->column_pos[j]; + ptr->lrect.x = grid->column_pos[j]; } else { - ptr->rect.x = grid->column_pos[j] + ((grid->column_pos[end_column] - grid->column_pos[j]) / 2 - ptr->rect.w / 2); + ptr->lrect.x = grid->column_pos[j] + ((grid->column_pos[end_column] - grid->column_pos[j]) / 2 - ptr->lrect.w / 2); } if (ptr->sticky & LTK_STICKY_BOTTOM) { - ptr->rect.y = grid->row_pos[end_row] - ptr->rect.h; + ptr->lrect.y = grid->row_pos[end_row] - ptr->lrect.h; } else if (ptr->sticky & LTK_STICKY_TOP) { - ptr->rect.y = grid->row_pos[i]; + ptr->lrect.y = grid->row_pos[i]; } else { - ptr->rect.y = grid->row_pos[i] + ((grid->row_pos[end_row] - grid->row_pos[i]) / 2 - ptr->rect.h / 2); + ptr->lrect.y = grid->row_pos[i] + ((grid->row_pos[end_row] - grid->row_pos[i]) / 2 - ptr->lrect.h / 2); } + ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect); } } } @@ -288,29 +307,27 @@ static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) { ltk_grid *grid = (ltk_grid *)self; short size_changed = 0; - /* FIXME: this is kind of hacky right now because a size_change needs to be - checked here as well so the surface (if needed) is resized */ - int orig_w = widget->rect.w; - int orig_h = widget->rect.h; - widget->rect.w = widget->ideal_w; - widget->rect.h = widget->ideal_h; + int orig_w = widget->lrect.w; + int orig_h = widget->lrect.h; + widget->lrect.w = widget->ideal_w; + widget->lrect.h = widget->ideal_h; if (grid->column_weights[widget->column] == 0 && - widget->rect.w > grid->column_widths[widget->column]) { - grid->widget.ideal_w += widget->rect.w - grid->column_widths[widget->column]; - grid->column_widths[widget->column] = widget->rect.w; + widget->lrect.w > grid->column_widths[widget->column]) { + grid->widget.ideal_w += widget->lrect.w - grid->column_widths[widget->column]; + grid->column_widths[widget->column] = widget->lrect.w; size_changed = 1; } if (grid->row_weights[widget->row] == 0 && - widget->rect.h > grid->row_heights[widget->row]) { - grid->widget.ideal_h += widget->rect.h - grid->row_heights[widget->row]; - grid->row_heights[widget->row] = widget->rect.h; + widget->lrect.h > grid->row_heights[widget->row]) { + grid->widget.ideal_h += widget->lrect.h - grid->row_heights[widget->row]; + grid->row_heights[widget->row] = widget->lrect.h; size_changed = 1; } if (size_changed && grid->widget.parent && grid->widget.parent->vtable->child_size_change) grid->widget.parent->vtable->child_size_change(grid->widget.parent, (ltk_widget *)grid); else ltk_recalculate_grid((ltk_widget *)grid); - if (widget->rect.w != orig_w || widget->rect.h != orig_h) + if (widget->lrect.w != orig_w || widget->lrect.h != orig_h) ltk_widget_resize(widget); } @@ -338,7 +355,7 @@ ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid, } widget->parent = (ltk_widget *)grid; ltk_grid_child_size_change((ltk_widget *)grid, widget); - ltk_window_invalidate_rect(window, grid->widget.rect); + ltk_window_invalidate_widget_rect(window, &grid->widget); return 0; } @@ -356,7 +373,7 @@ ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err) { grid->widget_grid[i * grid->columns + j] = NULL; } } - ltk_window_invalidate_rect(widget->window, grid->widget.rect); + ltk_window_invalidate_widget_rect(self->window, &grid->widget); return 0; } @@ -383,6 +400,82 @@ ltk_grid_find_nearest_row(ltk_grid *grid, int y) { return -1; } +/* FIXME: maybe come up with a more efficient method */ +static ltk_widget * +ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) { + ltk_grid *grid = (ltk_grid *)self; + ltk_widget *minw = NULL; + int min_dist = INT_MAX; + int cx = rect.x + rect.w / 2; + int cy = rect.y + rect.h / 2; + ltk_rect r; + int dist; + /* FIXME: rows and columns shouldn't be int */ + for (size_t i = 0; i < (size_t)(grid->rows * grid->columns); i++) { + if (!grid->widget_grid[i]) + continue; + /* FIXME: this checks widgets with row/columnspan > 1 multiple times */ + r = grid->widget_grid[i]->lrect; + dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy); + if (dist < min_dist) { + min_dist = dist; + minw = grid->widget_grid[i]; + } + } + return minw; +} + +/* FIXME: assertions to check that widget row/column are legal */ +static ltk_widget * +ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget) { + ltk_grid *grid = (ltk_grid *)self; + unsigned int col = widget->column; + ltk_widget *cur = NULL; + while (col-- > 0) { + cur = grid->widget_grid[widget->row * grid->columns + col]; + if (cur && cur != widget) + return cur; + } + return NULL; +} + +static ltk_widget * +ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget) { + ltk_grid *grid = (ltk_grid *)self; + ltk_widget *cur = NULL; + for (int col = widget->column + 1; col < grid->columns; col++) { + cur = grid->widget_grid[widget->row * grid->columns + col]; + if (cur && cur != widget) + return cur; + } + return NULL; +} + +static ltk_widget * +ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget) { + ltk_grid *grid = (ltk_grid *)self; + unsigned int row = widget->row; + ltk_widget *cur = NULL; + while (row-- > 0) { + cur = grid->widget_grid[row * grid->columns + widget->column]; + if (cur && cur != widget) + return cur; + } + return NULL; +} + +static ltk_widget * +ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget) { + ltk_grid *grid = (ltk_grid *)self; + ltk_widget *cur = NULL; + for (int row = widget->row + 1; row < grid->rows; row++) { + cur = grid->widget_grid[row * grid->columns + widget->column]; + if (cur && cur != widget) + return cur; + } + return NULL; +} + static ltk_widget * ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) { ltk_grid *grid = (ltk_grid *)self; @@ -391,11 +484,53 @@ ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) { 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)) + if (ptr && ltk_collide_rect(ptr->crect, x, y)) return ptr; return NULL; } +static ltk_widget * +ltk_grid_prev_child(ltk_widget *self, ltk_widget *child) { + ltk_grid *grid = (ltk_grid *)self; + unsigned int start = child->row * grid->columns + child->column; + while (start-- > 0) { + if (grid->widget_grid[start]) + return grid->widget_grid[start]; + } + return NULL; +} + +static ltk_widget * +ltk_grid_next_child(ltk_widget *self, ltk_widget *child) { + ltk_grid *grid = (ltk_grid *)self; + unsigned int start = child->row * grid->columns + child->column; + while (++start < (unsigned int)(grid->rows * grid->columns)) { + if (grid->widget_grid[start] && grid->widget_grid[start] != child) + return grid->widget_grid[start]; + } + return NULL; +} + +static ltk_widget * +ltk_grid_first_child(ltk_widget *self) { + ltk_grid *grid = (ltk_grid *)self; + for (unsigned int i = 0; i < (unsigned int)(grid->rows * grid->columns); i++) { + if (grid->widget_grid[i]) + return grid->widget_grid[i]; + } + return NULL; +} + +static ltk_widget * +ltk_grid_last_child(ltk_widget *self) { + ltk_grid *grid = (ltk_grid *)self; + for (unsigned int i = grid->rows * grid->columns; i-- > 0;) { + if (grid->widget_grid[i]) + return grid->widget_grid[i]; + } + return NULL; +} + /* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> [sticky] */ static int ltk_grid_cmd_add( diff --git a/src/label.c b/src/label.c @@ -35,7 +35,7 @@ #define MAX_LABEL_PADDING 500 -static void ltk_label_draw(ltk_widget *self, ltk_rect clip); +static void ltk_label_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip); static ltk_label *ltk_label_create(ltk_window *window, const char *id, char *text); static void ltk_label_destroy(ltk_widget *self, int shallow); @@ -58,12 +58,13 @@ static struct ltk_widget_vtable vtable = { .mouse_leave = NULL, .mouse_enter = NULL, .type = LTK_WIDGET_LABEL, - .flags = LTK_NEEDS_REDRAW, + .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_SPECIAL, }; static struct { ltk_color text_color; ltk_color bg_color; + ltk_color bg_color_active; int pad; } theme; @@ -71,6 +72,7 @@ int parseinfo_sorted = 0; static ltk_theme_parseinfo parseinfo[] = { {"bg-color", THEME_COLOR, {.color = &theme.bg_color}, {.color = "#000000"}, 0, 0, 0}, + {"bg-color-active", THEME_COLOR, {.color = &theme.bg_color_active}, {.color = "#222222"}, 0, 0, 0}, {"pad", THEME_INT, {.i = &theme.pad}, {.i = 5}, 0, MAX_LABEL_PADDING, 0}, {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0}, }; @@ -91,23 +93,25 @@ ltk_label_uninitialize_theme(ltk_window *window) { } static void -ltk_label_draw(ltk_widget *self, ltk_rect clip) { +ltk_label_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { ltk_label *label = (ltk_label *)self; - ltk_rect rect = label->widget.rect; - ltk_rect clip_final = ltk_rect_intersect(clip, rect); + ltk_rect lrect = self->lrect; + ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); + if (clip_final.w <= 0 || clip_final.h <= 0) + return; ltk_surface *s; - ltk_surface_cache_request_surface_size(label->key, self->rect.w, self->rect.h); + ltk_surface_cache_request_surface_size(label->key, lrect.w, lrect.h); if (!ltk_surface_cache_get_surface(label->key, &s) || self->dirty) ltk_label_redraw_surface(label, s); - ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y); + ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y); } static void ltk_label_redraw_surface(ltk_label *label, ltk_surface *s) { - ltk_rect r = label->widget.rect; + ltk_rect r = label->widget.lrect; r.x = 0; r.y = 0; - ltk_surface_fill_rect(s, &theme.bg_color, r); + ltk_surface_fill_rect(s, (label->widget.state & LTK_ACTIVE) ? &theme.bg_color_active : &theme.bg_color, r); int text_w, text_h; ltk_text_line_get_size(label->tl, &text_w, &text_h); @@ -124,9 +128,9 @@ ltk_label_create(ltk_window *window, const char *id, char *text) { label->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1); int text_w, text_h; ltk_text_line_get_size(label->tl, &text_w, &text_h); + ltk_fill_widget_defaults(&label->widget, id, window, &vtable, label->widget.ideal_w, label->widget.ideal_h); label->widget.ideal_w = text_w + theme.pad * 2; label->widget.ideal_h = text_h + theme.pad * 2; - ltk_fill_widget_defaults(&label->widget, id, window, &vtable, label->widget.ideal_w, label->widget.ideal_h); label->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, label->widget.ideal_w, label->widget.ideal_h); return label; @@ -142,8 +146,6 @@ ltk_label_destroy(ltk_widget *self, int shallow) { } ltk_surface_cache_release_key(label->key); ltk_text_line_destroy(label->tl); - ltk_remove_widget(self->id); - ltk_free(self->id); ltk_free(label); } diff --git a/src/ltk.h b/src/ltk.h @@ -60,6 +60,7 @@ struct ltk_window { ltk_rect rect; ltk_window_theme *theme; ltk_rect dirty_rect; + size_t cur_kbd; /* FIXME: generic array */ ltk_widget **popups; size_t popups_num; @@ -84,7 +85,7 @@ 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_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release); void ltk_quit(ltk_window *window); void ltk_unregister_timer(int timer_id); @@ -97,4 +98,8 @@ int ltk_queue_specific_event(ltk_widget *widget, const char *type, uint32_t mask int ltk_queue_sock_write(int client, const char *str, int len); int ltk_queue_sock_write_fmt(int client, const char *fmt, ...); +ltk_point ltk_widget_pos_to_global(ltk_widget *widget, int x, int y); +ltk_point ltk_global_to_widget_pos(ltk_widget *widget, int x, int y); +void ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget); + #endif diff --git a/src/ltkd.c b/src/ltkd.c @@ -29,6 +29,7 @@ #include <unistd.h> #include <signal.h> #include <stdint.h> +#include <locale.h> #include <inttypes.h> #include <sys/un.h> @@ -57,8 +58,8 @@ #include "box.h" #include "menu.h" #include "macros.h" +#include "config.h" -#define MAX_WINDOW_BORDER_WIDTH 100 #define MAX_WINDOW_FONT_SIZE 200 #define MAX_SOCK_CONNS 20 @@ -143,7 +144,10 @@ static char *sock_path = NULL; global originally, but that's just the way it is. */ static ltk_window *main_window = NULL; -int main(int argc, char *argv[]) { +int +main(int argc, char *argv[]) { + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); int ch; char *title = "LTK Window"; while ((ch = getopt(argc, argv, "dt:")) != -1) { @@ -284,6 +288,10 @@ ltk_mainloop(ltk_window *window) { /* FIXME: framerate limiting for draw */ + /* initialize keyboard mapping */ + ltk_generate_keyboard_event(window->renderdata, &event); + ltk_handle_event(window, &event); + while (running) { rfds = sock_state.rallfds; wfds = sock_state.wallfds; @@ -295,7 +303,7 @@ ltk_mainloop(ltk_window *window) { 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, &event); + ltk_next_event(window->renderdata, window->cur_kbd, &event); ltk_handle_event(window, &event); } @@ -481,7 +489,9 @@ ltk_cleanup(void) { ltk_free(sockets[i].tokens.tokens); } + ltk_config_cleanup(); ltk_widgets_cleanup(); + ltk_events_cleanup(); if (main_window) { ltk_uninitialize_theme(main_window); ltk_destroy_window(main_window); @@ -530,9 +540,12 @@ ltk_set_root_widget_cmd( return 1; } window->root_widget = widget; - ltk_window_invalidate_rect(window, widget->rect); - widget->rect.w = window->rect.w; - widget->rect.h = window->rect.h; + widget->lrect.x = 0; + widget->lrect.y = 0; + widget->lrect.w = window->rect.w; + widget->lrect.h = window->rect.h; + widget->crect = widget->lrect; + ltk_window_invalidate_rect(window, widget->lrect); ltk_widget_resize(widget); return 0; @@ -546,6 +559,38 @@ ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) { window->dirty_rect = ltk_rect_union(rect, window->dirty_rect); } +ltk_point +ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) { + ltk_widget *cur = widget; + while (cur) { + x += cur->lrect.x; + y += cur->lrect.y; + if (cur->popup) + break; + cur = cur->parent; + } + return (ltk_point){x, y}; +} + +ltk_point +ltk_global_to_widget_pos(ltk_widget *widget, int x, int y) { + ltk_widget *cur = widget; + while (cur) { + x -= cur->lrect.x; + y -= cur->lrect.y; + if (cur->popup) + break; + cur = cur->parent; + } + return (ltk_point){x, y}; +} + +void +ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget) { + ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0); + ltk_window_invalidate_rect(window, (ltk_rect){glob.x, glob.y, widget->lrect.w, widget->lrect.h}); +} + /* FIXME: generic event handling functions that take the actual uint32_t event type, etc. */ int ltk_queue_specific_event(ltk_widget *widget, const char *type, uint32_t mask, const char *data) { @@ -586,12 +631,12 @@ ltk_redraw_window(ltk_window *window) { ltk_surface_fill_rect(window->surface, &window->theme->bg, (ltk_rect){0, 0, window->rect.w, window->rect.h}); if (window->root_widget) { ptr = window->root_widget; - ptr->vtable->draw(ptr, window->rect); + ptr->vtable->draw(ptr, window->surface, 0, 0, window->rect); } /* last popup is the newest one, so draw that last */ for (size_t i = 0; i < window->popups_num; i++) { ptr = window->popups[i]; - ptr->vtable->draw(ptr, window->rect); + ptr->vtable->draw(ptr, window->surface, ptr->lrect.x, ptr->lrect.y, ltk_rect_relative(ptr->lrect, window->rect)); } renderer_swap_buffers(window->renderdata); } @@ -612,8 +657,9 @@ ltk_window_other_event(ltk_window *window, ltk_event *event) { ltk_window_invalidate_rect(window, window->rect); ltk_surface_update_size(window->surface, w, h); if (ptr) { - ptr->rect.w = w; - ptr->rect.h = h; + ptr->lrect.w = w; + ptr->lrect.h = h; + ptr->crect = ptr->lrect; ltk_widget_resize(ptr); } } @@ -696,6 +742,7 @@ ltk_window_register_popup(ltk_window *window, ltk_widget *popup) { ); } window->popups[window->popups_num++] = popup; + popup->popup = 1; } void @@ -704,6 +751,7 @@ ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup) { return; for (size_t i = 0; i < window->popups_num; i++) { if (window->popups[i] == popup) { + popup->popup = 0; memmove( window->popups + i, window->popups + i + 1, @@ -730,6 +778,7 @@ ltk_window_unregister_all_popups(ltk_window *window) { window->popups_locked = 1; for (size_t i = 0; i < window->popups_num; i++) { window->popups[i]->hidden = 1; + window->popups[i]->popup = 0; ltk_widget_hide(window->popups[i]); } window->popups_num = 0; @@ -747,7 +796,6 @@ ltk_window_unregister_all_popups(ltk_window *window) { ltk_window_theme window_theme; static ltk_theme_parseinfo theme_parseinfo[] = { - {"border-width", THEME_INT, {.i = &window_theme.border_width}, {.i = 0}, 0, MAX_WINDOW_BORDER_WIDTH, 0}, {"font-size", THEME_INT, {.i = &window_theme.font_size}, {.i = 15}, 0, MAX_WINDOW_FONT_SIZE, 0}, {"font", THEME_STRING, {.str = &window_theme.font}, {.str = "Liberation Mono"}, 0, 0, 0}, {"bg", THEME_COLOR, {.color = &window_theme.bg}, {.color = "#000000"}, 0, 0, 0}, @@ -831,10 +879,24 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int window->popups = NULL; window->popups_num = window->popups_alloc = 0; window->popups_locked = 0; + window->cur_kbd = 0; ltk_renderdata *renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned int h); - void renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg_pixel, ltk_color *border_pixel, unsigned int border_width); window->renderdata = renderer_create_window(title, x, y, w, h); + /* FIXME: search different directories for config */ + char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg"); + char *errstr = NULL; + if (ltk_config_parsefile(config_path, &errstr)) { + if (errstr) { + ltk_warn("Unable to load config: %s\n", errstr); + ltk_free(errstr); + } + if (ltk_config_load_default(&errstr)) { + /* FIXME: I guess errstr isn't freed here, but whatever */ + ltk_fatal("Unable to load default config: %s\n", errstr); + } + } + ltk_free(config_path); theme_path = ltk_strcat_useful(ltk_dir, "/theme.ini"); window->theme = &window_theme; ltk_load_theme(window, theme_path); @@ -842,7 +904,7 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int /* FIXME: fix theme memory leaks when exit happens between here and the end of this function */ - renderer_set_window_properties(window->renderdata, &window->theme->bg, &window->theme->fg, window->theme->border_width); + renderer_set_window_properties(window->renderdata, &window->theme->bg); window->root_widget = NULL; window->hover_widget = NULL; @@ -879,22 +941,34 @@ ltk_destroy_window(ltk_window *window) { ltk_free(window); } +/* event must have global coordinates! */ 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; + int orig_x = event->x, orig_y = event->y; if (old) { ltk_widget_state old_state = old->state; old->state &= ~LTK_HOVER; ltk_widget_change_state(old, old_state); - if (old->vtable->mouse_leave) + if (old->vtable->mouse_leave) { + ltk_point local = ltk_global_to_widget_pos(old, event->x, event->y); + event->x = local.x; + event->y = local.y; old->vtable->mouse_leave(old, event); + event->x = orig_x; + event->y = orig_y; + } } window->hover_widget = widget; if (widget) { - if (widget->vtable->mouse_enter) + if (widget->vtable->mouse_enter) { + ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y); + event->x = local.x; + event->y = local.y; widget->vtable->mouse_enter(widget, event); + } ltk_widget_state old_state = widget->state; widget->state |= LTK_HOVER; ltk_widget_change_state(widget, old_state); @@ -922,6 +996,9 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { } ltk_widget_state old_state = cur->state; cur->state |= LTK_ACTIVE; + /* FIXME: should all be set focused? */ + if (cur == widget && !(cur->vtable->flags & LTK_NEEDS_KEYBOARD)) + widget->state |= LTK_FOCUSED; ltk_widget_change_state(cur, old_state); cur = cur->parent; } @@ -948,21 +1025,25 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { } void -ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget) { +ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release) { if (window->pressed_widget == widget) return; - /* FIXME: won't work properly when key navigation is added and enter can be - used to set a widget to pressed while the pointer is still on another - widget */ - /* -> also need generic pressed/released callbacks instead of just mouse_press/leave */ if (window->pressed_widget) { ltk_widget_state old_state = window->pressed_widget->state; window->pressed_widget->state &= ~LTK_PRESSED; ltk_widget_change_state(window->pressed_widget, old_state); ltk_window_set_active_widget(window, window->pressed_widget); + /* FIXME: this is a bit weird because the release handler for menuentry + indirectly calls ltk_widget_hide, which messes with the pressed widget */ + /* FIXME: isn't it redundant to check that state is pressed? */ + if (release && window->pressed_widget->vtable->release && (old_state & LTK_PRESSED)) { + window->pressed_widget->vtable->release(window->pressed_widget); + } } window->pressed_widget = widget; if (widget) { + if (widget->vtable->press) + widget->vtable->press(widget); ltk_widget_state old_state = widget->state; widget->state |= LTK_PRESSED; ltk_widget_change_state(widget, old_state); @@ -971,10 +1052,13 @@ ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget) { static void ltk_handle_event(ltk_window *window, ltk_event *event) { + size_t kbd_idx; switch (event->type) { case LTK_KEYPRESS_EVENT: + ltk_window_key_press_event(window, &event->key); break; case LTK_KEYRELEASE_EVENT: + ltk_window_key_release_event(window, &event->key); break; case LTK_BUTTONPRESS_EVENT: ltk_window_mouse_press_event(window, &event->button); @@ -985,6 +1069,13 @@ ltk_handle_event(ltk_window *window, ltk_event *event) { case LTK_MOTION_EVENT: ltk_window_motion_notify_event(window, &event->motion); break; + case LTK_KEYBOARDCHANGE_EVENT: + /* FIXME: emit event */ + if (ltk_config_get_language_index(event->keyboard.new_kbd, &kbd_idx)) + ltk_warn("No language mapping for language \"%s\".\n", event->keyboard.new_kbd); + else + window->cur_kbd = kbd_idx; + break; default: if (window->other_event) window->other_event(window, event); @@ -1317,6 +1408,7 @@ handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err } else { err->type = ERR_INVALID_ARGUMENT; err->arg = 2; + return 1; } if (num_tokens == 5) { if (!strcmp(tokens[4], "lock")) { diff --git a/src/memory.c b/src/memory.c @@ -40,6 +40,24 @@ ltk_strdup_debug(const char *s, const char *caller, const char *file, int line) return str; } +char * +ltk_strndup_impl(const char *s, size_t n) { + char *str = strndup(s, n); + if (!str) + ltk_fatal("Out of memory.\n"); + return str; +} + +char * +ltk_strndup_debug(const char *s, size_t n, const char *caller, const char *file, int line) { + char *str = strndup(s, n); + fprintf(stderr, "DEBUG: strndup %p to %p in %s (%s:%d)\n", + (void *)s, (void *)str, caller, file, line); + if (!str) + ltk_fatal("Out of memory.\n"); + return str; +} + void * ltk_malloc_impl(size_t size) { void *ptr = malloc(size); @@ -137,3 +155,20 @@ ideal_array_size(size_t old, size_t needed) { ret = 1; /* not sure if this is necessary */ return ret; } + +char * +ltk_print_fmt(char *fmt, ...) { + va_list args; + va_start(args, fmt); + int len = vsnprintf(NULL, 0, fmt, args); + /* FIXME: what should be done on error? */ + if (len < 0) + ltk_fatal("Error in vsnprintf called from print_fmt"); + /* FIXME: overflow */ + char *str = ltk_malloc(len + 1); + va_end(args); + va_start(args, fmt); + vsnprintf(str, len + 1, fmt, args); + va_end(args); + return str; +} diff --git a/src/memory.h b/src/memory.h @@ -14,21 +14,23 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef _LTK_MEMORY_H_ -#define _LTK_MEMORY_H_ +#ifndef LTK_MEMORY_H +#define LTK_MEMORY_H /* FIXME: Move ltk_warn, etc. to util.* */ -/* Requires: <stdlib.h> */ +#include <stdlib.h> -#if DEV == 1 +#if MEMDEBUG == 1 #define ltk_strdup(s) ltk_strdup_debug(s, __func__, __FILE__, __LINE__) + #define ltk_strndup(s, n) ltk_strndup_debug(s, n, __func__, __FILE__, __LINE__) #define ltk_malloc(size) ltk_malloc_debug(size, __func__, __FILE__, __LINE__) #define ltk_calloc(nmemb, size) ltk_calloc_debug(nmemb, size, __func__, __FILE__, __LINE__) #define ltk_realloc(ptr, size) ltk_realloc_debug(ptr, size, __func__, __FILE__, __LINE__) #define ltk_free(ptr) ltk_free_debug(ptr, __func__, __FILE__, __LINE__) #else #define ltk_strdup(s) ltk_strdup_impl(s) + #define ltk_strndup(s, n) ltk_strndup_impl(s, n) #define ltk_malloc(size) ltk_malloc_impl(size); #define ltk_calloc(nmemb, size) ltk_calloc_impl(nmemb, size); #define ltk_realloc(ptr, size) ltk_realloc_impl(ptr, size); @@ -36,11 +38,13 @@ #endif char *ltk_strdup_impl(const char *s); +char *ltk_strndup_impl(const char *s, size_t n); void *ltk_malloc_impl(size_t size); void *ltk_calloc_impl(size_t nmemb, size_t size); void *ltk_realloc_impl(void *ptr, size_t size); char *ltk_strdup_debug(const char *s, const char *caller, const char *file, int line); +char *ltk_strndup_debug(const char *s, size_t n, const char *caller, const char *file, int line); void *ltk_malloc_debug(size_t size, const char *caller, const char *file, int line); void *ltk_calloc_debug(size_t nmemb, size_t size, const char *caller, const char *file, int line); void *ltk_realloc_debug(void *ptr, size_t size, const char *caller, const char *file, int line); @@ -49,4 +53,9 @@ void *ltk_reallocarray(void *optr, size_t nmemb, size_t size); size_t ideal_array_size(size_t old, size_t needed); -#endif /* _LTK_MEMORY_H_ */ +/* This acts like snprintf but automatically allocates + a string of the appropriate size. + Like the other functions here, it exits on error. */ +char *ltk_print_fmt(char *fmt, ...); + +#endif /* LTK_MEMORY_H */ diff --git a/src/menu.c b/src/menu.c @@ -85,8 +85,9 @@ static struct entry_theme { ltk_color fill_disabled; } menu_entry_theme, submenu_entry_theme; +static void ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r); static void ltk_menu_resize(ltk_widget *self); -static void ltk_menu_draw(ltk_widget *self, ltk_rect clip); +static void ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret); static void ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step); static void ltk_menu_scroll_callback(void *data); @@ -106,16 +107,29 @@ static void shrink_entries(ltk_menu *menu); static size_t get_entry_with_id(ltk_menu *menu, const char *id); static void ltk_menu_destroy(ltk_widget *self, int shallow); +static ltk_widget *ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect); +static ltk_widget *ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget); +static ltk_widget *ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget); + static ltk_menuentry *ltk_menuentry_create(ltk_window *window, const char *id, const char *text); -static void ltk_menuentry_draw(ltk_widget *self, ltk_rect clip); +static void ltk_menuentry_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); static void ltk_menuentry_destroy(ltk_widget *self, int shallow); static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state); -static int ltk_menuentry_mouse_release(ltk_widget *self, ltk_button_event *event); +static int ltk_menuentry_release(ltk_widget *self); static void ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry); static int ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err); static void ltk_menuentry_detach_submenu(ltk_menuentry *e); static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err); +static int ltk_menuentry_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err); + +static ltk_widget *ltk_menu_prev_child(ltk_widget *self, ltk_widget *child); +static ltk_widget *ltk_menu_next_child(ltk_widget *self, ltk_widget *child); +static ltk_widget *ltk_menu_first_child(ltk_widget *self); +static ltk_widget *ltk_menu_last_child(ltk_widget *self); +static ltk_widget *ltk_menuentry_get_child(ltk_widget *self); #define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)e->widget.parent)->is_submenu) @@ -135,6 +149,16 @@ static struct ltk_widget_vtable vtable = { .destroy = &ltk_menu_destroy, .child_size_change = &recalc_ideal_menu_size, .remove_child = &ltk_menu_remove_child, + .prev_child = &ltk_menu_prev_child, + .next_child = &ltk_menu_next_child, + .first_child = &ltk_menu_first_child, + .last_child = &ltk_menu_last_child, + .nearest_child = &ltk_menu_nearest_child, + .nearest_child_left = &ltk_menu_nearest_child_left, + .nearest_child_right = &ltk_menu_nearest_child_right, + .nearest_child_above = &ltk_menu_nearest_child_above, + .nearest_child_below = &ltk_menu_nearest_child_below, + .ensure_rect_shown = &ltk_menu_ensure_rect_shown, .type = LTK_WIDGET_MENU, .flags = LTK_NEEDS_REDRAW, }; @@ -144,7 +168,8 @@ static struct ltk_widget_vtable entry_vtable = { .key_release = NULL, .mouse_press = NULL, .motion_notify = NULL, - .mouse_release = &ltk_menuentry_mouse_release, + .mouse_release = NULL, + .release = &ltk_menuentry_release, .mouse_enter = NULL, .mouse_leave = NULL, .get_child_at_pos = NULL, @@ -154,7 +179,9 @@ static struct ltk_widget_vtable entry_vtable = { .draw = &ltk_menuentry_draw, .destroy = &ltk_menuentry_destroy, .child_size_change = NULL, - .remove_child = NULL, + .remove_child = &ltk_menuentry_remove_child, + .first_child = &ltk_menuentry_get_child, + .last_child = &ltk_menuentry_get_child, .type = LTK_WIDGET_MENUENTRY, .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_HOVER_IS_ACTIVE, }; @@ -312,8 +339,16 @@ ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) { } } +static ltk_widget * +ltk_menuentry_get_child(ltk_widget *self) { + ltk_menuentry *e = (ltk_menuentry *)self; + if (e->submenu && !e->submenu->widget.hidden) + return &e->submenu->widget; + return NULL; +} + static void -ltk_menuentry_draw(ltk_widget *self, ltk_rect clip) { +ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { /* FIXME: figure out how hidden should work */ if (self->hidden) return; @@ -338,11 +373,12 @@ ltk_menuentry_draw(ltk_widget *self, ltk_rect clip) { border = &t->border; fill = &t->fill; } - ltk_rect rect = self->rect; - ltk_rect clip_final = ltk_rect_intersect(clip, rect); + ltk_rect lrect = self->lrect; + ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); if (clip_final.w <= 0 || clip_final.h <= 0) return; - ltk_surface_fill_rect(self->window->surface, fill, clip_final); + ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h}; + ltk_surface_fill_rect(draw_surf, fill, surf_clip); ltk_surface *s; int text_w, text_h; @@ -352,70 +388,77 @@ ltk_menuentry_draw(ltk_widget *self, ltk_rect clip) { ltk_text_line_draw(entry->text_line, s, text, 0, 0); self->dirty = 0; } - int text_x = rect.x + t->text_pad + t->border_width; - int text_y = rect.y + t->text_pad + t->border_width; + int text_x = t->text_pad + t->border_width; + int text_y = t->text_pad + t->border_width; ltk_rect text_clip = ltk_rect_intersect(clip, (ltk_rect){text_x, text_y, text_w, text_h}); ltk_surface_copy( - s, self->window->surface, - (ltk_rect){text_clip.x - text_x, text_clip.y - text_y, text_clip.w, text_clip.h}, text_clip.x, text_clip.y + s, draw_surf, + (ltk_rect){text_clip.x - text_x, text_clip.y - text_y, text_clip.w, text_clip.h}, x + text_clip.x, y + text_clip.y ); if (in_submenu && entry->submenu) { ltk_point arrow_points[] = { - {rect.x + rect.w - t->arrow_pad - t->border_width, rect.y + rect.h / 2}, - {rect.x + rect.w - t->arrow_pad - t->border_width - t->arrow_size, rect.y + rect.h / 2 - t->arrow_size / 2}, - {rect.x + rect.w - t->arrow_pad - t->border_width - t->arrow_size, rect.y + rect.h / 2 + t->arrow_size / 2} + {x + lrect.w - t->arrow_pad - t->border_width, y + lrect.h / 2}, + {x + lrect.w - t->arrow_pad - t->border_width - t->arrow_size, y + lrect.h / 2 - t->arrow_size / 2}, + {x + lrect.w - t->arrow_pad - t->border_width - t->arrow_size, y + lrect.h / 2 + t->arrow_size / 2} }; - ltk_surface_fill_polygon_clipped(self->window->surface, text, arrow_points, LENGTH(arrow_points), clip_final); + ltk_surface_fill_polygon_clipped(draw_surf, text, arrow_points, LENGTH(arrow_points), surf_clip); } - ltk_surface_draw_border_clipped(self->window->surface, border, rect, clip_final, t->border_width, t->border_sides); + ltk_surface_draw_border_clipped(draw_surf, border, (ltk_rect){x, y, lrect.w, lrect.h}, surf_clip, t->border_width, t->border_sides); } static void -ltk_menu_draw(ltk_widget *self, ltk_rect clip) { +ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { if (self->hidden) return; ltk_menu *menu = (ltk_menu *)self; - ltk_rect rect = self->rect; - ltk_rect clip_final = ltk_rect_intersect(clip, rect); + ltk_rect lrect = self->lrect; + ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); + if (clip_final.w <= 0 || clip_final.h <= 0) + return; struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; - ltk_surface_fill_rect(self->window->surface, &t->background, self->rect); + ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h}; + ltk_surface_fill_rect(s, &t->background, surf_clip); + ltk_widget *ptr = NULL; for (size_t i = 0; i < menu->num_entries; i++) { /* FIXME: I guess it could be improved *slightly* by making the clip rect smaller when scrollarrows are shown */ /* draw active entry after others so it isn't hidden with compress_borders */ if ((menu->entries[i]->widget.state & (LTK_ACTIVE | LTK_PRESSED | LTK_HOVER)) && i < menu->num_entries - 1) { - ltk_menuentry_draw(&menu->entries[i + 1]->widget, clip_final); - ltk_menuentry_draw(&menu->entries[i]->widget, clip_final); + ptr = &menu->entries[i + 1]->widget; + ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); + ptr = &menu->entries[i]->widget; + ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); i++; } else { - ltk_menuentry_draw(&menu->entries[i]->widget, clip_final); + ptr = &menu->entries[i]->widget; + ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); } } /* FIXME: active, pressed states */ int sz = t->arrow_size + t->arrow_pad * 2; - int ww = self->rect.w; - int wh = self->rect.h; - int wx = self->rect.x; - int wy = self->rect.y; + int ww = self->lrect.w; + int wh = self->lrect.h; + int wx = x, wy = y; int mbw = t->border_width; /* FIXME: handle pathological case where rect is so small that this still draws outside */ - if (rect.w < (int)self->ideal_w) { - ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, sz, wh - mbw * 2}); - ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + ww - sz - mbw, wy + mbw, sz, wh - mbw * 2}); + /* -> this is currently a mess because some parts handle clipping properly, but the scroll arrow drawing doesn't */ + if (lrect.w < (int)self->ideal_w) { + ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, sz, wh - mbw * 2}); + ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){wx + ww - sz - mbw, wy + mbw, sz, wh - mbw * 2}); ltk_point arrow_points[3] = { {wx + t->arrow_pad + mbw, wy + wh / 2}, {wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 - t->arrow_size / 2}, {wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 + t->arrow_size / 2} }; - ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); arrow_points[0] = (ltk_point){wx + ww - t->arrow_pad - mbw, wy + wh / 2}; arrow_points[1] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 - t->arrow_size / 2}; arrow_points[2] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 + t->arrow_size / 2}; - ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); } - if (rect.h < (int)self->ideal_h) { + if (lrect.h < (int)self->ideal_h) { ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz}); ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz}); ltk_point arrow_points[3] = { @@ -423,13 +466,13 @@ ltk_menu_draw(ltk_widget *self, ltk_rect clip) { {wx + ww / 2 - t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size}, {wx + ww / 2 + t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size} }; - ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); arrow_points[0] = (ltk_point){wx + ww / 2, wy + wh - t->arrow_pad - mbw}; arrow_points[1] = (ltk_point){wx + ww / 2 - t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size}; arrow_points[2] = (ltk_point){wx + ww / 2 + t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size}; - ltk_surface_fill_polygon(self->window->surface, &t->scroll_arrow_color, arrow_points, 3); + ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); } - ltk_surface_draw_border(self->window->surface, &t->border, rect, mbw, LTK_BORDER_ALL); + ltk_surface_draw_border_clipped(s, &t->border, (ltk_rect){x, y, lrect.w, lrect.h}, surf_clip, mbw, LTK_BORDER_ALL); self->dirty = 0; } @@ -445,41 +488,65 @@ ltk_menu_resize(ltk_widget *self) { if (menu->y_scroll_offset > max_y) menu->y_scroll_offset = max_y; - ltk_rect rect = self->rect; + ltk_rect lrect = self->lrect; struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme; int ideal_w = self->ideal_w, ideal_h = self->ideal_h; int arrow_size = t->arrow_pad * 2 + t->arrow_size; - int start_x = rect.w < ideal_w ? arrow_size : 0; - int start_y = rect.h < ideal_h ? arrow_size : 0; + int start_x = lrect.w < ideal_w ? arrow_size : 0; + int start_y = lrect.h < ideal_h ? arrow_size : 0; start_x += t->border_width; start_y += t->border_width; int mbw = t->border_width; - int cur_abs_x = -(int)menu->x_scroll_offset + rect.x + start_x + t->pad; - int cur_abs_y = -(int)menu->y_scroll_offset + rect.y + start_y + t->pad; + int cur_abs_x = -(int)menu->x_scroll_offset + start_x + t->pad; + int cur_abs_y = -(int)menu->y_scroll_offset + start_y + t->pad; for (size_t i = 0; i < menu->num_entries; i++) { ltk_menuentry *e = menu->entries[i]; - e->widget.rect.x = cur_abs_x; - e->widget.rect.y = cur_abs_y; + e->widget.lrect.x = cur_abs_x; + e->widget.lrect.y = cur_abs_y; if (menu->is_submenu) { - e->widget.rect.w = ideal_w - 2 * t->pad - 2 * mbw; - e->widget.rect.h = e->widget.ideal_h; + e->widget.lrect.w = ideal_w - 2 * t->pad - 2 * mbw; + e->widget.lrect.h = e->widget.ideal_h; cur_abs_y += e->widget.ideal_h + t->pad; if (et->compress_borders) cur_abs_y -= et->border_width; } else { - e->widget.rect.w = e->widget.ideal_w; - e->widget.rect.h = ideal_h - 2 * t->pad - 2 * mbw; + e->widget.lrect.w = e->widget.ideal_w; + e->widget.lrect.h = ideal_h - 2 * t->pad - 2 * mbw; cur_abs_x += e->widget.ideal_w + t->pad; if (et->compress_borders) cur_abs_x -= et->border_width; } + e->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, e->widget.lrect); } self->dirty = 1; - ltk_window_invalidate_rect(self->window, self->rect); + ltk_window_invalidate_widget_rect(self->window, self); +} + +static void +ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r) { + ltk_menu *menu = (ltk_menu *)self; + struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; + int extra_size = theme->arrow_size + theme->arrow_pad * 2 + theme->border_width; + int delta = 0; + if (self->lrect.w < (int)self->ideal_w && !menu->is_submenu) { + if (r.x + r.w > self->lrect.w - extra_size && r.w <= self->lrect.w - 2 * extra_size) + delta = r.x - (self->lrect.w - extra_size - r.w); + else if (r.x < extra_size || r.w > self->lrect.w - 2 * extra_size) + delta = r.x - extra_size; + if (delta) + ltk_menu_scroll(menu, 0, 0, 0, 1, delta); + } else if (self->lrect.h < (int)self->ideal_h && menu->is_submenu) { + if (r.y + r.h > self->lrect.h - extra_size && r.h <= self->lrect.h - 2 * extra_size) + delta = r.y - (self->lrect.h - extra_size - r.h); + else if (r.y < extra_size || r.h > self->lrect.h - 2 * extra_size) + delta = r.y - extra_size; + if (delta) + ltk_menu_scroll(menu, 0, 1, 0, 0, delta); + } } static void @@ -488,11 +555,11 @@ ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret) { int extra_size = theme->arrow_size * 2 + theme->arrow_pad * 4; *x_ret = 0; *y_ret = 0; - if (menu->widget.rect.w < (int)menu->widget.ideal_w) { - *x_ret = menu->widget.ideal_w - (menu->widget.rect.w - extra_size); + if (menu->widget.lrect.w < (int)menu->widget.ideal_w) { + *x_ret = menu->widget.ideal_w - (menu->widget.lrect.w - extra_size); } - if (menu->widget.rect.h < (int)menu->widget.ideal_h) { - *y_ret = menu->widget.ideal_h - (menu->widget.rect.h - extra_size); + if (menu->widget.lrect.h < (int)menu->widget.ideal_h) { + *y_ret = menu->widget.ideal_h - (menu->widget.lrect.h - extra_size); } } @@ -523,7 +590,7 @@ ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step) { fabs(y_old - menu->y_scroll_offset) > 0.01) { ltk_menu_resize(&menu->widget); menu->widget.dirty = 1; - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); + ltk_window_invalidate_widget_rect(menu->widget.window, &menu->widget); } } @@ -555,21 +622,22 @@ ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) { struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; int arrow_size = t->arrow_size + t->arrow_pad * 2; int mbw = t->border_width; - int start_x = self->rect.x + mbw, end_x = self->rect.x + self->rect.w - mbw; - int start_y = self->rect.y + mbw, end_y = self->rect.y + self->rect.h - mbw; - if (self->rect.w < (int)self->ideal_w) { + int start_x = mbw, end_x = self->lrect.w - mbw; + int start_y = mbw, end_y = self->lrect.h - mbw; + if (self->lrect.w < (int)self->ideal_w) { start_x += arrow_size; end_x -= arrow_size; } - if (self->rect.h < (int)self->ideal_h) { + if (self->lrect.h < (int)self->ideal_h) { start_y += arrow_size; end_y -= arrow_size; } + /* FIXME: use crect for this */ if (!ltk_collide_rect((ltk_rect){start_x, start_y, end_x - start_x, end_y - start_y}, x, y)) return NULL; for (size_t i = 0; i < menu->num_entries; i++) { - if (ltk_collide_rect(menu->entries[i]->widget.rect, x, y)) + if (ltk_collide_rect(menu->entries[i]->widget.crect, x, y)) return &menu->entries[i]->widget; } return NULL; @@ -578,21 +646,21 @@ 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.rect, x, y)) + if (!ltk_collide_rect(menu->widget.lrect, x, y)) return 0; int t = 0, b = 0, l = 0,r = 0; struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; int arrow_size = theme->arrow_size + theme->arrow_pad * 2; - if (menu->widget.rect.w < (int)menu->widget.ideal_w) { - if (x < menu->widget.rect.x + arrow_size) + if (menu->widget.lrect.w < (int)menu->widget.ideal_w) { + if (x < arrow_size) l = 1; - else if (x > menu->widget.rect.x + menu->widget.rect.w - arrow_size) + else if (x > menu->widget.lrect.w - arrow_size) r = 1; } - if (menu->widget.rect.h < (int)menu->widget.ideal_h) { - if (y < menu->widget.rect.y + arrow_size) + if (menu->widget.lrect.h < (int)menu->widget.ideal_h) { + if (y < arrow_size) t = 1; - else if (y > menu->widget.rect.y + menu->widget.rect.h - arrow_size) + else if (y > menu->widget.lrect.h - arrow_size) b = 1; } if (t == menu->scroll_top_hover && @@ -610,43 +678,42 @@ set_scroll_timer(ltk_menu *menu, int x, int y) { return 1; } +/* FIXME: The mouse release handler checks if the mouse collides with the rect of the widget + before calling this, but that doesn't work with menuentries because part of their rect may + be hidden when scrolling in a menu. Maybe widgets also need a "visible rect"? */ static int -ltk_menuentry_mouse_release(ltk_widget *self, ltk_button_event *event) { - (void)event; +ltk_menuentry_release(ltk_widget *self) { ltk_menuentry *e = (ltk_menuentry *)self; int in_submenu = IN_SUBMENU(e); int keep_popup = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus; - /* FIXME: problem when scrolling because actual shown rect may not be entire rect */ - if ((self->state & LTK_PRESSED) && event->button == LTK_BUTTONL && ltk_collide_rect(self->rect, event->x, event->y)) { - if (in_submenu || !keep_popup) { - ltk_window_unregister_all_popups(self->window); - } - ltk_queue_specific_event(self, "menu", LTK_PWEVENTMASK_MENUENTRY_PRESS, "press"); - return 1; + if (in_submenu || !keep_popup) { + ltk_window_unregister_all_popups(self->window); } - return 0; + ltk_queue_specific_event(self, "menu", LTK_PWEVENTMASK_MENUENTRY_PRESS, "press"); + return 1; } static int ltk_menu_mouse_press(ltk_widget *self, ltk_button_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, event->x, event->y); + 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, event->x, event->y); + 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, event->x, event->y); + 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, event->x, event->y); + ltk_window_fake_motion_event(self->window, glob.x, glob.y); break; default: return 0; @@ -662,7 +729,7 @@ ltk_menu_hide(ltk_widget *self) { menu->scroll_bottom_hover = menu->scroll_top_hover = 0; menu->scroll_left_hover = menu->scroll_right_hover = 0; ltk_window_unregister_popup(self->window, self); - ltk_window_invalidate_rect(self->window, self->rect); + ltk_window_invalidate_widget_rect(self->window, self); /* FIXME: this is really ugly/hacky */ if (menu->unpopup_submenus_on_hide && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY && self->parent->parent && self->parent->parent->vtable->type == LTK_WIDGET_MENU) { @@ -677,13 +744,17 @@ popup_active_menu(ltk_menuentry *e) { if (!e->submenu) return; int in_submenu = 0, was_opened_left = 0; - ltk_rect menu_rect = e->widget.rect; - ltk_rect entry_rect = e->widget.rect; + ltk_rect menu_rect = e->widget.lrect; + ltk_point entry_global = ltk_widget_pos_to_global(&e->widget, 0, 0); + ltk_point menu_global; if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { ltk_menu *menu = (ltk_menu *)e->widget.parent; in_submenu = menu->is_submenu; was_opened_left = menu->was_opened_left; - menu_rect = menu->widget.rect; + menu_rect = menu->widget.lrect; + menu_global = ltk_widget_pos_to_global(e->widget.parent, 0, 0); + } else { + menu_global = ltk_widget_pos_to_global(&e->widget, 0, 0); } int win_w = e->widget.window->rect.w; int win_h = e->widget.window->rect.h; @@ -692,10 +763,10 @@ popup_active_menu(ltk_menuentry *e) { int ideal_h = submenu->widget.ideal_h; int x_final = 0, y_final = 0, w_final = ideal_w, h_final = ideal_h; if (in_submenu) { - int space_left = menu_rect.x; - int space_right = win_w - (menu_rect.x + menu_rect.w); - int x_right = menu_rect.x + menu_rect.w; - int x_left = menu_rect.x - ideal_w; + int space_left = menu_global.x; + int space_right = win_w - (menu_global.x + menu_rect.w); + int x_right = menu_global.x + menu_rect.w; + int x_left = menu_global.x - ideal_w; if (submenu_theme.compress_borders) { x_right -= submenu_theme.border_width; x_left += submenu_theme.border_width; @@ -730,7 +801,7 @@ popup_active_menu(ltk_menuentry *e) { } } /* subtract padding and border width so the actual entries are at the right position */ - y_final = entry_rect.y - submenu_theme.pad - submenu_theme.border_width; + y_final = entry_global.y - submenu_theme.pad - submenu_theme.border_width; if (y_final + ideal_h > win_h) y_final = win_h - ideal_h; if (y_final < 0) { @@ -738,10 +809,10 @@ popup_active_menu(ltk_menuentry *e) { h_final = win_h; } } else { - int space_top = menu_rect.y; - int space_bottom = win_h - (menu_rect.y + menu_rect.h); - int y_top = menu_rect.y - ideal_h; - int y_bottom = menu_rect.y + menu_rect.h; + int space_top = menu_global.y; + int space_bottom = win_h - (menu_global.y + menu_rect.h); + int y_top = menu_global.y - ideal_h; + int y_bottom = menu_global.y + menu_rect.h; if (menu_theme.compress_borders) { y_top += menu_theme.border_width; y_bottom -= menu_theme.border_width; @@ -752,10 +823,12 @@ popup_active_menu(ltk_menuentry *e) { y_final = 0; h_final = menu_rect.y; } + submenu->was_opened_above = 1; } else { y_final = y_bottom; if (space_bottom < ideal_h) h_final = space_bottom; + submenu->was_opened_above = 0; } /* FIXME: maybe threshold so there's always at least a part of the menu contents shown (instead of maybe just a few pixels) */ @@ -764,7 +837,7 @@ popup_active_menu(ltk_menuentry *e) { y_final = 0; h_final = win_h; } - x_final = entry_rect.x; + x_final = entry_global.x; if (x_final + ideal_w > win_w) x_final = win_w - ideal_w; if (x_final < 0) { @@ -776,17 +849,18 @@ popup_active_menu(ltk_menuentry *e) { submenu->x_scroll_offset = submenu->y_scroll_offset = 0; submenu->scroll_top_hover = submenu->scroll_bottom_hover = 0; submenu->scroll_left_hover = submenu->scroll_right_hover = 0; - submenu->widget.rect.x = x_final; - submenu->widget.rect.y = y_final; - submenu->widget.rect.w = w_final; - submenu->widget.rect.h = h_final; + submenu->widget.lrect.x = x_final; + submenu->widget.lrect.y = y_final; + submenu->widget.lrect.w = w_final; + submenu->widget.lrect.h = h_final; + submenu->widget.crect = submenu->widget.lrect; submenu->widget.dirty = 1; submenu->widget.hidden = 0; submenu->popup_submenus = 0; submenu->unpopup_submenus_on_hide = 1; ltk_menu_resize(&submenu->widget); ltk_window_register_popup(e->widget.window, (ltk_widget *)submenu); - ltk_window_invalidate_rect(submenu->widget.window, submenu->widget.rect); + ltk_window_invalidate_widget_rect(submenu->widget.window, &submenu->widget); } static void @@ -829,6 +903,7 @@ ltk_menu_create(ltk_window *window, const char *id, int is_submenu) { menu->x_scroll_offset = menu->y_scroll_offset = 0; menu->is_submenu = is_submenu; menu->was_opened_left = 0; + menu->was_opened_above = 0; menu->scroll_timer_id = -1; menu->scroll_top_hover = menu->scroll_bottom_hover = 0; menu->scroll_left_hover = menu->scroll_right_hover = 0; @@ -894,7 +969,7 @@ recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget) { } menu->widget.dirty = 1; if (!menu->widget.hidden) - ltk_window_invalidate_rect(menu->widget.window, menu->widget.rect); + ltk_window_invalidate_widget_rect(menu->widget.window, &menu->widget); } static void @@ -930,20 +1005,31 @@ ltk_menuentry_create(ltk_window *window, const char *id, const char *text) { return e; } +static int +ltk_menuentry_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err) { + ltk_menuentry *e = (ltk_menuentry *)self; + if (widget != &e->submenu->widget) { + err->type = ERR_WIDGET_NOT_IN_CONTAINER; + return 1; + } + widget->parent = NULL; + e->submenu = NULL; + ltk_menuentry_recalc_ideal_size(e); + return 0; +} + static void ltk_menuentry_destroy(ltk_widget *self, int shallow) { ltk_menuentry *e = (ltk_menuentry *)self; - /* FIXME: should be in widget destroy function */ - ltk_free(e->widget.id); ltk_text_line_destroy(e->text_line); ltk_surface_cache_release_key(e->text_surface_key); /* FIXME: function to call when parent is destroyed */ /* also function to call when parent added */ - /* also function to call when child destroyed */ if (e->submenu) { e->submenu->widget.parent = NULL; if (!shallow) { - ltk_menu_destroy(&e->submenu->widget, shallow); + ltk_error err; + ltk_widget_destroy(&e->submenu->widget, shallow, &err); } } ltk_free(e); @@ -989,7 +1075,7 @@ ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err submenu->widget.parent = &e->widget; } if (!e->widget.hidden) - ltk_window_invalidate_rect(e->widget.window, e->widget.rect); + ltk_window_invalidate_widget_rect(e->widget.window, &e->widget); return 0; } @@ -1066,6 +1152,162 @@ ltk_menu_remove_all_entries(ltk_menu *menu) { recalc_ideal_menu_size(&menu->widget, NULL); } +static ltk_widget * +ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect) { + ltk_menu *menu = (ltk_menu *)self; + ltk_widget *minw = NULL; + int min_dist = INT_MAX; + int cx = rect.x + rect.w / 2; + int cy = rect.y + rect.h / 2; + ltk_rect r; + int dist; + for (size_t i = 0; i < menu->num_entries; i++) { + r = menu->entries[i]->widget.lrect; + dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy); + if (dist < min_dist) { + min_dist = dist; + minw = &menu->entries[i]->widget; + } + } + return minw; +} + +/* FIXME: These need to be updated if all menus are allowed to be horizontal or vertical! */ +/* FIXME: this doesn't work properly when parent and child are both on the same side, + but I guess there's no good way to fix that */ +/* FIXME: behavior is a bit weird when e.g. moving down when every active menu entry in hierarchy + is already at bottom of respective menu - the top-level menu will give the first submenu in + the current active hierarchy as child widget again, and nearest_child on that submenu will + (probably) give the bottom widget again, so nothing changes except that all submenus except + for the first and second one disappeare */ +static ltk_widget * +ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget) { + ltk_menu *menu = (ltk_menu *)self; + ltk_widget *left = NULL; + if (!menu->is_submenu) { + left = ltk_menu_prev_child(self, widget); + } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY && + ((ltk_menuentry *)widget)->submenu && + !((ltk_menuentry *)widget)->submenu->widget.hidden && + ((ltk_menuentry *)widget)->submenu->was_opened_left) { + left = &((ltk_menuentry *)widget)->submenu->widget; + } else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = (ltk_menuentry *)self->parent; + if (!menu->was_opened_left && IN_SUBMENU(e)) { + left = self->parent; + } else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { + left = ltk_menu_prev_child(e->widget.parent, &e->widget); + } + } + return left; +} + +static ltk_widget * +ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget) { + ltk_menu *menu = (ltk_menu *)self; + ltk_widget *right = NULL; + if (!menu->is_submenu) { + right = ltk_menu_next_child(self, widget); + } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY && + ((ltk_menuentry *)widget)->submenu && + !((ltk_menuentry *)widget)->submenu->widget.hidden && + !((ltk_menuentry *)widget)->submenu->was_opened_left) { + right = &((ltk_menuentry *)widget)->submenu->widget; + } else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = (ltk_menuentry *)self->parent; + if (menu->was_opened_left && IN_SUBMENU(e)) { + right = self->parent; + } else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { + right = ltk_menu_next_child(e->widget.parent, &e->widget); + } + } + return right; +} + +static ltk_widget * +ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget) { + ltk_menu *menu = (ltk_menu *)self; + ltk_widget *above = NULL; + if (menu->is_submenu) { + above = ltk_menu_prev_child(self, widget); + if (!above && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = (ltk_menuentry *)self->parent; + if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { + ltk_menu *pmenu = (ltk_menu *)e->widget.parent; + if (!menu->was_opened_above && !pmenu->is_submenu) { + above = self->parent; + } else if (pmenu->is_submenu) { + above = ltk_menu_prev_child(e->widget.parent, &e->widget); + } + } + } + } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = (ltk_menuentry *)widget; + if (e->submenu && !e->submenu->widget.hidden && e->submenu->was_opened_above) { + above = &e->submenu->widget; + } + } + return above; +} + +static ltk_widget * +ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget) { + ltk_menu *menu = (ltk_menu *)self; + ltk_widget *below = NULL; + if (menu->is_submenu) { + below = ltk_menu_next_child(self, widget); + if (!below && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = (ltk_menuentry *)self->parent; + if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { + ltk_menu *pmenu = (ltk_menu *)e->widget.parent; + if (menu->was_opened_above && !pmenu->is_submenu) { + below = self->parent; + } else if (pmenu->is_submenu) { + below = ltk_menu_next_child(e->widget.parent, &e->widget); + } + } + } + } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = (ltk_menuentry *)widget; + if (e->submenu && !e->submenu->widget.hidden && !e->submenu->was_opened_above) { + below = &e->submenu->widget; + } + } + return below; +} + +static ltk_widget * +ltk_menu_prev_child(ltk_widget *self, ltk_widget *child) { + ltk_menu *menu = (ltk_menu *)self; + for (size_t i = menu->num_entries; i-- > 0;) { + if (&menu->entries[i]->widget == child) + return i > 0 ? &menu->entries[i-1]->widget : NULL; + } + return NULL; +} + +static ltk_widget * +ltk_menu_next_child(ltk_widget *self, ltk_widget *child) { + ltk_menu *menu = (ltk_menu *)self; + for (size_t i = 0; i < menu->num_entries; i++) { + if (&menu->entries[i]->widget == child) + return i < menu->num_entries - 1 ? &menu->entries[i+1]->widget : NULL; + } + return NULL; +} + +static ltk_widget * +ltk_menu_first_child(ltk_widget *self) { + ltk_menu *menu = (ltk_menu *)self; + return menu->num_entries > 0 ? &menu->entries[0]->widget : NULL; +} + +static ltk_widget * +ltk_menu_last_child(ltk_widget *self) { + ltk_menu *menu = (ltk_menu *)self; + return menu->num_entries > 0 ? &menu->entries[menu->num_entries-1]->widget : NULL; +} + /* FIXME: unregister from window popups? */ static void ltk_menuentry_detach_submenu(ltk_menuentry *e) { @@ -1084,17 +1326,19 @@ ltk_menu_destroy(ltk_widget *self, int shallow) { } if (menu->scroll_timer_id >= 0) ltk_unregister_timer(menu->scroll_timer_id); + ltk_window_unregister_popup(self->window, self); if (!shallow) { + ltk_error err; for (size_t i = 0; i < menu->num_entries; i++) { - ltk_menuentry_destroy(&menu->entries[i]->widget, shallow); + /* for efficiency - to avoid ltk_widget_destroy calling + ltk_menu_remove_child for each of the entries */ + menu->entries[i]->widget.parent = NULL; + ltk_widget_destroy(&menu->entries[i]->widget, shallow, &err); } + ltk_free(menu->entries); + } else { + ltk_menu_remove_all_entries(menu); } - ltk_menu_remove_all_entries(menu); - ltk_window_unregister_popup(self->window, self); - /* FIXME: what to do on error here? */ - /* FIXME: maybe unregister popup in ltk_remove_widget? */ - ltk_remove_widget(self->id); - ltk_free(self->id); ltk_free(menu); } diff --git a/src/menu.h b/src/menu.h @@ -33,6 +33,7 @@ typedef struct { int scroll_timer_id; char is_submenu; char was_opened_left; + char was_opened_above; /* FIXME: better names */ char popup_submenus; char unpopup_submenus_on_hide; diff --git a/src/scrollbar.c b/src/scrollbar.c @@ -33,7 +33,7 @@ #define MAX_SCROLLBAR_WIDTH 100 /* completely arbitrary */ -static void ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip); +static void ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip); 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); @@ -102,10 +102,11 @@ ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size) { scrollbar->virtual_size = virtual_size; } +/* get rekt */ static ltk_rect -get_handle_rect(ltk_scrollbar *sc) { +handle_get_rect(ltk_scrollbar *sc) { ltk_rect r; - ltk_rect sc_rect = sc->widget.rect; + ltk_rect sc_rect = sc->widget.lrect; if (sc->orient == LTK_HORIZONTAL) { r.y = 0; r.h = sc_rect.h; @@ -126,12 +127,16 @@ get_handle_rect(ltk_scrollbar *sc) { return r; } +/* FIXME: implement clipping directly without extra surface */ static void -ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) { +ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { /* FIXME: dirty attribute */ ltk_scrollbar *scrollbar = (ltk_scrollbar *)self; ltk_color *bg = NULL, *fg = NULL; - ltk_rect rect = scrollbar->widget.rect; + ltk_rect lrect = self->lrect; + ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); + if (clip_final.w <= 0 || clip_final.h <= 0) + return; /* FIXME: proper theme for hover */ if (self->state & LTK_DISABLED) { bg = &theme.bg_disabled; @@ -147,14 +152,13 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) { fg = &theme.fg_normal; } ltk_surface *s; - ltk_surface_cache_request_surface_size(scrollbar->key, self->rect.w, self->rect.h); + ltk_surface_cache_request_surface_size(scrollbar->key, lrect.w, lrect.h); ltk_surface_cache_get_surface(scrollbar->key, &s); - ltk_surface_fill_rect(s, bg, (ltk_rect){0, 0, rect.w, rect.h}); + ltk_surface_fill_rect(s, bg, (ltk_rect){0, 0, lrect.w, lrect.h}); /* FIXME: maybe too much calculation in draw function - move to resizing function? */ - ltk_surface_fill_rect(s, fg, get_handle_rect(scrollbar)); - ltk_rect clip_final = ltk_rect_intersect(clip, rect); - ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y); + ltk_surface_fill_rect(s, fg, handle_get_rect(scrollbar)); + ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y); } static int @@ -164,17 +168,17 @@ ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) { if (event->button != LTK_BUTTONL) return 0; int ex = event->x, ey = event->y; - ltk_rect handle_rect = get_handle_rect(sc); + ltk_rect handle_rect = handle_get_rect(sc); if (sc->orient == LTK_HORIZONTAL) { if (ex < handle_rect.x || ex > handle_rect.x + handle_rect.w) { - sc->cur_pos = (sc->virtual_size / (double)sc->widget.rect.w) * (ex - handle_rect.w / 2 - sc->widget.rect.x); + sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.w) * (ex - handle_rect.w / 2 - sc->widget.lrect.x); } - max_pos = sc->virtual_size > sc->widget.rect.w ? sc->virtual_size - sc->widget.rect.w : 0; + max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0; } else { if (ey < handle_rect.y || ey > handle_rect.y + handle_rect.h) { - sc->cur_pos = (sc->virtual_size / (double)sc->widget.rect.h) * (ey - handle_rect.h / 2 - sc->widget.rect.y); + sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.h) * (ey - handle_rect.h / 2 - sc->widget.lrect.y); } - max_pos = sc->virtual_size > sc->widget.rect.h ? sc->virtual_size - sc->widget.rect.h : 0; + max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0; } if (sc->cur_pos < 0) sc->cur_pos = 0; @@ -194,11 +198,11 @@ ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) { int max_pos; double scale; if (sc->orient == LTK_HORIZONTAL) { - max_pos = sc->virtual_size > sc->widget.rect.w ? sc->virtual_size - sc->widget.rect.w : 0; - scale = sc->virtual_size / (double)sc->widget.rect.w; + max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0; + scale = sc->virtual_size / (double)sc->widget.lrect.w; } else { - max_pos = sc->virtual_size > sc->widget.rect.h ? sc->virtual_size - sc->widget.rect.h : 0; - scale = sc->virtual_size / (double)sc->widget.rect.h; + max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0; + scale = sc->virtual_size / (double)sc->widget.lrect.h; } if (scaled) sc->cur_pos += scale * delta; @@ -238,9 +242,9 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback sc->cur_pos = 0; sc->orient = orient; if (orient == LTK_HORIZONTAL) - sc->widget.rect.h = theme.size; + sc->widget.ideal_h = theme.size; else - sc->widget.rect.w = theme.size; + sc->widget.ideal_w = theme.size; sc->callback = callback; sc->callback_data = data; sc->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, sc->widget.ideal_w, sc->widget.ideal_h); diff --git a/src/theme.h b/src/theme.h @@ -44,6 +44,4 @@ int ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *pro int ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len); void ltk_theme_uninitialize(ltk_window *window, ltk_theme_parseinfo *parseinfo, size_t len); -#define LENGTH(X) (sizeof(X) / sizeof(X[0])) - #endif /* _LTK_THEME_H_ */ diff --git a/src/util.c b/src/util.c @@ -152,3 +152,13 @@ ltk_fatal_errno(const char *format, ...) { va_end(args); ltk_fatal("system error: %s\n", errstr); } + +int +str_array_equal(char *terminated, char *array, size_t len) { + if (!strncmp(terminated, array, len)) { + /* this is kind of inefficient, but there's no way to know + otherwise if strncmp just stopped comparing after a '\0' */ + return strlen(terminated) == len; + } + return 0; +} diff --git a/src/util.h b/src/util.h @@ -18,6 +18,7 @@ #define _LTK_UTIL_H_ #include <stdarg.h> +#include <stddef.h> long long ltk_strtonum( const char *numstr, long long minval, @@ -39,4 +40,14 @@ void ltk_warn_errno(const char *format, ...); void ltk_fatal(const char *format, ...); void ltk_warn(const char *format, ...); +/* + * Compare the nul-terminated string 'terminated' with the char + * array 'array' with length 'len'. + * Returns non-zero if they are equal, 0 otherwise. + */ +/* Note: this doesn't work if array contains '\0'. */ +int str_array_equal(char *terminated, char *array, size_t len); + +#define LENGTH(X) (sizeof(X) / sizeof(X[0])) + #endif /* _LTK_UTIL_H_ */ diff --git a/src/widget.c b/src/widget.c @@ -1,5 +1,3 @@ -/* FIXME: store coordinates relative to parent widget */ -/* FIXME: Destroy function for widget to destroy pixmap! */ /* * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org> * @@ -28,6 +26,13 @@ #include "util.h" #include "khash.h" #include "surface_cache.h" +#include "widget_config.h" +#include "config.h" + +struct ltk_key_callback { + char *func_name; + int (*callback)(ltk_window *, ltk_key_event *, int handled); +}; static void ltk_destroy_widget_hash(void); @@ -37,6 +42,11 @@ static khash_t(widget) *widget_hash = NULL; /* FIXME: any better way to do this? */ static int hash_locked = 0; +/* needed for passing keyboard events down the hierarchy */ +static ltk_widget **widget_stack = NULL; +static size_t widget_stack_alloc = 0; +static size_t widget_stack_len = 0; + static void ltk_destroy_widget_hash(void) { hash_locked = 1; @@ -174,6 +184,7 @@ ltk_widgets_init() { void ltk_widgets_cleanup() { + free(widget_stack); if (widget_hash) ltk_destroy_widget_hash(); } @@ -193,10 +204,17 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window, widget->state = LTK_NORMAL; widget->row = 0; - widget->rect.x = 0; - widget->rect.y = 0; - widget->rect.w = w; - widget->rect.h = h; + widget->lrect.x = 0; + widget->lrect.y = 0; + widget->lrect.w = w; + widget->lrect.h = h; + widget->crect.x = 0; + widget->crect.y = 0; + widget->crect.w = w; + widget->crect.h = h; + widget->popup = 0; + + widget->ideal_w = widget->ideal_h = 0; widget->event_masks = NULL; widget->masks_num = widget->masks_alloc = 0; @@ -241,6 +259,7 @@ ltk_widget_hide(ltk_widget *widget) { while (active) { if (active == widget) { set_next = 1; + /* FIXME: use config values for all_activatable */ } else if (set_next && (active->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { ltk_window_set_active_widget(active->window, active); break; @@ -253,6 +272,7 @@ ltk_widget_hide(ltk_widget *widget) { /* FIXME: Maybe pass the new width as arg here? That would make a bit more sense */ +/* FIXME: maybe give global and local position in event */ void ltk_widget_resize(ltk_widget *widget) { int lock_client = -1; @@ -261,16 +281,16 @@ ltk_widget_resize(ltk_widget *widget) { ltk_queue_sock_write_fmt( widget->event_masks[i].client, "eventl %s widget configure %d %d %d %d\n", - widget->id, widget->rect.x, widget->rect.y, - widget->rect.w, widget->rect.h + widget->id, widget->lrect.x, widget->lrect.y, + widget->lrect.w, widget->lrect.h ); lock_client = widget->event_masks[i].client; } else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) { ltk_queue_sock_write_fmt( widget->event_masks[i].client, "event %s widget configure %d %d %d %d\n", - widget->id, widget->rect.x, widget->rect.y, - widget->rect.w, widget->rect.h + widget->id, widget->lrect.x, widget->lrect.y, + widget->lrect.w, widget->lrect.h ); } } @@ -285,6 +305,8 @@ ltk_widget_resize(ltk_widget *widget) { void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) { + if (old_state == widget->state) + return; int lock_client = -1; /* FIXME: give old and new state in event */ for (size_t i = 0; i < widget->masks_num; i++) { @@ -309,19 +331,31 @@ ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) { widget->vtable->change_state(widget, old_state); if (widget->vtable->flags & LTK_NEEDS_REDRAW) { widget->dirty = 1; - ltk_window_invalidate_rect(widget->window, widget->rect); + ltk_window_invalidate_widget_rect(widget->window, widget); } } +/* x and y are global! */ static ltk_widget * -get_widget_under_pointer(ltk_widget *widget, int x, int y) { +get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret) { + ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0); ltk_widget *next = NULL; + *local_x_ret = x - glob.x; + *local_y_ret = y - glob.y; while (widget && widget->vtable->get_child_at_pos) { - next = widget->vtable->get_child_at_pos(widget, x, y); - if (!next) + next = widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret); + if (!next) { break; - else + } else { widget = next; + if (next->popup) { + *local_x_ret = x - next->lrect.x; + *local_y_ret = y - next->lrect.y; + } else { + *local_x_ret -= next->lrect.x; + *local_y_ret -= next->lrect.y; + } + } } return widget; } @@ -329,7 +363,7 @@ get_widget_under_pointer(ltk_widget *widget, int x, int y) { 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)) + if (ltk_collide_rect(window->popups[i]->crect, x, y)) return window->popups[i]; } return NULL; @@ -343,6 +377,7 @@ is_parent(ltk_widget *parent, ltk_widget *child) { return child != NULL; } +/* FIXME: fix global and local coordinates! */ static int queue_mouse_event(ltk_widget *widget, char *type, uint32_t mask, int x, int y) { int lock_client = -1; @@ -351,16 +386,16 @@ 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, "eventl %s widget %s %d %d %d %d\n", - widget->id, type, x, y, - x - widget->rect.x, y - widget->rect.y + widget->id, type, x, y, x, y + /* 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\n", - widget->id, type, x, y, - x - widget->rect.x, y - widget->rect.y + widget->id, type, x, y, x, y + /* x - widget->rect.x, y - widget->rect.y */ ); } } @@ -371,6 +406,515 @@ queue_mouse_event(ltk_widget *widget, char *type, uint32_t mask, int x, int y) { return 0; } +static void +ensure_active_widget_shown(ltk_window *window) { + ltk_widget *widget = window->active_widget; + if (!widget) + return; + ltk_rect r = widget->lrect; + while (widget->parent) { + if (widget->parent->vtable->ensure_rect_shown) + widget->parent->vtable->ensure_rect_shown(widget->parent, r); + widget = widget->parent; + r.x += widget->lrect.x; + r.y += widget->lrect.y; + /* FIXME: this currently just aborts if a widget is positioned + absolutely because I'm not sure what the best action would + be in that case */ + if (widget->popup) + break; + } + ltk_window_invalidate_widget_rect(window, widget); +} + +/* FIXME: come up with a more elegant way to handle this? */ +/* FIXME: Handle hidden state here instead of in widgets */ +/* FIXME: handle disabled state */ +static int +prev_child(ltk_window *window) { + if (!window->root_widget) + return 0; + ltk_config *config = ltk_config_get(); + ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; + ltk_widget *new, *cur = window->active_widget; + int changed = 0; + ltk_widget *prevcur = cur; + while (1) { + if (cur) { + while (cur->parent) { + new = NULL; + if (cur->parent->vtable->prev_child) + new = cur->parent->vtable->prev_child(cur->parent, cur); + if (new) { + cur = new; + ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL; + while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) { + cur = new; + if (cur->vtable->flags & act_flags) + last_activatable = cur; + } + if (last_activatable) { + cur = last_activatable; + changed = 1; + break; + } + } else { + cur = cur->parent; + if (cur->vtable->flags & act_flags) { + changed = 1; + break; + } + } + } + } + if (!changed) { + cur = window->root_widget; + ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL; + while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) { + cur = new; + if (cur->vtable->flags & act_flags) + last_activatable = cur; + } + if (last_activatable) + cur = last_activatable; + } + if (prevcur == cur || (cur && (cur->vtable->flags & act_flags))) + break; + prevcur = cur; + } + /* FIXME: What exactly should be done if no activatable widget exists? */ + if (cur != window->active_widget) { + ltk_window_set_active_widget(window, cur); + ensure_active_widget_shown(window); + return 1; + } + return 0; +} + +static int +next_child(ltk_window *window) { + if (!window->root_widget) + return 0; + ltk_config *config = ltk_config_get(); + ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; + ltk_widget *new, *cur = window->active_widget; + int changed = 0; + ltk_widget *prevcur = cur; + while (1) { + if (cur) { + while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) { + cur = new; + if (cur->vtable->flags & act_flags) { + changed = 1; + break; + } + } + if (!changed) { + while (cur->parent) { + new = NULL; + if (cur->parent->vtable->next_child) + new = cur->parent->vtable->next_child(cur->parent, cur); + if (new) { + cur = new; + if (cur->vtable->flags & act_flags) { + changed = 1; + break; + } + while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) { + cur = new; + if (cur->vtable->flags & act_flags) { + changed = 1; + break; + } + } + if (changed) + break; + } else { + cur = cur->parent; + } + } + } + } + if (!changed) { + cur = window->root_widget; + if (!(cur->vtable->flags & act_flags)) { + while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) { + cur = new; + if (cur->vtable->flags & act_flags) + break; + } + } + if (!(cur->vtable->flags & act_flags)) + cur = window->root_widget; + } + if (prevcur == cur || (cur && (cur->vtable->flags & act_flags))) + break; + prevcur = cur; + } + if (cur != window->active_widget) { + ltk_window_set_active_widget(window, cur); + ensure_active_widget_shown(window); + return 1; + } + return 0; +} + +/* FIXME: moving up/down/left/right needs to be rethought + it generally is a bit weird, and in particular, nearest_child always searches for the child + that has the smallest distance to the given rect, so it may not be the child that the user + expects when going down (e.g. a vertical box with one widget closer vertically but on the + other side horizontally, thus possibly leading to a different widget that is farther away + vertically to be chosen instead) - what would be logical here? */ +static ltk_widget * +nearest_child(ltk_widget *widget, ltk_rect r) { + ltk_point local = ltk_global_to_widget_pos(widget, r.x, r.y); + return widget->vtable->nearest_child(widget, (ltk_rect){local.x, local.y, r.w, r.h}); +} + +/* FIXME: maybe wrap around in these two functions? */ +static int +left_top_child(ltk_window *window, int left) { + if (!window->root_widget) + return 0; + ltk_config *config = ltk_config_get(); + ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; + ltk_widget *new, *cur = window->active_widget; + ltk_rect old_rect = {0, 0, 0, 0}; + if (cur) { + ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y}; + old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h}; + } + if (cur) { + while (cur->parent) { + new = NULL; + if (left) { + if (cur->parent->vtable->nearest_child_left) + new = cur->parent->vtable->nearest_child_left(cur->parent, cur); + } else { + if (cur->parent->vtable->nearest_child_above) + new = cur->parent->vtable->nearest_child_above(cur->parent, cur); + } + if (new) { + cur = new; + ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL; + while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) { + cur = new; + if (cur->vtable->flags & act_flags) + last_activatable = cur; + } + if (last_activatable) { + cur = last_activatable; + break; + } + } else { + cur = cur->parent; + if (cur->vtable->flags & act_flags) { + break; + } + } + } + } else { + cur = window->root_widget; + ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL; + ltk_rect r = {cur->lrect.w, cur->lrect.h, 0, 0}; + while (cur->vtable->nearest_child && (new = nearest_child(cur, r))) { + cur = new; + if (cur->vtable->flags & act_flags) + last_activatable = cur; + } + if (last_activatable) + cur = last_activatable; + } + /* FIXME: What exactly should be done if no activatable widget exists? */ + if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) { + ltk_window_set_active_widget(window, cur); + ensure_active_widget_shown(window); + return 1; + } + return 0; +} + +static int +right_bottom_child(ltk_window *window, int right) { + if (!window->root_widget) + return 0; + ltk_config *config = ltk_config_get(); + ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; + ltk_widget *new, *cur = window->active_widget; + int changed = 0; + ltk_rect old_rect = {0, 0, 0, 0}; + ltk_rect corner = {0, 0, 0, 0}; + if (cur) { + ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y}; + corner = (ltk_rect){glob.x, glob.y, 0, 0}; + old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h}; + while (cur->vtable->nearest_child && (new = nearest_child(cur, corner))) { + cur = new; + if (cur->vtable->flags & act_flags) { + changed = 1; + break; + } + } + if (!changed) { + while (cur->parent) { + new = NULL; + if (right) { + if (cur->parent->vtable->nearest_child_right) + new = cur->parent->vtable->nearest_child_right(cur->parent, cur); + } else { + if (cur->parent->vtable->nearest_child_below) + new = cur->parent->vtable->nearest_child_below(cur->parent, cur); + } + if (new) { + cur = new; + if (cur->vtable->flags & act_flags) { + changed = 1; + break; + } + while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) { + cur = new; + if (cur->vtable->flags & act_flags) { + changed = 1; + break; + } + } + if (changed) + break; + } else { + cur = cur->parent; + } + } + } + } else { + cur = window->root_widget; + if (!(cur->vtable->flags & act_flags)) { + while (cur->vtable->nearest_child && (new = nearest_child(cur, (ltk_rect){0, 0, 0, 0}))) { + cur = new; + if (cur->vtable->flags & act_flags) + break; + } + } + if (!(cur->vtable->flags & act_flags)) + cur = window->root_widget; + } + if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) { + ltk_window_set_active_widget(window, cur); + ensure_active_widget_shown(window); + return 1; + } + return 0; +} + +/* FIXME: maybe just set this when active widget changes */ +/* -> but would also need to change it when widgets are created/destroyed or parents change */ +static void +gen_widget_stack(ltk_widget *bottom) { + widget_stack_len = 0; + while (bottom) { + if (widget_stack_len + 1 > widget_stack_alloc) { + widget_stack_alloc = ideal_array_size(widget_stack_alloc, widget_stack_len + 1); + widget_stack = ltk_reallocarray(widget_stack, widget_stack_alloc, sizeof(ltk_widget *)); + } + widget_stack[widget_stack_len++] = bottom; + bottom = bottom->parent; + } +} + +/* FIXME: The focus behavior needs to be rethought. It's currently hard-coded in the vtable for each + widget type, but what if the program using ltk wants to catch keyboard events even if the widget + doesn't do that by default? */ +static int +cb_focus_active(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) { + /* FIXME: maybe also set widgets above in hierarchy? */ + ltk_widget_state old_state = window->active_widget->state; + window->active_widget->state |= LTK_FOCUSED; + ltk_widget_change_state(window->active_widget, old_state); + return 1; + } + return 0; +} + +static int +cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) { + ltk_widget_state old_state = window->active_widget->state; + window->active_widget->state &= ~LTK_FOCUSED; + ltk_widget_change_state(window->active_widget, old_state); + return 1; + } + return 0; +} + +static int +cb_move_prev(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + return prev_child(window); +} + +static int +cb_move_next(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + return next_child(window); +} + +static int +cb_move_left(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + return left_top_child(window, 1); +} + +static int +cb_move_right(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + return right_bottom_child(window, 1); +} + +static int +cb_move_up(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + return left_top_child(window, 0); +} + +static int +cb_move_down(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + return right_bottom_child(window, 0); +} + +static int +cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { + /* FIXME: only set pressed if needs keyboard? */ + ltk_window_set_pressed_widget(window, window->active_widget, 0); + return 1; + } + return 0; +} + +static int +cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + if (window->pressed_widget) { + ltk_window_set_pressed_widget(window, NULL, 1); + return 1; + } + return 0; +} + +static int +cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled) { + (void)event; + (void)handled; + if (window->popups_num > 0) { + ltk_window_unregister_all_popups(window); + return 1; + } + return 0; +} + +static ltk_key_callback key_callbacks[] = { + {"move-next", &cb_move_next}, + {"move-prev", &cb_move_prev}, + {"move-up", &cb_move_up}, + {"move-down", &cb_move_down}, + {"move-left", &cb_move_left}, + {"move-right", &cb_move_right}, + {"focus-active", &cb_focus_active}, + {"unfocus-active", &cb_unfocus_active}, + {"set-pressed", &cb_set_pressed}, + {"unset-pressed", &cb_unset_pressed}, + {"remove-popups", &cb_remove_popups}, +}; + +/* FIXME: binary search (copy from ledit) */ +ltk_key_callback * +ltk_get_key_func(char *name, size_t len) { + for (size_t i = 0; i < LENGTH(key_callbacks); i++) { + if (str_array_equal(key_callbacks[i].func_name, name, len)) + return &key_callbacks[i]; + } + return NULL; +} + +/* FIXME: should keyrelease events be ignored if the corresponding keypress event + was consumed for movement? */ +/* FIXME: check if there's any weirdness when combining return and mouse press */ +/* FIXME: maybe it doesn't really make sense to make e.g. text entry pressed when enter is pressed? */ +/* FIXME: implement key binding flag to run before widget handler is called */ +void +ltk_window_key_press_event(ltk_window *window, ltk_key_event *event) { + /* FIXME: how to handle config being NULL? */ + ltk_config *config = ltk_config_get(); + int handled = 0; + if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { + gen_widget_stack(window->active_widget); + for (size_t i = widget_stack_len; i-- > 0 && !handled;) { + /* FIXME: send event to socket! */ + if (widget_stack[i]->vtable->key_press && widget_stack[i]->vtable->key_press(widget_stack[i], event)) { + handled = 1; + break; + } + } + } + ltk_keypress_binding *b = NULL; + for (size_t i = 0; i < config->keys.press_len; i++) { + b = &config->keys.press_bindings[i]; + if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) { + continue; + } else if (b->text) { + if (event->mapped && !strcmp(b->text, event->mapped)) + handled |= b->callback->callback(window, event, handled); + } else if (b->rawtext) { + if (event->text && !strcmp(b->text, event->text)) + handled |= b->callback->callback(window, event, handled); + } else if (b->sym != LTK_KEY_NONE) { + if (event->sym == b->sym) + handled |= b->callback->callback(window, event, handled); + } + } + +} + +/* FIXME: need to actually check if any of parent widgets are focused and still pass to them even if bottom widget not focused? */ +void +ltk_window_key_release_event(ltk_window *window, ltk_key_event *event) { + /* FIXME: emit event */ + ltk_config *config = ltk_config_get(); + int handled = 0; + if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { + gen_widget_stack(window->active_widget); + for (size_t i = widget_stack_len; i-- > 0 && !handled;) { + if (widget_stack[i]->vtable->key_release && widget_stack[i]->vtable->key_release(widget_stack[i], event)) { + handled = 1; + break; + } + } + } + ltk_keyrelease_binding *b = NULL; + for (size_t i = 0; i < config->keys.release_len; i++) { + b = &config->keys.release_bindings[i]; + if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) { + continue; + } else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) { + handled |= b->callback->callback(window, event, handled); + } + } +} + /* FIXME: This is still weird. */ void ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { @@ -384,9 +928,11 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { ltk_window_unregister_all_popups(window); return; } - ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y); + 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: need to add more flags for more fine-grained control -> also, should the widget still get mouse_press even if state doesn't change? */ + /* FIXME: doesn't work with e.g. disabled menu entries */ if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { ltk_window_unregister_all_popups(window); } @@ -401,6 +947,9 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { int first = 1; 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) { /* 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 */ @@ -409,8 +958,9 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) { 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)) { - ltk_window_set_pressed_widget(window, cur_widget); + ltk_window_set_pressed_widget(window, cur_widget, 0); first = 0; } } @@ -430,9 +980,10 @@ ltk_window_fake_motion_event(ltk_window *window, int x, int y) { 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; if (!widget) { widget = get_hover_popup(window, event->x, event->y); - widget = get_widget_under_pointer(widget, 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)) { @@ -441,19 +992,30 @@ ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) { widget->vtable->mouse_release(widget, event); } if (event->button == LTK_BUTTONL) { - ltk_window_set_pressed_widget(window, NULL); + int release = 0; + if (window->pressed_widget) { + ltk_rect prect = window->pressed_widget->lrect; + ltk_point pglob = ltk_widget_pos_to_global(window->pressed_widget, 0, 0); + if (ltk_collide_rect((ltk_rect){pglob.x, pglob.y, prect.w, prect.h}, orig_x, orig_y)) + release = 1; + } + ltk_window_set_pressed_widget(window, NULL, release); /* send motion notify to widget under pointer */ - /* FIXME: only when not collide with rect */ - ltk_window_fake_motion_event(window, event->x, event->y); + /* FIXME: only when not collide with rect? */ + ltk_window_fake_motion_event(window, orig_x, orig_y); } } void ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) { ltk_widget *widget = get_hover_popup(window, event->x, event->y); + int orig_x = event->x, orig_y = event->y; if (!widget) { widget = window->pressed_widget; if (widget) { + ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y); + event->x = local.x; + event->y = local.y; if (widget->vtable->motion_notify) widget->vtable->motion_notify(widget, event); return; @@ -462,14 +1024,18 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) { } if (!widget) return; - if (!ltk_collide_rect(widget->rect, event->x, event->y)) { + ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y); + if (!ltk_collide_rect((ltk_rect){0, 0, widget->lrect.w, widget->lrect.h}, local.x, local.y)) { ltk_window_set_hover_widget(widget->window, NULL, event); return; } - ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y); + ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y); int first = 1; 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_mouse_event(cur_widget, "mousemotion", LTK_PEVENTMASK_MOUSEMOTION, event->x, event->y)) handled = 1; @@ -478,7 +1044,10 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) { /* set first non-disabled widget to hover widget */ /* FIXME: should enter/leave event be sent to parent when moving from/to widget nested in parent? */ + /* FIXME: use config values for all_activatable */ if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { + event->x = orig_x; + event->y = orig_y; ltk_window_set_hover_widget(window, cur_widget, event); first = 0; } @@ -488,8 +1057,11 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) { else break; } - if (first) + if (first) { + event->x = orig_x; + event->y = orig_y; ltk_window_set_hover_widget(window, NULL, event); + } } int @@ -551,6 +1123,11 @@ ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err) { widget, widget->parent, err ); } + ltk_remove_widget(widget->id); + ltk_free(widget->id); + widget->id = NULL; + ltk_free(widget->event_masks); + widget->event_masks = NULL; widget->vtable->destroy(widget, shallow); return invalid; diff --git a/src/widget.h b/src/widget.h @@ -31,7 +31,7 @@ typedef enum { LTK_ACTIVATABLE_NORMAL = 1, LTK_ACTIVATABLE_SPECIAL = 2, LTK_ACTIVATABLE_ALWAYS = 1|2, - LTK_GRABS_INPUT = 4, + LTK_NEEDS_KEYBOARD = 4, LTK_NEEDS_REDRAW = 8, LTK_HOVER_IS_ACTIVE = 16, } ltk_widget_flags; @@ -54,7 +54,8 @@ typedef enum { LTK_PRESSED = 2, LTK_ACTIVE = 4, LTK_HOVERACTIVE = 1 | 4, - LTK_DISABLED = 8, + LTK_FOCUSED = 8, + LTK_DISABLED = 16, } ltk_widget_state; #include "surface_cache.h" @@ -78,7 +79,19 @@ struct ltk_widget { struct ltk_widget_vtable *vtable; - ltk_rect rect; + /* FIXME: crect and lrect are a bit weird still */ + /* FIXME: especially the relative positioning is really weird for + popups because they're positioned globally but still have a + parent-child relationship - weird things can probably happen */ + /* both rects relative to parent (except for popups) */ + /* collision rect is only part that is actually shown and used for + collision with mouse (but may still not be drawn if hidden by + something else) - e.g. in a box with scrolling, a widget that + is half cut off by a side of the box will have the logical rect + going past the side of the box, but the collision rect will only + be the part inside the box */ + ltk_rect crect; /* collision rect */ + ltk_rect lrect; /* logical rect */ unsigned int ideal_w; unsigned int ideal_h; @@ -93,6 +106,9 @@ struct ltk_widget { unsigned short column; unsigned short row_span; unsigned short column_span; + /* needed to properly handle handle local coordinates since + popups are positioned globally instead of locally */ + char popup; char dirty; char hidden; }; @@ -100,32 +116,42 @@ struct ltk_widget { /* FIXME: just give the structs for the actual event type here instead of the generic ltk_event */ struct ltk_widget_vtable { - void (*key_press)(struct ltk_widget *, ltk_event *); - void (*key_release)(struct ltk_widget *, ltk_event *); + int (*key_press)(struct ltk_widget *, ltk_key_event *); + int (*key_release)(struct ltk_widget *, ltk_key_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 *); + int (*press)(struct ltk_widget *); + int (*release)(struct ltk_widget *); void (*resize)(struct ltk_widget *); void (*hide)(struct ltk_widget *); - void (*draw)(struct ltk_widget *, ltk_rect); + /* draw_surface: surface to draw it on + x, y: position of logical rectangle on surface + clip: clipping rectangle, relative to logical rectangle */ + void (*draw)(struct ltk_widget *self, ltk_surface *draw_surface, int x, int y, ltk_rect clip); void (*change_state)(struct ltk_widget *, ltk_widget_state); void (*destroy)(struct ltk_widget *, int); + /* rect is in self's coordinate system */ struct ltk_widget *(*nearest_child)(struct ltk_widget *self, ltk_rect rect); - struct ltk_widget *(*nearest_child_left)(struct ltk_widget *self, ltk_rect rect); - struct ltk_widget *(*nearest_child_right)(struct ltk_widget *self, ltk_rect rect); - struct ltk_widget *(*nearest_child_above)(struct ltk_widget *self, ltk_rect rect); - struct ltk_widget *(*nearest_child_below)(struct ltk_widget *self, ltk_rect rect); + struct ltk_widget *(*nearest_child_left)(struct ltk_widget *self, ltk_widget *widget); + struct ltk_widget *(*nearest_child_right)(struct ltk_widget *self, ltk_widget *widget); + struct ltk_widget *(*nearest_child_above)(struct ltk_widget *self, ltk_widget *widget); + struct ltk_widget *(*nearest_child_below)(struct ltk_widget *self, ltk_widget *widget); struct ltk_widget *(*next_child)(struct ltk_widget *self, ltk_widget *child); struct ltk_widget *(*prev_child)(struct ltk_widget *self, ltk_widget *child); struct ltk_widget *(*first_child)(struct ltk_widget *self); + struct ltk_widget *(*last_child)(struct ltk_widget *self); - void (*child_size_change) (struct ltk_widget *, struct ltk_widget *); + void (*child_size_change)(struct ltk_widget *, struct ltk_widget *); int (*remove_child)(struct ltk_widget *, struct ltk_widget *, ltk_error *); + /* x and y relative to widget's lrect! */ struct ltk_widget *(*get_child_at_pos)(struct ltk_widget *, int x, int y); + /* r is in self's coordinate system */ + void (*ensure_rect_shown)(struct ltk_widget *self, ltk_rect r); ltk_widget_type type; ltk_widget_flags flags; @@ -138,6 +164,8 @@ void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, struct ltk_win struct ltk_widget_vtable *vtable, int w, int h); void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state); /* FIXME: move to separate window.h */ +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_release_event(ltk_window *window, ltk_button_event *event); void ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event); diff --git a/src/widget_config.h b/src/widget_config.h @@ -0,0 +1,7 @@ +#ifndef LTK_WIDGET_CONFIG_H +#define LTK_WIDGET_CONFIG_H + +typedef struct ltk_key_callback ltk_key_callback; +ltk_key_callback *ltk_get_key_func(char *name, size_t len); + +#endif /* LTK_WIDGET_CONFIG_H */ diff --git a/src/xlib_shared.h b/src/xlib_shared.h @@ -15,6 +15,12 @@ struct ltk_renderdata { XdbeBackBuffer back_buf; Drawable drawable; int depth; + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + int xkb_event_type; + int xkb_supported; }; #endif /* XLIB_SHARED_H */ diff --git a/test.gui b/test.gui @@ -1,7 +1,8 @@ -grid grd1 create 2 1 +grid grd1 create 2 2 grid grd1 set-row-weight 0 1 grid grd1 set-row-weight 1 1 grid grd1 set-column-weight 0 1 +grid grd1 set-column-weight 1 1 set-root-widget grd1 box box1 create vertical grid grd1 add box1 0 0 1 1 nsew @@ -19,4 +20,8 @@ button btn6 create "2 I'm another boring button." box box2 add btn4 ew box box2 add btn5 e box box2 add btn6 +button btn7 create "Button 7" +button btn8 create "Button 8" +grid grd1 add btn7 0 1 1 1 nsew +grid grd1 add btn8 1 1 1 1 ew mask-add btn1 button press diff --git a/test.sh b/test.sh @@ -1,10 +1,6 @@ #!/bin/sh # This is very hacky. -# -# All events are still printed to the terminal currently because -# the second './ltkc' still prints everything - event masks aren't -# supported yet. export LTKDIR="`pwd`/.ltk" ltk_id=`./src/ltkd -t "Cool Window"` diff --git a/test2.gui b/test2.gui @@ -42,3 +42,4 @@ submenu submenu3 create menu submenu3 add-entry entry16 menuentry entry15 attach-submenu submenu3 grid grd1 add menu1 0 0 1 1 ew +mask-add entry10 menuentry press diff --git a/test3.gui b/test3.gui @@ -0,0 +1,13 @@ +grid grd1 create 3 1 +grid grd1 set-row-weight 0 1 +grid grd1 set-row-weight 1 1 +grid grd1 set-row-weight 2 1 +grid grd1 set-column-weight 0 1 +set-root-widget grd1 +button btn1 create "I'm a button!" +button btn2 create "I'm also a button!" +button btn3 create "I'm another boring button." +grid grd1 add btn1 0 0 1 1 +grid grd1 add btn2 1 0 1 1 +grid grd1 add btn3 2 0 1 1 +mask-add btn1 button press diff --git a/test3.sh b/test3.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +export LTKDIR="`pwd`/.ltk" +ltk_id=`./src/ltkd -t "Cool Window"` +if [ $? -ne 0 ]; then + echo "Unable to start ltkd." >&2 + exit 1 +fi + +cat test3.gui | ./src/ltkc $ltk_id | while read cmd +do + case "$cmd" in + *"event btn1 button press") + echo "quit" + ;; + *) + printf "%s\n" "$cmd" >&2 + ;; + esac +done | ./src/ltkc $ltk_id diff --git a/testbox.sh b/testbox.sh @@ -7,7 +7,7 @@ if [ $? -ne 0 ]; then exit 1 fi -cmds="box box1 create vertical\nset-root-widget box1\nbutton exit_btn create \"Exit\"\nmask-add exit_btn button press\nbox box1 add exit_btn +cmds="box box1 create vertical\nset-root-widget box1\nlabel lblbla create \"Hi\"\nbox box1 add lblbla w\nbutton exit_btn create \"Exit\"\nmask-add exit_btn button press\nbox box1 add exit_btn $(curl -s gopher://lumidify.org | awk -F'\t' ' BEGIN {btn = 0; lbl = 0;} /^i/ { printf "label lbl%s create \"%s\"\nbox box1 add lbl%s w\n", lbl, substr($1, 2), lbl; lbl++ }