ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

window.c (42217B)


      1 /* FIXME: signal handling is really ugly and inconsistent at the moment */
      2 /*
      3  * Copyright (c) 2020-2024 lumidify <nobody@lumidify.org>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any
      6  * purpose with or without fee is hereby granted, provided that the above
      7  * copyright notice and this permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16  */
     17 
     18 #include <string.h>
     19 #include <math.h>
     20 
     21 #include "ltk.h"
     22 #include "util.h"
     23 #include "color.h"
     24 #include "array.h"
     25 #include "widget.h"
     26 #include "window.h"
     27 #include "memory.h"
     28 #include "config.h"
     29 #include "eventdefs.h"
     30 #include "widget_internal.h"
     31 
     32 #define MAX_WINDOW_FONT_SIZE 20000
     33 
     34 static void gen_widget_stack(ltk_widget *bottom);
     35 static ltk_widget *get_hover_popup(ltk_window *window, int x, int y);
     36 static int is_parent(ltk_widget *parent, ltk_widget *child);
     37 static ltk_widget *get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret);
     38 
     39 static int ltk_window_key_press_event(ltk_widget *self, ltk_key_event *event);
     40 static int ltk_window_key_release_event(ltk_widget *self, ltk_key_event *event);
     41 static int ltk_window_mouse_press_event(ltk_widget *self, ltk_button_event *event);
     42 static int ltk_window_mouse_scroll_event(ltk_widget *self, ltk_scroll_event *event);
     43 static int ltk_window_mouse_release_event(ltk_widget *self, ltk_button_event *event);
     44 static int ltk_window_motion_notify_event(ltk_widget *self, ltk_motion_event *event);
     45 static void ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
     46 
     47 /* FIXME: actually use this properly */
     48 static struct ltk_widget_vtable vtable = {
     49 	.key_press = &ltk_window_key_press_event,
     50 	.key_release = &ltk_window_key_release_event,
     51 	.mouse_press = &ltk_window_mouse_press_event,
     52 	.mouse_release = &ltk_window_mouse_release_event,
     53 	.release = NULL,
     54 	.motion_notify = &ltk_window_motion_notify_event,
     55 	.mouse_leave = NULL,
     56 	.mouse_enter = NULL,
     57 	.change_state = NULL,
     58 	.get_child_at_pos = NULL,
     59 	.resize = NULL,
     60 	.hide = NULL,
     61 	.draw = &ltk_window_redraw,
     62 	.destroy = &ltk_window_destroy,
     63 	.child_size_change = NULL,
     64 	.remove_child = NULL,
     65 	.type = LTK_WIDGET_WINDOW,
     66 	.flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
     67 	.invalid_signal = LTK_WINDOW_SIGNAL_INVALID,
     68 };
     69 
     70 static int cb_focus_active(ltk_widget *self, ltk_key_event *event);
     71 static int cb_unfocus_active(ltk_widget *self, ltk_key_event *event);
     72 static int cb_move_prev(ltk_widget *self, ltk_key_event *event);
     73 static int cb_move_next(ltk_widget *self, ltk_key_event *event);
     74 static int cb_move_left(ltk_widget *self, ltk_key_event *event);
     75 static int cb_move_right(ltk_widget *self, ltk_key_event *event);
     76 static int cb_move_up(ltk_widget *self, ltk_key_event *event);
     77 static int cb_move_down(ltk_widget *self, ltk_key_event *event);
     78 static int cb_set_pressed(ltk_widget *self, ltk_key_event *event);
     79 static int cb_unset_pressed(ltk_widget *self, ltk_key_event *event);
     80 static int cb_remove_popups(ltk_widget *self, ltk_key_event *event);
     81 
     82 static ltk_keybinding_cb cb_map[] = {
     83 	{"focus-active", &cb_focus_active},
     84 	{"move-down", &cb_move_down},
     85 	{"move-left", &cb_move_left},
     86 	{"move-next", &cb_move_next},
     87 	{"move-prev", &cb_move_prev},
     88 	{"move-right", &cb_move_right},
     89 	{"move-up", &cb_move_up},
     90 	{"remove-popups", &cb_remove_popups},
     91 	{"set-pressed", &cb_set_pressed},
     92 	{"unfocus-active", &cb_unfocus_active},
     93 	{"unset-pressed", &cb_unset_pressed},
     94 };
     95 
     96 static ltk_array(keypress) *keypresses = NULL;
     97 static ltk_array(keyrelease) *keyreleases = NULL;
     98 
     99 void
    100 ltk_window_get_keybinding_parseinfo(
    101         ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
    102         ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
    103         ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
    104 ) {
    105 	*press_cbs_ret = cb_map;
    106 	*press_len_ret = LENGTH(cb_map);
    107 	*release_cbs_ret = cb_map;
    108 	*release_len_ret = LENGTH(cb_map);
    109 	if (!keypresses)
    110 		keypresses = ltk_array_create(keypress, 1);
    111 	if (!keyreleases)
    112 		keyreleases = ltk_array_create(keyrelease, 1);
    113 	*presses_ret = keypresses;
    114 	*releases_ret = keyreleases;
    115 }
    116 
    117 /* needed for passing keyboard events down the hierarchy */
    118 static ltk_widget **widget_stack = NULL;
    119 static size_t widget_stack_alloc = 0;
    120 static size_t widget_stack_len = 0;
    121 
    122 static struct {
    123 	int border_width;
    124 	ltk_size font_size;
    125 	char *font;
    126 	ltk_color *fg;
    127 	ltk_color *bg;
    128 } theme;
    129 
    130 static ltk_theme_parseinfo theme_parseinfo[] = {
    131 	{"bg", THEME_COLOR, {.color = &theme.bg}, {.color = "#000000"}, 0, 0, 0},
    132 	{"fg", THEME_COLOR, {.color = &theme.fg}, {.color = "#FFFFFF"}, 0, 0, 0},
    133 	{"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
    134 	{"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, MAX_WINDOW_FONT_SIZE, 0},
    135 };
    136 
    137 void
    138 ltk_window_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
    139 	*p = theme_parseinfo;
    140 	*len = LENGTH(theme_parseinfo);
    141 }
    142 
    143 void
    144 ltk_window_cleanup(void) {
    145 	ltk_keypress_bindings_destroy(keypresses);
    146 	ltk_keyrelease_bindings_destroy(keyreleases);
    147 	keypresses = NULL;
    148 	keyreleases = NULL;
    149 	ltk_free(widget_stack);
    150 	widget_stack = NULL;
    151 }
    152 
    153 static void
    154 ensure_active_widget_shown(ltk_window *window) {
    155 	ltk_widget *widget = window->active_widget;
    156 	if (!widget)
    157 		return;
    158 	ltk_rect r = widget->lrect;
    159 	while (widget->parent) {
    160 		if (widget->parent->vtable->ensure_rect_shown)
    161 			widget->parent->vtable->ensure_rect_shown(widget->parent, r);
    162 		widget = widget->parent;
    163 		r.x += widget->lrect.x;
    164 		r.y += widget->lrect.y;
    165 		/* FIXME: this currently just aborts if a widget is positioned
    166 		   absolutely because I'm not sure what the best action would
    167 		   be in that case */
    168 		if (widget->popup)
    169 			break;
    170 	}
    171 	ltk_window_invalidate_widget_rect(window, widget);
    172 }
    173 
    174 /* FIXME: should keyrelease events be ignored if the corresponding keypress event
    175    was consumed for movement? */
    176 /* FIXME: check if there's any weirdness when combining return and mouse press */
    177 /* FIXME: maybe it doesn't really make sense to make e.g. text entry pressed when enter is pressed? */
    178 /* FIXME: implement key binding flag to run before widget handler is called */
    179 static int
    180 ltk_window_key_press_event(ltk_widget *self, ltk_key_event *event) {
    181 	ltk_window *window = LTK_CAST_WINDOW(self);
    182 	int handled = 0;
    183 	ltk_callback_arg args[] = {LTK_MAKE_ARG_KEY_EVENT(event)};
    184 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
    185 		gen_widget_stack(window->active_widget);
    186 		for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
    187 			if (ltk_widget_emit_signal(widget_stack[i], LTK_WIDGET_SIGNAL_KEY_PRESS, (ltk_callback_arglist){args, LENGTH(args)}) ||
    188 			    (widget_stack[i]->vtable->key_press && widget_stack[i]->vtable->key_press(widget_stack[i], event))) {
    189 				handled = 1;
    190 				break;
    191 			}
    192 		}
    193 	}
    194 	ltk_widget_handle_keypress_bindings(self, event, keypresses, handled);
    195 	return 1;
    196 }
    197 
    198 /* FIXME: need to actually check if any of parent widgets are focused and still pass to them even if bottom widget not focused? */
    199 static int
    200 ltk_window_key_release_event(ltk_widget *self, ltk_key_event *event) {
    201 	ltk_window *window = LTK_CAST_WINDOW(self);
    202 	int handled = 0;
    203 	ltk_callback_arg args[] = {LTK_MAKE_ARG_KEY_EVENT(event)};
    204 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
    205 		gen_widget_stack(window->active_widget);
    206 		for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
    207 			if (ltk_widget_emit_signal(widget_stack[i], LTK_WIDGET_SIGNAL_KEY_RELEASE, (ltk_callback_arglist){args, LENGTH(args)}) ||
    208 			    (widget_stack[i]->vtable->key_release && widget_stack[i]->vtable->key_release(widget_stack[i], event))) {
    209 				handled = 1;
    210 				break;
    211 			}
    212 		}
    213 	}
    214 	ltk_widget_handle_keyrelease_bindings(self, event, keyreleases, handled);
    215 	return 1;
    216 }
    217 
    218 /* FIXME: This is still weird. */
    219 static int
    220 ltk_window_mouse_press_event(ltk_widget *self, ltk_button_event *event) {
    221 	ltk_window *window = LTK_CAST_WINDOW(self);
    222 	ltk_widget *widget = get_hover_popup(window, event->x, event->y);
    223 	int check_hide = 0;
    224 	if (!widget) {
    225 		widget = window->root_widget;
    226 		check_hide = 1;
    227 	}
    228 	if (!widget) {
    229 		ltk_window_unregister_all_popups(window);
    230 		return 1;
    231 	}
    232 	int orig_x = event->x, orig_y = event->y;
    233 	ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
    234 	/* FIXME: need to add more flags for more fine-grained control
    235 	   -> also, should the widget still get mouse_press even if state doesn't change? */
    236 	/* FIXME: doesn't work with e.g. disabled menu entries */
    237 	if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
    238 		ltk_window_unregister_all_popups(window);
    239 	}
    240 
    241 	/* FIXME: this doesn't make much sense if the popups aren't a
    242 	   hierarchy (right now, they're just menus, so that's always
    243 	   a hierarchy */
    244 	/* don't hide popups if they are children of the now pressed widget */
    245 	if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0])))
    246 		ltk_window_unregister_all_popups(window);
    247 
    248 	/* FIXME: popups don't always have their children geometrically contained within parents,
    249 	   so this won't work properly in all cases */
    250 	int first = 1;
    251 	ltk_callback_arg args[] = {LTK_MAKE_ARG_BUTTON_EVENT(event)};
    252 	while (cur_widget) {
    253 		int handled = 0;
    254 		ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
    255 		event->x = local.x;
    256 		event->y = local.y;
    257 		if (cur_widget->state != LTK_DISABLED) {
    258 			/* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled)
    259 			   get mouse press, but they are only set to pressed if they are activatable */
    260 			handled = ltk_widget_emit_signal(cur_widget, LTK_WIDGET_SIGNAL_MOUSE_PRESS, (ltk_callback_arglist){args, LENGTH(args)});
    261 			if (!handled && cur_widget->vtable->mouse_press)
    262 				handled = cur_widget->vtable->mouse_press(cur_widget, event);
    263 			/* set first non-disabled widget to pressed widget */
    264 			/* FIXME: use config values for all_activatable */
    265 			if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
    266 				ltk_window_set_pressed_widget(window, cur_widget, 0);
    267 				first = 0;
    268 			}
    269 		}
    270 		if (!handled)
    271 			cur_widget = cur_widget->parent;
    272 		else
    273 			break;
    274 	}
    275 	return 1;
    276 }
    277 
    278 static int
    279 ltk_window_mouse_scroll_event(ltk_widget *self, ltk_scroll_event *event) {
    280 	ltk_window *window = LTK_CAST_WINDOW(self);
    281 	/* FIXME: should it first be sent to pressed widget? */
    282 	ltk_widget *widget = get_hover_popup(window, event->x, event->y);
    283 	if (!widget)
    284 		widget = window->root_widget;
    285 	if (!widget)
    286 		return 1;
    287 	int orig_x = event->x, orig_y = event->y;
    288 	ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
    289 	ltk_callback_arg args[] = {LTK_MAKE_ARG_SCROLL_EVENT(event)};
    290 	/* FIXME: same issue with popups like in mouse_press above */
    291 	while (cur_widget) {
    292 		int handled = 0;
    293 		ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
    294 		event->x = local.x;
    295 		event->y = local.y;
    296 		if (cur_widget->state != LTK_DISABLED) {
    297 			/* FIXME: see function above
    298 			if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy))
    299 				handled = 1; */
    300 			handled = ltk_widget_emit_signal(cur_widget, LTK_WIDGET_SIGNAL_MOUSE_SCROLL, (ltk_callback_arglist){args, LENGTH(args)});
    301 			if (!handled && cur_widget->vtable->mouse_scroll)
    302 				handled = cur_widget->vtable->mouse_scroll(cur_widget, event);
    303 		}
    304 		if (!handled)
    305 			cur_widget = cur_widget->parent;
    306 		else
    307 			break;
    308 	}
    309 	return 1;
    310 }
    311 
    312 void
    313 ltk_window_fake_motion_event(ltk_window *window, int x, int y) {
    314 	ltk_widget *self = LTK_CAST_WIDGET(window);
    315 	ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y};
    316 	ltk_callback_arg args[] = {LTK_MAKE_ARG_MOTION_EVENT(&e)};
    317 	if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) {
    318 		self->vtable->motion_notify(LTK_CAST_WIDGET(window), &e);
    319 	}
    320 }
    321 
    322 static int
    323 ltk_window_mouse_release_event(ltk_widget *self, ltk_button_event *event) {
    324 	ltk_window *window = LTK_CAST_WINDOW(self);
    325 	ltk_widget *widget = window->pressed_widget;
    326 	int orig_x = event->x, orig_y = event->y;
    327 	/* FIXME: why does this only take pressed widget and popups into account? */
    328 	if (!widget) {
    329 		widget = get_hover_popup(window, event->x, event->y);
    330 		widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
    331 	}
    332 	/* FIXME: loop up to top of hierarchy if not handled */
    333 	/* FIXME: see functions above
    334 	if (widget && queue_mouse_event(widget, event->type, event->x, event->y)) { */
    335 		/* NOP */
    336 	if (widget) {
    337 		ltk_callback_arg args[] = {LTK_MAKE_ARG_BUTTON_EVENT(event)};
    338 		if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_MOUSE_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) {
    339 			if (widget->vtable->mouse_release)
    340 				widget->vtable->mouse_release(widget, event);
    341 		}
    342 	}
    343 	if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) {
    344 		int release = 0;
    345 		if (window->pressed_widget) {
    346 			ltk_rect prect = window->pressed_widget->lrect;
    347 			ltk_point pglob = ltk_widget_pos_to_global(window->pressed_widget, 0, 0);
    348 			if (ltk_collide_rect((ltk_rect){pglob.x, pglob.y, prect.w, prect.h}, orig_x, orig_y))
    349 				release = 1;
    350 		}
    351 		ltk_window_set_pressed_widget(window, NULL, release);
    352 		/* send motion notify to widget under pointer */
    353 		/* FIXME: only when not collide with rect? */
    354 		ltk_window_fake_motion_event(window, orig_x, orig_y);
    355 	}
    356 	return 1;
    357 }
    358 
    359 static int
    360 ltk_window_motion_notify_event(ltk_widget *self, ltk_motion_event *event) {
    361 	ltk_window *window = LTK_CAST_WINDOW(self);
    362 	ltk_widget *widget = get_hover_popup(window, event->x, event->y);
    363 	int orig_x = event->x, orig_y = event->y;
    364 	ltk_callback_arg args[] = {LTK_MAKE_ARG_MOTION_EVENT(event)};
    365 	if (!widget) {
    366 		widget = window->pressed_widget;
    367 		if (widget) {
    368 			ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
    369 			event->x = local.x;
    370 			event->y = local.y;
    371 			if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) {
    372 				if (widget->vtable->motion_notify)
    373 					widget->vtable->motion_notify(widget, event);
    374 			}
    375 			return 1;
    376 		}
    377 		widget = window->root_widget;
    378 	}
    379 	if (!widget)
    380 		return 1;
    381 	ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
    382 	if (!ltk_collide_rect((ltk_rect){0, 0, widget->lrect.w, widget->lrect.h}, local.x, local.y)) {
    383 		ltk_window_set_hover_widget(widget->window, NULL, event);
    384 		return 1;
    385 	}
    386 	ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
    387 	int first = 1;
    388 	while (cur_widget) {
    389 		int handled = 0;
    390 		ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
    391 		event->x = local.x;
    392 		event->y = local.y;
    393 		if (cur_widget->state != LTK_DISABLED) {
    394 			/* FIXME: see functions above
    395 			if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y))
    396 				handled = 1; */
    397 			handled = ltk_widget_emit_signal(cur_widget, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)});
    398 			if (!handled && cur_widget->vtable->motion_notify)
    399 				handled = cur_widget->vtable->motion_notify(cur_widget, event);
    400 			/* set first non-disabled widget to hover widget */
    401 			/* FIXME: should enter/leave event be sent to parent
    402 			   when moving from/to widget nested in parent? */
    403 			/* FIXME: use config values for all_activatable */
    404 			if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
    405 				event->x = orig_x;
    406 				event->y = orig_y;
    407 				ltk_window_set_hover_widget(window, cur_widget, event);
    408 				first = 0;
    409 			}
    410 		}
    411 		if (!handled)
    412 			cur_widget = cur_widget->parent;
    413 		else
    414 			break;
    415 	}
    416 	if (first) {
    417 		event->x = orig_x;
    418 		event->y = orig_y;
    419 		ltk_window_set_hover_widget(window, NULL, event);
    420 	}
    421 	return 1;
    422 }
    423 
    424 void
    425 ltk_window_set_root_widget(ltk_window *window, ltk_widget *widget) {
    426 	window->root_widget = widget;
    427 	widget->lrect.x = 0;
    428 	widget->lrect.y = 0;
    429 	widget->lrect.w = window->rect.w;
    430 	widget->lrect.h = window->rect.h;
    431 	widget->crect = widget->lrect;
    432 	ltk_window_invalidate_rect(window, widget->lrect);
    433 	ltk_widget_resize(widget);
    434 }
    435 
    436 void
    437 ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
    438 	if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0)
    439 		window->dirty_rect = rect;
    440 	else
    441 		window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
    442 }
    443 
    444 void
    445 ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget) {
    446 	ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
    447 	ltk_window_invalidate_rect(window, (ltk_rect){glob.x, glob.y, widget->lrect.w, widget->lrect.h});
    448 }
    449 
    450 static void
    451 ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
    452 	(void)draw_surf;
    453 	(void)x;
    454 	(void)y;
    455 	(void)clip;
    456 	if (!self) return;
    457 	ltk_window *window = LTK_CAST_WINDOW(self);
    458 	ltk_widget *ptr;
    459 	if (window->dirty_rect.x >= window->rect.w) return;
    460 	if (window->dirty_rect.y >= window->rect.h) return;
    461 	if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
    462 		window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
    463 	if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
    464 		window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
    465 	/* FIXME: this should use window->dirty_rect, but that doesn't work
    466 	   properly with double buffering */
    467 	ltk_surface_fill_rect(window->surface, theme.bg, (ltk_rect){0, 0, window->rect.w, window->rect.h});
    468 	if (window->root_widget) {
    469 		ptr = window->root_widget;
    470 		ltk_widget_draw(ptr, window->surface, 0, 0, window->rect);
    471 	}
    472 	/* last popup is the newest one, so draw that last */
    473 	for (size_t i = 0; i < window->popups_num; i++) {
    474 		ptr = window->popups[i];
    475 		ltk_widget_draw(ptr, window->surface, ptr->lrect.x, ptr->lrect.y, ltk_rect_relative(ptr->lrect, window->rect));
    476 	}
    477 	ltk_renderer_swap_buffers(window->renderwindow);
    478 	window->dirty_rect.w = 0;
    479 	window->dirty_rect.h = 0;
    480 }
    481 
    482 static void
    483 ltk_window_other_event(ltk_window *window, ltk_event *event) {
    484 	ltk_widget *ptr = window->root_widget;
    485 	/* FIXME: decide whether this should be moved to separate resize function in window vtable */
    486 	if (event->type == LTK_CONFIGURE_EVENT) {
    487 		ltk_window_unregister_all_popups(window);
    488 		int w, h;
    489 		w = event->configure.w;
    490 		h = event->configure.h;
    491 		int orig_w = window->rect.w;
    492 		int orig_h = window->rect.h;
    493 		if (orig_w != w || orig_h != h) {
    494 			window->rect.w = w;
    495 			window->rect.h = h;
    496 			ltk_window_invalidate_rect(window, window->rect);
    497 			ltk_surface_update_size(window->surface, w, h);
    498 			if (ltk_widget_emit_signal(LTK_CAST_WIDGET(window), LTK_WIDGET_SIGNAL_RESIZE, LTK_EMPTY_ARGLIST))
    499 				return;
    500 			if (ptr) {
    501 				ptr->lrect.w = w;
    502 				ptr->lrect.h = h;
    503 				ptr->crect = ptr->lrect;
    504 				ltk_widget_resize(ptr);
    505 			}
    506 		}
    507 	} else if (event->type == LTK_EXPOSE_EVENT) {
    508 		ltk_rect r;
    509 		r.x = event->expose.x;
    510 		r.y = event->expose.y;
    511 		r.w = event->expose.w;
    512 		r.h = event->expose.h;
    513 		ltk_window_invalidate_rect(window, r);
    514 	} else if (event->type == LTK_WINDOWCLOSE_EVENT) {
    515 		ltk_widget_emit_signal(LTK_CAST_WIDGET(window), LTK_WINDOW_SIGNAL_CLOSE, LTK_EMPTY_ARGLIST);
    516 	} else if (event->type == LTK_DPICHANGE_EVENT) {
    517 		if (window->root_widget) {
    518 			ltk_window_unregister_all_popups(window); /* easier than trying to resize them */
    519 			ltk_widget_recalc_ideal_size(window->root_widget);
    520 			ltk_widget_resize(window->root_widget);
    521 		}
    522 	}
    523 }
    524 
    525 /* FIXME: check for duplicates? */
    526 void
    527 ltk_window_register_popup(ltk_window *window, ltk_widget *popup) {
    528 	if (window->popups_num == window->popups_alloc) {
    529 		window->popups_alloc = ideal_array_size(
    530 		    window->popups_alloc, window->popups_num + 1
    531 		);
    532 		window->popups = ltk_reallocarray(
    533 		    window->popups, window->popups_alloc, sizeof(ltk_widget *)
    534 		);
    535 	}
    536 	window->popups[window->popups_num++] = popup;
    537 	popup->popup = 1;
    538 }
    539 
    540 void
    541 ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup) {
    542 	if (window->popups_locked)
    543 		return;
    544 	for (size_t i = 0; i < window->popups_num; i++) {
    545 		if (window->popups[i] == popup) {
    546 			popup->popup = 0;
    547 			memmove(
    548 			    window->popups + i,
    549 			    window->popups + i + 1,
    550 			    sizeof(ltk_widget *) * (window->popups_num - i - 1)
    551 			);
    552 			window->popups_num--;
    553 			size_t sz = ideal_array_size(
    554 			    window->popups_alloc, window->popups_num
    555 			);
    556 			if (sz != window->popups_alloc) {
    557 				window->popups_alloc = sz;
    558 				window->popups = ltk_reallocarray(
    559 				    window->popups, sz, sizeof(ltk_widget *)
    560 				);
    561 			}
    562 			return;
    563 		}
    564 	}
    565 }
    566 
    567 /* FIXME: where should actual hiding happen? */
    568 void
    569 ltk_window_unregister_all_popups(ltk_window *window) {
    570 	window->popups_locked = 1;
    571 	for (size_t i = 0; i < window->popups_num; i++) {
    572 		window->popups[i]->hidden = 1;
    573 		window->popups[i]->popup = 0;
    574 		ltk_widget_hide(window->popups[i]);
    575 	}
    576 	window->popups_num = 0;
    577 	/* somewhat arbitrary, but should be enough for most cases */
    578 	if (window->popups_num > 4) {
    579 		window->popups = ltk_reallocarray(
    580 		    window->popups, 4, sizeof(ltk_widget *)
    581 		);
    582 		window->popups_alloc = 4;
    583 	}
    584 	window->popups_locked = 0;
    585 	/* I guess just invalidate everything instead of being smart */
    586 	ltk_window_invalidate_rect(window, window->rect);
    587 }
    588 
    589 /* FIXME: support more options like child windows */
    590 ltk_window *
    591 ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h) {
    592 	ltk_window *window = ltk_malloc(sizeof(ltk_window));
    593 
    594 	window->popups = NULL;
    595 	window->popups_num = window->popups_alloc = 0;
    596 	window->popups_locked = 0;
    597 
    598 	ltk_general_config *config = ltk_config_get_general();
    599 	unsigned int dpi = (unsigned int)round(config->dpi_scale * config->fixed_dpi * 5);
    600 	window->renderwindow = ltk_renderer_create_window(data, title, x, y, w, h, dpi);
    601 	ltk_renderer_set_window_properties(window->renderwindow, theme.bg);
    602 
    603 	window->root_widget = NULL;
    604 	window->hover_widget = NULL;
    605 	window->active_widget = NULL;
    606 	window->pressed_widget = NULL;
    607 
    608 	//FIXME: use widget rect
    609 	window->rect.w = w;
    610 	window->rect.h = h;
    611 	window->rect.x = 0;
    612 	window->rect.y = 0;
    613 	window->dirty_rect.w = 0;
    614 	window->dirty_rect.h = 0;
    615 	window->dirty_rect.x = 0;
    616 	window->dirty_rect.y = 0;
    617 
    618 	window->surface_cache = ltk_surface_cache_create(window->renderwindow);
    619 	window->surface = ltk_surface_from_window(window->renderwindow, w, h);
    620 
    621 	/* This is a bit weird because the window entry points to itself */
    622 	/* This needs to be called after window->renderwindow is set */
    623 	ltk_fill_widget_defaults(&window->widget, window, &vtable, 0, 0);
    624 
    625 	return window;
    626 }
    627 
    628 /* FIXME: check if widget window matches in all public functions */
    629 
    630 void
    631 ltk_window_destroy_intern(ltk_window *window) {
    632 	if (window->root_widget) {
    633 		ltk_widget_destroy(window->root_widget, 0);
    634 	}
    635 	if (window->popups)
    636 		ltk_free(window->popups);
    637 	ltk_surface_cache_destroy(window->surface_cache);
    638 	ltk_surface_destroy(window->surface);
    639 	ltk_renderer_destroy_window(window->renderwindow);
    640 	ltk_free(window);
    641 }
    642 
    643 /* event must have global coordinates! */
    644 void
    645 ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event) {
    646 	ltk_widget *old = window->hover_widget;
    647 	if (old == widget)
    648 		return;
    649 	int orig_x = event->x, orig_y = event->y;
    650 	ltk_callback_arg args[] = {LTK_MAKE_ARG_MOTION_EVENT(event)};
    651 	if (old) {
    652 		ltk_widget_state old_state = old->state;
    653 		old->state &= ~LTK_HOVER;
    654 		ltk_widget_change_state(old, old_state);
    655 		ltk_point local = ltk_global_to_widget_pos(old, event->x, event->y);
    656 		event->x = local.x;
    657 		event->y = local.y;
    658 		if (!ltk_widget_emit_signal(old, LTK_WIDGET_SIGNAL_MOUSE_LEAVE, (ltk_callback_arglist){args, LENGTH(args)})) {
    659 			if (old->vtable->mouse_leave)
    660 				old->vtable->mouse_leave(old, event);
    661 		}
    662 		event->x = orig_x;
    663 		event->y = orig_y;
    664 	}
    665 	window->hover_widget = widget;
    666 	if (widget) {
    667 		ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
    668 		event->x = local.x;
    669 		event->y = local.y;
    670 		if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_MOUSE_ENTER, (ltk_callback_arglist){args, LENGTH(args)})) {
    671 			if (widget->vtable->mouse_enter)
    672 				widget->vtable->mouse_enter(widget, event);
    673 		}
    674 		ltk_widget_state old_state = widget->state;
    675 		widget->state |= LTK_HOVER;
    676 		ltk_widget_change_state(widget, old_state);
    677 		if ((widget->vtable->flags & LTK_HOVER_IS_ACTIVE) && widget != window->active_widget)
    678 			ltk_window_set_active_widget(window, widget);
    679 	}
    680 }
    681 
    682 void
    683 ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
    684 	if (window->active_widget == widget) {
    685 		return;
    686 	}
    687 	ltk_widget *old = window->active_widget;
    688 	/* Note: this has to be set at the beginning to
    689 	   avoid infinite recursion in some cases */
    690 	window->active_widget = widget;
    691 	ltk_widget *common_parent = NULL;
    692 	if (widget) {
    693 		ltk_widget *cur = widget;
    694 		while (cur) {
    695 			if (cur->state & LTK_ACTIVE) {
    696 				common_parent = cur;
    697 				break;
    698 			}
    699 			ltk_widget_state old_state = cur->state;
    700 			cur->state |= LTK_ACTIVE;
    701 			/* FIXME: should all be set focused? */
    702 			if (cur == widget && !(cur->vtable->flags & LTK_NEEDS_KEYBOARD))
    703 				widget->state |= LTK_FOCUSED;
    704 			ltk_widget_change_state(cur, old_state);
    705 			cur = cur->parent;
    706 		}
    707 	}
    708 	/* FIXME: better variable names; generally make this nicer */
    709 	/* special case if old is parent of new active widget */
    710 	ltk_widget *tmp = common_parent;
    711 	while (tmp) {
    712 		if (tmp == old)
    713 			return;
    714 		tmp = tmp->parent;
    715 	}
    716 	if (old) {
    717 		old->state &= ~LTK_FOCUSED;
    718 		ltk_widget *cur = old;
    719 		while (cur) {
    720 			if (cur == common_parent)
    721 				break;
    722 			ltk_widget_state old_state = cur->state;
    723 			cur->state &= ~LTK_ACTIVE;
    724 			ltk_widget_change_state(cur, old_state);
    725 			cur = cur->parent;
    726 		}
    727 	}
    728 }
    729 
    730 void
    731 ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release) {
    732 	if (window->pressed_widget == widget)
    733 		return;
    734 	if (window->pressed_widget) {
    735 		ltk_widget_state old_state = window->pressed_widget->state;
    736 		window->pressed_widget->state &= ~LTK_PRESSED;
    737 		ltk_widget_change_state(window->pressed_widget, old_state);
    738 		ltk_window_set_active_widget(window, window->pressed_widget);
    739 		/* FIXME: this is a bit weird because the release handler for menuentry
    740 		   indirectly calls ltk_widget_hide, which messes with the pressed widget */
    741 		/* FIXME: isn't it redundant to check that state is pressed? */
    742 		if (release && (old_state & LTK_PRESSED)) {
    743 			if (!ltk_widget_emit_signal(window->pressed_widget, LTK_WIDGET_SIGNAL_RELEASE, LTK_EMPTY_ARGLIST)) {
    744 				if (window->pressed_widget->vtable->release)
    745 					window->pressed_widget->vtable->release(window->pressed_widget);
    746 			}
    747 		}
    748 	}
    749 	window->pressed_widget = widget;
    750 	if (widget) {
    751 		if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_PRESS, LTK_EMPTY_ARGLIST)) {
    752 			if (widget->vtable->press)
    753 				widget->vtable->press(widget);
    754 		}
    755 		ltk_widget_state old_state = widget->state;
    756 		widget->state |= LTK_PRESSED;
    757 		ltk_widget_change_state(widget, old_state);
    758 	}
    759 }
    760 
    761 void
    762 ltk_window_handle_event(ltk_window *window, ltk_event *event) {
    763 	ltk_widget *self = LTK_CAST_WIDGET(window);
    764 	ltk_callback_arg args[1];
    765 	switch (event->type) {
    766 	case LTK_KEYPRESS_EVENT:
    767 		args[0] = LTK_MAKE_ARG_KEY_EVENT(&event->key);
    768 		if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_KEY_PRESS, (ltk_callback_arglist){args, LENGTH(args)})) {
    769 			ltk_window_key_press_event(self, &event->key);
    770 		}
    771 		break;
    772 	case LTK_KEYRELEASE_EVENT:
    773 		args[0] = LTK_MAKE_ARG_KEY_EVENT(&event->key);
    774 		if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_KEY_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) {
    775 			ltk_window_key_release_event(self, &event->key);
    776 		}
    777 		break;
    778 	case LTK_BUTTONPRESS_EVENT:
    779 	case LTK_2BUTTONPRESS_EVENT:
    780 	case LTK_3BUTTONPRESS_EVENT:
    781 		args[0] = LTK_MAKE_ARG_BUTTON_EVENT(&event->button);
    782 		if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOUSE_PRESS, (ltk_callback_arglist){args, LENGTH(args)})) {
    783 			ltk_window_mouse_press_event(self, &event->button);
    784 		}
    785 		break;
    786 	case LTK_SCROLL_EVENT:
    787 		args[0] = LTK_MAKE_ARG_SCROLL_EVENT(&event->scroll);
    788 		if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOUSE_SCROLL, (ltk_callback_arglist){args, LENGTH(args)})) {
    789 			ltk_window_mouse_scroll_event(self, &event->scroll);
    790 		}
    791 		break;
    792 	case LTK_BUTTONRELEASE_EVENT:
    793 	case LTK_2BUTTONRELEASE_EVENT:
    794 	case LTK_3BUTTONRELEASE_EVENT:
    795 		args[0] = LTK_MAKE_ARG_BUTTON_EVENT(&event->button);
    796 		if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOUSE_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) {
    797 			ltk_window_mouse_release_event(self, &event->button);
    798 		}
    799 		break;
    800 	case LTK_MOTION_EVENT:
    801 		args[0] = LTK_MAKE_ARG_MOTION_EVENT(&event->motion);
    802 		if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) {
    803 			ltk_window_motion_notify_event(self, &event->motion);
    804 		}
    805 		break;
    806 	default:
    807 		ltk_window_other_event(window, event);
    808 	}
    809 }
    810 
    811 /* x and y are global! */
    812 static ltk_widget *
    813 get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret) {
    814 	ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
    815 	ltk_widget *next = NULL;
    816 	*local_x_ret = x - glob.x;
    817 	*local_y_ret = y - glob.y;
    818 	while (widget && widget->vtable->get_child_at_pos) {
    819 		next = widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret);
    820 		if (!next) {
    821 			break;
    822 		} else {
    823 			widget = next;
    824 			if (next->popup) {
    825 				*local_x_ret = x - next->lrect.x;
    826 				*local_y_ret = y - next->lrect.y;
    827 			} else {
    828 				*local_x_ret -= next->lrect.x;
    829 				*local_y_ret -= next->lrect.y;
    830 			}
    831 		}
    832 	}
    833 	return widget;
    834 }
    835 
    836 static ltk_widget *
    837 get_hover_popup(ltk_window *window, int x, int y) {
    838 	for (size_t i = window->popups_num; i-- > 0;) {
    839 		if (ltk_collide_rect(window->popups[i]->crect, x, y))
    840 			return window->popups[i];
    841 	}
    842 	return NULL;
    843 }
    844 
    845 static int
    846 is_parent(ltk_widget *parent, ltk_widget *child) {
    847 	while (child && child != parent) {
    848 		child = child->parent;
    849 	}
    850 	return child != NULL;
    851 }
    852 
    853 /* FIXME: come up with a more elegant way to handle this? */
    854 /* FIXME: Handle hidden state here instead of in widgets */
    855 /* FIXME: handle disabled state */
    856 static int
    857 prev_child(ltk_window *window) {
    858 	if (!window->root_widget)
    859 		return 0;
    860 	ltk_general_config *config = ltk_config_get_general();
    861 	ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
    862 	ltk_widget *new, *cur = window->active_widget;
    863 	int changed = 0;
    864 	ltk_widget *prevcur = cur;
    865 	while (1) {
    866 		if (cur) {
    867 			while (cur->parent) {
    868 				new = NULL;
    869 				if (cur->parent->vtable->prev_child)
    870 					new = cur->parent->vtable->prev_child(cur->parent, cur);
    871 				if (new) {
    872 					cur = new;
    873 					ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
    874 					while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
    875 						cur = new;
    876 						if (cur->vtable->flags & act_flags)
    877 							last_activatable = cur;
    878 					}
    879 					if (last_activatable) {
    880 						cur = last_activatable;
    881 						changed = 1;
    882 						break;
    883 					}
    884 				} else {
    885 					cur = cur->parent;
    886 					if (cur->vtable->flags & act_flags) {
    887 						changed = 1;
    888 						break;
    889 					}
    890 				}
    891 			}
    892 		}
    893 		if (!changed) {
    894 			cur = window->root_widget;
    895 			ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
    896 			while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
    897 				cur = new;
    898 				if (cur->vtable->flags & act_flags)
    899 					last_activatable = cur;
    900 			}
    901 			if (last_activatable)
    902 				cur = last_activatable;
    903 		}
    904 		if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
    905 			break;
    906 		prevcur = cur;
    907 	}
    908 	/* FIXME: What exactly should be done if no activatable widget exists? */
    909 	if (cur != window->active_widget) {
    910 		ltk_window_set_active_widget(window, cur);
    911 		ensure_active_widget_shown(window);
    912 		return 1;
    913 	}
    914 	return 0;
    915 }
    916 
    917 static int
    918 next_child(ltk_window *window) {
    919 	if (!window->root_widget)
    920 		return 0;
    921 	ltk_general_config *config = ltk_config_get_general();
    922 	ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
    923 	ltk_widget *new, *cur = window->active_widget;
    924 	int changed = 0;
    925 	ltk_widget *prevcur = cur;
    926 	while (1) {
    927 		if (cur) {
    928 
    929 			while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
    930 				cur = new;
    931 				if (cur->vtable->flags & act_flags) {
    932 					changed = 1;
    933 					break;
    934 				}
    935 			}
    936 			if (!changed) {
    937 				while (cur->parent) {
    938 					new = NULL;
    939 					if (cur->parent->vtable->next_child)
    940 						new = cur->parent->vtable->next_child(cur->parent, cur);
    941 					if (new) {
    942 						cur = new;
    943 						if (cur->vtable->flags & act_flags) {
    944 							changed = 1;
    945 							break;
    946 						}
    947 						while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
    948 							cur = new;
    949 							if (cur->vtable->flags & act_flags) {
    950 								changed = 1;
    951 								break;
    952 							}
    953 						}
    954 						if (changed)
    955 							break;
    956 					} else {
    957 						cur = cur->parent;
    958 					}
    959 				}
    960 			}
    961 		}
    962 		if (!changed) {
    963 			cur = window->root_widget;
    964 			if (!(cur->vtable->flags & act_flags)) {
    965 				while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
    966 					cur = new;
    967 					if (cur->vtable->flags & act_flags)
    968 						break;
    969 				}
    970 			}
    971 			if (!(cur->vtable->flags & act_flags))
    972 				cur = window->root_widget;
    973 		}
    974 		if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
    975 			break;
    976 		prevcur = cur;
    977 	}
    978 	if (cur != window->active_widget) {
    979 		ltk_window_set_active_widget(window, cur);
    980 		ensure_active_widget_shown(window);
    981 		return 1;
    982 	}
    983 	return 0;
    984 }
    985 
    986 /* FIXME: moving up/down/left/right needs to be rethought
    987    it generally is a bit weird, and in particular, nearest_child always searches for the child
    988    that has the smallest distance to the given rect, so it may not be the child that the user
    989    expects when going down (e.g. a vertical box with one widget closer vertically but on the
    990    other side horizontally, thus possibly leading to a different widget that is farther away
    991    vertically to be chosen instead) - what would be logical here? */
    992 static ltk_widget *
    993 nearest_child(ltk_widget *widget, ltk_rect r) {
    994 	ltk_point local = ltk_global_to_widget_pos(widget, r.x, r.y);
    995 	ltk_rect rect = {local.x, local.y, r.w, r.h};
    996 	if (widget->vtable->nearest_child)
    997 		return widget->vtable->nearest_child(widget, rect);
    998 	return NULL;
    999 }
   1000 
   1001 /* FIXME: maybe wrap around in these two functions? */
   1002 static int
   1003 left_top_child(ltk_window *window, int left) {
   1004 	if (!window->root_widget)
   1005 		return 0;
   1006 	ltk_general_config *config = ltk_config_get_general();
   1007 	ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
   1008 	ltk_widget *new, *cur = window->active_widget;
   1009 	ltk_rect old_rect = {0, 0, 0, 0};
   1010 	ltk_widget *last_activatable = NULL;
   1011 	if (!cur) {
   1012 		cur = window->root_widget;
   1013 		if (cur->vtable->flags & act_flags)
   1014 			last_activatable = cur;
   1015 		ltk_rect r = {cur->lrect.w, cur->lrect.h, 0, 0};
   1016 		while ((new = nearest_child(cur, r))) {
   1017 			cur = new;
   1018 			if (cur->vtable->flags & act_flags)
   1019 				last_activatable = cur;
   1020 		}
   1021 	}
   1022 	if (last_activatable) {
   1023 		cur = last_activatable;
   1024 	} else if (cur) {
   1025 		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};
   1026 		old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
   1027 		while (cur->parent) {
   1028 			new = NULL;
   1029 			if (left) {
   1030 				if (cur->parent->vtable->nearest_child_left)
   1031 					new = cur->parent->vtable->nearest_child_left(cur->parent, cur);
   1032 			} else {
   1033 				if (cur->parent->vtable->nearest_child_above)
   1034 					new = cur->parent->vtable->nearest_child_above(cur->parent, cur);
   1035 			}
   1036 			if (new) {
   1037 				cur = new;
   1038 				ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
   1039 				while ((new = nearest_child(cur, old_rect))) {
   1040 					cur = new;
   1041 					if (cur->vtable->flags & act_flags)
   1042 						last_activatable = cur;
   1043 				}
   1044 				if (last_activatable) {
   1045 					cur = last_activatable;
   1046 					break;
   1047 				}
   1048 			} else {
   1049 				cur = cur->parent;
   1050 				if (cur->vtable->flags & act_flags) {
   1051 					break;
   1052 				}
   1053 			}
   1054 		}
   1055 	}
   1056 	/* FIXME: What exactly should be done if no activatable widget exists? */
   1057 	if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
   1058 		ltk_window_set_active_widget(window, cur);
   1059 		ensure_active_widget_shown(window);
   1060 		return 1;
   1061 	}
   1062 	return 0;
   1063 }
   1064 
   1065 static int
   1066 right_bottom_child(ltk_window *window, int right) {
   1067 	if (!window->root_widget)
   1068 		return 0;
   1069 	ltk_general_config *config = ltk_config_get_general();
   1070 	ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
   1071 	ltk_widget *new, *cur = window->active_widget;
   1072 	int changed = 0;
   1073 	ltk_rect old_rect = {0, 0, 0, 0};
   1074 	ltk_rect corner = {0, 0, 0, 0};
   1075 	int found_activatable = 0;
   1076 	if (!cur) {
   1077 		cur = window->root_widget;
   1078 		if (!(cur->vtable->flags & act_flags)) {
   1079 			while ((new = nearest_child(cur, (ltk_rect){0, 0, 0, 0}))) {
   1080 				cur = new;
   1081 				if (cur->vtable->flags & act_flags) {
   1082 					found_activatable = 1;
   1083 					break;
   1084 				}
   1085 			}
   1086 		}
   1087 	}
   1088 	if (!found_activatable) {
   1089 		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};
   1090 		corner = (ltk_rect){glob.x, glob.y, 0, 0};
   1091 		old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
   1092 		while ((new = nearest_child(cur, corner))) {
   1093 			cur = new;
   1094 			if (cur->vtable->flags & act_flags) {
   1095 				changed = 1;
   1096 				break;
   1097 			}
   1098 		}
   1099 		if (!changed) {
   1100 			while (cur->parent) {
   1101 				new = NULL;
   1102 				if (right) {
   1103 					if (cur->parent->vtable->nearest_child_right)
   1104 						new = cur->parent->vtable->nearest_child_right(cur->parent, cur);
   1105 				} else {
   1106 					if (cur->parent->vtable->nearest_child_below)
   1107 						new = cur->parent->vtable->nearest_child_below(cur->parent, cur);
   1108 				}
   1109 				if (new) {
   1110 					cur = new;
   1111 					if (cur->vtable->flags & act_flags) {
   1112 						changed = 1;
   1113 						break;
   1114 					}
   1115 					while ((new = nearest_child(cur, old_rect))) {
   1116 						cur = new;
   1117 						if (cur->vtable->flags & act_flags) {
   1118 							changed = 1;
   1119 							break;
   1120 						}
   1121 					}
   1122 					if (changed)
   1123 						break;
   1124 				} else {
   1125 					cur = cur->parent;
   1126 				}
   1127 			}
   1128 		}
   1129 	}
   1130 	if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
   1131 		ltk_window_set_active_widget(window, cur);
   1132 		ensure_active_widget_shown(window);
   1133 		return 1;
   1134 	}
   1135 	return 0;
   1136 }
   1137 
   1138 /* FIXME: maybe just set this when active widget changes */
   1139 /* -> but would also need to change it when widgets are created/destroyed or parents change */
   1140 static void
   1141 gen_widget_stack(ltk_widget *bottom) {
   1142 	widget_stack_len = 0;
   1143 	while (bottom) {
   1144 		if (widget_stack_len + 1 > widget_stack_alloc) {
   1145 			widget_stack_alloc = ideal_array_size(widget_stack_alloc, widget_stack_len + 1);
   1146 			widget_stack = ltk_reallocarray(widget_stack, widget_stack_alloc, sizeof(ltk_widget *));
   1147 		}
   1148 		widget_stack[widget_stack_len++] = bottom;
   1149 		bottom = bottom->parent;
   1150 	}
   1151 }
   1152 
   1153 /* FIXME: The focus behavior needs to be rethought. It's currently hard-coded in the vtable for each
   1154    widget type, but what if the program using ltk wants to catch keyboard events even if the widget
   1155    doesn't do that by default? */
   1156 static int
   1157 cb_focus_active(ltk_widget *self, ltk_key_event *event) {
   1158 	(void)event;
   1159 	ltk_window *window = LTK_CAST_WINDOW(self);
   1160 	if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) {
   1161 		/* FIXME: maybe also set widgets above in hierarchy? */
   1162 		ltk_widget_state old_state = window->active_widget->state;
   1163 		window->active_widget->state |= LTK_FOCUSED;
   1164 		ltk_widget_change_state(window->active_widget, old_state);
   1165 		return 1;
   1166 	}
   1167 	return 0;
   1168 }
   1169 
   1170 static int
   1171 cb_unfocus_active(ltk_widget *self, ltk_key_event *event) {
   1172 	(void)event;
   1173 	ltk_window *window = LTK_CAST_WINDOW(self);
   1174 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) {
   1175 		ltk_widget_state old_state = window->active_widget->state;
   1176 		window->active_widget->state &= ~LTK_FOCUSED;
   1177 		ltk_widget_change_state(window->active_widget, old_state);
   1178 		return 1;
   1179 	}
   1180 	return 0;
   1181 }
   1182 
   1183 static int
   1184 cb_move_prev(ltk_widget *self, ltk_key_event *event) {
   1185 	(void)event;
   1186 	ltk_window *window = LTK_CAST_WINDOW(self);
   1187 	return prev_child(window);
   1188 }
   1189 
   1190 static int
   1191 cb_move_next(ltk_widget *self, ltk_key_event *event) {
   1192 	(void)event;
   1193 	ltk_window *window = LTK_CAST_WINDOW(self);
   1194 	return next_child(window);
   1195 }
   1196 
   1197 static int
   1198 cb_move_left(ltk_widget *self, ltk_key_event *event) {
   1199 	(void)event;
   1200 	ltk_window *window = LTK_CAST_WINDOW(self);
   1201 	return left_top_child(window, 1);
   1202 }
   1203 
   1204 static int
   1205 cb_move_right(ltk_widget *self, ltk_key_event *event) {
   1206 	(void)event;
   1207 	ltk_window *window = LTK_CAST_WINDOW(self);
   1208 	return right_bottom_child(window, 1);
   1209 }
   1210 
   1211 static int
   1212 cb_move_up(ltk_widget *self, ltk_key_event *event) {
   1213 	(void)event;
   1214 	ltk_window *window = LTK_CAST_WINDOW(self);
   1215 	return left_top_child(window, 0);
   1216 }
   1217 
   1218 static int
   1219 cb_move_down(ltk_widget *self, ltk_key_event *event) {
   1220 	(void)event;
   1221 	ltk_window *window = LTK_CAST_WINDOW(self);
   1222 	return right_bottom_child(window, 0);
   1223 }
   1224 
   1225 static int
   1226 cb_set_pressed(ltk_widget *self, ltk_key_event *event) {
   1227 	(void)event;
   1228 	ltk_window *window = LTK_CAST_WINDOW(self);
   1229 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
   1230 		/* FIXME: only set pressed if needs keyboard? */
   1231 		ltk_window_set_pressed_widget(window, window->active_widget, 0);
   1232 		return 1;
   1233 	}
   1234 	return 0;
   1235 }
   1236 
   1237 static int
   1238 cb_unset_pressed(ltk_widget *self, ltk_key_event *event) {
   1239 	(void)event;
   1240 	ltk_window *window = LTK_CAST_WINDOW(self);
   1241 	if (window->pressed_widget) {
   1242 		ltk_window_set_pressed_widget(window, NULL, 1);
   1243 		return 1;
   1244 	}
   1245 	return 0;
   1246 }
   1247 
   1248 static int
   1249 cb_remove_popups(ltk_widget *self, ltk_key_event *event) {
   1250 	(void)event;
   1251 	ltk_window *window = LTK_CAST_WINDOW(self);
   1252 	if (window->popups_num > 0) {
   1253 		ltk_window_unregister_all_popups(window);
   1254 		return 1;
   1255 	}
   1256 	return 0;
   1257 }