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
26namespace ui {
27
28using namespace gfx;
29
30Grid::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
41Grid::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
59Grid::~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 */
90void 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
103Grid::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
124void 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
196void 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
211void 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
225void 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.
241void 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
263void 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
314void 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
387void 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
396void 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 extra_total = (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_foreach = 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
458bool 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.
518void 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
538void 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
558void 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