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