| 1 | // Aseprite UI Library |
| 2 | // Copyright (C) 2018-2020 Igara Studio S.A. |
| 3 | // Copyright (C) 2001-2017 David Capello |
| 4 | // |
| 5 | // This file is released under the terms of the MIT license. |
| 6 | // Read LICENSE.txt for more information. |
| 7 | |
| 8 | #ifdef HAVE_CONFIG_H |
| 9 | #include "config.h" |
| 10 | #endif |
| 11 | |
| 12 | #include "base/memory.h" |
| 13 | #include "gfx/size.h" |
| 14 | #include "ui/grid.h" |
| 15 | #include "ui/message.h" |
| 16 | #include "ui/size_hint_event.h" |
| 17 | #include "ui/resize_event.h" |
| 18 | #include "ui/theme.h" |
| 19 | #include "ui/widget.h" |
| 20 | |
| 21 | #include <algorithm> |
| 22 | #include <cstdio> |
| 23 | #include <cstdlib> |
| 24 | #include <cstring> |
| 25 | |
| 26 | namespace ui { |
| 27 | |
| 28 | using namespace gfx; |
| 29 | |
| 30 | Grid::Cell::Cell() |
| 31 | { |
| 32 | parent = nullptr; |
| 33 | child = nullptr; |
| 34 | hspan = 0; |
| 35 | vspan = 0; |
| 36 | align = 0; |
| 37 | w = 0; |
| 38 | h = 0; |
| 39 | } |
| 40 | |
| 41 | Grid::Grid(int columns, bool same_width_columns) |
| 42 | : Widget(kGridWidget) |
| 43 | , m_colstrip(columns) |
| 44 | { |
| 45 | ASSERT(columns > 0); |
| 46 | |
| 47 | enableFlags(IGNORE_MOUSE); |
| 48 | |
| 49 | m_same_width_columns = same_width_columns; |
| 50 | |
| 51 | for (std::size_t col=0; col<m_colstrip.size(); ++col) { |
| 52 | m_colstrip[col].size = 0; |
| 53 | m_colstrip[col].expand_count = 0; |
| 54 | } |
| 55 | |
| 56 | initTheme(); |
| 57 | } |
| 58 | |
| 59 | Grid::~Grid() |
| 60 | { |
| 61 | // Delete all cells. |
| 62 | for (std::size_t row=0; row<m_cells.size(); ++row) |
| 63 | for (std::size_t col=0; col<m_cells[row].size(); ++col) |
| 64 | delete m_cells[row][col]; |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Adds a child widget in the specified grid. |
| 69 | * |
| 70 | * @param widget The grid widget. |
| 71 | * @param child The child widget. |
| 72 | * @param hspan |
| 73 | * @param vspan |
| 74 | * @param align |
| 75 | * It's a combination of the following values: |
| 76 | * |
| 77 | * - HORIZONTAL: The widget'll get excess horizontal space. |
| 78 | * - VERTICAL: The widget'll get excess vertical space. |
| 79 | * |
| 80 | * - LEFT: Sets horizontal alignment to the beginning of cell. |
| 81 | * - CENTER: Sets horizontal alignment to the center of cell. |
| 82 | * - RIGHT: Sets horizontal alignment to the end of cell. |
| 83 | * - None: Uses the whole horizontal space of the cell. |
| 84 | * |
| 85 | * - TOP: Sets vertical alignment to the beginning of the cell. |
| 86 | * - MIDDLE: Sets vertical alignment to the center of the cell. |
| 87 | * - BOTTOM: Sets vertical alignment to the end of the cell. |
| 88 | * - None: Uses the whole vertical space of the cell. |
| 89 | */ |
| 90 | void Grid::addChildInCell(Widget* child, int hspan, int vspan, int align) |
| 91 | { |
| 92 | ASSERT(hspan > 0); |
| 93 | ASSERT(vspan > 0); |
| 94 | |
| 95 | addChild(child); |
| 96 | |
| 97 | if (!putWidgetInCell(child, hspan, vspan, align)) { |
| 98 | expandRows(m_rowstrip.size()+1); |
| 99 | putWidgetInCell(child, hspan, vspan, align); |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | Grid::Info Grid::getChildInfo(Widget* child) |
| 104 | { |
| 105 | Info info; |
| 106 | for (int row=0; row<(int)m_rowstrip.size(); ++row) { |
| 107 | for (int col=0; col<(int)m_colstrip.size(); ++col) { |
| 108 | Cell* cell = m_cells[row][col]; |
| 109 | |
| 110 | if (cell->child == child) { |
| 111 | info.col = col; |
| 112 | info.row = row; |
| 113 | info.hspan = cell->hspan; |
| 114 | info.vspan = cell->vspan; |
| 115 | info.grid_cols = m_colstrip.size(); |
| 116 | info.grid_rows = m_rowstrip.size(); |
| 117 | return info; |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | return info; |
| 122 | } |
| 123 | |
| 124 | void Grid::onResize(ResizeEvent& ev) |
| 125 | { |
| 126 | gfx::Rect rect = ev.bounds(); |
| 127 | int pos_x, pos_y; |
| 128 | Size reqSize; |
| 129 | int x, y, w, h; |
| 130 | int col, row; |
| 131 | |
| 132 | setBoundsQuietly(rect); |
| 133 | |
| 134 | calculateSize(); |
| 135 | distributeSize(rect); |
| 136 | |
| 137 | pos_y = rect.y + border().top(); |
| 138 | for (row=0; row<(int)m_rowstrip.size(); ++row) { |
| 139 | pos_x = rect.x + border().left(); |
| 140 | |
| 141 | for (col=0; col<(int)m_colstrip.size(); ++col) { |
| 142 | Cell* cell = m_cells[row][col]; |
| 143 | |
| 144 | if (cell->child != nullptr && |
| 145 | cell->parent == nullptr && |
| 146 | !(cell->child->hasFlags(HIDDEN))) { |
| 147 | x = pos_x; |
| 148 | y = pos_y; |
| 149 | |
| 150 | calculateCellSize(col, cell->hspan, m_colstrip, w); |
| 151 | calculateCellSize(row, cell->vspan, m_rowstrip, h); |
| 152 | |
| 153 | reqSize = cell->child->sizeHint(); |
| 154 | |
| 155 | if (cell->align & LEFT) { |
| 156 | w = reqSize.w; |
| 157 | } |
| 158 | else if (cell->align & CENTER) { |
| 159 | x += w/2 - reqSize.w/2; |
| 160 | w = reqSize.w; |
| 161 | } |
| 162 | else if (cell->align & RIGHT) { |
| 163 | x += w - reqSize.w; |
| 164 | w = reqSize.w; |
| 165 | } |
| 166 | |
| 167 | if (cell->align & TOP) { |
| 168 | h = reqSize.h; |
| 169 | } |
| 170 | else if (cell->align & MIDDLE) { |
| 171 | y += h/2 - reqSize.h/2; |
| 172 | h = reqSize.h; |
| 173 | } |
| 174 | else if (cell->align & BOTTOM) { |
| 175 | y += h - reqSize.h; |
| 176 | h = reqSize.h; |
| 177 | } |
| 178 | |
| 179 | if (x+w > rect.x+rect.w-border().right()) |
| 180 | w = rect.x+rect.w-border().right()-x; |
| 181 | if (y+h > rect.y+rect.h-border().bottom()) |
| 182 | h = rect.y+rect.h-border().bottom()-y; |
| 183 | |
| 184 | cell->child->setBounds(Rect(x, y, w, h)); |
| 185 | } |
| 186 | |
| 187 | if (m_colstrip[col].size > 0) |
| 188 | pos_x += m_colstrip[col].size + childSpacing(); |
| 189 | } |
| 190 | |
| 191 | if (m_rowstrip[row].size > 0) |
| 192 | pos_y += m_rowstrip[row].size + childSpacing(); |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | void Grid::onSizeHint(SizeHintEvent& ev) |
| 197 | { |
| 198 | calculateSize(); |
| 199 | |
| 200 | // Calculate the total |
| 201 | gfx::Size sz(0, 0); |
| 202 | sumStripSize(m_colstrip, sz.w); |
| 203 | sumStripSize(m_rowstrip, sz.h); |
| 204 | |
| 205 | sz.w += border().width(); |
| 206 | sz.h += border().height(); |
| 207 | |
| 208 | ev.setSizeHint(sz); |
| 209 | } |
| 210 | |
| 211 | void Grid::sumStripSize(const std::vector<Strip>& strip, int& size) |
| 212 | { |
| 213 | int i, j; |
| 214 | |
| 215 | size = 0; |
| 216 | for (i=j=0; i<(int)strip.size(); ++i) { |
| 217 | if (strip[i].size > 0) { |
| 218 | size += strip[i].size; |
| 219 | if (++j > 1) |
| 220 | size += this->childSpacing(); |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | void Grid::calculateCellSize(int start, int span, const std::vector<Strip>& strip, int& size) |
| 226 | { |
| 227 | int i, j; |
| 228 | |
| 229 | size = 0; |
| 230 | |
| 231 | for (i=start, j=0; i<start+span; ++i) { |
| 232 | if (strip[i].size > 0) { |
| 233 | size += strip[i].size; |
| 234 | if (++j > 1) |
| 235 | size += this->childSpacing(); |
| 236 | } |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | // Calculates the size of each strip (rows and columns) in the grid. |
| 241 | void Grid::calculateSize() |
| 242 | { |
| 243 | if (m_rowstrip.size() == 0) |
| 244 | return; |
| 245 | |
| 246 | calculateStripSize(m_colstrip, m_rowstrip, HORIZONTAL); |
| 247 | calculateStripSize(m_rowstrip, m_colstrip, VERTICAL); |
| 248 | |
| 249 | expandStrip(m_colstrip, m_rowstrip, &Grid::incColSize); |
| 250 | expandStrip(m_rowstrip, m_colstrip, &Grid::incRowSize); |
| 251 | |
| 252 | // Same width in all columns |
| 253 | if (m_same_width_columns) { |
| 254 | int max_w = 0; |
| 255 | for (int col=0; col<(int)m_colstrip.size(); ++col) |
| 256 | max_w = std::max(max_w, m_colstrip[col].size); |
| 257 | |
| 258 | for (int col=0; col<(int)m_colstrip.size(); ++col) |
| 259 | m_colstrip[col].size = max_w; |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | void Grid::calculateStripSize(std::vector<Strip>& colstrip, |
| 264 | std::vector<Strip>& rowstrip, int align) |
| 265 | { |
| 266 | Cell* cell; |
| 267 | |
| 268 | // For each column |
| 269 | for (int col=0; col<(int)colstrip.size(); ++col) { |
| 270 | // A counter of widgets that want more space in this column |
| 271 | int expand_count = 0; |
| 272 | |
| 273 | // For each row |
| 274 | for (int row=0; row<(int)rowstrip.size(); ++row) { |
| 275 | // For each cell |
| 276 | if (&colstrip == &m_colstrip) |
| 277 | cell = m_cells[row][col]; |
| 278 | else |
| 279 | cell = m_cells[col][row]; // Transposed |
| 280 | |
| 281 | if (cell->child != nullptr) { |
| 282 | if (cell->parent == nullptr) { |
| 283 | // If the widget isn't hidden then we can request its size |
| 284 | if (!(cell->child->hasFlags(HIDDEN))) { |
| 285 | Size reqSize = cell->child->sizeHint(); |
| 286 | cell->w = reqSize.w - (cell->hspan-1) * this->childSpacing(); |
| 287 | cell->h = reqSize.h - (cell->vspan-1) * this->childSpacing(); |
| 288 | |
| 289 | if ((cell->align & align) == align) |
| 290 | ++expand_count; |
| 291 | } |
| 292 | else |
| 293 | cell->w = cell->h = 0; |
| 294 | } |
| 295 | else { |
| 296 | if (!(cell->child->hasFlags(HIDDEN))) { |
| 297 | if ((cell->parent->align & align) == align) |
| 298 | ++expand_count; |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | if (&colstrip == &m_colstrip) |
| 303 | row += cell->vspan-1; |
| 304 | else |
| 305 | row += cell->hspan-1; // Transposed |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | colstrip[col].size = 0; |
| 310 | colstrip[col].expand_count = expand_count; |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | void Grid::expandStrip(std::vector<Strip>& colstrip, |
| 315 | std::vector<Strip>& rowstrip, |
| 316 | void (Grid::*incCol)(int, int)) |
| 317 | { |
| 318 | bool more_span; |
| 319 | int i, current_span = 1; |
| 320 | |
| 321 | do { |
| 322 | more_span = false; |
| 323 | for (int col=0; col<(int)colstrip.size(); ++col) { |
| 324 | for (int row=0; row<(int)rowstrip.size(); ++row) { |
| 325 | int cell_size; |
| 326 | int cell_span; |
| 327 | Cell* cell; |
| 328 | |
| 329 | // For each cell |
| 330 | if (&colstrip == &m_colstrip) { |
| 331 | cell = m_cells[row][col]; |
| 332 | cell_size = cell->w; |
| 333 | cell_span = cell->hspan; |
| 334 | } |
| 335 | else { |
| 336 | cell = m_cells[col][row]; // Transposed |
| 337 | cell_size = cell->h; |
| 338 | cell_span = cell->vspan; |
| 339 | } |
| 340 | |
| 341 | if (cell->child != nullptr && |
| 342 | cell->parent == nullptr && |
| 343 | cell_size > 0) { |
| 344 | ASSERT(cell_span > 0); |
| 345 | |
| 346 | if (cell_span == current_span) { |
| 347 | // Calculate the maximum (expand_count) in cell's columns. |
| 348 | int max_expand_count = 0; |
| 349 | for (i=col; i<col+cell_span; ++i) |
| 350 | max_expand_count = std::max(max_expand_count, |
| 351 | colstrip[i].expand_count); |
| 352 | |
| 353 | int expand = 0; // How many columns have the maximum value of "expand_count" |
| 354 | int last_expand = 0; // This variable is used to add the remainder space to the last column |
| 355 | for (i=col; i<col+cell_span; ++i) { |
| 356 | if (colstrip[i].expand_count == max_expand_count) { |
| 357 | ++expand; |
| 358 | last_expand = i; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | // Divide the available size of the cell in the number of columns which are expandible |
| 363 | int size = cell_size / expand; |
| 364 | for (i=col; i<col+cell_span; ++i) { |
| 365 | if (colstrip[i].expand_count == max_expand_count) { |
| 366 | // For the last column, use all the available space in the column |
| 367 | if (last_expand == i) { |
| 368 | if (&colstrip == &m_colstrip) |
| 369 | size = cell->w; |
| 370 | else |
| 371 | size = cell->h; // Transposed |
| 372 | } |
| 373 | (this->*incCol)(i, size); |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | else if (cell_span > current_span) { |
| 378 | more_span = true; |
| 379 | } |
| 380 | } |
| 381 | } |
| 382 | } |
| 383 | ++current_span; |
| 384 | } while (more_span); |
| 385 | } |
| 386 | |
| 387 | void Grid::distributeSize(const gfx::Rect& rect) |
| 388 | { |
| 389 | if (m_rowstrip.size() == 0) |
| 390 | return; |
| 391 | |
| 392 | distributeStripSize(m_colstrip, rect.w, border().width(), m_same_width_columns); |
| 393 | distributeStripSize(m_rowstrip, rect.h, border().height(), false); |
| 394 | } |
| 395 | |
| 396 | void Grid::distributeStripSize(std::vector<Strip>& colstrip, |
| 397 | int rect_size, int border_size, bool same_width) |
| 398 | { |
| 399 | int i, j; |
| 400 | |
| 401 | int max_expand_count = 0; |
| 402 | for (i=0; i<(int)colstrip.size(); ++i) |
| 403 | max_expand_count = std::max(max_expand_count, |
| 404 | colstrip[i].expand_count); |
| 405 | |
| 406 | int total_req = 0; |
| 407 | int wantmore_count = 0; |
| 408 | for (i=j=0; i<(int)colstrip.size(); ++i) { |
| 409 | if (colstrip[i].size > 0) { |
| 410 | total_req += colstrip[i].size; |
| 411 | if (++j > 1) |
| 412 | total_req += this->childSpacing(); |
| 413 | } |
| 414 | |
| 415 | if (colstrip[i].expand_count == max_expand_count || same_width) { |
| 416 | ++wantmore_count; |
| 417 | } |
| 418 | } |
| 419 | total_req += border_size; |
| 420 | |
| 421 | int = (rect_size - total_req); |
| 422 | |
| 423 | // Expand or reduce "expandable" strip |
| 424 | if ((wantmore_count > 0) && |
| 425 | ((extra_total > 0 && (max_expand_count > 0 || same_width)) || |
| 426 | (extra_total < 0))) { |
| 427 | // If a expandable column-strip was empty (size=0) then we have |
| 428 | // to reduce the extra_total size because a new child-spacing is |
| 429 | // added by this column |
| 430 | for (i=0; i<(int)colstrip.size(); ++i) { |
| 431 | if ((colstrip[i].size == 0) && |
| 432 | (colstrip[i].expand_count == max_expand_count || same_width)) { |
| 433 | extra_total -= SGN(extra_total)*this->childSpacing(); |
| 434 | } |
| 435 | } |
| 436 | |
| 437 | int = extra_total / wantmore_count; |
| 438 | |
| 439 | for (i=0; i<(int)colstrip.size(); ++i) { |
| 440 | if (colstrip[i].expand_count == max_expand_count || same_width) { |
| 441 | ASSERT(wantmore_count > 0); |
| 442 | |
| 443 | colstrip[i].size += extra_foreach; |
| 444 | extra_total -= extra_foreach; |
| 445 | |
| 446 | if (--wantmore_count == 0) { |
| 447 | colstrip[i].size += extra_total; |
| 448 | extra_total = 0; |
| 449 | } |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | ASSERT(wantmore_count == 0); |
| 454 | ASSERT(extra_total == 0); |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | bool Grid::putWidgetInCell(Widget* child, int hspan, int vspan, int align) |
| 459 | { |
| 460 | int col, row, colbeg, colend, rowend; |
| 461 | Cell *cell, *parentcell; |
| 462 | |
| 463 | for (row=0; row<(int)m_rowstrip.size(); ++row) { |
| 464 | for (col=0; col<(int)m_colstrip.size(); ++col) { |
| 465 | cell = m_cells[row][col]; |
| 466 | |
| 467 | if (cell->child == nullptr) { |
| 468 | cell->child = child; |
| 469 | cell->hspan = hspan; |
| 470 | cell->vspan = vspan; |
| 471 | cell->align = align; |
| 472 | |
| 473 | parentcell = cell; |
| 474 | colbeg = col; |
| 475 | colend = std::min(col+hspan, (int)m_colstrip.size()); |
| 476 | rowend = row+vspan; |
| 477 | |
| 478 | expandRows(row+vspan); |
| 479 | |
| 480 | for (++col; col<colend; ++col) { |
| 481 | cell = m_cells[row][col]; |
| 482 | |
| 483 | // If these asserts fails, it's really possible that you |
| 484 | // specified bad values for hspan or vspan (they are |
| 485 | // overlapping with other cells). |
| 486 | ASSERT(cell->parent == nullptr); |
| 487 | ASSERT(cell->child == nullptr); |
| 488 | |
| 489 | cell->parent = parentcell; |
| 490 | cell->child = child; |
| 491 | cell->hspan = colend - col; |
| 492 | cell->vspan = rowend - row; |
| 493 | } |
| 494 | |
| 495 | for (++row; row<rowend; ++row) { |
| 496 | for (col=colbeg; col<colend; ++col) { |
| 497 | cell = m_cells[row][col]; |
| 498 | |
| 499 | ASSERT(cell->parent == nullptr); |
| 500 | ASSERT(cell->child == nullptr); |
| 501 | |
| 502 | cell->parent = parentcell; |
| 503 | cell->child = child; |
| 504 | cell->hspan = colend - col; |
| 505 | cell->vspan = rowend - row; |
| 506 | } |
| 507 | } |
| 508 | return true; |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | return false; |
| 514 | } |
| 515 | |
| 516 | // Expands the grid's rows to reach the specified quantity of rows in |
| 517 | // the parameter. |
| 518 | void Grid::expandRows(int rows) |
| 519 | { |
| 520 | if ((int)m_rowstrip.size() < rows) { |
| 521 | int old_size = (int)m_rowstrip.size(); |
| 522 | |
| 523 | m_cells.resize(rows); |
| 524 | m_rowstrip.resize(rows); |
| 525 | |
| 526 | for (int row=old_size; row<rows; ++row) { |
| 527 | m_cells[row].resize(m_colstrip.size()); |
| 528 | m_rowstrip[row].size = 0; |
| 529 | m_rowstrip[row].expand_count = 0; |
| 530 | |
| 531 | for (int col=0; col<(int)m_cells[row].size(); ++col) { |
| 532 | m_cells[row][col] = new Cell; |
| 533 | } |
| 534 | } |
| 535 | } |
| 536 | } |
| 537 | |
| 538 | void Grid::incColSize(int col, int size) |
| 539 | { |
| 540 | m_colstrip[col].size += size; |
| 541 | |
| 542 | for (int row=0; row<(int)m_rowstrip.size(); ) { |
| 543 | Cell* cell = m_cells[row][col]; |
| 544 | |
| 545 | if (cell->child != nullptr) { |
| 546 | if (cell->parent != nullptr) |
| 547 | cell->parent->w -= size; |
| 548 | else |
| 549 | cell->w -= size; |
| 550 | |
| 551 | row += cell->vspan; |
| 552 | } |
| 553 | else |
| 554 | ++row; |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | void Grid::incRowSize(int row, int size) |
| 559 | { |
| 560 | m_rowstrip[row].size += size; |
| 561 | |
| 562 | for (int col=0; col<(int)m_colstrip.size(); ) { |
| 563 | Cell* cell = m_cells[row][col]; |
| 564 | |
| 565 | if (cell->child != nullptr) { |
| 566 | if (cell->parent != nullptr) |
| 567 | cell->parent->h -= size; |
| 568 | else |
| 569 | cell->h -= size; |
| 570 | |
| 571 | col += cell->hspan; |
| 572 | } |
| 573 | else |
| 574 | ++col; |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | } // namespace ui |
| 579 | |