URI:
       grid.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
       ---
       grid.c (22334B)
       ---
            1 /* FIXME: sometimes, resizing doesn't work properly when running test.sh */
            2 
            3 /*
            4  * Copyright (c) 2016-2026 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 <stddef.h>
           27 #include <limits.h>
           28 
           29 #include "memory.h"
           30 #include "rect.h"
           31 #include "widget.h"
           32 #include "util.h"
           33 #include "grid.h"
           34 #include "graphics.h"
           35 #include "ltk.h"
           36 
           37 /* just a wrapper around ltk_grid_remove to make types match */
           38 static int ltk_grid_remove_child(ltk_widget *self, ltk_widget_id childid);
           39 
           40 static void ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
           41 static void ltk_grid_destroy(ltk_widget *self, int shallow);
           42 static void ltk_recalculate_grid(ltk_widget *self);
           43 static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget_id childid);
           44 static int ltk_grid_find_nearest_column(ltk_grid *grid, int x);
           45 static int ltk_grid_find_nearest_row(ltk_grid *grid, int y);
           46 static ltk_widget_id ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y);
           47 
           48 static ltk_widget_id ltk_grid_prev_child(ltk_widget *self, ltk_widget_id childid);
           49 static ltk_widget_id ltk_grid_next_child(ltk_widget *self, ltk_widget_id childid);
           50 static ltk_widget_id ltk_grid_first_child(ltk_widget *self);
           51 static ltk_widget_id ltk_grid_last_child(ltk_widget *self);
           52 
           53 static ltk_widget_id ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect);
           54 static ltk_widget_id ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget_id childid);
           55 static ltk_widget_id ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget_id childid);
           56 static ltk_widget_id ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget_id childid);
           57 static ltk_widget_id ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget_id childid);
           58 
           59 static void ltk_grid_recalc_ideal_size(ltk_widget *self);
           60 
           61 static struct ltk_widget_vtable vtable = {
           62         .draw = &ltk_grid_draw,
           63         .destroy = &ltk_grid_destroy,
           64         .resize = &ltk_recalculate_grid,
           65         .hide = NULL,
           66         .change_state = NULL,
           67         .child_size_change = &ltk_grid_child_size_change,
           68         .remove_child = &ltk_grid_remove_child,
           69         .mouse_press = NULL,
           70         .mouse_scroll = NULL,
           71         .mouse_release = NULL,
           72         .motion_notify = NULL,
           73         .get_child_at_pos = &ltk_grid_get_child_at_pos,
           74         .mouse_leave = NULL,
           75         .mouse_enter = NULL,
           76         .key_press = NULL,
           77         .key_release = NULL,
           78         .prev_child = &ltk_grid_prev_child,
           79         .next_child = &ltk_grid_next_child,
           80         .first_child = &ltk_grid_first_child,
           81         .last_child = &ltk_grid_last_child,
           82         .nearest_child = &ltk_grid_nearest_child,
           83         .nearest_child_left = &ltk_grid_nearest_child_left,
           84         .nearest_child_right = &ltk_grid_nearest_child_right,
           85         .nearest_child_above = &ltk_grid_nearest_child_above,
           86         .nearest_child_below = &ltk_grid_nearest_child_below,
           87         .recalc_ideal_size = &ltk_grid_recalc_ideal_size,
           88         .type = LTK_WIDGET_GRID,
           89         .flags = 0,
           90         .invalid_signal = LTK_GRID_SIGNAL_INVALID,
           91 };
           92 
           93 /* FIXME: only set "dirty" bit to avoid constant recalculation when
           94    setting multiple row/column weights? */
           95 void
           96 ltk_grid_set_row_weight(ltk_widget_id gridid, int row, int weight) {
           97         ltk_widget *self = ltk_get_widget_from_id(gridid);
           98         ltk_grid *grid = LTK_CAST_GRID(self);
           99         ltk_assert(row < grid->rows);
          100         grid->row_weights[row] = weight;
          101         ltk_recalculate_grid(self);
          102 }
          103 
          104 void
          105 ltk_grid_set_column_weight(ltk_widget_id gridid, int column, int weight) {
          106         ltk_widget *self = ltk_get_widget_from_id(gridid);
          107         ltk_grid *grid = LTK_CAST_GRID(self);
          108         ltk_assert(column < grid->columns);
          109         grid->column_weights[column] = weight;
          110         ltk_recalculate_grid(self);
          111 }
          112 
          113 static void
          114 ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
          115         ltk_grid *grid = LTK_CAST_GRID(self);
          116         int i;
          117         ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip);
          118         for (i = 0; i < grid->rows * grid->columns; i++) {
          119                 if (LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i]))
          120                         continue;
          121                 ltk_widget *ptr = ltk_get_widget_from_id(grid->widget_grid[i]);
          122                 int max_w = grid->column_pos[ptr->column + ptr->column_span] - grid->column_pos[ptr->column];
          123                 int max_h = grid->row_pos[ptr->row + ptr->row_span] - grid->row_pos[ptr->row];
          124                 ltk_rect r = ltk_rect_intersect(
          125                     (ltk_rect){grid->column_pos[ptr->column], grid->row_pos[ptr->row], max_w, max_h}, real_clip
          126                 );
          127                 ltk_widget_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, r));
          128         }
          129 }
          130 
          131 ltk_widget_id
          132 ltk_grid_create(ltk_widget_id windowid, int rows, int columns) {
          133         ltk_grid *grid = ltk_malloc(sizeof(ltk_grid));
          134 
          135         ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(grid), windowid, &vtable, 0, 0);
          136 
          137         grid->rows = rows;
          138         grid->columns = columns;
          139         grid->widget_grid = ltk_malloc(rows * columns * sizeof(ltk_widget_id));
          140         grid->row_heights = ltk_malloc(rows * sizeof(int));
          141         grid->column_widths = ltk_malloc(columns * sizeof(int));
          142         grid->row_weights = ltk_malloc(rows * sizeof(int));
          143         grid->column_weights = ltk_malloc(columns * sizeof(int));
          144         /* Positions have one extra for the end */
          145         grid->row_pos = ltk_malloc((rows + 1) * sizeof(int));
          146         grid->column_pos = ltk_malloc((columns + 1) * sizeof(int));
          147 
          148         memset(grid->row_heights, 0, rows * sizeof(int));
          149         memset(grid->row_weights, 0, rows * sizeof(int));
          150         memset(grid->row_pos, 0, (rows + 1) * sizeof(int));
          151         memset(grid->column_widths, 0, columns * sizeof(int));
          152         memset(grid->column_weights, 0, columns * sizeof(int));
          153         memset(grid->column_pos, 0, (columns + 1) * sizeof(int));
          154         memset(grid->column_pos, 0, (columns + 1) * sizeof(int));
          155         for (int i = 0; i < rows * columns; i++) {
          156                 grid->widget_grid[i] = LTK_WIDGET_ID_NONE;
          157         }
          158 
          159         ltk_recalculate_grid(LTK_CAST_WIDGET(grid));
          160         return id;
          161 }
          162 
          163 static void
          164 ltk_grid_destroy(ltk_widget *self, int shallow) {
          165         ltk_grid *grid = LTK_CAST_GRID(self);
          166         for (int i = 0; i < grid->rows * grid->columns; i++) {
          167                 ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[i]);
          168                 if (ptr) {
          169                         ptr->parent = LTK_WIDGET_ID_NONE;
          170                         if (!shallow) {
          171                                 /* required to avoid freeing a widget multiple times
          172                                    if row_span or column_span is not 1 */
          173                                 for (int r = ptr->row; r < ptr->row + ptr->row_span; r++) {
          174                                         for (int c = ptr->column; c < ptr->column + ptr->column_span; c++) {
          175                                                 grid->widget_grid[r * grid->columns + c] = LTK_WIDGET_ID_NONE;
          176                                         }
          177                                 }
          178                                 ltk_widget_destroy(ptr, shallow);
          179                         }
          180                 }
          181         }
          182         ltk_free(grid->widget_grid);
          183         ltk_free(grid->row_heights);
          184         ltk_free(grid->column_widths);
          185         ltk_free(grid->row_weights);
          186         ltk_free(grid->column_weights);
          187         ltk_free(grid->row_pos);
          188         ltk_free(grid->column_pos);
          189         ltk_free(grid);
          190 }
          191 
          192 static void
          193 ltk_recalculate_grid(ltk_widget *self) {
          194         ltk_grid *grid = LTK_CAST_GRID(self);
          195         int height_static = 0, width_static = 0;
          196         int total_row_weight = 0, total_column_weight = 0;
          197         double height_unit = 0, width_unit = 0;
          198         int currentx = 0, currenty = 0;
          199         int i, j;
          200         for (i = 0; i < grid->rows; i++) {
          201                 total_row_weight += grid->row_weights[i];
          202                 if (grid->row_weights[i] == 0) {
          203                         height_static += grid->row_heights[i];
          204                 }
          205         }
          206         for (i = 0; i < grid->columns; i++) {
          207                 total_column_weight += grid->column_weights[i];
          208                 if (grid->column_weights[i] == 0) {
          209                         width_static += grid->column_widths[i];
          210                 }
          211         }
          212         /* FIXME: what should be done when static height or width is larger than grid? */
          213         if (total_row_weight > 0) {
          214                 height_unit = (double)(self->lrect.h - height_static) / (double)total_row_weight;
          215         }
          216         if (total_column_weight > 0) {
          217                 width_unit = (double)(self->lrect.w - width_static) / (double)total_column_weight;
          218         }
          219         for (i = 0; i < grid->rows; i++) {
          220                 grid->row_pos[i] = currenty;
          221                 if (grid->row_weights[i] > 0) {
          222                         grid->row_heights[i] = grid->row_weights[i] * height_unit;
          223                 }
          224                 currenty += grid->row_heights[i];
          225         }
          226         grid->row_pos[grid->rows] = currenty;
          227         for (i = 0; i < grid->columns; i++) {
          228                 grid->column_pos[i] = currentx;
          229                 if (grid->column_weights[i] > 0) {
          230                         grid->column_widths[i] = grid->column_weights[i] * width_unit;
          231                 }
          232                 currentx += grid->column_widths[i];
          233         }
          234         grid->column_pos[grid->columns] = currentx;
          235         /*int orig_width, orig_height;*/
          236         int end_column, end_row;
          237         for (i = 0; i < grid->rows; i++) {
          238                 for (j = 0; j < grid->columns; j++) {
          239                         ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[i * grid->columns + j]);
          240                         if (!ptr || ptr->row != i || ptr->column != j)
          241                                 continue;
          242                         /*orig_width = ptr->lrect.w;
          243                         orig_height = ptr->lrect.h;*/
          244                         ptr->lrect.w = ptr->ideal_w;
          245                         ptr->lrect.h = ptr->ideal_h;
          246                         end_row = i + ptr->row_span;
          247                         end_column = j + ptr->column_span;
          248                         int max_w = grid->column_pos[end_column] - grid->column_pos[j];
          249                         int max_h = grid->row_pos[end_row] - grid->row_pos[i];
          250                         int stretch_width = (ptr->sticky & LTK_STICKY_LEFT) && (ptr->sticky & LTK_STICKY_RIGHT);
          251                         int shrink_width = (ptr->sticky & LTK_STICKY_SHRINK_WIDTH) && ptr->lrect.w > max_w;
          252                         int stretch_height = (ptr->sticky & LTK_STICKY_TOP) && (ptr->sticky & LTK_STICKY_BOTTOM);
          253                         int shrink_height = (ptr->sticky & LTK_STICKY_SHRINK_HEIGHT) && ptr->lrect.h > max_h;
          254                         if (stretch_width || shrink_width)
          255                                 ptr->lrect.w = max_w;
          256                         if (stretch_height || shrink_height)
          257                                 ptr->lrect.h = max_h;
          258                         if (ptr->sticky & LTK_STICKY_PRESERVE_ASPECT_RATIO) {
          259                                 if (!stretch_width && !shrink_width) {
          260                                         ptr->lrect.w = (int)(((double)ptr->lrect.h / ptr->ideal_h) * ptr->ideal_w);
          261                                 } else if (!stretch_height && !shrink_height) {
          262                                         ptr->lrect.h = (int)(((double)ptr->lrect.w / ptr->ideal_w) * ptr->ideal_h);
          263                                 } else {
          264                                         double scale_w = (double)ptr->lrect.w / ptr->ideal_w;
          265                                         double scale_h = (double)ptr->lrect.h / ptr->ideal_h;
          266                                         if (scale_w * ptr->ideal_h > ptr->lrect.h)
          267                                                 ptr->lrect.w = (int)(scale_h * ptr->ideal_w);
          268                                         else if (scale_h * ptr->ideal_w > ptr->lrect.w)
          269                                                 ptr->lrect.h = (int)(scale_w * ptr->ideal_h);
          270                                 }
          271                         }
          272 
          273                         /* the "default" case needs to come first because the widget may be stretched
          274                            with aspect ratio preserving, and in that case it should still be centered */
          275                         if (stretch_width || !(ptr->sticky & (LTK_STICKY_RIGHT|LTK_STICKY_LEFT))) {
          276                                 ptr->lrect.x = grid->column_pos[j] + (grid->column_pos[end_column] - grid->column_pos[j] - ptr->lrect.w) / 2;
          277                         } else if (ptr->sticky & LTK_STICKY_RIGHT) {
          278                                 ptr->lrect.x = grid->column_pos[end_column] - ptr->lrect.w;
          279                         } else if (ptr->sticky & LTK_STICKY_LEFT) {
          280                                 ptr->lrect.x = grid->column_pos[j];
          281                         }
          282 
          283                         if (stretch_height || !(ptr->sticky & (LTK_STICKY_TOP|LTK_STICKY_BOTTOM))) {
          284                                 ptr->lrect.y = grid->row_pos[i] + (grid->row_pos[end_row] - grid->row_pos[i] - ptr->lrect.h) / 2;
          285                         } else if (ptr->sticky & LTK_STICKY_BOTTOM) {
          286                                 ptr->lrect.y = grid->row_pos[end_row] - ptr->lrect.h;
          287                         } else if (ptr->sticky & LTK_STICKY_TOP) {
          288                                 ptr->lrect.y = grid->row_pos[i];
          289                         }
          290                         /* intersect both with the grid rect and with the rect of the covered cells since there may be
          291                            weird cases where the layout doesn't work properly and the cells are partially outside the grid */
          292                         ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect);
          293                         ptr->crect = ltk_rect_intersect((ltk_rect){grid->column_pos[j], grid->row_pos[i], max_w, max_h}, ptr->crect);
          294 
          295                         /* FIXME: Figure out a better system for this - it would be nice to make it more
          296                            efficient by not doing anything if nothing changed, but that doesn't work when
          297                            this function was called because of a child_size_change. In that case, if a
          298                            container widget is nested inside another container widget and another widget
          299                            inside the nested container sends a child_size_change but the toplevel container
          300                            doesn't change the size of the container, the position/size of the widget at the
          301                            bottom of the hierarchy will never be updated. That's why updates are forced
          302                            here even if seemingly nothing changed, but there probably is a better way. */
          303                         /*if (orig_width != ptr->lrect.w || orig_height != ptr->lrect.h)*/
          304                                 ltk_widget_resize(ptr);
          305                 }
          306         }
          307 }
          308 
          309 /* FIXME: what should be done if widget spans multiple rows/columns with static size? */
          310 static void
          311 ltk_grid_recalc_ideal_size(ltk_widget *self) {
          312         ltk_grid *grid = LTK_CAST_GRID(self);
          313         for (int j = 0; j < grid->columns; j++) {
          314                 if (grid->column_weights[j] == 0)
          315                         grid->column_widths[j] = 0;
          316         }
          317         for (int i = 0; i < grid->rows; i++) {
          318                 if (grid->row_weights[i] == 0)
          319                         grid->row_heights[i] = 0;
          320                 for (int j = 0; j < grid->columns; j++) {
          321                         ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[i * grid->columns + j]);
          322                         if (!ptr || ptr->row != i || ptr->column != j)
          323                                 continue;
          324                         ltk_widget_recalc_ideal_size(ptr);
          325                         if (grid->row_weights[i] == 0 && ptr->ideal_h > (unsigned int)grid->row_heights[i])
          326                                 grid->row_heights[i] = ptr->ideal_h;
          327                         if (grid->column_weights[j] == 0 && ptr->ideal_w > (unsigned int)grid->column_widths[j])
          328                                 grid->column_widths[j] = ptr->ideal_w;
          329                 }
          330         }
          331         self->ideal_w = self->ideal_h = 0;
          332         for (int i = 0; i < grid->rows; i++) {
          333                 if (grid->row_weights[i] == 0)
          334                         self->ideal_h += grid->row_heights[i];
          335         }
          336         for (int j = 0; j < grid->columns; j++) {
          337                 if (grid->column_weights[j] == 0)
          338                         self->ideal_w += grid->column_widths[j];
          339         }
          340 }
          341 
          342 /* FIXME: Maybe add debug stuff to check that grid is actually parent of widget */
          343 static void
          344 ltk_grid_child_size_change(ltk_widget *self, ltk_widget_id widgetid) {
          345         ltk_grid *grid = LTK_CAST_GRID(self);
          346         ltk_widget *widget = ltk_get_widget_from_id(widgetid);
          347         short size_changed = 0;
          348         int orig_w = widget->lrect.w;
          349         int orig_h = widget->lrect.h;
          350         widget->lrect.w = widget->ideal_w;
          351         widget->lrect.h = widget->ideal_h;
          352         if (grid->column_weights[widget->column] == 0 &&
          353             widget->lrect.w > grid->column_widths[widget->column]) {
          354                 self->ideal_w += widget->lrect.w - grid->column_widths[widget->column];
          355                 grid->column_widths[widget->column] = widget->lrect.w;
          356                 size_changed = 1;
          357         }
          358         if (grid->row_weights[widget->row] == 0 &&
          359             widget->lrect.h > grid->row_heights[widget->row]) {
          360                 self->ideal_h += widget->lrect.h - grid->row_heights[widget->row];
          361                 grid->row_heights[widget->row] = widget->lrect.h;
          362                 size_changed = 1;
          363         }
          364         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
          365         if (size_changed && parent && parent->vtable->child_size_change)
          366                 parent->vtable->child_size_change(parent, self->id);
          367         else
          368                 ltk_recalculate_grid(self);
          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 int
          375 ltk_grid_add(
          376         ltk_widget_id gridid, ltk_widget_id widgetid,
          377         int row, int column, int row_span, int column_span,
          378         ltk_sticky_mask sticky
          379 ) {
          380         ltk_widget *self = ltk_get_widget_from_id(gridid);
          381         ltk_grid *grid = LTK_CAST_GRID(self);
          382         ltk_widget *widget = ltk_get_widget_from_id(widgetid);
          383         if (!LTK_WIDGET_ID_IS_NONE(widget->parent))
          384                 return 1;
          385         /* FIXME: decide which checks should be asserts and which should be error returns */
          386         /* the client-server version of ltk shouldn't abort on errors like these */
          387         ltk_assert(row >= 0 && row + row_span <= grid->rows);
          388         ltk_assert(column >= 0 && column + column_span <= grid->columns);
          389 
          390         ltk_widget_recalc_ideal_size(widget);
          391 
          392         widget->sticky = sticky;
          393         widget->row = row;
          394         widget->column = column;
          395         widget->row_span = row_span;
          396         widget->column_span = column_span;
          397         for (int i = row; i < row + row_span; i++) {
          398                 for (int j = column; j < column + column_span; j++) {
          399                         grid->widget_grid[i * grid->columns + j] = widgetid;
          400                 }
          401         }
          402         widget->parent = gridid;
          403         ltk_grid_child_size_change(self, widgetid);
          404         ltk_window_invalidate_widget_rect(self->window, gridid);
          405 
          406         return 0;
          407 }
          408 
          409 int
          410 ltk_grid_remove(ltk_widget_id gridid, ltk_widget_id widgetid) {
          411         ltk_widget *self = ltk_get_widget_from_id(gridid);
          412         ltk_grid *grid = LTK_CAST_GRID(self);
          413         ltk_widget *widget = ltk_get_widget_from_id(widgetid);
          414         if (!LTK_WIDGET_ID_EQUAL(widget->parent, gridid))
          415                 return 1;
          416         widget->parent = LTK_WIDGET_ID_NONE;
          417         for (int i = widget->row; i < widget->row + widget->row_span; i++) {
          418                 for (int j = widget->column; j < widget->column + widget->column_span; j++) {
          419                         grid->widget_grid[i * grid->columns + j] = LTK_WIDGET_ID_NONE;
          420                 }
          421         }
          422         ltk_window_invalidate_widget_rect(self->window, gridid);
          423 
          424         return 0;
          425 }
          426 
          427 static int
          428 ltk_grid_remove_child(ltk_widget *self, ltk_widget_id widgetid) {
          429         return ltk_grid_remove(self->id, widgetid);
          430 }
          431 
          432 static int
          433 ltk_grid_find_nearest_column(ltk_grid *grid, int x) {
          434         int i;
          435         for (i = 0; i < grid->columns; i++) {
          436                 if (grid->column_pos[i] <= x && grid->column_pos[i + 1] >= x) {
          437                         return i;
          438                 }
          439         }
          440         return -1;
          441 }
          442 
          443 static int
          444 ltk_grid_find_nearest_row(ltk_grid *grid, int y) {
          445         int i;
          446         for (i = 0; i < grid->rows; i++) {
          447                 if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) {
          448                         return i;
          449                 }
          450         }
          451         return -1;
          452 }
          453 
          454 /* FIXME: maybe come up with a more efficient method */
          455 static ltk_widget_id
          456 ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) {
          457         ltk_grid *grid = LTK_CAST_GRID(self);
          458         ltk_widget_id minw = LTK_WIDGET_ID_NONE;
          459         int min_dist = INT_MAX;
          460         /* FIXME: rows and columns shouldn't be int */
          461         for (size_t i = 0; i < (size_t)(grid->rows * grid->columns); i++) {
          462                 if (LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i]))
          463                         continue;
          464                 /* FIXME: this checks widgets with row/columnspan > 1 multiple times */
          465                 ltk_widget *widget = ltk_get_widget_from_id(grid->widget_grid[i]);
          466                 ltk_rect r = widget->lrect;
          467                 int dist = ltk_rect_fakedist(rect, r);
          468                 if (dist < min_dist) {
          469                         min_dist = dist;
          470                         minw = grid->widget_grid[i];
          471                 }
          472         }
          473         return minw;
          474 }
          475 
          476 /* FIXME: assertions to check that widget row/column are legal */
          477 static ltk_widget_id
          478 ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget_id childid) {
          479         ltk_grid *grid = LTK_CAST_GRID(self);
          480         ltk_widget *child = ltk_get_widget_from_id(childid);
          481         unsigned int col = child->column;
          482         while (col-- > 0) {
          483                 ltk_widget_id cur = grid->widget_grid[child->row * grid->columns + col];
          484                 if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid))
          485                         return cur;
          486         }
          487         return LTK_WIDGET_ID_NONE;
          488 }
          489 
          490 static ltk_widget_id
          491 ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget_id childid) {
          492         ltk_grid *grid = LTK_CAST_GRID(self);
          493         ltk_widget *child = ltk_get_widget_from_id(childid);
          494         for (int col = child->column + 1; col < grid->columns; col++) {
          495                 ltk_widget_id cur = grid->widget_grid[child->row * grid->columns + col];
          496                 if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid))
          497                         return cur;
          498         }
          499         return LTK_WIDGET_ID_NONE;
          500 }
          501 
          502 /* FIXME: maybe these should also fall back to widgets in other columns if those
          503    exist but no widgets exist in the same column */
          504 static ltk_widget_id
          505 ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget_id childid) {
          506         ltk_grid *grid = LTK_CAST_GRID(self);
          507         ltk_widget *child = ltk_get_widget_from_id(childid);
          508         unsigned int row = child->row;
          509         while (row-- > 0) {
          510                 ltk_widget_id cur = grid->widget_grid[row * grid->columns + child->column];
          511                 if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid))
          512                         return cur;
          513         }
          514         return LTK_WIDGET_ID_NONE;
          515 }
          516 
          517 static ltk_widget_id
          518 ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget_id childid) {
          519         ltk_grid *grid = LTK_CAST_GRID(self);
          520         ltk_widget *child = ltk_get_widget_from_id(childid);
          521         for (int row = child->row + 1; row < grid->rows; row++) {
          522                 ltk_widget_id cur = grid->widget_grid[row * grid->columns + child->column];
          523                 if (!LTK_WIDGET_ID_IS_NONE(cur) && !LTK_WIDGET_ID_EQUAL(cur, childid))
          524                         return cur;
          525         }
          526         return LTK_WIDGET_ID_NONE;
          527 }
          528 
          529 static ltk_widget_id
          530 ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) {
          531         ltk_grid *grid = LTK_CAST_GRID(self);
          532         int row = ltk_grid_find_nearest_row(grid, y);
          533         int column = ltk_grid_find_nearest_column(grid, x);
          534         if (row == -1 || column == -1)
          535                 return LTK_WIDGET_ID_NONE;
          536         ltk_widget *ptr = ltk_get_widget_or_null_from_id(grid->widget_grid[row * grid->columns + column]);
          537         if (ptr && ltk_collide_rect(ptr->crect, x, y))
          538                 return ptr->id;
          539         return LTK_WIDGET_ID_NONE;
          540 }
          541 
          542 static ltk_widget_id
          543 ltk_grid_prev_child(ltk_widget *self, ltk_widget_id childid) {
          544         ltk_grid *grid = LTK_CAST_GRID(self);
          545         ltk_widget *child = ltk_get_widget_from_id(childid);
          546         unsigned int start = child->row * grid->columns + child->column;
          547         while (start-- > 0) {
          548                 if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[start]))
          549                         return grid->widget_grid[start];
          550         }
          551         return LTK_WIDGET_ID_NONE;
          552 }
          553 
          554 static ltk_widget_id
          555 ltk_grid_next_child(ltk_widget *self, ltk_widget_id childid) {
          556         ltk_grid *grid = LTK_CAST_GRID(self);
          557         ltk_widget *child = ltk_get_widget_from_id(childid);
          558         unsigned int start = child->row * grid->columns + child->column;
          559         while (++start < (unsigned int)(grid->rows * grid->columns)) {
          560                 if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[start]) &&
          561                     !LTK_WIDGET_ID_EQUAL(grid->widget_grid[start], childid))
          562                         return grid->widget_grid[start];
          563         }
          564         return LTK_WIDGET_ID_NONE;
          565 }
          566 
          567 static ltk_widget_id
          568 ltk_grid_first_child(ltk_widget *self) {
          569         ltk_grid *grid = LTK_CAST_GRID(self);
          570         for (unsigned int i = 0; i < (unsigned int)(grid->rows * grid->columns); i++) {
          571                 if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i]))
          572                         return grid->widget_grid[i];
          573         }
          574         return LTK_WIDGET_ID_NONE;
          575 }
          576 
          577 static ltk_widget_id
          578 ltk_grid_last_child(ltk_widget *self) {
          579         ltk_grid *grid = LTK_CAST_GRID(self);
          580         for (unsigned int i = grid->rows * grid->columns; i-- > 0;) {
          581                 if (!LTK_WIDGET_ID_IS_NONE(grid->widget_grid[i]))
          582                         return grid->widget_grid[i];
          583         }
          584         return LTK_WIDGET_ID_NONE;
          585 }