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