ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | 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 = &ltk_box_draw,
     58 	.destroy = &ltk_box_destroy,
     59 	.resize = &ltk_recalculate_box,
     60 	.child_size_change = &ltk_box_child_size_change,
     61 	.remove_child = &ltk_box_remove_child,
     62 	.key_press = NULL,
     63 	.key_release = NULL,
     64 	.mouse_press = NULL,
     65 	.mouse_scroll = &ltk_box_mouse_scroll,
     66 	.mouse_release = NULL,
     67 	.motion_notify = NULL,
     68 	.get_child_at_pos = &ltk_box_get_child_at_pos,
     69 	.mouse_leave = NULL,
     70 	.mouse_enter = NULL,
     71 	.prev_child = &ltk_box_prev_child,
     72 	.next_child = &ltk_box_next_child,
     73 	.first_child = &ltk_box_first_child,
     74 	.last_child = &ltk_box_last_child,
     75 	.nearest_child = &ltk_box_nearest_child,
     76 	.nearest_child_left = &ltk_box_nearest_child_left,
     77 	.nearest_child_right = &ltk_box_nearest_child_right,
     78 	.nearest_child_above = &ltk_box_nearest_child_above,
     79 	.nearest_child_below = &ltk_box_nearest_child_below,
     80 	.ensure_rect_shown = &ltk_box_ensure_rect_shown,
     81 	.recalc_ideal_size = &ltk_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 		&ltk_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 }