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 }