ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | 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 }