URI: 
       tbox.c - ltk - Socket-based GUI 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)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tbox.c (19290B)
       ---
            1 /*
            2  * Copyright (c) 2021-2023 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 <stdio.h>
           20 #include <stdlib.h>
           21 #include <stdarg.h>
           22 #include <stdint.h>
           23 #include <string.h>
           24 
           25 #include "event.h"
           26 #include "memory.h"
           27 #include "color.h"
           28 #include "rect.h"
           29 #include "widget.h"
           30 #include "ltk.h"
           31 #include "util.h"
           32 #include "scrollbar.h"
           33 #include "box.h"
           34 #include "cmd.h"
           35 
           36 static void ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
           37 static ltk_box *ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient);
           38 static void ltk_box_destroy(ltk_widget *self, int shallow);
           39 static void ltk_recalculate_box(ltk_widget *self);
           40 static void ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget);
           41 static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, ltk_sticky_mask sticky, ltk_error *err);
           42 static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err);
           43 /* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, ltk_error *err); */
           44 static void ltk_box_scroll(ltk_widget *self);
           45 static int ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event);
           46 static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y);
           47 static void ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r);
           48 
           49 static ltk_widget *ltk_box_prev_child(ltk_widget *self, ltk_widget *child);
           50 static ltk_widget *ltk_box_next_child(ltk_widget *self, ltk_widget *child);
           51 static ltk_widget *ltk_box_first_child(ltk_widget *self);
           52 static ltk_widget *ltk_box_last_child(ltk_widget *self);
           53 
           54 static ltk_widget *ltk_box_nearest_child(ltk_widget *self, ltk_rect rect);
           55 static ltk_widget *ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget);
           56 static ltk_widget *ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget);
           57 static ltk_widget *ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget);
           58 static ltk_widget *ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget);
           59 
           60 static struct ltk_widget_vtable vtable = {
           61         .change_state = NULL,
           62         .hide = NULL,
           63         .draw = &ltk_box_draw,
           64         .destroy = &ltk_box_destroy,
           65         .resize = &ltk_recalculate_box,
           66         .child_size_change = &ltk_box_child_size_change,
           67         .remove_child = &ltk_box_remove,
           68         .key_press = NULL,
           69         .key_release = NULL,
           70         .mouse_press = NULL,
           71         .mouse_scroll = &ltk_box_mouse_scroll,
           72         .mouse_release = NULL,
           73         .motion_notify = NULL,
           74         .get_child_at_pos = &ltk_box_get_child_at_pos,
           75         .mouse_leave = NULL,
           76         .mouse_enter = NULL,
           77         .prev_child = &ltk_box_prev_child,
           78         .next_child = &ltk_box_next_child,
           79         .first_child = &ltk_box_first_child,
           80         .last_child = &ltk_box_last_child,
           81         .nearest_child = &ltk_box_nearest_child,
           82         .nearest_child_left = &ltk_box_nearest_child_left,
           83         .nearest_child_right = &ltk_box_nearest_child_right,
           84         .nearest_child_above = &ltk_box_nearest_child_above,
           85         .nearest_child_below = &ltk_box_nearest_child_below,
           86         .ensure_rect_shown = &ltk_box_ensure_rect_shown,
           87         .type = LTK_WIDGET_BOX,
           88         .flags = 0,
           89 };
           90 
           91 static int ltk_box_cmd_add(
           92     ltk_window *window,
           93     ltk_box *box,
           94     ltk_cmd_token *tokens,
           95     size_t num_tokens,
           96     ltk_error *err);
           97 static int ltk_box_cmd_remove(
           98     ltk_window *window,
           99     ltk_box *box,
          100     ltk_cmd_token *tokens,
          101     size_t num_tokens,
          102     ltk_error *err);
          103 /*
          104 static int ltk_box_cmd_clear
          105     ltk_window *window,
          106     ltk_cmd_token *tokens,
          107     size_t num_tokens,
          108     ltk_error *err);
          109 */
          110 static int ltk_box_cmd_create(
          111     ltk_window *window,
          112     ltk_box *box,
          113     ltk_cmd_token *tokens,
          114     size_t num_tokens,
          115     ltk_error *err);
          116 
          117 static void
          118 ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
          119         ltk_box *box = (ltk_box *)self;
          120         ltk_widget *ptr;
          121         /* FIXME: clip out scrollbar */
          122         ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip);
          123         for (size_t i = 0; i < box->num_widgets; i++) {
          124                 ptr = box->widgets[i];
          125                 /* FIXME: Maybe continue immediately if widget is
          126                    obviously outside of clipping rect */
          127                 ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, real_clip));
          128         }
          129         box->sc->widget.vtable->draw(
          130             (ltk_widget *)box->sc, s,
          131             x + box->sc->widget.lrect.x,
          132             y + box->sc->widget.lrect.y,
          133             ltk_rect_relative(box->sc->widget.lrect, real_clip)
          134         );
          135 }
          136 
          137 static ltk_box *
          138 ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
          139         ltk_box *box = ltk_malloc(sizeof(ltk_box));
          140 
          141         ltk_fill_widget_defaults(&box->widget, id, window, &vtable, 0, 0);
          142 
          143         box->sc = ltk_scrollbar_create(window, orient, &ltk_box_scroll, box);
          144         box->sc->widget.parent = &box->widget;
          145         box->widgets = NULL;
          146         box->num_alloc = 0;
          147         box->num_widgets = 0;
          148         box->orient = orient;
          149         if (orient == LTK_HORIZONTAL)
          150                 box->widget.ideal_h = box->sc->widget.ideal_h;
          151         else
          152                 box->widget.ideal_w = box->sc->widget.ideal_w;
          153         ltk_recalculate_box(&box->widget);
          154 
          155         return box;
          156 }
          157 
          158 static void
          159 ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r) {
          160         ltk_box *box = (ltk_box *)self;
          161         int delta = 0;
          162         if (box->orient == LTK_HORIZONTAL) {
          163                 if (r.x + r.w > self->lrect.w && r.w <= self->lrect.w)
          164                         delta = r.x - (self->lrect.w - r.w);
          165                 else if (r.x < 0 || r.w > self->lrect.w)
          166                         delta = r.x;
          167         } else {
          168                 if (r.y + r.h > self->lrect.h && r.h <= self->lrect.h)
          169                         delta = r.y - (self->lrect.h - r.h);
          170                 else if (r.y < 0 || r.h > self->lrect.h)
          171                         delta = r.y;
          172         }
          173         if (delta)
          174                 ltk_scrollbar_scroll(&box->sc->widget, delta, 0);
          175 }
          176 
          177 static void
          178 ltk_box_destroy(ltk_widget *self, int shallow) {
          179         ltk_box *box = (ltk_box *)self;
          180         ltk_widget *ptr;
          181         ltk_error err;
          182         for (size_t i = 0; i < box->num_widgets; i++) {
          183                 ptr = box->widgets[i];
          184                 ptr->parent = NULL;
          185                 if (!shallow)
          186                         ltk_widget_destroy(ptr, shallow, &err);
          187         }
          188         ltk_free(box->widgets);
          189         box->sc->widget.parent = NULL;
          190         /* FIXME: this is a bit weird because sc isn't a normal widget
          191            (it isn't in the widget hash) */
          192         ltk_widget_destroy(&box->sc->widget, 0, &err);
          193         ltk_free(box);
          194 }
          195 
          196 /* FIXME: Make this function name more consistent */
          197 /* FIXME: The widget positions are set with the old scrollbar->cur_pos, before the
          198    virtual_size is set - this can cause problems when a widget changes its size
          199    (in the scrolled direction) when resized. */
          200 /* FIXME: avoid complete recalculation when just scrolling (only position updated) */
          201 static void
          202 ltk_recalculate_box(ltk_widget *self) {
          203         ltk_box *box = (ltk_box *)self;
          204         ltk_widget *ptr;
          205         ltk_rect *sc_rect = &box->sc->widget.lrect;
          206         int cur_pos = 0;
          207         if (box->orient == LTK_HORIZONTAL)
          208                 sc_rect->h = box->sc->widget.ideal_h;
          209         else
          210                 sc_rect->w = box->sc->widget.ideal_w;
          211         for (size_t i = 0; i < box->num_widgets; i++) {
          212                 ptr = box->widgets[i];
          213                 if (box->orient == LTK_HORIZONTAL) {
          214                         ptr->lrect.x = cur_pos - box->sc->cur_pos;
          215                         if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM)
          216                                 ptr->lrect.h = box->widget.lrect.h - sc_rect->h;
          217                         if (ptr->sticky & LTK_STICKY_TOP)
          218                                 ptr->lrect.y = 0;
          219                         else if (ptr->sticky & LTK_STICKY_BOTTOM)
          220                                 ptr->lrect.y = box->widget.lrect.h - ptr->lrect.h - sc_rect->h;
          221                         else
          222                                 ptr->lrect.y = (box->widget.lrect.h - ptr->lrect.h) / 2;
          223                         cur_pos += ptr->lrect.w;
          224                 } else {
          225                         ptr->lrect.y = cur_pos - box->sc->cur_pos;
          226                         if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT)
          227                                 ptr->lrect.w = box->widget.lrect.w - sc_rect->w;
          228                         if (ptr->sticky & LTK_STICKY_LEFT)
          229                                 ptr->lrect.x = 0;
          230                         else if (ptr->sticky & LTK_STICKY_RIGHT)
          231                                 ptr->lrect.x = box->widget.lrect.w - ptr->lrect.w - sc_rect->w;
          232                         else
          233                                 ptr->lrect.x = (box->widget.lrect.w - ptr->lrect.w) / 2;
          234                         cur_pos += ptr->lrect.h;
          235                 }
          236                 ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect);
          237                 ltk_widget_resize(ptr);
          238         }
          239         ltk_scrollbar_set_virtual_size(box->sc, cur_pos);
          240         if (box->orient == LTK_HORIZONTAL) {
          241                 sc_rect->x = 0;
          242                 sc_rect->y = box->widget.lrect.h - sc_rect->h;
          243                 sc_rect->w = box->widget.lrect.w;
          244         } else {
          245                 sc_rect->x = box->widget.lrect.w - sc_rect->w;
          246                 sc_rect->y = 0;
          247                 sc_rect->h = box->widget.lrect.h;
          248         }
          249         *sc_rect = ltk_rect_intersect(*sc_rect, (ltk_rect){0, 0, box->widget.lrect.w, box->widget.lrect.h});
          250         box->sc->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, *sc_rect);
          251         ltk_widget_resize((ltk_widget *)box->sc);
          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 
          261 static void
          262 ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) {
          263         ltk_box *box = (ltk_box *)self;
          264         short size_changed = 0;
          265         /* This is always reset here - if it needs to be changed,
          266            the resize function called by the last child_size_change
          267            function will fix it */
          268         /* Note: This seems a bit weird, but if each widget set its rect itself,
          269            that would also lead to weird things. For instance, if a butten is
          270            added to after a box after being ungridded, and its rect was changed
          271            by the grid (e.g. because of a column weight), who should reset the
          272            rect if it doesn't have sticky set? Of course, the resize function
          273            could also set all widgets even if they don't have any sticky
          274            settings, but there'd probably be some catch as well. */
          275         /* FIXME: the same comment as in grid.c applies */
          276         int orig_w = widget->lrect.w;
          277         int orig_h = widget->lrect.h;
          278         widget->lrect.w = widget->ideal_w;
          279         widget->lrect.h = widget->ideal_h;
          280         int sc_w = box->sc->widget.lrect.w;
          281         int sc_h = box->sc->widget.lrect.h;
          282         if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h > box->widget.ideal_h) {
          283                 box->widget.ideal_h = widget->ideal_h + sc_h;
          284                 size_changed = 1;
          285         } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w > box->widget.ideal_h) {
          286                 box->widget.ideal_w = widget->ideal_w + sc_w;
          287                 size_changed = 1;
          288         }
          289 
          290         if (size_changed && box->widget.parent && box->widget.parent->vtable->child_size_change)
          291                 box->widget.parent->vtable->child_size_change(box->widget.parent, (ltk_widget *)box);
          292         else
          293                 ltk_recalculate_box((ltk_widget *)box);
          294         if (orig_w != widget->lrect.w || orig_h != widget->lrect.h)
          295                 ltk_widget_resize(widget);
          296 }
          297 
          298 static int
          299 ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, ltk_sticky_mask sticky, ltk_error *err) {
          300         if (widget->parent) {
          301                 err->type = ERR_WIDGET_IN_CONTAINER;
          302                 return 1;
          303         }
          304         if (box->num_widgets >= box->num_alloc) {
          305                 size_t new_size = box->num_alloc > 0 ? box->num_alloc * 2 : 4;
          306                 ltk_widget **new = ltk_realloc(box->widgets, new_size * sizeof(ltk_widget *));
          307                 box->num_alloc = new_size;
          308                 box->widgets = new;
          309         }
          310 
          311         int sc_w = box->sc->widget.lrect.w;
          312         int sc_h = box->sc->widget.lrect.h;
          313 
          314         box->widgets[box->num_widgets++] = widget;
          315         if (box->orient == LTK_HORIZONTAL) {
          316                 box->widget.ideal_w += widget->ideal_w;
          317                 if (widget->ideal_h + sc_h > box->widget.ideal_h)
          318                         box->widget.ideal_h = widget->ideal_h + sc_h;
          319         } else {
          320                 box->widget.ideal_h += widget->ideal_h;
          321                 if (widget->ideal_w + sc_w > box->widget.ideal_w)
          322                         box->widget.ideal_w = widget->ideal_w + sc_w;
          323         }
          324         widget->parent = (ltk_widget *)box;
          325         widget->sticky = sticky;
          326         ltk_box_child_size_change((ltk_widget *)box, widget);
          327         ltk_window_invalidate_widget_rect(window, &box->widget);
          328 
          329         return 0;
          330 }
          331 
          332 static int
          333 ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err) {
          334         ltk_box *box = (ltk_box *)self;
          335         int sc_w = box->sc->widget.lrect.w;
          336         int sc_h = box->sc->widget.lrect.h;
          337         if (widget->parent != (ltk_widget *)box) {
          338                 err->type = ERR_WIDGET_NOT_IN_CONTAINER;
          339                 return 1;
          340         }
          341         widget->parent = NULL;
          342         for (size_t i = 0; i < box->num_widgets; i++) {
          343                 if (box->widgets[i] == widget) {
          344                         if (i < box->num_widgets - 1)
          345                                 memmove(box->widgets + i, box->widgets + i + 1,
          346                                     (box->num_widgets - i - 1) * sizeof(ltk_widget *));
          347                         box->num_widgets--;
          348                         ltk_window_invalidate_widget_rect(widget->window, &box->widget);
          349                         /* search for new ideal width/height */
          350                         /* FIXME: make this all a bit nicer and break the lines better */
          351                         /* FIXME: other part of ideal size not updated */
          352                         if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h == box->widget.ideal_h) {
          353                                 box->widget.ideal_h = 0;
          354                                 for (size_t j = 0; j < box->num_widgets; j++) {
          355                                         if (box->widgets[j]->ideal_h + sc_h > box->widget.ideal_h)
          356                                                 box->widget.ideal_h = box->widgets[j]->ideal_h + sc_h;
          357                                 }
          358                                 if (box->widget.parent)
          359                                         ltk_widget_resize(box->widget.parent);
          360                         } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w == box->widget.ideal_w) {
          361                                 box->widget.ideal_w = 0;
          362                                 for (size_t j = 0; j < box->num_widgets; j++) {
          363                                         if (box->widgets[j]->ideal_w + sc_w > box->widget.ideal_w)
          364                                                 box->widget.ideal_w = box->widgets[j]->ideal_w + sc_w;
          365                                 }
          366                                 if (box->widget.parent)
          367                                         ltk_widget_resize(box->widget.parent);
          368                         }
          369                         return 0;
          370                 }
          371         }
          372 
          373         err->type = ERR_WIDGET_NOT_IN_CONTAINER;
          374         return 1;
          375 }
          376 
          377 /* FIXME: maybe come up with a more efficient method */
          378 static ltk_widget *
          379 ltk_box_nearest_child(ltk_widget *self, ltk_rect rect) {
          380         ltk_box *box = (ltk_box *)self;
          381         ltk_widget *minw = NULL;
          382         int min_dist = INT_MAX;
          383         int cx = rect.x + rect.w / 2;
          384         int cy = rect.y + rect.h / 2;
          385         ltk_rect r;
          386         int dist;
          387         for (size_t i = 0; i < box->num_widgets; i++) {
          388                 r = box->widgets[i]->lrect;
          389                 dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy);
          390                 if (dist < min_dist) {
          391                         min_dist = dist;
          392                         minw = box->widgets[i];
          393                 }
          394         }
          395         return minw;
          396 }
          397 
          398 static ltk_widget *
          399 ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
          400         ltk_box *box = (ltk_box *)self;
          401         if (box->orient == LTK_VERTICAL)
          402                 return NULL;
          403         return ltk_box_prev_child(self, widget);
          404 }
          405 
          406 static ltk_widget *
          407 ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
          408         ltk_box *box = (ltk_box *)self;
          409         if (box->orient == LTK_VERTICAL)
          410                 return NULL;
          411         return ltk_box_next_child(self, widget);
          412 }
          413 
          414 static ltk_widget *
          415 ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
          416         ltk_box *box = (ltk_box *)self;
          417         if (box->orient == LTK_HORIZONTAL)
          418                 return NULL;
          419         return ltk_box_prev_child(self, widget);
          420 }
          421 
          422 static ltk_widget *
          423 ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
          424         ltk_box *box = (ltk_box *)self;
          425         if (box->orient == LTK_HORIZONTAL)
          426                 return NULL;
          427         return ltk_box_next_child(self, widget);
          428 }
          429 
          430 static ltk_widget *
          431 ltk_box_prev_child(ltk_widget *self, ltk_widget *child) {
          432         ltk_box *box = (ltk_box *)self;
          433         for (size_t i = box->num_widgets; i-- > 0;) {
          434                 if (box->widgets[i] == child)
          435                         return i > 0 ? box->widgets[i-1] : NULL;
          436         }
          437         return NULL;
          438 }
          439 
          440 static ltk_widget *
          441 ltk_box_next_child(ltk_widget *self, ltk_widget *child) {
          442         ltk_box *box = (ltk_box *)self;
          443         for (size_t i = 0; i < box->num_widgets; i++) {
          444                 if (box->widgets[i] == child)
          445                         return i < box->num_widgets - 1 ? box->widgets[i+1] : NULL;
          446         }
          447         return NULL;
          448 }
          449 
          450 static ltk_widget *
          451 ltk_box_first_child(ltk_widget *self) {
          452         ltk_box *box = (ltk_box *)self;
          453         return box->num_widgets > 0 ? box->widgets[0] : NULL;
          454 }
          455 
          456 static ltk_widget *
          457 ltk_box_last_child(ltk_widget *self) {
          458         ltk_box *box = (ltk_box *)self;
          459         return box->num_widgets > 0 ? box->widgets[box->num_widgets-1] : NULL;
          460 }
          461 
          462 static void
          463 ltk_box_scroll(ltk_widget *self) {
          464         ltk_box *box = (ltk_box *)self;
          465         ltk_recalculate_box(self);
          466         ltk_window_invalidate_widget_rect(box->widget.window, &box->widget);
          467 }
          468 
          469 static ltk_widget *
          470 ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) {
          471         ltk_box *box = (ltk_box *)self;
          472         if (ltk_collide_rect(box->sc->widget.crect, x, y))
          473                 return (ltk_widget *)box->sc;
          474         for (size_t i = 0; i < box->num_widgets; i++) {
          475                 if (ltk_collide_rect(box->widgets[i]->crect, x, y))
          476                         return box->widgets[i];
          477         }
          478         return NULL;
          479 }
          480 
          481 static int
          482 ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
          483         ltk_box *box = (ltk_box *)self;
          484         if (event->dy) {
          485                 /* FIXME: horizontal scrolling, etc. */
          486                 /* FIXME: configure scrollstep */
          487                 int delta = event->dy * -15;
          488                 ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0);
          489                 ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y);
          490                 ltk_window_fake_motion_event(self->window, glob.x, glob.y);
          491                 return 1;
          492         }
          493         return 0;
          494 }
          495 
          496 /* box <box id> add <widget id> [sticky] */
          497 static int
          498 ltk_box_cmd_add(
          499     ltk_window *window,
          500     ltk_box *box,
          501     ltk_cmd_token *tokens,
          502     size_t num_tokens,
          503     ltk_error *err) {
          504         ltk_cmdarg_parseinfo cmd[] = {
          505                 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0},
          506                 {.type = CMDARG_STICKY, .optional = 1},
          507         };
          508         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          509                 return 1;
          510         if (ltk_box_add(window, cmd[0].val.widget, box, cmd[1].initialized ? cmd[1].val.sticky : 0, err)) {
          511                 err->arg = 0;
          512                 return 1;
          513         }
          514         return 0;
          515 }
          516 
          517 /* box <box id> remove <widget id> */
          518 static int
          519 ltk_box_cmd_remove(
          520     ltk_window *window,
          521     ltk_box *box,
          522     ltk_cmd_token *tokens,
          523     size_t num_tokens,
          524     ltk_error *err) {
          525         ltk_cmdarg_parseinfo cmd[] = {
          526                 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0},
          527         };
          528         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          529                 return 1;
          530         if (ltk_box_remove(cmd[0].val.widget, (ltk_widget *)box, err)) {
          531                 err->arg = 0;
          532                 return 1;
          533         }
          534         return 0;
          535 }
          536 
          537 /* box <box id> create <orientation> */
          538 static int
          539 ltk_box_cmd_create(
          540     ltk_window *window,
          541     ltk_box *box_unneeded,
          542     ltk_cmd_token *tokens,
          543     size_t num_tokens,
          544     ltk_error *err) {
          545         (void)box_unneeded;
          546         ltk_cmdarg_parseinfo cmd[] = {
          547                 {.type = CMDARG_IGNORE, .optional = 0},
          548                 {.type = CMDARG_STRING, .optional = 0},
          549                 {.type = CMDARG_IGNORE, .optional = 0},
          550                 {.type = CMDARG_ORIENTATION, .optional = 0},
          551         };
          552         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          553                 return 1;
          554         if (!ltk_widget_id_free(cmd[1].val.str)) {
          555                 err->type = ERR_WIDGET_ID_IN_USE;
          556                 err->arg = 1;
          557                 return 1;
          558         }
          559         ltk_box *box = ltk_box_create(window, cmd[1].val.str, cmd[3].val.orient);
          560         ltk_set_widget((ltk_widget *)box, cmd[1].val.str);
          561 
          562         return 0;
          563 }
          564 
          565 static struct box_cmd {
          566         char *name;
          567         int (*func)(ltk_window *, ltk_box *, ltk_cmd_token *, size_t, ltk_error *);
          568         int needs_all;
          569 } box_cmds[] = {
          570         {"add", &ltk_box_cmd_add, 0},
          571         {"create", &ltk_box_cmd_create, 1},
          572         {"remove", &ltk_box_cmd_remove, 0},
          573 };
          574 
          575 GEN_CMD_HELPERS(ltk_box_cmd, LTK_WIDGET_BOX, ltk_box, box_cmds, struct box_cmd)