ltk

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

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 = &ltk_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 = &ltk_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 }