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