QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskListViewSkinlet.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskListViewSkinlet.h"
7#include "QskListView.h"
8
9#include "QskColorFilter.h"
10#include "QskGraphic.h"
11#include "QskBoxHints.h"
12#include "QskSGNode.h"
13#include "QskSkinStateChanger.h"
14#include "QskQuick.h"
15
16#include <qmath.h>
17#include <qsgnode.h>
18#include <qtransform.h>
19
20namespace
21{
22 class ForegroundNode : public QSGNode
23 {
24 public:
25 void invalidate()
26 {
27 removeAllChildNodes();
28 m_columnCount = m_oldRowMin = m_oldRowMax = -1;
29 }
30
31 void rearrangeNodes( int rowMin, int rowMax, int columnCount )
32 {
33 const bool doReorder = ( columnCount == m_columnCount )
34 && ( rowMin <= m_oldRowMax ) && ( rowMax >= m_oldRowMin );
35
36 if ( doReorder )
37 {
38 /*
39 We have nodes that will be at a different position in the
40 list of child nodes - however their content might be unchanged.
41
42 This is a common situation, when resizing or scrolling the list.
43
44 To avoid that the text/graphic nodes have to be updated we
45 try to rearrange the nodes, so that they are in increasing
46 row order for further processing.
47
48 To avoid delete/new calls we append the leading and prepend
49 the trailing nodes, so that they might be used later.
50 */
51
52 if ( rowMin >= m_oldRowMin )
53 {
54 for ( int row = m_oldRowMin; row < rowMin; row++ )
55 {
56 for ( int col = 0; col < columnCount; col++ )
57 {
58 auto childNode = firstChild();
59 removeChildNode( childNode );
60 appendChildNode( childNode );
61 }
62 }
63 }
64 else
65 {
66 for ( int row = rowMin; row < m_oldRowMin; row++ )
67 {
68 for ( int col = 0; col < columnCount; col++ )
69 {
70 auto childNode = lastChild();
71 removeChildNode( childNode );
72 prependChildNode( childNode );
73 }
74 }
75 }
76 }
77
78 m_oldRowMin = rowMin;
79 m_oldRowMax = rowMax;
80 m_columnCount = columnCount;
81 }
82
83 private:
84 /*
85 When scrolling the majority of the child nodes are simply translated
86 while only few rows appear/disappear. To implement this in an efficient
87 way we store how the nodes were related to the rows in the previous update,
88 */
89
90 int m_oldRowMin = -1;
91 int m_oldRowMax = -1;
92 int m_columnCount = -1;
93 };
94
95 class ListViewNode final : public QSGTransformNode
96 {
97 public:
98 inline ListViewNode()
99 {
100 m_backgroundNode.setFlag( QSGNode::OwnedByParent, false );
101 appendChildNode( &m_backgroundNode );
102
103 m_foregroundNode.setFlag( QSGNode::OwnedByParent, false );
104 appendChildNode( &m_foregroundNode );
105 }
106
107 void initialize( const QskListView* listView )
108 {
109 const auto scrollPos = listView->scrollPos();
110 setMatrix( QTransform::fromTranslate( -scrollPos.x(), -scrollPos.y() ) );
111
112 m_clipRect = listView->viewContentsRect();
113 m_rowHeight = listView->rowHeight();
114
115 m_rowMin = qFloor( scrollPos.y() / m_rowHeight );
116
117 const auto rowMax = ( scrollPos.y() + m_clipRect.height() ) / m_rowHeight;
118 m_rowMax = qFloor( rowMax - 10e-6 );
119
120 if ( m_rowMax >= listView->rowCount() )
121 m_rowMax = listView->rowCount() - 1;
122 }
123
124 QRectF clipRect() const { return m_clipRect; }
125
126 int rowMin() const { return m_rowMin; }
127 int rowMax() const { return m_rowMax; }
128 int rowCount() const { return m_rowMax - m_rowMin + 1; }
129
130 int rowHeight() const { return m_rowHeight; }
131
132 QSGNode* backgroundNode() { return &m_backgroundNode; }
133 ForegroundNode* foregroundNode() { return &m_foregroundNode; }
134
135 private:
136 // caching some calculations to speed things up
137
138 QRectF m_clipRect;
139 qreal m_rowHeight;
140
141 int m_rowMin, m_rowMax;
142
143 QSGNode m_backgroundNode;
144 ForegroundNode m_foregroundNode;
145 };
146}
147
148static inline ListViewNode* qskListViewNode( const QskListView* listView )
149{
150 if ( auto node = const_cast< QSGNode* >( qskPaintNode( listView ) ) )
151 {
152 using namespace QskSGNode;
153
154 node = findChildNode( node, QskScrollViewSkinlet::ContentsRootRole );
155 if ( node )
156 {
157 node = node->firstChild();
158 if ( node )
159 {
160 Q_ASSERT( node->type() == QSGNode::TransformNodeType );
161 return static_cast< ListViewNode* >( node );
162 }
163 }
164 }
165
166 return nullptr;
167}
168
169static inline ListViewNode* qskListViewNode( const QskSkinnable* skinnable )
170{
171 return qskListViewNode( static_cast< const QskListView* >( skinnable ) );
172}
173
174QskListViewSkinlet::QskListViewSkinlet( QskSkin* skin )
175 : Inherited( skin )
176{
177}
178
179QskListViewSkinlet::~QskListViewSkinlet() = default;
180
181QSGNode* QskListViewSkinlet::updateContentsNode(
182 const QskScrollView* scrollView, QSGNode* node ) const
183{
184 const auto listView = static_cast< const QskListView* >( scrollView );
185
186 auto listViewNode = QskSGNode::ensureNode< ListViewNode >( node );
187 listViewNode->initialize( listView );
188
189 updateBackgroundNodes( listView, listViewNode->backgroundNode() );
190 updateForegroundNodes( listView, listViewNode->foregroundNode() );
191
192 return listViewNode;
193}
194
195void QskListViewSkinlet::updateBackgroundNodes(
196 const QskListView* listView, QSGNode* backgroundNode ) const
197{
198 using Q = QskListView;
199
200 auto listViewNode = static_cast< const ListViewNode* >( backgroundNode->parent() );
201
202 auto rowNode = backgroundNode->firstChild();
203
204 for ( int row = listViewNode->rowMin(); row <= listViewNode->rowMax(); row++ )
205 {
206 QskSkinStateChanger stateChanger( listView );
207 stateChanger.setStates( sampleStates( listView, Q::Cell, row ), row );
208
209 const auto rect = sampleRect( listView, listView->contentsRect(), Q::Cell, row );
210
211 auto newNode = updateBoxNode( listView, rowNode, rect, Q::Cell );
212 if ( newNode )
213 {
214 if ( newNode->parent() != backgroundNode )
215 backgroundNode->appendChildNode( newNode );
216 else
217 rowNode = newNode->nextSibling();
218 }
219 }
220
221 QskSGNode::removeAllChildNodesFrom( backgroundNode, rowNode );
222}
223
224void QskListViewSkinlet::updateForegroundNodes(
225 const QskListView* listView, QSGNode* parentNode ) const
226{
227 auto foregroundNode = static_cast< ForegroundNode* >( parentNode );
228
229 if ( listView->rowCount() <= 0 || listView->columnCount() <= 0 )
230 {
231 foregroundNode->invalidate();
232 return;
233 }
234
235 auto listViewNode = static_cast< const ListViewNode* >( parentNode->parent() );
236
237 const auto clipRect = listViewNode->clipRect();
238
239 const int rowMin = listViewNode->rowMin();
240 const int rowMax = listViewNode->rowMax();
241
242 foregroundNode->rearrangeNodes( rowMin, rowMax, listView->columnCount() );
243
244#if 1
245 // should be optimized for visible columns only
246 const int colMin = 0;
247 const int colMax = listView->columnCount() - 1;
248#endif
249
250 const auto margins = listView->paddingHint( QskListView::Cell );
251
252 updateVisibleForegroundNodes(
253 listView, foregroundNode, rowMin, rowMax, margins );
254
255 // finally putting the nodes into their position
256 auto node = foregroundNode->firstChild();
257
258 const auto rowHeight = listView->rowHeight();
259 auto y = clipRect.top() + rowMin * rowHeight;
260
261 for ( int row = rowMin; row <= rowMax; row++ )
262 {
263 qreal x = clipRect.left();
264
265 for ( int col = colMin; col <= colMax; col++ )
266 {
267 Q_ASSERT( node->type() == QSGNode::TransformNodeType );
268 auto transformNode = static_cast< QSGTransformNode* >( node );
269
270 transformNode->setMatrix(
271 QTransform::fromTranslate( x + margins.left(), y + margins.top() ) );
272
273 node = node->nextSibling();
274 x += listView->columnWidth( col );
275 }
276
277 y += rowHeight;
278 }
279}
280
281void QskListViewSkinlet::updateVisibleForegroundNodes(
282 const QskListView* listView, QSGNode* parentNode,
283 int rowMin, int rowMax, const QMarginsF& margins ) const
284{
285 auto node = parentNode->firstChild();
286
287 for ( int row = rowMin; row <= rowMax; row++ )
288 {
289 const auto h = listView->rowHeight() - ( margins.top() + margins.bottom() );
290
291 for ( int col = 0; col < listView->columnCount(); col++ )
292 {
293 const auto w = listView->columnWidth( col ) - ( margins.left() + margins.right() );
294
295 node = updateForegroundNode( listView,
296 parentNode, static_cast< QSGTransformNode* >( node ),
297 row, col, QSizeF( w, h ) );
298
299 node = node->nextSibling();
300 }
301 }
302
303 QskSGNode::removeAllChildNodesFrom( parentNode, node );
304}
305
306QSGTransformNode* QskListViewSkinlet::updateForegroundNode(
307 const QskListView* listView, QSGNode* parentNode, QSGTransformNode* cellNode,
308 int row, int col, const QSizeF& size ) const
309{
310 const QRectF cellRect( 0.0, 0.0, size.width(), size.height() );
311
312 /*
313 Text nodes already have a transform root node - to avoid inserting extra
314 transform nodes, the code below becomes a bit more complicated.
315 */
316 QSGTransformNode* newCellNode = nullptr;
317
318 if ( cellNode && ( cellNode->type() == QSGNode::TransformNodeType ) )
319 {
320 QSGNode* oldNode = cellNode;
321
322 auto newNode = updateCellNode( listView, oldNode, cellRect, row, col );
323 if ( newNode )
324 {
325 if ( newNode->type() == QSGNode::TransformNodeType )
326 {
327 newCellNode = static_cast< QSGTransformNode* >( newNode );
328 }
329 else
330 {
331 newCellNode = new QSGTransformNode();
332 newCellNode->appendChildNode( newNode );
333 }
334 }
335 }
336 else
337 {
338 QSGNode* oldNode = cellNode ? cellNode->firstChild() : nullptr;
339 auto newNode = updateCellNode( listView, oldNode, cellRect, row, col );
340
341 if ( newNode )
342 {
343 if ( newNode->type() == QSGNode::TransformNodeType )
344 {
345 newCellNode = static_cast< QSGTransformNode* >( newNode );
346 }
347 else
348 {
349 if ( cellNode == nullptr )
350 {
351 newCellNode = new QSGTransformNode();
352 newCellNode->appendChildNode( newNode );
353 }
354 else
355 {
356 if ( newNode != oldNode )
357 {
358 delete cellNode->firstChild();
359 cellNode->appendChildNode( newNode );
360
361 newCellNode = cellNode;
362 }
363 }
364 }
365 }
366 }
367
368 if ( newCellNode == nullptr )
369 newCellNode = new QSGTransformNode();
370
371 if ( cellNode != newCellNode )
372 {
373 if ( cellNode )
374 {
375 parentNode->insertChildNodeAfter( newCellNode, cellNode );
376 delete cellNode;
377 }
378 else
379 {
380 parentNode->appendChildNode( newCellNode );
381 }
382 }
383
384 return newCellNode;
385}
386
387QSGNode* QskListViewSkinlet::updateCellNode( const QskListView* listView,
388 QSGNode* contentNode, const QRectF& rect, int row, int col ) const
389{
390 using Q = QskListView;
391 using namespace QskSGNode;
392
393 QskSkinStateChanger stateChanger( listView );
394 stateChanger.setStates( sampleStates( listView, Q::Cell, row ), row );
395
396 QSGNode* newNode = nullptr;
397
398#if 1
399 /*
400 Alignments, text options etc are user definable attributes and need
401 to be adjustable - at least individually for each column - from the
402 public API of QskListView TODO ...
403 */
404#endif
405 const auto alignment = listView->alignmentHint(
406 Q::Cell, Qt::AlignVCenter | Qt::AlignLeft );
407
408 const auto value = listView->valueAt( row, col );
409
410 if ( value.canConvert< QskGraphic >() )
411 {
412 if ( nodeRole( contentNode ) == GraphicRole )
413 newNode = contentNode;
414
415 const auto colorFilter = listView->effectiveGraphicFilter( Q::Graphic );
416
417 newNode = updateGraphicNode( listView, newNode,
418 value.value< QskGraphic >(), colorFilter, rect, alignment );
419
420 if ( newNode )
421 setNodeRole( newNode, GraphicRole );
422 }
423 else if ( value.canConvert< QString >() )
424 {
425 if ( nodeRole( contentNode ) == TextRole )
426 newNode = contentNode;
427
428 newNode = updateTextNode( listView, newNode, rect, alignment,
429 value.toString(), Q::Text );
430
431 if ( newNode )
432 setNodeRole( newNode, TextRole );
433 }
434 else
435 {
436 qWarning() << "QskListViewSkinlet: got unsupported QVariant type" << value.typeName();
437 }
438
439 return newNode;
440}
441
442QSizeF QskListViewSkinlet::sizeHint( const QskSkinnable* skinnable,
443 Qt::SizeHint which, const QSizeF& ) const
444{
445 const auto listView = static_cast< const QskListView* >( skinnable );
446
447 qreal w = -1.0; // shouldn't we return something ???
448
449 if ( which != Qt::MaximumSize )
450 {
451 if ( listView->preferredWidthFromColumns() )
452 {
453 w = listView->scrollableSize().width();
454 w += listView->metric( QskScrollView::VerticalScrollBar | QskAspect::Size );
455 }
456 }
457
458 return QSizeF( w, -1.0 );
459}
460
461QskAspect::States QskListViewSkinlet::sampleStates( const QskSkinnable* skinnable,
462 QskAspect::Subcontrol subControl, int index ) const
463{
464 using Q = QskListView;
465
466 if ( subControl == Q::Cell || subControl == Q::Text || subControl == Q::Graphic )
467 {
468 const auto listView = static_cast< const QskListView* >( skinnable );
469 return listView->rowStates( index );
470 }
471
472 return Inherited::sampleStates( skinnable, subControl, index );
473}
474
475QRectF QskListViewSkinlet::sampleRect( const QskSkinnable* skinnable,
476 const QRectF& contentsRect, QskAspect::Subcontrol subControl, int index ) const
477{
478 using Q = QskListView;
479
480 const auto listView = static_cast< const QskListView* >( skinnable );
481
482 if ( subControl == Q::Cell )
483 {
484 auto node = qskListViewNode( listView );
485 const auto clipRect = node ? node->clipRect() : listView->viewContentsRect();
486
487 const auto w = clipRect.width();
488 const auto h = listView->rowHeight();
489 const auto x = clipRect.left() + listView->scrollPos().x();
490 const auto y = clipRect.top() + index * h;
491
492 return QRectF( x, y, w, h );
493 }
494
495 return Inherited::sampleRect( skinnable, contentsRect, subControl, index );
496}
497
498#include "moc_QskListViewSkinlet.cpp"
Subcontrol
For use within the rendering or lay-outing of a specific QskSkinnable.
Definition QskAspect.h:104
QRectF contentsRect() const
A paint device for scalable graphics.
Definition QskGraphic.h:28
QMarginsF paddingHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a padding hint.
QskColorFilter effectiveGraphicFilter(QskAspect::Subcontrol) const
qreal metric(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a metric hint.
Qt::Alignment alignmentHint(QskAspect, Qt::Alignment=Qt::Alignment()) const
Retrieves an alignment hint.