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 = <k_grid_draw, 63 .destroy = <k_grid_destroy, 64 .resize = <k_recalculate_grid, 65 .hide = NULL, 66 .change_state = NULL, 67 .child_size_change = <k_grid_child_size_change, 68 .remove_child = <k_grid_remove_child, 69 .mouse_press = NULL, 70 .mouse_scroll = NULL, 71 .mouse_release = NULL, 72 .motion_notify = NULL, 73 .get_child_at_pos = <k_grid_get_child_at_pos, 74 .mouse_leave = NULL, 75 .mouse_enter = NULL, 76 .key_press = NULL, 77 .key_release = NULL, 78 .prev_child = <k_grid_prev_child, 79 .next_child = <k_grid_next_child, 80 .first_child = <k_grid_first_child, 81 .last_child = <k_grid_last_child, 82 .nearest_child = <k_grid_nearest_child, 83 .nearest_child_left = <k_grid_nearest_child_left, 84 .nearest_child_right = <k_grid_nearest_child_right, 85 .nearest_child_above = <k_grid_nearest_child_above, 86 .nearest_child_below = <k_grid_nearest_child_below, 87 .recalc_ideal_size = <k_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 }