URI: 
       tgrid.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
       ---
       tgrid.c (24579B)
       ---
            1 /* FIXME: sometimes, resizing doesn't work properly when running test.sh */
            2 
            3 /*
            4  * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
            5  *
            6  * Permission to use, copy, modify, and/or distribute this software for any
            7  * purpose with or without fee is hereby granted, provided that the above
            8  * copyright notice and this permission notice appear in all copies.
            9  *
           10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
           11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
           12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
           13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
           14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
           15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
           16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
           17  */
           18 
           19 /* TODO: make ungrid function also adjust static row/column width/height
           20    -> also, how should the grid deal with a widget spanning over multiple
           21       rows/columns with static size - if all are static, it could just
           22       divide the widget size (it would complicate things, though), but
           23       what should happen if some rows/columns under the span do have a
           24       positive weight? */
           25 
           26 #include <stdio.h>
           27 #include <stdlib.h>
           28 #include <string.h>
           29 #include <stdarg.h>
           30 #include <stdint.h>
           31 
           32 #include "event.h"
           33 #include "memory.h"
           34 #include "color.h"
           35 #include "rect.h"
           36 #include "widget.h"
           37 #include "ltk.h"
           38 #include "util.h"
           39 #include "grid.h"
           40 #include "cmd.h"
           41 
           42 static void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight);
           43 static void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight);
           44 static void ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
           45 static ltk_grid *ltk_grid_create(ltk_window *window, const char *id,
           46     int rows, int columns);
           47 static void ltk_grid_destroy(ltk_widget *self, int shallow);
           48 static void ltk_recalculate_grid(ltk_widget *self);
           49 static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget);
           50 static int ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
           51     int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err);
           52 static int ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err);
           53 static int ltk_grid_find_nearest_column(ltk_grid *grid, int x);
           54 static int ltk_grid_find_nearest_row(ltk_grid *grid, int y);
           55 static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y);
           56 
           57 static ltk_widget *ltk_grid_prev_child(ltk_widget *self, ltk_widget *child);
           58 static ltk_widget *ltk_grid_next_child(ltk_widget *self, ltk_widget *child);
           59 static ltk_widget *ltk_grid_first_child(ltk_widget *self);
           60 static ltk_widget *ltk_grid_last_child(ltk_widget *self);
           61 
           62 static ltk_widget *ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect);
           63 static ltk_widget *ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget);
           64 static ltk_widget *ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget);
           65 static ltk_widget *ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget);
           66 static ltk_widget *ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget);
           67 
           68 static struct ltk_widget_vtable vtable = {
           69         .draw = &ltk_grid_draw,
           70         .destroy = &ltk_grid_destroy,
           71         .resize = &ltk_recalculate_grid,
           72         .hide = NULL,
           73         .change_state = NULL,
           74         .child_size_change = &ltk_grid_child_size_change,
           75         .remove_child = &ltk_grid_ungrid,
           76         .mouse_press = NULL,
           77         .mouse_scroll = NULL,
           78         .mouse_release = NULL,
           79         .motion_notify = NULL,
           80         .get_child_at_pos = &ltk_grid_get_child_at_pos,
           81         .mouse_leave = NULL,
           82         .mouse_enter = NULL,
           83         .key_press = NULL,
           84         .key_release = NULL,
           85         .prev_child = &ltk_grid_prev_child,
           86         .next_child = &ltk_grid_next_child,
           87         .first_child = &ltk_grid_first_child,
           88         .last_child = &ltk_grid_last_child,
           89         .nearest_child = &ltk_grid_nearest_child,
           90         .nearest_child_left = &ltk_grid_nearest_child_left,
           91         .nearest_child_right = &ltk_grid_nearest_child_right,
           92         .nearest_child_above = &ltk_grid_nearest_child_above,
           93         .nearest_child_below = &ltk_grid_nearest_child_below,
           94         .type = LTK_WIDGET_GRID,
           95         .flags = 0,
           96 };
           97 
           98 static int ltk_grid_cmd_add(
           99     ltk_window *window,
          100     ltk_grid *grid,
          101     ltk_cmd_token *tokens,
          102     size_t num_tokens,
          103     ltk_error *err);
          104 static int ltk_grid_cmd_ungrid(
          105     ltk_window *window,
          106     ltk_grid *grid,
          107     ltk_cmd_token *tokens,
          108     size_t num_tokens,
          109     ltk_error *err);
          110 static int ltk_grid_cmd_create(
          111     ltk_window *window,
          112     ltk_grid *grid,
          113     ltk_cmd_token *tokens,
          114     size_t num_tokens,
          115     ltk_error *err);
          116 static int ltk_grid_cmd_set_row_weight(
          117     ltk_window *window,
          118     ltk_grid *grid,
          119     ltk_cmd_token *tokens,
          120     size_t num_tokens,
          121     ltk_error *err);
          122 static int ltk_grid_cmd_set_column_weight(
          123     ltk_window *window,
          124     ltk_grid *grid,
          125     ltk_cmd_token *tokens,
          126     size_t num_tokens,
          127     ltk_error *err);
          128 
          129 static void
          130 ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight) {
          131         grid->row_weights[row] = weight;
          132         ltk_recalculate_grid((ltk_widget *)grid);
          133 }
          134 
          135 static void
          136 ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight) {
          137         grid->column_weights[column] = weight;
          138         ltk_recalculate_grid((ltk_widget *)grid);
          139 }
          140 
          141 static void
          142 ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
          143         ltk_grid *grid = (ltk_grid *)self;
          144         int i;
          145         ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip);
          146         for (i = 0; i < grid->rows * grid->columns; i++) {
          147                 if (!grid->widget_grid[i])
          148                         continue;
          149                 ltk_widget *ptr = grid->widget_grid[i];
          150                 int max_w = grid->column_pos[ptr->column + ptr->column_span] - grid->column_pos[ptr->column];
          151                 int max_h = grid->row_pos[ptr->row + ptr->row_span] - grid->row_pos[ptr->row];
          152                 ltk_rect r = ltk_rect_intersect(
          153                     (ltk_rect){grid->column_pos[ptr->column], grid->row_pos[ptr->row], max_w, max_h}, real_clip
          154                 );
          155                 ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, r));
          156         }
          157 }
          158 
          159 static ltk_grid *
          160 ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
          161         ltk_grid *grid = ltk_malloc(sizeof(ltk_grid));
          162 
          163         ltk_fill_widget_defaults(&grid->widget, id, window, &vtable, 0, 0);
          164 
          165         grid->rows = rows;
          166         grid->columns = columns;
          167         grid->widget_grid = ltk_malloc(rows * columns * sizeof(ltk_widget));
          168         grid->row_heights = ltk_malloc(rows * sizeof(int));
          169         grid->column_widths = ltk_malloc(rows * sizeof(int));
          170         grid->row_weights = ltk_malloc(rows * sizeof(int));
          171         grid->column_weights = ltk_malloc(columns * sizeof(int));
          172         /* Positions have one extra for the end */
          173         grid->row_pos = ltk_malloc((rows + 1) * sizeof(int));
          174         grid->column_pos = ltk_malloc((columns + 1) * sizeof(int));
          175         /* FIXME: wow, that's horrible, this should just use memset */
          176         int i;
          177         for (i = 0; i < rows; i++) {
          178                 grid->row_heights[i] = 0;
          179                 grid->row_weights[i] = 0;
          180                 grid->row_pos[i] = 0;
          181         }
          182         grid->row_pos[rows] = 0;
          183         for (i = 0; i < columns; i++) {
          184                 grid->column_widths[i] = 0;
          185                 grid->column_weights[i] = 0;
          186                 grid->column_pos[i] = 0;
          187         }
          188         grid->column_pos[columns] = 0;
          189         for (i = 0; i < rows * columns; i++) {
          190                 grid->widget_grid[i] = NULL;
          191         }
          192 
          193         ltk_recalculate_grid((ltk_widget *)grid);
          194         return grid;
          195 }
          196 
          197 static void
          198 ltk_grid_destroy(ltk_widget *self, int shallow) {
          199         ltk_grid *grid = (ltk_grid *)self;
          200         ltk_widget *ptr;
          201         ltk_error err;
          202         for (int i = 0; i < grid->rows * grid->columns; i++) {
          203                 if (grid->widget_grid[i]) {
          204                         ptr = grid->widget_grid[i];
          205                         ptr->parent = NULL;
          206                         if (!shallow) {
          207                                 /* required to avoid freeing a widget multiple times
          208                                    if row_span or column_span is not 1 */
          209                                 for (int r = ptr->row; r < ptr->row + ptr->row_span; r++) {
          210                                         for (int c = ptr->column; c < ptr->column + ptr->column_span; c++) {
          211                                                 grid->widget_grid[r * grid->columns + c] = NULL;
          212                                         }
          213                                 }
          214                                 ltk_widget_destroy(ptr, shallow, &err);
          215                         }
          216                 }
          217         }
          218         ltk_free(grid->widget_grid);
          219         ltk_free(grid->row_heights);
          220         ltk_free(grid->column_widths);
          221         ltk_free(grid->row_weights);
          222         ltk_free(grid->column_weights);
          223         ltk_free(grid->row_pos);
          224         ltk_free(grid->column_pos);
          225         ltk_free(grid);
          226 }
          227 
          228 static void
          229 ltk_recalculate_grid(ltk_widget *self) {
          230         ltk_grid *grid = (ltk_grid *)self;
          231         unsigned int height_static = 0, width_static = 0;
          232         unsigned int total_row_weight = 0, total_column_weight = 0;
          233         float height_unit = 0, width_unit = 0;
          234         unsigned int currentx = 0, currenty = 0;
          235         int i, j;
          236         for (i = 0; i < grid->rows; i++) {
          237                 total_row_weight += grid->row_weights[i];
          238                 if (grid->row_weights[i] == 0) {
          239                         height_static += grid->row_heights[i];
          240                 }
          241         }
          242         for (i = 0; i < grid->columns; i++) {
          243                 total_column_weight += grid->column_weights[i];
          244                 if (grid->column_weights[i] == 0) {
          245                         width_static += grid->column_widths[i];
          246                 }
          247         }
          248         /* FIXME: what should be done when static height or width is larger than grid? */
          249         if (total_row_weight > 0) {
          250                 height_unit = (float) (grid->widget.lrect.h - height_static) / (float) total_row_weight;
          251         }
          252         if (total_column_weight > 0) {
          253                 width_unit = (float) (grid->widget.lrect.w - width_static) / (float) total_column_weight;
          254         }
          255         for (i = 0; i < grid->rows; i++) {
          256                 grid->row_pos[i] = currenty;
          257                 if (grid->row_weights[i] > 0) {
          258                         grid->row_heights[i] = grid->row_weights[i] * height_unit;
          259                 }
          260                 currenty += grid->row_heights[i];
          261         }
          262         grid->row_pos[grid->rows] = currenty;
          263         for (i = 0; i < grid->columns; i++) {
          264                 grid->column_pos[i] = currentx;
          265                 if (grid->column_weights[i] > 0) {
          266                         grid->column_widths[i] = grid->column_weights[i] * width_unit;
          267                 }
          268                 currentx += grid->column_widths[i];
          269         }
          270         grid->column_pos[grid->columns] = currentx;
          271         /*int orig_width, orig_height;*/
          272         int end_column, end_row;
          273         for (i = 0; i < grid->rows; i++) {
          274                 for (j = 0; j < grid->columns; j++) {
          275                         ltk_widget *ptr = grid->widget_grid[i * grid->columns + j];
          276                         if (!ptr || ptr->row != i || ptr->column != j)
          277                                 continue;
          278                         /*orig_width = ptr->lrect.w;
          279                         orig_height = ptr->lrect.h;*/
          280                         ptr->lrect.w = ptr->ideal_w;
          281                         ptr->lrect.h = ptr->ideal_h;
          282                         end_row = i + ptr->row_span;
          283                         end_column = j + ptr->column_span;
          284                         int max_w = grid->column_pos[end_column] - grid->column_pos[j];
          285                         int max_h = grid->row_pos[end_row] - grid->row_pos[i];
          286                         int stretch_width = (ptr->sticky & LTK_STICKY_LEFT) && (ptr->sticky & LTK_STICKY_RIGHT);
          287                         int shrink_width = (ptr->sticky & LTK_STICKY_SHRINK_WIDTH) && ptr->lrect.w > max_w;
          288                         int stretch_height = (ptr->sticky & LTK_STICKY_TOP) && (ptr->sticky & LTK_STICKY_BOTTOM);
          289                         int shrink_height = (ptr->sticky & LTK_STICKY_SHRINK_HEIGHT) && ptr->lrect.h > max_h;
          290                         if (stretch_width || shrink_width)
          291                                 ptr->lrect.w = max_w;
          292                         if (stretch_height || shrink_height)
          293                                 ptr->lrect.h = max_h;
          294                         if (ptr->sticky & LTK_STICKY_PRESERVE_ASPECT_RATIO) {
          295                                 if (!stretch_width && !shrink_width) {
          296                                         ptr->lrect.w = (int)(((double)ptr->lrect.h / ptr->ideal_h) * ptr->ideal_w);
          297                                 } else if (!stretch_height && !shrink_height) {
          298                                         ptr->lrect.h = (int)(((double)ptr->lrect.w / ptr->ideal_w) * ptr->ideal_h);
          299                                 } else {
          300                                         double scale_w = (double)ptr->lrect.w / ptr->ideal_w;
          301                                         double scale_h = (double)ptr->lrect.h / ptr->ideal_h;
          302                                         if (scale_w * ptr->ideal_h > ptr->lrect.h)
          303                                                 ptr->lrect.w = (int)(scale_h * ptr->ideal_w);
          304                                         else if (scale_h * ptr->ideal_w > ptr->lrect.w)
          305                                                 ptr->lrect.h = (int)(scale_w * ptr->ideal_h);
          306                                 }
          307                         }
          308                         /* FIXME: Figure out a better system for this - it would be nice to make it more
          309                            efficient by not doing anything if nothing changed, but that doesn't work when
          310                            this function was called because of a child_size_change. In that case, if a
          311                            container widget is nested inside another container widget and another widget
          312                            inside the nested container sends a child_size_change but the toplevel container
          313                            doesn't change the size of the container, the position/size of the widget at the
          314                            bottom of the hierarchy will never be updated. That's why updates are forced
          315                            here even if seemingly nothing changed, but there probably is a better way. */
          316                         /*if (orig_width != ptr->lrect.w || orig_height != ptr->lrect.h)*/
          317                                 ltk_widget_resize(ptr);
          318 
          319                         /* the "default" case needs to come first because the widget may be stretched
          320                            with aspect ratio preserving, and in that case it should still be centered */
          321                         if (stretch_width || !(ptr->sticky & (LTK_STICKY_RIGHT|LTK_STICKY_LEFT))) {
          322                                 ptr->lrect.x = grid->column_pos[j] + (grid->column_pos[end_column] - grid->column_pos[j] - ptr->lrect.w) / 2;
          323                         } else if (ptr->sticky & LTK_STICKY_RIGHT) {
          324                                 ptr->lrect.x = grid->column_pos[end_column] - ptr->lrect.w;
          325                         } else if (ptr->sticky & LTK_STICKY_LEFT) {
          326                                 ptr->lrect.x = grid->column_pos[j];
          327                         }
          328 
          329                         if (stretch_height || !(ptr->sticky & (LTK_STICKY_TOP|LTK_STICKY_BOTTOM))) {
          330                                 ptr->lrect.y = grid->row_pos[i] + (grid->row_pos[end_row] - grid->row_pos[i] - ptr->lrect.h) / 2;
          331                         } else if (ptr->sticky & LTK_STICKY_BOTTOM) {
          332                                 ptr->lrect.y = grid->row_pos[end_row] - ptr->lrect.h;
          333                         } else if (ptr->sticky & LTK_STICKY_TOP) {
          334                                 ptr->lrect.y = grid->row_pos[i];
          335                         }
          336                         /* intersect both with the grid rect and with the rect of the covered cells since there may be
          337                            weird cases where the layout doesn't work properly and the cells are partially outside the grid */
          338                         ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect);
          339                         ptr->crect = ltk_rect_intersect((ltk_rect){grid->column_pos[j], grid->row_pos[i], max_w, max_h}, ptr->crect);
          340                 }
          341         }
          342 }
          343 
          344 /* FIXME: Maybe add debug stuff to check that grid is actually parent of widget */
          345 static void
          346 ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) {
          347         ltk_grid *grid = (ltk_grid *)self;
          348         short size_changed = 0;
          349         int orig_w = widget->lrect.w;
          350         int orig_h = widget->lrect.h;
          351         widget->lrect.w = widget->ideal_w;
          352         widget->lrect.h = widget->ideal_h;
          353         if (grid->column_weights[widget->column] == 0 &&
          354             widget->lrect.w > grid->column_widths[widget->column]) {
          355                 grid->widget.ideal_w += widget->lrect.w - grid->column_widths[widget->column];
          356                 grid->column_widths[widget->column] = widget->lrect.w;
          357                 size_changed = 1;
          358         }
          359         if (grid->row_weights[widget->row] == 0 &&
          360             widget->lrect.h > grid->row_heights[widget->row]) {
          361                 grid->widget.ideal_h += widget->lrect.h - grid->row_heights[widget->row];
          362                 grid->row_heights[widget->row] = widget->lrect.h;
          363                 size_changed = 1;
          364         }
          365         if (size_changed && grid->widget.parent && grid->widget.parent->vtable->child_size_change)
          366                 grid->widget.parent->vtable->child_size_change(grid->widget.parent, (ltk_widget *)grid);
          367         else
          368                 ltk_recalculate_grid((ltk_widget *)grid);
          369         if (widget->lrect.w != orig_w || widget->lrect.h != orig_h)
          370                 ltk_widget_resize(widget);
          371 }
          372 
          373 /* FIXME: Check if widget already exists at position */
          374 static int
          375 ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
          376     int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err) {
          377         if (widget->parent) {
          378                 err->type = ERR_WIDGET_IN_CONTAINER;
          379                 return 1;
          380         }
          381         if (row + row_span > grid->rows || column + column_span > grid->columns) {
          382                 err->type = ERR_GRID_INVALID_POSITION;
          383                 return 1;
          384         }
          385         widget->sticky = sticky;
          386         widget->row = row;
          387         widget->column = column;
          388         widget->row_span = row_span;
          389         widget->column_span = column_span;
          390         for (int i = row; i < row + row_span; i++) {
          391                 for (int j = column; j < column + column_span; j++) {
          392                         grid->widget_grid[i * grid->columns + j] = widget;
          393                 }
          394         }
          395         widget->parent = (ltk_widget *)grid;
          396         ltk_grid_child_size_change((ltk_widget *)grid, widget);
          397         ltk_window_invalidate_widget_rect(window, &grid->widget);
          398 
          399         return 0;
          400 }
          401 
          402 static int
          403 ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err) {
          404         ltk_grid *grid = (ltk_grid *)self;
          405         if (widget->parent != (ltk_widget *)grid) {
          406                 err->type = ERR_WIDGET_NOT_IN_CONTAINER;
          407                 return 1;
          408         }
          409         widget->parent = NULL;
          410         for (int i = widget->row; i < widget->row + widget->row_span; i++) {
          411                 for (int j = widget->column; j < widget->column + widget->column_span; j++) {
          412                         grid->widget_grid[i * grid->columns + j] = NULL;
          413                 }
          414         }
          415         ltk_window_invalidate_widget_rect(self->window, &grid->widget);
          416 
          417         return 0;
          418 }
          419 
          420 static int
          421 ltk_grid_find_nearest_column(ltk_grid *grid, int x) {
          422         int i;
          423         for (i = 0; i < grid->columns; i++) {
          424                 if (grid->column_pos[i] <= x && grid->column_pos[i + 1] >= x) {
          425                         return i;
          426                 }
          427         }
          428         return -1;
          429 }
          430 
          431 static int
          432 ltk_grid_find_nearest_row(ltk_grid *grid, int y) {
          433         int i;
          434         for (i = 0; i < grid->rows; i++) {
          435                 if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) {
          436                         return i;
          437                 }
          438         }
          439         return -1;
          440 }
          441 
          442 /* FIXME: maybe come up with a more efficient method */
          443 static ltk_widget *
          444 ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) {
          445         ltk_grid *grid = (ltk_grid *)self;
          446         ltk_widget *minw = NULL;
          447         int min_dist = INT_MAX;
          448         int cx = rect.x + rect.w / 2;
          449         int cy = rect.y + rect.h / 2;
          450         ltk_rect r;
          451         int dist;
          452         /* FIXME: rows and columns shouldn't be int */
          453         for (size_t i = 0; i < (size_t)(grid->rows * grid->columns); i++) {
          454                 if (!grid->widget_grid[i])
          455                         continue;
          456                 /* FIXME: this checks widgets with row/columnspan > 1 multiple times */
          457                 r = grid->widget_grid[i]->lrect;
          458                 dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy);
          459                 if (dist < min_dist) {
          460                         min_dist = dist;
          461                         minw = grid->widget_grid[i];
          462                 }
          463         }
          464         return minw;
          465 }
          466 
          467 /* FIXME: assertions to check that widget row/column are legal */
          468 static ltk_widget *
          469 ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
          470         ltk_grid *grid = (ltk_grid *)self;
          471         unsigned int col = widget->column;
          472         ltk_widget *cur = NULL;
          473         while (col-- > 0) {
          474                 cur = grid->widget_grid[widget->row * grid->columns + col];
          475                 if (cur && cur != widget)
          476                         return cur;
          477         }
          478         return NULL;
          479 }
          480 
          481 static ltk_widget *
          482 ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
          483         ltk_grid *grid = (ltk_grid *)self;
          484         ltk_widget *cur = NULL;
          485         for (int col = widget->column + 1; col < grid->columns; col++) {
          486                 cur = grid->widget_grid[widget->row * grid->columns + col];
          487                 if (cur && cur != widget)
          488                         return cur;
          489         }
          490         return NULL;
          491 }
          492 
          493 static ltk_widget *
          494 ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
          495         ltk_grid *grid = (ltk_grid *)self;
          496         unsigned int row = widget->row;
          497         ltk_widget *cur = NULL;
          498         while (row-- > 0) {
          499                 cur = grid->widget_grid[row * grid->columns + widget->column];
          500                 if (cur && cur != widget)
          501                         return cur;
          502         }
          503         return NULL;
          504 }
          505 
          506 static ltk_widget *
          507 ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
          508         ltk_grid *grid = (ltk_grid *)self;
          509         ltk_widget *cur = NULL;
          510         for (int row = widget->row + 1; row < grid->rows; row++) {
          511                 cur = grid->widget_grid[row * grid->columns + widget->column];
          512                 if (cur && cur != widget)
          513                         return cur;
          514         }
          515         return NULL;
          516 }
          517 
          518 static ltk_widget *
          519 ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) {
          520         ltk_grid *grid = (ltk_grid *)self;
          521         int row = ltk_grid_find_nearest_row(grid, y);
          522         int column = ltk_grid_find_nearest_column(grid, x);
          523         if (row == -1 || column == -1)
          524                 return 0;
          525         ltk_widget *ptr = grid->widget_grid[row * grid->columns + column];
          526         if (ptr && ltk_collide_rect(ptr->crect, x, y))
          527                 return ptr;
          528         return NULL;
          529 }
          530 
          531 static ltk_widget *
          532 ltk_grid_prev_child(ltk_widget *self, ltk_widget *child) {
          533         ltk_grid *grid = (ltk_grid *)self;
          534         unsigned int start = child->row * grid->columns + child->column;
          535         while (start-- > 0) {
          536                 if (grid->widget_grid[start])
          537                         return grid->widget_grid[start];
          538         }
          539         return NULL;
          540 }
          541 
          542 static ltk_widget *
          543 ltk_grid_next_child(ltk_widget *self, ltk_widget *child) {
          544         ltk_grid *grid = (ltk_grid *)self;
          545         unsigned int start = child->row * grid->columns + child->column;
          546         while (++start < (unsigned int)(grid->rows * grid->columns)) {
          547                 if (grid->widget_grid[start] && grid->widget_grid[start] != child)
          548                         return grid->widget_grid[start];
          549         }
          550         return NULL;
          551 }
          552 
          553 static ltk_widget *
          554 ltk_grid_first_child(ltk_widget *self) {
          555         ltk_grid *grid = (ltk_grid *)self;
          556         for (unsigned int i = 0; i < (unsigned int)(grid->rows * grid->columns); i++) {
          557                 if (grid->widget_grid[i])
          558                         return grid->widget_grid[i];
          559         }
          560         return NULL;
          561 }
          562 
          563 static ltk_widget *
          564 ltk_grid_last_child(ltk_widget *self) {
          565         ltk_grid *grid = (ltk_grid *)self;
          566         for (unsigned int i = grid->rows * grid->columns; i-- > 0;) {
          567                 if (grid->widget_grid[i])
          568                         return grid->widget_grid[i];
          569         }
          570         return NULL;
          571 }
          572 
          573 /* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> [sticky] */
          574 static int
          575 ltk_grid_cmd_add(
          576     ltk_window *window,
          577     ltk_grid *grid,
          578     ltk_cmd_token *tokens,
          579     size_t num_tokens,
          580     ltk_error *err) {
          581         ltk_cmdarg_parseinfo cmd[] = {
          582                 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0},
          583                 {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0},
          584                 {.type = CMDARG_INT, .min = 0, .max = grid->columns - 1, .optional = 0},
          585                 {.type = CMDARG_INT, .min = 0, .max = grid->rows, .optional = 0},
          586                 {.type = CMDARG_INT, .min = 0, .max = grid->columns, .optional = 0},
          587                 {.type = CMDARG_STICKY, .optional = 1},
          588         };
          589         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          590                 return 1;
          591         /* FIXME: better error reporting for invalid grid position */
          592         /* FIXME: check if recalculation deals properly with rowspan/columnspan
          593            that goes over the edge of the grid */
          594         if (ltk_grid_add(
          595             window, cmd[0].val.widget, grid,
          596             cmd[1].val.i, cmd[2].val.i, cmd[3].val.i, cmd[4].val.i,
          597             cmd[5].initialized ? cmd[5].val.sticky : 0, err)) {
          598                 err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 0 : -1;
          599                 return 1;
          600         }
          601         return 0;
          602 }
          603 
          604 /* grid <grid id> remove <widget id> */
          605 static int
          606 ltk_grid_cmd_ungrid(
          607     ltk_window *window,
          608     ltk_grid *grid,
          609     ltk_cmd_token *tokens,
          610     size_t num_tokens,
          611     ltk_error *err) {
          612         ltk_cmdarg_parseinfo cmd[] = {
          613                 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0}
          614         };
          615         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          616                 return 1;
          617         if (ltk_grid_ungrid(cmd[0].val.widget, (ltk_widget *)grid, err)) {
          618                 err->arg = 0;
          619                 return 1;
          620         }
          621         return 0;
          622 }
          623 
          624 /* FIXME: max size of 64 is completely arbitrary! */
          625 /* grid <grid id> create <rows> <columns> */
          626 static int
          627 ltk_grid_cmd_create(
          628     ltk_window *window,
          629     ltk_grid *grid_unneeded,
          630     ltk_cmd_token *tokens,
          631     size_t num_tokens,
          632     ltk_error *err) {
          633         (void)grid_unneeded;
          634         ltk_cmdarg_parseinfo cmd[] = {
          635                 {.type = CMDARG_IGNORE, .optional = 0},
          636                 {.type = CMDARG_STRING, .optional = 0},
          637                 {.type = CMDARG_IGNORE, .optional = 0},
          638                 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
          639                 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
          640         };
          641         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          642                 return 1;
          643         if (!ltk_widget_id_free(cmd[1].val.str)) {
          644                 err->type = ERR_WIDGET_ID_IN_USE;
          645                 err->arg = 1;
          646                 return 1;
          647         }
          648         ltk_grid *grid = ltk_grid_create(window, cmd[1].val.str, cmd[3].val.i, cmd[4].val.i);
          649         ltk_set_widget((ltk_widget *)grid, cmd[1].val.str);
          650 
          651         return 0;
          652 }
          653 
          654 /* FIXME: 64 is completely arbitrary */
          655 /* grid <grid id> set-row-weight <row> <weight> */
          656 static int
          657 ltk_grid_cmd_set_row_weight(
          658     ltk_window *window,
          659     ltk_grid *grid,
          660     ltk_cmd_token *tokens,
          661     size_t num_tokens,
          662     ltk_error *err) {
          663         ltk_cmdarg_parseinfo cmd[] = {
          664                 {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0},
          665                 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
          666         };
          667         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          668                 return 1;
          669         ltk_grid_set_row_weight(grid, cmd[0].val.i, cmd[1].val.i);
          670 
          671         return 0;
          672 }
          673 
          674 
          675 /* FIXME: 64 is completely arbitrary */
          676 /* FIXME: check for overflows in various grid calculations (at least when larger values are allowed) */
          677 /* grid <grid id> set-column-weight <column> <weight> */
          678 static int
          679 ltk_grid_cmd_set_column_weight(
          680     ltk_window *window,
          681     ltk_grid *grid,
          682     ltk_cmd_token *tokens,
          683     size_t num_tokens,
          684     ltk_error *err) {
          685         ltk_cmdarg_parseinfo cmd[] = {
          686                 {.type = CMDARG_INT, .min = 0, .max = grid->columns - 1, .optional = 0},
          687                 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
          688         };
          689         if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
          690                 return 1;
          691         ltk_grid_set_column_weight(grid, cmd[0].val.i, cmd[1].val.i);
          692 
          693         return 0;
          694 }
          695 
          696 static struct grid_cmd {
          697         char *name;
          698         int (*func)(ltk_window *, ltk_grid *, ltk_cmd_token *, size_t, ltk_error *);
          699         int needs_all;
          700 } grid_cmds[] = {
          701         {"add", &ltk_grid_cmd_add, 0},
          702         {"create", &ltk_grid_cmd_create, 1},
          703         {"remove", &ltk_grid_cmd_ungrid, 0},
          704         {"set-column-weight", &ltk_grid_cmd_set_column_weight, 0},
          705         {"set-row-weight", &ltk_grid_cmd_set_row_weight, 0},
          706 };
          707 
          708 GEN_CMD_HELPERS(ltk_grid_cmd, LTK_WIDGET_GRID, ltk_grid, grid_cmds, struct grid_cmd)