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