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 | |