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