ltk

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

commit 46ddc040dfd9d76a78a5f7b4d1d080e141c7dd2f
parent 9b2b4596605e6594a2538f6b3bce3ffd9e90c652
Author: lumidify <nobody@lumidify.org>
Date:   Wed, 13 May 2026 13:18:27 +0200

Store widgets in central array and use IDs in most public functions

Diffstat:
MLICENSE | 2+-
Mexamples/ltk/test.c | 94++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/ltk/array.h | 82++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/ltk/box.c | 385++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/ltk/box.h | 22+++++++++++-----------
Msrc/ltk/button.c | 29+++++++++++++----------------
Msrc/ltk/button.h | 4++--
Msrc/ltk/checkbutton.c | 40++++++++++++++++++++--------------------
Msrc/ltk/checkbutton.h | 8++++----
Msrc/ltk/combobox.c | 236+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/ltk/combobox.h | 19++++++++++---------
Msrc/ltk/entry.c | 78+++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/ltk/entry.h | 6++----
Msrc/ltk/grid.c | 253++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/ltk/grid.h | 18++++++++----------
Msrc/ltk/image_widget.c | 19+++++++------------
Msrc/ltk/image_widget.h | 4++--
Msrc/ltk/label.c | 14+++++---------
Msrc/ltk/label.h | 4++--
Msrc/ltk/ltk.c | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/ltk/ltk.h | 24+++++++++++++++++++++---
Msrc/ltk/menu.c | 747++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/ltk/menu.h | 43+++++++++++++++++++------------------------
Msrc/ltk/radiobutton.c | 75+++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/ltk/radiobutton.h | 14+++++++-------
Msrc/ltk/scrollbar.c | 46+++++++++++++++++++++++++---------------------
Msrc/ltk/scrollbar.h | 8++++----
Msrc/ltk/widget.c | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/ltk/widget.h | 151+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/ltk/widget_internal.h | 10+++++-----
Msrc/ltk/window.c | 511+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/ltk/window.h | 42++++++++++++++++++++++--------------------
Msrc/ltkd/box.c | 24+++++++++++-------------
Msrc/ltkd/button.c | 10+++++-----
Msrc/ltkd/cmd.h | 12++++++------
Msrc/ltkd/entry.c | 10+++++-----
Msrc/ltkd/grid.c | 44+++++++++++++++++++++++---------------------
Msrc/ltkd/image_widget.c | 10+++++-----
Msrc/ltkd/label.c | 10+++++-----
Msrc/ltkd/ltkd.c | 57+++++++++++++++++++++++++++++++--------------------------
Msrc/ltkd/ltkd.h | 6+++---
Msrc/ltkd/menu.c | 82+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/ltkd/widget.c | 42++++++++++++++++++++++++------------------
Msrc/ltkd/widget.h | 8++++----
44 files changed, 2015 insertions(+), 1661 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -4,7 +4,7 @@ src/ltk/ctrlsel.*, and src/ltk/macros.h for third-party licenses. ISC License The Lumidify ToolKit (LTK) -Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> +Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/examples/ltk/test.c b/examples/ltk/test.c @@ -45,75 +45,75 @@ main(int argc, char *argv[]) { (void)argv; ltk_init(); - ltk_window *window = ltk_window_create("Hi", 0, 0, 500, 500); - ltk_grid *grid = ltk_grid_create(window, 5, 2); + ltk_widget_id window = ltk_window_create("Hi", 0, 0, 500, 500); + ltk_widget_id grid = ltk_grid_create(window, 5, 2); ltk_grid_set_column_weight(grid, 0, 1); ltk_grid_set_column_weight(grid, 1, 1); ltk_grid_set_row_weight(grid, 4, 1); - ltk_button *button = ltk_button_create(window, "I'm a button!"); - ltk_button *button1 = ltk_button_create(window, "I'm also a button!"); - ltk_label *label = ltk_label_create(window, "I'm a label!"); + ltk_widget_id button = ltk_button_create(window, "I'm a button!"); + ltk_widget_id button1 = ltk_button_create(window, "I'm also a button!"); + ltk_widget_id label = ltk_label_create(window, "I'm a label!"); ltk_image *img = ltk_image_create_from_path("test.jpg"); if (!img) { fprintf(stderr, "Unable to load image.\n"); return 1; } - ltk_image_widget *iw = ltk_image_widget_create(window, img); - ltk_entry *entry = ltk_entry_create(window, ""); - ltk_menu *menu = ltk_menu_create(window); - ltk_menuentry *e1 = ltk_menuentry_create(window, "Hi"); - ltk_menuentry *e2 = ltk_menuentry_create(window, "I'm a submenu"); + ltk_widget_id iw = ltk_image_widget_create(window, img); + ltk_widget_id entry = ltk_entry_create(window, ""); + ltk_widget_id menu = ltk_menu_create(window); + ltk_widget_id e1 = ltk_menuentry_create(window, "Hi"); + ltk_widget_id e2 = ltk_menuentry_create(window, "I'm a submenu"); ltk_menu_add_entry(menu, e1); ltk_menu_add_entry(menu, e2); - ltk_menu *submenu = ltk_submenu_create(window); - ltk_menuentry *e3 = ltk_menuentry_create(window, "Menu Entry 1"); - ltk_menuentry *e4 = ltk_menuentry_create(window, "Quit"); + ltk_widget_id submenu = ltk_submenu_create(window); + ltk_widget_id e3 = ltk_menuentry_create(window, "Menu Entry 1"); + ltk_widget_id e4 = ltk_menuentry_create(window, "Quit"); ltk_menu_add_entry(submenu, e3); ltk_menu_add_entry(submenu, e4); ltk_menuentry_attach_submenu(e2, submenu); - ltk_box *box = ltk_box_create(window, LTK_VERTICAL); - ltk_button *btn1 = ltk_button_create(window, "Bla1"); - ltk_button *btn2 = ltk_button_create(window, "Bla2"); - ltk_button *btn3 = ltk_button_create(window, "Bla3"); - ltk_button *btn4 = ltk_button_create(window, "Bla4"); - ltk_button *btn5 = ltk_button_create(window, "Bla5"); - ltk_box_add(box, LTK_CAST_WIDGET(btn1), LTK_STICKY_LEFT); - ltk_box_add(box, LTK_CAST_WIDGET(btn2), LTK_STICKY_LEFT); - ltk_box_add(box, LTK_CAST_WIDGET(btn3), LTK_STICKY_LEFT); - ltk_box_add(box, LTK_CAST_WIDGET(btn4), LTK_STICKY_LEFT); - ltk_box_add(box, LTK_CAST_WIDGET(btn5), LTK_STICKY_LEFT); + ltk_widget_id box = ltk_box_create(window, LTK_VERTICAL); + ltk_widget_id btn1 = ltk_button_create(window, "Bla1"); + ltk_widget_id btn2 = ltk_button_create(window, "Bla2"); + ltk_widget_id btn3 = ltk_button_create(window, "Bla3"); + ltk_widget_id btn4 = ltk_button_create(window, "Bla4"); + ltk_widget_id btn5 = ltk_button_create(window, "Bla5"); + ltk_box_add(box, btn1, LTK_STICKY_LEFT); + ltk_box_add(box, btn2, LTK_STICKY_LEFT); + ltk_box_add(box, btn3, LTK_STICKY_LEFT); + ltk_box_add(box, btn4, LTK_STICKY_LEFT); + ltk_box_add(box, btn5, LTK_STICKY_LEFT); - ltk_checkbutton *cbtn1 = ltk_checkbutton_create(window, "Checkbutton1", 0); - ltk_checkbutton *cbtn2 = ltk_checkbutton_create(window, "Checkbutton2", 1); - ltk_box_add(box, LTK_CAST_WIDGET(cbtn1), LTK_STICKY_LEFT); - ltk_box_add(box, LTK_CAST_WIDGET(cbtn2), LTK_STICKY_LEFT); + ltk_widget_id cbtn1 = ltk_checkbutton_create(window, "Checkbutton1", 0); + ltk_widget_id cbtn2 = ltk_checkbutton_create(window, "Checkbutton2", 1); + ltk_box_add(box, cbtn1, LTK_STICKY_LEFT); + ltk_box_add(box, cbtn2, LTK_STICKY_LEFT); - ltk_radiobutton *rbtn1 = ltk_radiobutton_create(window, "Radiobutton1", 0, NULL); - ltk_radiobutton *rbtn2 = ltk_radiobutton_create(window, "Radiobutton2", 1, rbtn1); - ltk_box_add(box, LTK_CAST_WIDGET(rbtn1), LTK_STICKY_LEFT); - ltk_box_add(box, LTK_CAST_WIDGET(rbtn2), LTK_STICKY_LEFT); + ltk_widget_id rbtn1 = ltk_radiobutton_create(window, "Radiobutton1", 0, LTK_WIDGET_ID_NONE); + ltk_widget_id rbtn2 = ltk_radiobutton_create(window, "Radiobutton2", 1, rbtn1); + ltk_box_add(box, rbtn1, LTK_STICKY_LEFT); + ltk_box_add(box, rbtn2, LTK_STICKY_LEFT); - ltk_combobox *combo = ltk_combobox_create(window); + ltk_widget_id combo = ltk_combobox_create(window); ltk_combobox_add_option(combo, "Option 1"); ltk_combobox_add_option(combo, "Option 2"); ltk_combobox_add_option(combo, "Option 3"); ltk_combobox_add_option(combo, "Option 4"); - ltk_grid_add(grid, LTK_CAST_WIDGET(menu), 0, 0, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT); - ltk_grid_add(grid, LTK_CAST_WIDGET(combo), 0, 1, 1, 1, LTK_STICKY_LEFT); - ltk_grid_add(grid, LTK_CAST_WIDGET(button), 1, 0, 1, 1, LTK_STICKY_LEFT); - ltk_grid_add(grid, LTK_CAST_WIDGET(button1), 1, 1, 1, 1, LTK_STICKY_RIGHT); - ltk_grid_add(grid, LTK_CAST_WIDGET(label), 2, 0, 1, 1, LTK_STICKY_RIGHT); - ltk_grid_add(grid, LTK_CAST_WIDGET(iw), 2, 1, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT|LTK_STICKY_PRESERVE_ASPECT_RATIO); - ltk_grid_add(grid, LTK_CAST_WIDGET(entry), 3, 0, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT); - ltk_grid_add(grid, LTK_CAST_WIDGET(box), 4, 0, 1, 2, LTK_STICKY_LEFT|LTK_STICKY_RIGHT|LTK_STICKY_TOP|LTK_STICKY_BOTTOM); - ltk_window_set_root_widget(window, LTK_CAST_WIDGET(grid)); - ltk_widget_register_signal_handler(LTK_CAST_WIDGET(button), LTK_BUTTON_SIGNAL_PRESSED, &quit, LTK_ARG_VOID); - ltk_widget_register_signal_handler(LTK_CAST_WIDGET(e4), LTK_MENUENTRY_SIGNAL_PRESSED, &quit, LTK_ARG_VOID); - ltk_widget_register_signal_handler(LTK_CAST_WIDGET(button1), LTK_BUTTON_SIGNAL_PRESSED, &printstuff, LTK_MAKE_ARG_INT(5)); - ltk_widget_register_signal_handler(LTK_CAST_WIDGET(window), LTK_WINDOW_SIGNAL_CLOSE, &quit, LTK_ARG_VOID); - ltk_widget_register_signal_handler(LTK_CAST_WIDGET(button1), LTK_WIDGET_SIGNAL_CHANGE_STATE, &printstate, LTK_ARG_VOID); + ltk_grid_add(grid, menu, 0, 0, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT); + ltk_grid_add(grid, combo, 0, 1, 1, 1, LTK_STICKY_LEFT); + ltk_grid_add(grid, button, 1, 0, 1, 1, LTK_STICKY_LEFT); + ltk_grid_add(grid, button1, 1, 1, 1, 1, LTK_STICKY_RIGHT); + ltk_grid_add(grid, label, 2, 0, 1, 1, LTK_STICKY_RIGHT); + ltk_grid_add(grid, iw, 2, 1, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT|LTK_STICKY_PRESERVE_ASPECT_RATIO); + ltk_grid_add(grid, entry, 3, 0, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT); + ltk_grid_add(grid, box, 4, 0, 1, 2, LTK_STICKY_LEFT|LTK_STICKY_RIGHT|LTK_STICKY_TOP|LTK_STICKY_BOTTOM); + ltk_window_set_root_widget(window, grid); + ltk_widget_register_signal_handler(button, LTK_BUTTON_SIGNAL_PRESSED, &quit, LTK_ARG_VOID); + ltk_widget_register_signal_handler(e4, LTK_MENUENTRY_SIGNAL_PRESSED, &quit, LTK_ARG_VOID); + ltk_widget_register_signal_handler(button1, LTK_BUTTON_SIGNAL_PRESSED, &printstuff, LTK_MAKE_ARG_INT(5)); + ltk_widget_register_signal_handler(window, LTK_WINDOW_SIGNAL_CLOSE, &quit, LTK_ARG_VOID); + ltk_widget_register_signal_handler(button1, LTK_WIDGET_SIGNAL_CHANGE_STATE, &printstate, LTK_ARG_VOID); ltk_mainloop(); return 0; diff --git a/src/ltk/array.h b/src/ltk/array.h @@ -1,5 +1,7 @@ +/* FIXME: oops, I guess I left the old MIT license here - not sure if I should just + leave it as is or update it to ISC... */ /* - * Copyright (c) 2020-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2020-2026 lumidify <nobody@lumidify.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -47,20 +49,24 @@ typedef struct { \ size_t len; \ } ltk_array_##name; -#define LTK_ARRAY_INIT_FUNC_DECL_BASE(name, type, storage) \ -LTK_UNUSED_FUNC storage ltk_array_##name *ltk_array_create_##name(size_t initial_capacity); \ -LTK_UNUSED_FUNC storage type ltk_array_pop_##name(ltk_array_##name *ar); \ -LTK_UNUSED_FUNC storage size_t ltk_array_remove_if_##name( \ - ltk_array_##name *ar, int (*callback)(type *, void *), void *data \ -); \ -LTK_UNUSED_FUNC storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len); \ -LTK_UNUSED_FUNC storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len); \ -LTK_UNUSED_FUNC storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size); \ -LTK_UNUSED_FUNC storage void ltk_array_destroy_##name(ltk_array_##name *ar); \ -LTK_UNUSED_FUNC storage void ltk_array_clear_##name(ltk_array_##name *ar); \ -LTK_UNUSED_FUNC storage void ltk_array_append_##name(ltk_array_##name *ar, type elem); \ -LTK_UNUSED_FUNC storage void ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)); \ -LTK_UNUSED_FUNC storage type ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index); \ +#define LTK_ARRAY_INIT_FUNC_DECL_BASE(name, type, storage) \ +LTK_UNUSED_FUNC storage ltk_array_##name *ltk_array_create_##name(size_t initial_capacity); \ +LTK_UNUSED_FUNC storage type ltk_array_pop_##name(ltk_array_##name *ar); \ +LTK_UNUSED_FUNC storage size_t ltk_array_remove_if_##name( \ + ltk_array_##name *ar, int (*callback)(type *, void *), void *data \ +); \ +LTK_UNUSED_FUNC storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len); \ +LTK_UNUSED_FUNC storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type elem); \ +LTK_UNUSED_FUNC storage void ltk_array_insert_multiple_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len); \ +LTK_UNUSED_FUNC storage void ltk_array_delete_##name(ltk_array_##name *ar, size_t index, size_t len); \ +LTK_UNUSED_FUNC storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size, type default_val); \ +LTK_UNUSED_FUNC storage void ltk_array_change_capacity_##name(ltk_array_##name *ar, size_t size); \ +LTK_UNUSED_FUNC storage void ltk_array_len_to_capacity_##name(ltk_array_##name *ar, type default_val); \ +LTK_UNUSED_FUNC storage void ltk_array_destroy_##name(ltk_array_##name *ar); \ +LTK_UNUSED_FUNC storage void ltk_array_reset_##name(ltk_array_##name *ar); \ +LTK_UNUSED_FUNC storage void ltk_array_append_##name(ltk_array_##name *ar, type elem); \ +LTK_UNUSED_FUNC storage void ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)); \ +LTK_UNUSED_FUNC storage type ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index); \ LTK_UNUSED_FUNC storage void ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e); #define LTK_ARRAY_INIT_IMPL_BASE(name, type, storage) \ @@ -102,7 +108,7 @@ LTK_UNUSED_FUNC storage void \ ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) { \ if (index > ar->len) \ ltk_fatal("Array index out of bounds\n"); \ - ltk_array_resize_##name(ar, ar->len + len); \ + ltk_array_change_capacity_##name(ar, ar->len + len); \ ar->len += len; \ if (ar->len - len == index) \ return; \ @@ -111,7 +117,13 @@ ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) { } \ \ LTK_UNUSED_FUNC storage void \ -ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len) { \ +ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type elem) { \ + ltk_array_prepare_gap_##name(ar, index, 1); \ + ar->buf[index] = elem; \ +} \ + \ +LTK_UNUSED_FUNC storage void \ +ltk_array_insert_multiple_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len) { \ ltk_array_prepare_gap_##name(ar, index, len); \ for (size_t i = 0; i < len; i++) { \ ar->buf[index + i] = elem[i]; \ @@ -131,18 +143,27 @@ ltk_array_delete_##name(ltk_array_##name *ar, size_t index, size_t len) { \ LTK_UNUSED_FUNC storage void \ ltk_array_append_##name(ltk_array_##name *ar, type elem) { \ if (ar->len == ar->buf_size) \ - ltk_array_resize_##name(ar, ar->len + 1); \ + ltk_array_change_capacity_##name(ar, ar->len + 1); \ ar->buf[ar->len++] = elem; \ } \ \ LTK_UNUSED_FUNC storage void \ -ltk_array_clear_##name(ltk_array_##name *ar) { \ +ltk_array_reset_##name(ltk_array_##name *ar) { \ ar->len = 0; \ - ltk_array_resize_##name(ar, 1); \ + ltk_array_change_capacity_##name(ar, 1); \ } \ \ LTK_UNUSED_FUNC storage void \ -ltk_array_resize_##name(ltk_array_##name *ar, size_t len) { \ +ltk_array_resize_##name(ltk_array_##name *ar, size_t len, type default_val) { \ + ltk_array_change_capacity_##name(ar, len); \ + for (size_t i = ar->len; i < len; i++) { \ + ar->buf[i] = default_val; \ + } \ + ar->len = len; \ +} \ + \ +LTK_UNUSED_FUNC storage void \ +ltk_array_change_capacity_##name(ltk_array_##name *ar, size_t len) { \ size_t new_size = ideal_array_size(ar->buf_size, len); \ if (new_size != ar->buf_size) { \ ar->buf = ltk_reallocarray(ar->buf, new_size, sizeof(type)); \ @@ -152,6 +173,14 @@ ltk_array_resize_##name(ltk_array_##name *ar, size_t len) { \ } \ \ LTK_UNUSED_FUNC storage void \ +ltk_array_len_to_capacity_##name(ltk_array_##name *ar, type default_val) { \ + for (size_t i = ar->len; i < ar->buf_size; i++) { \ + ar->buf[i] = default_val; \ + } \ + ar->len = ar->buf_size; \ +} \ + \ +LTK_UNUSED_FUNC storage void \ ltk_array_destroy_##name(ltk_array_##name *ar) { \ if (!ar) \ return; \ @@ -183,15 +212,20 @@ ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e) { \ ar->buf[index] = e; \ } +/* FIXME: resize and change_capacity should maybe also take destroy_func optionally if size/capacity is decreased */ #define ltk_array(name) ltk_array_##name #define ltk_array_create(name, initial_capacity) ltk_array_create_##name(initial_capacity) #define ltk_array_pop(name, ar) ltk_array_pop_##name(ar) #define ltk_array_remove_if(name, ar, callback, data) ltk_array_remove_if_##name(ar, callback, data) -#define ltk_array_insert(name, ar, index, elem, len) ltk_array_insert_##name(ar, index, elem, len) +#define ltk_array_insert(name, ar, index, elem) ltk_array_insert_##name(ar, index, elem) +#define ltk_array_insert_multiple(name, ar, index, elem, len) ltk_array_insert_multiple_##name(ar, index, elem, len) #define ltk_array_delete(name, ar, index, len) ltk_array_delete_##name(ar, index, len) -#define ltk_array_resize(name, ar, size) ltk_array_resize_##name(ar, size) +#define ltk_array_resize(name, ar, size, default_val) ltk_array_resize_##name(ar, size, default_val) +#define ltk_array_change_capacity(name, ar, size) ltk_array_change_capacity_##name(ar, size) +#define ltk_array_len_to_capacity(name, ar, default_val) ltk_array_len_to_capacity_##name(ar, default_val) #define ltk_array_destroy(name, ar) ltk_array_destroy_##name(ar) -#define ltk_array_clear(name, ar) ltk_array_clear_##name(ar) +#define ltk_array_reset(name, ar) ltk_array_reset_##name(ar) +#define ltk_array_clear(ar) ((ar)->len = 0) #define ltk_array_append(name, ar, elem) ltk_array_append_##name(ar, elem) #define ltk_array_destroy_deep(name, ar, destroy_func) ltk_array_destroy_deep_##name(ar, destroy_func) #define ltk_array_len(ar) ((ar)->len) diff --git a/src/ltk/box.c b/src/ltk/box.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,28 +26,29 @@ #include "rect.h" #include "scrollbar.h" #include "widget.h" +#include "ltk.h" static void ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); static void ltk_box_destroy(ltk_widget *self, int shallow); static void ltk_recalculate_box(ltk_widget *self); -static void ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget); -static int ltk_box_remove_child(ltk_widget *self, ltk_widget *widget); +static void ltk_box_child_size_change(ltk_widget *self, ltk_widget_id childid); +static int ltk_box_remove_child(ltk_widget *self, ltk_widget_id widgetid); /* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow); */ static int ltk_box_scroll_cb(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data); static int ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event); -static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y); +static ltk_widget_id ltk_box_get_child_at_pos(ltk_widget *self, int x, int y); static void ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r); -static ltk_widget *ltk_box_prev_child(ltk_widget *self, ltk_widget *child); -static ltk_widget *ltk_box_next_child(ltk_widget *self, ltk_widget *child); -static ltk_widget *ltk_box_first_child(ltk_widget *self); -static ltk_widget *ltk_box_last_child(ltk_widget *self); +static ltk_widget_id ltk_box_prev_child(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_box_next_child(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_box_first_child(ltk_widget *self); +static ltk_widget_id ltk_box_last_child(ltk_widget *self); -static ltk_widget *ltk_box_nearest_child(ltk_widget *self, ltk_rect rect); -static ltk_widget *ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget); +static ltk_widget_id ltk_box_nearest_child(ltk_widget *self, ltk_rect rect); +static ltk_widget_id ltk_box_nearest_child_left(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_box_nearest_child_right(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_box_nearest_child_above(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_box_nearest_child_below(ltk_widget *self, ltk_widget_id childid); static void ltk_box_recalc_ideal_size(ltk_widget *self); @@ -87,47 +88,48 @@ static struct ltk_widget_vtable vtable = { static void ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { ltk_box *box = LTK_CAST_BOX(self); - ltk_widget *ptr; /* FIXME: clip out scrollbar */ ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip); - for (size_t i = 0; i < box->num_widgets; i++) { - ptr = box->widgets[i]; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget *ptr = ltk_get_widget_from_id(ltk_array_get(box->widgets, i)); /* FIXME: Maybe continue immediately if widget is obviously outside of clipping rect */ ltk_widget_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, real_clip)); } + ltk_widget *scrollbar = ltk_get_widget_from_id(box->scrollbar); ltk_widget_draw( - LTK_CAST_WIDGET(box->sc), s, - x + box->sc->widget.lrect.x, - y + box->sc->widget.lrect.y, - ltk_rect_relative(box->sc->widget.lrect, real_clip) + scrollbar, s, + x + scrollbar->lrect.x, + y + scrollbar->lrect.y, + ltk_rect_relative(scrollbar->lrect, real_clip) ); } -ltk_box * -ltk_box_create(ltk_window *window, ltk_orientation orient) { +ltk_widget_id +ltk_box_create(ltk_widget_id windowid, ltk_orientation orient) { ltk_box *box = ltk_malloc(sizeof(ltk_box)); ltk_widget *self = LTK_CAST_WIDGET(box); - ltk_fill_widget_defaults(self, window, &vtable, 0, 0); + ltk_widget_id id = ltk_initialize_widget(self, windowid, &vtable, 0, 0); - box->sc = ltk_scrollbar_create(window, orient); - box->sc->widget.parent = self; + box->scrollbar = ltk_scrollbar_create(windowid, orient); + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + scwidget->parent = id; + /* FIXM: separate callback function that takes widget ID that can be used safely externally + (e.g. if an independent scrollbar should be associated with the box) */ ltk_widget_register_signal_handler( - LTK_CAST_WIDGET(box->sc), LTK_SCROLLBAR_SIGNAL_SCROLL, - &ltk_box_scroll_cb, LTK_MAKE_ARG_WIDGET(self) + box->scrollbar, LTK_SCROLLBAR_SIGNAL_SCROLL, + &ltk_box_scroll_cb, LTK_MAKE_ARG_WIDGET_ID(self->id) ); - box->widgets = NULL; - box->num_alloc = 0; - box->num_widgets = 0; + box->widgets = ltk_array_create(widget_id, 1); box->orient = orient; if (orient == LTK_HORIZONTAL) - box->widget.ideal_h = box->sc->widget.ideal_h; + self->ideal_h = scwidget->ideal_h; else - box->widget.ideal_w = box->sc->widget.ideal_w; + self->ideal_w = scwidget->ideal_w; ltk_recalculate_box(self); - return box; + return id; } static void @@ -146,22 +148,22 @@ ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r) { delta = r.y; } if (delta) - ltk_scrollbar_scroll(LTK_CAST_WIDGET(box->sc), delta, 0); + ltk_scrollbar_scroll(box->scrollbar, delta, 0); } static void ltk_box_destroy(ltk_widget *self, int shallow) { ltk_box *box = LTK_CAST_BOX(self); - ltk_widget *ptr; - for (size_t i = 0; i < box->num_widgets; i++) { - ptr = box->widgets[i]; - ptr->parent = NULL; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget *ptr = ltk_get_widget_from_id(ltk_array_get(box->widgets, i)); + ptr->parent = LTK_WIDGET_ID_NONE; if (!shallow) ltk_widget_destroy(ptr, shallow); } - ltk_free(box->widgets); - box->sc->widget.parent = NULL; - ltk_widget_destroy(LTK_CAST_WIDGET(box->sc), 0); + ltk_array_destroy(widget_id, box->widgets); + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + scwidget->parent = LTK_WIDGET_ID_NONE; + ltk_widget_destroy(scwidget, 0); ltk_free(box); } @@ -173,65 +175,65 @@ ltk_box_destroy(ltk_widget *self, int shallow) { static void ltk_recalculate_box(ltk_widget *self) { ltk_box *box = LTK_CAST_BOX(self); - ltk_widget *ptr; - ltk_rect *sc_rect = &box->sc->widget.lrect; + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(scwidget); + ltk_rect *sc_rect = &scwidget->lrect; int cur_pos = 0; if (box->orient == LTK_HORIZONTAL) - sc_rect->h = box->sc->widget.ideal_h; + sc_rect->h = scwidget->ideal_h; else - sc_rect->w = box->sc->widget.ideal_w; - for (size_t i = 0; i < box->num_widgets; i++) { - ptr = box->widgets[i]; + sc_rect->w = scwidget->ideal_w; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget *ptr = ltk_get_widget_from_id(ltk_array_get(box->widgets, i)); ptr->lrect.w = ptr->ideal_w; ptr->lrect.h = ptr->ideal_h; if (box->orient == LTK_HORIZONTAL) { - ptr->lrect.x = cur_pos - box->sc->cur_pos; + ptr->lrect.x = cur_pos - sc->cur_pos; if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) - ptr->lrect.h = box->widget.lrect.h - sc_rect->h; + ptr->lrect.h = self->lrect.h - sc_rect->h; if (ptr->sticky & LTK_STICKY_TOP) ptr->lrect.y = 0; else if (ptr->sticky & LTK_STICKY_BOTTOM) - ptr->lrect.y = box->widget.lrect.h - ptr->lrect.h - sc_rect->h; + ptr->lrect.y = self->lrect.h - ptr->lrect.h - sc_rect->h; else - ptr->lrect.y = (box->widget.lrect.h - ptr->lrect.h) / 2; + ptr->lrect.y = (self->lrect.h - ptr->lrect.h) / 2; cur_pos += ptr->lrect.w; } else { - ptr->lrect.y = cur_pos - box->sc->cur_pos; + ptr->lrect.y = cur_pos - sc->cur_pos; if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT) - ptr->lrect.w = box->widget.lrect.w - sc_rect->w; + ptr->lrect.w = self->lrect.w - sc_rect->w; if (ptr->sticky & LTK_STICKY_LEFT) ptr->lrect.x = 0; else if (ptr->sticky & LTK_STICKY_RIGHT) - ptr->lrect.x = box->widget.lrect.w - ptr->lrect.w - sc_rect->w; + ptr->lrect.x = self->lrect.w - ptr->lrect.w - sc_rect->w; else - ptr->lrect.x = (box->widget.lrect.w - ptr->lrect.w) / 2; + ptr->lrect.x = (self->lrect.w - ptr->lrect.w) / 2; cur_pos += ptr->lrect.h; } ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect); ltk_widget_resize(ptr); } - ltk_scrollbar_set_virtual_size(box->sc, cur_pos); + ltk_scrollbar_set_virtual_size(box->scrollbar, cur_pos); if (box->orient == LTK_HORIZONTAL) { sc_rect->x = 0; - sc_rect->y = box->widget.lrect.h - sc_rect->h; - sc_rect->w = box->widget.lrect.w; + sc_rect->y = self->lrect.h - sc_rect->h; + sc_rect->w = self->lrect.w; } else { - sc_rect->x = box->widget.lrect.w - sc_rect->w; + sc_rect->x = self->lrect.w - sc_rect->w; sc_rect->y = 0; - sc_rect->h = box->widget.lrect.h; + sc_rect->h = self->lrect.h; } - *sc_rect = ltk_rect_intersect(*sc_rect, (ltk_rect){0, 0, box->widget.lrect.w, box->widget.lrect.h}); - box->sc->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, *sc_rect); - ltk_widget_resize(LTK_CAST_WIDGET(box->sc)); + *sc_rect = ltk_rect_intersect(*sc_rect, (ltk_rect){0, 0, self->lrect.w, self->lrect.h}); + scwidget->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, *sc_rect); + ltk_widget_resize(scwidget); } static void ltk_box_recalc_ideal_size(ltk_widget *self) { ltk_box *box = LTK_CAST_BOX(self); - ltk_widget *ptr; self->ideal_w = self->ideal_h = 0; - for (size_t i = 0; i < box->num_widgets; i++) { - ptr = box->widgets[i]; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget *ptr = ltk_get_widget_from_id(ltk_array_get(box->widgets, i)); ltk_widget_recalc_ideal_size(ptr); if (box->orient == LTK_HORIZONTAL && ptr->ideal_h > self->ideal_h) { self->ideal_h = ptr->ideal_h; @@ -241,11 +243,12 @@ ltk_box_recalc_ideal_size(ltk_widget *self) { self->ideal_h += ptr->ideal_h; } } - ltk_widget_recalc_ideal_size(LTK_CAST_WIDGET(box->sc)); + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + ltk_widget_recalc_ideal_size(scwidget); if (box->orient == LTK_HORIZONTAL) - self->ideal_h += box->sc->widget.ideal_h; + self->ideal_h += scwidget->ideal_h; else if (box->orient == LTK_VERTICAL) - self->ideal_w += box->sc->widget.ideal_w; + self->ideal_w += scwidget->ideal_w; } /* FIXME: This entire resizing thing is a bit weird. For instance, if a label @@ -254,10 +257,14 @@ ltk_box_recalc_ideal_size(ltk_widget *self) { ideal size? Ideal size wouldn't really make sense here, but then the box might be forced to add a scrollbar even though the parent widget would actually give it more space if it knew that it needed it. */ +/* In the case with the label, there would need to be a way to say what the ideal + height is, given the current width, but I guess that eventually brings us to + a constraint solver, which I'd like to avoid... */ static void -ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) { +ltk_box_child_size_change(ltk_widget *self, ltk_widget_id childid) { ltk_box *box = LTK_CAST_BOX(self); + ltk_widget *child = ltk_get_widget_from_id(childid); short size_changed = 0; /* This is always reset here - if it needs to be changed, the resize function called by the last child_size_change @@ -270,217 +277,231 @@ ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) { could also set all widgets even if they don't have any sticky settings, but there'd probably be some catch as well. */ /* FIXME: the same comment as in grid.c applies */ - int orig_w = widget->lrect.w; - int orig_h = widget->lrect.h; - widget->lrect.w = widget->ideal_w; - widget->lrect.h = widget->ideal_h; - int sc_w = box->sc->widget.lrect.w; - int sc_h = box->sc->widget.lrect.h; - if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h > box->widget.ideal_h) { - box->widget.ideal_h = widget->ideal_h + sc_h; + int orig_w = child->lrect.w; + int orig_h = child->lrect.h; + child->lrect.w = child->ideal_w; + child->lrect.h = child->ideal_h; + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + int sc_w = scwidget->lrect.w; + int sc_h = scwidget->lrect.h; + if (box->orient == LTK_HORIZONTAL && child->ideal_h + sc_h > self->ideal_h) { + self->ideal_h = child->ideal_h + sc_h; size_changed = 1; - } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w > box->widget.ideal_h) { - box->widget.ideal_w = widget->ideal_w + sc_w; + } else if (box->orient == LTK_VERTICAL && child->ideal_w + sc_w > self->ideal_h) { + self->ideal_w = child->ideal_w + sc_w; size_changed = 1; } - if (size_changed && box->widget.parent && box->widget.parent->vtable->child_size_change) - box->widget.parent->vtable->child_size_change(box->widget.parent, (ltk_widget *)box); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (size_changed && parent && parent->vtable->child_size_change) + parent->vtable->child_size_change(parent, self->id); else - ltk_recalculate_box(LTK_CAST_WIDGET(box)); - if (orig_w != widget->lrect.w || orig_h != widget->lrect.h) - ltk_widget_resize(widget); + ltk_recalculate_box(self); + if (orig_w != child->lrect.w || orig_h != child->lrect.h) + ltk_widget_resize(child); } int -ltk_box_add(ltk_box *box, ltk_widget *widget, ltk_sticky_mask sticky) { - if (widget->parent) +ltk_box_add(ltk_widget_id boxid, ltk_widget_id widgetid, ltk_sticky_mask sticky) { + ltk_widget *self = ltk_get_widget_from_id(boxid); + ltk_box *box = LTK_CAST_BOX(self); + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + if (!LTK_WIDGET_ID_IS_NONE(widget->parent)) return 1; - if (box->num_widgets >= box->num_alloc) { - size_t new_size = box->num_alloc > 0 ? box->num_alloc * 2 : 4; - ltk_widget **new = ltk_realloc(box->widgets, new_size * sizeof(ltk_widget *)); - box->num_alloc = new_size; - box->widgets = new; - } ltk_widget_recalc_ideal_size(widget); - int sc_w = box->sc->widget.lrect.w; - int sc_h = box->sc->widget.lrect.h; + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + int sc_w = scwidget->lrect.w; + int sc_h = scwidget->lrect.h; - box->widgets[box->num_widgets++] = widget; + ltk_array_append(widget_id, box->widgets, widgetid); if (box->orient == LTK_HORIZONTAL) { - box->widget.ideal_w += widget->ideal_w; - if (widget->ideal_h + sc_h > box->widget.ideal_h) - box->widget.ideal_h = widget->ideal_h + sc_h; + self->ideal_w += widget->ideal_w; + if (widget->ideal_h + sc_h > self->ideal_h) + self->ideal_h = widget->ideal_h + sc_h; } else { - box->widget.ideal_h += widget->ideal_h; - if (widget->ideal_w + sc_w > box->widget.ideal_w) - box->widget.ideal_w = widget->ideal_w + sc_w; + self->ideal_h += widget->ideal_h; + if (widget->ideal_w + sc_w > self->ideal_w) + self->ideal_w = widget->ideal_w + sc_w; } - widget->parent = LTK_CAST_WIDGET(box); + widget->parent = self->id; widget->sticky = sticky; - ltk_box_child_size_change(LTK_CAST_WIDGET(box), widget); - ltk_window_invalidate_widget_rect(box->widget.window, LTK_CAST_WIDGET(box)); + ltk_box_child_size_change(self, widgetid); + ltk_window_invalidate_widget_rect(self->window, self->id); return 0; } int -ltk_box_remove_index(ltk_box *box, size_t index) { - if (index >= box->num_widgets) +ltk_box_remove_index(ltk_widget_id boxid, size_t index) { + ltk_widget *self = ltk_get_widget_from_id(boxid); + ltk_box *box = LTK_CAST_BOX(self); + if (index >= ltk_array_len(box->widgets)) return 1; - ltk_widget *self = LTK_CAST_WIDGET(box); - ltk_widget *widget = box->widgets[index]; - int sc_w = box->sc->widget.lrect.w; - int sc_h = box->sc->widget.lrect.h; - if (index < box->num_widgets - 1) - memmove(box->widgets + index, box->widgets + index + 1, - (box->num_widgets - index - 1) * sizeof(ltk_widget *)); - box->num_widgets--; - ltk_window_invalidate_widget_rect(self->window, self); + ltk_widget *widget = ltk_get_widget_from_id(ltk_array_get(box->widgets, index)); + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + int sc_w = scwidget->lrect.w; + int sc_h = scwidget->lrect.h; + ltk_array_delete(widget_id, box->widgets, index, 1); + ltk_window_invalidate_widget_rect(self->window, self->id); /* search for new ideal width/height */ /* FIXME: make this all a bit nicer and break the lines better */ /* FIXME: other part of ideal size not updated */ if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h == self->ideal_h) { self->ideal_h = 0; - for (size_t j = 0; j < box->num_widgets; j++) { - if (box->widgets[j]->ideal_h + sc_h > self->ideal_h) - self->ideal_h = box->widgets[j]->ideal_h + sc_h; + for (size_t j = 0; j < ltk_array_len(box->widgets); j++) { + ltk_widget *w = ltk_get_widget_from_id(ltk_array_get(box->widgets, j)); + if (w->ideal_h + sc_h > self->ideal_h) + self->ideal_h = w->ideal_h + sc_h; } - if (self->parent) - ltk_widget_resize(self->parent); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (parent) + ltk_widget_resize(parent); } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w == self->ideal_w) { self->ideal_w = 0; - for (size_t j = 0; j < box->num_widgets; j++) { - if (box->widgets[j]->ideal_w + sc_w > self->ideal_w) - self->ideal_w = box->widgets[j]->ideal_w + sc_w; + for (size_t j = 0; j < ltk_array_len(box->widgets); j++) { + ltk_widget *w = ltk_get_widget_from_id(ltk_array_get(box->widgets, j)); + if (w->ideal_w + sc_w > self->ideal_w) + self->ideal_w = w->ideal_w + sc_w; } - if (self->parent) - ltk_widget_resize(self->parent); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (parent) + ltk_widget_resize(parent); } return 0; } int -ltk_box_remove(ltk_box *box, ltk_widget *widget) { - if (widget->parent != LTK_CAST_WIDGET(box)) +ltk_box_remove(ltk_widget_id boxid, ltk_widget_id widgetid) { + ltk_widget *self = ltk_get_widget_from_id(boxid); + ltk_box *box = LTK_CAST_BOX(self); + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + if (!LTK_WIDGET_ID_EQUAL(widget->parent, self->id)) return 1; - widget->parent = NULL; - for (size_t i = 0; i < box->num_widgets; i++) { - if (box->widgets[i] == widget) { - return ltk_box_remove_index(box, i); - } + widget->parent = LTK_WIDGET_ID_NONE; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget_id id = ltk_array_get(box->widgets, i); + if (LTK_WIDGET_ID_EQUAL(id, widgetid)) + return ltk_box_remove_index(boxid, i); } return 1; } static int -ltk_box_remove_child(ltk_widget *self, ltk_widget *widget) { - return ltk_box_remove(LTK_CAST_BOX(self), widget); +ltk_box_remove_child(ltk_widget *self, ltk_widget_id widgetid) { + return ltk_box_remove(self->id, widgetid); } /* FIXME: maybe come up with a more efficient method */ -static ltk_widget * +static ltk_widget_id ltk_box_nearest_child(ltk_widget *self, ltk_rect rect) { ltk_box *box = LTK_CAST_BOX(self); - ltk_widget *minw = NULL; + ltk_widget_id minw = LTK_WIDGET_ID_NONE; int min_dist = INT_MAX; - for (size_t i = 0; i < box->num_widgets; i++) { - ltk_rect r = box->widgets[i]->lrect; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget_id id = ltk_array_get(box->widgets, i); + ltk_widget *widget = ltk_get_widget_from_id(id); + ltk_rect r = widget->lrect; int dist = ltk_rect_fakedist(rect, r); if (dist < min_dist) { min_dist = dist; - minw = box->widgets[i]; + minw = id; } } return minw; } -static ltk_widget * -ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_box_nearest_child_left(ltk_widget *self, ltk_widget_id childid) { ltk_box *box = LTK_CAST_BOX(self); if (box->orient == LTK_VERTICAL) - return NULL; - return ltk_box_prev_child(self, widget); + return LTK_WIDGET_ID_NONE; + return ltk_box_prev_child(self, childid); } -static ltk_widget * -ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_box_nearest_child_right(ltk_widget *self, ltk_widget_id childid) { ltk_box *box = LTK_CAST_BOX(self); if (box->orient == LTK_VERTICAL) - return NULL; - return ltk_box_next_child(self, widget); + return LTK_WIDGET_ID_NONE; + return ltk_box_next_child(self, childid); } -static ltk_widget * -ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_box_nearest_child_above(ltk_widget *self, ltk_widget_id childid) { ltk_box *box = LTK_CAST_BOX(self); if (box->orient == LTK_HORIZONTAL) - return NULL; - return ltk_box_prev_child(self, widget); + return LTK_WIDGET_ID_NONE; + return ltk_box_prev_child(self, childid); } -static ltk_widget * -ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_box_nearest_child_below(ltk_widget *self, ltk_widget_id childid) { ltk_box *box = LTK_CAST_BOX(self); if (box->orient == LTK_HORIZONTAL) - return NULL; - return ltk_box_next_child(self, widget); + return LTK_WIDGET_ID_NONE; + return ltk_box_next_child(self, childid); } -static ltk_widget * -ltk_box_prev_child(ltk_widget *self, ltk_widget *child) { +static ltk_widget_id +ltk_box_prev_child(ltk_widget *self, ltk_widget_id childid) { ltk_box *box = LTK_CAST_BOX(self); - for (size_t i = box->num_widgets; i-- > 0;) { - if (box->widgets[i] == child) - return i > 0 ? box->widgets[i-1] : NULL; + for (size_t i = ltk_array_len(box->widgets); i-- > 0;) { + ltk_widget_id id = ltk_array_get(box->widgets, i); + if (LTK_WIDGET_ID_EQUAL(id, childid)) + return i > 0 ? ltk_array_get(box->widgets, i - 1) : LTK_WIDGET_ID_NONE; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * -ltk_box_next_child(ltk_widget *self, ltk_widget *child) { +static ltk_widget_id +ltk_box_next_child(ltk_widget *self, ltk_widget_id childid) { ltk_box *box = LTK_CAST_BOX(self); - for (size_t i = 0; i < box->num_widgets; i++) { - if (box->widgets[i] == child) - return i < box->num_widgets - 1 ? box->widgets[i+1] : NULL; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget_id id = ltk_array_get(box->widgets, i); + if (LTK_WIDGET_ID_EQUAL(id, childid)) + return i < ltk_array_len(box->widgets) - 1 ? ltk_array_get(box->widgets, i + 1) : LTK_WIDGET_ID_NONE; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_box_first_child(ltk_widget *self) { ltk_box *box = LTK_CAST_BOX(self); - return box->num_widgets > 0 ? box->widgets[0] : NULL; + return ltk_array_len(box->widgets) > 0 ? ltk_array_get(box->widgets, 0) : LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_box_last_child(ltk_widget *self) { ltk_box *box = LTK_CAST_BOX(self); - return box->num_widgets > 0 ? box->widgets[box->num_widgets-1] : NULL; + size_t len = ltk_array_len(box->widgets); + return len > 0 ? ltk_array_get(box->widgets, len - 1) : LTK_WIDGET_ID_NONE; } static int ltk_box_scroll_cb(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) { (void)self; (void)args; - ltk_widget *boxw = LTK_CAST_ARG_WIDGET(data); + ltk_widget_id boxid = LTK_CAST_ARG_WIDGET_ID(data); + ltk_widget *boxw = ltk_get_widget_from_id(boxid); ltk_recalculate_box(boxw); - ltk_window_invalidate_widget_rect(boxw->window, boxw); + ltk_window_invalidate_widget_rect(boxw->window, boxw->id); return 1; } -static ltk_widget * +static ltk_widget_id ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) { ltk_box *box = LTK_CAST_BOX(self); - if (ltk_collide_rect(box->sc->widget.crect, x, y)) - return (ltk_widget *)box->sc; - for (size_t i = 0; i < box->num_widgets; i++) { - if (ltk_collide_rect(box->widgets[i]->crect, x, y)) - return box->widgets[i]; + ltk_widget *scwidget = ltk_get_widget_from_id(box->scrollbar); + if (ltk_collide_rect(scwidget->crect, x, y)) + return box->scrollbar; + for (size_t i = 0; i < ltk_array_len(box->widgets); i++) { + ltk_widget *widget = ltk_get_widget_from_id(ltk_array_get(box->widgets, i)); + if (ltk_collide_rect(widget->crect, x, y)) + return widget->id; } - return NULL; + return LTK_WIDGET_ID_NONE; } static int @@ -490,7 +511,7 @@ ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) { /* FIXME: horizontal scrolling, etc. */ /* FIXME: configure scrollstep */ int delta = event->dy * -15; - ltk_scrollbar_scroll(LTK_CAST_WIDGET(box->sc), delta, 0); + ltk_scrollbar_scroll(box->scrollbar, delta, 0); ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y); ltk_window_fake_motion_event(self->window, glob.x, glob.y); return 1; diff --git a/src/ltk/box.h b/src/ltk/box.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,20 +24,20 @@ #define LTK_BOX_SIGNAL_INVALID -1 +/* FIXME: allow to associate any scrollbar with box (maybe even multiple?) */ +/* -> can be done using signals without any special handling? */ typedef struct { ltk_widget widget; - ltk_scrollbar *sc; - ltk_widget *pressed_widget; - ltk_widget *active_widget; - ltk_widget **widgets; - size_t num_alloc; - size_t num_widgets; + ltk_widget_id scrollbar; + ltk_widget_id pressed_widget; + ltk_widget_id active_widget; + ltk_array(widget_id) *widgets; ltk_orientation orient; } ltk_box; -ltk_box *ltk_box_create(ltk_window *window, ltk_orientation orient); -int ltk_box_add(ltk_box *box, ltk_widget *widget, ltk_sticky_mask sticky); -int ltk_box_remove(ltk_box *box, ltk_widget *widget); -int ltk_box_remove_index(ltk_box *box, size_t index); +ltk_widget_id ltk_box_create(ltk_widget_id windowid, ltk_orientation orient); +int ltk_box_add(ltk_widget_id boxid, ltk_widget_id widgetid, ltk_sticky_mask sticky); +int ltk_box_remove(ltk_widget_id boxid, ltk_widget_id widgetid); +int ltk_box_remove_index(ltk_widget_id boxid, size_t index); #endif /* LTK_BOX_H */ diff --git a/src/ltk/button.c b/src/ltk/button.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -155,18 +155,19 @@ ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect static int ltk_button_release(ltk_widget *self) { - ltk_widget_emit_signal(self, LTK_BUTTON_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_BUTTON_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST); return 1; } static void recalc_ideal_size(ltk_button *button) { + ltk_widget *self = LTK_CAST_WIDGET(button); int text_w, text_h; ltk_text_line_get_size(button->tl, &text_w, &text_h); - int bw = ltk_size_to_pixel(theme.border_width, LTK_CAST_WIDGET(button)->last_dpi); - int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(button)->last_dpi); - button->widget.ideal_w = text_w + bw * 2 + pad * 2; - button->widget.ideal_h = text_h + bw * 2 + pad * 2; + int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi); + int pad = ltk_size_to_pixel(theme.pad, self->last_dpi); + self->ideal_w = text_w + bw * 2 + pad * 2; + self->ideal_h = text_h + bw * 2 + pad * 2; } static void @@ -177,28 +178,24 @@ ltk_button_recalc_ideal_size(ltk_widget *self) { recalc_ideal_size(button); } -ltk_button * -ltk_button_create(ltk_window *window, const char *text) { +ltk_widget_id +ltk_button_create(ltk_widget_id windowid, const char *text) { ltk_button *button = ltk_malloc(sizeof(ltk_button)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, 0, 0); + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(button), windowid, &vtable, 0, 0); button->tl = ltk_text_line_create_const_text_default( - theme.font, ltk_size_to_pixel(theme.font_size, button->widget.last_dpi), text, -1 + theme.font, ltk_size_to_pixel(theme.font_size, LTK_CAST_WIDGET(button)->last_dpi), text, -1 ); recalc_ideal_size(button); - button->widget.dirty = 1; + LTK_CAST_WIDGET(button)->dirty = 1; - return button; + return id; } static void ltk_button_destroy(ltk_widget *self, int shallow) { (void)shallow; ltk_button *button = LTK_CAST_BUTTON(self); - if (!button) { - ltk_warn("Tried to destroy NULL button.\n"); - return; - } ltk_text_line_destroy(button->tl); ltk_free(button); } diff --git a/src/ltk/button.h b/src/ltk/button.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,6 +29,6 @@ typedef struct { ltk_text_line *tl; } ltk_button; -ltk_button *ltk_button_create(ltk_window *window, const char *text); +ltk_widget_id ltk_button_create(ltk_widget_id windowid, const char *text); #endif /* LTK_BUTTON_H */ diff --git a/src/ltk/checkbutton.c b/src/ltk/checkbutton.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2024-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -206,33 +206,37 @@ static int ltk_checkbutton_release(ltk_widget *self) { ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self); button->checked = !button->checked; - ltk_widget_emit_signal(self, LTK_CHECKBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_CHECKBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); return 1; } int -ltk_checkbutton_get_checked(ltk_checkbutton *button) { +ltk_checkbutton_get_checked(ltk_widget_id buttonid) { + ltk_widget *self = ltk_get_widget_from_id(buttonid); + ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self); return button->checked; } void -ltk_checkbutton_set_checked(ltk_checkbutton *button, int checked) { +ltk_checkbutton_set_checked(ltk_widget_id buttonid, int checked) { + ltk_widget *self = ltk_get_widget_from_id(buttonid); + ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self); button->checked = checked; - ltk_widget *self = LTK_CAST_WIDGET(button); - ltk_window_invalidate_widget_rect(self->window, self); - ltk_widget_emit_signal(self, LTK_CHECKBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); + ltk_window_invalidate_widget_rect(self->window, self->id); + ltk_widget_emit_signal(self->id, LTK_CHECKBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); } #define MAX(a, b) ((a) > (b) ? (a) : (b)) static void recalc_ideal_size(ltk_checkbutton *button) { + ltk_widget *self = LTK_CAST_WIDGET(button); int text_w, text_h; ltk_text_line_get_size(button->tl, &text_w, &text_h); - int box_size = ltk_size_to_pixel(theme.box_size, LTK_CAST_WIDGET(button)->last_dpi); - int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(button)->last_dpi); - button->widget.ideal_w = text_w + pad * 3 + box_size; - button->widget.ideal_h = MAX(text_h, box_size) + pad * 2; + int box_size = ltk_size_to_pixel(theme.box_size, self->last_dpi); + int pad = ltk_size_to_pixel(theme.pad, self->last_dpi); + self->ideal_w = text_w + pad * 3 + box_size; + self->ideal_h = MAX(text_h, box_size) + pad * 2; } static void @@ -243,29 +247,25 @@ ltk_checkbutton_recalc_ideal_size(ltk_widget *self) { recalc_ideal_size(button); } -ltk_checkbutton * -ltk_checkbutton_create(ltk_window *window, const char *text, int checked) { +ltk_widget_id +ltk_checkbutton_create(ltk_widget_id windowid, const char *text, int checked) { ltk_checkbutton *button = ltk_malloc(sizeof(ltk_checkbutton)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, 0, 0); + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(button), windowid, &vtable, 0, 0); button->checked = checked; button->tl = ltk_text_line_create_const_text_default( theme.font, ltk_size_to_pixel(theme.font_size, button->widget.last_dpi), text, -1 ); recalc_ideal_size(button); - button->widget.dirty = 1; + LTK_CAST_WIDGET(button)->dirty = 1; - return button; + return id; } static void ltk_checkbutton_destroy(ltk_widget *self, int shallow) { (void)shallow; ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self); - if (!button) { - ltk_warn("Tried to destroy NULL checkbutton.\n"); - return; - } ltk_text_line_destroy(button->tl); ltk_free(button); } diff --git a/src/ltk/checkbutton.h b/src/ltk/checkbutton.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2024-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -30,8 +30,8 @@ typedef struct { int checked; } ltk_checkbutton; -ltk_checkbutton *ltk_checkbutton_create(ltk_window *window, const char *text, int checked); -int ltk_checkbutton_get_checked(ltk_checkbutton *button); -void ltk_checkbutton_set_checked(ltk_checkbutton *button, int checked); +ltk_widget_id ltk_checkbutton_create(ltk_widget_id windowid, const char *text, int checked); +int ltk_checkbutton_get_checked(ltk_widget_id buttonid); +void ltk_checkbutton_set_checked(ltk_widget_id buttonid, int checked); #endif /* LTK_CHECKBUTTON_H */ diff --git a/src/ltk/combobox.c b/src/ltk/combobox.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2024-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -37,9 +37,9 @@ static void ltk_combobox_draw(ltk_widget *self, ltk_surface *draw_surf, int x, i static int ltk_combobox_release(ltk_widget *self); static void ltk_combobox_destroy(ltk_widget *self, int shallow); static void ltk_combobox_recalc_ideal_size(ltk_widget *self); -static int ltk_combobox_remove_child(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_combobox_get_child(ltk_widget *self); -static ltk_widget *ltk_combobox_nearest_child(ltk_widget *self, ltk_rect rect); +static int ltk_combobox_remove_child(ltk_widget *self, ltk_widget_id widgetid); +static ltk_widget_id ltk_combobox_get_child(ltk_widget *self); +static ltk_widget_id ltk_combobox_nearest_child(ltk_widget *self, ltk_rect rect); static int ltk_combobox_key_press(ltk_widget *self, ltk_key_event *event); static int choose_external(ltk_widget *self, ltk_key_event *event); static void ltk_combobox_cmd_return(ltk_widget *self, char *text, size_t len); @@ -215,19 +215,23 @@ ltk_combobox_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_re /* FIXME: this is kind of ugly because it uses a lot of internal knowledge about menus */ static void popup_dropdown(ltk_combobox *combobox) { - if (!combobox->dropdown || ltk_menu_get_num_entries(combobox->dropdown) == 0) + ltk_widget *self = LTK_CAST_WIDGET(combobox); + ltk_widget *dropdownw = ltk_get_widget_or_null_from_id(combobox->dropdown); + if (!dropdownw || ltk_menu_get_num_entries(combobox->dropdown) == 0) return; - ltk_rect combo_rect = LTK_CAST_WIDGET(combobox)->lrect; - ltk_point combo_global = ltk_widget_pos_to_global(LTK_CAST_WIDGET(combobox), 0, 0); - - int win_w = LTK_CAST_WIDGET(combobox)->window->rect.w; - int win_h = LTK_CAST_WIDGET(combobox)->window->rect.h; - ltk_menu *dropdown = combobox->dropdown; - ltk_widget_recalc_ideal_size(LTK_CAST_WIDGET(dropdown)); - int ideal_w = dropdown->widget.ideal_w; - int ideal_h = dropdown->widget.ideal_h; + ltk_menu *dropdown = LTK_CAST_MENU(dropdownw); + ltk_rect combo_rect = self->lrect; + ltk_point combo_global = ltk_widget_pos_to_global(self, 0, 0); + + ltk_widget *windoww = ltk_get_widget_from_id(self->window); + ltk_window *window = LTK_CAST_WINDOW(windoww); + int win_w = window->rect.w; + int win_h = window->rect.h; + ltk_widget_recalc_ideal_size(dropdownw); + int ideal_w = dropdownw->ideal_w; + int ideal_h = dropdownw->ideal_h; int x_final = 0, y_final = 0, w_final = ideal_w, h_final = ideal_h; - int combo_bw = ltk_size_to_pixel(theme.border_width, LTK_CAST_WIDGET(combobox)->last_dpi); + int combo_bw = ltk_size_to_pixel(theme.border_width, self->last_dpi); int space_top = combo_global.y; int space_bottom = win_h - (combo_global.y + combo_rect.h); @@ -267,24 +271,25 @@ popup_dropdown(ltk_combobox *combobox) { dropdown->x_scroll_offset = dropdown->y_scroll_offset = 0; dropdown->scroll_top_hover = dropdown->scroll_bottom_hover = 0; dropdown->scroll_left_hover = dropdown->scroll_right_hover = 0; - dropdown->widget.lrect.x = x_final; - dropdown->widget.lrect.y = y_final; - dropdown->widget.lrect.w = w_final; - dropdown->widget.lrect.h = h_final; - dropdown->widget.crect = LTK_CAST_WIDGET(dropdown)->lrect; - dropdown->widget.dirty = 1; - dropdown->widget.hidden = 0; + dropdownw->lrect.x = x_final; + dropdownw->lrect.y = y_final; + dropdownw->lrect.w = w_final; + dropdownw->lrect.h = h_final; + dropdownw->crect = LTK_CAST_WIDGET(dropdown)->lrect; + dropdownw->dirty = 1; + dropdownw->hidden = 0; dropdown->popup_submenus = 0; dropdown->unpopup_submenus_on_hide = 1; - ltk_widget_resize(LTK_CAST_WIDGET(dropdown)); - ltk_window_register_popup(LTK_CAST_WIDGET(combobox)->window, LTK_CAST_WIDGET(dropdown)); - ltk_window_invalidate_widget_rect(LTK_CAST_WIDGET(dropdown)->window, LTK_CAST_WIDGET(dropdown)); + ltk_widget_resize(dropdownw); + ltk_window_register_popup(self->window, combobox->dropdown); + ltk_window_invalidate_widget_rect(dropdownw->window, combobox->dropdown); } static void unpopup_dropdown(ltk_combobox *combobox) { - if (combobox->dropdown && !LTK_CAST_WIDGET(combobox->dropdown)->hidden) { - ltk_widget_hide(LTK_CAST_WIDGET(combobox->dropdown)); + ltk_widget *dropdownw = ltk_get_widget_or_null_from_id(combobox->dropdown); + if (dropdownw && !dropdownw->hidden) { + ltk_widget_hide(dropdownw); } } @@ -294,9 +299,10 @@ unpopup_dropdown(ltk_combobox *combobox) { static int ltk_combobox_release(ltk_widget *self) { ltk_combobox *combo = LTK_CAST_COMBOBOX(self); - if (!combo->dropdown) + ltk_widget *dropdownw = ltk_get_widget_or_null_from_id(combo->dropdown); + if (!dropdownw) return 0; - if (combo->dropdown->widget.hidden) + if (dropdownw->hidden) popup_dropdown(combo); else unpopup_dropdown(combo); @@ -307,12 +313,13 @@ ltk_combobox_release(ltk_widget *self) { static void recalc_ideal_size(ltk_combobox *combobox) { + ltk_widget *self = LTK_CAST_WIDGET(combobox); int text_w, text_h; ltk_text_line_get_size(combobox->tl, &text_w, &text_h); - int arrow_size = ltk_size_to_pixel(theme.arrow_size, LTK_CAST_WIDGET(combobox)->last_dpi); - int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(combobox)->last_dpi); - combobox->widget.ideal_w = text_w + pad * 3 + arrow_size; - combobox->widget.ideal_h = MAX(text_h, arrow_size) + pad * 2; + int arrow_size = ltk_size_to_pixel(theme.arrow_size, self->last_dpi); + int pad = ltk_size_to_pixel(theme.pad, self->last_dpi); + self->ideal_w = text_w + pad * 3 + arrow_size; + self->ideal_h = MAX(text_h, arrow_size) + pad * 2; } static void @@ -325,33 +332,35 @@ ltk_combobox_recalc_ideal_size(ltk_widget *self) { static void combobox_set_active(ltk_combobox *combo, size_t idx, const char *text) { + ltk_widget *self = LTK_CAST_WIDGET(combo); combo->cur_active = idx; ltk_text_line_set_const_text(combo->tl, text); recalc_ideal_size(combo); - if (combo->widget.parent && combo->widget.parent->vtable->child_size_change) { - combo->widget.parent->vtable->child_size_change(combo->widget.parent, LTK_CAST_WIDGET(combo)); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (parent && parent->vtable->child_size_change) { + parent->vtable->child_size_change(parent, self->id); } - ltk_widget_emit_signal(LTK_CAST_WIDGET(combo), LTK_COMBOBOX_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_COMBOBOX_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); } static int handle_entry_pressed(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) { (void)args; - ltk_menuentry *e = LTK_CAST_MENUENTRY(self); - ltk_combobox *combo = LTK_CAST_COMBOBOX(LTK_CAST_ARG_WIDGET(data)); - if (!combo->dropdown) /* shouldn't be possible */ + ltk_widget *combow = ltk_get_widget_from_id(LTK_CAST_ARG_WIDGET_ID(data)); + ltk_combobox *combo = LTK_CAST_COMBOBOX(combow); + if (LTK_WIDGET_ID_IS_NONE(combo->dropdown)) /* shouldn't be possible */ return 1; - size_t idx = ltk_menu_get_entry_index(combo->dropdown, e); + size_t idx = ltk_menu_get_entry_index(combo->dropdown, self->id); if (idx == SIZE_MAX) /* shouldn't be possible */ return 1; - combobox_set_active(combo, idx, ltk_menuentry_get_text(e)); + combobox_set_active(combo, idx, ltk_menuentry_get_text(self->id)); return 1; } static void ltk_combobox_cmd_return(ltk_widget *self, char *text, size_t len) { ltk_combobox *combo = LTK_CAST_COMBOBOX(self); - if (!combo->dropdown) + if (LTK_WIDGET_ID_IS_NONE(combo->dropdown)) return; /* need to copy since it's not nul-terminated */ char *textcopy = ltk_strndup(text, len); @@ -359,7 +368,8 @@ ltk_combobox_cmd_return(ltk_widget *self, char *text, size_t len) { /* only take text until first newline into account */ if (nl) *nl = '\0'; - for (size_t i = 0; i < ltk_menu_get_num_entries(combo->dropdown); i++) { + size_t num_entries = ltk_menu_get_num_entries(combo->dropdown); + for (size_t i = 0; i < num_entries; i++) { if (!strcmp(textcopy, ltk_menuentry_get_text(ltk_menu_get_entry(combo->dropdown, i)))) { combobox_set_active(combo, i, textcopy); break; @@ -372,7 +382,7 @@ static int choose_external(ltk_widget *self, ltk_key_event *event) { (void)event; ltk_combobox *combo = LTK_CAST_COMBOBOX(self); - if (!combo->dropdown || ltk_menu_get_num_entries(combo->dropdown) == 0) + if (LTK_WIDGET_ID_IS_NONE(combo->dropdown) || ltk_menu_get_num_entries(combo->dropdown) == 0) return 1; ltk_general_config *config = ltk_config_get_general(); /* FIXME: allow arguments to key mappings - this would allow to have different key mappings @@ -383,11 +393,12 @@ choose_external(ltk_widget *self, ltk_key_event *event) { /* FIXME: somehow show that there was an error if this returns 1? */ /* FIXME: change interface to not require length of cmd */ txtbuf *tmpbuf = txtbuf_new(); - for (size_t i = 0; i < ltk_menu_get_num_entries(combo->dropdown); i++) { + size_t num_entries = ltk_menu_get_num_entries(combo->dropdown); + for (size_t i = 0; i < num_entries; i++) { txtbuf_append(tmpbuf, ltk_menuentry_get_text(ltk_menu_get_entry(combo->dropdown, i))); txtbuf_append(tmpbuf, "\n"); } - ltk_call_cmd(self, config->option_chooser, txtbuf_get_text(tmpbuf), txtbuf_len(tmpbuf)); + ltk_call_cmd(self->id, config->option_chooser, txtbuf_get_text(tmpbuf), txtbuf_len(tmpbuf)); txtbuf_destroy(tmpbuf); } return 1; @@ -399,18 +410,22 @@ ltk_combobox_key_press(ltk_widget *self, ltk_key_event *event) { } const char * -ltk_combobox_get_text(ltk_combobox *combo) { - if (!combo->dropdown) +ltk_combobox_get_text(ltk_widget_id comboboxid) { + ltk_widget *self = ltk_get_widget_from_id(comboboxid); + ltk_combobox *combobox = LTK_CAST_COMBOBOX(self); + if (LTK_WIDGET_ID_IS_NONE(combobox->dropdown)) return NULL; - ltk_menuentry *e = ltk_menu_get_entry(combo->dropdown, combo->cur_active); - if (!e) + ltk_widget_id e = ltk_menu_get_entry(combobox->dropdown, combobox->cur_active); + if (LTK_WIDGET_ID_IS_NONE(e)) return NULL; return ltk_menuentry_get_text(e); } size_t -ltk_combobox_get_index(ltk_combobox *combo) { - return combo->cur_active; +ltk_combobox_get_index(ltk_widget_id comboboxid) { + ltk_widget *self = ltk_get_widget_from_id(comboboxid); + ltk_combobox *combobox = LTK_CAST_COMBOBOX(self); + return combobox->cur_active; } /* FIXME: this is really hacky - it was added to remove some weird effects when moving @@ -424,29 +439,32 @@ ltk_combobox_get_index(ltk_combobox *combo) { static int handle_dropdown_change_state(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) { (void)args; - ltk_menu *menu = LTK_CAST_MENU(self); - ltk_combobox *combo = LTK_CAST_COMBOBOX(LTK_CAST_ARG_WIDGET(data)); - if (menu != combo->dropdown) /* should never happen */ + ltk_widget *combow = ltk_get_widget_from_id(LTK_CAST_ARG_WIDGET_ID(data)); + ltk_combobox *combo = LTK_CAST_COMBOBOX(combow); + if (!LTK_WIDGET_ID_EQUAL(self->id, combo->dropdown)) /* should never happen */ return 0; - if (!(menu->widget.state & LTK_ACTIVE) && !menu->widget.hidden) + if (!(self->state & LTK_ACTIVE) && !self->hidden) ltk_widget_hide(self); return 0; } int -ltk_combobox_insert_option(ltk_combobox *combobox, const char *option, size_t idx) { +ltk_combobox_insert_option(ltk_widget_id comboboxid, const char *option, size_t idx) { + ltk_widget *self = ltk_get_widget_from_id(comboboxid); + ltk_combobox *combobox = LTK_CAST_COMBOBOX(self); unpopup_dropdown(combobox); /* just to avoid weird effects */ - if (!combobox->dropdown) { - combobox->dropdown = ltk_submenu_create(LTK_CAST_WIDGET(combobox)->window); - LTK_CAST_WIDGET(combobox->dropdown)->parent = LTK_CAST_WIDGET(combobox); + if (LTK_WIDGET_ID_IS_NONE(combobox->dropdown)) { + combobox->dropdown = ltk_submenu_create(self->window); + ltk_widget *dropdownw = ltk_get_widget_from_id(combobox->dropdown); + dropdownw->parent = self->id; ltk_widget_register_signal_handler( - LTK_CAST_WIDGET(combobox->dropdown), LTK_WIDGET_SIGNAL_CHANGE_STATE, - &handle_dropdown_change_state, LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(combobox)) + combobox->dropdown, LTK_WIDGET_SIGNAL_CHANGE_STATE, + &handle_dropdown_change_state, LTK_MAKE_ARG_WIDGET_ID(self->id) ); } - ltk_menuentry *e = ltk_menuentry_create(LTK_CAST_WIDGET(combobox)->window, option); + ltk_widget_id e = ltk_menuentry_create(self->window, option); if (ltk_menu_insert_entry(combobox->dropdown, e, idx)) { - ltk_widget_destroy(LTK_CAST_WIDGET(e), 0); + ltk_widget_id_destroy(e, 0); return 1; } size_t num = ltk_menu_get_num_entries(combobox->dropdown); @@ -456,14 +474,16 @@ ltk_combobox_insert_option(ltk_combobox *combobox, const char *option, size_t id combobox->cur_active++; } ltk_widget_register_signal_handler( - LTK_CAST_WIDGET(e), LTK_MENUENTRY_SIGNAL_PRESSED, - &handle_entry_pressed, LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(combobox)) + e, LTK_MENUENTRY_SIGNAL_PRESSED, + &handle_entry_pressed, LTK_MAKE_ARG_WIDGET_ID(self->id) ); return 0; } int -ltk_combobox_add_option(ltk_combobox *combobox, const char *option) { +ltk_combobox_add_option(ltk_widget_id comboboxid, const char *option) { + ltk_widget *self = ltk_get_widget_from_id(comboboxid); + ltk_combobox *combobox = LTK_CAST_COMBOBOX(self); /* it's easier to just completely ban options with newlines instead of dealing with weird cases where the external option-chooser splits options at newlines */ @@ -471,15 +491,16 @@ ltk_combobox_add_option(ltk_combobox *combobox, const char *option) { if (strchr(option, '\n')) return 1; unpopup_dropdown(combobox); /* just to avoid weird effects */ - if (!combobox->dropdown) { - combobox->dropdown = ltk_submenu_create(LTK_CAST_WIDGET(combobox)->window); - LTK_CAST_WIDGET(combobox->dropdown)->parent = LTK_CAST_WIDGET(combobox); + if (LTK_WIDGET_ID_IS_NONE(combobox->dropdown)) { + combobox->dropdown = ltk_submenu_create(self->window); + ltk_widget *dropdownw = ltk_get_widget_from_id(combobox->dropdown); + dropdownw->parent = self->id; ltk_widget_register_signal_handler( - LTK_CAST_WIDGET(combobox->dropdown), LTK_WIDGET_SIGNAL_CHANGE_STATE, - &handle_dropdown_change_state, LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(combobox)) + combobox->dropdown, LTK_WIDGET_SIGNAL_CHANGE_STATE, + &handle_dropdown_change_state, LTK_MAKE_ARG_WIDGET_ID(self->id) ); } - ltk_menuentry *e = ltk_menuentry_create(LTK_CAST_WIDGET(combobox)->window, option); + ltk_widget_id e = ltk_menuentry_create(self->window, option); /* this should never fail */ ltk_menu_add_entry(combobox->dropdown, e); size_t num = ltk_menu_get_num_entries(combobox->dropdown); @@ -487,27 +508,29 @@ ltk_combobox_add_option(ltk_combobox *combobox, const char *option) { combobox_set_active(combobox, 0, option); } ltk_widget_register_signal_handler( - LTK_CAST_WIDGET(e), LTK_MENUENTRY_SIGNAL_PRESSED, - &handle_entry_pressed, LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(combobox)) + e, LTK_MENUENTRY_SIGNAL_PRESSED, + &handle_entry_pressed, LTK_MAKE_ARG_WIDGET_ID(self->id) ); return 0; } int -ltk_combobox_remove_option_index(ltk_combobox *combobox, size_t idx) { - if (!combobox->dropdown) +ltk_combobox_remove_option_index(ltk_widget_id comboboxid, size_t idx) { + ltk_widget *self = ltk_get_widget_from_id(comboboxid); + ltk_combobox *combobox = LTK_CAST_COMBOBOX(self); + if (LTK_WIDGET_ID_IS_NONE(combobox->dropdown)) return 1; unpopup_dropdown(combobox); /* just to avoid weird effects */ - ltk_menuentry *e = ltk_menu_remove_entry_index(combobox->dropdown, idx); - if (!e) return 1; - ltk_widget_destroy(LTK_CAST_WIDGET(e), 0); + ltk_widget_id e = ltk_menu_remove_entry_index(combobox->dropdown, idx); + if (LTK_WIDGET_ID_IS_NONE(e)) return 1; + ltk_widget_id_destroy(e, 0); if (idx == combobox->cur_active) { size_t num = ltk_menu_get_num_entries(combobox->dropdown); if (num == 0) { combobox_set_active(combobox, SIZE_MAX, ""); } else { e = ltk_menu_get_entry(combobox->dropdown, combobox->cur_active); - if (!e) ltk_fatal("Unable to get menu entry. This should not happen."); + if (LTK_WIDGET_ID_IS_NONE(e)) ltk_fatal("Unable to get menu entry. This should not happen."); combobox_set_active(combobox, idx >= num ? num - 1 : idx, ltk_menuentry_get_text(e)); } } @@ -515,8 +538,10 @@ ltk_combobox_remove_option_index(ltk_combobox *combobox, size_t idx) { } void -ltk_combobox_remove_all_options(ltk_combobox *combobox) { - if (!combobox->dropdown) +ltk_combobox_remove_all_options(ltk_widget_id comboboxid) { + ltk_widget *self = ltk_get_widget_from_id(comboboxid); + ltk_combobox *combobox = LTK_CAST_COMBOBOX(self); + if (LTK_WIDGET_ID_IS_NONE(combobox->dropdown)) return; unpopup_dropdown(combobox); /* just to avoid weird effects */ ltk_menu_remove_all_entries(combobox->dropdown); @@ -526,58 +551,57 @@ ltk_combobox_remove_all_options(ltk_combobox *combobox) { /* NOTE: This should never be called since the dropdown is managed completely by the combobox, but it's here just in case. */ static int -ltk_combobox_remove_child(ltk_widget *self, ltk_widget *widget) { +ltk_combobox_remove_child(ltk_widget *self, ltk_widget_id widgetid) { ltk_combobox *combo = LTK_CAST_COMBOBOX(self); - if (widget != LTK_CAST_WIDGET(combo->dropdown)) + if (!LTK_WIDGET_ID_EQUAL(widgetid, combo->dropdown)) return 1; - widget->parent = NULL; - combo->dropdown = NULL; + ltk_widget *dropdownw = ltk_get_widget_from_id(widgetid); + dropdownw->parent = LTK_WIDGET_ID_NONE; + combo->dropdown = LTK_WIDGET_ID_NONE; return 0; } -static ltk_widget * +static ltk_widget_id ltk_combobox_get_child(ltk_widget *self) { ltk_combobox *combo = LTK_CAST_COMBOBOX(self); - if (combo->dropdown && !combo->dropdown->widget.hidden) - return LTK_CAST_WIDGET(combo->dropdown); - return NULL; + ltk_widget *dropdownw = ltk_get_widget_or_null_from_id(combo->dropdown); + if (dropdownw && !dropdownw->hidden) + return combo->dropdown; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_combobox_nearest_child(ltk_widget *self, ltk_rect rect) { (void)rect; return ltk_combobox_get_child(self); } -ltk_combobox * -ltk_combobox_create(ltk_window *window) { +ltk_widget_id +ltk_combobox_create(ltk_widget_id windowid) { ltk_combobox *combobox = ltk_malloc(sizeof(ltk_combobox)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(combobox), window, &vtable, 0, 0); - combobox->dropdown = NULL; + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(combobox), windowid, &vtable, 0, 0); + combobox->dropdown = LTK_WIDGET_ID_NONE; combobox->cur_active = SIZE_MAX; /* FIXME: only create once text has been added */ combobox->tl = ltk_text_line_create_const_text_default( - theme.font, ltk_size_to_pixel(theme.font_size, combobox->widget.last_dpi), "", -1 + theme.font, ltk_size_to_pixel(theme.font_size, LTK_CAST_WIDGET(combobox)->last_dpi), "", -1 ); recalc_ideal_size(combobox); - combobox->widget.dirty = 1; + LTK_CAST_WIDGET(combobox)->dirty = 1; - return combobox; + return id; } static void ltk_combobox_destroy(ltk_widget *self, int shallow) { (void)shallow; ltk_combobox *combo = LTK_CAST_COMBOBOX(self); - if (!combo) { - ltk_warn("Tried to destroy NULL combobox.\n"); - return; - } ltk_text_line_destroy(combo->tl); - if (combo->dropdown) { - LTK_CAST_WIDGET(combo->dropdown)->parent = NULL; - ltk_widget_destroy(LTK_CAST_WIDGET(combo->dropdown), 0); + ltk_widget *dropdownw = ltk_get_widget_or_null_from_id(combo->dropdown); + if (dropdownw) { + dropdownw->parent = LTK_WIDGET_ID_NONE; + ltk_widget_destroy(dropdownw, 0); } ltk_free(combo); } diff --git a/src/ltk/combobox.h b/src/ltk/combobox.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2024-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,7 @@ #ifndef LTK_COMBOBOX_H #define LTK_COMBOBOX_H +#include <stddef.h> #include "text.h" #include "widget.h" #include "window.h" @@ -28,16 +29,16 @@ typedef struct { ltk_widget widget; ltk_text_line *tl; - ltk_menu *dropdown; + ltk_widget_id dropdown; /* ltk_menu */ size_t cur_active; } ltk_combobox; -ltk_combobox *ltk_combobox_create(ltk_window *window); -int ltk_combobox_insert_option(ltk_combobox *combobox, const char *option, size_t idx); -int ltk_combobox_add_option(ltk_combobox *combobox, const char *option); -int ltk_combobox_remove_option_index(ltk_combobox *combobox, size_t idx); -void ltk_combobox_remove_all_options(ltk_combobox *combobox); -const char *ltk_combobox_get_text(ltk_combobox *combo); -size_t ltk_combobox_get_index(ltk_combobox *combo); +ltk_widget_id ltk_combobox_create(ltk_widget_id windowid); +int ltk_combobox_insert_option(ltk_widget_id comboboxid, const char *option, size_t idx); +int ltk_combobox_add_option(ltk_widget_id comboboxid, const char *option); +int ltk_combobox_remove_option_index(ltk_widget_id comboboxid, size_t idx); +void ltk_combobox_remove_all_options(ltk_widget_id comboboxid); +const char *ltk_combobox_get_text(ltk_widget_id comboboxid); +size_t ltk_combobox_get_index(ltk_widget_id comboboxid); #endif /* LTK_COMBOBOX_H */ diff --git a/src/ltk/entry.c b/src/ltk/entry.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -58,9 +58,6 @@ static int ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event); static int ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event); static void ltk_entry_cmd_return(ltk_widget *self, char *text, size_t len); -/* FIXME: also allow binding key release, not just press */ -typedef void (*cb_func)(ltk_entry *, ltk_key_event *); - /* FIXME: configure mouse actions, e.g. select-word-under-pointer, move-cursor-to-pointer */ static int cursor_to_beginning(ltk_widget *self, ltk_key_event *event); @@ -79,7 +76,6 @@ static int delete_char_backwards(ltk_widget *self, ltk_key_event *event); static int delete_char_forwards(ltk_widget *self, ltk_key_event *event); static int edit_external(ltk_widget *self, ltk_key_event *event); -static void recalc_ideal_size(ltk_entry *entry); static void ensure_cursor_shown(ltk_entry *entry); static void insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor); @@ -279,13 +275,14 @@ xy_to_pos(ltk_entry *e, int x, int y, int snap) { static void set_selection(ltk_entry *entry, size_t start, size_t end) { + ltk_widget *self = LTK_CAST_WIDGET(entry); entry->sel_start = start; entry->sel_end = end; ltk_text_line_clear_attrs(entry->tl); if (start != end) ltk_text_line_add_attr_bg(entry->tl, entry->sel_start, entry->sel_end, theme.selection_color); - entry->widget.dirty = 1; - ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget); + self->dirty = 1; + ltk_window_invalidate_widget_rect(self->window, self->id); } static void @@ -435,6 +432,7 @@ expand_selection_right(ltk_widget *self, ltk_key_event *event) { static void delete_text(ltk_entry *entry, size_t start, size_t end) { + ltk_widget *self = LTK_CAST_WIDGET(entry); memmove(entry->text + start, entry->text + end, entry->len - end); entry->len -= end - start; entry->text[entry->len] = '\0'; @@ -443,8 +441,8 @@ delete_text(ltk_entry *entry, size_t start, size_t end) { ltk_text_line_set_text(entry->tl, entry->text, 0); recalc_ideal_size(entry); ensure_cursor_shown(entry); - entry->widget.dirty = 1; - ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget); + self->dirty = 1; + ltk_window_invalidate_widget_rect(self->window, self->id); } static int @@ -486,40 +484,44 @@ select_all(ltk_widget *self, ltk_key_event *event) { static void recalc_ideal_size_with_notification(ltk_entry *entry) { + ltk_widget *self = LTK_CAST_WIDGET(entry); /* FIXME: need to react to resize and adjust cur_offset */ - unsigned int old_w = entry->widget.ideal_w; - unsigned int old_h = entry->widget.ideal_h; + unsigned int old_w = self->ideal_w; + unsigned int old_h = self->ideal_h; recalc_ideal_size(entry); - if (old_w != entry->widget.ideal_w || old_h != entry->widget.ideal_h) { - if (entry->widget.parent && entry->widget.parent->vtable->child_size_change) - entry->widget.parent->vtable->child_size_change(entry->widget.parent, &entry->widget); + if (old_w != self->ideal_w || old_h != self->ideal_h) { + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (parent && parent->vtable->child_size_change) + parent->vtable->child_size_change(parent, self->id); } } static void ensure_cursor_shown(ltk_entry *entry) { + ltk_widget *self = LTK_CAST_WIDGET(entry); int x, y, w, h; ltk_text_line_pos_to_rect(entry->tl, entry->pos, &x, &y, &w, &h); /* FIXME: test if anything weird can happen since resize is called by parent->child_size_change, and then the stuff on the next few lines is done afterwards */ /* FIXME: adjustable cursor width */ - int bw = ltk_size_to_pixel(theme.border_width, LTK_CAST_WIDGET(entry)->last_dpi); - int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(entry)->last_dpi); - int text_w = entry->widget.lrect.w - 2 * bw - 2 * pad; + int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi); + int pad = ltk_size_to_pixel(theme.pad, self->last_dpi); + int text_w = self->lrect.w - 2 * bw - 2 * pad; if (x + 1 > text_w + entry->cur_offset) { entry->cur_offset = x - text_w + 1; - entry->widget.dirty = 1; - ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget); + self->dirty = 1; + ltk_window_invalidate_widget_rect(self->window, self->id); } else if (x < entry->cur_offset) { entry->cur_offset = x; - entry->widget.dirty = 1; - ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget); + self->dirty = 1; + ltk_window_invalidate_widget_rect(self->window, self->id); } } /* FIXME: maybe make this a regular key binding with wildcard text like in ledit? */ static void insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor) { + ltk_widget *self = LTK_CAST_WIDGET(entry); size_t num = 0; /* FIXME: this is ugly and there are probably a lot of other cases that need to be handled */ @@ -554,8 +556,8 @@ insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor) { ltk_text_line_set_text(entry->tl, entry->text, 0); recalc_ideal_size_with_notification(entry); ensure_cursor_shown(entry); - entry->widget.dirty = 1; - ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget); + self->dirty = 1; + ltk_window_invalidate_widget_rect(self->window, self->id); } static void @@ -578,7 +580,7 @@ edit_external(ltk_widget *self, ltk_key_event *event) { } else { /* FIXME: somehow show that there was an error if this returns 1? */ /* FIXME: change interface to not require length of cmd */ - ltk_call_cmd(LTK_CAST_WIDGET(entry), config->line_editor, entry->text, entry->len); + ltk_call_cmd(self->id, config->line_editor, entry->text, entry->len); } return 1; } @@ -588,7 +590,7 @@ ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) { ltk_entry *entry = LTK_CAST_ENTRY(self); if (ltk_widget_handle_keypress_bindings(self, event, keypresses, 0)) { self->dirty = 1; - ltk_window_invalidate_widget_rect(self->window, self); + ltk_window_invalidate_widget_rect(self->window, self->id); return 1; } if (event->text && (event->modmask & (LTK_MOD_CTRL | LTK_MOD_ALT | LTK_MOD_SUPER)) == 0) { @@ -728,12 +730,13 @@ ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event) { static void recalc_ideal_size(ltk_entry *entry) { + ltk_widget *self = LTK_CAST_WIDGET(entry); int text_w, text_h; ltk_text_line_get_size(entry->tl, &text_w, &text_h); - int bw = ltk_size_to_pixel(theme.border_width, LTK_CAST_WIDGET(entry)->last_dpi); - int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(entry)->last_dpi); - entry->widget.ideal_w = text_w + bw * 2 + pad * 2; - entry->widget.ideal_h = text_h + bw * 2 + pad * 2; + int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi); + int pad = ltk_size_to_pixel(theme.pad, self->last_dpi); + self->ideal_w = text_w + bw * 2 + pad * 2; + self->ideal_h = text_h + bw * 2 + pad * 2; } static void @@ -744,13 +747,14 @@ ltk_entry_recalc_ideal_size(ltk_widget *self) { recalc_ideal_size(entry); } -ltk_entry * -ltk_entry_create(ltk_window *window, const char *text) { +ltk_widget_id +ltk_entry_create(ltk_widget_id windowid, const char *text) { ltk_entry *entry = ltk_malloc(sizeof(ltk_entry)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(entry), window, &vtable, 0, 0); + ltk_widget *self = LTK_CAST_WIDGET(entry); + ltk_widget_id id = ltk_initialize_widget(self, windowid, &vtable, 0, 0); entry->tl = ltk_text_line_create_const_text_default( - theme.font, ltk_size_to_pixel(theme.font_size, entry->widget.last_dpi), text, -1 + theme.font, ltk_size_to_pixel(theme.font_size, self->last_dpi), text, -1 ); recalc_ideal_size(entry); @@ -761,19 +765,15 @@ ltk_entry_create(ltk_window *window, const char *text) { entry->pos = entry->sel_start = entry->sel_end = 0; entry->sel_side = 0; entry->selecting = 0; - entry->widget.dirty = 1; + self->dirty = 1; - return entry; + return id; } static void ltk_entry_destroy(ltk_widget *self, int shallow) { (void)shallow; ltk_entry *entry = LTK_CAST_ENTRY(self); - if (!entry) { - ltk_warn("Tried to destroy NULL entry.\n"); - return; - } ltk_free(entry->text); ltk_text_line_destroy(entry->tl); ltk_free(entry); diff --git a/src/ltk/entry.h b/src/ltk/entry.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,8 +18,6 @@ #define LTK_ENTRY_H #include <stddef.h> -#include "config.h" -#include "graphics.h" #include "widget.h" #include "window.h" #include "text.h" @@ -38,6 +36,6 @@ typedef struct { char selecting; } ltk_entry; -ltk_entry *ltk_entry_create(ltk_window *window, const char *text); +ltk_widget_id ltk_entry_create(ltk_widget_id windowid, const char *text); #endif /* LTK_ENTRY_H */ diff --git a/src/ltk/grid.c b/src/ltk/grid.c @@ -1,7 +1,7 @@ /* FIXME: sometimes, resizing doesn't work properly when running test.sh */ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -32,37 +32,29 @@ #include "util.h" #include "grid.h" #include "graphics.h" +#include "ltk.h" -void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight); -void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight); -ltk_grid *ltk_grid_create(ltk_window *window, int rows, int columns); -int ltk_grid_add( - ltk_grid *grid, ltk_widget *widget, - int row, int column, int row_span, int column_span, - ltk_sticky_mask sticky -); /* just a wrapper around ltk_grid_remove to make types match */ -static int ltk_grid_remove_child(ltk_widget *self, ltk_widget *widget); -int ltk_grid_remove(ltk_grid *grid, ltk_widget *widget); +static int ltk_grid_remove_child(ltk_widget *self, ltk_widget_id childid); static void ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); static void ltk_grid_destroy(ltk_widget *self, int shallow); static void ltk_recalculate_grid(ltk_widget *self); -static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget); +static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget_id childid); static int ltk_grid_find_nearest_column(ltk_grid *grid, int x); static int ltk_grid_find_nearest_row(ltk_grid *grid, int y); -static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y); +static ltk_widget_id ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y); -static ltk_widget *ltk_grid_prev_child(ltk_widget *self, ltk_widget *child); -static ltk_widget *ltk_grid_next_child(ltk_widget *self, ltk_widget *child); -static ltk_widget *ltk_grid_first_child(ltk_widget *self); -static ltk_widget *ltk_grid_last_child(ltk_widget *self); +static ltk_widget_id ltk_grid_prev_child(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_grid_next_child(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_grid_first_child(ltk_widget *self); +static ltk_widget_id ltk_grid_last_child(ltk_widget *self); -static ltk_widget *ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect); -static ltk_widget *ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget); +static ltk_widget_id ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect); +static ltk_widget_id ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget_id childid); static void ltk_grid_recalc_ideal_size(ltk_widget *self); @@ -98,20 +90,24 @@ static struct ltk_widget_vtable vtable = { .invalid_signal = LTK_GRID_SIGNAL_INVALID, }; -/* FIXME: only set "dirty" bit to avoid constand recalculation when +/* FIXME: only set "dirty" bit to avoid constant recalculation when setting multiple row/column weights? */ void -ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight) { +ltk_grid_set_row_weight(ltk_widget_id gridid, int row, int weight) { + ltk_widget *self = ltk_get_widget_from_id(gridid); + ltk_grid *grid = LTK_CAST_GRID(self); ltk_assert(row < grid->rows); grid->row_weights[row] = weight; - ltk_recalculate_grid(LTK_CAST_WIDGET(grid)); + ltk_recalculate_grid(self); } void -ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight) { +ltk_grid_set_column_weight(ltk_widget_id gridid, int column, int weight) { + ltk_widget *self = ltk_get_widget_from_id(gridid); + ltk_grid *grid = LTK_CAST_GRID(self); ltk_assert(column < grid->columns); grid->column_weights[column] = weight; - ltk_recalculate_grid(LTK_CAST_WIDGET(grid)); + ltk_recalculate_grid(self); } static void @@ -120,9 +116,9 @@ ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { int i; ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip); for (i = 0; i < grid->rows * grid->columns; i++) { - if (!grid->widget_grid[i]) + if (LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i])) continue; - ltk_widget *ptr = grid->widget_grid[i]; + ltk_widget *ptr = ltk_get_widget_from_id(grid->widget_grid[i]); int max_w = grid->column_pos[ptr->column + ptr->column_span] - grid->column_pos[ptr->column]; int max_h = grid->row_pos[ptr->row + ptr->row_span] - grid->row_pos[ptr->row]; ltk_rect r = ltk_rect_intersect( @@ -132,58 +128,51 @@ ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { } } -ltk_grid * -ltk_grid_create(ltk_window *window, int rows, int columns) { +ltk_widget_id +ltk_grid_create(ltk_widget_id windowid, int rows, int columns) { ltk_grid *grid = ltk_malloc(sizeof(ltk_grid)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(grid), window, &vtable, 0, 0); + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(grid), windowid, &vtable, 0, 0); grid->rows = rows; grid->columns = columns; - grid->widget_grid = ltk_malloc(rows * columns * sizeof(ltk_widget)); + grid->widget_grid = ltk_malloc(rows * columns * sizeof(ltk_widget_id)); grid->row_heights = ltk_malloc(rows * sizeof(int)); - grid->column_widths = ltk_malloc(rows * sizeof(int)); + grid->column_widths = ltk_malloc(columns * sizeof(int)); grid->row_weights = ltk_malloc(rows * sizeof(int)); grid->column_weights = ltk_malloc(columns * sizeof(int)); /* Positions have one extra for the end */ grid->row_pos = ltk_malloc((rows + 1) * sizeof(int)); grid->column_pos = ltk_malloc((columns + 1) * sizeof(int)); - /* FIXME: wow, that's horrible, this should just use memset */ - int i; - for (i = 0; i < rows; i++) { - grid->row_heights[i] = 0; - grid->row_weights[i] = 0; - grid->row_pos[i] = 0; - } - grid->row_pos[rows] = 0; - for (i = 0; i < columns; i++) { - grid->column_widths[i] = 0; - grid->column_weights[i] = 0; - grid->column_pos[i] = 0; - } - grid->column_pos[columns] = 0; - for (i = 0; i < rows * columns; i++) { - grid->widget_grid[i] = NULL; + + memset(grid->row_heights, 0, rows * sizeof(int)); + memset(grid->row_weights, 0, rows * sizeof(int)); + memset(grid->row_pos, 0, (rows + 1) * sizeof(int)); + memset(grid->column_widths, 0, columns * sizeof(int)); + memset(grid->column_weights, 0, columns * sizeof(int)); + memset(grid->column_pos, 0, (columns + 1) * sizeof(int)); + memset(grid->column_pos, 0, (columns + 1) * sizeof(int)); + for (int i = 0; i < rows * columns; i++) { + grid->widget_grid[i] = LTK_WIDGET_ID_NONE; } ltk_recalculate_grid(LTK_CAST_WIDGET(grid)); - return grid; + return id; } static void ltk_grid_destroy(ltk_widget *self, int shallow) { ltk_grid *grid = LTK_CAST_GRID(self); - ltk_widget *ptr; for (int i = 0; i < grid->rows * grid->columns; i++) { - if (grid->widget_grid[i]) { - ptr = grid->widget_grid[i]; - ptr->parent = NULL; + ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[i]); + if (ptr) { + ptr->parent = LTK_WIDGET_ID_NONE; if (!shallow) { /* required to avoid freeing a widget multiple times if row_span or column_span is not 1 */ for (int r = ptr->row; r < ptr->row + ptr->row_span; r++) { for (int c = ptr->column; c < ptr->column + ptr->column_span; c++) { - grid->widget_grid[r * grid->columns + c] = NULL; + grid->widget_grid[r * grid->columns + c] = LTK_WIDGET_ID_NONE; } } ltk_widget_destroy(ptr, shallow); @@ -247,7 +236,7 @@ ltk_recalculate_grid(ltk_widget *self) { int end_column, end_row; for (i = 0; i < grid->rows; i++) { for (j = 0; j < grid->columns; j++) { - ltk_widget *ptr = grid->widget_grid[i * grid->columns + j]; + ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[i * grid->columns + j]); if (!ptr || ptr->row != i || ptr->column != j) continue; /*orig_width = ptr->lrect.w; @@ -329,7 +318,7 @@ ltk_grid_recalc_ideal_size(ltk_widget *self) { if (grid->row_weights[i] == 0) grid->row_heights[i] = 0; for (int j = 0; j < grid->columns; j++) { - ltk_widget *ptr = grid->widget_grid[i * grid->columns + j]; + ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[i * grid->columns + j]); if (!ptr || ptr->row != i || ptr->column != j) continue; ltk_widget_recalc_ideal_size(ptr); @@ -352,8 +341,9 @@ ltk_grid_recalc_ideal_size(ltk_widget *self) { /* FIXME: Maybe add debug stuff to check that grid is actually parent of widget */ static void -ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) { +ltk_grid_child_size_change(ltk_widget *self, ltk_widget_id widgetid) { ltk_grid *grid = LTK_CAST_GRID(self); + ltk_widget *widget = ltk_get_widget_from_id(widgetid); short size_changed = 0; int orig_w = widget->lrect.w; int orig_h = widget->lrect.h; @@ -371,10 +361,11 @@ ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) { grid->row_heights[widget->row] = widget->lrect.h; size_changed = 1; } - if (size_changed && self->parent && self->parent->vtable->child_size_change) - self->parent->vtable->child_size_change(self->parent, LTK_CAST_WIDGET(grid)); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (size_changed && parent && parent->vtable->child_size_change) + parent->vtable->child_size_change(parent, self->id); else - ltk_recalculate_grid(LTK_CAST_WIDGET(grid)); + ltk_recalculate_grid(self); if (widget->lrect.w != orig_w || widget->lrect.h != orig_h) ltk_widget_resize(widget); } @@ -382,11 +373,14 @@ ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) { /* FIXME: Check if widget already exists at position */ int ltk_grid_add( - ltk_grid *grid, ltk_widget *widget, + ltk_widget_id gridid, ltk_widget_id widgetid, int row, int column, int row_span, int column_span, ltk_sticky_mask sticky ) { - if (widget->parent) + ltk_widget *self = ltk_get_widget_from_id(gridid); + ltk_grid *grid = LTK_CAST_GRID(self); + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + if (!LTK_WIDGET_ID_IS_NONE(widget->parent)) return 1; /* FIXME: decide which checks should be asserts and which should be error returns */ /* the client-server version of ltk shouldn't abort on errors like these */ @@ -402,34 +396,37 @@ ltk_grid_add( widget->column_span = column_span; for (int i = row; i < row + row_span; i++) { for (int j = column; j < column + column_span; j++) { - grid->widget_grid[i * grid->columns + j] = widget; + grid->widget_grid[i * grid->columns + j] = widgetid; } } - widget->parent = LTK_CAST_WIDGET(grid); - ltk_grid_child_size_change(LTK_CAST_WIDGET(grid), widget); - ltk_window_invalidate_widget_rect(LTK_CAST_WIDGET(grid)->window, LTK_CAST_WIDGET(grid)); + widget->parent = gridid; + ltk_grid_child_size_change(self, widgetid); + ltk_window_invalidate_widget_rect(self->window, gridid); return 0; } int -ltk_grid_remove(ltk_grid *grid, ltk_widget *widget) { - if (widget->parent != LTK_CAST_WIDGET(grid)) +ltk_grid_remove(ltk_widget_id gridid, ltk_widget_id widgetid) { + ltk_widget *self = ltk_get_widget_from_id(gridid); + ltk_grid *grid = LTK_CAST_GRID(self); + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + if (!LTK_WIDGET_ID_EQUAL(widget->parent, gridid)) return 1; - widget->parent = NULL; + widget->parent = LTK_WIDGET_ID_NONE; for (int i = widget->row; i < widget->row + widget->row_span; i++) { for (int j = widget->column; j < widget->column + widget->column_span; j++) { - grid->widget_grid[i * grid->columns + j] = NULL; + grid->widget_grid[i * grid->columns + j] = LTK_WIDGET_ID_NONE; } } - ltk_window_invalidate_widget_rect(LTK_CAST_WIDGET(grid)->window, LTK_CAST_WIDGET(grid)); + ltk_window_invalidate_widget_rect(self->window, gridid); return 0; } static int -ltk_grid_remove_child(ltk_widget *self, ltk_widget *widget) { - return ltk_grid_remove(LTK_CAST_GRID(self), widget); +ltk_grid_remove_child(ltk_widget *self, ltk_widget_id widgetid) { + return ltk_grid_remove(self->id, widgetid); } static int @@ -455,17 +452,18 @@ ltk_grid_find_nearest_row(ltk_grid *grid, int y) { } /* FIXME: maybe come up with a more efficient method */ -static ltk_widget * +static ltk_widget_id ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) { ltk_grid *grid = LTK_CAST_GRID(self); - ltk_widget *minw = NULL; + ltk_widget_id minw = LTK_WIDGET_ID_NONE; int min_dist = INT_MAX; /* FIXME: rows and columns shouldn't be int */ for (size_t i = 0; i < (size_t)(grid->rows * grid->columns); i++) { - if (!grid->widget_grid[i]) + if (LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i])) continue; /* FIXME: this checks widgets with row/columnspan > 1 multiple times */ - ltk_rect r = grid->widget_grid[i]->lrect; + ltk_widget *widget = ltk_get_widget_from_id(grid->widget_grid[i]); + ltk_rect r = widget->lrect; int dist = ltk_rect_fakedist(rect, r); if (dist < min_dist) { min_dist = dist; @@ -476,109 +474,112 @@ ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) { } /* FIXME: assertions to check that widget row/column are legal */ -static ltk_widget * -ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget_id childid) { ltk_grid *grid = LTK_CAST_GRID(self); - unsigned int col = widget->column; - ltk_widget *cur = NULL; + ltk_widget *child = ltk_get_widget_from_id(childid); + unsigned int col = child->column; while (col-- > 0) { - cur = grid->widget_grid[widget->row * grid->columns + col]; - if (cur && cur != widget) + ltk_widget_id cur = grid->widget_grid[child->row * grid->columns + col]; + if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid)) return cur; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * -ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget_id childid) { ltk_grid *grid = LTK_CAST_GRID(self); - ltk_widget *cur = NULL; - for (int col = widget->column + 1; col < grid->columns; col++) { - cur = grid->widget_grid[widget->row * grid->columns + col]; - if (cur && cur != widget) + ltk_widget *child = ltk_get_widget_from_id(childid); + for (int col = child->column + 1; col < grid->columns; col++) { + ltk_widget_id cur = grid->widget_grid[child->row * grid->columns + col]; + if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid)) return cur; } - return NULL; + return LTK_WIDGET_ID_NONE; } /* FIXME: maybe these should also fall back to widgets in other columns if those exist but no widgets exist in the same column */ -static ltk_widget * -ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget_id childid) { ltk_grid *grid = LTK_CAST_GRID(self); - unsigned int row = widget->row; - ltk_widget *cur = NULL; + ltk_widget *child = ltk_get_widget_from_id(childid); + unsigned int row = child->row; while (row-- > 0) { - cur = grid->widget_grid[row * grid->columns + widget->column]; - if (cur && cur != widget) + ltk_widget_id cur = grid->widget_grid[row * grid->columns + child->column]; + if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid)) return cur; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * -ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget_id childid) { ltk_grid *grid = LTK_CAST_GRID(self); - ltk_widget *cur = NULL; - for (int row = widget->row + 1; row < grid->rows; row++) { - cur = grid->widget_grid[row * grid->columns + widget->column]; - if (cur && cur != widget) + ltk_widget *child = ltk_get_widget_from_id(childid); + for (int row = child->row + 1; row < grid->rows; row++) { + ltk_widget_id cur = grid->widget_grid[row * grid->columns + child->column]; + if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid)) return cur; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) { ltk_grid *grid = LTK_CAST_GRID(self); int row = ltk_grid_find_nearest_row(grid, y); int column = ltk_grid_find_nearest_column(grid, x); if (row == -1 || column == -1) - return 0; - ltk_widget *ptr = grid->widget_grid[row * grid->columns + column]; + return LTK_WIDGET_ID_NONE; + ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[row * grid->columns + column]); if (ptr && ltk_collide_rect(ptr->crect, x, y)) - return ptr; - return NULL; + return ptr->id; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * -ltk_grid_prev_child(ltk_widget *self, ltk_widget *child) { +static ltk_widget_id +ltk_grid_prev_child(ltk_widget *self, ltk_widget_id childid) { ltk_grid *grid = LTK_CAST_GRID(self); + ltk_widget *child = ltk_get_widget_from_id(childid); unsigned int start = child->row * grid->columns + child->column; while (start-- > 0) { - if (grid->widget_grid[start]) + if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[start])) return grid->widget_grid[start]; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * -ltk_grid_next_child(ltk_widget *self, ltk_widget *child) { +static ltk_widget_id +ltk_grid_next_child(ltk_widget *self, ltk_widget_id childid) { ltk_grid *grid = LTK_CAST_GRID(self); + ltk_widget *child = ltk_get_widget_from_id(childid); unsigned int start = child->row * grid->columns + child->column; while (++start < (unsigned int)(grid->rows * grid->columns)) { - if (grid->widget_grid[start] && grid->widget_grid[start] != child) + if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[start]) && + !LTK_WIDGET_ID_EQUAL(grid->widget_grid[start], childid)) return grid->widget_grid[start]; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_grid_first_child(ltk_widget *self) { ltk_grid *grid = LTK_CAST_GRID(self); for (unsigned int i = 0; i < (unsigned int)(grid->rows * grid->columns); i++) { - if (grid->widget_grid[i]) + if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i])) return grid->widget_grid[i]; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_grid_last_child(ltk_widget *self) { ltk_grid *grid = LTK_CAST_GRID(self); for (unsigned int i = grid->rows * grid->columns; i-- > 0;) { - if (grid->widget_grid[i]) + if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i])) return grid->widget_grid[i]; } - return NULL; + return LTK_WIDGET_ID_NONE; } diff --git a/src/ltk/grid.h b/src/ltk/grid.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,12 +22,10 @@ #define LTK_GRID_SIGNAL_INVALID -1 -/* - * Struct to represent a grid widget. - */ +/* FIXME: maybe changes types to unsigned int */ typedef struct { ltk_widget widget; - ltk_widget **widget_grid; + ltk_widget_id *widget_grid; int *row_heights; int *column_widths; int *row_weights; @@ -39,14 +37,14 @@ typedef struct { } ltk_grid; /* FIXME: proper error handling for different errors in add/remove */ -ltk_grid *ltk_grid_create(ltk_window *window, int rows, int columns); +ltk_widget_id ltk_grid_create(ltk_widget_id windowid, int rows, int columns); int ltk_grid_add( - ltk_grid *grid, ltk_widget *widget, + ltk_widget_id gridid, ltk_widget_id widget, int row, int column, int row_span, int column_span, ltk_sticky_mask sticky ); -int ltk_grid_remove(ltk_grid *grid, ltk_widget *widget); -void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight); -void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight); +int ltk_grid_remove(ltk_widget_id gridid, ltk_widget_id widget); +void ltk_grid_set_row_weight(ltk_widget_id gridid, int row, int weight); +void ltk_grid_set_column_weight(ltk_widget_id gridid, int column, int weight); #endif /* LTK_GRID_H */ diff --git a/src/ltk/image_widget.c b/src/ltk/image_widget.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2023-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -71,28 +71,23 @@ ltk_image_widget_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, lt ); } -ltk_image_widget * -ltk_image_widget_create(ltk_window *window, ltk_image *img) { +ltk_widget_id +ltk_image_widget_create(ltk_widget_id windowid, ltk_image *img) { ltk_image_widget *imw = ltk_malloc(sizeof(ltk_image_widget)); imw->img = img; int w = ltk_image_get_width(imw->img); int h = ltk_image_get_height(imw->img); - ltk_fill_widget_defaults(&imw->widget, window, &vtable, w, h); - imw->widget.ideal_w = w; - imw->widget.ideal_h = h; + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(imw), windowid, &vtable, w, h); + LTK_CAST_WIDGET(imw)->ideal_w = w; + LTK_CAST_WIDGET(imw)->ideal_h = h; - return imw; + return id; } static void ltk_image_widget_destroy(ltk_widget *self, int shallow) { (void)shallow; ltk_image_widget *img = LTK_CAST_IMAGE_WIDGET(self); - /* FIXME: make warnings like this consistent across widgets */ - if (!img) { - ltk_warn("Tried to destroy NULL image widget.\n"); - return; - } ltk_image_destroy(img->img); ltk_free(img); } diff --git a/src/ltk/image_widget.h b/src/ltk/image_widget.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2023-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -27,6 +27,6 @@ typedef struct { } ltk_image_widget; /* WARNING: takes over img! */ -ltk_image_widget *ltk_image_widget_create(ltk_window *window, ltk_image *img); +ltk_widget_id ltk_image_widget_create(ltk_widget_id windowid, ltk_image *img); #endif /* LTK_IMAGE_WIDGET_H */ diff --git a/src/ltk/label.c b/src/ltk/label.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -113,27 +113,23 @@ ltk_label_recalc_ideal_size(ltk_widget *self) { recalc_ideal_size(label); } -ltk_label * -ltk_label_create(ltk_window *window, const char *text) { +ltk_widget_id +ltk_label_create(ltk_widget_id windowid, const char *text) { ltk_label *label = ltk_malloc(sizeof(ltk_label)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(label), window, &vtable, 0, 0); + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(label), windowid, &vtable, 0, 0); label->tl = ltk_text_line_create_const_text_default( theme.font, ltk_size_to_pixel(theme.font_size, label->widget.last_dpi), text, -1 ); recalc_ideal_size(label); - return label; + return id; } static void ltk_label_destroy(ltk_widget *self, int shallow) { (void)shallow; ltk_label *label = LTK_CAST_LABEL(self); - if (!label) { - ltk_warn("Tried to destroy NULL label.\n"); - return; - } ltk_text_line_destroy(label->tl); ltk_free(label); } diff --git a/src/ltk/label.h b/src/ltk/label.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,6 +28,6 @@ typedef struct { ltk_text_line *tl; } ltk_label; -ltk_label *ltk_label_create(ltk_window *window, const char *text); +ltk_widget_id ltk_label_create(ltk_widget_id windowid, const char *text); #endif /* LTK_LABEL_H */ diff --git a/src/ltk/ltk.c b/src/ltk/ltk.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -45,44 +45,65 @@ #include "widget_internal.h" typedef struct { - ltk_widget *caller; + ltk_widget_id caller; char *infile; char *outfile; int pid; } ltk_cmdinfo; +typedef struct { + void (*callback)(ltk_callback_arg data); + ltk_callback_arg data; + struct timespec repeat; + struct timespec remaining; + int id; +} ltk_timer; + LTK_ARRAY_INIT_DECL_STATIC(window, ltk_window *) LTK_ARRAY_INIT_IMPL_STATIC(window, ltk_window *) LTK_ARRAY_INIT_DECL_STATIC(rwindow, ltk_renderwindow *) LTK_ARRAY_INIT_IMPL_STATIC(rwindow, ltk_renderwindow *) LTK_ARRAY_INIT_DECL_STATIC(cmdinfo, ltk_cmdinfo) LTK_ARRAY_INIT_IMPL_STATIC(cmdinfo, ltk_cmdinfo) +LTK_ARRAY_INIT_DECL_STATIC(widget, ltk_widget *) +LTK_ARRAY_INIT_IMPL_STATIC(widget, ltk_widget *) +LTK_ARRAY_INIT_DECL_STATIC(gen_num, ltk_widget_id_int_type) +LTK_ARRAY_INIT_IMPL_STATIC(gen_num, ltk_widget_id_int_type) +LTK_ARRAY_INIT_DECL_STATIC(timer, ltk_timer) +LTK_ARRAY_INIT_IMPL_STATIC(timer, ltk_timer) static struct { ltk_renderdata *renderdata; ltk_text_context *text_context; ltk_clipboard *clipboard; + /* FIXME: For the current event mechanism to work properly, the windows need to be stored + in a separate array here in addition to the general widgets array that they are stored + in anyways. Maybe fix this so they are only stored in one place? */ ltk_array(window) *windows; ltk_array(rwindow) *rwindows; /* PID of external command called e.g. by text widget to edit text. ON exit, cmd_caller->vtable->cmd_return is called with the text the external command wrote to a file. */ /*FIXME: this needs to be checked whenever a widget is destroyed!*/ + /* -> actually, if widget ID system is implemented, this can just check if ID is still valid on command return + -> although, that could cause weirdness in extreme edge cases when the generation number of the ID has + wrapped around so there is actually a valid ID at that position - would at least need to check if cmd_return is actually valid for widget */ ltk_array(cmdinfo) *cmds; + ltk_array(timer) *timers; + /* widgets is an array of all widgets, with entries of deleted widgets being NULL until + they are reused. cur_widget_clock_pos contains the index from which a search for an + empty slot should start. If no empty slot is found and the size of the array is less + than LTK_MAX_WIDGET_IDX, the array size is increased. In order to give an error when + a widget that has been deleted is requested and the slot has already been filled with + a new widget, each widget ID contains a generation number. gen_nums contains the + current generation number for each slot, which is increased whenever a widget is added + to the slot. A valid widget ID can never have a generation number of 0 (this is so + there's a special widget ID that can be used to signal that the ID is invalid). */ + ltk_array(widget) *widgets; + ltk_array(gen_num) *gen_nums; + ltk_widget_id_int_type cur_widget_clock_pos; size_t cur_kbd; -} shared_data = {NULL, NULL, NULL, NULL, NULL, NULL, 0}; - -typedef struct { - void (*callback)(ltk_callback_arg data); - ltk_callback_arg data; - struct timespec repeat; - struct timespec remaining; - int id; -} ltk_timer; - -static ltk_timer *timers = NULL; -static size_t timers_num = 0; -static size_t timers_alloc = 0; +} shared_data = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0}; static void ltk_handle_event(ltk_event *event); @@ -156,9 +177,70 @@ ltk_init(void) { shared_data.windows = ltk_array_create(window, 1); shared_data.rwindows = ltk_array_create(rwindow, 1); shared_data.cmds = ltk_array_create(cmdinfo, 1); + shared_data.widgets = ltk_array_create(widget, 1); + shared_data.gen_nums = ltk_array_create(gen_num, 1); + shared_data.timers = ltk_array_create(timer, 1); return 0; /* FIXME: or maybe 1? */ } +ltk_widget * +ltk_get_widget_from_id(ltk_widget_id id) { + ltk_assert(id.gen > 0); + ltk_assert(id.idx < ltk_array_len(shared_data.widgets)); + ltk_widget *widget = ltk_array_get(shared_data.widgets, id.idx); + ltk_assert(widget != NULL); + ltk_assert(widget->id.gen == id.gen); + return widget; +} + +ltk_widget * +ltk_get_widget_or_null_from_id(ltk_widget_id id) { + return id.gen == 0 ? NULL : ltk_get_widget_from_id(id); +} + +ltk_widget * +ltk_get_widget_or_null_from_id_nofail(ltk_widget_id id) { + if (id.gen == 0 || id.idx >= ltk_array_len(shared_data.widgets)) + return NULL; + ltk_widget *widget = ltk_array_get(shared_data.widgets, id.idx); + if (!widget || widget->id.gen != id.gen) + return NULL; + return widget; +} + +ltk_widget_id +ltk_store_widget(ltk_widget *widget) { + for (size_t i = 0; i < ltk_array_len(shared_data.widgets); ++i) { + if (!ltk_array_get(shared_data.widgets, shared_data.cur_widget_clock_pos)) { + size_t idx = shared_data.cur_widget_clock_pos; + ltk_array_get(shared_data.widgets, idx) = widget; + ltk_array_get(shared_data.gen_nums, idx)++; + shared_data.cur_widget_clock_pos++; + shared_data.cur_widget_clock_pos %= ltk_array_len(shared_data.widgets); + return (ltk_widget_id){idx, ltk_array_get(shared_data.gen_nums, idx)}; + } + shared_data.cur_widget_clock_pos++; + shared_data.cur_widget_clock_pos %= ltk_array_len(shared_data.widgets); + } + size_t idx = ltk_array_len(shared_data.widgets); + ltk_assert(idx <= LTK_MAX_WIDGET_IDX); + ltk_array_append(widget, shared_data.widgets, widget); + ltk_array_len_to_capacity(widget, shared_data.widgets, NULL); + if (ltk_array_len(shared_data.widgets) > (size_t)LTK_MAX_WIDGET_IDX + 1U) + ltk_array_resize(widget, shared_data.widgets, (size_t)LTK_MAX_WIDGET_IDX + 1U, NULL); + ltk_array_resize(gen_num, shared_data.gen_nums, ltk_array_len(shared_data.widgets), 0); + ltk_array_get(shared_data.gen_nums, idx) = 1; + shared_data.cur_widget_clock_pos = idx + 1; + shared_data.cur_widget_clock_pos %= ltk_array_len(shared_data.widgets); + return (ltk_widget_id){idx, 1}; +} + +void +ltk_clear_widget_slot(ltk_widget_id id) { + ltk_assert(id.idx < ltk_array_len(shared_data.widgets)); + ltk_array_get(shared_data.widgets, id.idx) = NULL; +} + static struct { struct timespec last; struct timespec lasttimer; @@ -197,11 +279,11 @@ ltk_mainloop_step(int limit_framerate) { for (size_t i = 0; i < ltk_array_len(shared_data.cmds); i++) { info = &(ltk_array_get(shared_data.cmds, i)); if (info->pid == pid) { - /* FIXME: actually NULL this when widgets are destroyed */ - if (!info->caller) { + ltk_widget *caller = ltk_get_widget_or_null_from_id_nofail(info->caller); + if (!caller) { ltk_warn("Widget disappeared while text was being edited in external program\n"); /* FIXME: call overwritten cmd_return! */ - } else if (info->caller->vtable->cmd_return) { + } else if (caller->vtable->cmd_return) { size_t file_len = 0; char *errstr = NULL; char *filename = info->outfile ? info->outfile : info->infile; @@ -209,7 +291,7 @@ ltk_mainloop_step(int limit_framerate) { if (!contents) { ltk_warn("Unable to read file '%s' written by external command: %s\n", filename, errstr); } else { - info->caller->vtable->cmd_return(info->caller, contents, file_len); + caller->vtable->cmd_return(caller, contents, file_len); ltk_free0(contents); } } @@ -238,16 +320,17 @@ ltk_mainloop_step(int limit_framerate) { /* Note: it should be safe to give the same pointer as the first and last argument, as long as ltk_timespecsub/add isn't changed incompatibly */ size_t i = 0; - while (i < timers_num) { - ltk_timespecsub(&timers[i].remaining, &elapsed, &timers[i].remaining); - if (timers[i].remaining.tv_sec < 0 || - (timers[i].remaining.tv_sec == 0 && timers[i].remaining.tv_nsec == 0)) { - timers[i].callback(timers[i].data); - if (timers[i].repeat.tv_sec == 0 && timers[i].repeat.tv_nsec == 0) { + while (i < ltk_array_len(shared_data.timers)) { + ltk_timer *cur_timer = &ltk_array_get(shared_data.timers, i); + ltk_timespecsub(&cur_timer->remaining, &elapsed, &cur_timer->remaining); + if (cur_timer->remaining.tv_sec < 0 || + (cur_timer->remaining.tv_sec == 0 && cur_timer->remaining.tv_nsec == 0)) { + cur_timer->callback(cur_timer->data); + if (cur_timer->repeat.tv_sec == 0 && cur_timer->repeat.tv_nsec == 0) { /* remove timer because it has no repeat */ - memmove(timers + i, timers + i + 1, sizeof(ltk_timer) * (timers_num - i - 1)); + ltk_array_delete(timer, shared_data.timers, i, 1); } else { - ltk_timespecadd(&timers[i].remaining, &timers[i].repeat, &timers[i].remaining); + ltk_timespecadd(&cur_timer->remaining, &cur_timer->repeat, &cur_timer->remaining); i++; } } else { @@ -256,8 +339,8 @@ ltk_mainloop_step(int limit_framerate) { } mainloop_data.lasttimer = now; - for (size_t i = 0; i < shared_data.windows->len; i++) { - ltk_window *window = shared_data.windows->buf[i]; + for (size_t i = 0; i < ltk_array_len(shared_data.windows); i++) { + ltk_window *window = ltk_array_get(shared_data.windows, i); if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) { ltk_widget_draw(LTK_CAST_WIDGET(window), NULL, 0, 0, (ltk_rect){0, 0, 0, 0}); } @@ -310,17 +393,31 @@ ltk_deinit(void) { ltk_array_destroy(cmdinfo, shared_data.cmds); } shared_data.cmds = NULL; - if (shared_data.windows) { - for (size_t i = 0; i < ltk_array_len(shared_data.windows); i++) { - ltk_window *window = ltk_array_get(shared_data.windows, i); - ltk_widget_destroy(LTK_CAST_WIDGET(window), 0); - } - ltk_array_destroy(window, shared_data.windows); + /* FIXME: maybe special destroy function for cleanup at end that doesn't emit signals, etc. */ + for (size_t i = 0; i < ltk_array_len(shared_data.windows); i++) { + ltk_window *window = ltk_array_get(shared_data.windows, i); + ltk_widget_destroy(LTK_CAST_WIDGET(window), 1); } + ltk_array_destroy(window, shared_data.windows); shared_data.windows = NULL; - if (shared_data.rwindows) - ltk_array_destroy(rwindow, shared_data.rwindows); + ltk_array_destroy(rwindow, shared_data.rwindows); shared_data.rwindows = NULL; + for (size_t i = 0; i < ltk_array_len(shared_data.widgets); i++) { + /* FIXME: not sure about this warning - the idea is to do a deep destroy of the + windows above, then warn if there are any widgets that were not destroyed + (i.e. not referenced in a window), but I'm not sure how useful that is */ + ltk_widget *widget = ltk_array_get(shared_data.widgets, i); + if (widget) { + ltk_warn("Widget not contained within window still stored at index %zu in widget array on exit.\n", i); + ltk_widget_destroy(widget, 0); + } + } + ltk_array_destroy(widget, shared_data.widgets); + shared_data.widgets = NULL; + ltk_array_destroy(gen_num, shared_data.gen_nums); + shared_data.gen_nums = NULL; + ltk_array_destroy(timer, shared_data.timers); + shared_data.timers = NULL; for (size_t i = 0; i < LENGTH(widget_funcs); i++) { if (widget_funcs[i].cleanup) widget_funcs[i].cleanup(); @@ -338,7 +435,7 @@ ltk_deinit(void) { } /* FIXME: check everywhere if initialized already */ -ltk_window * +ltk_widget_id ltk_window_create(const char *title, int x, int y, unsigned int w, unsigned int h) { /* FIXME: more asserts, or maybe global "initialized" flag */ ltk_assert(shared_data.renderdata != NULL); @@ -347,7 +444,7 @@ ltk_window_create(const char *title, int x, int y, unsigned int w, unsigned int ltk_window *window = ltk_window_create_intern(shared_data.renderdata, title, x, y, w, h); ltk_array_append(window, shared_data.windows, window); ltk_array_append(rwindow, shared_data.rwindows, window->renderwindow); - return window; + return LTK_CAST_WIDGET(window)->id; } void @@ -372,24 +469,11 @@ ltk_get_clipboard(void) { } /* FIXME: optimize timer handling - maybe also a sort of priority queue */ -/* FIXME: JUST USE A GENERIC DYNAMIC ARRAY ALREADY!!!!! */ void ltk_unregister_timer(int timer_id) { - for (size_t i = 0; i < timers_num; i++) { - if (timers[i].id == timer_id) { - memmove( - timers + i, - timers + i + 1, - sizeof(ltk_timer) * (timers_num - i - 1) - ); - timers_num--; - size_t sz = ideal_array_size(timers_alloc, timers_num); - if (sz != timers_alloc) { - timers_alloc = sz; - timers = ltk_reallocarray( - timers, sz, sizeof(ltk_timer) - ); - } + for (size_t i = 0; i < ltk_array_len(shared_data.timers); i++) { + if (ltk_array_get(shared_data.timers, i).id == timer_id) { + ltk_array_delete(timer, shared_data.timers, i, 1); return; } } @@ -402,27 +486,22 @@ ltk_register_timer(long first, long repeat, void (*callback)(ltk_callback_arg da first = 0; if (repeat < 0) repeat = 0; - if (timers_num == timers_alloc) { - timers_alloc = ideal_array_size(timers_alloc, timers_num + 1); - timers = ltk_reallocarray( - timers, timers_alloc, sizeof(ltk_timer) - ); - } /* FIXME: better finding of id */ /* FIXME: maybe store sorted by id */ int id = 0; - for (size_t i = 0; i < timers_num; i++) { - if (timers[i].id >= id) - id = timers[i].id + 1; + for (size_t i = 0; i < ltk_array_len(shared_data.timers); i++) { + if (ltk_array_get(shared_data.timers, i).id >= id) + id = ltk_array_get(shared_data.timers, i).id + 1; } - ltk_timer *t = &timers[timers_num++]; - t->callback = callback; - t->data = data; - t->repeat.tv_sec = repeat / 1000; - t->repeat.tv_nsec = (repeat % 1000) * 1000; - t->remaining.tv_sec = first / 1000; - t->remaining.tv_nsec = (first % 1000) * 1000; - t->id = id; + ltk_timer t; + t.callback = callback; + t.data = data; + t.repeat.tv_sec = repeat / 1000; + t.repeat.tv_nsec = (repeat % 1000) * 1000; + t.remaining.tv_sec = first / 1000; + t.remaining.tv_nsec = (first % 1000) * 1000; + t.id = id; + ltk_array_append(timer, shared_data.timers, t); return id; } @@ -435,11 +514,11 @@ str_free_helper(char *elem) { } int -ltk_call_cmd(ltk_widget *caller, ltk_array(cmd) *cmd, const char *text, size_t textlen) { +ltk_call_cmd(ltk_widget_id callerid, ltk_array(cmd) *cmd, const char *text, size_t textlen) { /* FIXME: maybe support stdin/stdout without temporary files by just piping directly */ /* FIXME: support environment variable $TMPDIR */ ltk_cmdinfo info = { - .caller = NULL, .infile = NULL, .outfile = NULL, .pid = -1 + .caller = LTK_WIDGET_ID_NONE, .infile = NULL, .outfile = NULL, .pid = -1 }; ltk_array(str) *cmdstr = ltk_array_create(str, 4); txtbuf *tmpbuf = txtbuf_new(); @@ -511,7 +590,7 @@ ltk_call_cmd(ltk_widget *caller, ltk_array(cmd) *cmd, const char *text, size_t t goto error; } } - ltk_array_append(str, cmdstr, NULL); /* necessary for execve */ + ltk_array_append(str, cmdstr, NULL); /* necessary for execvp */ txtbuf_destroy(tmpbuf); tmpbuf = NULL; @@ -535,7 +614,7 @@ ltk_call_cmd(ltk_widget *caller, ltk_array(cmd) *cmd, const char *text, size_t t ltk_array_destroy_deep(str, cmdstr, &str_free_helper); info.pid = fret; - info.caller = caller; + info.caller = callerid; ltk_array_append(cmdinfo, shared_data.cmds, info); if (infd != -1) @@ -573,7 +652,10 @@ ltk_handle_event(ltk_event *event) { shared_data.cur_kbd = kbd_idx; } else { if (event->any.window_id < ltk_array_len(shared_data.windows)) { - ltk_window_handle_event(ltk_array_get(shared_data.windows, event->any.window_id), event); + ltk_window *window = ltk_array_get(shared_data.windows, event->any.window_id); + ltk_window_handle_event(LTK_CAST_WIDGET(window)->id, event); + } else { + ltk_warn("Invalid window ID %zu in event.", event->any.window_id); } } } diff --git a/src/ltk/ltk.h b/src/ltk/ltk.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -40,8 +40,26 @@ void ltk_unregister_timer(int timer_id); int ltk_register_timer(long first, long repeat, void (*callback)(ltk_callback_arg data), ltk_callback_arg data); /* These are here so they can be added to the global array in ltk.c */ -ltk_window *ltk_window_create(const char *title, int x, int y, unsigned int w, unsigned int h); -void ltk_window_destroy(ltk_widget *self, int shallow); +/* (I guess these could be moved to window.h, I just like to have the declarations in the .h file + corresponding to the .c file containing the definitions) */ +ltk_widget_id ltk_window_create(const char *title, int x, int y, unsigned int w, unsigned int h); + +/* Get widget from ID. An invalid ID or an ID of a deleted widget both are fatal errors + (unless the generation number wrapped around and the slot has been reused, causing a + different widget to be returned). */ +ltk_widget *ltk_get_widget_from_id(ltk_widget_id id); +/* Same as above, but NULL is returned when the widget ID is LTK_WIDGET_ID_NONE. + If there is some other problem with the ID, a fatal error is still caused. */ +ltk_widget *ltk_get_widget_or_null_from_id(ltk_widget_id id); +/* Same as above, but NULL is always returned when a fatal error would otherwise be caused. */ +ltk_widget *ltk_get_widget_or_null_from_id_nofail(ltk_widget_id id); +/* Store a widget and return its ID. If no more widgets can be stored, a fatal + error occurs. */ +ltk_widget_id ltk_store_widget(ltk_widget *widget); +/* Remove a widget that has previously been stored. If no widget exists for the given + ID, a fatal error is caused. */ +void ltk_clear_widget_slot(ltk_widget_id id); +/* FIXME}; move store and clear functions to private header */ /* convenience function to use the default text context */ ltk_text_line *ltk_text_line_create_default(const char *font, int font_size, char *text, int take_over_text, int width); diff --git a/src/ltk/menu.c b/src/ltk/menu.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -95,7 +95,7 @@ static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_re static void ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step); static void ltk_menu_scroll_callback(ltk_callback_arg data); static void stop_scrolling(ltk_menu *menu); -static ltk_widget *ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y); +static ltk_widget_id ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y); static int set_scroll_timer(ltk_menu *menu, int x, int y); static int ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event); static void ltk_menu_hide(ltk_widget *self); @@ -104,40 +104,39 @@ static void unpopup_active_entry(ltk_menuentry *e); static int ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event); static int ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event); static int ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event); -static void shrink_entries(ltk_menu *menu); static void ltk_menu_destroy(ltk_widget *self, int shallow); -static ltk_menu *ltk_menu_create_base(ltk_window *window, int is_submenu); +static ltk_widget_id ltk_menu_create_base(ltk_widget_id windowid, int is_submenu); -static int ltk_menu_remove_child(ltk_widget *self, ltk_widget *widget); -static int ltk_menuentry_remove_child(ltk_widget *self, ltk_widget *widget); +static int ltk_menu_remove_child(ltk_widget *self, ltk_widget_id childid); +static int ltk_menuentry_remove_child(ltk_widget *self, ltk_widget_id childid); -static ltk_widget *ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect); -static ltk_widget *ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget); -static ltk_widget *ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget); +static ltk_widget_id ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect); +static ltk_widget_id ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget_id childid); static void ltk_menuentry_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); static void ltk_menuentry_destroy(ltk_widget *self, int shallow); static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state); static int ltk_menuentry_release(ltk_widget *self); -static ltk_widget *ltk_menu_prev_child(ltk_widget *self, ltk_widget *child); -static ltk_widget *ltk_menu_next_child(ltk_widget *self, ltk_widget *child); -static ltk_widget *ltk_menu_first_child(ltk_widget *self); -static ltk_widget *ltk_menu_last_child(ltk_widget *self); -static ltk_widget *ltk_menuentry_get_child(ltk_widget *self); +static ltk_widget_id ltk_menu_prev_child(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_menu_next_child(ltk_widget *self, ltk_widget_id childid); +static ltk_widget_id ltk_menu_first_child(ltk_widget *self); +static ltk_widget_id ltk_menu_last_child(ltk_widget *self); +static ltk_widget_id ltk_menuentry_get_child(ltk_widget *self); /* FIXME: these functions are named really badly */ -static void recalc_ideal_menu_size_with_notification(ltk_widget *self, ltk_widget *widget); +static void recalc_ideal_menu_size_with_notification(ltk_widget *self, ltk_widget_id childid); static void recalc_ideal_menu_size(ltk_menu *menu); static void ltk_menuentry_set_font(ltk_menuentry *entry); static void ltk_menu_recalc_ideal_size(ltk_widget *self); static void ltk_menuentry_recalc_ideal_size(ltk_widget *self); static void ltk_menuentry_recalc_ideal_size_with_notification(ltk_menuentry *entry); -#define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)e->widget.parent)->is_submenu) +#define IS_SUBMENU(parent) (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU && (LTK_CAST_MENU(parent))->is_submenu) static struct ltk_widget_vtable vtable = { .key_press = NULL, @@ -295,34 +294,38 @@ ltk_submenuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) { static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) { ltk_menuentry *e = LTK_CAST_MENUENTRY(self); - int in_submenu = IN_SUBMENU(e); - int submenus_opened = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus; + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + ltk_menu *parentm = parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU ? LTK_CAST_MENU(parent) : NULL; + ltk_widget *submenu = ltk_get_widget_or_null_from_id(e->submenu); + int in_submenu = IS_SUBMENU(parent); + int submenus_opened = parentm && parentm->popup_submenus; if (!(self->state & (LTK_ACTIVE | LTK_PRESSED))) { /* Note: This only has to take care of the submenu that is the direct child of e because ltk_window_set_active_widget already calls change_state for the whole hierarchy */ unpopup_active_entry(e); } else if ((self->state & LTK_PRESSED) && !(old_state & LTK_PRESSED) && submenus_opened) { - ((ltk_menu *)self->parent)->popup_submenus = 0; + parentm->popup_submenus = 0; } else if (((self->state & LTK_PRESSED) || ((self->state & LTK_ACTIVE) && (in_submenu || submenus_opened))) && - e->submenu && e->submenu->widget.hidden) { + submenu && submenu->hidden) { popup_active_menu(e); - if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENU) - ((ltk_menu *)self->parent)->popup_submenus = 1; + if (parentm) + parentm->popup_submenus = 1; } } -static ltk_widget * +static ltk_widget_id ltk_menuentry_get_child(ltk_widget *self) { ltk_menuentry *e = LTK_CAST_MENUENTRY(self); - if (e->submenu && !e->submenu->widget.hidden) - return &e->submenu->widget; - return NULL; + ltk_widget *submenu = ltk_get_widget_or_null_from_id(e->submenu); + return submenu && !submenu->hidden ? e->submenu : LTK_WIDGET_ID_NONE; } const char * -ltk_menuentry_get_text(ltk_menuentry *entry) { +ltk_menuentry_get_text(ltk_widget_id entryid) { + ltk_widget *entryw = ltk_get_widget_from_id(entryid); + ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw); return ltk_text_line_get_text(entry->text_line); } @@ -332,7 +335,8 @@ ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_r if (self->hidden) return; ltk_menuentry *entry = LTK_CAST_MENUENTRY(self); - int in_submenu = IN_SUBMENU(entry); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + int in_submenu = IS_SUBMENU(parent); struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme; int bw = ltk_size_to_pixel(t->border_width, self->last_dpi); int text_pad = ltk_size_to_pixel(t->text_pad, self->last_dpi); @@ -370,7 +374,7 @@ ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_r ltk_rect text_clip = ltk_rect_intersect(surf_clip, (ltk_rect){text_x, text_y, text_w, text_h}); ltk_text_line_draw_clipped(entry->text_line, draw_surf, text, text_x, text_y, text_clip); - if (in_submenu && entry->submenu) { + if (in_submenu && !LTK_WIDGET_ID_IS_NONE(entry->submenu)) { ltk_point arrow_points[] = { {x + lrect.w - arrow_pad - bw, y + lrect.h / 2}, {x + lrect.w - arrow_pad - bw - arrow_size, y + lrect.h / 2 - arrow_size / 2}, @@ -399,19 +403,17 @@ ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h}; ltk_surface_fill_rect(s, t->background, surf_clip); - ltk_widget *ptr = NULL; - for (size_t i = 0; i < menu->num_entries; i++) { + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { /* FIXME: I guess it could be improved *slightly* by making the clip rect smaller when scrollarrows are shown */ /* draw active entry after others so it isn't hidden with compress_borders */ - if ((menu->entries[i]->widget.state & (LTK_ACTIVE | LTK_PRESSED | LTK_HOVER)) && i < menu->num_entries - 1) { - ptr = &menu->entries[i + 1]->widget; - ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); - ptr = &menu->entries[i]->widget; + ltk_widget *ptr = ltk_get_widget_from_id(ltk_array_get(menu->entries, i)); + if ((ptr->state & (LTK_ACTIVE | LTK_PRESSED | LTK_HOVER)) && i < ltk_array_len(menu->entries) - 1) { + ltk_widget *ptr2 = ltk_get_widget_from_id(ltk_array_get(menu->entries, i + 1)); + ltk_menuentry_draw(ptr2, s, x + ptr2->lrect.x, y + ptr2->lrect.y, ltk_rect_relative(ptr2->lrect, clip_final)); ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); i++; } else { - ptr = &menu->entries[i]->widget; ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); } } @@ -439,8 +441,10 @@ ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3); } if (lrect.h < (int)self->ideal_h) { - ltk_surface_fill_rect(self->window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz}); - ltk_surface_fill_rect(self->window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz}); + ltk_widget *windoww = ltk_get_widget_from_id(self->window); + ltk_window *window = LTK_CAST_WINDOW(windoww); + ltk_surface_fill_rect(window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz}); + ltk_surface_fill_rect(window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz}); ltk_point arrow_points[3] = { {wx + ww / 2, wy + arrow_pad + mbw}, {wx + ww / 2 - arrow_size / 2, wy + arrow_pad + mbw + arrow_size}, @@ -472,7 +476,6 @@ ltk_menu_resize(ltk_widget *self) { struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme; - int bw = ltk_size_to_pixel(t->border_width, self->last_dpi); int pad = ltk_size_to_pixel(t->pad, self->last_dpi); int arrow_pad = ltk_size_to_pixel(t->arrow_pad, self->last_dpi); @@ -490,27 +493,27 @@ ltk_menu_resize(ltk_widget *self) { int cur_abs_x = -(int)menu->x_scroll_offset + start_x + pad; int cur_abs_y = -(int)menu->y_scroll_offset + start_y + pad; - for (size_t i = 0; i < menu->num_entries; i++) { - ltk_menuentry *e = menu->entries[i]; - e->widget.lrect.x = cur_abs_x; - e->widget.lrect.y = cur_abs_y; + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { + ltk_widget *widget = ltk_get_widget_from_id(ltk_array_get(menu->entries, i)); + widget->lrect.x = cur_abs_x; + widget->lrect.y = cur_abs_y; if (menu->is_submenu) { - e->widget.lrect.w = ideal_w - 2 * pad - 2 * mbw; - e->widget.lrect.h = e->widget.ideal_h; - cur_abs_y += e->widget.ideal_h + pad; + widget->lrect.w = ideal_w - 2 * pad - 2 * mbw; + widget->lrect.h = widget->ideal_h; + cur_abs_y += widget->ideal_h + pad; if (et->compress_borders) cur_abs_y -= entry_bw; } else { - e->widget.lrect.w = e->widget.ideal_w; - e->widget.lrect.h = ideal_h - 2 * pad - 2 * mbw; - cur_abs_x += e->widget.ideal_w + pad; + widget->lrect.w = widget->ideal_w; + widget->lrect.h = ideal_h - 2 * pad - 2 * mbw; + cur_abs_x += widget->ideal_w + pad; if (et->compress_borders) cur_abs_x -= entry_bw; } - e->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, e->widget.lrect); + widget->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, widget->lrect); } self->dirty = 1; - ltk_window_invalidate_widget_rect(self->window, self); + ltk_window_invalidate_widget_rect(self->window, self->id); } static void @@ -541,17 +544,18 @@ ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r) { static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret) { + ltk_widget *self = LTK_CAST_WIDGET(menu); struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; - int arrow_pad = ltk_size_to_pixel(theme->arrow_pad, LTK_CAST_WIDGET(menu)->last_dpi); - int arrow_size = ltk_size_to_pixel(theme->arrow_size, LTK_CAST_WIDGET(menu)->last_dpi); + int arrow_pad = ltk_size_to_pixel(theme->arrow_pad, self->last_dpi); + int arrow_size = ltk_size_to_pixel(theme->arrow_size, self->last_dpi); int extra_size = arrow_size * 2 + arrow_pad * 4; *x_ret = 0; *y_ret = 0; - if (menu->widget.lrect.w < (int)menu->widget.ideal_w) { - *x_ret = menu->widget.ideal_w - (menu->widget.lrect.w - extra_size); + if (self->lrect.w < (int)self->ideal_w) { + *x_ret = self->ideal_w - (self->lrect.w - extra_size); } - if (menu->widget.lrect.h < (int)menu->widget.ideal_h) { - *y_ret = menu->widget.ideal_h - (menu->widget.lrect.h - extra_size); + if (self->lrect.h < (int)self->ideal_h) { + *y_ret = self->ideal_h - (self->lrect.h - extra_size); } } @@ -580,16 +584,18 @@ ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step) { /* FIXME: sensible epsilon? */ if (fabs(x_old - menu->x_scroll_offset) > 0.01 || fabs(y_old - menu->y_scroll_offset) > 0.01) { - ltk_menu_resize(&menu->widget); + ltk_widget *self = LTK_CAST_WIDGET(menu); + ltk_menu_resize(self); menu->widget.dirty = 1; - ltk_window_invalidate_widget_rect(menu->widget.window, &menu->widget); + ltk_window_invalidate_widget_rect(self->window, self->id); } } /* FIXME: show scroll arrow disabled when nothing further */ static void ltk_menu_scroll_callback(ltk_callback_arg data) { - ltk_widget *self = LTK_CAST_ARG_WIDGET(data); + ltk_widget_id id = LTK_CAST_ARG_WIDGET_ID(data); + ltk_widget *self = ltk_get_widget_from_id(id); ltk_menu *menu = LTK_CAST_MENU(self); ltk_menu_scroll( menu, @@ -609,7 +615,7 @@ stop_scrolling(ltk_menu *menu) { } /* FIXME: should ideal_w, ideal_h just be int? */ -static ltk_widget * +static ltk_widget_id ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) { ltk_menu *menu = LTK_CAST_MENU(self); struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; @@ -629,36 +635,39 @@ ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) { } /* FIXME: use crect for this */ if (!ltk_collide_rect((ltk_rect){start_x, start_y, end_x - start_x, end_y - start_y}, x, y)) - return NULL; + return LTK_WIDGET_ID_NONE; - for (size_t i = 0; i < menu->num_entries; i++) { - if (ltk_collide_rect(menu->entries[i]->widget.crect, x, y)) - return &menu->entries[i]->widget; + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { + ltk_widget_id childid = ltk_array_get(menu->entries, i); + ltk_widget *child = ltk_get_widget_from_id(childid); + if (ltk_collide_rect(child->crect, x, y)) + return childid; } - return NULL; + return LTK_WIDGET_ID_NONE; } /* FIXME: make sure timers are always destroyed when widget is destroyed */ static int set_scroll_timer(ltk_menu *menu, int x, int y) { + ltk_widget *self = LTK_CAST_WIDGET(menu); /* this check probably isn't necessary, but whatever */ - if (x < 0 || y < 0 || x >= menu->widget.lrect.w || y >= menu->widget.lrect.h) + if (x < 0 || y < 0 || x >= self->lrect.w || y >= self->lrect.h) return 0; int t = 0, b = 0, l = 0,r = 0; struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; - int arrow_pad = ltk_size_to_pixel(theme->arrow_pad, LTK_CAST_WIDGET(menu)->last_dpi); - int raw_arrow_size = ltk_size_to_pixel(theme->arrow_size, LTK_CAST_WIDGET(menu)->last_dpi); + int arrow_pad = ltk_size_to_pixel(theme->arrow_pad, self->last_dpi); + int raw_arrow_size = ltk_size_to_pixel(theme->arrow_size, self->last_dpi); int arrow_size = raw_arrow_size + arrow_pad * 2; - if (menu->widget.lrect.w < (int)menu->widget.ideal_w) { + if (self->lrect.w < (int)self->ideal_w) { if (x < arrow_size) l = 1; - else if (x > menu->widget.lrect.w - arrow_size) + else if (x > self->lrect.w - arrow_size) r = 1; } - if (menu->widget.lrect.h < (int)menu->widget.ideal_h) { + if (self->lrect.h < (int)self->ideal_h) { if (y < arrow_size) t = 1; - else if (y > menu->widget.lrect.h - arrow_size) + else if (y > self->lrect.h - arrow_size) b = 1; } if (t == menu->scroll_top_hover && @@ -671,8 +680,8 @@ set_scroll_timer(ltk_menu *menu, int x, int y) { menu->scroll_bottom_hover = b; menu->scroll_left_hover = l; menu->scroll_right_hover = r; - ltk_menu_scroll_callback(LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(menu))); - menu->scroll_timer_id = ltk_register_timer(0, 300, &ltk_menu_scroll_callback, LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(menu))); + ltk_menu_scroll_callback(LTK_MAKE_ARG_WIDGET(self)); + menu->scroll_timer_id = ltk_register_timer(0, 300, &ltk_menu_scroll_callback, LTK_MAKE_ARG_WIDGET_ID(self->id)); return 1; } @@ -681,13 +690,13 @@ set_scroll_timer(ltk_menu *menu, int x, int y) { be hidden when scrolling in a menu. Maybe widgets also need a "visible rect"? */ static int ltk_menuentry_release(ltk_widget *self) { - ltk_menuentry *e = LTK_CAST_MENUENTRY(self); - int in_submenu = IN_SUBMENU(e); - int keep_popup = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus; + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + int in_submenu = IS_SUBMENU(parent); + int keep_popup = parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU && (LTK_CAST_MENU(parent))->popup_submenus; if (in_submenu || !keep_popup) { ltk_window_unregister_all_popups(self->window); } - ltk_widget_emit_signal(self, LTK_MENUENTRY_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_MENUENTRY_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST); return 1; } @@ -717,12 +726,14 @@ ltk_menu_hide(ltk_widget *self) { ltk_unregister_timer(menu->scroll_timer_id); menu->scroll_bottom_hover = menu->scroll_top_hover = 0; menu->scroll_left_hover = menu->scroll_right_hover = 0; - ltk_window_unregister_popup(self->window, self); - ltk_window_invalidate_widget_rect(self->window, self); + ltk_window_unregister_popup(self->window, self->id); + ltk_window_invalidate_widget_rect(self->window, self->id); /* FIXME: this is really ugly/hacky */ - if (menu->unpopup_submenus_on_hide && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY && - self->parent->parent && self->parent->parent->vtable->type == LTK_WIDGET_MENU) { - ((ltk_menu *)self->parent->parent)->popup_submenus = 0; + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + ltk_widget *pparent = parent ? ltk_get_widget_or_null_from_id(parent->parent) : NULL; + if (menu->unpopup_submenus_on_hide && parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY && + pparent && LTK_WIDGET_TYPE(pparent) == LTK_WIDGET_MENU) { + (LTK_CAST_MENU(pparent))->popup_submenus = 0; } menu->unpopup_submenus_on_hide = 1; } @@ -730,31 +741,36 @@ ltk_menu_hide(ltk_widget *self) { /* FIXME: hacky because entries need to know about their parents to be able to properly position the popup */ static void popup_active_menu(ltk_menuentry *e) { - if (!e->submenu) + ltk_widget *self = LTK_CAST_WIDGET(e); + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu); + if (!submenuw) return; + ltk_menu *submenu = LTK_CAST_MENU(submenuw); int in_submenu = 0, was_opened_left = 0; - ltk_rect menu_rect = e->widget.lrect; - ltk_point entry_global = ltk_widget_pos_to_global(LTK_CAST_WIDGET(e), 0, 0); + ltk_rect menu_rect = self->lrect; + ltk_point entry_global = ltk_widget_pos_to_global(self, 0, 0); ltk_point menu_global; - if (LTK_CAST_WIDGET(e)->parent && LTK_CAST_WIDGET(e)->parent->vtable->type == LTK_WIDGET_MENU) { - ltk_menu *menu = LTK_CAST_MENU(LTK_CAST_WIDGET(e)->parent); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU) { + ltk_menu *menu = LTK_CAST_MENU(parent); in_submenu = menu->is_submenu; was_opened_left = menu->was_opened_left; - menu_rect = menu->widget.lrect; - menu_global = ltk_widget_pos_to_global(LTK_CAST_WIDGET(e)->parent, 0, 0); + menu_rect = LTK_CAST_WIDGET(menu)->lrect; + menu_global = ltk_widget_pos_to_global(parent, 0, 0); } else { - menu_global = ltk_widget_pos_to_global(LTK_CAST_WIDGET(e), 0, 0); + menu_global = ltk_widget_pos_to_global(self, 0, 0); } - int win_w = e->widget.window->rect.w; - int win_h = e->widget.window->rect.h; - ltk_menu *submenu = e->submenu; - ltk_widget_recalc_ideal_size(LTK_CAST_WIDGET(submenu)); - int ideal_w = submenu->widget.ideal_w; - int ideal_h = submenu->widget.ideal_h; + ltk_widget *windoww = ltk_get_widget_from_id(self->window); + ltk_window *window = LTK_CAST_WINDOW(windoww); + int win_w = window->rect.w; + int win_h = window->rect.h; + ltk_widget_recalc_ideal_size(submenuw); + int ideal_w = submenuw->ideal_w; + int ideal_h = submenuw->ideal_h; int x_final = 0, y_final = 0, w_final = ideal_w, h_final = ideal_h; - int submenu_bw = ltk_size_to_pixel(submenu_theme.border_width, LTK_CAST_WIDGET(e)->last_dpi); - int submenu_pad = ltk_size_to_pixel(submenu_theme.pad, LTK_CAST_WIDGET(e)->last_dpi); - int menu_bw = ltk_size_to_pixel(menu_theme.border_width, LTK_CAST_WIDGET(e)->last_dpi); + int submenu_bw = ltk_size_to_pixel(submenu_theme.border_width, self->last_dpi); + int submenu_pad = ltk_size_to_pixel(submenu_theme.pad, self->last_dpi); + int menu_bw = ltk_size_to_pixel(menu_theme.border_width, self->last_dpi); if (in_submenu) { int space_left = menu_global.x; int space_right = win_w - (menu_global.x + menu_rect.w); @@ -846,21 +862,23 @@ popup_active_menu(ltk_menuentry *e) { submenu->widget.lrect.y = y_final; submenu->widget.lrect.w = w_final; submenu->widget.lrect.h = h_final; - submenu->widget.crect = LTK_CAST_WIDGET(submenu)->lrect; + submenu->widget.crect = submenuw->lrect; submenu->widget.dirty = 1; submenu->widget.hidden = 0; submenu->popup_submenus = 0; submenu->unpopup_submenus_on_hide = 1; - ltk_widget_resize(LTK_CAST_WIDGET(submenu)); - ltk_window_register_popup(LTK_CAST_WIDGET(e)->window, LTK_CAST_WIDGET(submenu)); - ltk_window_invalidate_widget_rect(LTK_CAST_WIDGET(submenu)->window, LTK_CAST_WIDGET(submenu)); + ltk_widget_resize(submenuw); + ltk_window_register_popup(self->window, e->submenu); + ltk_window_invalidate_widget_rect(submenuw->window, submenuw->id); } static void unpopup_active_entry(ltk_menuentry *e) { - if (e->submenu && !e->submenu->widget.hidden) { - e->submenu->unpopup_submenus_on_hide = 0; - ltk_widget_hide(&e->submenu->widget); + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu); + if (submenuw && !submenuw->hidden) { + ltk_menu *submenu = LTK_CAST_MENU(submenuw); + submenu->unpopup_submenus_on_hide = 0; + ltk_widget_hide(submenuw); } } @@ -889,18 +907,18 @@ ltk_menu_recalc_ideal_size(ltk_widget *self) { recalc_ideal_menu_size(menu); } -static ltk_menu * -ltk_menu_create_base(ltk_window *window, int is_submenu) { +static ltk_widget_id +ltk_menu_create_base(ltk_widget_id windowid, int is_submenu) { ltk_menu *menu = ltk_malloc(sizeof(ltk_menu)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(menu), window, &vtable, 0, 0); + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(menu), windowid, &vtable, 0, 0); + ltk_widget *self = LTK_CAST_WIDGET(menu); - int menu_pad = ltk_size_to_pixel(menu_theme.pad, LTK_CAST_WIDGET(menu)->last_dpi); - menu->widget.ideal_w = menu_pad; - menu->widget.ideal_h = menu_pad; - menu->widget.dirty = 1; + int menu_pad = ltk_size_to_pixel(menu_theme.pad, self->last_dpi); + self->ideal_w = menu_pad; + self->ideal_h = menu_pad; + self->dirty = 1; - menu->entries = NULL; - menu->num_entries = menu->num_alloc = 0; + menu->entries = ltk_array_create(widget_id, 1); menu->x_scroll_offset = menu->y_scroll_offset = 0; menu->is_submenu = is_submenu; menu->was_opened_left = 0; @@ -914,34 +932,28 @@ ltk_menu_create_base(ltk_window *window, int is_submenu) { unnecessary redrawing */ recalc_ideal_menu_size(menu); - return menu; + return id; } -ltk_menu * -ltk_menu_create(ltk_window *window) { - return ltk_menu_create_base(window, 0); +ltk_widget_id +ltk_menu_create(ltk_widget_id windowid) { + return ltk_menu_create_base(windowid, 0); } -ltk_menu * -ltk_submenu_create(ltk_window *window) { - return ltk_menu_create_base(window, 1); +ltk_widget_id +ltk_submenu_create(ltk_widget_id windowid) { + return ltk_menu_create_base(windowid, 1); } static int -insert_entry(ltk_menu *menu, ltk_menuentry *e, size_t idx) { - if (idx > menu->num_entries) +insert_entry(ltk_widget_id menuid, ltk_widget_id menuentryid, size_t idx) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + if (!menu->entries) + menu->entries = ltk_array_create(widget_id, 1); + if (idx > ltk_array_len(menu->entries)) return 1; - if (menu->num_entries == menu->num_alloc) { - menu->num_alloc = ideal_array_size(menu->num_alloc, menu->num_entries + 1); - menu->entries = ltk_reallocarray(menu->entries, menu->num_alloc, sizeof(ltk_menuentry *)); - } - memmove( - menu->entries + idx + 1, - menu->entries + idx, - sizeof(ltk_menuentry *) * (menu->num_entries - idx) - ); - menu->num_entries++; - menu->entries[idx] = e; + ltk_array_insert(widget_id, menu->entries, idx, menuentryid); return 0; } @@ -950,71 +962,77 @@ recalc_ideal_menu_size(ltk_menu *menu) { struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme; - int bw = ltk_size_to_pixel(t->border_width, LTK_CAST_WIDGET(menu)->last_dpi); - int pad = ltk_size_to_pixel(t->pad, LTK_CAST_WIDGET(menu)->last_dpi); - int entry_bw = ltk_size_to_pixel(et->border_width, LTK_CAST_WIDGET(menu)->last_dpi); + ltk_widget *self = LTK_CAST_WIDGET(menu); + int bw = ltk_size_to_pixel(t->border_width, self->last_dpi); + int pad = ltk_size_to_pixel(t->pad, self->last_dpi); + int entry_bw = ltk_size_to_pixel(et->border_width, self->last_dpi); - menu->widget.ideal_w = menu->widget.ideal_h = pad + bw * 2; - ltk_menuentry *e; - for (size_t i = 0; i < menu->num_entries; i++) { - e = menu->entries[i]; - ltk_widget_recalc_ideal_size(LTK_CAST_WIDGET(e)); + self->ideal_w = self->ideal_h = pad + bw * 2; + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { + ltk_widget_id id = ltk_array_get(menu->entries, i); + ltk_widget *ew = ltk_get_widget_from_id(id); + ltk_widget_recalc_ideal_size(ew); if (menu->is_submenu) { - menu->widget.ideal_w = MAX((pad + bw) * 2 + (int)e->widget.ideal_w, (int)menu->widget.ideal_w); - menu->widget.ideal_h += e->widget.ideal_h + pad; + self->ideal_w = MAX((pad + bw) * 2 + (int)ew->ideal_w, (int)self->ideal_w); + self->ideal_h += ew->ideal_h + pad; if (et->compress_borders && i != 0) - menu->widget.ideal_h -= entry_bw; + self->ideal_h -= entry_bw; } else { - menu->widget.ideal_w += e->widget.ideal_w + pad; + self->ideal_w += ew->ideal_w + pad; if (et->compress_borders && i != 0) - menu->widget.ideal_w -= entry_bw; - menu->widget.ideal_h = MAX((pad + bw) * 2 + (int)e->widget.ideal_h, (int)menu->widget.ideal_h); + self->ideal_w -= entry_bw; + self->ideal_h = MAX((pad + bw) * 2 + (int)ew->ideal_h, (int)self->ideal_h); } } } /* FIXME: rename these functions */ static void -recalc_ideal_menu_size_with_notification(ltk_widget *self, ltk_widget *widget) { +recalc_ideal_menu_size_with_notification(ltk_widget *self, ltk_widget_id childid) { ltk_menu *menu = LTK_CAST_MENU(self); + ltk_widget *child = ltk_get_widget_from_id(childid); /* If widget with size change is submenu, it doesn't affect this menu */ - if (widget && widget->vtable->type == LTK_WIDGET_MENU) { - ltk_widget_resize(widget); + if (child && LTK_WIDGET_TYPE(child) == LTK_WIDGET_MENU) { + ltk_widget_resize(child); return; } unsigned int old_w = self->ideal_w, old_h = self->ideal_h; recalc_ideal_menu_size(menu); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); if ((old_w != self->ideal_w || old_h != self->ideal_h) && - self->parent && self->parent->vtable->child_size_change) { - self->parent->vtable->child_size_change(self->parent, self); + parent && parent->vtable->child_size_change) { + parent->vtable->child_size_change(parent, self->id); } else { ltk_menu_resize(self); } self->dirty = 1; if (!self->hidden) - ltk_window_invalidate_widget_rect(self->window, self); + ltk_window_invalidate_widget_rect(self->window, self->id); } static void recalc_ideal_menuentry_size(ltk_menuentry *entry) { - int in_submenu = IN_SUBMENU(entry); + ltk_widget *self = LTK_CAST_WIDGET(entry); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + int in_submenu = IS_SUBMENU(parent); struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme; - int bw = ltk_size_to_pixel(t->border_width, LTK_CAST_WIDGET(entry)->last_dpi); - int text_pad = ltk_size_to_pixel(t->text_pad, LTK_CAST_WIDGET(entry)->last_dpi); - int arrow_pad = ltk_size_to_pixel(t->arrow_pad, LTK_CAST_WIDGET(entry)->last_dpi); - int arrow_size = ltk_size_to_pixel(t->arrow_size, LTK_CAST_WIDGET(entry)->last_dpi); - int extra_size = (in_submenu && entry->submenu) ? arrow_pad * 2 + arrow_size : 0; + int bw = ltk_size_to_pixel(t->border_width, self->last_dpi); + int text_pad = ltk_size_to_pixel(t->text_pad, self->last_dpi); + int arrow_pad = ltk_size_to_pixel(t->arrow_pad, self->last_dpi); + int arrow_size = ltk_size_to_pixel(t->arrow_size, self->last_dpi); + int extra_size = (in_submenu && !LTK_WIDGET_ID_IS_NONE(entry->submenu)) ? arrow_pad * 2 + arrow_size : 0; int text_w, text_h; ltk_text_line_get_size(entry->text_line, &text_w, &text_h); - entry->widget.ideal_w = text_w + extra_size + (text_pad + bw) * 2; - entry->widget.ideal_h = MAX(text_h + text_pad * 2, extra_size) + bw * 2; + self->ideal_w = text_w + extra_size + (text_pad + bw) * 2; + self->ideal_h = MAX(text_h + text_pad * 2, extra_size) + bw * 2; } static void ltk_menuentry_recalc_ideal_size(ltk_widget *self) { ltk_menuentry *entry = LTK_CAST_MENUENTRY(self); - struct entry_theme *t = IN_SUBMENU(entry) ? &submenu_entry_theme : &menu_entry_theme; + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + struct entry_theme *t = IS_SUBMENU(parent) ? &submenu_entry_theme : &menu_entry_theme; int font_size = ltk_size_to_pixel(t->font_size, self->last_dpi); ltk_text_line_set_font_size(entry->text_line, font_size); recalc_ideal_menuentry_size(entry); @@ -1022,8 +1040,10 @@ ltk_menuentry_recalc_ideal_size(ltk_widget *self) { static void ltk_menuentry_set_font(ltk_menuentry *entry) { - struct entry_theme *t = IN_SUBMENU(entry) ? &submenu_entry_theme : &menu_entry_theme; - int font_size = ltk_size_to_pixel(t->font_size, LTK_CAST_WIDGET(entry)->last_dpi); + ltk_widget *self = LTK_CAST_WIDGET(entry); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + struct entry_theme *t = IS_SUBMENU(parent) ? &submenu_entry_theme : &menu_entry_theme; + int font_size = ltk_size_to_pixel(t->font_size, self->last_dpi); ltk_text_line_set_font(entry->text_line, t->font, font_size); } @@ -1031,36 +1051,40 @@ static void ltk_menuentry_recalc_ideal_size_with_notification(ltk_menuentry *entry) { recalc_ideal_menuentry_size(entry); /* FIXME: only call if something changed */ - if (entry->widget.parent && entry->widget.parent->vtable->child_size_change) { - entry->widget.parent->vtable->child_size_change(entry->widget.parent, (ltk_widget *)entry); + ltk_widget *self = LTK_CAST_WIDGET(entry); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + if (parent && parent->vtable->child_size_change) { + parent->vtable->child_size_change(parent, self->id); } } -ltk_menuentry * -ltk_menuentry_create(ltk_window *window, const char *text) { +ltk_widget_id +ltk_menuentry_create(ltk_widget_id windowid, const char *text) { ltk_menuentry *e = ltk_malloc(sizeof(ltk_menuentry)); - ltk_fill_widget_defaults(&e->widget, window, &entry_vtable, 0, 0); + ltk_widget *self = LTK_CAST_WIDGET(e); + ltk_widget_id id = ltk_initialize_widget(self, windowid, &entry_vtable, 0, 0); e->text_line = ltk_text_line_create_const_text_default( menu_entry_theme.font, - ltk_size_to_pixel(menu_entry_theme.font_size, e->widget.last_dpi), + ltk_size_to_pixel(menu_entry_theme.font_size, self->last_dpi), text, -1 ); - e->submenu = NULL; + e->submenu = LTK_WIDGET_ID_NONE; /* Note: This is only set as a dummy value! The actual ideal size can't be set until it is part of a menu because it needs to know which theme it should use */ recalc_ideal_menuentry_size(e); - e->widget.dirty = 1; - return e; + self->dirty = 1; + return id; } static int -ltk_menuentry_remove_child(ltk_widget *self, ltk_widget *widget) { +ltk_menuentry_remove_child(ltk_widget *self, ltk_widget_id widgetid) { ltk_menuentry *e = LTK_CAST_MENUENTRY(self); - if (widget != LTK_CAST_WIDGET(e->submenu)) + if (LTK_WIDGET_ID_IS_NONE(widgetid) || !LTK_WIDGET_ID_EQUAL(widgetid, e->submenu)) return 1; - widget->parent = NULL; - e->submenu = NULL; + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + widget->parent = LTK_WIDGET_ID_NONE; + e->submenu = LTK_WIDGET_ID_NONE; ltk_menuentry_recalc_ideal_size_with_notification(e); return 0; } @@ -1071,155 +1095,164 @@ ltk_menuentry_destroy(ltk_widget *self, int shallow) { ltk_text_line_destroy(e->text_line); /* FIXME: function to call when parent is destroyed */ /* also function to call when parent added */ - if (e->submenu) { - e->submenu->widget.parent = NULL; - if (!shallow) { - ltk_widget_destroy(LTK_CAST_WIDGET(e->submenu), shallow); - } + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu); + if (submenuw) { + submenuw->parent = LTK_WIDGET_ID_NONE; + if (!shallow) + ltk_widget_destroy(submenuw, shallow); } ltk_free(e); } int -ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx) { - if (entry->widget.parent) +ltk_menu_insert_entry(ltk_widget_id menuid, ltk_widget_id entryid, size_t idx) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_widget *entryw = ltk_get_widget_from_id(entryid); + if (!LTK_WIDGET_ID_IS_NONE(entryw->parent)) return 1; /* already child of some widget */ - if (insert_entry(menu, entry, idx)) + if (insert_entry(menuid, entryid, idx)) return 2; /* invalid index */ - entry->widget.parent = &menu->widget; + entryw->parent = menuid; /* the theme may have changed if the entry switched between menu and submenu */ - ltk_menuentry_set_font(entry); - ltk_menuentry_recalc_ideal_size_with_notification(entry); - recalc_ideal_menu_size_with_notification(LTK_CAST_WIDGET(menu), NULL); - menu->widget.dirty = 1; + ltk_menuentry_set_font(LTK_CAST_MENUENTRY(entryw)); + ltk_menuentry_recalc_ideal_size_with_notification(LTK_CAST_MENUENTRY(entryw)); + recalc_ideal_menu_size_with_notification(menuw, entryid); + menuw->dirty = 1; return 0; } int -ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry) { - return ltk_menu_insert_entry(menu, entry, menu->num_entries); +ltk_menu_add_entry(ltk_widget_id menuid, ltk_widget_id entryid) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + return ltk_menu_insert_entry(menuid, entryid, ltk_array_len(menu->entries)); } /* FIXME: maybe allow any menu and just change is_submenu (also need to recalculate size then) */ int -ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu) { +ltk_menuentry_attach_submenu(ltk_widget_id entryid, ltk_widget_id submenuid) { + ltk_widget *entryw = ltk_get_widget_from_id(entryid); + ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw); + ltk_widget *submenuw = ltk_get_widget_from_id(submenuid); + ltk_menu *submenu = LTK_CAST_MENU(submenuw); if (!submenu->is_submenu) return 1; /* menu is not submenu */ - else if (e->submenu) + else if (!LTK_WIDGET_ID_IS_NONE(entry->submenu)) return 2; /* entry already contains submenu */ - e->submenu = submenu; - ltk_menuentry_recalc_ideal_size_with_notification(e); - e->widget.dirty = 1; - if (submenu) { - submenu->widget.hidden = 1; - submenu->widget.parent = LTK_CAST_WIDGET(e); - } - if (!e->widget.hidden) - ltk_window_invalidate_widget_rect(e->widget.window, LTK_CAST_WIDGET(e)); + entry->submenu = submenuid; + ltk_menuentry_recalc_ideal_size_with_notification(entry); + entryw->dirty = 1; + submenuw->hidden = 1; + submenuw->parent = entryid; + if (!entryw->hidden) + ltk_window_invalidate_widget_rect(entryw->window, entryw->id); return 0; } /* FIXME: hide all entries when menu hidden? */ -static void -shrink_entries(ltk_menu *menu) { - size_t new_alloc = ideal_array_size(menu->num_alloc, menu->num_entries); - if (new_alloc != menu->num_alloc) { - menu->entries = ltk_reallocarray(menu->entries, new_alloc, sizeof(ltk_menuentry *)); - menu->num_alloc = new_alloc; - } -} - -ltk_menuentry * -ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx) { - if (idx >= menu->num_entries) - return NULL; /* invalid index */ - menu->entries[idx]->widget.parent = NULL; +ltk_widget_id +ltk_menu_remove_entry_index(ltk_widget_id menuid, size_t idx) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + if (idx >= ltk_array_len(menu->entries)) + return LTK_WIDGET_ID_NONE; /* invalid index */ + ltk_widget_id entryid = ltk_array_get(menu->entries, idx); + ltk_widget *entry = ltk_get_widget_from_id(entryid); + entry->parent = LTK_WIDGET_ID_NONE; /* I don't think this is needed because the entry isn't shown anywhere. Its size will be recalculated once it is added to a menu again. */ /* ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[idx]); */ - ltk_menuentry *ret = menu->entries[idx]; - memmove( - menu->entries + idx, - menu->entries + idx + 1, - sizeof(ltk_menuentry *) * (menu->num_entries - idx - 1) - ); - menu->num_entries--; - shrink_entries(menu); - recalc_ideal_menu_size_with_notification(LTK_CAST_WIDGET(menu), NULL); - return ret; + ltk_array_delete(widget_id, menu->entries, idx, 1); + recalc_ideal_menu_size_with_notification(menuw, LTK_WIDGET_ID_NONE); + return entryid; } size_t -ltk_menu_get_entry_index(ltk_menu *menu, ltk_menuentry *entry) { - for (size_t i = 0; i < menu->num_entries; i++) { - if (menu->entries[i] == entry) +ltk_menu_get_entry_index(ltk_widget_id menuid, ltk_widget_id entryid) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { + ltk_widget_id id = ltk_array_get(menu->entries, i); + if (LTK_WIDGET_ID_EQUAL(id, entryid)) return i; } return SIZE_MAX; } size_t -ltk_menu_get_num_entries(ltk_menu *menu) { - return menu->num_entries; +ltk_menu_get_num_entries(ltk_widget_id menuid) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + return ltk_array_len(menu->entries); } -ltk_menuentry * -ltk_menu_get_entry(ltk_menu *menu, size_t idx) { - if (idx >= menu->num_entries) - return NULL; - return menu->entries[idx]; +ltk_widget_id +ltk_menu_get_entry(ltk_widget_id menuid, size_t idx) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + if (idx >= ltk_array_len(menu->entries)) + return LTK_WIDGET_ID_NONE; + return ltk_array_get(menu->entries, idx); } int -ltk_menu_remove_entry(ltk_menu *menu, ltk_menuentry *entry) { - size_t idx = ltk_menu_get_entry_index(menu, entry); - if (idx >= menu->num_entries) +ltk_menu_remove_entry(ltk_widget_id menuid, ltk_widget_id entryid) { + size_t idx = ltk_menu_get_entry_index(menuid, entryid); + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + if (idx >= ltk_array_len(menu->entries)) return 1; - ltk_menuentry *ret = ltk_menu_remove_entry_index(menu, idx); - if (!ret) /* shouldn't be possible */ + ltk_widget_id ret = ltk_menu_remove_entry_index(menuid, idx); + if (LTK_WIDGET_ID_IS_NONE(ret)) /* shouldn't be possible */ return 1; return 0; } static int -ltk_menu_remove_child(ltk_widget *self, ltk_widget *child) { +ltk_menu_remove_child(ltk_widget *self, ltk_widget_id childid) { /* FIXME: this is kind of ugly - it's just there to avoid aborting if a different widget type is given */ - if (child->vtable->type != LTK_WIDGET_MENUENTRY) + ltk_widget *child = ltk_get_widget_from_id(childid); + if (LTK_WIDGET_TYPE(child) != LTK_WIDGET_MENUENTRY) return 1; - return ltk_menu_remove_entry(LTK_CAST_MENU(self), LTK_CAST_MENUENTRY(child)); + return ltk_menu_remove_entry(self->id, childid); } /* TODO: add function to also destroy the entries when removing them */ +/* -> note: ltk_menu_remove_all_entries is used by ltk_menu_destroy, so + that has to be updated if this changes */ void -ltk_menu_remove_all_entries(ltk_menu *menu) { - for (size_t i = 0; i < menu->num_entries; i++) { - menu->entries[i]->widget.parent = NULL; +ltk_menu_remove_all_entries(ltk_widget_id menuid) { + ltk_widget *menuw = ltk_get_widget_from_id(menuid); + ltk_menu *menu = LTK_CAST_MENU(menuw); + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { + ltk_widget *child = ltk_get_widget_from_id(ltk_array_get(menu->entries, i)); + child->parent = LTK_WIDGET_ID_NONE; /* I don't think this is needed because the entry isn't shown anywhere. Its size will be recalculated once it is added to a menu again. */ /* ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[i]); */ } - menu->num_entries = menu->num_alloc = 0; - ltk_free0(menu->entries); - recalc_ideal_menu_size_with_notification(LTK_CAST_WIDGET(menu), NULL); + ltk_array_reset(widget_id, menu->entries); + recalc_ideal_menu_size_with_notification(menuw, LTK_WIDGET_ID_NONE); } -static ltk_widget * +static ltk_widget_id ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect) { ltk_menu *menu = LTK_CAST_MENU(self); - ltk_widget *minw = NULL; + ltk_widget_id minw = LTK_WIDGET_ID_NONE; int min_dist = INT_MAX; - for (size_t i = 0; i < menu->num_entries; i++) { - ltk_rect r = LTK_CAST_WIDGET(menu->entries[i])->lrect; + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { + ltk_widget *child = ltk_get_widget_from_id(ltk_array_get(menu->entries, i)); + ltk_rect r = child->lrect; /* FIXME: maybe simplify this since all entries are laid out horizontal/vertical anyways */ int dist = ltk_rect_fakedist(rect, r); if (dist < min_dist) { min_dist = dist; - minw = LTK_CAST_WIDGET(menu->entries[i]); + minw = ltk_array_get(menu->entries, i); } } return minw; @@ -1233,167 +1266,175 @@ ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect) { the current active hierarchy as child widget again, and nearest_child on that submenu will (probably) give the bottom widget again, so nothing changes except that all submenus except for the first and second one disappear */ -static ltk_widget * -ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget_id childid) { ltk_menu *menu = LTK_CAST_MENU(self); - ltk_menuentry *entry = LTK_CAST_MENUENTRY(widget); - ltk_widget *left = NULL; + ltk_widget *entryw = ltk_get_widget_from_id(childid); + ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw); + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(entry->submenu); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + ltk_widget_id left = LTK_WIDGET_ID_NONE; if (!menu->is_submenu) { - left = ltk_menu_prev_child(self, widget); - } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY && - entry->submenu && - !entry->submenu->widget.hidden && - entry->submenu->was_opened_left) { - left = LTK_CAST_WIDGET(entry->submenu); - } else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { - ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent); - if (!menu->was_opened_left && IN_SUBMENU(e)) { + left = ltk_menu_prev_child(self, childid); + } else if (submenuw && !submenuw->hidden && LTK_CAST_MENU(submenuw)->was_opened_left) { + left = submenuw->id; + } else if (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) { + ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent); + if (!menu->was_opened_left && IS_SUBMENU(grandparent)) { left = self->parent; - } else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { - left = ltk_menu_prev_child(e->widget.parent, &e->widget); + } else if (!IS_SUBMENU(grandparent) && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) { + left = ltk_menu_prev_child(grandparent, self->parent); } } return left; } -static ltk_widget * -ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget_id childid) { ltk_menu *menu = LTK_CAST_MENU(self); - ltk_menuentry *entry = LTK_CAST_MENUENTRY(widget); - ltk_widget *right = NULL; + ltk_widget *entryw = ltk_get_widget_from_id(childid); + ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw); + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(entry->submenu); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + ltk_widget_id right = LTK_WIDGET_ID_NONE; if (!menu->is_submenu) { - right = ltk_menu_next_child(self, widget); - } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY && - entry->submenu && - !entry->submenu->widget.hidden && - !entry->submenu->was_opened_left) { - right = LTK_CAST_WIDGET(entry->submenu); - } else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { - ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent); - if (menu->was_opened_left && IN_SUBMENU(e)) { + right = ltk_menu_next_child(self, childid); + } else if (submenuw && !submenuw->hidden && !LTK_CAST_MENU(submenuw)->was_opened_left) { + right = entry->submenu; + } else if (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) { + ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent); + if (menu->was_opened_left && IS_SUBMENU(grandparent)) { right = self->parent; - } else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { - right = ltk_menu_next_child(e->widget.parent, LTK_CAST_WIDGET(e)); + } else if (!IS_SUBMENU(grandparent) && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) { + right = ltk_menu_next_child(grandparent, self->parent); } } return right; } -static ltk_widget * -ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget_id childid) { ltk_menu *menu = LTK_CAST_MENU(self); - ltk_widget *above = NULL; + ltk_widget *childw = ltk_get_widget_from_id(childid); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + ltk_widget_id above = LTK_WIDGET_ID_NONE; if (menu->is_submenu) { - above = ltk_menu_prev_child(self, widget); - if (!above && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { - ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent); - if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { - ltk_menu *pmenu = LTK_CAST_MENU(e->widget.parent); - if (!menu->was_opened_above && !pmenu->is_submenu) { + above = ltk_menu_prev_child(self, childid); + if (LTK_WIDGET_ID_IS_NONE(above) && parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) { + ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent); + if (grandparent && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) { + ltk_menu *gpmenu = LTK_CAST_MENU(grandparent); + if (!menu->was_opened_above && !gpmenu->is_submenu) { above = self->parent; - } else if (pmenu->is_submenu) { - above = ltk_menu_prev_child(e->widget.parent, LTK_CAST_WIDGET(e)); + } else if (gpmenu->is_submenu) { + above = ltk_menu_prev_child(grandparent, self->parent); } } } - } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) { - ltk_menuentry *e = LTK_CAST_MENUENTRY(widget); - if (e->submenu && !e->submenu->widget.hidden && e->submenu->was_opened_above) { - above = LTK_CAST_WIDGET(e->submenu); + } else if (LTK_WIDGET_TYPE(childw) == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = LTK_CAST_MENUENTRY(childw); + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu); + if (submenuw && !submenuw->hidden && LTK_CAST_MENU(submenuw)->was_opened_above) { + above = e->submenu; } } return above; } -static ltk_widget * -ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget) { +static ltk_widget_id +ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget_id childid) { ltk_menu *menu = LTK_CAST_MENU(self); - ltk_widget *below = NULL; + ltk_widget *childw = ltk_get_widget_from_id(childid); + ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent); + ltk_widget_id below = LTK_WIDGET_ID_NONE; if (menu->is_submenu) { - below = ltk_menu_next_child(self, widget); - if (!below && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { - ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent); - if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { - ltk_menu *pmenu = LTK_CAST_MENU(e->widget.parent); - if (menu->was_opened_above && !pmenu->is_submenu) { + below = ltk_menu_next_child(self, childid); + if (LTK_WIDGET_ID_IS_NONE(below) && parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) { + ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent); + if (grandparent && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) { + ltk_menu *gpmenu = LTK_CAST_MENU(grandparent); + if (menu->was_opened_above && !gpmenu->is_submenu) { below = self->parent; - } else if (pmenu->is_submenu) { - below = ltk_menu_next_child(e->widget.parent, LTK_CAST_WIDGET(e)); + } else if (gpmenu->is_submenu) { + below = ltk_menu_next_child(grandparent, self->parent); } } } - } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) { - ltk_menuentry *e = LTK_CAST_MENUENTRY(widget); - if (e->submenu && !e->submenu->widget.hidden && !e->submenu->was_opened_above) { - below = LTK_CAST_WIDGET(e->submenu); + } else if (LTK_WIDGET_TYPE(childw) == LTK_WIDGET_MENUENTRY) { + ltk_menuentry *e = LTK_CAST_MENUENTRY(childw); + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu); + if (submenuw && !submenuw->hidden && !LTK_CAST_MENU(submenuw)->was_opened_above) { + below = e->submenu; } } return below; } -static ltk_widget * -ltk_menu_prev_child(ltk_widget *self, ltk_widget *child) { +static ltk_widget_id +ltk_menu_prev_child(ltk_widget *self, ltk_widget_id childid) { ltk_menu *menu = LTK_CAST_MENU(self); - for (size_t i = menu->num_entries; i-- > 0;) { - if (LTK_CAST_WIDGET(menu->entries[i]) == child) - return i > 0 ? LTK_CAST_WIDGET(menu->entries[i-1]) : NULL; + for (size_t i = ltk_array_len(menu->entries); i-- > 0;) { + ltk_widget_id id = ltk_array_get(menu->entries, i); + if (LTK_WIDGET_ID_EQUAL(id, childid)) + return i > 0 ? ltk_array_get(menu->entries, i - 1) : LTK_WIDGET_ID_NONE; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * -ltk_menu_next_child(ltk_widget *self, ltk_widget *child) { +static ltk_widget_id +ltk_menu_next_child(ltk_widget *self, ltk_widget_id childid) { ltk_menu *menu = LTK_CAST_MENU(self); - for (size_t i = 0; i < menu->num_entries; i++) { - if (LTK_CAST_WIDGET(menu->entries[i]) == child) - return i < menu->num_entries - 1 ? LTK_CAST_WIDGET(menu->entries[i+1]) : NULL; + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { + ltk_widget_id id = ltk_array_get(menu->entries, i); + if (LTK_WIDGET_ID_EQUAL(id, childid)) + return i < ltk_array_len(menu->entries) - 1 ? ltk_array_get(menu->entries, i + 1) : LTK_WIDGET_ID_NONE; } - return NULL; + return LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_menu_first_child(ltk_widget *self) { ltk_menu *menu = LTK_CAST_MENU(self); - return menu->num_entries > 0 ? LTK_CAST_WIDGET(menu->entries[0]) : NULL; + return ltk_array_len(menu->entries) > 0 ? ltk_array_get(menu->entries, 0) : LTK_WIDGET_ID_NONE; } -static ltk_widget * +static ltk_widget_id ltk_menu_last_child(ltk_widget *self) { ltk_menu *menu = LTK_CAST_MENU(self); - return menu->num_entries > 0 ? LTK_CAST_WIDGET(menu->entries[menu->num_entries-1]) : NULL; + return ltk_array_len(menu->entries) > 0 ? ltk_array_get(menu->entries, ltk_array_len(menu->entries) - 1) : LTK_WIDGET_ID_NONE; } /* FIXME: unregister from window popups? */ int -ltk_menuentry_detach_submenu(ltk_menuentry *e) { - if (!e->submenu) +ltk_menuentry_detach_submenu(ltk_widget_id entryid) { + ltk_widget *entryw = ltk_get_widget_from_id(entryid); + ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw); + ltk_widget *submenuw = ltk_get_widget_or_null_from_id(entry->submenu); + if (!submenuw) return 1; - e->submenu->widget.parent = NULL; - e->submenu = NULL; - ltk_menuentry_recalc_ideal_size_with_notification(e); + submenuw->parent = LTK_WIDGET_ID_NONE; + entry->submenu = LTK_WIDGET_ID_NONE; + ltk_menuentry_recalc_ideal_size_with_notification(entry); return 0; } static void ltk_menu_destroy(ltk_widget *self, int shallow) { ltk_menu *menu = LTK_CAST_MENU(self); - if (!menu) { - ltk_warn("Tried to destroy NULL menu.\n"); - return; - } if (menu->scroll_timer_id >= 0) ltk_unregister_timer(menu->scroll_timer_id); - ltk_window_unregister_popup(self->window, self); + ltk_window_unregister_popup(self->window, self->id); if (!shallow) { - for (size_t i = 0; i < menu->num_entries; i++) { + for (size_t i = 0; i < ltk_array_len(menu->entries); i++) { /* for efficiency - to avoid ltk_widget_destroy calling ltk_menu_remove_child for each of the entries */ - menu->entries[i]->widget.parent = NULL; - ltk_widget_destroy(LTK_CAST_WIDGET(menu->entries[i]), shallow); + ltk_widget *child = ltk_get_widget_from_id(ltk_array_get(menu->entries, i)); + child->parent = LTK_WIDGET_ID_NONE; + ltk_widget_destroy(child, shallow); } - ltk_free(menu->entries); + ltk_array_destroy(widget_id, menu->entries); } else { - ltk_menu_remove_all_entries(menu); + ltk_menu_remove_all_entries(self->id); } ltk_free(menu); } diff --git a/src/ltk/menu.h b/src/ltk/menu.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,14 +28,9 @@ #define LTK_MENUENTRY_SIGNAL_PRESSED -1 #define LTK_MENUENTRY_SIGNAL_INVALID -2 -struct ltk_menuentry; -typedef struct ltk_menuentry ltk_menuentry; - typedef struct { ltk_widget widget; - ltk_menuentry **entries; - size_t num_entries; - size_t num_alloc; + ltk_array(widget_id) *entries; double x_scroll_offset; double y_scroll_offset; int scroll_timer_id; @@ -54,32 +49,32 @@ typedef struct { /* FIXME: maybe need to set entire widget hierarchy to hover state so menu entry is also hover when nested widget is hover? */ -struct ltk_menuentry { +typedef struct { ltk_widget widget; /* FIXME: I guess if the regular label got the ability to change its color, a label could just be used instead of this */ ltk_text_line *text_line; - ltk_menu *submenu; -}; + ltk_widget_id submenu; +} ltk_menuentry; /* FIXME: ltk_orientation for hor/vert! should submenus also allow setting orientation? -> would maybe look weird in some cases */ /* FIXME: allow orientation */ -ltk_menu *ltk_menu_create(ltk_window *window); -ltk_menu *ltk_submenu_create(ltk_window *window); -ltk_menuentry *ltk_menuentry_create(ltk_window *window, const char *text); -int ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu); -int ltk_menuentry_detach_submenu(ltk_menuentry *e); -const char *ltk_menuentry_get_text(ltk_menuentry *entry); -int ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx); -int ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry); -ltk_menuentry *ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx); -int ltk_menu_remove_entry(ltk_menu *menu, ltk_menuentry *entry); -void ltk_menu_remove_all_entries(ltk_menu *menu); -size_t ltk_menu_get_num_entries(ltk_menu *menu); -ltk_menuentry *ltk_menu_get_entry(ltk_menu *menu, size_t idx); -size_t ltk_menu_get_entry_index(ltk_menu *menu, ltk_menuentry *entry); +ltk_widget_id ltk_menu_create(ltk_widget_id window); +ltk_widget_id ltk_submenu_create(ltk_widget_id window); +ltk_widget_id ltk_menuentry_create(ltk_widget_id window, const char *text); +int ltk_menuentry_attach_submenu(ltk_widget_id entry, ltk_widget_id submenu); +int ltk_menuentry_detach_submenu(ltk_widget_id entry); +const char *ltk_menuentry_get_text(ltk_widget_id entry); +int ltk_menu_insert_entry(ltk_widget_id menu, ltk_widget_id entry, size_t idx); +int ltk_menu_add_entry(ltk_widget_id menu, ltk_widget_id entry); +ltk_widget_id ltk_menu_remove_entry_index(ltk_widget_id menu, size_t idx); +int ltk_menu_remove_entry(ltk_widget_id menu, ltk_widget_id entry); +void ltk_menu_remove_all_entries(ltk_widget_id menu); +size_t ltk_menu_get_num_entries(ltk_widget_id menu); +ltk_widget_id ltk_menu_get_entry(ltk_widget_id menu, size_t idx); +size_t ltk_menu_get_entry_index(ltk_widget_id menu, ltk_widget_id entry); #endif /* LTK_MENU_H */ diff --git a/src/ltk/radiobutton.c b/src/ltk/radiobutton.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2024-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -210,14 +210,17 @@ ltk_radiobutton_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk static void uncheck_other_buttons(ltk_radiobutton *button) { - ltk_radiobutton *prev = button->prev, *next = button->next; + ltk_widget *prev = ltk_get_widget_or_null_from_id(button->prev); + ltk_widget *next = ltk_get_widget_or_null_from_id(button->next); while (prev) { - ltk_radiobutton_set_checked(prev, 0); - prev = prev->prev; + ltk_radiobutton_set_checked(prev->id, 0); + ltk_radiobutton *cur_btn = LTK_CAST_RADIOBUTTON(prev); + prev = ltk_get_widget_or_null_from_id(cur_btn->prev); } while (next) { - ltk_radiobutton_set_checked(next, 0); - next = next->next; + ltk_radiobutton_set_checked(next->id, 0); + ltk_radiobutton *cur_btn = LTK_CAST_RADIOBUTTON(next); + next = ltk_get_widget_or_null_from_id(cur_btn->next); } } @@ -227,34 +230,38 @@ ltk_radiobutton_release(ltk_widget *self) { button->checked = !button->checked; if (button->checked) uncheck_other_buttons(button); - ltk_widget_emit_signal(self, LTK_RADIOBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_RADIOBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST); return 1; } int -ltk_radiobutton_get_checked(ltk_radiobutton *button) { +ltk_radiobutton_get_checked(ltk_widget_id buttonid) { + ltk_widget *self = ltk_get_widget_from_id(buttonid); + ltk_radiobutton *button = LTK_CAST_RADIOBUTTON(self); return button->checked; } void -ltk_radiobutton_set_checked(ltk_radiobutton *button, int checked) { +ltk_radiobutton_set_checked(ltk_widget_id buttonid, int checked) { + ltk_widget *self = ltk_get_widget_from_id(buttonid); + ltk_radiobutton *button = LTK_CAST_RADIOBUTTON(self); button->checked = checked; if (checked) uncheck_other_buttons(button); - ltk_widget *self = LTK_CAST_WIDGET(button); - ltk_window_invalidate_widget_rect(self->window, self); + ltk_window_invalidate_widget_rect(self->window, self->id); } #define MAX(a, b) ((a) > (b) ? (a) : (b)) static void recalc_ideal_size(ltk_radiobutton *button) { + ltk_widget *self = LTK_CAST_WIDGET(button); int text_w, text_h; ltk_text_line_get_size(button->tl, &text_w, &text_h); - int circle_size = ltk_size_to_pixel(theme.circle_size, LTK_CAST_WIDGET(button)->last_dpi); - int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(button)->last_dpi); - button->widget.ideal_w = text_w + pad * 3 + circle_size; - button->widget.ideal_h = MAX(text_h, circle_size) + pad * 2; + int circle_size = ltk_size_to_pixel(theme.circle_size, self->last_dpi); + int pad = ltk_size_to_pixel(theme.pad, self->last_dpi); + self->ideal_w = text_w + pad * 3 + circle_size; + self->ideal_h = MAX(text_h, circle_size) + pad * 2; } static void @@ -265,16 +272,18 @@ ltk_radiobutton_recalc_ideal_size(ltk_widget *self) { recalc_ideal_size(button); } -ltk_radiobutton * -ltk_radiobutton_create(ltk_window *window, const char *text, int checked, ltk_radiobutton *group_member) { +ltk_widget_id +ltk_radiobutton_create(ltk_widget_id windowid, const char *text, int checked, ltk_widget_id group_memberid) { ltk_radiobutton *button = ltk_malloc(sizeof(ltk_radiobutton)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, 0, 0); + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(button), windowid, &vtable, 0, 0); button->checked = checked; - button->prev = button->next = NULL; - if (group_member) { - button->prev = group_member; + button->prev = button->next = LTK_WIDGET_ID_NONE; + ltk_widget *group_memberw = ltk_get_widget_or_null_from_id(group_memberid); + if (group_memberw) { + button->prev = group_memberid; + ltk_radiobutton *group_member = LTK_CAST_RADIOBUTTON(group_memberw); button->next = group_member->next; - group_member->next = button; + group_member->next = id; /* I guess it's technically possible for a button that is only created and never added to the widget hierarchy to cause the other buttons to be unchecked, but I guess that's just the way it is. */ @@ -286,23 +295,25 @@ ltk_radiobutton_create(ltk_window *window, const char *text, int checked, ltk_ra theme.font, ltk_size_to_pixel(theme.font_size, button->widget.last_dpi), text, -1 ); recalc_ideal_size(button); - button->widget.dirty = 1; + LTK_CAST_WIDGET(button)->dirty = 1; - return button; + return id; } static void ltk_radiobutton_destroy(ltk_widget *self, int shallow) { (void)shallow; ltk_radiobutton *button = LTK_CAST_RADIOBUTTON(self); - if (!button) { - ltk_warn("Tried to destroy NULL radiobutton.\n"); - return; - } ltk_text_line_destroy(button->tl); - if (button->prev) - button->prev->next = button->next; - if (button->next) - button->next->prev = button->prev; + ltk_widget *prevw = ltk_get_widget_or_null_from_id(button->prev); + if (prevw) { + ltk_radiobutton *prev = LTK_CAST_RADIOBUTTON(prevw); + prev->next = button->next; + } + ltk_widget *nextw = ltk_get_widget_or_null_from_id(button->next); + if (nextw) { + ltk_radiobutton *next = LTK_CAST_RADIOBUTTON(nextw); + next->prev = button->prev; + } ltk_free(button); } diff --git a/src/ltk/radiobutton.h b/src/ltk/radiobutton.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2024-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,16 +24,16 @@ #define LTK_RADIOBUTTON_SIGNAL_CHANGED -1 #define LTK_RADIOBUTTON_SIGNAL_INVALID -2 -typedef struct ltk_radiobutton { +typedef struct { ltk_widget widget; ltk_text_line *tl; - struct ltk_radiobutton *prev; - struct ltk_radiobutton *next; + ltk_widget_id prev; + ltk_widget_id next; int checked; } ltk_radiobutton; -ltk_radiobutton *ltk_radiobutton_create(ltk_window *window, const char *text, int checked, ltk_radiobutton *group_member); -int ltk_radiobutton_get_checked(ltk_radiobutton *button); -void ltk_radiobutton_set_checked(ltk_radiobutton *button, int checked); +ltk_widget_id ltk_radiobutton_create(ltk_widget_id windowid, const char *text, int checked, ltk_widget_id group_memberid); +int ltk_radiobutton_get_checked(ltk_widget_id buttonid); +void ltk_radiobutton_set_checked(ltk_widget_id buttonid, int checked); #endif /* LTK_RADIOBUTTON_H */ diff --git a/src/ltk/scrollbar.c b/src/ltk/scrollbar.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,6 +26,7 @@ #include "graphics.h" #include "scrollbar.h" #include "eventdefs.h" +#include "ltk.h" #define MAX_SCROLLBAR_WIDTH 10000 /* completely arbitrary */ @@ -83,7 +84,9 @@ ltk_scrollbar_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) { } void -ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size) { +ltk_scrollbar_set_virtual_size(ltk_widget_id scrollbarid, int virtual_size) { + ltk_widget *self = ltk_get_widget_from_id(scrollbarid); + ltk_scrollbar *scrollbar = LTK_CAST_SCROLLBAR(self); /* FIXME: some sort of error? */ if (virtual_size <= 0) return; @@ -95,7 +98,7 @@ ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size) { static ltk_rect handle_get_rect(ltk_scrollbar *sc) { ltk_rect r; - ltk_rect sc_rect = sc->widget.lrect; + ltk_rect sc_rect = LTK_CAST_WIDGET(sc)->lrect; if (sc->orient == LTK_HORIZONTAL) { r.y = 0; r.h = sc_rect.h; @@ -156,20 +159,20 @@ ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) { ltk_rect handle_rect = handle_get_rect(sc); if (sc->orient == LTK_HORIZONTAL) { if (ex < handle_rect.x || ex > handle_rect.x + handle_rect.w) { - sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.w) * (ex - handle_rect.w / 2 - sc->widget.lrect.x); + sc->cur_pos = (sc->virtual_size / (double)self->lrect.w) * (ex - handle_rect.w / 2 - self->lrect.x); } - max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0; + max_pos = sc->virtual_size > self->lrect.w ? sc->virtual_size - self->lrect.w : 0; } else { if (ey < handle_rect.y || ey > handle_rect.y + handle_rect.h) { - sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.h) * (ey - handle_rect.h / 2 - sc->widget.lrect.y); + sc->cur_pos = (sc->virtual_size / (double)self->lrect.h) * (ey - handle_rect.h / 2 - self->lrect.y); } - max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0; + max_pos = sc->virtual_size > self->lrect.h ? sc->virtual_size - self->lrect.h : 0; } if (sc->cur_pos < 0) sc->cur_pos = 0; else if (sc->cur_pos > max_pos) sc->cur_pos = max_pos; - ltk_widget_emit_signal(self, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST); sc->last_mouse_x = event->x; sc->last_mouse_y = event->y; return 1; @@ -178,16 +181,17 @@ ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) { /* FIXME: also queue redraw */ /* FIXME: improve interface (scaled is weird) */ void -ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) { +ltk_scrollbar_scroll(ltk_widget_id scrollbarid, int delta, int scaled) { + ltk_widget *self = ltk_get_widget_from_id(scrollbarid); ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self); int max_pos; double scale; if (sc->orient == LTK_HORIZONTAL) { - max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0; - scale = sc->virtual_size / (double)sc->widget.lrect.w; + max_pos = sc->virtual_size > self->lrect.w ? sc->virtual_size - self->lrect.w : 0; + scale = sc->virtual_size / (double)self->lrect.w; } else { - max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0; - scale = sc->virtual_size / (double)sc->widget.lrect.h; + max_pos = sc->virtual_size > self->lrect.h ? sc->virtual_size - self->lrect.h : 0; + scale = sc->virtual_size / (double)self->lrect.h; } if (scaled) sc->cur_pos += scale * delta; @@ -197,7 +201,7 @@ ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) { sc->cur_pos = 0; else if (sc->cur_pos > max_pos) sc->cur_pos = max_pos; - ltk_widget_emit_signal(self, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST); } static int @@ -211,7 +215,7 @@ ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) { delta = event->x - sc->last_mouse_x; else delta = event->y - sc->last_mouse_y; - ltk_scrollbar_scroll(self, delta, 1); + ltk_scrollbar_scroll(self->id, delta, 1); sc->last_mouse_x = event->x; sc->last_mouse_y = event->y; return 1; @@ -222,15 +226,15 @@ ltk_scrollbar_recalc_ideal_size(ltk_widget *self) { ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self); int size = ltk_size_to_pixel(theme.size, self->last_dpi); if (sc->orient == LTK_HORIZONTAL) - sc->widget.ideal_h = size; + self->ideal_h = size; else - sc->widget.ideal_w = size; + self->ideal_w = size; } -ltk_scrollbar * -ltk_scrollbar_create(ltk_window *window, ltk_orientation orient) { +ltk_widget_id +ltk_scrollbar_create(ltk_widget_id windowid, ltk_orientation orient) { ltk_scrollbar *sc = ltk_malloc(sizeof(ltk_scrollbar)); - ltk_fill_widget_defaults(LTK_CAST_WIDGET(sc), window, &vtable, 1, 1); /* FIXME: proper size */ + ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(sc), windowid, &vtable, 1, 1); /* FIXME: proper size */ sc->last_mouse_x = sc->last_mouse_y = 0; /* This cannot be 0 because that leads to divide-by-zero */ sc->virtual_size = 1; @@ -238,7 +242,7 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient) { sc->orient = orient; ltk_scrollbar_recalc_ideal_size(LTK_CAST_WIDGET(sc)); - return sc; + return id; } static void diff --git a/src/ltk/scrollbar.h b/src/ltk/scrollbar.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -32,8 +32,8 @@ typedef struct { ltk_orientation orient; } ltk_scrollbar; -void ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size); -ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient); -void ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled); +void ltk_scrollbar_set_virtual_size(ltk_widget_id scrollbarid, int virtual_size); +ltk_widget_id ltk_scrollbar_create(ltk_widget_id windowid, ltk_orientation orient); +void ltk_scrollbar_scroll(ltk_widget_id scrollbarid, int delta, int scaled); #endif /* LTK_SCROLLBAR_H */ diff --git a/src/ltk/widget.c b/src/ltk/widget.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,18 +24,24 @@ #include "memory.h" #include "array.h" #include "eventdefs.h" +#include "ltk.h" LTK_ARRAY_INIT_FUNC_DECL_STATIC(signal, ltk_signal_callback_info) LTK_ARRAY_INIT_IMPL_STATIC(signal, ltk_signal_callback_info) +LTK_ARRAY_INIT_IMPL(widget_id, ltk_widget_id) /* FIXME: this should probably not take w and h */ -void -ltk_fill_widget_defaults( - ltk_widget *widget, ltk_window *window, +/* FIXME: how much sense does it make to give a window id here? + maybe instead set that automatically when adding widget to container? + currently, it's weird anyways because each widget has a window set, + but when adding a widget to a container, nothing checks if the windows + match */ +ltk_widget_id +ltk_initialize_widget( + ltk_widget *widget, ltk_widget_id windowid, struct ltk_widget_vtable *vtable, int w, int h ) { - widget->window = window; - widget->parent = NULL; + widget->parent = LTK_WIDGET_ID_NONE; /* FIXME: possibly check that draw and destroy aren't NULL */ widget->vtable = vtable; @@ -63,41 +69,55 @@ ltk_fill_widget_defaults( widget->hidden = 0; widget->vtable_copied = 0; widget->signal_cbs = NULL; - /* FIXME: maybe set this to a dummy value here and don't initialize - ideal_w/h at all until it is actually needed? */ - widget->last_dpi = ltk_renderer_get_window_dpi(window->renderwindow); + + widget->id = ltk_store_widget(widget); + widget->window = windowid; + if (LTK_WIDGET_ID_IS_NONE(windowid)) { + widget->window = widget->id; /* a bit hacky */ + } else { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); + /* FIXME: maybe set this to a dummy value here and don't initialize + ideal_w/h at all until it is actually needed? */ + widget->last_dpi = ltk_renderer_get_window_dpi(window->renderwindow); + } /* FIXME: null other members! */ + return widget->id; } void ltk_widget_hide(ltk_widget *widget) { /* FIXME: it may not make sense to call this here */ - if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_HIDE, LTK_EMPTY_ARGLIST)) + if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_HIDE, LTK_EMPTY_ARGLIST)) return; if (widget->vtable->hide) widget->vtable->hide(widget); widget->hidden = 1; /* remove hover state */ /* FIXME: this needs to call change_state but that might cause issues */ - ltk_widget *hover = widget->window->hover_widget; + ltk_widget *windoww = ltk_get_widget_from_id(widget->window); + ltk_window *window = LTK_CAST_WINDOW(windoww); + ltk_widget *hover_orig = ltk_get_widget_or_null_from_id(window->hover_widget); + ltk_widget *hover = hover_orig; while (hover) { if (hover == widget) { - widget->window->hover_widget->state &= ~LTK_HOVER; - widget->window->hover_widget = NULL; + hover_orig->state &= ~LTK_HOVER; + window->hover_widget = LTK_WIDGET_ID_NONE; break; } - hover = hover->parent; + hover = ltk_get_widget_or_null_from_id(hover->parent); } - ltk_widget *pressed = widget->window->pressed_widget; + ltk_widget *pressed_orig = ltk_get_widget_or_null_from_id(window->pressed_widget); + ltk_widget *pressed = pressed_orig; while (pressed) { if (pressed == widget) { - widget->window->pressed_widget->state &= ~LTK_PRESSED; - widget->window->pressed_widget = NULL; + pressed_orig->state &= ~LTK_PRESSED; + window->pressed_widget = LTK_WIDGET_ID_NONE; break; } - pressed = pressed->parent; + pressed = ltk_get_widget_or_null_from_id(pressed->parent); } - ltk_widget *active = widget->window->active_widget; + ltk_widget *active = ltk_get_widget_or_null_from_id(window->active_widget); /* if current active widget is child, set active widget to widget above in hierarchy */ int set_next = 0; while (active) { @@ -105,13 +125,19 @@ ltk_widget_hide(ltk_widget *widget) { set_next = 1; /* FIXME: use config values for all_activatable */ } else if (set_next && (active->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { - ltk_window_set_active_widget(active->window, active); + ltk_window_set_active_widget(active->window, active->id); break; } - active = active->parent; + active = ltk_get_widget_or_null_from_id(active->parent); } if (set_next && !active) - ltk_window_set_active_widget(active->window, NULL); + ltk_window_set_active_widget(active->window, LTK_WIDGET_ID_NONE); +} + +void +ltk_widget_id_hide(ltk_widget_id widgetid) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + ltk_widget_hide(widget); } /* FIXME: Maybe pass the new width as arg here? @@ -119,7 +145,7 @@ ltk_widget_hide(ltk_widget *widget) { /* FIXME: maybe give global and local position in event */ void ltk_widget_resize(ltk_widget *widget) { - if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_RESIZE, LTK_EMPTY_ARGLIST)) + if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_RESIZE, LTK_EMPTY_ARGLIST)) return; if (widget->vtable->resize) widget->vtable->resize(widget); @@ -134,7 +160,7 @@ ltk_widget_draw(ltk_widget *widget, ltk_surface *draw_surf, int x, int y, ltk_re LTK_MAKE_ARG_INT(y), LTK_MAKE_ARG_RECT(clip_rect) }; - if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_DRAW, (ltk_callback_arglist){args, LENGTH(args)})) + if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_DRAW, (ltk_callback_arglist){args, LENGTH(args)})) return; if (widget->vtable->draw) widget->vtable->draw(widget, draw_surf, x, y, clip_rect); @@ -145,40 +171,49 @@ ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) { if (old_state == widget->state) return; ltk_callback_arg args[] = {LTK_MAKE_ARG_INT(old_state)}; - if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_CHANGE_STATE, (ltk_callback_arglist){args, LENGTH(args)})) + if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_CHANGE_STATE, (ltk_callback_arglist){args, LENGTH(args)})) return; if (widget->vtable->change_state) widget->vtable->change_state(widget, old_state); if (widget->vtable->flags & LTK_NEEDS_REDRAW) { widget->dirty = 1; - ltk_window_invalidate_widget_rect(widget->window, widget); + ltk_window_invalidate_widget_rect(widget->window, widget->id); } } /* FIXME: document that it's really dangerous to overwrite remove_child or destroy */ int ltk_widget_destroy(ltk_widget *widget, int shallow) { - ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_DESTROY, LTK_EMPTY_ARGLIST); + ltk_widget_id id = widget->id; + ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_DESTROY, LTK_EMPTY_ARGLIST); /* widget->parent->remove_child should never be NULL because of the fact that the widget is set as parent, but let's just check anyways... */ int invalid = 0; - if (widget->parent) { - if (widget->parent->vtable->remove_child) - invalid = widget->parent->vtable->remove_child(widget->parent, widget); - } - if (widget->vtable_copied) { - ltk_free(widget->vtable); - widget->vtable = NULL; + ltk_widget *parent = ltk_get_widget_or_null_from_id(widget->parent); + if (parent) { + if (parent->vtable->remove_child) + invalid = parent->vtable->remove_child(parent, widget->id); } if (widget->signal_cbs) { ltk_array_destroy(signal, widget->signal_cbs); widget->signal_cbs = NULL; } - widget->vtable->destroy(widget, shallow); + ltk_widget_vtable *vtable = widget->vtable; + int vtable_copied = widget->vtable_copied; + vtable->destroy(widget, shallow); + if (vtable_copied) + ltk_free(vtable); + ltk_clear_widget_slot(id); return invalid; } +int +ltk_widget_id_destroy(ltk_widget_id widgetid, int shallow) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + return ltk_widget_destroy(widget, shallow); +} + ltk_point ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) { ltk_widget *cur = widget; @@ -187,7 +222,7 @@ ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) { y += cur->lrect.y; if (cur->popup) break; - cur = cur->parent; + cur = ltk_get_widget_or_null_from_id(cur->parent); } return (ltk_point){x, y}; } @@ -200,13 +235,14 @@ ltk_global_to_widget_pos(ltk_widget *widget, int x, int y) { y -= cur->lrect.y; if (cur->popup) break; - cur = cur->parent; + cur = ltk_get_widget_or_null_from_id(cur->parent); } return (ltk_point){x, y}; } int -ltk_widget_register_signal_handler(ltk_widget *widget, int type, ltk_signal_callback callback, ltk_callback_arg data) { +ltk_widget_register_signal_handler(ltk_widget_id widgetid, int type, ltk_signal_callback callback, ltk_callback_arg data) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); if ((type >= LTK_WIDGET_SIGNAL_INVALID) || type <= widget->vtable->invalid_signal) return 1; if (!widget->signal_cbs) { @@ -216,8 +252,13 @@ ltk_widget_register_signal_handler(ltk_widget *widget, int type, ltk_signal_call return 0; } +/* FIXME: this takes widget_id, but calls functions that take ltk_widget* + Should this be changed to also take ltk_widget*? + Changing the callbacks to take widget_id doesn't really make sense because they + are only called from here anyways, where ltk_widget* already is required anyways */ int -ltk_widget_emit_signal(ltk_widget *widget, int type, ltk_callback_arglist args) { +ltk_widget_emit_signal(ltk_widget_id widgetid, int type, ltk_callback_arglist args) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); if (!widget->signal_cbs) return 0; int handled = 0; @@ -235,7 +276,8 @@ filter_by_type(ltk_signal_callback_info *info, void *data) { } size_t -ltk_widget_remove_signal_handler_by_type(ltk_widget *widget, int type) { +ltk_widget_remove_signal_handler_by_type(ltk_widget_id widgetid, int type) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); if (!widget->signal_cbs) return 0; return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_type, &type); @@ -251,7 +293,8 @@ filter_by_callback(ltk_signal_callback_info *info, void *data) { } size_t -ltk_widget_remove_signal_handler_by_callback(ltk_widget *widget, ltk_signal_callback callback) { +ltk_widget_remove_signal_handler_by_callback(ltk_widget_id widgetid, ltk_signal_callback callback) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); if (!widget->signal_cbs) return 0; /* callback can't be passed directly because ISO C forbids @@ -273,10 +316,11 @@ filter_by_info(ltk_signal_callback_info *info, void *data) { size_t ltk_widget_remove_signal_handler_by_info( - ltk_widget *widget, + ltk_widget_id widgetid, int (*filter_func)(ltk_signal_callback_info *to_check, ltk_signal_callback_info *info), ltk_signal_callback_info *info) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); if (!widget->signal_cbs) return 0; struct delete_wrapper data = {filter_func, info}; @@ -284,7 +328,8 @@ ltk_widget_remove_signal_handler_by_info( } void -ltk_widget_remove_all_signal_handlers(ltk_widget *widget) { +ltk_widget_remove_all_signal_handlers(ltk_widget_id widgetid) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); if (!widget->signal_cbs) return; ltk_array_destroy(signal, widget->signal_cbs); @@ -294,7 +339,8 @@ ltk_widget_remove_all_signal_handlers(ltk_widget *widget) { int ltk_widget_register_type(void); /* FIXME */ ltk_widget_vtable * -ltk_widget_get_editable_vtable(ltk_widget *widget) { +ltk_widget_get_editable_vtable(ltk_widget_id widgetid) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); if (!widget->vtable_copied) { ltk_widget_vtable *vtable = ltk_malloc(sizeof(ltk_widget_vtable)); memcpy(vtable, widget->vtable, sizeof(ltk_widget_vtable)); @@ -305,7 +351,8 @@ ltk_widget_get_editable_vtable(ltk_widget *widget) { void ltk_widget_recalc_ideal_size(ltk_widget *widget) { - unsigned int dpi = ltk_renderer_get_window_dpi(widget->window->renderwindow); + ltk_widget *windoww = ltk_get_widget_from_id(widget->window); + unsigned int dpi = ltk_renderer_get_window_dpi(LTK_CAST_WINDOW(windoww)->renderwindow); if (dpi == widget->last_dpi) return; widget->last_dpi = dpi; diff --git a/src/ltk/widget.h b/src/ltk/widget.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,14 +22,38 @@ that handler - maybe loop if container widget deletes children but they call par /* FIXME: destroy signal for widgets (also window) */ #include <stddef.h> +#include <stdint.h> #include "array.h" #include "event.h" #include "graphics.h" #include "rect.h" #include "util.h" -struct ltk_widget; -struct ltk_window; +/* This MUST be unsigned! */ +/* FIXME: would uint16_t be enough? */ +typedef uint32_t ltk_widget_id_int_type; +/* This could be decreased if wanted (but it can't be larger than + the maximum size of ltk_widget_id_int_type!) */ +#define LTK_MAX_WIDGET_IDX ((ltk_widget_id_int_type)-1) +typedef struct { + /* index in global widget array */ + ltk_widget_id_int_type idx; + /* generation number, increased every time an index is reused + this is never allowed to be 0 for a valid widget */ + ltk_widget_id_int_type gen; +} ltk_widget_id; +#define LTK_WIDGET_ID_NONE (ltk_widget_id){0, 0} +/* Note: This is required in order to initialize a variable with static storage + duration since the (ltk_widget_id) cast causes the expression to not be + constant. Technically, this isn't necessary as long as the NONE value is just + {0, 0} since static variables are initialized to zero anyways, but I prefer + to be explicit. */ +#define LTK_WIDGET_ID_NONE_STATIC {0, 0} +#define LTK_WIDGET_ID_IS_NONE(id) ((id).idx == 0 && (id).gen == 0) +#define LTK_WIDGET_ID_EQUAL(a, b) ((a).idx == (b).idx && (a).gen == (b).gen) + +LTK_ARRAY_INIT_DECL(widget_id, ltk_widget_id) + typedef struct ltk_widget ltk_widget; typedef enum { @@ -100,6 +124,7 @@ typedef struct { size_t sz; char c; ltk_widget *widget; + ltk_widget_id wid; char *str; const char *cstr; ltk_key_event *key_event; @@ -119,6 +144,7 @@ typedef struct { LTK_TYPE_SIZE_T, LTK_TYPE_CHAR, LTK_TYPE_WIDGET, + LTK_TYPE_WIDGET_ID, LTK_TYPE_STRING, LTK_TYPE_CONST_STRING, LTK_TYPE_KEY_EVENT, @@ -137,6 +163,7 @@ typedef struct { #define LTK_MAKE_ARG_SIZE_T(data) ((ltk_callback_arg){.type = LTK_TYPE_SIZE_T, .arg = {.sz = (data)}}) #define LTK_MAKE_ARG_CHAR(data) ((ltk_callback_arg){.type = LTK_TYPE_CHAR, .arg = {.c = (data)}}) #define LTK_MAKE_ARG_WIDGET(data) ((ltk_callback_arg){.type = LTK_TYPE_WIDGET, .arg = {.widget = (data)}}) +#define LTK_MAKE_ARG_WIDGET_ID(data) ((ltk_callback_arg){.type = LTK_TYPE_WIDGET_ID, .arg = {.wid = (data)}}) #define LTK_MAKE_ARG_STRING(data) ((ltk_callback_arg){.type = LTK_TYPE_STRING, .arg = {.str = (data)}}) #define LTK_MAKE_ARG_CONST_STRING(data) ((ltk_callback_arg){.type = LTK_TYPE_CONST_STRING, .arg = {.cstr = (data)}}) #define LTK_MAKE_ARG_KEY_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_KEY_EVENT, .arg = {.key_event = (data)}}) @@ -153,6 +180,7 @@ typedef struct { #define LTK_CAST_ARG_SIZE_T(carg) (ltk_assert(carg.type == LTK_TYPE_SIZE_T), carg.arg.sz) #define LTK_CAST_ARG_CHAR(carg) (ltk_assert(carg.type == LTK_TYPE_CHAR), carg.arg.c) #define LTK_CAST_ARG_WIDGET(carg) (ltk_assert(carg.type == LTK_TYPE_WIDGET), carg.arg.widget) +#define LTK_CAST_ARG_WIDGET_ID(carg) (ltk_assert(carg.type == LTK_TYPE_WIDGET_ID), carg.arg.wid) #define LTK_CAST_ARG_STRING(carg) (ltk_assert(carg.type == LTK_TYPE_STRING), carg.arg.str) #define LTK_CAST_ARG_CONST_STRING(carg) (ltk_assert(carg.type == LTK_TYPE_CONST_STRING), carg.arg.cstr) #define LTK_CAST_ARG_KEY_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_KEY_EVENT), carg.arg.key_event) @@ -167,6 +195,7 @@ typedef struct { #define LTK_GET_ARG_SIZE_T(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_SIZE_T(cargs.args[i])) #define LTK_GET_ARG_CHAR(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_CHAR(cargs.args[i])) #define LTK_GET_ARG_WIDGET(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_WIDGET(cargs.args[i])) +#define LTK_GET_ARG_WIDGET_ID(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_WIDGET_ID(cargs.args[i])) #define LTK_GET_ARG_STRING(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_STRING(cargs.args[i])) #define LTK_GET_ARG_CONST_STRING(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_CONST_STRING(cargs.args[i])) #define LTK_GET_ARG_KEY_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_KEY_EVENT(cargs.args[i])) @@ -177,19 +206,21 @@ typedef struct { #define LTK_GET_ARG_RECT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_RECT(cargs.args[i])) #define LTK_CAST_WIDGET(w) (&(w)->widget) -#define LTK_CAST_WINDOW(w) (ltk_assert(w->vtable->type == LTK_WIDGET_WINDOW), (ltk_window *)(w)) -#define LTK_CAST_LABEL(w) (ltk_assert(w->vtable->type == LTK_WIDGET_LABEL), (ltk_label *)(w)) -#define LTK_CAST_BUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_BUTTON), (ltk_button *)(w)) -#define LTK_CAST_GRID(w) (ltk_assert(w->vtable->type == LTK_WIDGET_GRID), (ltk_grid *)(w)) -#define LTK_CAST_IMAGE_WIDGET(w) (ltk_assert(w->vtable->type == LTK_WIDGET_IMAGE), (ltk_image_widget *)(w)) -#define LTK_CAST_ENTRY(w) (ltk_assert(w->vtable->type == LTK_WIDGET_ENTRY), (ltk_entry *)(w)) -#define LTK_CAST_MENU(w) (ltk_assert(w->vtable->type == LTK_WIDGET_MENU), (ltk_menu *)(w)) -#define LTK_CAST_MENUENTRY(w) (ltk_assert(w->vtable->type == LTK_WIDGET_MENUENTRY), (ltk_menuentry *)(w)) -#define LTK_CAST_SCROLLBAR(w) (ltk_assert(w->vtable->type == LTK_WIDGET_SCROLLBAR), (ltk_scrollbar *)(w)) -#define LTK_CAST_BOX(w) (ltk_assert(w->vtable->type == LTK_WIDGET_BOX), (ltk_box *)(w)) -#define LTK_CAST_CHECKBUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_CHECKBUTTON), (ltk_checkbutton *)(w)) -#define LTK_CAST_RADIOBUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_RADIOBUTTON), (ltk_radiobutton *)(w)) -#define LTK_CAST_COMBOBOX(w) (ltk_assert(w->vtable->type == LTK_WIDGET_COMBOBOX), (ltk_combobox *)(w)) +#define LTK_CAST_WINDOW(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_WINDOW), (ltk_window *)(w)) +#define LTK_CAST_LABEL(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_LABEL), (ltk_label *)(w)) +#define LTK_CAST_BUTTON(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_BUTTON), (ltk_button *)(w)) +#define LTK_CAST_GRID(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_GRID), (ltk_grid *)(w)) +#define LTK_CAST_IMAGE_WIDGET(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_IMAGE), (ltk_image_widget *)(w)) +#define LTK_CAST_ENTRY(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_ENTRY), (ltk_entry *)(w)) +#define LTK_CAST_MENU(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_MENU), (ltk_menu *)(w)) +#define LTK_CAST_MENUENTRY(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_MENUENTRY), (ltk_menuentry *)(w)) +#define LTK_CAST_SCROLLBAR(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_SCROLLBAR), (ltk_scrollbar *)(w)) +#define LTK_CAST_BOX(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_BOX), (ltk_box *)(w)) +#define LTK_CAST_CHECKBUTTON(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_CHECKBUTTON), (ltk_checkbutton *)(w)) +#define LTK_CAST_RADIOBUTTON(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_RADIOBUTTON), (ltk_radiobutton *)(w)) +#define LTK_CAST_COMBOBOX(w) (ltk_assert((w)->vtable->type == LTK_WIDGET_COMBOBOX), (ltk_combobox *)(w)) + +#define LTK_WIDGET_TYPE(w) (w->vtable->type) /* FIXME: a bit weird because window never gets some of these signals */ #define LTK_WIDGET_SIGNAL_KEY_PRESS 1 @@ -228,23 +259,24 @@ typedef struct { int type; } ltk_signal_callback_info; -int ltk_widget_register_signal_handler(ltk_widget *widget, int type, ltk_signal_callback callback, ltk_callback_arg data); -int ltk_widget_emit_signal(ltk_widget *widget, int type, ltk_callback_arglist args); -size_t ltk_widget_remove_signal_handler_by_type(ltk_widget *widget, int type); -size_t ltk_widget_remove_signal_handler_by_callback(ltk_widget *widget, ltk_signal_callback callback); +int ltk_widget_register_signal_handler(ltk_widget_id widgetid, int type, ltk_signal_callback callback, ltk_callback_arg data); +int ltk_widget_emit_signal(ltk_widget_id widgetid, int type, ltk_callback_arglist args); +size_t ltk_widget_remove_signal_handler_by_type(ltk_widget_id widgetid, int type); +size_t ltk_widget_remove_signal_handler_by_callback(ltk_widget_id widgetid, ltk_signal_callback callback); size_t ltk_widget_remove_signal_handler_by_info( - ltk_widget *widget, + ltk_widget_id widgetid, int (*filter_func)(ltk_signal_callback_info *to_check, ltk_signal_callback_info *info), ltk_signal_callback_info *info ); -void ltk_widget_remove_all_signal_handlers(ltk_widget *widget); +void ltk_widget_remove_all_signal_handlers(ltk_widget_id widgetid); int ltk_widget_register_type(void); LTK_ARRAY_INIT_STRUCT_DECL(signal, ltk_signal_callback_info) struct ltk_widget { - struct ltk_window *window; - struct ltk_widget *parent; + ltk_widget_id id; + ltk_widget_id window; + ltk_widget_id parent; struct ltk_widget_vtable *vtable; @@ -292,53 +324,53 @@ struct ltk_widget { }; typedef struct ltk_widget_vtable { - int (*key_press)(struct ltk_widget *, ltk_key_event *); - int (*key_release)(struct ltk_widget *, ltk_key_event *); + int (*key_press)(ltk_widget *self, ltk_key_event *event); + int (*key_release)(ltk_widget *self, ltk_key_event *event); /* press/release also receive double/triple-click/release */ - int (*mouse_press)(struct ltk_widget *, ltk_button_event *); - int (*mouse_release)(struct ltk_widget *, ltk_button_event *); - int (*mouse_scroll)(struct ltk_widget *, ltk_scroll_event *); - int (*motion_notify)(struct ltk_widget *, ltk_motion_event *); - int (*mouse_leave)(struct ltk_widget *, ltk_motion_event *); - int (*mouse_enter)(struct ltk_widget *, ltk_motion_event *); - int (*press)(struct ltk_widget *); - int (*release)(struct ltk_widget *); - void (*cmd_return)(struct ltk_widget *self, char *text, size_t len); + int (*mouse_press)(ltk_widget *self, ltk_button_event *event); + int (*mouse_release)(ltk_widget *self, ltk_button_event *event); + int (*mouse_scroll)(ltk_widget *self, ltk_scroll_event *event); + int (*motion_notify)(ltk_widget *self, ltk_motion_event *event); + int (*mouse_leave)(ltk_widget *self, ltk_motion_event *event); + int (*mouse_enter)(ltk_widget *self, ltk_motion_event *event); + int (*press)(ltk_widget *self); + int (*release)(ltk_widget *self); + void (*cmd_return)(ltk_widget *self, char *text, size_t len); /* This is called when the DPI changes. It must not call child_size_change on the parent or do any actual resizing (only ideal_w and ideal_h should be set), but it should call ltk_widget_recalc_ideal_size on any child widgets. */ /* When a widget is added to a container, the container should call ltk_widget_recalc_ideal_size on the widget in case the DPI changed. */ - void (*recalc_ideal_size)(struct ltk_widget *); - void (*resize)(struct ltk_widget *); - void (*hide)(struct ltk_widget *); + void (*recalc_ideal_size)(ltk_widget *self); + void (*resize)(ltk_widget *self); + void (*hide)(ltk_widget *self); /* draw_surface: surface to draw it on x, y: position of logical rectangle on surface clip: clipping rectangle, relative to logical rectangle */ - void (*draw)(struct ltk_widget *self, ltk_surface *draw_surface, int x, int y, ltk_rect clip); - void (*change_state)(struct ltk_widget *, ltk_widget_state); - void (*destroy)(struct ltk_widget *, int); + void (*draw)(ltk_widget *self, ltk_surface *draw_surface, int x, int y, ltk_rect clip); + void (*change_state)(ltk_widget *self, ltk_widget_state state); + void (*destroy)(ltk_widget *self, int shallow); /* rect is in self's coordinate system */ - struct ltk_widget *(*nearest_child)(struct ltk_widget *self, ltk_rect rect); - struct ltk_widget *(*nearest_child_left)(struct ltk_widget *self, ltk_widget *widget); - struct ltk_widget *(*nearest_child_right)(struct ltk_widget *self, ltk_widget *widget); - struct ltk_widget *(*nearest_child_above)(struct ltk_widget *self, ltk_widget *widget); - struct ltk_widget *(*nearest_child_below)(struct ltk_widget *self, ltk_widget *widget); - struct ltk_widget *(*next_child)(struct ltk_widget *self, ltk_widget *child); - struct ltk_widget *(*prev_child)(struct ltk_widget *self, ltk_widget *child); - struct ltk_widget *(*first_child)(struct ltk_widget *self); - struct ltk_widget *(*last_child)(struct ltk_widget *self); + ltk_widget_id (*nearest_child)(ltk_widget *self, ltk_rect rect); + ltk_widget_id (*nearest_child_left)(ltk_widget *self, ltk_widget_id widget); + ltk_widget_id (*nearest_child_right)(ltk_widget *self, ltk_widget_id widget); + ltk_widget_id (*nearest_child_above)(ltk_widget *self, ltk_widget_id widget); + ltk_widget_id (*nearest_child_below)(ltk_widget *self, ltk_widget_id widget); + ltk_widget_id (*next_child)(ltk_widget *self, ltk_widget_id child); + ltk_widget_id (*prev_child)(ltk_widget *self, ltk_widget_id child); + ltk_widget_id (*first_child)(ltk_widget *self); + ltk_widget_id (*last_child)(ltk_widget *self); /* This is called when the ideal size of a child is changed (e.g. text is added to a text entry). */ - void (*child_size_change)(struct ltk_widget *, struct ltk_widget *); - int (*remove_child)(struct ltk_widget *, struct ltk_widget *); + void (*child_size_change)(ltk_widget *self, ltk_widget_id child); + int (*remove_child)(ltk_widget *self, ltk_widget_id child); /* x and y relative to widget's lrect! */ - struct ltk_widget *(*get_child_at_pos)(struct ltk_widget *, int x, int y); + ltk_widget_id (*get_child_at_pos)(ltk_widget *self, int x, int y); /* r is in self's coordinate system */ - void (*ensure_rect_shown)(struct ltk_widget *self, ltk_rect r); + void (*ensure_rect_shown)(ltk_widget *self, ltk_rect r); ltk_widget_type type; ltk_widget_flags flags; @@ -346,11 +378,14 @@ typedef struct ltk_widget_vtable { } ltk_widget_vtable; void ltk_widget_hide(ltk_widget *widget); +void ltk_widget_id_hide(ltk_widget_id widgetid); int ltk_widget_destroy(ltk_widget *widget, int shallow); -void ltk_fill_widget_defaults( - ltk_widget *widget, struct ltk_window *window, +int ltk_widget_id_destroy(ltk_widget_id widgetid, int shallow); +ltk_widget_id ltk_initialize_widget( + ltk_widget *widget, ltk_widget_id windowid, struct ltk_widget_vtable *vtable, int w, int h ); +/* FIXME: container should always check if parent of child widget is correct (or at least before destroying?) */ void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state); void ltk_widget_resize(ltk_widget *widget); void ltk_widget_draw(ltk_widget *widget, ltk_surface *draw_surf, int x, int y, ltk_rect clip_rect); @@ -359,7 +394,9 @@ void ltk_widget_get_ideal_size(ltk_widget *widget, unsigned int *ideal_w_ret, un ltk_point ltk_widget_pos_to_global(ltk_widget *widget, int x, int y); ltk_point ltk_global_to_widget_pos(ltk_widget *widget, int x, int y); -ltk_widget_vtable *ltk_widget_get_editable_vtable(ltk_widget *widget); +/* FIXME: either remove this functionality or update all widget functions + so they never assume that a certain entry is actually present in the vtable */ +ltk_widget_vtable *ltk_widget_get_editable_vtable(ltk_widget_id widgetid); void ltk_widget_recalc_ideal_size(ltk_widget *widget); diff --git a/src/ltk/widget_internal.h b/src/ltk/widget_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2024-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,8 @@ #ifndef LTK_WIDGET_INTERNAL_H #define LTK_WIDGET_INTERNAL_H +/* FIXME: use __attribute__((visibility("hidden"))) for compilers that support it */ + #include <stddef.h> #include "array.h" @@ -53,6 +55,7 @@ void ltk_entry_get_keybinding_parseinfo( void ltk_window_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len); ltk_window *ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h); +void ltk_window_destroy(ltk_widget *self, int shallow); void ltk_window_destroy_intern(ltk_window *window); void ltk_window_cleanup(void); void ltk_window_get_keybinding_parseinfo( @@ -61,10 +64,7 @@ void ltk_window_get_keybinding_parseinfo( ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret ); -/* FIXME: how to avoid bad things happening while external program open? maybe store cmd widget somewhere (but could be multiple!) and check if widget to destroy is one of those --> alternative: store all widgets in array and only give out IDs, then when returning from cmd, widget is already destroyed and can be ignored --> first option maybe just set callback, etc. of current cmd to NULL so widget can still be destroyed */ -int ltk_call_cmd(ltk_widget *caller, ltk_array(cmd) *cmd, const char *text, size_t textlen); +int ltk_call_cmd(ltk_widget_id caller, ltk_array(cmd) *cmd, const char *text, size_t textlen); int ltk_widget_handle_keypress_bindings(ltk_widget *widget, ltk_key_event *event, ltk_array(keypress) *keypresses, int handled); int ltk_widget_handle_keyrelease_bindings(ltk_widget *widget, ltk_key_event *event, ltk_array(keyrelease) *keyreleases, int handled); diff --git a/src/ltk/window.c b/src/ltk/window.c @@ -1,6 +1,6 @@ /* FIXME: signal handling is really ugly and inconsistent at the moment */ /* - * Copyright (c) 2020-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2020-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -31,7 +31,7 @@ #define MAX_WINDOW_FONT_SIZE 20000 -static void gen_widget_stack(ltk_widget *bottom); +static void gen_widget_stack(ltk_widget_id bottom); static ltk_widget *get_hover_popup(ltk_window *window, int x, int y); static int is_parent(ltk_widget *parent, ltk_widget *child); static ltk_widget *get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret); @@ -115,9 +115,7 @@ ltk_window_get_keybinding_parseinfo( } /* needed for passing keyboard events down the hierarchy */ -static ltk_widget **widget_stack = NULL; -static size_t widget_stack_alloc = 0; -static size_t widget_stack_len = 0; +static ltk_array(widget_id) *widget_stack = NULL; static struct { int border_width; @@ -146,20 +144,22 @@ ltk_window_cleanup(void) { ltk_keyrelease_bindings_destroy(keyreleases); keypresses = NULL; keyreleases = NULL; - ltk_free(widget_stack); + if (widget_stack) + ltk_array_destroy(widget_id, widget_stack); widget_stack = NULL; } static void ensure_active_widget_shown(ltk_window *window) { - ltk_widget *widget = window->active_widget; + ltk_widget *widget = ltk_get_widget_or_null_from_id(window->active_widget); if (!widget) return; ltk_rect r = widget->lrect; - while (widget->parent) { - if (widget->parent->vtable->ensure_rect_shown) - widget->parent->vtable->ensure_rect_shown(widget->parent, r); - widget = widget->parent; + ltk_widget *parent = ltk_get_widget_or_null_from_id(widget->parent); + while (parent) { + if (parent->vtable->ensure_rect_shown) + parent->vtable->ensure_rect_shown(parent, r); + widget = parent; r.x += widget->lrect.x; r.y += widget->lrect.y; /* FIXME: this currently just aborts if a widget is positioned @@ -167,8 +167,12 @@ ensure_active_widget_shown(ltk_window *window) { be in that case */ if (widget->popup) break; + parent = ltk_get_widget_or_null_from_id(parent->parent); } - ltk_window_invalidate_widget_rect(window, widget); + /* FIXME: there could be weird situations with widgets that are not + geometrically within their parent so that this doesn't + invalidate all necessary regions */ + ltk_window_invalidate_widget_rect(LTK_CAST_WIDGET(window)->id, widget->id); } /* FIXME: should keyrelease events be ignored if the corresponding keypress event @@ -181,11 +185,14 @@ ltk_window_key_press_event(ltk_widget *self, ltk_key_event *event) { ltk_window *window = LTK_CAST_WINDOW(self); int handled = 0; ltk_callback_arg args[] = {LTK_MAKE_ARG_KEY_EVENT(event)}; - if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { + ltk_widget *active_widget = ltk_get_widget_or_null_from_id(window->active_widget); + if (active_widget && (active_widget->state & LTK_FOCUSED)) { gen_widget_stack(window->active_widget); - for (size_t i = widget_stack_len; i-- > 0 && !handled;) { - if (ltk_widget_emit_signal(widget_stack[i], LTK_WIDGET_SIGNAL_KEY_PRESS, (ltk_callback_arglist){args, LENGTH(args)}) || - (widget_stack[i]->vtable->key_press && widget_stack[i]->vtable->key_press(widget_stack[i], event))) { + for (size_t i = ltk_array_len(widget_stack); i-- > 0 && !handled;) { + ltk_widget_id id = ltk_array_get(widget_stack, i); + ltk_widget *widget = ltk_get_widget_from_id(id); + if (ltk_widget_emit_signal(id, LTK_WIDGET_SIGNAL_KEY_PRESS, (ltk_callback_arglist){args, LENGTH(args)}) || + (widget->vtable->key_press && widget->vtable->key_press(widget, event))) { handled = 1; break; } @@ -201,11 +208,14 @@ ltk_window_key_release_event(ltk_widget *self, ltk_key_event *event) { ltk_window *window = LTK_CAST_WINDOW(self); int handled = 0; ltk_callback_arg args[] = {LTK_MAKE_ARG_KEY_EVENT(event)}; - if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { + ltk_widget *active_widget = ltk_get_widget_or_null_from_id(window->active_widget); + if (active_widget && (active_widget->state & LTK_FOCUSED)) { gen_widget_stack(window->active_widget); - for (size_t i = widget_stack_len; i-- > 0 && !handled;) { - if (ltk_widget_emit_signal(widget_stack[i], LTK_WIDGET_SIGNAL_KEY_RELEASE, (ltk_callback_arglist){args, LENGTH(args)}) || - (widget_stack[i]->vtable->key_release && widget_stack[i]->vtable->key_release(widget_stack[i], event))) { + for (size_t i = ltk_array_len(widget_stack); i-- > 0 && !handled;) { + ltk_widget_id id = ltk_array_get(widget_stack, i); + ltk_widget *widget = ltk_get_widget_from_id(id); + if (ltk_widget_emit_signal(id, LTK_WIDGET_SIGNAL_KEY_RELEASE, (ltk_callback_arglist){args, LENGTH(args)}) || + (widget->vtable->key_release && widget->vtable->key_release(widget, event))) { handled = 1; break; } @@ -222,11 +232,11 @@ ltk_window_mouse_press_event(ltk_widget *self, ltk_button_event *event) { ltk_widget *widget = get_hover_popup(window, event->x, event->y); int check_hide = 0; if (!widget) { - widget = window->root_widget; + widget = ltk_get_widget_or_null_from_id(window->root_widget); check_hide = 1; } if (!widget) { - ltk_window_unregister_all_popups(window); + ltk_window_unregister_all_popups(self->id); return 1; } int orig_x = event->x, orig_y = event->y; @@ -235,15 +245,15 @@ ltk_window_mouse_press_event(ltk_widget *self, ltk_button_event *event) { -> also, should the widget still get mouse_press even if state doesn't change? */ /* FIXME: doesn't work with e.g. disabled menu entries */ if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { - ltk_window_unregister_all_popups(window); + ltk_window_unregister_all_popups(self->id); } /* FIXME: this doesn't make much sense if the popups aren't a hierarchy (right now, they're just menus, so that's always a hierarchy */ /* don't hide popups if they are children of the now pressed widget */ - if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0]))) - ltk_window_unregister_all_popups(window); + 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))))) + ltk_window_unregister_all_popups(self->id); /* FIXME: popups don't always have their children geometrically contained within parents, so this won't work properly in all cases */ @@ -257,18 +267,18 @@ ltk_window_mouse_press_event(ltk_widget *self, ltk_button_event *event) { if (cur_widget->state != LTK_DISABLED) { /* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled) get mouse press, but they are only set to pressed if they are activatable */ - handled = ltk_widget_emit_signal(cur_widget, LTK_WIDGET_SIGNAL_MOUSE_PRESS, (ltk_callback_arglist){args, LENGTH(args)}); + handled = ltk_widget_emit_signal(cur_widget->id, LTK_WIDGET_SIGNAL_MOUSE_PRESS, (ltk_callback_arglist){args, LENGTH(args)}); if (!handled && cur_widget->vtable->mouse_press) handled = cur_widget->vtable->mouse_press(cur_widget, event); /* set first non-disabled widget to pressed widget */ /* FIXME: use config values for all_activatable */ if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { - ltk_window_set_pressed_widget(window, cur_widget, 0); + ltk_window_set_pressed_widget(self->id, cur_widget->id, 0); first = 0; } } if (!handled) - cur_widget = cur_widget->parent; + cur_widget = ltk_get_widget_or_null_from_id(cur_widget->parent); else break; } @@ -281,7 +291,7 @@ ltk_window_mouse_scroll_event(ltk_widget *self, ltk_scroll_event *event) { /* FIXME: should it first be sent to pressed widget? */ ltk_widget *widget = get_hover_popup(window, event->x, event->y); if (!widget) - widget = window->root_widget; + widget = ltk_get_widget_or_null_from_id(window->root_widget); if (!widget) return 1; int orig_x = event->x, orig_y = event->y; @@ -297,12 +307,12 @@ ltk_window_mouse_scroll_event(ltk_widget *self, ltk_scroll_event *event) { /* FIXME: see function above if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy)) handled = 1; */ - handled = ltk_widget_emit_signal(cur_widget, LTK_WIDGET_SIGNAL_MOUSE_SCROLL, (ltk_callback_arglist){args, LENGTH(args)}); + handled = ltk_widget_emit_signal(cur_widget->id, LTK_WIDGET_SIGNAL_MOUSE_SCROLL, (ltk_callback_arglist){args, LENGTH(args)}); if (!handled && cur_widget->vtable->mouse_scroll) handled = cur_widget->vtable->mouse_scroll(cur_widget, event); } if (!handled) - cur_widget = cur_widget->parent; + cur_widget = ltk_get_widget_or_null_from_id(cur_widget->parent); else break; } @@ -310,19 +320,19 @@ ltk_window_mouse_scroll_event(ltk_widget *self, ltk_scroll_event *event) { } void -ltk_window_fake_motion_event(ltk_window *window, int x, int y) { - ltk_widget *self = LTK_CAST_WIDGET(window); +ltk_window_fake_motion_event(ltk_widget_id windowid, int x, int y) { + ltk_widget *self = ltk_get_widget_from_id(windowid); ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y}; ltk_callback_arg args[] = {LTK_MAKE_ARG_MOTION_EVENT(&e)}; - if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) { - self->vtable->motion_notify(LTK_CAST_WIDGET(window), &e); + if (!ltk_widget_emit_signal(windowid, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) { + self->vtable->motion_notify(self, &e); } } static int ltk_window_mouse_release_event(ltk_widget *self, ltk_button_event *event) { ltk_window *window = LTK_CAST_WINDOW(self); - ltk_widget *widget = window->pressed_widget; + ltk_widget *widget = ltk_get_widget_or_null_from_id(window->pressed_widget); int orig_x = event->x, orig_y = event->y; /* FIXME: why does this only take pressed widget and popups into account? */ if (!widget) { @@ -335,23 +345,24 @@ ltk_window_mouse_release_event(ltk_widget *self, ltk_button_event *event) { /* NOP */ if (widget) { ltk_callback_arg args[] = {LTK_MAKE_ARG_BUTTON_EVENT(event)}; - if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_MOUSE_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_MOUSE_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) { if (widget->vtable->mouse_release) widget->vtable->mouse_release(widget, event); } } if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) { int release = 0; - if (window->pressed_widget) { - ltk_rect prect = window->pressed_widget->lrect; - ltk_point pglob = ltk_widget_pos_to_global(window->pressed_widget, 0, 0); + ltk_widget *pressed_widget = ltk_get_widget_or_null_from_id(window->pressed_widget); + if (pressed_widget) { + ltk_rect prect = pressed_widget->lrect; + ltk_point pglob = ltk_widget_pos_to_global(pressed_widget, 0, 0); if (ltk_collide_rect((ltk_rect){pglob.x, pglob.y, prect.w, prect.h}, orig_x, orig_y)) release = 1; } - ltk_window_set_pressed_widget(window, NULL, release); + ltk_window_set_pressed_widget(self->id, LTK_WIDGET_ID_NONE, release); /* send motion notify to widget under pointer */ /* FIXME: only when not collide with rect? */ - ltk_window_fake_motion_event(window, orig_x, orig_y); + ltk_window_fake_motion_event(self->id, orig_x, orig_y); } return 1; } @@ -363,24 +374,24 @@ ltk_window_motion_notify_event(ltk_widget *self, ltk_motion_event *event) { int orig_x = event->x, orig_y = event->y; ltk_callback_arg args[] = {LTK_MAKE_ARG_MOTION_EVENT(event)}; if (!widget) { - widget = window->pressed_widget; + widget = ltk_get_widget_or_null_from_id(window->pressed_widget); if (widget) { ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y); event->x = local.x; event->y = local.y; - if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) { if (widget->vtable->motion_notify) widget->vtable->motion_notify(widget, event); } return 1; } - widget = window->root_widget; + widget = ltk_get_widget_or_null_from_id(window->root_widget); } if (!widget) return 1; ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y); if (!ltk_collide_rect((ltk_rect){0, 0, widget->lrect.w, widget->lrect.h}, local.x, local.y)) { - ltk_window_set_hover_widget(widget->window, NULL, event); + ltk_window_set_hover_widget(widget->window, LTK_WIDGET_ID_NONE, event); return 1; } ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y); @@ -394,7 +405,7 @@ ltk_window_motion_notify_event(ltk_widget *self, ltk_motion_event *event) { /* FIXME: see functions above if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y)) handled = 1; */ - handled = ltk_widget_emit_signal(cur_widget, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)}); + handled = ltk_widget_emit_signal(cur_widget->id, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)}); if (!handled && cur_widget->vtable->motion_notify) handled = cur_widget->vtable->motion_notify(cur_widget, event); /* set first non-disabled widget to hover widget */ @@ -404,37 +415,42 @@ ltk_window_motion_notify_event(ltk_widget *self, ltk_motion_event *event) { if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) { event->x = orig_x; event->y = orig_y; - ltk_window_set_hover_widget(window, cur_widget, event); + ltk_window_set_hover_widget(self->id, cur_widget->id, event); first = 0; } } if (!handled) - cur_widget = cur_widget->parent; + cur_widget = ltk_get_widget_or_null_from_id(cur_widget->parent); else break; } if (first) { event->x = orig_x; event->y = orig_y; - ltk_window_set_hover_widget(window, NULL, event); + ltk_window_set_hover_widget(self->id, LTK_WIDGET_ID_NONE, event); } return 1; } void -ltk_window_set_root_widget(ltk_window *window, ltk_widget *widget) { - window->root_widget = widget; +ltk_window_set_root_widget(ltk_widget_id windowid, ltk_widget_id widgetid) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); + ltk_widget *widget = ltk_get_widget_from_id(widgetid); + window->root_widget = widgetid; widget->lrect.x = 0; widget->lrect.y = 0; widget->lrect.w = window->rect.w; widget->lrect.h = window->rect.h; widget->crect = widget->lrect; - ltk_window_invalidate_rect(window, widget->lrect); + ltk_window_invalidate_rect(windowid, widget->lrect); ltk_widget_resize(widget); } void -ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) { +ltk_window_invalidate_rect(ltk_widget_id windowid, ltk_rect rect) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0) window->dirty_rect = rect; else @@ -442,7 +458,8 @@ ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) { } void -ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget) { +ltk_window_invalidate_widget_rect(ltk_widget_id window, ltk_widget_id widgetid) { + ltk_widget *widget = ltk_get_widget_from_id(widgetid); ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0); ltk_window_invalidate_rect(window, (ltk_rect){glob.x, glob.y, widget->lrect.w, widget->lrect.h}); } @@ -455,7 +472,6 @@ ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_re (void)clip; if (!self) return; ltk_window *window = LTK_CAST_WINDOW(self); - ltk_widget *ptr; if (window->dirty_rect.x >= window->rect.w) return; if (window->dirty_rect.y >= window->rect.h) return; if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w) @@ -465,13 +481,13 @@ ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_re /* FIXME: this should use window->dirty_rect, but that doesn't work properly with double buffering */ ltk_surface_fill_rect(window->surface, theme.bg, (ltk_rect){0, 0, window->rect.w, window->rect.h}); - if (window->root_widget) { - ptr = window->root_widget; + ltk_widget *ptr = ltk_get_widget_or_null_from_id(window->root_widget); + if (ptr) { ltk_widget_draw(ptr, window->surface, 0, 0, window->rect); } /* last popup is the newest one, so draw that last */ - for (size_t i = 0; i < window->popups_num; i++) { - ptr = window->popups[i]; + for (size_t i = 0; i < ltk_array_len(window->popups); i++) { + ptr = ltk_get_widget_from_id(ltk_array_get(window->popups, i)); ltk_widget_draw(ptr, window->surface, ptr->lrect.x, ptr->lrect.y, ltk_rect_relative(ptr->lrect, window->rect)); } ltk_renderer_swap_buffers(window->renderwindow); @@ -481,10 +497,11 @@ ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_re static void ltk_window_other_event(ltk_window *window, ltk_event *event) { - ltk_widget *ptr = window->root_widget; + ltk_widget *self = LTK_CAST_WIDGET(window); + ltk_widget *ptr = ltk_get_widget_or_null_from_id(window->root_widget); /* FIXME: decide whether this should be moved to separate resize function in window vtable */ if (event->type == LTK_CONFIGURE_EVENT) { - ltk_window_unregister_all_popups(window); + ltk_window_unregister_all_popups(self->id); int w, h; w = event->configure.w; h = event->configure.h; @@ -493,9 +510,9 @@ ltk_window_other_event(ltk_window *window, ltk_event *event) { if (orig_w != w || orig_h != h) { window->rect.w = w; window->rect.h = h; - ltk_window_invalidate_rect(window, window->rect); + ltk_window_invalidate_rect(self->id, window->rect); ltk_surface_update_size(window->surface, w, h); - if (ltk_widget_emit_signal(LTK_CAST_WIDGET(window), LTK_WIDGET_SIGNAL_RESIZE, LTK_EMPTY_ARGLIST)) + if (ltk_widget_emit_signal(self->id, LTK_WIDGET_SIGNAL_RESIZE, LTK_EMPTY_ARGLIST)) return; if (ptr) { ptr->lrect.w = w; @@ -510,55 +527,41 @@ ltk_window_other_event(ltk_window *window, ltk_event *event) { r.y = event->expose.y; r.w = event->expose.w; r.h = event->expose.h; - ltk_window_invalidate_rect(window, r); + ltk_window_invalidate_rect(self->id, r); } else if (event->type == LTK_WINDOWCLOSE_EVENT) { - ltk_widget_emit_signal(LTK_CAST_WIDGET(window), LTK_WINDOW_SIGNAL_CLOSE, LTK_EMPTY_ARGLIST); + ltk_widget_emit_signal(self->id, LTK_WINDOW_SIGNAL_CLOSE, LTK_EMPTY_ARGLIST); } else if (event->type == LTK_DPICHANGE_EVENT) { - if (window->root_widget) { - ltk_window_unregister_all_popups(window); /* easier than trying to resize them */ - ltk_widget_recalc_ideal_size(window->root_widget); - ltk_widget_resize(window->root_widget); + ltk_widget *root_widget = ltk_get_widget_or_null_from_id(window->root_widget); + if (root_widget) { + ltk_window_unregister_all_popups(self->id); /* easier than trying to resize them */ + ltk_widget_recalc_ideal_size(root_widget); + ltk_widget_resize(root_widget); } } } /* FIXME: check for duplicates? */ void -ltk_window_register_popup(ltk_window *window, ltk_widget *popup) { - if (window->popups_num == window->popups_alloc) { - window->popups_alloc = ideal_array_size( - window->popups_alloc, window->popups_num + 1 - ); - window->popups = ltk_reallocarray( - window->popups, window->popups_alloc, sizeof(ltk_widget *) - ); - } - window->popups[window->popups_num++] = popup; +ltk_window_register_popup(ltk_widget_id windowid, ltk_widget_id popup_id) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); + ltk_array_append(widget_id, window->popups, popup_id); + ltk_widget *popup = ltk_get_widget_from_id(popup_id); popup->popup = 1; } void -ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup) { +ltk_window_unregister_popup(ltk_widget_id windowid, ltk_widget_id popup_id) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); if (window->popups_locked) return; - for (size_t i = 0; i < window->popups_num; i++) { - if (window->popups[i] == popup) { + for (size_t i = 0; i < ltk_array_len(window->popups); i++) { + ltk_widget_id id = ltk_array_get(window->popups, i); + if (LTK_WIDGET_ID_EQUAL(id, popup_id)) { + ltk_widget *popup = ltk_get_widget_from_id(popup_id); popup->popup = 0; - memmove( - window->popups + i, - window->popups + i + 1, - sizeof(ltk_widget *) * (window->popups_num - i - 1) - ); - window->popups_num--; - size_t sz = ideal_array_size( - window->popups_alloc, window->popups_num - ); - if (sz != window->popups_alloc) { - window->popups_alloc = sz; - window->popups = ltk_reallocarray( - window->popups, sz, sizeof(ltk_widget *) - ); - } + ltk_array_delete(widget_id, window->popups, i, 1); return; } } @@ -566,24 +569,20 @@ ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup) { /* FIXME: where should actual hiding happen? */ void -ltk_window_unregister_all_popups(ltk_window *window) { +ltk_window_unregister_all_popups(ltk_widget_id windowid) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); window->popups_locked = 1; - for (size_t i = 0; i < window->popups_num; i++) { - window->popups[i]->hidden = 1; - window->popups[i]->popup = 0; - ltk_widget_hide(window->popups[i]); - } - window->popups_num = 0; - /* somewhat arbitrary, but should be enough for most cases */ - if (window->popups_num > 4) { - window->popups = ltk_reallocarray( - window->popups, 4, sizeof(ltk_widget *) - ); - window->popups_alloc = 4; + for (size_t i = 0; i < ltk_array_len(window->popups); i++) { + ltk_widget *popup = ltk_get_widget_from_id(ltk_array_get(window->popups, i)); + popup->hidden = 1; + popup->popup = 0; + ltk_widget_hide(popup); } + ltk_array_reset(widget_id, window->popups); window->popups_locked = 0; /* I guess just invalidate everything instead of being smart */ - ltk_window_invalidate_rect(window, window->rect); + ltk_window_invalidate_rect(windoww->id, window->rect); } /* FIXME: support more options like child windows */ @@ -591,8 +590,7 @@ ltk_window * ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h) { ltk_window *window = ltk_malloc(sizeof(ltk_window)); - window->popups = NULL; - window->popups_num = window->popups_alloc = 0; + window->popups = ltk_array_create(widget_id, 1);; window->popups_locked = 0; ltk_general_config *config = ltk_config_get_general(); @@ -600,10 +598,10 @@ ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, window->renderwindow = ltk_renderer_create_window(data, title, x, y, w, h, dpi); ltk_renderer_set_window_properties(window->renderwindow, theme.bg); - window->root_widget = NULL; - window->hover_widget = NULL; - window->active_widget = NULL; - window->pressed_widget = NULL; + window->root_widget = LTK_WIDGET_ID_NONE; + window->hover_widget = LTK_WIDGET_ID_NONE; + window->active_widget = LTK_WIDGET_ID_NONE; + window->pressed_widget = LTK_WIDGET_ID_NONE; //FIXME: use widget rect window->rect.w = w; @@ -620,7 +618,12 @@ ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, /* This is a bit weird because the window entry points to itself */ /* This needs to be called after window->renderwindow is set */ - ltk_fill_widget_defaults(&window->widget, window, &vtable, 0, 0); + ltk_initialize_widget(LTK_CAST_WIDGET(window), LTK_WIDGET_ID_NONE, &vtable, 0, 0); + /* set manually because it isn't set automatically in ltk_initialize_widget + because the given window id is none + Note: this shouldn't just be set to the initial dpi calculated above since + ltk_renderer_create_window can change that */ + LTK_CAST_WIDGET(window)->last_dpi = ltk_renderer_get_window_dpi(window->renderwindow); return window; } @@ -629,11 +632,11 @@ ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, void ltk_window_destroy_intern(ltk_window *window) { - if (window->root_widget) { - ltk_widget_destroy(window->root_widget, 0); + ltk_widget *root_widget = ltk_get_widget_or_null_from_id(window->root_widget); + if (root_widget) { + ltk_widget_destroy(root_widget, 0); } - if (window->popups) - ltk_free(window->popups); + ltk_array_destroy(widget_id, window->popups); ltk_surface_cache_destroy(window->surface_cache); ltk_surface_destroy(window->surface); ltk_renderer_destroy_window(window->renderwindow); @@ -642,12 +645,15 @@ ltk_window_destroy_intern(ltk_window *window) { /* event must have global coordinates! */ void -ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event) { - ltk_widget *old = window->hover_widget; - if (old == widget) +ltk_window_set_hover_widget(ltk_widget_id windowid, ltk_widget_id widgetid, ltk_motion_event *event) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); + ltk_widget_id oldid = window->hover_widget; + if (LTK_WIDGET_ID_EQUAL(oldid, widgetid)) return; int orig_x = event->x, orig_y = event->y; ltk_callback_arg args[] = {LTK_MAKE_ARG_MOTION_EVENT(event)}; + ltk_widget *old = ltk_get_widget_or_null_from_id(oldid); if (old) { ltk_widget_state old_state = old->state; old->state &= ~LTK_HOVER; @@ -655,40 +661,45 @@ ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_e ltk_point local = ltk_global_to_widget_pos(old, event->x, event->y); event->x = local.x; event->y = local.y; - if (!ltk_widget_emit_signal(old, LTK_WIDGET_SIGNAL_MOUSE_LEAVE, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(oldid, LTK_WIDGET_SIGNAL_MOUSE_LEAVE, (ltk_callback_arglist){args, LENGTH(args)})) { if (old->vtable->mouse_leave) old->vtable->mouse_leave(old, event); } event->x = orig_x; event->y = orig_y; } - window->hover_widget = widget; + window->hover_widget = widgetid; + ltk_widget *widget = ltk_get_widget_or_null_from_id(widgetid); if (widget) { ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y); event->x = local.x; event->y = local.y; - if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_MOUSE_ENTER, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(widgetid, LTK_WIDGET_SIGNAL_MOUSE_ENTER, (ltk_callback_arglist){args, LENGTH(args)})) { if (widget->vtable->mouse_enter) widget->vtable->mouse_enter(widget, event); } ltk_widget_state old_state = widget->state; widget->state |= LTK_HOVER; ltk_widget_change_state(widget, old_state); - if ((widget->vtable->flags & LTK_HOVER_IS_ACTIVE) && widget != window->active_widget) - ltk_window_set_active_widget(window, widget); + if ((widget->vtable->flags & LTK_HOVER_IS_ACTIVE) && !LTK_WIDGET_ID_EQUAL(widgetid, window->active_widget)) + ltk_window_set_active_widget(windowid, widgetid); } } void -ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { - if (window->active_widget == widget) { +ltk_window_set_active_widget(ltk_widget_id windowid, ltk_widget_id widgetid) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); + if (LTK_WIDGET_ID_EQUAL(window->active_widget, widgetid)) { return; } - ltk_widget *old = window->active_widget; + ltk_widget_id oldid = window->active_widget; + ltk_widget *old = ltk_get_widget_or_null_from_id(oldid); /* Note: this has to be set at the beginning to avoid infinite recursion in some cases */ - window->active_widget = widget; + window->active_widget = widgetid; ltk_widget *common_parent = NULL; + ltk_widget *widget = ltk_get_widget_or_null_from_id(widgetid); if (widget) { ltk_widget *cur = widget; while (cur) { @@ -702,7 +713,7 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { if (cur == widget && !(cur->vtable->flags & LTK_NEEDS_KEYBOARD)) widget->state |= LTK_FOCUSED; ltk_widget_change_state(cur, old_state); - cur = cur->parent; + cur = ltk_get_widget_or_null_from_id(cur->parent); } } /* FIXME: better variable names; generally make this nicer */ @@ -711,7 +722,7 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { while (tmp) { if (tmp == old) return; - tmp = tmp->parent; + tmp = ltk_get_widget_or_null_from_id(tmp->parent); } if (old) { old->state &= ~LTK_FOCUSED; @@ -722,33 +733,37 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { ltk_widget_state old_state = cur->state; cur->state &= ~LTK_ACTIVE; ltk_widget_change_state(cur, old_state); - cur = cur->parent; + cur = ltk_get_widget_or_null_from_id(cur->parent); } } } void -ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release) { - if (window->pressed_widget == widget) +ltk_window_set_pressed_widget(ltk_widget_id windowid, ltk_widget_id widgetid, int release) { + ltk_widget *windoww = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(windoww); + if (LTK_WIDGET_ID_EQUAL(window->pressed_widget, widgetid)) return; - if (window->pressed_widget) { - ltk_widget_state old_state = window->pressed_widget->state; - window->pressed_widget->state &= ~LTK_PRESSED; - ltk_widget_change_state(window->pressed_widget, old_state); - ltk_window_set_active_widget(window, window->pressed_widget); + ltk_widget *old = ltk_get_widget_or_null_from_id(window->pressed_widget); + if (old) { + ltk_widget_state old_state = old->state; + old->state &= ~LTK_PRESSED; + ltk_widget_change_state(old, old_state); + ltk_window_set_active_widget(windowid, window->pressed_widget); /* FIXME: this is a bit weird because the release handler for menuentry indirectly calls ltk_widget_hide, which messes with the pressed widget */ /* FIXME: isn't it redundant to check that state is pressed? */ if (release && (old_state & LTK_PRESSED)) { if (!ltk_widget_emit_signal(window->pressed_widget, LTK_WIDGET_SIGNAL_RELEASE, LTK_EMPTY_ARGLIST)) { - if (window->pressed_widget->vtable->release) - window->pressed_widget->vtable->release(window->pressed_widget); + if (old->vtable->release) + old->vtable->release(old); } } } - window->pressed_widget = widget; + window->pressed_widget = widgetid; + ltk_widget *widget = ltk_get_widget_or_null_from_id(widgetid); if (widget) { - if (!ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_PRESS, LTK_EMPTY_ARGLIST)) { + if (!ltk_widget_emit_signal(widgetid, LTK_WIDGET_SIGNAL_PRESS, LTK_EMPTY_ARGLIST)) { if (widget->vtable->press) widget->vtable->press(widget); } @@ -759,19 +774,20 @@ ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int releas } void -ltk_window_handle_event(ltk_window *window, ltk_event *event) { - ltk_widget *self = LTK_CAST_WIDGET(window); +ltk_window_handle_event(ltk_widget_id windowid, ltk_event *event) { + ltk_widget *self = ltk_get_widget_from_id(windowid); + ltk_window *window = LTK_CAST_WINDOW(self); ltk_callback_arg args[1]; switch (event->type) { case LTK_KEYPRESS_EVENT: args[0] = LTK_MAKE_ARG_KEY_EVENT(&event->key); - if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_KEY_PRESS, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(windowid, LTK_WIDGET_SIGNAL_KEY_PRESS, (ltk_callback_arglist){args, LENGTH(args)})) { ltk_window_key_press_event(self, &event->key); } break; case LTK_KEYRELEASE_EVENT: args[0] = LTK_MAKE_ARG_KEY_EVENT(&event->key); - if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_KEY_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(windowid, LTK_WIDGET_SIGNAL_KEY_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) { ltk_window_key_release_event(self, &event->key); } break; @@ -779,13 +795,13 @@ ltk_window_handle_event(ltk_window *window, ltk_event *event) { case LTK_2BUTTONPRESS_EVENT: case LTK_3BUTTONPRESS_EVENT: args[0] = LTK_MAKE_ARG_BUTTON_EVENT(&event->button); - if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOUSE_PRESS, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(windowid, LTK_WIDGET_SIGNAL_MOUSE_PRESS, (ltk_callback_arglist){args, LENGTH(args)})) { ltk_window_mouse_press_event(self, &event->button); } break; case LTK_SCROLL_EVENT: args[0] = LTK_MAKE_ARG_SCROLL_EVENT(&event->scroll); - if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOUSE_SCROLL, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(windowid, LTK_WIDGET_SIGNAL_MOUSE_SCROLL, (ltk_callback_arglist){args, LENGTH(args)})) { ltk_window_mouse_scroll_event(self, &event->scroll); } break; @@ -793,13 +809,13 @@ ltk_window_handle_event(ltk_window *window, ltk_event *event) { case LTK_2BUTTONRELEASE_EVENT: case LTK_3BUTTONRELEASE_EVENT: args[0] = LTK_MAKE_ARG_BUTTON_EVENT(&event->button); - if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOUSE_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(windowid, LTK_WIDGET_SIGNAL_MOUSE_RELEASE, (ltk_callback_arglist){args, LENGTH(args)})) { ltk_window_mouse_release_event(self, &event->button); } break; case LTK_MOTION_EVENT: args[0] = LTK_MAKE_ARG_MOTION_EVENT(&event->motion); - if (!ltk_widget_emit_signal(self, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) { + if (!ltk_widget_emit_signal(windowid, LTK_WIDGET_SIGNAL_MOTION_NOTIFY, (ltk_callback_arglist){args, LENGTH(args)})) { ltk_window_motion_notify_event(self, &event->motion); } break; @@ -816,7 +832,7 @@ get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_x_ret = x - glob.x; *local_y_ret = y - glob.y; while (widget && widget->vtable->get_child_at_pos) { - next = widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret); + next = ltk_get_widget_or_null_from_id(widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret)); if (!next) { break; } else { @@ -835,9 +851,11 @@ get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int static ltk_widget * get_hover_popup(ltk_window *window, int x, int y) { - for (size_t i = window->popups_num; i-- > 0;) { - if (ltk_collide_rect(window->popups[i]->crect, x, y)) - return window->popups[i]; + for (size_t i = ltk_array_len(window->popups); i-- > 0;) { + ltk_widget_id id = ltk_array_get(window->popups, i); + ltk_widget *widget = ltk_get_widget_from_id(id); + if (ltk_collide_rect(widget->crect, x, y)) + return widget; } return NULL; } @@ -845,7 +863,7 @@ get_hover_popup(ltk_window *window, int x, int y) { static int is_parent(ltk_widget *parent, ltk_widget *child) { while (child && child != parent) { - child = child->parent; + child = ltk_get_widget_or_null_from_id(child->parent); } return child != NULL; } @@ -855,23 +873,25 @@ is_parent(ltk_widget *parent, ltk_widget *child) { /* FIXME: handle disabled state */ static int prev_child(ltk_window *window) { - if (!window->root_widget) + ltk_widget *root_widget = ltk_get_widget_or_null_from_id(window->root_widget); + if (!root_widget) return 0; ltk_general_config *config = ltk_config_get_general(); ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; - ltk_widget *new, *cur = window->active_widget; + ltk_widget *new, *cur = ltk_get_widget_or_null_from_id(window->active_widget); int changed = 0; ltk_widget *prevcur = cur; while (1) { if (cur) { - while (cur->parent) { + ltk_widget *parent = ltk_get_widget_or_null_from_id(cur->parent); + while (parent) { new = NULL; - if (cur->parent->vtable->prev_child) - new = cur->parent->vtable->prev_child(cur->parent, cur); + if (parent->vtable->prev_child) + new = ltk_get_widget_or_null_from_id(parent->vtable->prev_child(parent, cur->id)); if (new) { cur = new; ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL; - while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) { + while (cur->vtable->last_child && (new = ltk_get_widget_or_null_from_id(cur->vtable->last_child(cur)))) { cur = new; if (cur->vtable->flags & act_flags) last_activatable = cur; @@ -882,18 +902,19 @@ prev_child(ltk_window *window) { break; } } else { - cur = cur->parent; + cur = parent; if (cur->vtable->flags & act_flags) { changed = 1; break; } } + parent = ltk_get_widget_or_null_from_id(cur->parent); } } if (!changed) { - cur = window->root_widget; + cur = root_widget; ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL; - while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) { + while (cur->vtable->last_child && (new = ltk_get_widget_or_null_from_id(cur->vtable->last_child(cur)))) { cur = new; if (cur->vtable->flags & act_flags) last_activatable = cur; @@ -906,8 +927,8 @@ prev_child(ltk_window *window) { prevcur = cur; } /* FIXME: What exactly should be done if no activatable widget exists? */ - if (cur != window->active_widget) { - ltk_window_set_active_widget(window, cur); + if (!LTK_WIDGET_ID_EQUAL(cur->id, window->active_widget)) { + ltk_window_set_active_widget(LTK_CAST_WIDGET(window)->id, cur->id); ensure_active_widget_shown(window); return 1; } @@ -916,17 +937,17 @@ prev_child(ltk_window *window) { static int next_child(ltk_window *window) { - if (!window->root_widget) + ltk_widget *root_widget = ltk_get_widget_or_null_from_id(window->root_widget); + if (!root_widget) return 0; ltk_general_config *config = ltk_config_get_general(); ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; - ltk_widget *new, *cur = window->active_widget; + ltk_widget *new, *cur = ltk_get_widget_or_null_from_id(window->active_widget); int changed = 0; ltk_widget *prevcur = cur; while (1) { if (cur) { - - while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) { + while (cur->vtable->first_child && (new = ltk_get_widget_or_null_from_id(cur->vtable->first_child(cur)))) { cur = new; if (cur->vtable->flags & act_flags) { changed = 1; @@ -934,17 +955,18 @@ next_child(ltk_window *window) { } } if (!changed) { - while (cur->parent) { + ltk_widget *parent = ltk_get_widget_or_null_from_id(cur->parent); + while (parent) { new = NULL; - if (cur->parent->vtable->next_child) - new = cur->parent->vtable->next_child(cur->parent, cur); + if (parent->vtable->next_child) + new = ltk_get_widget_or_null_from_id(parent->vtable->next_child(parent, cur->id)); if (new) { cur = new; if (cur->vtable->flags & act_flags) { changed = 1; break; } - while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) { + while (cur->vtable->first_child && (new = ltk_get_widget_or_null_from_id(cur->vtable->first_child(cur)))) { cur = new; if (cur->vtable->flags & act_flags) { changed = 1; @@ -954,29 +976,30 @@ next_child(ltk_window *window) { if (changed) break; } else { - cur = cur->parent; + cur = parent; } + parent = ltk_get_widget_or_null_from_id(cur->parent); } } } if (!changed) { - cur = window->root_widget; + cur = root_widget; if (!(cur->vtable->flags & act_flags)) { - while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) { + while (cur->vtable->first_child && (new = ltk_get_widget_or_null_from_id(cur->vtable->first_child(cur)))) { cur = new; if (cur->vtable->flags & act_flags) break; } } if (!(cur->vtable->flags & act_flags)) - cur = window->root_widget; + cur = root_widget; } if (prevcur == cur || (cur && (cur->vtable->flags & act_flags))) break; prevcur = cur; } - if (cur != window->active_widget) { - ltk_window_set_active_widget(window, cur); + if (!LTK_WIDGET_ID_EQUAL(cur->id, window->active_widget)) { + ltk_window_set_active_widget(LTK_CAST_WIDGET(window)->id, cur->id); ensure_active_widget_shown(window); return 1; } @@ -994,22 +1017,23 @@ nearest_child(ltk_widget *widget, ltk_rect r) { ltk_point local = ltk_global_to_widget_pos(widget, r.x, r.y); ltk_rect rect = {local.x, local.y, r.w, r.h}; if (widget->vtable->nearest_child) - return widget->vtable->nearest_child(widget, rect); + return ltk_get_widget_or_null_from_id(widget->vtable->nearest_child(widget, rect)); return NULL; } /* FIXME: maybe wrap around in these two functions? */ static int left_top_child(ltk_window *window, int left) { - if (!window->root_widget) + ltk_widget *root_widget = ltk_get_widget_or_null_from_id(window->root_widget); + if (!root_widget) return 0; ltk_general_config *config = ltk_config_get_general(); ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; - ltk_widget *new, *cur = window->active_widget; + ltk_widget *new, *cur = ltk_get_widget_or_null_from_id(window->active_widget); ltk_rect old_rect = {0, 0, 0, 0}; ltk_widget *last_activatable = NULL; if (!cur) { - cur = window->root_widget; + cur = root_widget; if (cur->vtable->flags & act_flags) last_activatable = cur; ltk_rect r = {cur->lrect.w, cur->lrect.h, 0, 0}; @@ -1022,16 +1046,23 @@ left_top_child(ltk_window *window, int left) { if (last_activatable) { cur = last_activatable; } else if (cur) { - ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y}; + ltk_widget *parent = ltk_get_widget_or_null_from_id(cur->parent); + ltk_point glob = parent ? ltk_widget_pos_to_global(parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y}; old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h}; - while (cur->parent) { + while (parent) { new = NULL; if (left) { - if (cur->parent->vtable->nearest_child_left) - new = cur->parent->vtable->nearest_child_left(cur->parent, cur); + if (parent->vtable->nearest_child_left) { + new = ltk_get_widget_or_null_from_id( + parent->vtable->nearest_child_left(parent, cur->id) + ); + } } else { - if (cur->parent->vtable->nearest_child_above) - new = cur->parent->vtable->nearest_child_above(cur->parent, cur); + if (parent->vtable->nearest_child_above) { + new = ltk_get_widget_or_null_from_id( + parent->vtable->nearest_child_above(parent, cur->id) + ); + } } if (new) { cur = new; @@ -1046,16 +1077,17 @@ left_top_child(ltk_window *window, int left) { break; } } else { - cur = cur->parent; + cur = parent; if (cur->vtable->flags & act_flags) { break; } } + parent = ltk_get_widget_or_null_from_id(cur->parent); } } /* FIXME: What exactly should be done if no activatable widget exists? */ - if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) { - ltk_window_set_active_widget(window, cur); + if (cur && !LTK_WIDGET_ID_EQUAL(cur->id, window->active_widget) && (cur->vtable->flags & act_flags)) { + ltk_window_set_active_widget(LTK_CAST_WIDGET(window)->id, cur->id); ensure_active_widget_shown(window); return 1; } @@ -1064,17 +1096,18 @@ left_top_child(ltk_window *window, int left) { static int right_bottom_child(ltk_window *window, int right) { - if (!window->root_widget) + ltk_widget *root_widget = ltk_get_widget_or_null_from_id(window->root_widget); + if (!root_widget) return 0; ltk_general_config *config = ltk_config_get_general(); ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL; - ltk_widget *new, *cur = window->active_widget; + ltk_widget *new, *cur = ltk_get_widget_or_null_from_id(window->active_widget); int changed = 0; ltk_rect old_rect = {0, 0, 0, 0}; ltk_rect corner = {0, 0, 0, 0}; int found_activatable = 0; if (!cur) { - cur = window->root_widget; + cur = root_widget; if (!(cur->vtable->flags & act_flags)) { while ((new = nearest_child(cur, (ltk_rect){0, 0, 0, 0}))) { cur = new; @@ -1086,7 +1119,8 @@ right_bottom_child(ltk_window *window, int right) { } } if (!found_activatable) { - 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}; + ltk_widget *parent = ltk_get_widget_or_null_from_id(cur->parent); + ltk_point glob = parent ? ltk_widget_pos_to_global(parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y}; corner = (ltk_rect){glob.x, glob.y, 0, 0}; old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h}; while ((new = nearest_child(cur, corner))) { @@ -1097,14 +1131,20 @@ right_bottom_child(ltk_window *window, int right) { } } if (!changed) { - while (cur->parent) { + while ((parent = ltk_get_widget_or_null_from_id(cur->parent))) { new = NULL; if (right) { - if (cur->parent->vtable->nearest_child_right) - new = cur->parent->vtable->nearest_child_right(cur->parent, cur); + if (parent->vtable->nearest_child_right) { + new = ltk_get_widget_or_null_from_id( + parent->vtable->nearest_child_right(parent, cur->id) + ); + } } else { - if (cur->parent->vtable->nearest_child_below) - new = cur->parent->vtable->nearest_child_below(cur->parent, cur); + if (parent->vtable->nearest_child_below) { + new = ltk_get_widget_or_null_from_id( + parent->vtable->nearest_child_below(parent, cur->id) + ); + } } if (new) { cur = new; @@ -1122,13 +1162,13 @@ right_bottom_child(ltk_window *window, int right) { if (changed) break; } else { - cur = cur->parent; + cur = ltk_get_widget_or_null_from_id(cur->parent); } } } } - if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) { - ltk_window_set_active_widget(window, cur); + if (cur && !LTK_WIDGET_ID_EQUAL(cur->id, window->active_widget) && (cur->vtable->flags & act_flags)) { + ltk_window_set_active_widget(LTK_CAST_WIDGET(window)->id, cur->id); ensure_active_widget_shown(window); return 1; } @@ -1138,15 +1178,15 @@ right_bottom_child(ltk_window *window, int right) { /* FIXME: maybe just set this when active widget changes */ /* -> but would also need to change it when widgets are created/destroyed or parents change */ static void -gen_widget_stack(ltk_widget *bottom) { - widget_stack_len = 0; +gen_widget_stack(ltk_widget_id bottomid) { + if (!widget_stack) + widget_stack = ltk_array_create(widget_id, 1); + ltk_array_clear(widget_stack); + ltk_widget *bottom = ltk_get_widget_or_null_from_id(bottomid); while (bottom) { - if (widget_stack_len + 1 > widget_stack_alloc) { - widget_stack_alloc = ideal_array_size(widget_stack_alloc, widget_stack_len + 1); - widget_stack = ltk_reallocarray(widget_stack, widget_stack_alloc, sizeof(ltk_widget *)); - } - widget_stack[widget_stack_len++] = bottom; - bottom = bottom->parent; + ltk_array_append(widget_id, widget_stack, bottomid); + bottomid = bottom->parent; + bottom = ltk_get_widget_or_null_from_id(bottomid); } } @@ -1157,11 +1197,12 @@ static int cb_focus_active(ltk_widget *self, ltk_key_event *event) { (void)event; ltk_window *window = LTK_CAST_WINDOW(self); - if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) { + ltk_widget *active_widget = ltk_get_widget_or_null_from_id(window->active_widget); + if (active_widget && !(active_widget->state & LTK_FOCUSED)) { /* FIXME: maybe also set widgets above in hierarchy? */ - ltk_widget_state old_state = window->active_widget->state; - window->active_widget->state |= LTK_FOCUSED; - ltk_widget_change_state(window->active_widget, old_state); + ltk_widget_state old_state = active_widget->state; + active_widget->state |= LTK_FOCUSED; + ltk_widget_change_state(active_widget, old_state); return 1; } return 0; @@ -1171,10 +1212,11 @@ static int cb_unfocus_active(ltk_widget *self, ltk_key_event *event) { (void)event; ltk_window *window = LTK_CAST_WINDOW(self); - if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) { - ltk_widget_state old_state = window->active_widget->state; - window->active_widget->state &= ~LTK_FOCUSED; - ltk_widget_change_state(window->active_widget, old_state); + ltk_widget *active_widget = ltk_get_widget_or_null_from_id(window->active_widget); + if (active_widget && (active_widget->state & LTK_FOCUSED) && (active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) { + ltk_widget_state old_state = active_widget->state; + active_widget->state &= ~LTK_FOCUSED; + ltk_widget_change_state(active_widget, old_state); return 1; } return 0; @@ -1226,9 +1268,10 @@ static int cb_set_pressed(ltk_widget *self, ltk_key_event *event) { (void)event; ltk_window *window = LTK_CAST_WINDOW(self); - if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { + ltk_widget *active_widget = ltk_get_widget_or_null_from_id(window->active_widget); + if (active_widget && (active_widget->state & LTK_FOCUSED)) { /* FIXME: only set pressed if needs keyboard? */ - ltk_window_set_pressed_widget(window, window->active_widget, 0); + ltk_window_set_pressed_widget(self->id, window->active_widget, 0); return 1; } return 0; @@ -1238,8 +1281,8 @@ static int cb_unset_pressed(ltk_widget *self, ltk_key_event *event) { (void)event; ltk_window *window = LTK_CAST_WINDOW(self); - if (window->pressed_widget) { - ltk_window_set_pressed_widget(window, NULL, 1); + if (!LTK_WIDGET_ID_IS_NONE(window->pressed_widget)) { + ltk_window_set_pressed_widget(self->id, LTK_WIDGET_ID_NONE, 1); return 1; } return 0; @@ -1249,8 +1292,8 @@ static int cb_remove_popups(ltk_widget *self, ltk_key_event *event) { (void)event; ltk_window *window = LTK_CAST_WINDOW(self); - if (window->popups_num > 0) { - ltk_window_unregister_all_popups(window); + if (ltk_array_len(window->popups) > 0) { + ltk_window_unregister_all_popups(self->id); return 1; } return 0; diff --git a/src/ltk/window.h b/src/ltk/window.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2020-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,17 +33,14 @@ typedef struct ltk_window { ltk_surface_cache *surface_cache; ltk_surface *surface; /* FIXME: check if these are reset properly if widget is deleted */ - ltk_widget *root_widget; - ltk_widget *hover_widget; - ltk_widget *active_widget; - ltk_widget *pressed_widget; + ltk_widget_id root_widget; + ltk_widget_id hover_widget; + ltk_widget_id active_widget; + ltk_widget_id pressed_widget; ltk_rect rect; ltk_rect dirty_rect; - /* FIXME: generic array */ - ltk_widget **popups; - size_t popups_num; - size_t popups_alloc; + ltk_array(widget_id) *popups; /* This is a hack so ltk_window_unregister_all_popups can call hide for all popup widgets even if the hide function already calls ltk_window_unregister_popup */ @@ -51,23 +48,28 @@ typedef struct ltk_window { } ltk_window; /* FIXME: which of these should be internal to LTK? */ -void ltk_window_handle_event(ltk_window *window, ltk_event *event); -void ltk_window_fake_motion_event(ltk_window *window, int x, int y); +/* FIXME: should this maybe still take ltk_window directly? */ +void ltk_window_handle_event(ltk_widget_id windowid, ltk_event *event); +void ltk_window_fake_motion_event(ltk_widget_id window, int x, int y); -void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect); -void ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget); +/* FIXME: should there be versions that directly take window and widget instead of IDs? + -> I guess for invalidate_widget_rect, it doesn't really change much efficiency-wise + since getting the absolute widget rect goes through the parent hierarchy and needs + to look up all the widgets on the path anyways */ +void ltk_window_invalidate_rect(ltk_widget_id window, ltk_rect rect); +void ltk_window_invalidate_widget_rect(ltk_widget_id window, ltk_widget_id widget); -void ltk_window_set_root_widget(ltk_window *window, ltk_widget *widget); -void ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event); -void ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget); -void ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release); +void ltk_window_set_root_widget(ltk_widget_id window, ltk_widget_id widget); +void ltk_window_set_hover_widget(ltk_widget_id window, ltk_widget_id widget, ltk_motion_event *event); +void ltk_window_set_active_widget(ltk_widget_id window, ltk_widget_id widget); +void ltk_window_set_pressed_widget(ltk_widget_id window, ltk_widget_id widget, int release); /* IMPORTANT: Callers must call ltk_widget_recalc_ideal_size and ltk_widget_resize first to take DPI changes into account. It wouldn't make sense for ltk_window_register_popup to call these functions because the calling function usually needs to know the actual size of the popup to determine where to place it. */ -void ltk_window_register_popup(ltk_window *window, ltk_widget *popup); -void ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup); -void ltk_window_unregister_all_popups(ltk_window *window); +void ltk_window_register_popup(ltk_widget_id window, ltk_widget_id popup); +void ltk_window_unregister_popup(ltk_widget_id window, ltk_widget_id popup); +void ltk_window_unregister_all_popups(ltk_widget_id window); #endif /* LTK_WINDOW_H */ diff --git a/src/ltkd/box.c b/src/ltkd/box.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,20 +28,19 @@ /* box <box id> add <widget id> [sticky] */ static int ltkd_box_cmd_add( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_box *box = LTK_CAST_BOX(widget->widget); + (void)windowid; ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_UNKNOWN, .optional = 0}, {.type = CMDARG_STICKY, .optional = 1}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - if (ltk_box_add(box, cmd[0].val.widget->widget, cmd[1].initialized ? cmd[1].val.sticky : 0)) { + if (ltk_box_add(widget->ltkid, cmd[0].val.widget->ltkid, cmd[1].initialized ? cmd[1].val.sticky : 0)) { err->type = ERR_WIDGET_IN_CONTAINER; err->arg = 0; return 1; @@ -52,19 +51,18 @@ ltkd_box_cmd_add( /* box <box id> remove <widget id> */ static int ltkd_box_cmd_remove( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_box *box = LTK_CAST_BOX(widget->widget); + (void)windowid; ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_UNKNOWN, .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - if (ltk_box_remove(box, cmd[0].val.widget->widget)) { + if (ltk_box_remove(widget->ltkid, cmd[0].val.widget->ltkid)) { err->type = ERR_WIDGET_NOT_IN_CONTAINER; err->arg = 0; return 1; @@ -75,7 +73,7 @@ ltkd_box_cmd_remove( /* box <box id> create <orientation> */ static int ltkd_box_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -89,9 +87,9 @@ ltkd_box_cmd_create( }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_box *box = ltk_box_create(window, cmd[3].val.orient); - if (!ltkd_widget_create(LTK_CAST_WIDGET(box), cmd[1].val.str, NULL, 0, err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(box), 1); + ltk_widget_id box = ltk_box_create(windowid, cmd[3].val.orient); + if (!ltkd_widget_create(box, cmd[1].val.str, NULL, 0, err)) { + ltk_widget_id_destroy(box, 1); err->arg = 1; return 1; } diff --git a/src/ltkd/button.c b/src/ltkd/button.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -41,7 +41,7 @@ static ltkd_event_handler handlers[] = { /* button <button id> create <text> */ static int ltkd_button_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -55,9 +55,9 @@ ltkd_button_cmd_create( }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_button *button = ltk_button_create(window, cmd[3].val.str); - if (!ltkd_widget_create(LTK_CAST_WIDGET(button), cmd[1].val.str, handlers, LENGTH(handlers), err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(button), 1); + ltk_widget_id button = ltk_button_create(windowid, cmd[3].val.str); + if (!ltkd_widget_create(button, cmd[1].val.str, handlers, LENGTH(handlers), err)) { + ltk_widget_id_destroy(button, 1); err->arg = 1; return 1; } diff --git a/src/ltkd/cmd.h b/src/ltkd/cmd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2023-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -30,7 +30,7 @@ typedef struct { char *name; - int (*func)(ltk_window *, ltkd_widget *, ltkd_cmd_token *, size_t, ltkd_error *); + int (*func)(ltk_widget_id, ltkd_widget *, ltkd_cmd_token *, size_t, ltkd_error *); int needs_all; } ltkd_cmd_info; @@ -81,7 +81,7 @@ int ltkd_parse_cmd( /* FIXME: This doesn't really need to be a macro anymore since it doesn't have to be generic for different types anymore */ #define GEN_CMD_HELPERS_PROTO(func_name) \ -int func_name(ltk_window *window, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err); +int func_name(ltk_widget_id windowid, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err); #define GEN_CMD_HELPERS(func_name, widget_type, array_name) \ @@ -104,7 +104,7 @@ array_name##_sort_helper(const void *entry1v, const void *entry2v) { \ \ int \ func_name( \ - ltk_window *window, \ + ltk_widget_id windowid, \ ltkd_cmd_token *tokens, \ size_t num_tokens, \ ltkd_error *err) { \ @@ -139,14 +139,14 @@ func_name( \ return 1; \ } \ if (e->needs_all) { \ - return e->func(window, NULL, tokens, num_tokens, err); \ + return e->func(windowid, NULL, tokens, num_tokens, err); \ } else { \ ltkd_widget *widget = ltkd_get_widget(tokens[1].text, widget_type, err); \ if (!widget) { \ err->arg = 1; \ return 1; \ } \ - int ret = e->func(window, widget, tokens + 3, num_tokens - 3, err); \ + int ret = e->func(windowid, widget, tokens + 3, num_tokens - 3, err); \ if (ret && err->arg >= 0) \ err->arg += 3; \ return ret; \ diff --git a/src/ltkd/entry.c b/src/ltkd/entry.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,7 +29,7 @@ /* entry <entry id> create <text> */ static int ltkd_entry_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -43,9 +43,9 @@ ltkd_entry_cmd_create( }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_entry *entry = ltk_entry_create(window, cmd[3].val.str); - if (!ltkd_widget_create(LTK_CAST_WIDGET(entry), cmd[1].val.str, NULL, 0, err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(entry), 1); + ltk_widget_id entry = ltk_entry_create(windowid, cmd[3].val.str); + if (!ltkd_widget_create(entry, cmd[1].val.str, NULL, 0, err)) { + ltk_widget_id_destroy(entry, 1); err->arg = 1; return 1; } diff --git a/src/ltkd/grid.c b/src/ltkd/grid.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,13 +24,14 @@ /* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> [sticky] */ static int ltkd_grid_cmd_add( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_grid *grid = LTK_CAST_GRID(widget->widget); + (void)windowid; + ltk_widget *gridw = ltk_get_widget_from_id(widget->ltkid); + ltk_grid *grid = LTK_CAST_GRID(gridw); ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_UNKNOWN, .optional = 0}, {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0}, @@ -55,7 +56,7 @@ ltkd_grid_cmd_add( /* FIXME: check if recalculation deals properly with rowspan/columnspan that goes over the edge of the grid */ if (ltk_grid_add( - grid, cmd[0].val.widget->widget, + widget->ltkid, cmd[0].val.widget->ltkid, row, col, rowspan, colspan, cmd[5].initialized ? cmd[5].val.sticky : 0)) { err->type = ERR_WIDGET_IN_CONTAINER; @@ -68,19 +69,18 @@ ltkd_grid_cmd_add( /* grid <grid id> remove <widget id> */ static int ltkd_grid_cmd_ungrid( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_grid *grid = LTK_CAST_GRID(widget->widget); + (void)windowid; ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_UNKNOWN, .optional = 0} }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - if (ltk_grid_remove(grid, cmd[0].val.widget->widget)) { + if (ltk_grid_remove(widget->ltkid, cmd[0].val.widget->ltkid)) { err->type = ERR_WIDGET_NOT_IN_CONTAINER; err->arg = 0; return 1; @@ -92,7 +92,7 @@ ltkd_grid_cmd_ungrid( /* grid <grid id> create <rows> <columns> */ static int ltkd_grid_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -107,9 +107,9 @@ ltkd_grid_cmd_create( }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_grid *grid = ltk_grid_create(window, cmd[3].val.i, cmd[4].val.i); - if (!ltkd_widget_create(LTK_CAST_WIDGET(grid), cmd[1].val.str, NULL, 0, err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(grid), 1); + ltk_widget_id grid = ltk_grid_create(windowid, cmd[3].val.i, cmd[4].val.i); + if (!ltkd_widget_create(grid, cmd[1].val.str, NULL, 0, err)) { + ltk_widget_id_destroy(grid, 1); err->arg = 1; return 1; } @@ -121,20 +121,21 @@ ltkd_grid_cmd_create( /* grid <grid id> set-row-weight <row> <weight> */ static int ltkd_grid_cmd_set_row_weight( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_grid *grid = LTK_CAST_GRID(widget->widget); + (void)windowid; + ltk_widget *gridw = ltk_get_widget_from_id(widget->ltkid); + ltk_grid *grid = LTK_CAST_GRID(gridw); ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0}, {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_grid_set_row_weight(grid, cmd[0].val.i, cmd[1].val.i); + ltk_grid_set_row_weight(widget->ltkid, cmd[0].val.i, cmd[1].val.i); return 0; } @@ -145,20 +146,21 @@ ltkd_grid_cmd_set_row_weight( /* grid <grid id> set-column-weight <column> <weight> */ static int ltkd_grid_cmd_set_column_weight( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_grid *grid = LTK_CAST_GRID(widget->widget); + (void)windowid; + ltk_widget *gridw = ltk_get_widget_from_id(widget->ltkid); + ltk_grid *grid = LTK_CAST_GRID(gridw); ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_INT, .min = 0, .max = grid->columns - 1, .optional = 0}, {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_grid_set_column_weight(grid, cmd[0].val.i, cmd[1].val.i); + ltk_grid_set_column_weight(widget->ltkid, cmd[0].val.i, cmd[1].val.i); return 0; } diff --git a/src/ltkd/image_widget.c b/src/ltkd/image_widget.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2023-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,7 +29,7 @@ /* image <image id> create <filename> <data> */ static int ltkd_image_widget_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -51,9 +51,9 @@ ltkd_image_widget_cmd_create( err->arg = -1; return 1; } - ltk_image_widget *imgw = ltk_image_widget_create(window, img); - if (!ltkd_widget_create(LTK_CAST_WIDGET(imgw), cmd[1].val.str, NULL, 0, err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(imgw), 1); + ltk_widget_id imgw = ltk_image_widget_create(windowid, img); + if (!ltkd_widget_create(imgw, cmd[1].val.str, NULL, 0, err)) { + ltk_widget_id_destroy(imgw, 1); err->arg = 1; return 1; } diff --git a/src/ltkd/label.c b/src/ltkd/label.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,7 +28,7 @@ /* label <label id> create <text> */ static int ltkd_label_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -42,9 +42,9 @@ ltkd_label_cmd_create( }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_label *label = ltk_label_create(window, cmd[3].val.str); - if (!ltkd_widget_create(LTK_CAST_WIDGET(label), cmd[1].val.str, NULL, 0, err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(label), 1); + ltk_widget_id label = ltk_label_create(windowid, cmd[3].val.str); + if (!ltkd_widget_create(label, cmd[1].val.str, NULL, 0, err)) { + ltk_widget_id_destroy(label, 1); err->arg = 1; return 1; } diff --git a/src/ltkd/ltkd.c b/src/ltkd/ltkd.c @@ -1,8 +1,9 @@ +/* FIXME: standardize functions taking window or not */ /* FIXME: Figure out how to properly print window id */ /* FIXME: error checking in tokenizer (is this necessary?) */ /* FIXME: strip whitespace at end of lines in socket format */ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -51,6 +52,7 @@ #define READ_BLK_SIZE 128 #define WRITE_BLK_SIZE 128 +/* FIXME: replace with generic array */ struct token_list { ltkd_cmd_token *tokens; /* FIXME: size_t everywhere */ @@ -81,7 +83,7 @@ static struct ltkd_sock_info { static int daemonize_flag = 1; -static void ltkd_mainloop(ltk_window *window); +static void ltkd_mainloop(void); static char *get_sock_path(char *basedir, unsigned long id); static FILE *open_log(char *dir); static void daemonize(void); @@ -90,8 +92,8 @@ static int push_token(struct token_list *tl, char *token); static int read_sock(struct ltkd_sock_info *sock); static int write_sock(struct ltkd_sock_info *sock); static int tokenize_command(struct ltkd_sock_info *sock); -static int ltkd_set_root_widget_cmd(ltk_window *window, ltkd_cmd_token *tokens, int num_tokens, ltkd_error *err); -static int process_commands(ltk_window *window, int client); +static int ltkd_set_root_widget_cmd(ltk_widget_id windowid, ltkd_cmd_token *tokens, int num_tokens, ltkd_error *err); +static int process_commands(ltk_widget_id windowid, int client); static int add_client(int fd); static int listen_sock(const char *sock_path); static int accept_sock(int listenfd); @@ -107,11 +109,12 @@ static FILE *ltkd_logfile = NULL; static char *sock_path = NULL; /* Note: Most functions still take this explicitly because it wasn't global originally, but that's just the way it is. */ -static ltk_window *main_window = NULL; +static ltk_widget_id main_window = LTK_WIDGET_ID_NONE_STATIC; +static unsigned long main_window_id = 0; typedef struct { char *name; - int (*cmd)(ltk_window *, ltkd_cmd_token *, size_t, ltkd_error *); + int (*cmd)(ltk_widget_id, ltkd_cmd_token *, size_t, ltkd_error *); } ltkd_widget_funcs; /* FIXME: use binary search when searching for the widget */ @@ -202,11 +205,13 @@ main(int argc, char *argv[]) { /* FIXME: set window size properly - I only run it in a tiling WM anyways, so it doesn't matter, but still... */ main_window = ltk_window_create(title, 0, 0, 500, 500); - ltk_widget_register_signal_handler(LTK_CAST_WIDGET(main_window), LTK_WINDOW_SIGNAL_CLOSE, &ltkd_window_close, LTK_ARG_VOID); - - sock_path = get_sock_path(ltkd_dir, ltk_renderer_get_window_id(main_window->renderwindow)); + ltk_widget *windoww = ltk_get_widget_from_id(main_window); + main_window_id = ltk_renderer_get_window_id(LTK_CAST_WINDOW(windoww)->renderwindow); + sock_path = get_sock_path(ltkd_dir, main_window_id); if (!sock_path) ltkd_fatal_errno("Unable to allocate memory for socket path.\n"); + ltk_widget_register_signal_handler(main_window, LTK_WINDOW_SIGNAL_CLOSE, &ltkd_window_close, LTK_ARG_VOID); + /* Note: sockets should be initialized to 0 because it is static */ for (int i = 0; i < MAX_SOCK_CONNS; i++) { sockets[i].fd = -1; /* socket unused */ @@ -216,7 +221,7 @@ main(int argc, char *argv[]) { sockets[i].tokens.tokens = NULL; } - ltkd_mainloop(main_window); + ltkd_mainloop(); return 0; } @@ -231,7 +236,7 @@ static struct { can be executed, so for instance the widget that caused the lock could also be destroyed, causing issues when this function returns */ int -ltkd_handle_lock_client(ltk_window *window, int client) { +ltkd_handle_lock_client(ltk_widget_id windowid, int client) { if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1) return 0; fd_set rfds, wfds, rallfds, wallfds; @@ -257,7 +262,7 @@ ltkd_handle_lock_client(ltk_window *window, int client) { int ret; while ((ret = read_sock(&sockets[client])) == 1) { int pret; - if ((pret = process_commands(window, client)) == 1) + if ((pret = process_commands(windowid, client)) == 1) return 1; else if (pret == -1) return 0; @@ -301,7 +306,7 @@ ltkd_handle_lock_client(ltk_window *window, int client) { /* FIXME: need to remove event masks from all widgets when removing client */ static void -ltkd_mainloop(ltk_window *window) { +ltkd_mainloop(void) { fd_set rfds, wfds; int retval; int clifd; @@ -318,7 +323,7 @@ ltkd_mainloop(ltk_window *window) { FD_SET(sock_state.listenfd, &sock_state.rallfds); sock_state.maxfd = sock_state.listenfd; - printf("%lu", ltk_renderer_get_window_id(main_window->renderwindow)); + printf("%lu", main_window_id); fflush(stdout); if (daemonize_flag) daemonize(); @@ -357,7 +362,7 @@ ltkd_mainloop(ltk_window *window) { /* or maybe measure time and break after max time? */ int ret; while ((ret = read_sock(&sockets[i])) == 1) { - process_commands(window, i); + process_commands(main_window, i); } if (ret == 0) { ltkd_widget_remove_client(i); @@ -499,7 +504,8 @@ ltkd_cleanup(void) { } ltkd_widgets_cleanup(); - main_window = NULL; + main_window = LTK_WIDGET_ID_NONE; + main_window_id = 0; ltk_deinit(); } @@ -518,8 +524,8 @@ ltkd_log_msg(const char *mode, const char *format, va_list args) { timeptr = localtime(&clock); strftime(logtime, 25, "%Y-%m-%d %H:%M:%S", timeptr); - if (main_window) - fprintf(stderr, "%s ltkd(%lu) %s: ", logtime, ltk_renderer_get_window_id(main_window->renderwindow), mode); + if (!LTK_WIDGET_ID_IS_NONE(main_window)) + fprintf(stderr, "%s ltkd(%lu) %s: ", logtime, main_window_id, mode); else fprintf(stderr, "%s ltkd(?) %s: ", logtime, mode); vfprintf(stderr, format, args); @@ -527,11 +533,10 @@ ltkd_log_msg(const char *mode, const char *format, va_list args) { static int ltkd_set_root_widget_cmd( - ltk_window *window, + ltk_widget_id windowid, ltkd_cmd_token *tokens, int num_tokens, ltkd_error *err) { - ltkd_widget *widget; if (num_tokens != 2) { err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS; err->arg = -1; @@ -541,12 +546,12 @@ ltkd_set_root_widget_cmd( err->arg = 1; return 1; } - widget = ltkd_get_widget(tokens[1].text, LTK_WIDGET_UNKNOWN, err); + ltkd_widget *widget = ltkd_get_widget(tokens[1].text, LTK_WIDGET_UNKNOWN, err); if (!widget) { err->arg = 1; return 1; } - ltk_window_set_root_widget(window, widget->widget); + ltk_window_set_root_widget(windowid, widget->ltkid); return 0; } @@ -975,7 +980,7 @@ handle_mask_command(int client, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_ /* Returns 1 if command was 'event-unlock true', -1 if command was 'event-unlock false', 0 otherwise. */ static int -process_commands(ltk_window *window, int client) { +process_commands(ltk_widget_id windowid, int client) { if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1) return 0; struct ltkd_sock_info *sock = &sockets[client]; @@ -1013,12 +1018,12 @@ process_commands(ltk_window *window, int client) { err = 1; seq = sock->last_seq; } else if (strcmp(tokens[0].text, "set-root-widget") == 0) { - err = ltkd_set_root_widget_cmd(window, tokens, num_tokens, &errdetail); + err = ltkd_set_root_widget_cmd(windowid, tokens, num_tokens, &errdetail); } else if (strcmp(tokens[0].text, "quit") == 0) { ltkd_quit(); last = 1; } else if (strcmp(tokens[0].text, "destroy") == 0) { - err = ltkd_widget_destroy_cmd(window, tokens, num_tokens, &errdetail); + err = ltkd_widget_destroy_cmd(windowid, tokens, num_tokens, &errdetail); } else if (strncmp(tokens[0].text, "mask", 4) == 0) { err = handle_mask_command(client, tokens, num_tokens, &errdetail); } else if (strcmp(tokens[0].text, "event-unlock") == 0) { @@ -1040,7 +1045,7 @@ process_commands(ltk_window *window, int client) { int found = 0; for (size_t i = 0; i < LENGTH(widget_funcs); i++) { if (widget_funcs[i].cmd && !strcmp(tokens[0].text, widget_funcs[i].name)) { - err = widget_funcs[i].cmd(window, tokens, num_tokens, &errdetail); + err = widget_funcs[i].cmd(windowid, tokens, num_tokens, &errdetail); found = 1; } } diff --git a/src/ltkd/ltkd.h b/src/ltkd/ltkd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,8 +33,8 @@ typedef enum { LTK_EVENT_MENU = 1 << 3 } ltkd_userevent_type; -void ltkd_queue_event(ltk_window *window, ltkd_userevent_type type, const char *id, const char *data); -int ltkd_handle_lock_client(ltk_window *window, int client); +void ltkd_queue_event(ltk_widget_id windowid, ltkd_userevent_type type, const char *id, const char *data); +int ltkd_handle_lock_client(ltk_widget_id windowid, int client); int ltkd_queue_sock_write(int client, const char *str, int len); int ltkd_queue_sock_write_fmt(int client, const char *fmt, ...); diff --git a/src/ltkd/menu.c b/src/ltkd/menu.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,11 +26,12 @@ #include <ltk/ltk.h> #include <ltk/util.h> #include <ltk/menu.h> +#include <ltk/array.h> /* [sub]menu <menu id> create */ static int ltkd_menu_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -43,14 +44,14 @@ ltkd_menu_cmd_create( }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_menu *menu; + ltk_widget_id menu; if (!strcmp(cmd[0].val.str, "menu")) { - menu = ltk_menu_create(window); + menu = ltk_menu_create(windowid); } else { - menu = ltk_submenu_create(window); + menu = ltk_submenu_create(windowid); } - if (!ltkd_widget_create(LTK_CAST_WIDGET(menu), cmd[1].val.str, NULL, 0, err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(menu), 1); + if (!ltkd_widget_create(menu, cmd[1].val.str, NULL, 0, err)) { + ltk_widget_id_destroy(menu, 1); err->arg = 1; return 1; } @@ -60,21 +61,22 @@ ltkd_menu_cmd_create( /* menu <menu id> insert-entry <entry widget id> <index> */ static int ltkd_menu_cmd_insert_entry( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_menu *menu = LTK_CAST_MENU(widget->widget); + (void)windowid; + ltk_widget *menuw = ltk_get_widget_from_id(widget->ltkid); + ltk_menu *menu = LTK_CAST_MENU(menuw); ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENUENTRY, .optional = 0}, - {.type = CMDARG_INT, .min = 0, .max = menu->num_entries, .optional = 0}, + {.type = CMDARG_INT, .min = 0, .max = ltk_array_len(menu->entries), .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; int ret; - if ((ret = ltk_menu_insert_entry(menu, LTK_CAST_MENUENTRY(cmd[0].val.widget->widget), cmd[1].val.i))) { + if ((ret = ltk_menu_insert_entry(widget->ltkid, cmd[0].val.widget->ltkid, cmd[1].val.i))) { err->type = ret == 1 ? ERR_WIDGET_IN_CONTAINER : ERR_INVALID_INDEX; err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 0 : 1; return 1; @@ -85,19 +87,18 @@ ltkd_menu_cmd_insert_entry( /* menu <menu id> add-entry <entry widget id> */ static int ltkd_menu_cmd_add_entry( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_menu *menu = LTK_CAST_MENU(widget->widget); + (void)windowid; ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENUENTRY, .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - if (ltk_menu_add_entry(menu, LTK_CAST_MENUENTRY(cmd[0].val.widget->widget))) { + if (ltk_menu_add_entry(widget->ltkid, cmd[0].val.widget->ltkid)) { err->type = ERR_WIDGET_IN_CONTAINER; err->arg = 0; return 1; @@ -108,19 +109,21 @@ ltkd_menu_cmd_add_entry( /* menu <menu id> remove-entry-index <entry index> */ static int ltkd_menu_cmd_remove_entry_index( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_menu *menu = LTK_CAST_MENU(widget->widget); + (void)windowid; + ltk_widget *menuw = ltk_get_widget_from_id(widget->ltkid); + ltk_menu *menu = LTK_CAST_MENU(menuw); ltkd_cmdarg_parseinfo cmd[] = { - {.type = CMDARG_INT, .min = 0, .max = menu->num_entries - 1, .optional = 0}, + {.type = CMDARG_INT, .min = 0, .max = ltk_array_len(menu->entries) - 1, .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - if (!ltk_menu_remove_entry_index(menu, cmd[0].val.i)) { + ltk_widget_id ret = ltk_menu_remove_entry_index(widget->ltkid, cmd[0].val.i); + if (LTK_WIDGET_ID_IS_NONE(ret)) { err->type = ERR_WIDGET_NOT_IN_CONTAINER; err->arg = 0; return 1; @@ -131,20 +134,18 @@ ltkd_menu_cmd_remove_entry_index( /* menu <menu id> remove-entry-id <entry id> */ static int ltkd_menu_cmd_remove_entry_id( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_menu *menu = LTK_CAST_MENU(widget->widget); + (void)windowid; ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENUENTRY, .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_menuentry *entry = LTK_CAST_MENUENTRY(cmd[0].val.widget->widget); - if (!ltk_menu_remove_entry(menu, entry)) { + if (!ltk_menu_remove_entry(widget->ltkid, cmd[0].val.widget->ltkid)) { err->type = ERR_WIDGET_NOT_IN_CONTAINER; err->arg = 0; return 1; @@ -155,17 +156,16 @@ ltkd_menu_cmd_remove_entry_id( /* menu <menu id> remove-all-entries */ static int ltkd_menu_cmd_remove_all_entries( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; + (void)windowid; (void)tokens; (void)num_tokens; (void)err; - ltk_menu *menu = LTK_CAST_MENU(widget->widget); - ltk_menu_remove_all_entries(menu); + ltk_menu_remove_all_entries(widget->ltkid); return 0; } @@ -184,7 +184,7 @@ static ltkd_event_handler entry_handlers[] = { /* menuentry <id> create <text> */ static int ltkd_menuentry_cmd_create( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget_unneeded, ltkd_cmd_token *tokens, size_t num_tokens, @@ -198,9 +198,9 @@ ltkd_menuentry_cmd_create( }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; - ltk_menuentry *e = ltk_menuentry_create(window, cmd[3].val.str); - if (!ltkd_widget_create(LTK_CAST_WIDGET(e), cmd[1].val.str, entry_handlers, LENGTH(entry_handlers), err)) { - ltk_widget_destroy(LTK_CAST_WIDGET(e), 1); + ltk_widget_id e = ltk_menuentry_create(windowid, cmd[3].val.str); + if (!ltkd_widget_create(e, cmd[1].val.str, entry_handlers, LENGTH(entry_handlers), err)) { + ltk_widget_id_destroy(e, 1); err->arg = 1; return 1; } @@ -210,20 +210,19 @@ ltkd_menuentry_cmd_create( /* menuentry <menuentry id> attach-submenu <submenu id> */ static int ltkd_menuentry_cmd_attach_submenu( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; - ltk_menuentry *e = LTK_CAST_MENUENTRY(widget->widget); + (void)windowid; ltkd_cmdarg_parseinfo cmd[] = { {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENU, .optional = 0}, }; if (ltkd_parse_cmd(tokens, num_tokens, cmd, LENGTH(cmd), err)) return 1; int ret; - if ((ret = ltk_menuentry_attach_submenu(e, LTK_CAST_MENU(cmd[0].val.widget->widget)))) { + if ((ret = ltk_menuentry_attach_submenu(widget->ltkid, cmd[0].val.widget->ltkid))) { /* FIXME: allow setting err->arg to arg before the args given to function */ /*err->arg = err->type == ERR_MENU_NOT_SUBMENU ? 0 : -2;*/ err->type = ret == 1 ? ERR_MENU_NOT_SUBMENU : ERR_MENU_ENTRY_CONTAINS_SUBMENU; @@ -236,17 +235,16 @@ ltkd_menuentry_cmd_attach_submenu( /* menuentry <menuentry id> detach-submenu */ static int ltkd_menuentry_cmd_detach_submenu( - ltk_window *window, + ltk_widget_id windowid, ltkd_widget *widget, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; + (void)windowid; (void)tokens; (void)num_tokens; (void)err; - ltk_menuentry *e = LTK_CAST_MENUENTRY(widget->widget); - ltk_menuentry_detach_submenu(e); + ltk_menuentry_detach_submenu(widget->ltkid); return 0; } diff --git a/src/ltkd/widget.c b/src/ltkd/widget.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -45,13 +45,14 @@ static int ltkd_widget_change_state(ltk_widget *widget_unused, ltk_callback_argl /* FIXME: generic event handling functions that take the actual uint32_t event type, etc. */ int ltkd_widget_queue_specific_event(ltkd_widget *widget, const char *type, uint32_t mask, const char *data) { + ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid); for (size_t i = 0; i < widget->masks_num; i++) { if (widget->event_masks[i].lwmask & mask) { ltkd_queue_sock_write_fmt( widget->event_masks[i].client, "eventl %s %s %s\n", widget->id, type, data ); - if (ltkd_handle_lock_client(widget->widget->window, widget->event_masks[i].client)) + if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client)) return 1; } else if (widget->event_masks[i].wmask & mask) { ltkd_queue_sock_write_fmt( @@ -68,22 +69,23 @@ ltkd_widget_resize(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_cal (void)widget_unused; (void)args; ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg); + ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid); for (size_t i = 0; i < widget->masks_num; i++) { if (widget->event_masks[i].lmask & LTKD_PEVENTMASK_RESIZE) { ltkd_queue_sock_write_fmt( widget->event_masks[i].client, "eventl %s widget configure %d %d %d %d\n", - widget->id, widget->widget->lrect.x, widget->widget->lrect.y, - widget->widget->lrect.w, widget->widget->lrect.h + widget->id, ltkwid->lrect.x, ltkwid->lrect.y, + ltkwid->lrect.w, ltkwid->lrect.h ); - if (ltkd_handle_lock_client(widget->widget->window, widget->event_masks[i].client)) + if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client)) return 1; } else if (widget->event_masks[i].mask & LTKD_PEVENTMASK_RESIZE) { ltkd_queue_sock_write_fmt( widget->event_masks[i].client, "event %s widget configure %d %d %d %d\n", - widget->id, widget->widget->lrect.x, widget->widget->lrect.y, - widget->widget->lrect.w, widget->widget->lrect.h + widget->id, ltkwid->lrect.x, ltkwid->lrect.y, + ltkwid->lrect.w, ltkwid->lrect.h ); } } @@ -95,6 +97,7 @@ ltkd_widget_change_state(ltk_widget *widget_unused, ltk_callback_arglist args, l (void)widget_unused; (void)args; ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg); + ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid); /* FIXME: give old and new state in event */ for (size_t i = 0; i < widget->masks_num; i++) { if (widget->event_masks[i].lmask & LTKD_PEVENTMASK_STATECHANGE) { @@ -102,7 +105,7 @@ ltkd_widget_change_state(ltk_widget *widget_unused, ltk_callback_arglist args, l widget->event_masks[i].client, "eventl %s widget statechange\n", widget->id ); - if (ltkd_handle_lock_client(widget->widget->window, widget->event_masks[i].client)) + if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client)) return 1; } else if (widget->event_masks[i].mask & LTKD_PEVENTMASK_STATECHANGE) { ltkd_queue_sock_write_fmt( @@ -189,12 +192,12 @@ set_event_handlers(ltkd_widget *widget, uint32_t before, uint32_t after, ltkd_ev if (!(before & 1) && (after & 1)) { if (handlers[i].callback) { ltk_widget_register_signal_handler( - widget->widget, handlers[i].type, + widget->ltkid, handlers[i].type, handlers[i].callback, LTK_MAKE_ARG_VOIDP(widget) ); } } else if ((before & 1) && !(after & 1)) { - ltk_widget_remove_signal_handler_by_callback(widget->widget, handlers[i].callback); + ltk_widget_remove_signal_handler_by_callback(widget->ltkid, handlers[i].callback); } before >>= 1; after >>= 1; @@ -364,6 +367,7 @@ queue_mouse_event(ltkd_widget *widget, ltk_event_type type, int x, int y) { typename = "mousepress"; break; } + ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid); for (size_t i = 0; i < widget->masks_num; i++) { if (widget->event_masks[i].lmask & mask) { ltkd_queue_sock_write_fmt( @@ -372,7 +376,7 @@ queue_mouse_event(ltkd_widget *widget, ltk_event_type type, int x, int y) { widget->id, typename, x, y, x, y /* x - widget->rect.x, y - widget->rect.y */ ); - if (ltkd_handle_lock_client(widget->widget->window, widget->event_masks[i].client)) + if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client)) return 1; } else if (widget->event_masks[i].mask & mask) { ltkd_queue_sock_write_fmt( @@ -408,6 +412,7 @@ ltkd_widget_scroll_event(ltk_widget *widget_unused, ltk_callback_arglist args, l (void)widget_unused; ltk_scroll_event *event = LTK_GET_ARG_SCROLL_EVENT(args, 0); ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg); + ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid); uint32_t mask = LTKD_PEVENTMASK_MOUSESCROLL; for (size_t i = 0; i < widget->masks_num; i++) { if (widget->event_masks[i].lmask & mask) { @@ -417,7 +422,7 @@ ltkd_widget_scroll_event(ltk_widget *widget_unused, ltk_callback_arglist args, l widget->id, "mousescroll", event->x, event->y, event->x, event->y, event->dx, event->dy /* x - widget->widget->rect.x, y - widget->widget->rect.y */ ); - if (ltkd_handle_lock_client(widget->widget->window, widget->event_masks[i].client)) + if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client)) return 1; } else if (widget->event_masks[i].mask & mask) { ltkd_queue_sock_write_fmt( @@ -451,7 +456,8 @@ ltkd_get_widget(const char *id, ltk_widget_type type, ltkd_error *err) { return NULL; } widget = kh_value(widget_hash, k); - if (type != LTK_WIDGET_UNKNOWN && widget->widget->vtable->type != type) { + ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid); + if (type != LTK_WIDGET_UNKNOWN && LTK_WIDGET_TYPE(ltkwid) != type) { err->type = ERR_INVALID_WIDGET_TYPE; return NULL; } @@ -482,14 +488,14 @@ ltkd_remove_widget(const char *id) { ltkd_widget * ltkd_widget_create( - ltk_widget *widget, const char *id, + ltk_widget_id ltkid, const char *id, ltkd_event_handler *event_handlers, size_t num_event_handlers, ltkd_error *err){ if (!ltkd_widget_id_free(id)) { err->type = ERR_WIDGET_ID_IN_USE; return NULL; } ltkd_widget *w = ltk_malloc(sizeof(ltkd_widget)); - w->widget = widget; + w->ltkid = ltkid; w->id = ltk_strdup(id); w->event_masks = NULL; w->masks_num = w->masks_alloc = 0; @@ -506,17 +512,17 @@ ltkd_widget_destroy(ltkd_widget *widget, int shallow) { widget->id = NULL; ltk_free(widget->event_masks); widget->event_masks = NULL; - ltk_widget_destroy(widget->widget, shallow); + ltk_widget_id_destroy(widget->ltkid, shallow); ltk_free(widget); } int ltkd_widget_destroy_cmd( - ltk_window *window, + ltk_widget_id windowid, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) { - (void)window; + (void)windowid; int shallow = 1; if (num_tokens != 2 && num_tokens != 3) { err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS; diff --git a/src/ltkd/widget.h b/src/ltkd/widget.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -39,7 +39,7 @@ typedef struct { } ltkd_event_handler; typedef struct { - ltk_widget *widget; + ltk_widget_id ltkid; char *id; client_event_mask *event_masks; /* FIXME: kind of a waste of space to use size_t here */ @@ -54,7 +54,7 @@ typedef struct { void ltkd_widgets_init(); ltkd_widget *ltkd_widget_create( - ltk_widget *widget, const char *id, + ltk_widget_id widgetid, const char *id, ltkd_event_handler *event_handlers, size_t num_event_handlers, ltkd_error *err ); ltkd_widget *ltkd_get_widget(const char *id, ltk_widget_type type, ltkd_error *err); @@ -66,7 +66,7 @@ void ltkd_set_widget(ltkd_widget *widget, const char *id); void ltkd_remove_widget(const char *id);*/ void ltkd_widget_destroy(ltkd_widget *widget, int shallow); -int ltkd_widget_destroy_cmd(ltk_window *window, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err); +int ltkd_widget_destroy_cmd(ltk_widget_id windowid, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err); void ltkd_widget_remove_client(int client); void ltkd_widgets_cleanup(void);