Unity 8
 All Classes Functions Properties
organicgrid.cpp
1 /*
2  * Copyright (C) 2014 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "organicgrid.h"
18 
19 #pragma GCC diagnostic push
20 #pragma GCC diagnostic ignored "-pedantic"
21 #include <private/qquickitem_p.h>
22 #pragma GCC diagnostic pop
23 
24 static const qreal bufferRatio = 0.5;
25 
26 OrganicGrid::OrganicGrid()
27  : m_firstVisibleIndex(-1)
28  , m_numberOfModulesPerRow(-1)
29 {
30 }
31 
32 QSizeF OrganicGrid::smallDelegateSize() const
33 {
34  return m_smallDelegateSize;
35 }
36 
37 void OrganicGrid::setSmallDelegateSize(const QSizeF &size)
38 {
39  if (m_smallDelegateSize != size) {
40  m_smallDelegateSize = size;
41  Q_EMIT smallDelegateSizeChanged();
42 
43  if (isComponentComplete()) {
44  relayout();
45  }
46  }
47 }
48 
49 QSizeF OrganicGrid::bigDelegateSize() const
50 {
51  return m_bigDelegateSize;
52 }
53 
54 void OrganicGrid::setBigDelegateSize(const QSizeF &size)
55 {
56  if (m_bigDelegateSize != size) {
57  m_bigDelegateSize = size;
58  Q_EMIT bigDelegateSizeChanged();
59 
60  if (isComponentComplete()) {
61  relayout();
62  }
63  }
64 }
65 
66 QPointF OrganicGrid::positionForIndex(int modelIndex) const
67 {
68  const qreal moduleHeight = m_smallDelegateSize.height() + rowSpacing() + m_bigDelegateSize.height();
69  const qreal moduleWidth = m_smallDelegateSize.width() * 2 + columnSpacing() * 2 + m_bigDelegateSize.width();
70  const int itemsPerRow = m_numberOfModulesPerRow * 6;
71  const int rowIndex = floor(modelIndex / itemsPerRow);
72  const int columnIndex = floor((modelIndex - rowIndex * itemsPerRow) / 6);
73 
74  qreal yPos = (moduleHeight + rowSpacing()) * rowIndex;
75  const int moduleIndex = modelIndex % 6;
76  if (moduleIndex == 2) {
77  yPos += m_smallDelegateSize.height() + rowSpacing();
78  } else if (moduleIndex == 3 || moduleIndex == 5) {
79  yPos += m_bigDelegateSize.height() + rowSpacing();
80  }
81 
82  qreal xPos = (moduleWidth + columnSpacing()) * columnIndex;
83  if (moduleIndex == 1) {
84  xPos += m_smallDelegateSize.width() + columnSpacing();
85  } else if (moduleIndex == 3) {
86  xPos += m_bigDelegateSize.width() + columnSpacing();
87  } else if (moduleIndex == 4) {
88  xPos += (m_smallDelegateSize.width() + columnSpacing()) * 2;
89  } else if (moduleIndex == 5) {
90  xPos += m_bigDelegateSize.width() + m_smallDelegateSize.width() + columnSpacing() * 2;
91  }
92 
93  return QPointF(xPos, yPos);
94 }
95 
96 QSizeF OrganicGrid::sizeForIndex(int modelIndex) const
97 {
98  const int moduleIndex = modelIndex % 6;
99  if (moduleIndex == 0 || moduleIndex == 1 || moduleIndex == 3 || moduleIndex == 5) {
100  return m_smallDelegateSize;
101  } else {
102  return m_bigDelegateSize;
103  }
104 }
105 
106 void OrganicGrid::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
107 {
108  if (m_visibleItems.isEmpty()) {
109  *modelIndex = 0;
110  *yPos = 0;
111  } else {
112  *modelIndex = m_firstVisibleIndex + m_visibleItems.count();
113  // We create stuff in a 6-module basis, so always return back
114  // the y position of the first item
115  const int firstModuleIndex = ((*modelIndex) / 6) * 6;
116  *yPos = positionForIndex(firstModuleIndex).y();
117  }
118 }
119 
120 void OrganicGrid::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
121 {
122  if (m_visibleItems.isEmpty()) {
123  *modelIndex = 0;
124  *yPos = 0;
125  } else {
126  *modelIndex = m_firstVisibleIndex - 1;
127  // We create stuff in a 6-module basis, so always return back
128  // the y position of the last item bottom
129  const int lastModuleIndex = ((*modelIndex) / 6) * 6 + 5;
130  *yPos = positionForIndex(lastModuleIndex).y();
131  *yPos += sizeForIndex(lastModuleIndex).height();
132  }
133 }
134 
135 void OrganicGrid::addItemToView(int modelIndex, QQuickItem *item)
136 {
137  // modelIndex has to be either m_firstVisibleIndex - 1 or m_firstVisibleIndex + m_visibleItems.count() or the first
138  if (modelIndex == m_firstVisibleIndex + m_visibleItems.count()) {
139  m_visibleItems << item;
140  } else if (modelIndex == m_firstVisibleIndex - 1) {
141  m_firstVisibleIndex = modelIndex;
142  m_visibleItems.prepend(item);
143  } else if (modelIndex == 0) {
144  m_firstVisibleIndex = 0;
145  m_visibleItems << item;
146  } else {
147  qWarning() << "OrganicGrid::addItemToView - Got unexpected modelIndex"
148  << modelIndex << m_firstVisibleIndex << m_visibleItems.count();
149  return;
150  }
151 
152  const QPointF pos = positionForIndex(modelIndex);
153  item->setPosition(pos);
154 
155  item->setSize(sizeForIndex(modelIndex));
156 }
157 
158 bool OrganicGrid::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
159 {
160  bool changed = false;
161 
162  // As adding, we also remove in a 6-module basis
163  int lastModuleIndex = (m_firstVisibleIndex / 6) * 6 + 5;
164  bool removeIndex = positionForIndex(lastModuleIndex).y() + sizeForIndex(lastModuleIndex).height() < bufferFromY;
165  while (removeIndex && !m_visibleItems.isEmpty()) {
166  releaseItem(m_visibleItems.takeFirst());
167  changed = true;
168  m_firstVisibleIndex++;
169 
170  lastModuleIndex = (m_firstVisibleIndex / 6) * 6 + 5;
171  removeIndex = positionForIndex(lastModuleIndex).y() + sizeForIndex(lastModuleIndex).height() < bufferFromY;
172  }
173 
174  int firstModuleIndex = ((m_firstVisibleIndex + m_visibleItems.count() - 1) / 6) * 6;
175  removeIndex = positionForIndex(firstModuleIndex).y() > bufferToY;
176  while (removeIndex && !m_visibleItems.isEmpty()) {
177  releaseItem(m_visibleItems.takeLast());
178  changed = true;
179 
180  firstModuleIndex = ((m_firstVisibleIndex + m_visibleItems.count() - 1) / 6) * 6;
181  removeIndex = positionForIndex(firstModuleIndex).y() > bufferToY;
182  }
183 
184  if (m_visibleItems.isEmpty()) {
185  m_firstVisibleIndex = -1;
186  }
187 
188  return changed;
189 }
190 
191 void OrganicGrid::cleanupExistingItems()
192 {
193  Q_FOREACH(QQuickItem *item, m_visibleItems)
194  releaseItem(item);
195  m_visibleItems.clear();
196  m_firstVisibleIndex = -1;
197  setImplicitHeightDirty();
198 }
199 
200 void OrganicGrid::doRelayout()
201 {
202  const qreal moduleWidth = m_smallDelegateSize.width() * 2 + columnSpacing() * 2 + m_bigDelegateSize.width();
203  m_numberOfModulesPerRow = floor((width() + columnSpacing()) / (moduleWidth + columnSpacing()));
204  m_numberOfModulesPerRow = qMax(1, m_numberOfModulesPerRow);
205 
206  int i = m_firstVisibleIndex;
207  const QList<QQuickItem*> allItems = m_visibleItems;
208  m_visibleItems.clear();
209  Q_FOREACH(QQuickItem *item, allItems) {
210  addItemToView(i, item);
211  ++i;
212  }
213 }
214 
215 void OrganicGrid::updateItemCulling(qreal visibleFromY, qreal visibleToY)
216 {
217  Q_FOREACH(QQuickItem *item, m_visibleItems) {
218  QQuickItemPrivate::get(item)->setCulled(item->y() + item->height() <= visibleFromY || item->y() >= visibleToY);
219  }
220 }
221 
222 void OrganicGrid::calculateImplicitHeight()
223 {
224  const qreal moduleHeight = m_smallDelegateSize.height() + rowSpacing() + m_bigDelegateSize.height();
225  const int itemCount = !model() ? 0 : model()->rowCount();
226  const int itemsPerRow = m_numberOfModulesPerRow * 6;
227  const int fullRows = floor(itemCount / itemsPerRow);
228  const qreal fullRowsHeight = fullRows == 0 ? 0 : fullRows * moduleHeight + rowSpacing() * (fullRows - 1);
229 
230  const int remainingItems = itemCount - fullRows * itemsPerRow;
231  if (remainingItems == 0) {
232  setImplicitHeight(fullRowsHeight);
233  } else if (remainingItems <= 2) {
234  setImplicitHeight(fullRowsHeight + m_smallDelegateSize.height() + rowSpacing());
235  } else {
236  setImplicitHeight(fullRowsHeight + rowSpacing() + moduleHeight);
237  }
238 }
239 
240 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
241 void OrganicGrid::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
242 #else
243 void OrganicGrid::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
244 #endif
245 {
246 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
247  Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
248 #else
249  Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
250 #endif
251  for (int i = remove.count - 1; i >= 0; --i) {
252  const int indexToRemove = remove.index + i;
253  // We only support removing from the end
254  const int lastIndex = m_firstVisibleIndex + m_visibleItems.count() - 1;
255  if (indexToRemove == lastIndex) {
256  releaseItem(m_visibleItems.takeLast());
257  } else {
258  if (indexToRemove < lastIndex) {
259  qFatal("OrganicGrid only supports removal from the end of the model");
260  }
261  }
262  }
263  }
264  if (m_visibleItems.isEmpty()) {
265  m_firstVisibleIndex = -1;
266  }
267  setImplicitHeightDirty();
268 }