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