box.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
---
box.c (17354B)
---
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 /* FIXME: implement other sticky options now supported by grid */
18
19 #include <limits.h>
20 #include <string.h>
21
22 #include "box.h"
23 #include "event.h"
24 #include "graphics.h"
25 #include "memory.h"
26 #include "rect.h"
27 #include "scrollbar.h"
28 #include "widget.h"
29
30 static void ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
31 static void ltk_box_destroy(ltk_widget *self, int shallow);
32 static void ltk_recalculate_box(ltk_widget *self);
33 static void ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget);
34 static int ltk_box_remove_child(ltk_widget *self, ltk_widget *widget);
35 /* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow); */
36 static int ltk_box_scroll_cb(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data);
37 static int ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event);
38 static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y);
39 static void ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r);
40
41 static ltk_widget *ltk_box_prev_child(ltk_widget *self, ltk_widget *child);
42 static ltk_widget *ltk_box_next_child(ltk_widget *self, ltk_widget *child);
43 static ltk_widget *ltk_box_first_child(ltk_widget *self);
44 static ltk_widget *ltk_box_last_child(ltk_widget *self);
45
46 static ltk_widget *ltk_box_nearest_child(ltk_widget *self, ltk_rect rect);
47 static ltk_widget *ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget);
48 static ltk_widget *ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget);
49 static ltk_widget *ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget);
50 static ltk_widget *ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget);
51
52 static void ltk_box_recalc_ideal_size(ltk_widget *self);
53
54 static struct ltk_widget_vtable vtable = {
55 .change_state = NULL,
56 .hide = NULL,
57 .draw = <k_box_draw,
58 .destroy = <k_box_destroy,
59 .resize = <k_recalculate_box,
60 .child_size_change = <k_box_child_size_change,
61 .remove_child = <k_box_remove_child,
62 .key_press = NULL,
63 .key_release = NULL,
64 .mouse_press = NULL,
65 .mouse_scroll = <k_box_mouse_scroll,
66 .mouse_release = NULL,
67 .motion_notify = NULL,
68 .get_child_at_pos = <k_box_get_child_at_pos,
69 .mouse_leave = NULL,
70 .mouse_enter = NULL,
71 .prev_child = <k_box_prev_child,
72 .next_child = <k_box_next_child,
73 .first_child = <k_box_first_child,
74 .last_child = <k_box_last_child,
75 .nearest_child = <k_box_nearest_child,
76 .nearest_child_left = <k_box_nearest_child_left,
77 .nearest_child_right = <k_box_nearest_child_right,
78 .nearest_child_above = <k_box_nearest_child_above,
79 .nearest_child_below = <k_box_nearest_child_below,
80 .ensure_rect_shown = <k_box_ensure_rect_shown,
81 .recalc_ideal_size = <k_box_recalc_ideal_size,
82 .type = LTK_WIDGET_BOX,
83 .flags = 0,
84 .invalid_signal = LTK_BOX_SIGNAL_INVALID,
85 };
86
87 static void
88 ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
89 ltk_box *box = LTK_CAST_BOX(self);
90 ltk_widget *ptr;
91 /* FIXME: clip out scrollbar */
92 ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip);
93 for (size_t i = 0; i < box->num_widgets; i++) {
94 ptr = box->widgets[i];
95 /* FIXME: Maybe continue immediately if widget is
96 obviously outside of clipping rect */
97 ltk_widget_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, real_clip));
98 }
99 ltk_widget_draw(
100 LTK_CAST_WIDGET(box->sc), s,
101 x + box->sc->widget.lrect.x,
102 y + box->sc->widget.lrect.y,
103 ltk_rect_relative(box->sc->widget.lrect, real_clip)
104 );
105 }
106
107 ltk_box *
108 ltk_box_create(ltk_window *window, ltk_orientation orient) {
109 ltk_box *box = ltk_malloc(sizeof(ltk_box));
110 ltk_widget *self = LTK_CAST_WIDGET(box);
111
112 ltk_fill_widget_defaults(self, window, &vtable, 0, 0);
113
114 box->sc = ltk_scrollbar_create(window, orient);
115 box->sc->widget.parent = self;
116 ltk_widget_register_signal_handler(
117 LTK_CAST_WIDGET(box->sc), LTK_SCROLLBAR_SIGNAL_SCROLL,
118 <k_box_scroll_cb, LTK_MAKE_ARG_WIDGET(self)
119 );
120 box->widgets = NULL;
121 box->num_alloc = 0;
122 box->num_widgets = 0;
123 box->orient = orient;
124 if (orient == LTK_HORIZONTAL)
125 box->widget.ideal_h = box->sc->widget.ideal_h;
126 else
127 box->widget.ideal_w = box->sc->widget.ideal_w;
128 ltk_recalculate_box(self);
129
130 return box;
131 }
132
133 static void
134 ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r) {
135 ltk_box *box = LTK_CAST_BOX(self);
136 int delta = 0;
137 if (box->orient == LTK_HORIZONTAL) {
138 if (r.x + r.w > self->lrect.w && r.w <= self->lrect.w)
139 delta = r.x - (self->lrect.w - r.w);
140 else if (r.x < 0 || r.w > self->lrect.w)
141 delta = r.x;
142 } else {
143 if (r.y + r.h > self->lrect.h && r.h <= self->lrect.h)
144 delta = r.y - (self->lrect.h - r.h);
145 else if (r.y < 0 || r.h > self->lrect.h)
146 delta = r.y;
147 }
148 if (delta)
149 ltk_scrollbar_scroll(LTK_CAST_WIDGET(box->sc), delta, 0);
150 }
151
152 static void
153 ltk_box_destroy(ltk_widget *self, int shallow) {
154 ltk_box *box = LTK_CAST_BOX(self);
155 ltk_widget *ptr;
156 for (size_t i = 0; i < box->num_widgets; i++) {
157 ptr = box->widgets[i];
158 ptr->parent = NULL;
159 if (!shallow)
160 ltk_widget_destroy(ptr, shallow);
161 }
162 ltk_free(box->widgets);
163 box->sc->widget.parent = NULL;
164 ltk_widget_destroy(LTK_CAST_WIDGET(box->sc), 0);
165 ltk_free(box);
166 }
167
168 /* FIXME: Make this function name more consistent */
169 /* FIXME: The widget positions are set with the old scrollbar->cur_pos, before the
170 virtual_size is set - this can cause problems when a widget changes its size
171 (in the scrolled direction) when resized. */
172 /* FIXME: avoid complete recalculation when just scrolling (only position updated) */
173 static void
174 ltk_recalculate_box(ltk_widget *self) {
175 ltk_box *box = LTK_CAST_BOX(self);
176 ltk_widget *ptr;
177 ltk_rect *sc_rect = &box->sc->widget.lrect;
178 int cur_pos = 0;
179 if (box->orient == LTK_HORIZONTAL)
180 sc_rect->h = box->sc->widget.ideal_h;
181 else
182 sc_rect->w = box->sc->widget.ideal_w;
183 for (size_t i = 0; i < box->num_widgets; i++) {
184 ptr = box->widgets[i];
185 ptr->lrect.w = ptr->ideal_w;
186 ptr->lrect.h = ptr->ideal_h;
187 if (box->orient == LTK_HORIZONTAL) {
188 ptr->lrect.x = cur_pos - box->sc->cur_pos;
189 if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM)
190 ptr->lrect.h = box->widget.lrect.h - sc_rect->h;
191 if (ptr->sticky & LTK_STICKY_TOP)
192 ptr->lrect.y = 0;
193 else if (ptr->sticky & LTK_STICKY_BOTTOM)
194 ptr->lrect.y = box->widget.lrect.h - ptr->lrect.h - sc_rect->h;
195 else
196 ptr->lrect.y = (box->widget.lrect.h - ptr->lrect.h) / 2;
197 cur_pos += ptr->lrect.w;
198 } else {
199 ptr->lrect.y = cur_pos - box->sc->cur_pos;
200 if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT)
201 ptr->lrect.w = box->widget.lrect.w - sc_rect->w;
202 if (ptr->sticky & LTK_STICKY_LEFT)
203 ptr->lrect.x = 0;
204 else if (ptr->sticky & LTK_STICKY_RIGHT)
205 ptr->lrect.x = box->widget.lrect.w - ptr->lrect.w - sc_rect->w;
206 else
207 ptr->lrect.x = (box->widget.lrect.w - ptr->lrect.w) / 2;
208 cur_pos += ptr->lrect.h;
209 }
210 ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect);
211 ltk_widget_resize(ptr);
212 }
213 ltk_scrollbar_set_virtual_size(box->sc, cur_pos);
214 if (box->orient == LTK_HORIZONTAL) {
215 sc_rect->x = 0;
216 sc_rect->y = box->widget.lrect.h - sc_rect->h;
217 sc_rect->w = box->widget.lrect.w;
218 } else {
219 sc_rect->x = box->widget.lrect.w - sc_rect->w;
220 sc_rect->y = 0;
221 sc_rect->h = box->widget.lrect.h;
222 }
223 *sc_rect = ltk_rect_intersect(*sc_rect, (ltk_rect){0, 0, box->widget.lrect.w, box->widget.lrect.h});
224 box->sc->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, *sc_rect);
225 ltk_widget_resize(LTK_CAST_WIDGET(box->sc));
226 }
227
228 static void
229 ltk_box_recalc_ideal_size(ltk_widget *self) {
230 ltk_box *box = LTK_CAST_BOX(self);
231 ltk_widget *ptr;
232 self->ideal_w = self->ideal_h = 0;
233 for (size_t i = 0; i < box->num_widgets; i++) {
234 ptr = box->widgets[i];
235 ltk_widget_recalc_ideal_size(ptr);
236 if (box->orient == LTK_HORIZONTAL && ptr->ideal_h > self->ideal_h) {
237 self->ideal_h = ptr->ideal_h;
238 self->ideal_w += ptr->ideal_w;
239 } else if (box->orient == LTK_VERTICAL && ptr->ideal_w > self->ideal_w) {
240 self->ideal_w = ptr->ideal_w;
241 self->ideal_h += ptr->ideal_h;
242 }
243 }
244 ltk_widget_recalc_ideal_size(LTK_CAST_WIDGET(box->sc));
245 if (box->orient == LTK_HORIZONTAL)
246 self->ideal_h += box->sc->widget.ideal_h;
247 else if (box->orient == LTK_VERTICAL)
248 self->ideal_w += box->sc->widget.ideal_w;
249 }
250
251 /* FIXME: This entire resizing thing is a bit weird. For instance, if a label
252 in a vertical box increases its height because its width has been decreased
253 and it is forced to wrap, should that just change the rect or also the
254 ideal size? Ideal size wouldn't really make sense here, but then the box
255 might be forced to add a scrollbar even though the parent widget would
256 actually give it more space if it knew that it needed it. */
257
258 static void
259 ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) {
260 ltk_box *box = LTK_CAST_BOX(self);
261 short size_changed = 0;
262 /* This is always reset here - if it needs to be changed,
263 the resize function called by the last child_size_change
264 function will fix it */
265 /* Note: This seems a bit weird, but if each widget set its rect itself,
266 that would also lead to weird things. For instance, if a butten is
267 added to a box after being ungridded, and its rect was changed
268 by the grid (e.g. because of a column weight), who should reset the
269 rect if it doesn't have sticky set? Of course, the resize function
270 could also set all widgets even if they don't have any sticky
271 settings, but there'd probably be some catch as well. */
272 /* FIXME: the same comment as in grid.c applies */
273 int orig_w = widget->lrect.w;
274 int orig_h = widget->lrect.h;
275 widget->lrect.w = widget->ideal_w;
276 widget->lrect.h = widget->ideal_h;
277 int sc_w = box->sc->widget.lrect.w;
278 int sc_h = box->sc->widget.lrect.h;
279 if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h > box->widget.ideal_h) {
280 box->widget.ideal_h = widget->ideal_h + sc_h;
281 size_changed = 1;
282 } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w > box->widget.ideal_h) {
283 box->widget.ideal_w = widget->ideal_w + sc_w;
284 size_changed = 1;
285 }
286
287 if (size_changed && box->widget.parent && box->widget.parent->vtable->child_size_change)
288 box->widget.parent->vtable->child_size_change(box->widget.parent, (ltk_widget *)box);
289 else
290 ltk_recalculate_box(LTK_CAST_WIDGET(box));
291 if (orig_w != widget->lrect.w || orig_h != widget->lrect.h)
292 ltk_widget_resize(widget);
293 }
294
295 int
296 ltk_box_add(ltk_box *box, ltk_widget *widget, ltk_sticky_mask sticky) {
297 if (widget->parent)
298 return 1;
299 if (box->num_widgets >= box->num_alloc) {
300 size_t new_size = box->num_alloc > 0 ? box->num_alloc * 2 : 4;
301 ltk_widget **new = ltk_realloc(box->widgets, new_size * sizeof(ltk_widget *));
302 box->num_alloc = new_size;
303 box->widgets = new;
304 }
305 ltk_widget_recalc_ideal_size(widget);
306
307 int sc_w = box->sc->widget.lrect.w;
308 int sc_h = box->sc->widget.lrect.h;
309
310 box->widgets[box->num_widgets++] = widget;
311 if (box->orient == LTK_HORIZONTAL) {
312 box->widget.ideal_w += widget->ideal_w;
313 if (widget->ideal_h + sc_h > box->widget.ideal_h)
314 box->widget.ideal_h = widget->ideal_h + sc_h;
315 } else {
316 box->widget.ideal_h += widget->ideal_h;
317 if (widget->ideal_w + sc_w > box->widget.ideal_w)
318 box->widget.ideal_w = widget->ideal_w + sc_w;
319 }
320 widget->parent = LTK_CAST_WIDGET(box);
321 widget->sticky = sticky;
322 ltk_box_child_size_change(LTK_CAST_WIDGET(box), widget);
323 ltk_window_invalidate_widget_rect(box->widget.window, LTK_CAST_WIDGET(box));
324
325 return 0;
326 }
327
328 int
329 ltk_box_remove_index(ltk_box *box, size_t index) {
330 if (index >= box->num_widgets)
331 return 1;
332 ltk_widget *self = LTK_CAST_WIDGET(box);
333 ltk_widget *widget = box->widgets[index];
334 int sc_w = box->sc->widget.lrect.w;
335 int sc_h = box->sc->widget.lrect.h;
336 if (index < box->num_widgets - 1)
337 memmove(box->widgets + index, box->widgets + index + 1,
338 (box->num_widgets - index - 1) * sizeof(ltk_widget *));
339 box->num_widgets--;
340 ltk_window_invalidate_widget_rect(self->window, self);
341 /* search for new ideal width/height */
342 /* FIXME: make this all a bit nicer and break the lines better */
343 /* FIXME: other part of ideal size not updated */
344 if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h == self->ideal_h) {
345 self->ideal_h = 0;
346 for (size_t j = 0; j < box->num_widgets; j++) {
347 if (box->widgets[j]->ideal_h + sc_h > self->ideal_h)
348 self->ideal_h = box->widgets[j]->ideal_h + sc_h;
349 }
350 if (self->parent)
351 ltk_widget_resize(self->parent);
352 } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w == self->ideal_w) {
353 self->ideal_w = 0;
354 for (size_t j = 0; j < box->num_widgets; j++) {
355 if (box->widgets[j]->ideal_w + sc_w > self->ideal_w)
356 self->ideal_w = box->widgets[j]->ideal_w + sc_w;
357 }
358 if (self->parent)
359 ltk_widget_resize(self->parent);
360 }
361 return 0;
362 }
363
364 int
365 ltk_box_remove(ltk_box *box, ltk_widget *widget) {
366 if (widget->parent != LTK_CAST_WIDGET(box))
367 return 1;
368 widget->parent = NULL;
369 for (size_t i = 0; i < box->num_widgets; i++) {
370 if (box->widgets[i] == widget) {
371 return ltk_box_remove_index(box, i);
372 }
373 }
374
375 return 1;
376 }
377
378 static int
379 ltk_box_remove_child(ltk_widget *self, ltk_widget *widget) {
380 return ltk_box_remove(LTK_CAST_BOX(self), widget);
381 }
382
383 /* FIXME: maybe come up with a more efficient method */
384 static ltk_widget *
385 ltk_box_nearest_child(ltk_widget *self, ltk_rect rect) {
386 ltk_box *box = LTK_CAST_BOX(self);
387 ltk_widget *minw = NULL;
388 int min_dist = INT_MAX;
389 for (size_t i = 0; i < box->num_widgets; i++) {
390 ltk_rect r = box->widgets[i]->lrect;
391 int dist = ltk_rect_fakedist(rect, r);
392 if (dist < min_dist) {
393 min_dist = dist;
394 minw = box->widgets[i];
395 }
396 }
397 return minw;
398 }
399
400 static ltk_widget *
401 ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
402 ltk_box *box = LTK_CAST_BOX(self);
403 if (box->orient == LTK_VERTICAL)
404 return NULL;
405 return ltk_box_prev_child(self, widget);
406 }
407
408 static ltk_widget *
409 ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
410 ltk_box *box = LTK_CAST_BOX(self);
411 if (box->orient == LTK_VERTICAL)
412 return NULL;
413 return ltk_box_next_child(self, widget);
414 }
415
416 static ltk_widget *
417 ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
418 ltk_box *box = LTK_CAST_BOX(self);
419 if (box->orient == LTK_HORIZONTAL)
420 return NULL;
421 return ltk_box_prev_child(self, widget);
422 }
423
424 static ltk_widget *
425 ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
426 ltk_box *box = LTK_CAST_BOX(self);
427 if (box->orient == LTK_HORIZONTAL)
428 return NULL;
429 return ltk_box_next_child(self, widget);
430 }
431
432 static ltk_widget *
433 ltk_box_prev_child(ltk_widget *self, ltk_widget *child) {
434 ltk_box *box = LTK_CAST_BOX(self);
435 for (size_t i = box->num_widgets; i-- > 0;) {
436 if (box->widgets[i] == child)
437 return i > 0 ? box->widgets[i-1] : NULL;
438 }
439 return NULL;
440 }
441
442 static ltk_widget *
443 ltk_box_next_child(ltk_widget *self, ltk_widget *child) {
444 ltk_box *box = LTK_CAST_BOX(self);
445 for (size_t i = 0; i < box->num_widgets; i++) {
446 if (box->widgets[i] == child)
447 return i < box->num_widgets - 1 ? box->widgets[i+1] : NULL;
448 }
449 return NULL;
450 }
451
452 static ltk_widget *
453 ltk_box_first_child(ltk_widget *self) {
454 ltk_box *box = LTK_CAST_BOX(self);
455 return box->num_widgets > 0 ? box->widgets[0] : NULL;
456 }
457
458 static ltk_widget *
459 ltk_box_last_child(ltk_widget *self) {
460 ltk_box *box = LTK_CAST_BOX(self);
461 return box->num_widgets > 0 ? box->widgets[box->num_widgets-1] : NULL;
462 }
463
464 static int
465 ltk_box_scroll_cb(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) {
466 (void)self;
467 (void)args;
468 ltk_widget *boxw = LTK_CAST_ARG_WIDGET(data);
469 ltk_recalculate_box(boxw);
470 ltk_window_invalidate_widget_rect(boxw->window, boxw);
471 return 1;
472 }
473
474 static ltk_widget *
475 ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) {
476 ltk_box *box = LTK_CAST_BOX(self);
477 if (ltk_collide_rect(box->sc->widget.crect, x, y))
478 return (ltk_widget *)box->sc;
479 for (size_t i = 0; i < box->num_widgets; i++) {
480 if (ltk_collide_rect(box->widgets[i]->crect, x, y))
481 return box->widgets[i];
482 }
483 return NULL;
484 }
485
486 static int
487 ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
488 ltk_box *box = LTK_CAST_BOX(self);
489 if (event->dy) {
490 /* FIXME: horizontal scrolling, etc. */
491 /* FIXME: configure scrollstep */
492 int delta = event->dy * -15;
493 ltk_scrollbar_scroll(LTK_CAST_WIDGET(box->sc), delta, 0);
494 ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y);
495 ltk_window_fake_motion_event(self->window, glob.x, glob.y);
496 return 1;
497 }
498 return 0;
499 }