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 (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 }