URI: 
       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 = &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 }