URI:
       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 = &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_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 }