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