URI:
       Store widgets in central array and use IDs in most public functions - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 46ddc040dfd9d76a78a5f7b4d1d080e141c7dd2f
   DIR parent 9b2b4596605e6594a2538f6b3bce3ffd9e90c652
  HTML 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:
         M LICENSE                             |       2 +-
         M examples/ltk/test.c                 |      94 ++++++++++++++++----------------
         M src/ltk/array.h                     |      82 ++++++++++++++++++++++---------
         M src/ltk/box.c                       |     385 ++++++++++++++++---------------
         M src/ltk/box.h                       |      22 +++++++++++-----------
         M src/ltk/button.c                    |      29 +++++++++++++----------------
         M src/ltk/button.h                    |       4 ++--
         M src/ltk/checkbutton.c               |      40 ++++++++++++++++----------------
         M src/ltk/checkbutton.h               |       8 ++++----
         M src/ltk/combobox.c                  |     236 +++++++++++++++++--------------
         M src/ltk/combobox.h                  |      19 ++++++++++---------
         M src/ltk/entry.c                     |      78 ++++++++++++++++----------------
         M src/ltk/entry.h                     |       6 ++----
         M src/ltk/grid.c                      |     253 ++++++++++++++++---------------
         M src/ltk/grid.h                      |      18 ++++++++----------
         M src/ltk/image_widget.c              |      19 +++++++------------
         M src/ltk/image_widget.h              |       4 ++--
         M src/ltk/label.c                     |      14 +++++---------
         M src/ltk/label.h                     |       4 ++--
         M src/ltk/ltk.c                       |     236 +++++++++++++++++++++----------
         M src/ltk/ltk.h                       |      24 +++++++++++++++++++++---
         M src/ltk/menu.c                      |     747 ++++++++++++++++---------------
         M src/ltk/menu.h                      |      43 ++++++++++++++-----------------
         M src/ltk/radiobutton.c               |      75 ++++++++++++++++++-------------
         M src/ltk/radiobutton.h               |      14 +++++++-------
         M src/ltk/scrollbar.c                 |      46 +++++++++++++++++--------------
         M src/ltk/scrollbar.h                 |       8 ++++----
         M src/ltk/widget.c                    |     137 +++++++++++++++++++++----------
         M src/ltk/widget.h                    |     151 +++++++++++++++++++------------
         M src/ltk/widget_internal.h           |      10 +++++-----
         M src/ltk/window.c                    |     511 +++++++++++++++++--------------
         M src/ltk/window.h                    |      42 ++++++++++++++++---------------
         M src/ltkd/box.c                      |      24 +++++++++++-------------
         M src/ltkd/button.c                   |      10 +++++-----
         M src/ltkd/cmd.h                      |      12 ++++++------
         M src/ltkd/entry.c                    |      10 +++++-----
         M src/ltkd/grid.c                     |      44 ++++++++++++++++---------------
         M src/ltkd/image_widget.c             |      10 +++++-----
         M src/ltkd/label.c                    |      10 +++++-----
         M src/ltkd/ltkd.c                     |      57 +++++++++++++++++--------------
         M src/ltkd/ltkd.h                     |       6 +++---
         M src/ltkd/menu.c                     |      82 +++++++++++++++----------------
         M src/ltkd/widget.c                   |      42 ++++++++++++++++++-------------
         M src/ltkd/widget.h                   |       8 ++++----
       
       44 files changed, 2015 insertions(+), 1661 deletions(-)
       ---
   DIR 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
   DIR 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;
   DIR 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)
   DIR 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;
   DIR 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 */
   DIR 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);
        }
   DIR 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 */
   DIR 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);
        }
   DIR 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 */
   DIR 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);
        }
   DIR 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 */
   DIR 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);
   DIR 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 */
   DIR 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;
        }
   DIR 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 */
   DIR 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);
        }
   DIR 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 */
   DIR 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);
        }
   DIR 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 */
   DIR 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);
                        }
                }
        }
   DIR 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);
   DIR 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);
        }
   DIR 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 */
   DIR 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);
        }
   DIR 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 */
   DIR 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
   DIR 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 */
   DIR 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;
   DIR 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);
        
   DIR 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);
   DIR 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;
   DIR 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 */
   DIR 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;
                }
   DIR 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;
                }
   DIR 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;                                                                                        \
   DIR 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;
                }
   DIR 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;
        }
   DIR 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;
                }
   DIR 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;
                }
   DIR 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;
                                                }
                                        }
   DIR 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, ...);
        
   DIR 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;
        }
        
   DIR 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;
   DIR 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);