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/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 (10829B)
---
1 /*
2 * Copyright (c) 2021-2024 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
28 LTK_ARRAY_INIT_FUNC_DECL_STATIC(signal, ltk_signal_callback_info)
29 LTK_ARRAY_INIT_IMPL_STATIC(signal, ltk_signal_callback_info)
30
31 /* FIXME: this should probably not take w and h */
32 void
33 ltk_fill_widget_defaults(
34 ltk_widget *widget, ltk_window *window,
35 struct ltk_widget_vtable *vtable, int w, int h
36 ) {
37 widget->window = window;
38 widget->parent = NULL;
39
40 /* FIXME: possibly check that draw and destroy aren't NULL */
41 widget->vtable = vtable;
42
43 widget->state = LTK_NORMAL;
44 widget->row = 0;
45 widget->lrect.x = 0;
46 widget->lrect.y = 0;
47 widget->lrect.w = w;
48 widget->lrect.h = h;
49 widget->crect.x = 0;
50 widget->crect.y = 0;
51 widget->crect.w = w;
52 widget->crect.h = h;
53 widget->popup = 0;
54
55 widget->ideal_w = widget->ideal_h = 0;
56
57 widget->row = 0;
58 widget->column = 0;
59 widget->row_span = 0;
60 widget->column_span = 0;
61 widget->sticky = 0;
62 widget->dirty = 1;
63 widget->hidden = 0;
64 widget->vtable_copied = 0;
65 widget->signal_cbs = NULL;
66 /* FIXME: maybe set this to a dummy value here and don't initialize
67 ideal_w/h at all until it is actually needed? */
68 widget->last_dpi = ltk_renderer_get_window_dpi(window->renderwindow);
69 /* FIXME: null other members! */
70 }
71
72 void
73 ltk_widget_hide(ltk_widget *widget) {
74 /* FIXME: it may not make sense to call this here */
75 if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_HIDE, LTK_EMPTY_ARGLIST))
76 return;
77 if (widget->vtable->hide)
78 widget->vtable->hide(widget);
79 widget->hidden = 1;
80 /* remove hover state */
81 /* FIXME: this needs to call change_state but that might cause issues */
82 ltk_widget *hover = widget->window->hover_widget;
83 while (hover) {
84 if (hover == widget) {
85 widget->window->hover_widget->state &= ~LTK_HOVER;
86 widget->window->hover_widget = NULL;
87 break;
88 }
89 hover = hover->parent;
90 }
91 ltk_widget *pressed = widget->window->pressed_widget;
92 while (pressed) {
93 if (pressed == widget) {
94 widget->window->pressed_widget->state &= ~LTK_PRESSED;
95 widget->window->pressed_widget = NULL;
96 break;
97 }
98 pressed = pressed->parent;
99 }
100 ltk_widget *active = widget->window->active_widget;
101 /* if current active widget is child, set active widget to widget above in hierarchy */
102 int set_next = 0;
103 while (active) {
104 if (active == widget) {
105 set_next = 1;
106 /* FIXME: use config values for all_activatable */
107 } else if (set_next && (active->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
108 ltk_window_set_active_widget(active->window, active);
109 break;
110 }
111 active = active->parent;
112 }
113 if (set_next && !active)
114 ltk_window_set_active_widget(active->window, NULL);
115 }
116
117 /* FIXME: Maybe pass the new width as arg here?
118 That would make a bit more sense */
119 /* FIXME: maybe give global and local position in event */
120 void
121 ltk_widget_resize(ltk_widget *widget) {
122 if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_RESIZE, LTK_EMPTY_ARGLIST))
123 return;
124 if (widget->vtable->resize)
125 widget->vtable->resize(widget);
126 widget->dirty = 1;
127 }
128
129 void
130 ltk_widget_draw(ltk_widget *widget, ltk_surface *draw_surf, int x, int y, ltk_rect clip_rect) {
131 ltk_callback_arg args[] = {
132 LTK_MAKE_ARG_SURFACE(draw_surf),
133 LTK_MAKE_ARG_INT(x),
134 LTK_MAKE_ARG_INT(y),
135 LTK_MAKE_ARG_RECT(clip_rect)
136 };
137 if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_DRAW, (ltk_callback_arglist){args, LENGTH(args)}))
138 return;
139 if (widget->vtable->draw)
140 widget->vtable->draw(widget, draw_surf, x, y, clip_rect);
141 }
142
143 void
144 ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) {
145 if (old_state == widget->state)
146 return;
147 ltk_callback_arg args[] = {LTK_MAKE_ARG_INT(old_state)};
148 if (ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_CHANGE_STATE, (ltk_callback_arglist){args, LENGTH(args)}))
149 return;
150 if (widget->vtable->change_state)
151 widget->vtable->change_state(widget, old_state);
152 if (widget->vtable->flags & LTK_NEEDS_REDRAW) {
153 widget->dirty = 1;
154 ltk_window_invalidate_widget_rect(widget->window, widget);
155 }
156 }
157
158 /* FIXME: document that it's really dangerous to overwrite remove_child or destroy */
159 int
160 ltk_widget_destroy(ltk_widget *widget, int shallow) {
161 ltk_widget_emit_signal(widget, LTK_WIDGET_SIGNAL_DESTROY, LTK_EMPTY_ARGLIST);
162 /* widget->parent->remove_child should never be NULL because of the fact that
163 the widget is set as parent, but let's just check anyways... */
164 int invalid = 0;
165 if (widget->parent) {
166 if (widget->parent->vtable->remove_child)
167 invalid = widget->parent->vtable->remove_child(widget->parent, widget);
168 }
169 if (widget->vtable_copied) {
170 ltk_free(widget->vtable);
171 widget->vtable = NULL;
172 }
173 if (widget->signal_cbs) {
174 ltk_array_destroy(signal, widget->signal_cbs);
175 widget->signal_cbs = NULL;
176 }
177 widget->vtable->destroy(widget, shallow);
178
179 return invalid;
180 }
181
182 ltk_point
183 ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) {
184 ltk_widget *cur = widget;
185 while (cur) {
186 x += cur->lrect.x;
187 y += cur->lrect.y;
188 if (cur->popup)
189 break;
190 cur = cur->parent;
191 }
192 return (ltk_point){x, y};
193 }
194
195 ltk_point
196 ltk_global_to_widget_pos(ltk_widget *widget, int x, int y) {
197 ltk_widget *cur = widget;
198 while (cur) {
199 x -= cur->lrect.x;
200 y -= cur->lrect.y;
201 if (cur->popup)
202 break;
203 cur = cur->parent;
204 }
205 return (ltk_point){x, y};
206 }
207
208 int
209 ltk_widget_register_signal_handler(ltk_widget *widget, int type, ltk_signal_callback callback, ltk_callback_arg data) {
210 if ((type >= LTK_WIDGET_SIGNAL_INVALID) || type <= widget->vtable->invalid_signal)
211 return 1;
212 if (!widget->signal_cbs) {
213 widget->signal_cbs = ltk_array_create(signal, 1);
214 }
215 ltk_array_append_signal(widget->signal_cbs, (ltk_signal_callback_info){callback, data, type});
216 return 0;
217 }
218
219 int
220 ltk_widget_emit_signal(ltk_widget *widget, int type, ltk_callback_arglist args) {
221 if (!widget->signal_cbs)
222 return 0;
223 int handled = 0;
224 for (size_t i = 0; i < ltk_array_len(widget->signal_cbs); i++) {
225 if (ltk_array_get(widget->signal_cbs, i).type == type) {
226 handled |= ltk_array_get(widget->signal_cbs, i).callback(widget, args, ltk_array_get(widget->signal_cbs, i).data);
227 }
228 }
229 return handled;
230 }
231
232 static int
233 filter_by_type(ltk_signal_callback_info *info, void *data) {
234 return info->type == *(int *)data;
235 }
236
237 size_t
238 ltk_widget_remove_signal_handler_by_type(ltk_widget *widget, int type) {
239 if (!widget->signal_cbs)
240 return 0;
241 return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_type, &type);
242 }
243
244 struct func_wrapper {
245 ltk_signal_callback callback;
246 };
247
248 static int
249 filter_by_callback(ltk_signal_callback_info *info, void *data) {
250 return info->callback == ((struct func_wrapper *)data)->callback;
251 }
252
253 size_t
254 ltk_widget_remove_signal_handler_by_callback(ltk_widget *widget, ltk_signal_callback callback) {
255 if (!widget->signal_cbs)
256 return 0;
257 /* callback can't be passed directly because ISO C forbids
258 conversion of object pointer to function pointer */
259 struct func_wrapper data = {callback};
260 return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_callback, &data);
261 }
262
263 struct delete_wrapper {
264 int (*filter_func)(ltk_signal_callback_info *, ltk_signal_callback_info *);
265 ltk_signal_callback_info *info;
266 };
267
268 static int
269 filter_by_info(ltk_signal_callback_info *info, void *data) {
270 struct delete_wrapper *w = data;
271 return w->filter_func(info, w->info);
272 }
273
274 size_t
275 ltk_widget_remove_signal_handler_by_info(
276 ltk_widget *widget,
277 int (*filter_func)(ltk_signal_callback_info *to_check, ltk_signal_callback_info *info),
278 ltk_signal_callback_info *info) {
279
280 if (!widget->signal_cbs)
281 return 0;
282 struct delete_wrapper data = {filter_func, info};
283 return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_info, &data);
284 }
285
286 void
287 ltk_widget_remove_all_signal_handlers(ltk_widget *widget) {
288 if (!widget->signal_cbs)
289 return;
290 ltk_array_destroy(signal, widget->signal_cbs);
291 widget->signal_cbs = NULL;
292 }
293
294 int ltk_widget_register_type(void); /* FIXME */
295
296 ltk_widget_vtable *
297 ltk_widget_get_editable_vtable(ltk_widget *widget) {
298 if (!widget->vtable_copied) {
299 ltk_widget_vtable *vtable = ltk_malloc(sizeof(ltk_widget_vtable));
300 memcpy(vtable, widget->vtable, sizeof(ltk_widget_vtable));
301 widget->vtable_copied = 1;
302 }
303 return widget->vtable;
304 }
305
306 void
307 ltk_widget_recalc_ideal_size(ltk_widget *widget) {
308 unsigned int dpi = ltk_renderer_get_window_dpi(widget->window->renderwindow);
309 if (dpi == widget->last_dpi)
310 return;
311 widget->last_dpi = dpi;
312 if (widget->vtable->recalc_ideal_size)
313 widget->vtable->recalc_ideal_size(widget);
314 }
315
316 int
317 ltk_widget_handle_keypress_bindings(ltk_widget *widget, ltk_key_event *event, ltk_array(keypress) *keypresses, int handled) {
318 if (!keypresses)
319 return 0;
320 ltk_keypress_binding *b;
321 for (size_t i = 0; i < ltk_array_len(keypresses); i++) {
322 b = <k_array_get(keypresses, i).b;
323 if ((!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled))
324 continue;
325 /* FIXME: change naming (rawtext, text, mapped...) */
326 /* FIXME: a bit weird to mask out shift, but if that isn't done, it
327 would need to be included for all mappings with capital letters */
328 if ((b->mods == event->modmask && b->sym != LTK_KEY_NONE && b->sym == event->sym) ||
329 (b->mods == (event->modmask & ~LTK_MOD_SHIFT) &&
330 ((b->text && event->mapped && !strcmp(b->text, event->mapped)) ||
331 (b->rawtext && event->text && !strcmp(b->rawtext, event->text))))) {
332 handled |= ltk_array_get(keypresses, i).cb.func(widget, event);
333 }
334 }
335 return handled;
336 }
337
338 int
339 ltk_widget_handle_keyrelease_bindings(ltk_widget *widget, ltk_key_event *event, ltk_array(keyrelease) *keyreleases, int handled) {
340 if (!keyreleases)
341 return 0;
342 ltk_keyrelease_binding *b = NULL;
343 for (size_t i = 0; i < ltk_array_len(keyreleases); i++) {
344 b = <k_array_get(keyreleases, i).b;
345 if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
346 continue;
347 } else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) {
348 handled |= ltk_array_get(keyreleases, i).cb.func(widget, event);
349 }
350 }
351 return handled;
352 }