ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

grid.c (24579B)


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