widget.h (17298B)
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 /* need to check what happens when registering signal for destroy, but then calling ltk_destroy_widget from 18 that handler - maybe loop if container widget deletes children but they call parent->delete_child again */ 19 #ifndef LTK_WIDGET_H 20 #define LTK_WIDGET_H 21 22 /* FIXME: destroy signal for widgets (also window) */ 23 24 #include <stddef.h> 25 #include "array.h" 26 #include "event.h" 27 #include "graphics.h" 28 #include "rect.h" 29 #include "util.h" 30 31 struct ltk_widget; 32 struct ltk_window; 33 typedef struct ltk_widget ltk_widget; 34 35 typedef enum { 36 LTK_WIDGET_UNKNOWN = 0, 37 LTK_WIDGET_ANY, 38 LTK_WIDGET_GRID, 39 LTK_WIDGET_BUTTON, 40 LTK_WIDGET_LABEL, 41 LTK_WIDGET_BOX, 42 LTK_WIDGET_MENU, 43 LTK_WIDGET_MENUENTRY, 44 LTK_WIDGET_ENTRY, 45 LTK_WIDGET_IMAGE, 46 LTK_WIDGET_WINDOW, 47 LTK_WIDGET_SCROLLBAR, 48 LTK_WIDGET_CHECKBUTTON, 49 LTK_WIDGET_RADIOBUTTON, 50 LTK_WIDGET_COMBOBOX, 51 LTK_NUM_WIDGETS, 52 } ltk_widget_type; 53 54 typedef enum { 55 LTK_ACTIVATABLE_NORMAL = 1, 56 /* only activatable when "all-activatable" 57 is set to true in the config */ 58 LTK_ACTIVATABLE_SPECIAL = 2, 59 LTK_ACTIVATABLE_ALWAYS = 1|2, 60 /* FIXME: redundant or needs better name - is implied by entries in vtable 61 - if there are widgets that have keyboard functions in the vtable but 62 shouldn't have this set, then it's a bad name */ 63 LTK_NEEDS_KEYBOARD = 4, 64 LTK_NEEDS_REDRAW = 8, 65 LTK_HOVER_IS_ACTIVE = 16, 66 } ltk_widget_flags; 67 68 /* FIXME: "sticky" is maybe not the correct name anymore */ 69 typedef enum { 70 LTK_STICKY_NONE = 0, 71 LTK_STICKY_LEFT = 1 << 0, 72 LTK_STICKY_RIGHT = 1 << 1, 73 LTK_STICKY_TOP = 1 << 2, 74 LTK_STICKY_BOTTOM = 1 << 3, 75 LTK_STICKY_SHRINK_WIDTH = 1 << 4, 76 LTK_STICKY_SHRINK_HEIGHT = 1 << 5, 77 LTK_STICKY_PRESERVE_ASPECT_RATIO = 1 << 6, 78 } ltk_sticky_mask; 79 80 typedef enum { 81 LTK_VERTICAL, 82 LTK_HORIZONTAL 83 } ltk_orientation; 84 85 typedef enum { 86 LTK_NORMAL = 0, 87 LTK_HOVER = 1, 88 LTK_PRESSED = 2, 89 LTK_ACTIVE = 4, 90 LTK_HOVERACTIVE = 1 | 4, 91 LTK_FOCUSED = 8, 92 LTK_DISABLED = 16, 93 } ltk_widget_state; 94 95 /* FIXME: need "ltk_register_type" just to get unique integer for type checking */ 96 97 typedef struct { 98 union { 99 int i; 100 size_t sz; 101 char c; 102 ltk_widget *widget; 103 char *str; 104 const char *cstr; 105 ltk_key_event *key_event; 106 ltk_button_event *button_event; 107 ltk_scroll_event *scroll_event; 108 ltk_motion_event *motion_event; 109 ltk_surface *surface; 110 void *v; 111 /* FIXME: maybe rewrite the functions to take 112 pointers instead so this doesn't increase 113 the size of the union (thereby increasing 114 the size of every arg in the arglist) */ 115 ltk_rect rect; 116 } arg; 117 enum { 118 LTK_TYPE_INT, 119 LTK_TYPE_SIZE_T, 120 LTK_TYPE_CHAR, 121 LTK_TYPE_WIDGET, 122 LTK_TYPE_STRING, 123 LTK_TYPE_CONST_STRING, 124 LTK_TYPE_KEY_EVENT, 125 LTK_TYPE_BUTTON_EVENT, 126 LTK_TYPE_SCROLL_EVENT, 127 LTK_TYPE_MOTION_EVENT, 128 LTK_TYPE_SURFACE, 129 LTK_TYPE_RECT, 130 LTK_TYPE_VOID, 131 LTK_TYPE_VOIDP, 132 } type; 133 } ltk_callback_arg; 134 135 /* FIXME: STRING should be CHARP for consistency */ 136 #define LTK_MAKE_ARG_INT(data) ((ltk_callback_arg){.type = LTK_TYPE_INT, .arg = {.i = (data)}}) 137 #define LTK_MAKE_ARG_SIZE_T(data) ((ltk_callback_arg){.type = LTK_TYPE_SIZE_T, .arg = {.sz = (data)}}) 138 #define LTK_MAKE_ARG_CHAR(data) ((ltk_callback_arg){.type = LTK_TYPE_CHAR, .arg = {.c = (data)}}) 139 #define LTK_MAKE_ARG_WIDGET(data) ((ltk_callback_arg){.type = LTK_TYPE_WIDGET, .arg = {.widget = (data)}}) 140 #define LTK_MAKE_ARG_STRING(data) ((ltk_callback_arg){.type = LTK_TYPE_STRING, .arg = {.str = (data)}}) 141 #define LTK_MAKE_ARG_CONST_STRING(data) ((ltk_callback_arg){.type = LTK_TYPE_CONST_STRING, .arg = {.cstr = (data)}}) 142 #define LTK_MAKE_ARG_KEY_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_KEY_EVENT, .arg = {.key_event = (data)}}) 143 #define LTK_MAKE_ARG_BUTTON_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_BUTTON_EVENT, .arg = {.button_event = (data)}}) 144 #define LTK_MAKE_ARG_SCROLL_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_SCROLL_EVENT, .arg = {.scroll_event = (data)}}) 145 #define LTK_MAKE_ARG_MOTION_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_MOTION_EVENT, .arg = {.motion_event = (data)}}) 146 #define LTK_MAKE_ARG_SURFACE(data) ((ltk_callback_arg){.type = LTK_TYPE_SURFACE, .arg = {.surface = (data)}}) 147 #define LTK_MAKE_ARG_RECT(data) ((ltk_callback_arg){.type = LTK_TYPE_RECT, .arg = {.rect = (data)}}) 148 #define LTK_MAKE_ARG_VOIDP(data) ((ltk_callback_arg){.type = LTK_TYPE_VOIDP, .arg = {.v = (data)}}) 149 150 #define LTK_ARG_VOID ((ltk_callback_arg){.type = LTK_TYPE_VOID}) 151 152 #define LTK_CAST_ARG_INT(carg) (ltk_assert(carg.type == LTK_TYPE_INT), carg.arg.i) 153 #define LTK_CAST_ARG_SIZE_T(carg) (ltk_assert(carg.type == LTK_TYPE_SIZE_T), carg.arg.sz) 154 #define LTK_CAST_ARG_CHAR(carg) (ltk_assert(carg.type == LTK_TYPE_CHAR), carg.arg.c) 155 #define LTK_CAST_ARG_WIDGET(carg) (ltk_assert(carg.type == LTK_TYPE_WIDGET), carg.arg.widget) 156 #define LTK_CAST_ARG_STRING(carg) (ltk_assert(carg.type == LTK_TYPE_STRING), carg.arg.str) 157 #define LTK_CAST_ARG_CONST_STRING(carg) (ltk_assert(carg.type == LTK_TYPE_CONST_STRING), carg.arg.cstr) 158 #define LTK_CAST_ARG_KEY_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_KEY_EVENT), carg.arg.key_event) 159 #define LTK_CAST_ARG_BUTTON_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_BUTTON_EVENT), carg.arg.button_event) 160 #define LTK_CAST_ARG_SCROLL_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_SCROLL_EVENT), carg.arg.scroll_event) 161 #define LTK_CAST_ARG_MOTION_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_MOTION_EVENT), carg.arg.motion_event) 162 #define LTK_CAST_ARG_SURFACE(carg) (ltk_assert(carg.type == LTK_TYPE_SURFACE), carg.arg.surface) 163 #define LTK_CAST_ARG_RECT(carg) (ltk_assert(carg.type == LTK_TYPE_RECT), carg.arg.rect) 164 #define LTK_CAST_ARG_VOIDP(carg) (ltk_assert(carg.type == LTK_TYPE_VOIDP), carg.arg.v) 165 166 #define LTK_GET_ARG_INT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_INT(cargs.args[i])) 167 #define LTK_GET_ARG_SIZE_T(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_SIZE_T(cargs.args[i])) 168 #define LTK_GET_ARG_CHAR(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_CHAR(cargs.args[i])) 169 #define LTK_GET_ARG_WIDGET(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_WIDGET(cargs.args[i])) 170 #define LTK_GET_ARG_STRING(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_STRING(cargs.args[i])) 171 #define LTK_GET_ARG_CONST_STRING(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_CONST_STRING(cargs.args[i])) 172 #define LTK_GET_ARG_KEY_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_KEY_EVENT(cargs.args[i])) 173 #define LTK_GET_ARG_BUTTON_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_BUTTON_EVENT(cargs.args[i])) 174 #define LTK_GET_ARG_SCROLL_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_SCROLL_EVENT(cargs.args[i])) 175 #define LTK_GET_ARG_MOTION_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_MOTION_EVENT(cargs.args[i])) 176 #define LTK_GET_ARG_SURFACE(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_SURFACE(cargs.args[i])) 177 #define LTK_GET_ARG_RECT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_RECT(cargs.args[i])) 178 179 #define LTK_CAST_WIDGET(w) (&(w)->widget) 180 #define LTK_CAST_WINDOW(w) (ltk_assert(w->vtable->type == LTK_WIDGET_WINDOW), (ltk_window *)(w)) 181 #define LTK_CAST_LABEL(w) (ltk_assert(w->vtable->type == LTK_WIDGET_LABEL), (ltk_label *)(w)) 182 #define LTK_CAST_BUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_BUTTON), (ltk_button *)(w)) 183 #define LTK_CAST_GRID(w) (ltk_assert(w->vtable->type == LTK_WIDGET_GRID), (ltk_grid *)(w)) 184 #define LTK_CAST_IMAGE_WIDGET(w) (ltk_assert(w->vtable->type == LTK_WIDGET_IMAGE), (ltk_image_widget *)(w)) 185 #define LTK_CAST_ENTRY(w) (ltk_assert(w->vtable->type == LTK_WIDGET_ENTRY), (ltk_entry *)(w)) 186 #define LTK_CAST_MENU(w) (ltk_assert(w->vtable->type == LTK_WIDGET_MENU), (ltk_menu *)(w)) 187 #define LTK_CAST_MENUENTRY(w) (ltk_assert(w->vtable->type == LTK_WIDGET_MENUENTRY), (ltk_menuentry *)(w)) 188 #define LTK_CAST_SCROLLBAR(w) (ltk_assert(w->vtable->type == LTK_WIDGET_SCROLLBAR), (ltk_scrollbar *)(w)) 189 #define LTK_CAST_BOX(w) (ltk_assert(w->vtable->type == LTK_WIDGET_BOX), (ltk_box *)(w)) 190 #define LTK_CAST_CHECKBUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_CHECKBUTTON), (ltk_checkbutton *)(w)) 191 #define LTK_CAST_RADIOBUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_RADIOBUTTON), (ltk_radiobutton *)(w)) 192 #define LTK_CAST_COMBOBOX(w) (ltk_assert(w->vtable->type == LTK_WIDGET_COMBOBOX), (ltk_combobox *)(w)) 193 194 /* FIXME: a bit weird because window never gets some of these signals */ 195 #define LTK_WIDGET_SIGNAL_KEY_PRESS 1 196 #define LTK_WIDGET_SIGNAL_KEY_RELEASE 2 197 #define LTK_WIDGET_SIGNAL_MOUSE_PRESS 3 198 #define LTK_WIDGET_SIGNAL_MOUSE_RELEASE 4 199 #define LTK_WIDGET_SIGNAL_MOUSE_SCROLL 5 200 #define LTK_WIDGET_SIGNAL_MOTION_NOTIFY 6 201 #define LTK_WIDGET_SIGNAL_MOUSE_ENTER 7 202 #define LTK_WIDGET_SIGNAL_MOUSE_LEAVE 8 203 #define LTK_WIDGET_SIGNAL_PRESS 9 204 #define LTK_WIDGET_SIGNAL_RELEASE 10 205 /* FIXME: resize is currently triggered in a lot of cases where nothing is really resized */ 206 #define LTK_WIDGET_SIGNAL_RESIZE 11 207 #define LTK_WIDGET_SIGNAL_HIDE 12 208 #define LTK_WIDGET_SIGNAL_DRAW 13 209 #define LTK_WIDGET_SIGNAL_CHANGE_STATE 14 210 /* The return value for this is ignored, i.e. 211 the widget destroy function is always called. 212 Also, it doesn't receive the 'shallow' argument 213 that the widget method receives. */ 214 #define LTK_WIDGET_SIGNAL_DESTROY 15 215 #define LTK_WIDGET_SIGNAL_INVALID 16 216 217 typedef struct { 218 ltk_callback_arg *args; 219 size_t num; 220 } ltk_callback_arglist; 221 222 #define LTK_EMPTY_ARGLIST ((ltk_callback_arglist){NULL, 0}) 223 224 typedef int (*ltk_signal_callback)(ltk_widget *widget, ltk_callback_arglist args, ltk_callback_arg data); 225 typedef struct { 226 ltk_signal_callback callback; 227 ltk_callback_arg data; 228 int type; 229 } ltk_signal_callback_info; 230 231 int ltk_widget_register_signal_handler(ltk_widget *widget, int type, ltk_signal_callback callback, ltk_callback_arg data); 232 int ltk_widget_emit_signal(ltk_widget *widget, int type, ltk_callback_arglist args); 233 size_t ltk_widget_remove_signal_handler_by_type(ltk_widget *widget, int type); 234 size_t ltk_widget_remove_signal_handler_by_callback(ltk_widget *widget, ltk_signal_callback callback); 235 size_t ltk_widget_remove_signal_handler_by_info( 236 ltk_widget *widget, 237 int (*filter_func)(ltk_signal_callback_info *to_check, ltk_signal_callback_info *info), 238 ltk_signal_callback_info *info 239 ); 240 void ltk_widget_remove_all_signal_handlers(ltk_widget *widget); 241 int ltk_widget_register_type(void); 242 243 LTK_ARRAY_INIT_STRUCT_DECL(signal, ltk_signal_callback_info) 244 245 struct ltk_widget { 246 struct ltk_window *window; 247 struct ltk_widget *parent; 248 249 struct ltk_widget_vtable *vtable; 250 251 /* FIXME: crect and lrect are a bit weird still */ 252 /* FIXME: especially the relative positioning is really weird for 253 popups because they're positioned globally but still have a 254 parent-child relationship - weird things can probably happen */ 255 /* both rects relative to parent (except for popups) */ 256 /* collision rect is only part that is actually shown and used for 257 collision with mouse (but may still not be drawn if hidden by 258 something else) - e.g. in a box with scrolling, a widget that 259 is half cut off by a side of the box will have the logical rect 260 going past the side of the box, but the collision rect will only 261 be the part inside the box */ 262 ltk_rect crect; /* collision rect */ 263 ltk_rect lrect; /* logical rect */ 264 unsigned int ideal_w; 265 unsigned int ideal_h; 266 unsigned int last_dpi; 267 268 /* maybe mask to determine quickly which callbacks are included? 269 default signals only allowed to have one callback? */ 270 /* could maybe have just uint32_t mask so at least the lower signals 271 can be easily checked */ 272 ltk_array(signal) *signal_cbs; 273 274 ltk_widget_state state; 275 /* FIXME: store this in grid/box - for row_span, column_span the other cells could be marked with "not top left cell of widget" so they can be skipped */ 276 ltk_sticky_mask sticky; 277 unsigned short row; 278 unsigned short column; 279 unsigned short row_span; 280 unsigned short column_span; 281 /* ALSO NEED SIGNALS LIKE ADD-TEXT (called *before* text is inserted to check validity) - these would need argument 282 FIGURE OUT HOW TO DO KEY MAPPINGS - should reuse parts of builtin mapping handling 283 -> maybe something like tk 284 -> or maybe just say everyone needs to override event handler? but makes simple stuff more difficult 285 -> also need "global" mappings/key event handler for global shortcuts */ 286 /* needed to properly handle handle local coordinates since 287 popups are positioned globally instead of locally */ 288 char popup; 289 char dirty; 290 char hidden; 291 char vtable_copied; 292 }; 293 294 typedef struct ltk_widget_vtable { 295 int (*key_press)(struct ltk_widget *, ltk_key_event *); 296 int (*key_release)(struct ltk_widget *, ltk_key_event *); 297 /* press/release also receive double/triple-click/release */ 298 int (*mouse_press)(struct ltk_widget *, ltk_button_event *); 299 int (*mouse_release)(struct ltk_widget *, ltk_button_event *); 300 int (*mouse_scroll)(struct ltk_widget *, ltk_scroll_event *); 301 int (*motion_notify)(struct ltk_widget *, ltk_motion_event *); 302 int (*mouse_leave)(struct ltk_widget *, ltk_motion_event *); 303 int (*mouse_enter)(struct ltk_widget *, ltk_motion_event *); 304 int (*press)(struct ltk_widget *); 305 int (*release)(struct ltk_widget *); 306 void (*cmd_return)(struct ltk_widget *self, char *text, size_t len); 307 308 /* This is called when the DPI changes. It must not call child_size_change on 309 the parent or do any actual resizing (only ideal_w and ideal_h should be set), 310 but it should call ltk_widget_recalc_ideal_size on any child widgets. */ 311 /* When a widget is added to a container, the container should call 312 ltk_widget_recalc_ideal_size on the widget in case the DPI changed. */ 313 void (*recalc_ideal_size)(struct ltk_widget *); 314 void (*resize)(struct ltk_widget *); 315 void (*hide)(struct ltk_widget *); 316 /* draw_surface: surface to draw it on 317 x, y: position of logical rectangle on surface 318 clip: clipping rectangle, relative to logical rectangle */ 319 void (*draw)(struct ltk_widget *self, ltk_surface *draw_surface, int x, int y, ltk_rect clip); 320 void (*change_state)(struct ltk_widget *, ltk_widget_state); 321 void (*destroy)(struct ltk_widget *, int); 322 323 /* rect is in self's coordinate system */ 324 struct ltk_widget *(*nearest_child)(struct ltk_widget *self, ltk_rect rect); 325 struct ltk_widget *(*nearest_child_left)(struct ltk_widget *self, ltk_widget *widget); 326 struct ltk_widget *(*nearest_child_right)(struct ltk_widget *self, ltk_widget *widget); 327 struct ltk_widget *(*nearest_child_above)(struct ltk_widget *self, ltk_widget *widget); 328 struct ltk_widget *(*nearest_child_below)(struct ltk_widget *self, ltk_widget *widget); 329 struct ltk_widget *(*next_child)(struct ltk_widget *self, ltk_widget *child); 330 struct ltk_widget *(*prev_child)(struct ltk_widget *self, ltk_widget *child); 331 struct ltk_widget *(*first_child)(struct ltk_widget *self); 332 struct ltk_widget *(*last_child)(struct ltk_widget *self); 333 334 /* This is called when the ideal size of a child is changed (e.g. text is 335 added to a text entry). */ 336 void (*child_size_change)(struct ltk_widget *, struct ltk_widget *); 337 int (*remove_child)(struct ltk_widget *, struct ltk_widget *); 338 /* x and y relative to widget's lrect! */ 339 struct ltk_widget *(*get_child_at_pos)(struct ltk_widget *, int x, int y); 340 /* r is in self's coordinate system */ 341 void (*ensure_rect_shown)(struct ltk_widget *self, ltk_rect r); 342 343 ltk_widget_type type; 344 ltk_widget_flags flags; 345 int invalid_signal; 346 } ltk_widget_vtable; 347 348 void ltk_widget_hide(ltk_widget *widget); 349 int ltk_widget_destroy(ltk_widget *widget, int shallow); 350 void ltk_fill_widget_defaults( 351 ltk_widget *widget, struct ltk_window *window, 352 struct ltk_widget_vtable *vtable, int w, int h 353 ); 354 void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state); 355 void ltk_widget_resize(ltk_widget *widget); 356 void ltk_widget_draw(ltk_widget *widget, ltk_surface *draw_surf, int x, int y, ltk_rect clip_rect); 357 /* FIXME: layout widgets need to recalculate ideal size when children change! */ 358 void ltk_widget_get_ideal_size(ltk_widget *widget, unsigned int *ideal_w_ret, unsigned int *ideal_h_ret); 359 ltk_point ltk_widget_pos_to_global(ltk_widget *widget, int x, int y); 360 ltk_point ltk_global_to_widget_pos(ltk_widget *widget, int x, int y); 361 362 ltk_widget_vtable *ltk_widget_get_editable_vtable(ltk_widget *widget); 363 364 void ltk_widget_recalc_ideal_size(ltk_widget *widget); 365 366 #endif /* LTK_WIDGET_H */