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