Unity 8
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 OrganicGrid::OrganicGrid()
25  : m_firstVisibleIndex(-1)
26  , m_numberOfModulesPerRow(-1)
27 {
28 }
29 
30 QSizeF OrganicGrid::smallDelegateSize() const
31 {
32  return m_smallDelegateSize;
33 }
34 
35 void OrganicGrid::setSmallDelegateSize(const QSizeF &size)
36 {
37  if (m_smallDelegateSize != size) {
38  m_smallDelegateSize = size;
39  Q_EMIT smallDelegateSizeChanged();
40 
41  if (isComponentComplete()) {
42  relayout();
43  }
44  }
45 }
46 
47 QSizeF OrganicGrid::bigDelegateSize() const
48 {
49  return m_bigDelegateSize;
50 }
51 
52 void OrganicGrid::setBigDelegateSize(const QSizeF &size)
53 {
54  if (m_bigDelegateSize != size) {
55  m_bigDelegateSize = size;
56  Q_EMIT bigDelegateSizeChanged();
57 
58  if (isComponentComplete()) {
59  relayout();
60  }
61  }
62 }
63 
64 QPointF OrganicGrid::positionForIndex(int modelIndex) const
65 {
66  const qreal moduleHeight = m_smallDelegateSize.height() + rowSpacing() + m_bigDelegateSize.height();
67  const qreal moduleWidth = m_smallDelegateSize.width() * 2 + columnSpacing() * 2 + m_bigDelegateSize.width();
68  const int itemsPerRow = m_numberOfModulesPerRow * 6;
69  const int rowIndex = floor(modelIndex / itemsPerRow);
70  const int columnIndex = floor((modelIndex - rowIndex * itemsPerRow) / 6);
71 
72  qreal yPos = (moduleHeight + rowSpacing()) * rowIndex;
73  const int moduleIndex = modelIndex % 6;
74  if (moduleIndex == 2) {
75  yPos += m_smallDelegateSize.height() + rowSpacing();
76  } else if (moduleIndex == 3 || moduleIndex == 5) {
77  yPos += m_bigDelegateSize.height() + rowSpacing();
78  }
79 
80  qreal xPos = (moduleWidth + columnSpacing()) * columnIndex;
81  if (moduleIndex == 1) {
82  xPos += m_smallDelegateSize.width() + columnSpacing();
83  } else if (moduleIndex == 3) {
84  xPos += m_bigDelegateSize.width() + columnSpacing();
85  } else if (moduleIndex == 4) {
86  xPos += (m_smallDelegateSize.width() + columnSpacing()) * 2;
87  } else if (moduleIndex == 5) {
88  xPos += m_bigDelegateSize.width() + m_smallDelegateSize.width() + columnSpacing() * 2;
89  }
90 
91  return QPointF(xPos, yPos);
92 }
93 
94 QSizeF OrganicGrid::sizeForIndex(int modelIndex) const
95 {
96  const int moduleIndex = modelIndex % 6;
97  if (moduleIndex == 0 || moduleIndex == 1 || moduleIndex == 3 || moduleIndex == 5) {
98  return m_smallDelegateSize;
99  } else {
100  return m_bigDelegateSize;
101  }
102 }
103 
104 void OrganicGrid::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
105 {
106  if (m_visibleItems.isEmpty()) {
107  *modelIndex = 0;
108  *yPos = 0;
109  } else {
110  *modelIndex = m_firstVisibleIndex + m_visibleItems.count();
111  // We create stuff in a 6-module basis, so always return back
112  // the y position of the first item
113  const int firstModuleIndex = ((*modelIndex) / 6) * 6;
114  *yPos = positionForIndex(firstModuleIndex).y();
115  }
116 }
117 
118 void OrganicGrid::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
119 {
120  if (m_visibleItems.isEmpty()) {
121  *modelIndex = 0;
122  *yPos = 0;
123  } else {
124  *modelIndex = m_firstVisibleIndex - 1;
125  // We create stuff in a 6-module basis, so always return back
126  // the y position of the last item bottom
127  const int lastModuleIndex = ((*modelIndex) / 6) * 6 + 5;
128  *yPos = positionForIndex(lastModuleIndex).y();
129  *yPos += sizeForIndex(lastModuleIndex).height();
130  }
131 }
132 
133 void OrganicGrid::addItemToView(int modelIndex, QQuickItem *item)
134 {
135  // modelIndex has to be either m_firstVisibleIndex - 1 or m_firstVisibleIndex + m_visibleItems.count() or the first
136  if (modelIndex == m_firstVisibleIndex + m_visibleItems.count()) {
137  m_visibleItems << item;
138  } else if (modelIndex == m_firstVisibleIndex - 1) {
139  m_firstVisibleIndex = modelIndex;
140  m_visibleItems.prepend(item);
141  } else if (modelIndex == 0) {
142  m_firstVisibleIndex = 0;
143  m_visibleItems << item;
144  } else {
145  qWarning() << "OrganicGrid::addItemToView - Got unexpected modelIndex"
146  << modelIndex << m_firstVisibleIndex << m_visibleItems.count();
147  return;
148  }
149 
150  const QPointF pos = positionForIndex(modelIndex);
151  item->setPosition(pos);
152 
153  item->setSize(sizeForIndex(modelIndex));
154 }
155 
156 bool OrganicGrid::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
157 {
158  bool changed = false;
159 
160  // As adding, we also remove in a 6-module basis
161  int lastModuleIndex = (m_firstVisibleIndex / 6) * 6 + 5;
162  bool removeIndex = positionForIndex(lastModuleIndex).y() + sizeForIndex(lastModuleIndex).height() < bufferFromY;
163  while (removeIndex && !m_visibleItems.isEmpty()) {
164  releaseItem(m_visibleItems.takeFirst());
165  changed = true;
166  m_firstVisibleIndex++;
167 
168  lastModuleIndex = (m_firstVisibleIndex / 6) * 6 + 5;
169  removeIndex = positionForIndex(lastModuleIndex).y() + sizeForIndex(lastModuleIndex).height() < bufferFromY;
170  }
171 
172  int firstModuleIndex = ((m_firstVisibleIndex + m_visibleItems.count() - 1) / 6) * 6;
173  removeIndex = positionForIndex(firstModuleIndex).y() > bufferToY;
174  while (removeIndex && !m_visibleItems.isEmpty()) {
175  releaseItem(m_visibleItems.takeLast());
176  changed = true;
177 
178  firstModuleIndex = ((m_firstVisibleIndex + m_visibleItems.count() - 1) / 6) * 6;
179  removeIndex = positionForIndex(firstModuleIndex).y() > bufferToY;
180  }
181 
182  if (m_visibleItems.isEmpty()) {
183  m_firstVisibleIndex = -1;
184  }
185 
186  return changed;
187 }
188 
189 void OrganicGrid::cleanupExistingItems()
190 {
191  Q_FOREACH(QQuickItem *item, m_visibleItems)
192  releaseItem(item);
193  m_visibleItems.clear();
194  m_firstVisibleIndex = -1;
195  setImplicitHeightDirty();
196 }
197 
198 void OrganicGrid::doRelayout()
199 {
200  const qreal moduleWidth = m_smallDelegateSize.width() * 2 + columnSpacing() * 2 + m_bigDelegateSize.width();
201  m_numberOfModulesPerRow = floor((width() + columnSpacing()) / (moduleWidth + columnSpacing()));
202  m_numberOfModulesPerRow = qMax(1, m_numberOfModulesPerRow);
203 
204  int i = m_firstVisibleIndex;
205  const QList<QQuickItem*> allItems = m_visibleItems;
206  m_visibleItems.clear();
207  Q_FOREACH(QQuickItem *item, allItems) {
208  addItemToView(i, item);
209  ++i;
210  }
211 }
212 
213 void OrganicGrid::updateItemCulling(qreal visibleFromY, qreal visibleToY)
214 {
215  Q_FOREACH(QQuickItem *item, m_visibleItems) {
216  QQuickItemPrivate::get(item)->setCulled(item->y() + item->height() <= visibleFromY || item->y() >= visibleToY);
217  }
218 }
219 
220 void OrganicGrid::calculateImplicitHeight()
221 {
222  const qreal moduleHeight = m_smallDelegateSize.height() + rowSpacing() + m_bigDelegateSize.height();
223  const int itemCount = !model() ? 0 : model()->rowCount();
224  const int itemsPerRow = m_numberOfModulesPerRow * 6;
225  const int fullRows = floor(itemCount / itemsPerRow);
226  const qreal fullRowsHeight = fullRows == 0 ? 0 : fullRows * moduleHeight + rowSpacing() * (fullRows - 1);
227 
228  const int remainingItems = itemCount - fullRows * itemsPerRow;
229  if (remainingItems == 0) {
230  setImplicitHeight(fullRowsHeight);
231  } else if (remainingItems <= 2) {
232  setImplicitHeight(fullRowsHeight + m_smallDelegateSize.height() + rowSpacing());
233  } else {
234  setImplicitHeight(fullRowsHeight + rowSpacing() + moduleHeight);
235  }
236 }
237 
238 void OrganicGrid::processModelRemoves(const QVector<QQmlChangeSet::Change> &removes)
239 {
240  Q_FOREACH(const QQmlChangeSet::Change &remove, removes) {
241  for (int i = remove.count - 1; i >= 0; --i) {
242  const int indexToRemove = remove.index + i;
243  // We only support removing from the end
244  const int lastIndex = m_firstVisibleIndex + m_visibleItems.count() - 1;
245  if (indexToRemove == lastIndex) {
246  releaseItem(m_visibleItems.takeLast());
247  } else {
248  if (indexToRemove < lastIndex) {
249  qFatal("OrganicGrid only supports removal from the end of the model");
250  }
251  }
252  }
253  }
254  if (m_visibleItems.isEmpty()) {
255  m_firstVisibleIndex = -1;
256  }
257  setImplicitHeightDirty();
258 }