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