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:
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 = <k_box_get_child_at_pos,
.mouse_leave = NULL,
.mouse_enter = NULL,
+ .prev_child = <k_box_prev_child,
+ .next_child = <k_box_next_child,
+ .first_child = <k_box_first_child,
+ .last_child = <k_box_last_child,
+ .nearest_child = <k_box_nearest_child,
+ .nearest_child_left = <k_box_nearest_child_left,
+ .nearest_child_right = <k_box_nearest_child_right,
+ .nearest_child_above = <k_box_nearest_child_above,
+ .nearest_child_below = <k_box_nearest_child_below,
+ .ensure_rect_shown = <k_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, <k_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 = <k_button_mouse_release,
+ .mouse_release = NULL,
+ .release = <k_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 = <k_grid_draw,
.destroy = <k_grid_destroy,
@@ -68,6 +79,15 @@ static struct ltk_widget_vtable vtable = {
.mouse_enter = NULL,
.key_press = NULL,
.key_release = NULL,
+ .prev_child = <k_grid_prev_child,
+ .next_child = <k_grid_next_child,
+ .first_child = <k_grid_first_child,
+ .last_child = <k_grid_last_child,
+ .nearest_child = <k_grid_nearest_child,
+ .nearest_child_left = <k_grid_nearest_child_left,
+ .nearest_child_right = <k_grid_nearest_child_right,
+ .nearest_child_above = <k_grid_nearest_child_above,
+ .nearest_child_below = <k_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 = <k_menu_destroy,
.child_size_change = &recalc_ideal_menu_size,
.remove_child = <k_menu_remove_child,
+ .prev_child = <k_menu_prev_child,
+ .next_child = <k_menu_next_child,
+ .first_child = <k_menu_first_child,
+ .last_child = <k_menu_last_child,
+ .nearest_child = <k_menu_nearest_child,
+ .nearest_child_left = <k_menu_nearest_child_left,
+ .nearest_child_right = <k_menu_nearest_child_right,
+ .nearest_child_above = <k_menu_nearest_child_above,
+ .nearest_child_below = <k_menu_nearest_child_below,
+ .ensure_rect_shown = <k_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 = <k_menuentry_mouse_release,
+ .mouse_release = NULL,
+ .release = <k_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 = <k_menuentry_draw,
.destroy = <k_menuentry_destroy,
.child_size_change = NULL,
- .remove_child = NULL,
+ .remove_child = <k_menuentry_remove_child,
+ .first_child = <k_menuentry_get_child,
+ .last_child = <k_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++ }