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