1 /*
2  Copyright (C) 2010-2014 Kristian Duske
3 
4  This file is part of TrenchBroom.
5 
6  TrenchBroom is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  TrenchBroom is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifndef TrenchBroom_CellLayout_h
21 #define TrenchBroom_CellLayout_h
22 
23 #include "VecMath.h"
24 #include "Macros.h"
25 
26 #include <algorithm>
27 #include <cassert>
28 #include <limits>
29 #include <memory>
30 #include <vector>
31 
32 namespace TrenchBroom {
33     namespace View {
34         class LayoutBounds {
35         private:
36             float m_x;
37             float m_y;
38             float m_width;
39             float m_height;
40         public:
LayoutBounds()41             LayoutBounds() :
42             m_x(0.0f),
43             m_y(0.0f),
44             m_width(0.0f),
45             m_height(0.0f) {}
46 
LayoutBounds(const float x,const float y,const float width,const float height)47             LayoutBounds(const float x, const float y, const float width, const float height) :
48             m_x(x),
49             m_y(y),
50             m_width(width),
51             m_height(height) {}
52 
left()53             float left() const {
54                 return m_x;
55             }
56 
top()57             float top() const {
58                 return m_y;
59             }
60 
right()61             float right() const {
62                 return m_x + m_width;
63             }
64 
bottom()65             float bottom() const {
66                 return m_y + m_height;
67             }
68 
midX()69             float midX() const {
70                 return m_x + m_width / 2.0f;
71             }
72 
midY()73             float midY() const {
74                 return m_y + m_height / 2.0f;
75             }
76 
width()77             float width() const {
78                 return m_width;
79             }
80 
height()81             float height() const {
82                 return m_height;
83             }
84 
containsPoint(const float x,const float y)85             bool containsPoint(const float x, const float y) const {
86                 return x >= left() && x <= right() && y >= top() && y <= bottom();
87             }
88 
intersectsY(const float y,const float height)89             bool intersectsY(const float y, const float height) const {
90                 return bottom() >= y && top() <= y + height ;
91             }
92         };
93 
94         template <typename CellType>
95         class LayoutCell {
96         public:
97         private:
98             CellType m_item;
99             float m_x;
100             float m_y;
101             float m_itemWidth;
102             float m_itemHeight;
103             float m_titleWidth;
104             float m_titleHeight;
105             float m_titleMargin;
106             float m_scale;
107             LayoutBounds m_cellBounds;
108             LayoutBounds m_itemBounds;
109             LayoutBounds m_titleBounds;
110 
doLayout(const float maxUpScale,const float minWidth,const float maxWidth,const float minHeight,const float maxHeight)111             void doLayout(const float maxUpScale,
112                                  const float minWidth, const float maxWidth,
113                                  const float minHeight, const float maxHeight) {
114                 assert(0.0f < minWidth);
115                 assert(0.0f < minHeight);
116                 assert(minWidth <= maxWidth);
117                 assert(minHeight <= maxHeight);
118 
119                 m_scale = std::min(std::min(maxWidth / m_itemWidth, maxHeight / m_itemHeight), maxUpScale);
120                 const float scaledItemWidth = m_scale * m_itemWidth;
121                 const float scaledItemHeight = m_scale * m_itemHeight;
122                 const float clippedTitleWidth = std::min(m_titleWidth, maxWidth);
123                 const float cellWidth = std::max(minWidth, std::max(scaledItemWidth, clippedTitleWidth));
124                 const float cellHeight = std::max(minHeight, std::max(minHeight, scaledItemHeight) + m_titleHeight + m_titleMargin);
125                 const float itemY = m_y + std::max(0.0f, cellHeight - m_titleHeight - scaledItemHeight - m_titleMargin);
126 
127                 m_cellBounds = LayoutBounds(m_x,
128                                             m_y,
129                                             cellWidth,
130                                             cellHeight);
131                 m_itemBounds = LayoutBounds(m_x + (m_cellBounds.width() - scaledItemWidth) / 2.0f,
132                                             itemY,
133                                             scaledItemWidth,
134                                             scaledItemHeight);
135                 m_titleBounds = LayoutBounds(m_x + (m_cellBounds.width() - clippedTitleWidth) / 2.0f,
136                                              m_itemBounds.bottom() + m_titleMargin,
137                                              clippedTitleWidth,
138                                              m_titleHeight);
139             }
140         public:
LayoutCell(const CellType item,const float x,const float y,const float itemWidth,const float itemHeight,const float titleWidth,const float titleHeight,const float titleMargin,const float maxUpScale,const float minWidth,const float maxWidth,const float minHeight,const float maxHeight)141             LayoutCell(const CellType item,
142                        const float x, const float y,
143                        const float itemWidth, const float itemHeight,
144                        const float titleWidth, const float titleHeight,
145                        const float titleMargin,
146                        const float maxUpScale,
147                        const float minWidth, const float maxWidth,
148                        const float minHeight, const float maxHeight) :
149             m_item(item),
150             m_x(x),
151             m_y(y),
152             m_itemWidth(itemWidth),
153             m_itemHeight(itemHeight),
154             m_titleWidth(titleWidth),
155             m_titleHeight(titleHeight),
156             m_titleMargin(titleMargin) {
157                 doLayout(maxUpScale, minWidth, maxWidth, minHeight, maxHeight);
158             }
159 
hitTest(const float x,const float y)160             bool hitTest(const float x, const float y) const {
161                 return m_cellBounds.containsPoint(x, y) || m_titleBounds.containsPoint(x, y);
162             }
163 
scale()164             float scale() const {
165                 return m_scale;
166             }
167 
cellBounds()168             const LayoutBounds& cellBounds() const {
169                 return m_cellBounds;
170             }
171 
titleBounds()172             const LayoutBounds& titleBounds() const {
173                 return m_titleBounds;
174             }
175 
itemBounds()176             const LayoutBounds& itemBounds() const {
177                 return m_itemBounds;
178             }
179 
updateLayout(const float maxUpScale,const float minWidth,const float maxWidth,const float minHeight,const float maxHeight)180             void updateLayout(const float maxUpScale,
181                                      const float minWidth, const float maxWidth,
182                                      const float minHeight, const float maxHeight) {
183                 doLayout(maxUpScale, minWidth, maxWidth, minHeight, maxHeight);
184             }
185 
item()186             CellType item() const {
187                 return m_item;
188             }
189 
190         };
191 
192         template <typename CellType>
193         class LayoutRow {
194         public:
195             typedef LayoutCell<CellType> Cell;
196             typedef std::vector<Cell> CellList;
197         private:
198             float m_cellMargin;
199             float m_titleMargin;
200             float m_maxWidth;
201             size_t m_maxCells;
202             float m_maxUpScale;
203             float m_minCellWidth;
204             float m_maxCellWidth;
205             float m_minCellHeight;
206             float m_maxCellHeight;
207             LayoutBounds m_bounds;
208 
209             CellList m_cells;
210 
readjustItems()211             void readjustItems() {
212                 for (size_t i = 0; i < m_cells.size(); ++i)
213                     m_cells[i].updateLayout(m_maxUpScale, m_minCellWidth, m_maxCellWidth, m_minCellHeight, m_maxCellHeight);
214             }
215         public:
LayoutRow(const float x,const float y,const float cellMargin,const float titleMargin,const float maxWidth,const size_t maxCells,const float maxUpScale,const float minCellWidth,const float maxCellWidth,const float minCellHeight,const float maxCellHeight)216             LayoutRow(const float x, const float y,
217                       const float cellMargin,
218                       const float titleMargin,
219                       const float maxWidth,
220                       const size_t maxCells,
221                       const float maxUpScale,
222                       const float minCellWidth, const float maxCellWidth,
223                       const float minCellHeight, const float maxCellHeight) :
224             m_cellMargin(cellMargin),
225             m_titleMargin(titleMargin),
226             m_maxWidth(maxWidth),
227             m_maxCells(maxCells),
228             m_maxUpScale(maxUpScale),
229             m_minCellWidth(minCellWidth),
230             m_maxCellWidth(maxCellWidth),
231             m_minCellHeight(minCellHeight),
232             m_maxCellHeight(maxCellHeight),
233             m_bounds(x, y, 0.0f, 0.0f) {}
234 
235             const Cell& operator[] (const size_t index) const {
236                 assert(index >= 0 && index < m_cells.size());
237                 return m_cells[index];
238             }
239 
addItem(CellType item,const float itemWidth,const float itemHeight,const float titleWidth,const float titleHeight)240             bool addItem(CellType item,
241                                 const float itemWidth, const float itemHeight,
242                                 const float titleWidth, const float titleHeight) {
243                 float x = m_bounds.right();
244                 float width = m_bounds.width();
245                 if (!m_cells.empty()) {
246                     x += m_cellMargin;
247                     width += m_cellMargin;
248                 }
249 
250                 Cell cell(item, x, m_bounds.top(), itemWidth, itemHeight, titleWidth, titleHeight, m_titleMargin, m_maxUpScale, m_minCellWidth, m_maxCellWidth, m_minCellHeight, m_maxCellHeight);
251                 width += cell.cellBounds().width();
252 
253                 if (m_maxCells == 0 && width > m_maxWidth && !m_cells.empty())
254                     return false;
255                 if (m_maxCells > 0 && m_cells.size() >= m_maxCells - 1)
256                     return false;
257 
258                 const float newItemRowHeight = cell.cellBounds().height() - cell.titleBounds().height() - m_titleMargin;
259                 bool readjust = newItemRowHeight > m_minCellHeight;
260                 if (readjust) {
261                     m_minCellHeight = newItemRowHeight;
262                     assert(m_minCellHeight <= m_maxCellHeight);
263                     readjustItems();
264                 }
265 
266                 m_bounds = LayoutBounds(m_bounds.left(), m_bounds.top(), width, std::max(m_bounds.height(), cell.cellBounds().height()));
267 
268                 m_cells.push_back(cell);
269                 return true;
270             }
271 
272 
cells()273             const CellList& cells() const {
274                 return m_cells;
275             }
276 
cellAt(const float x,const float y,const Cell ** result)277             bool cellAt(const float x, const float y, const Cell** result) const {
278                 for (size_t i = 0; i < m_cells.size(); ++i) {
279                     const Cell& cell = m_cells[i];
280                     const LayoutBounds& cellBounds = cell.cellBounds();
281                     if (x > cellBounds.right())
282                         continue;
283                     else if (x < cellBounds.left())
284                         break;
285                     if (cell.hitTest(x, y)) {
286                         *result = &cell;
287                         return true;
288                     }
289                 }
290                 return false;
291             }
292 
bounds()293             const LayoutBounds& bounds() const {
294                 return m_bounds;
295             }
296 
intersectsY(const float y,const float height)297             bool intersectsY(const float y, const float height) const {
298                 return m_bounds.intersectsY(y, height);
299             }
300 
size()301             size_t size() const {
302                 return m_cells.size();
303             }
304         };
305 
306         template <typename CellType, typename GroupType>
307         class LayoutGroup {
308         public:
309             typedef LayoutRow<CellType> Row;
310             typedef std::vector<Row> RowList;
311         private:
312             GroupType m_item;
313             float m_cellMargin;
314             float m_titleMargin;
315             float m_rowMargin;
316             size_t m_maxCellsPerRow;
317             float m_maxUpScale;
318             float m_minCellWidth;
319             float m_maxCellWidth;
320             float m_minCellHeight;
321             float m_maxCellHeight;
322             LayoutBounds m_titleBounds;
323             LayoutBounds m_contentBounds;
324 
325             RowList m_rows;
326         public:
327             const Row& operator[] (const size_t index) const {
328                 assert(index >= 0 && index < m_rows.size());
329                 return m_rows[index];
330             }
331 
LayoutGroup(GroupType item,const float x,const float y,const float cellMargin,const float titleMargin,const float rowMargin,const float titleHeight,const float width,const size_t maxCellsPerRow,const float maxUpScale,const float minCellWidth,const float maxCellWidth,const float minCellHeight,const float maxCellHeight)332             LayoutGroup(GroupType item,
333                         const float x, const float y,
334                         const float cellMargin, const float titleMargin, const float rowMargin,
335                         const float titleHeight,
336                         const float width,
337                         const size_t maxCellsPerRow,
338                         const float maxUpScale,
339                         const float minCellWidth, const float maxCellWidth,
340                         const float minCellHeight, const float maxCellHeight) :
341             m_item(item),
342             m_cellMargin(cellMargin),
343             m_titleMargin(titleMargin),
344             m_rowMargin(rowMargin),
345             m_maxCellsPerRow(maxCellsPerRow),
346             m_maxUpScale(maxUpScale),
347             m_minCellWidth(minCellWidth),
348             m_maxCellWidth(maxCellWidth),
349             m_minCellHeight(minCellHeight),
350             m_maxCellHeight(maxCellHeight),
351             m_titleBounds(0.0f, y, width + 2.0f * x, titleHeight),
352             m_contentBounds(x, y + titleHeight + m_rowMargin, width, 0.0f),
353             m_rows() {}
354 
LayoutGroup(const float x,const float y,const float cellMargin,const float titleMargin,const float rowMargin,const float width,const size_t maxCellsPerRow,const float maxUpScale,const float minCellWidth,const float maxCellWidth,const float minCellHeight,const float maxCellHeight)355             LayoutGroup(const float x, const float y,
356                         const float cellMargin, const float titleMargin, const float rowMargin,
357                         const float width,
358                         const size_t maxCellsPerRow,
359                         const float maxUpScale,
360                         const float minCellWidth, const float maxCellWidth,
361                         const float minCellHeight, const float maxCellHeight) :
362             m_cellMargin(cellMargin),
363             m_titleMargin(titleMargin),
364             m_rowMargin(rowMargin),
365             m_maxCellsPerRow(maxCellsPerRow),
366             m_maxUpScale(maxUpScale),
367             m_minCellWidth(minCellWidth),
368             m_maxCellWidth(maxCellWidth),
369             m_minCellHeight(minCellHeight),
370             m_maxCellHeight(maxCellHeight),
371             m_titleBounds(x, y, width, 0.0f),
372             m_contentBounds(x, y, width, 0.0f),
373             m_rows() {}
374 
addItem(CellType item,const float itemWidth,const float itemHeight,const float titleWidth,const float titleHeight)375             void addItem(CellType item,
376                          const float itemWidth, const float itemHeight,
377                          const float titleWidth, const float titleHeight) {
378                 if (m_rows.empty()) {
379                     const float y = m_contentBounds.top();
380                     m_rows.push_back(Row(m_contentBounds.left(), y, m_cellMargin, m_titleMargin, m_contentBounds.width(), m_maxCellsPerRow, m_maxUpScale, m_minCellWidth, m_maxCellWidth, m_minCellHeight, m_maxCellHeight));
381                 }
382 
383                 const LayoutBounds oldBounds = m_rows.back().bounds();
384                 const float oldRowHeight = m_rows.back().bounds().height();
385                 if (!m_rows.back().addItem(item, itemWidth, itemHeight, titleWidth, titleHeight)) {
386                     const float y = oldBounds.bottom() + m_rowMargin;
387                     m_rows.push_back(Row(m_contentBounds.left(), y, m_cellMargin, m_titleMargin, m_contentBounds.width(), m_maxCellsPerRow, m_maxUpScale, m_minCellWidth, m_maxCellWidth, m_minCellHeight, m_maxCellHeight));
388 
389                     const bool added = (m_rows.back().addItem(item, itemWidth, itemHeight, titleWidth, titleHeight));
390                     assert(added);
391                     unused(added);
392 
393                     const float newRowHeight = m_rows.back().bounds().height();
394                     m_contentBounds = LayoutBounds(m_contentBounds.left(), m_contentBounds.top(), m_contentBounds.width(), m_contentBounds.height() + newRowHeight + m_rowMargin);
395                 } else {
396                     const float newRowHeight = m_rows.back().bounds().height();
397                     m_contentBounds = LayoutBounds(m_contentBounds.left(), m_contentBounds.top(), m_contentBounds.width(), m_contentBounds.height() + (newRowHeight - oldRowHeight));
398                 }
399             }
400 
indexOfRowAt(const float y)401             size_t indexOfRowAt(const float y) const {
402                 for (size_t i = 0; i < m_rows.size(); ++i) {
403                     const Row& row = m_rows[i];
404                     const LayoutBounds& rowBounds = row.bounds();
405                     if (y < rowBounds.bottom())
406                         return i;
407                 }
408 
409                 return m_rows.size();
410             }
411 
rowAt(const float y,const Row ** result)412             bool rowAt(const float y, const Row** result) const {
413                 size_t index = indexOfRowAt(y);
414                 if (index == m_rows.size())
415                     return false;
416 
417                 *result = &m_rows[index];
418                 return true;
419             }
420 
cellAt(const float x,const float y,const typename Row::Cell ** result)421             bool cellAt(const float x, const float y, const typename Row::Cell** result) const {
422                 for (size_t i = 0; i < m_rows.size(); ++i) {
423                     const Row& row = m_rows[i];
424                     const LayoutBounds& rowBounds = row.bounds();
425                     if (y > rowBounds.bottom())
426                         continue;
427                     else if (y < rowBounds.top())
428                         break;
429                     if (row.cellAt(x, y, result))
430                         return true;
431                 }
432 
433                 return false;
434             }
435 
hitTest(const float x,const float y)436             bool hitTest(const float x, const float y) const {
437                 return bounds().containsPoint(x, y);
438             }
439 
titleBounds()440             const LayoutBounds& titleBounds() const {
441                 return m_titleBounds;
442             }
443 
titleBoundsForVisibleRect(const float y,const float height,const float groupMargin)444             const LayoutBounds titleBoundsForVisibleRect(const float y, const float height, const float groupMargin) const {
445                 if (intersectsY(y, height) && m_titleBounds.top() < y) {
446                     if (y > m_contentBounds.bottom() - m_titleBounds.height() + groupMargin)
447                         return LayoutBounds(m_titleBounds.left(), m_contentBounds.bottom() - m_titleBounds.height() + groupMargin, m_titleBounds.width(), m_titleBounds.height());
448                     return LayoutBounds(m_titleBounds.left(), y, m_titleBounds.width(), m_titleBounds.height());
449                 }
450                 return m_titleBounds;
451             }
452 
contentBounds()453             const LayoutBounds& contentBounds() const {
454                 return m_contentBounds;
455             }
456 
bounds()457             const LayoutBounds bounds() const {
458                 return LayoutBounds(m_titleBounds.left(), m_titleBounds.top(), m_titleBounds.width(), m_contentBounds.bottom() - m_titleBounds.top());
459             }
460 
intersectsY(const float y,const float height)461             bool intersectsY(const float y, const float height) const {
462                 return bounds().intersectsY(y, height);
463             }
464 
item()465             GroupType item() const {
466                 return m_item;
467             }
468 
size()469             size_t size() const {
470                 return m_rows.size();
471             }
472         };
473 
474         template <typename CellType, typename GroupType>
475         class CellLayout {
476         public:
477             typedef LayoutGroup<CellType, GroupType> Group;
478             typedef std::vector<Group> GroupList;
479         private:
480             float m_width;
481             float m_cellMargin;
482             float m_titleMargin;
483             float m_rowMargin;
484             float m_groupMargin;
485             float m_outerMargin;
486             size_t m_maxCellsPerRow;
487             float m_maxUpScale;
488             float m_minCellWidth;
489             float m_maxCellWidth;
490             float m_minCellHeight;
491             float m_maxCellHeight;
492 
493             GroupList m_groups;
494             bool m_valid;
495             float m_height;
496 
validate()497             void validate() {
498                 if (m_width <= 0.0f)
499                     return;
500 
501                 m_height = 2.0f * m_outerMargin;
502                 m_valid = true;
503                 if (!m_groups.empty()) {
504                     GroupList copy = m_groups;
505                     m_groups.clear();
506 
507                     for (size_t i = 0; i < copy.size(); ++i) {
508                         Group& group = copy[i];
509                         addGroup(group.item(), group.titleBounds().height());
510                         for (size_t j = 0; j < group.size(); ++j) {
511                             const typename Group::Row& row = group[j];
512                             for (size_t k = 0; k < row.size(); k++) {
513                                 const typename Group::Row::Cell& cell = row[k];
514                                 const LayoutBounds& itemBounds = cell.itemBounds();
515                                 const LayoutBounds& titleBounds = cell.titleBounds();
516                                 float scale = cell.scale();
517                                 float itemWidth = itemBounds.width() / scale;
518                                 float itemHeight = itemBounds.height() / scale;
519                                 addItem(cell.item(), itemWidth, itemHeight, titleBounds.width(), titleBounds.height());
520                             }
521                         }
522                     }
523                 }
524             }
525         public:
526             const Group& operator[] (const size_t index) {
527                 assert(index >= 0 && index < m_groups.size());
528                 if (!m_valid)
529                     validate();
530                     return m_groups[index];
531             }
532 
533             CellLayout(const size_t maxCellsPerRow = 0) :
534             m_width(1.0f),
535             m_cellMargin(0.0f),
536             m_titleMargin(0.0f),
537             m_rowMargin(0.0f),
538             m_groupMargin(0.0f),
539             m_outerMargin(0.0f),
540             m_maxCellsPerRow(maxCellsPerRow),
541             m_maxUpScale(1.0f),
542             m_minCellWidth(100.0f),
543             m_maxCellWidth(100.0f),
544             m_minCellHeight(100.0f),
545             m_maxCellHeight(100.0f),
546             m_groups(),
547             m_valid(false),
548             m_height(0.0f) {
549                 invalidate();
550             }
551 
setCellMargin(const float cellMargin)552             void setCellMargin(const float cellMargin) {
553                 if (m_cellMargin == cellMargin)
554                     return;
555                 m_cellMargin = cellMargin;
556                 invalidate();
557             }
558 
setTitleMargin(const float titleMargin)559             void setTitleMargin(const float titleMargin) {
560                 if (m_titleMargin == titleMargin)
561                     return;
562                 m_titleMargin = titleMargin;
563                 invalidate();
564             }
565 
setRowMargin(const float rowMargin)566             void setRowMargin(const float rowMargin) {
567                 if (m_rowMargin == rowMargin)
568                     return;
569                 m_rowMargin = rowMargin;
570                 invalidate();
571             }
572 
setGroupMargin(const float groupMargin)573             void setGroupMargin(const float groupMargin) {
574                 if (m_groupMargin == groupMargin)
575                     return;
576                 m_groupMargin = groupMargin;
577                 invalidate();
578             }
579 
setOuterMargin(const float outerMargin)580             void setOuterMargin(const float outerMargin) {
581                 if (m_outerMargin == outerMargin)
582                     return;
583                 m_outerMargin = outerMargin;
584                 invalidate();
585             }
586 
addGroup(const GroupType groupItem,const float titleHeight)587             void addGroup(const GroupType groupItem, const float titleHeight) {
588                 if (!m_valid)
589                     validate();
590 
591                 float y = 0.0f;
592                 if (!m_groups.empty()) {
593                     y = m_groups.back().bounds().bottom() + m_groupMargin;
594                     m_height += m_groupMargin;
595                 }
596 
597                 m_groups.push_back(Group(groupItem, m_outerMargin, y, m_cellMargin, m_titleMargin, m_rowMargin, titleHeight, m_width - 2.0f * m_outerMargin, m_maxCellsPerRow, m_maxUpScale, m_minCellWidth, m_maxCellWidth, m_minCellHeight, m_maxCellHeight));
598                 m_height += m_groups.back().bounds().height();
599             }
600 
addItem(const CellType item,const float itemWidth,const float itemHeight,const float titleWidth,const float titleHeight)601             void addItem(const CellType item,
602                          const float itemWidth, const float itemHeight,
603                          const float titleWidth, const float titleHeight) {
604                 if (!m_valid)
605                     validate();
606 
607                 if (m_groups.empty()) {
608                     m_groups.push_back(Group(m_outerMargin, m_outerMargin, m_cellMargin, m_titleMargin, m_rowMargin, m_width - 2.0f * m_outerMargin, m_maxCellsPerRow, m_maxUpScale, m_minCellWidth, m_maxCellWidth, m_minCellHeight, m_maxCellHeight));
609                     m_height += titleHeight;
610                     if (titleHeight > 0.0f)
611                         m_height += m_rowMargin;
612                 }
613 
614                 const float oldGroupHeight = m_groups.back().bounds().height();
615                 m_groups.back().addItem(item, itemWidth, itemHeight, titleWidth, titleHeight);
616                 const float newGroupHeight = m_groups.back().bounds().height();
617 
618                 m_height += (newGroupHeight - oldGroupHeight);
619             }
620 
clear()621             void clear() {
622                 m_groups.clear();
623                 invalidate();
624             }
625 
cellAt(const float x,const float y,const typename Group::Row::Cell ** result)626             bool cellAt(const float x, const float y, const typename Group::Row::Cell** result) {
627                 if (!m_valid)
628                     validate();
629 
630                 for (size_t i = 0; i < m_groups.size(); ++i) {
631                     const Group& group = m_groups[i];
632                     const LayoutBounds groupBounds = group.bounds();
633                     if (y > groupBounds.bottom())
634                         continue;
635                     else if (y < groupBounds.top())
636                         break;
637                     if (group.cellAt(x, y, result))
638                         return true;
639                 }
640 
641                 return false;
642             }
643 
groupAt(const float x,const float y,Group * result)644             bool groupAt(const float x, const float y, Group* result) {
645                 if (!m_valid)
646                     validate();
647 
648                 for (size_t i = 0; i < m_groups.size(); ++i) {
649                     Group* group = m_groups[i];
650                     const LayoutBounds groupBounds = group->bounds();
651                     if (y > groupBounds.bottom())
652                         continue;
653                     else if (y < groupBounds.top())
654                         break;
655                     if (group->hitTest(x, y)) {
656                         result = group;
657                         return true;
658                     }
659                 }
660 
661                 return false;
662             }
663 
titleBoundsForVisibleRect(const Group & group,const float y,const float height)664             const LayoutBounds titleBoundsForVisibleRect(const Group& group, const float y, const float height) const {
665                 return group.titleBoundsForVisibleRect(y, height, m_groupMargin);
666             }
667 
rowPosition(const float y,const int offset)668             float rowPosition(const float y, const int offset) {
669                 if (!m_valid)
670                     validate();
671 
672                 size_t groupIndex = m_groups.size();
673                 for (size_t i = 0; i < m_groups.size(); ++i) {
674                     Group* candidate = &m_groups[i];
675                     const LayoutBounds groupBounds = candidate->bounds();
676                     if (y + m_rowMargin > groupBounds.bottom())
677                         continue;
678                     groupIndex = i;
679                     break;
680                 }
681 
682                 if (groupIndex == m_groups.size())
683                     return y;
684 
685                 size_t rowIndex = m_groups[groupIndex].indexOfRowAt(y);
686                 if (rowIndex == m_groups[groupIndex].size())
687                     return y;
688 
689                 if (offset == 0)
690                     return y;
691 
692                 int newIndex = static_cast<int>(rowIndex) + offset;
693                 if (newIndex < 0) {
694                     while (newIndex < 0 && groupIndex > 0)
695                         newIndex += m_groups[--groupIndex].size();
696                 } else if (newIndex >= static_cast<int>(m_groups[groupIndex].size())) {
697                     while (newIndex >= static_cast<int>(m_groups[groupIndex].size()) && groupIndex < m_groups.size() - 1)
698                         newIndex -= m_groups[groupIndex++].size();
699                 }
700 
701                 if (groupIndex < m_groups.size()) {
702                     if (newIndex >= 0) {
703                         rowIndex = static_cast<size_t>(newIndex);
704                         if (rowIndex < m_groups[groupIndex].size()) {
705                             return m_groups[groupIndex][rowIndex].bounds().top();
706                         }
707                     }
708                 }
709 
710 
711                 return y;
712             }
713 
size()714             size_t size() {
715                 if (!m_valid)
716                     validate();
717                 return m_groups.size();
718             }
719 
invalidate()720             void invalidate() {
721                 m_valid = false;
722             }
723 
setWidth(const float width)724             void setWidth(const float width) {
725                 if (m_width == width)
726                     return;
727                 m_width = width;
728                 invalidate();
729             }
730 
minCellWidth()731             float minCellWidth() const {
732                 return m_minCellWidth;
733             }
734 
maxCellWidth()735             float maxCellWidth() const {
736                 return m_maxCellWidth;
737             }
738 
setCellWidth(const float minCellWidth,const float maxCellWidth)739             void setCellWidth(const float minCellWidth, const float maxCellWidth) {
740                 assert(0.0f < minCellWidth);
741                 assert(minCellWidth <= maxCellWidth);
742 
743                 if (m_minCellWidth == minCellWidth && m_maxCellWidth == maxCellWidth)
744                     return;
745                 m_minCellWidth = minCellWidth;
746                 m_maxCellWidth = maxCellWidth;
747                 invalidate();
748             }
749 
minCellHeight()750             float minCellHeight() const {
751                 return m_minCellHeight;
752             }
753 
maxCellHeight()754             float maxCellHeight() const {
755                 return m_maxCellHeight;
756             }
757 
setCellHeight(const float minCellHeight,const float maxCellHeight)758             void setCellHeight(const float minCellHeight, const float maxCellHeight) {
759                 assert(0.0f < minCellHeight);
760                 assert(minCellHeight <= maxCellHeight);
761 
762                 if (m_minCellHeight == minCellHeight && m_maxCellHeight == maxCellHeight)
763                     return;
764                 m_minCellHeight = minCellHeight;
765                 m_maxCellHeight = maxCellHeight;
766                 invalidate();
767             }
768 
setMaxUpScale(const float maxUpScale)769             void setMaxUpScale(const float maxUpScale) {
770                 if (m_maxUpScale == maxUpScale)
771                     return;
772                 m_maxUpScale = maxUpScale;
773                 invalidate();
774             }
775 
width()776             float width() const {
777                 return m_width;
778             }
779 
height()780             float height() {
781                 if (!m_valid)
782                     validate();
783                 return m_height;
784             }
785 
outerMargin()786             float outerMargin() const {
787                 return m_outerMargin;
788             }
789 
groupMargin()790             float groupMargin() const {
791                 return m_groupMargin;
792             }
793 
rowMargin()794             float rowMargin() const {
795                 return m_rowMargin;
796             }
797 
cellMargin()798             float cellMargin() const {
799                 return m_cellMargin;
800             }
801         };
802     }
803 }
804 
805 #endif
806