widget.c - ltk - GUI toolkit for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
widget.c (13156B)
---
1 /*
2 * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org>
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stddef.h>
18 #include <string.h>
19
20 #include "rect.h"
21 #include "config.h"
22 #include "widget.h"
23 #include "window.h"
24 #include "memory.h"
25 #include "array.h"
26 #include "eventdefs.h"
27 #include "ltk.h"
28
29 LTK_ARRAY_INIT_FUNC_DECL_STATIC(signal, ltk_signal_callback_info)
30 LTK_ARRAY_INIT_IMPL_STATIC(signal, ltk_signal_callback_info)
31 LTK_ARRAY_INIT_IMPL(widget_id, ltk_widget_id)
32
33 /* FIXME: this should probably not take w and h */
34 /* FIXME: how much sense does it make to give a window id here?
35 maybe instead set that automatically when adding widget to container?
36 currently, it's weird anyways because each widget has a window set,
37 but when adding a widget to a container, nothing checks if the windows
38 match */
39 ltk_widget_id
40 ltk_initialize_widget(
41 ltk_widget *widget, ltk_widget_id windowid,
42 struct ltk_widget_vtable *vtable, int w, int h
43 ) {
44 widget->parent = LTK_WIDGET_ID_NONE;
45
46 /* FIXME: possibly check that draw and destroy aren't NULL */
47 widget->vtable = vtable;
48
49 widget->state = LTK_NORMAL;
50 widget->row = 0;
51 widget->lrect.x = 0;
52 widget->lrect.y = 0;
53 widget->lrect.w = w;
54 widget->lrect.h = h;
55 widget->crect.x = 0;
56 widget->crect.y = 0;
57 widget->crect.w = w;
58 widget->crect.h = h;
59 widget->popup = 0;
60
61 widget->ideal_w = widget->ideal_h = 0;
62
63 widget->row = 0;
64 widget->column = 0;
65 widget->row_span = 0;
66 widget->column_span = 0;
67 widget->sticky = 0;
68 widget->dirty = 1;
69 widget->hidden = 0;
70 widget->vtable_copied = 0;
71 widget->signal_cbs = NULL;
72
73 widget->id = ltk_store_widget(widget);
74 widget->window = windowid;
75 if (LTK_WIDGET_ID_IS_NONE(windowid)) {
76 widget->window = widget->id; /* a bit hacky */
77 } else {
78 ltk_widget *windoww = ltk_get_widget_from_id(windowid);
79 ltk_window *window = LTK_CAST_WINDOW(windoww);
80 /* FIXME: maybe set this to a dummy value here and don't initialize
81 ideal_w/h at all until it is actually needed? */
82 widget->last_dpi = ltk_renderer_get_window_dpi(window->renderwindow);
83 }
84 /* FIXME: null other members! */
85 return widget->id;
86 }
87
88 void
89 ltk_widget_hide(ltk_widget *widget) {
90 /* FIXME: it may not make sense to call this here */
91 if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_HIDE, LTK_EMPTY_ARGLIST))
92 return;
93 if (widget->vtable->hide)
94 widget->vtable->hide(widget);
95 widget->hidden = 1;
96 /* remove hover state */
97 /* FIXME: this needs to call change_state but that might cause issues */
98 ltk_widget *windoww = ltk_get_widget_from_id(widget->window);
99 ltk_window *window = LTK_CAST_WINDOW(windoww);
100 ltk_widget *hover_orig = ltk_get_widget_or_null_from_id(window->hover_widget);
101 ltk_widget *hover = hover_orig;
102 while (hover) {
103 if (hover == widget) {
104 hover_orig->state &= ~LTK_HOVER;
105 window->hover_widget = LTK_WIDGET_ID_NONE;
106 break;
107 }
108 hover = ltk_get_widget_or_null_from_id(hover->parent);
109 }
110 ltk_widget *pressed_orig = ltk_get_widget_or_null_from_id(window->pressed_widget);
111 ltk_widget *pressed = pressed_orig;
112 while (pressed) {
113 if (pressed == widget) {
114 pressed_orig->state &= ~LTK_PRESSED;
115 window->pressed_widget = LTK_WIDGET_ID_NONE;
116 break;
117 }
118 pressed = ltk_get_widget_or_null_from_id(pressed->parent);
119 }
120 ltk_widget *active = ltk_get_widget_or_null_from_id(window->active_widget);
121 /* if current active widget is child, set active widget to widget above in hierarchy */
122 int set_next = 0;
123 while (active) {
124 if (active == widget) {
125 set_next = 1;
126 /* FIXME: use config values for all_activatable */
127 } else if (set_next && (active->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
128 ltk_window_set_active_widget(active->window, active->id);
129 break;
130 }
131 active = ltk_get_widget_or_null_from_id(active->parent);
132 }
133 if (set_next && !active)
134 ltk_window_set_active_widget(active->window, LTK_WIDGET_ID_NONE);
135 }
136
137 void
138 ltk_widget_id_hide(ltk_widget_id widgetid) {
139 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
140 ltk_widget_hide(widget);
141 }
142
143 /* FIXME: Maybe pass the new width as arg here?
144 That would make a bit more sense */
145 /* FIXME: maybe give global and local position in event */
146 void
147 ltk_widget_resize(ltk_widget *widget) {
148 if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_RESIZE, LTK_EMPTY_ARGLIST))
149 return;
150 if (widget->vtable->resize)
151 widget->vtable->resize(widget);
152 widget->dirty = 1;
153 }
154
155 void
156 ltk_widget_draw(ltk_widget *widget, ltk_surface *draw_surf, int x, int y, ltk_rect clip_rect) {
157 ltk_callback_arg args[] = {
158 LTK_MAKE_ARG_SURFACE(draw_surf),
159 LTK_MAKE_ARG_INT(x),
160 LTK_MAKE_ARG_INT(y),
161 LTK_MAKE_ARG_RECT(clip_rect)
162 };
163 if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_DRAW, (ltk_callback_arglist){args, LENGTH(args)}))
164 return;
165 if (widget->vtable->draw)
166 widget->vtable->draw(widget, draw_surf, x, y, clip_rect);
167 }
168
169 void
170 ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) {
171 if (old_state == widget->state)
172 return;
173 ltk_callback_arg args[] = {LTK_MAKE_ARG_INT(old_state)};
174 if (ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_CHANGE_STATE, (ltk_callback_arglist){args, LENGTH(args)}))
175 return;
176 if (widget->vtable->change_state)
177 widget->vtable->change_state(widget, old_state);
178 if (widget->vtable->flags & LTK_NEEDS_REDRAW) {
179 widget->dirty = 1;
180 ltk_window_invalidate_widget_rect(widget->window, widget->id);
181 }
182 }
183
184 /* FIXME: document that it's really dangerous to overwrite remove_child or destroy */
185 int
186 ltk_widget_destroy(ltk_widget *widget, int shallow) {
187 ltk_widget_id id = widget->id;
188 ltk_widget_emit_signal(widget->id, LTK_WIDGET_SIGNAL_DESTROY, LTK_EMPTY_ARGLIST);
189 /* widget->parent->remove_child should never be NULL because of the fact that
190 the widget is set as parent, but let's just check anyways... */
191 int invalid = 0;
192 ltk_widget *parent = ltk_get_widget_or_null_from_id(widget->parent);
193 if (parent) {
194 if (parent->vtable->remove_child)
195 invalid = parent->vtable->remove_child(parent, widget->id);
196 }
197 if (widget->signal_cbs) {
198 ltk_array_destroy(signal, widget->signal_cbs);
199 widget->signal_cbs = NULL;
200 }
201 ltk_widget_vtable *vtable = widget->vtable;
202 int vtable_copied = widget->vtable_copied;
203 vtable->destroy(widget, shallow);
204 if (vtable_copied)
205 ltk_free(vtable);
206
207 ltk_clear_widget_slot(id);
208 return invalid;
209 }
210
211 int
212 ltk_widget_id_destroy(ltk_widget_id widgetid, int shallow) {
213 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
214 return ltk_widget_destroy(widget, shallow);
215 }
216
217 ltk_point
218 ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) {
219 ltk_widget *cur = widget;
220 while (cur) {
221 x += cur->lrect.x;
222 y += cur->lrect.y;
223 if (cur->popup)
224 break;
225 cur = ltk_get_widget_or_null_from_id(cur->parent);
226 }
227 return (ltk_point){x, y};
228 }
229
230 ltk_point
231 ltk_global_to_widget_pos(ltk_widget *widget, int x, int y) {
232 ltk_widget *cur = widget;
233 while (cur) {
234 x -= cur->lrect.x;
235 y -= cur->lrect.y;
236 if (cur->popup)
237 break;
238 cur = ltk_get_widget_or_null_from_id(cur->parent);
239 }
240 return (ltk_point){x, y};
241 }
242
243 int
244 ltk_widget_register_signal_handler(ltk_widget_id widgetid, int type, ltk_signal_callback callback, ltk_callback_arg data) {
245 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
246 if ((type >= LTK_WIDGET_SIGNAL_INVALID) || type <= widget->vtable->invalid_signal)
247 return 1;
248 if (!widget->signal_cbs) {
249 widget->signal_cbs = ltk_array_create(signal, 1);
250 }
251 ltk_array_append_signal(widget->signal_cbs, (ltk_signal_callback_info){callback, data, type});
252 return 0;
253 }
254
255 /* FIXME: this takes widget_id, but calls functions that take ltk_widget*
256 Should this be changed to also take ltk_widget*?
257 Changing the callbacks to take widget_id doesn't really make sense because they
258 are only called from here anyways, where ltk_widget* already is required anyways */
259 int
260 ltk_widget_emit_signal(ltk_widget_id widgetid, int type, ltk_callback_arglist args) {
261 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
262 if (!widget->signal_cbs)
263 return 0;
264 int handled = 0;
265 for (size_t i = 0; i < ltk_array_len(widget->signal_cbs); i++) {
266 if (ltk_array_get(widget->signal_cbs, i).type == type) {
267 handled |= ltk_array_get(widget->signal_cbs, i).callback(widget, args, ltk_array_get(widget->signal_cbs, i).data);
268 }
269 }
270 return handled;
271 }
272
273 static int
274 filter_by_type(ltk_signal_callback_info *info, void *data) {
275 return info->type == *(int *)data;
276 }
277
278 size_t
279 ltk_widget_remove_signal_handler_by_type(ltk_widget_id widgetid, int type) {
280 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
281 if (!widget->signal_cbs)
282 return 0;
283 return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_type, &type);
284 }
285
286 struct func_wrapper {
287 ltk_signal_callback callback;
288 };
289
290 static int
291 filter_by_callback(ltk_signal_callback_info *info, void *data) {
292 return info->callback == ((struct func_wrapper *)data)->callback;
293 }
294
295 size_t
296 ltk_widget_remove_signal_handler_by_callback(ltk_widget_id widgetid, ltk_signal_callback callback) {
297 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
298 if (!widget->signal_cbs)
299 return 0;
300 /* callback can't be passed directly because ISO C forbids
301 conversion of object pointer to function pointer */
302 struct func_wrapper data = {callback};
303 return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_callback, &data);
304 }
305
306 struct delete_wrapper {
307 int (*filter_func)(ltk_signal_callback_info *, ltk_signal_callback_info *);
308 ltk_signal_callback_info *info;
309 };
310
311 static int
312 filter_by_info(ltk_signal_callback_info *info, void *data) {
313 struct delete_wrapper *w = data;
314 return w->filter_func(info, w->info);
315 }
316
317 size_t
318 ltk_widget_remove_signal_handler_by_info(
319 ltk_widget_id widgetid,
320 int (*filter_func)(ltk_signal_callback_info *to_check, ltk_signal_callback_info *info),
321 ltk_signal_callback_info *info) {
322
323 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
324 if (!widget->signal_cbs)
325 return 0;
326 struct delete_wrapper data = {filter_func, info};
327 return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_info, &data);
328 }
329
330 void
331 ltk_widget_remove_all_signal_handlers(ltk_widget_id widgetid) {
332 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
333 if (!widget->signal_cbs)
334 return;
335 ltk_array_destroy(signal, widget->signal_cbs);
336 widget->signal_cbs = NULL;
337 }
338
339 int ltk_widget_register_type(void); /* FIXME */
340
341 ltk_widget_vtable *
342 ltk_widget_get_editable_vtable(ltk_widget_id widgetid) {
343 ltk_widget *widget = ltk_get_widget_from_id(widgetid);
344 if (!widget->vtable_copied) {
345 ltk_widget_vtable *vtable = ltk_malloc(sizeof(ltk_widget_vtable));
346 memcpy(vtable, widget->vtable, sizeof(ltk_widget_vtable));
347 widget->vtable_copied = 1;
348 }
349 return widget->vtable;
350 }
351
352 void
353 ltk_widget_recalc_ideal_size(ltk_widget *widget) {
354 ltk_widget *windoww = ltk_get_widget_from_id(widget->window);
355 unsigned int dpi = ltk_renderer_get_window_dpi(LTK_CAST_WINDOW(windoww)->renderwindow);
356 if (dpi == widget->last_dpi)
357 return;
358 widget->last_dpi = dpi;
359 if (widget->vtable->recalc_ideal_size)
360 widget->vtable->recalc_ideal_size(widget);
361 }
362
363 int
364 ltk_widget_handle_keypress_bindings(ltk_widget *widget, ltk_key_event *event, ltk_array(keypress) *keypresses, int handled) {
365 if (!keypresses)
366 return 0;
367 ltk_keypress_binding *b;
368 for (size_t i = 0; i < ltk_array_len(keypresses); i++) {
369 b = <k_array_get(keypresses, i).b;
370 if ((!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled))
371 continue;
372 /* FIXME: change naming (rawtext, text, mapped...) */
373 /* FIXME: a bit weird to mask out shift, but if that isn't done, it
374 would need to be included for all mappings with capital letters */
375 if ((b->mods == event->modmask && b->sym != LTK_KEY_NONE && b->sym == event->sym) ||
376 (b->mods == (event->modmask & ~LTK_MOD_SHIFT) &&
377 ((b->text && event->mapped && !strcmp(b->text, event->mapped)) ||
378 (b->rawtext && event->text && !strcmp(b->rawtext, event->text))))) {
379 handled |= ltk_array_get(keypresses, i).cb.func(widget, event);
380 }
381 }
382 return handled;
383 }
384
385 int
386 ltk_widget_handle_keyrelease_bindings(ltk_widget *widget, ltk_key_event *event, ltk_array(keyrelease) *keyreleases, int handled) {
387 if (!keyreleases)
388 return 0;
389 ltk_keyrelease_binding *b = NULL;
390 for (size_t i = 0; i < ltk_array_len(keyreleases); i++) {
391 b = <k_array_get(keyreleases, i).b;
392 if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
393 continue;
394 } else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) {
395 handled |= ltk_array_get(keyreleases, i).cb.func(widget, event);
396 }
397 }
398 return handled;
399 }