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