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/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 = &ltk_box_draw,
           59         .destroy = &ltk_box_destroy,
           60         .resize = &ltk_recalculate_box,
           61         .child_size_change = &ltk_box_child_size_change,
           62         .remove_child = &ltk_box_remove_child,
           63         .key_press = NULL,
           64         .key_release = NULL,
           65         .mouse_press = NULL,
           66         .mouse_scroll = &ltk_box_mouse_scroll,
           67         .mouse_release = NULL,
           68         .motion_notify = NULL,
           69         .get_child_at_pos = &ltk_box_get_child_at_pos,
           70         .mouse_leave = NULL,
           71         .mouse_enter = NULL,
           72         .prev_child = &ltk_box_prev_child,
           73         .next_child = &ltk_box_next_child,
           74         .first_child = &ltk_box_first_child,
           75         .last_child = &ltk_box_last_child,
           76         .nearest_child = &ltk_box_nearest_child,
           77         .nearest_child_left = &ltk_box_nearest_child_left,
           78         .nearest_child_right = &ltk_box_nearest_child_right,
           79         .nearest_child_above = &ltk_box_nearest_child_above,
           80         .nearest_child_below = &ltk_box_nearest_child_below,
           81         .ensure_rect_shown = &ltk_box_ensure_rect_shown,
           82         .recalc_ideal_size = &ltk_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                 &ltk_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 }