qskinny/src/controls/QskListViewSkinlet.cpp

533 lines
16 KiB
C++
Raw Normal View History

2017-07-21 16:21:34 +00:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
2023-04-06 07:23:37 +00:00
* SPDX-License-Identifier: BSD-3-Clause
2017-07-21 16:21:34 +00:00
*****************************************************************************/
#include "QskListViewSkinlet.h"
2018-08-03 06:30:23 +00:00
#include "QskListView.h"
2017-07-21 16:21:34 +00:00
#include "QskColorFilter.h"
2018-07-19 12:10:48 +00:00
#include "QskGraphic.h"
2020-11-22 14:27:58 +00:00
#include "QskSGNode.h"
2021-12-23 17:15:54 +00:00
#include "QskSkinStateChanger.h"
2017-07-21 16:21:34 +00:00
2018-08-03 06:15:28 +00:00
#include <qmath.h>
2018-07-19 12:10:48 +00:00
#include <qsgnode.h>
#include <qsgsimplerectnode.h>
#include <qtransform.h>
2017-07-21 16:21:34 +00:00
class QskListViewNode final : public QSGTransformNode
{
2018-08-03 06:15:28 +00:00
public:
inline QskListViewNode( int columnCount )
2019-09-12 08:44:41 +00:00
: m_columnCount( columnCount )
2017-07-21 16:21:34 +00:00
{
m_backgroundNode.setFlag( QSGNode::OwnedByParent, false );
appendChildNode( &m_backgroundNode );
m_foregroundNode.setFlag( QSGNode::OwnedByParent, false );
appendChildNode( &m_foregroundNode );
}
QSGNode* backgroundNode()
{
return &m_backgroundNode;
}
QSGNode* foregroundNode()
{
return &m_foregroundNode;
}
inline void resetRows( int rowMin, int rowMax )
{
m_rowMin = rowMin;
m_rowMax = rowMax;
}
2017-10-18 18:00:06 +00:00
2017-07-21 16:21:34 +00:00
inline int rowMin() const
{
return m_rowMin;
}
inline int rowMax() const
{
return m_rowMax;
}
inline bool intersects( int rowMin, int rowMax ) const
{
return ( rowMin <= m_rowMax ) && ( rowMax >= m_rowMin );
}
inline int nodeCount() const
{
2019-09-12 08:44:41 +00:00
return ( m_rowMin >= 0 ) ? ( m_rowMax - m_rowMin + 1 ) * m_columnCount : 0;
}
inline int columnCount() const
{
return m_columnCount;
2017-07-21 16:21:34 +00:00
}
inline void invalidate()
{
m_rowMin = m_rowMax = -1;
}
2018-08-03 06:15:28 +00:00
private:
2019-09-12 08:44:41 +00:00
int m_columnCount;
2021-12-22 14:08:27 +00:00
int m_rowMin = -1;
int m_rowMax = -1;
2017-07-21 16:21:34 +00:00
QSGNode m_backgroundNode;
QSGNode m_foregroundNode;
};
2018-08-03 06:15:28 +00:00
QskListViewSkinlet::QskListViewSkinlet( QskSkin* skin )
: Inherited( skin )
2017-07-21 16:21:34 +00:00
{
}
QskListViewSkinlet::~QskListViewSkinlet() = default;
QSGNode* QskListViewSkinlet::updateContentsNode(
const QskScrollView* scrollView, QSGNode* node ) const
{
const auto listView = static_cast< const QskListView* >( scrollView );
2017-07-21 16:21:34 +00:00
2021-12-22 14:08:27 +00:00
auto listViewNode = static_cast< QskListViewNode* >( node );
2017-07-21 16:21:34 +00:00
if ( listViewNode == nullptr )
listViewNode = new QskListViewNode( listView->columnCount() );
QTransform transform;
transform.translate( -listView->scrollPos().x(), -listView->scrollPos().y() );
listViewNode->setMatrix( transform );
updateBackgroundNodes( listView, listViewNode );
updateForegroundNodes( listView, listViewNode );
return listViewNode;
}
2023-04-28 08:47:36 +00:00
void QskListViewSkinlet::updateBackgroundNode( const QskListView* listView, QSGNode* rowNode, const QRectF& boxRect, const int row ) const
{
const auto selectedRow = listView->selectedRow();
const auto subControl = listView->rowSubControl( row );
// current row is a selected row
if ( row == selectedRow )
{
auto* boxNode = rowNode->firstChild();
auto prepend = boxNode == nullptr;
QskSkinStateChanger stateChanger( listView );
stateChanger.setStates( listView->skinStates() | QskListView::Selected );
boxNode = updateBoxNode( listView, boxNode, boxRect, subControl );
if ( boxNode && prepend )
2023-04-28 12:42:08 +00:00
{
2023-04-28 08:47:36 +00:00
rowNode->prependChildNode( boxNode );
}
}
// current row should be styled
else if ( subControl != QskAspect::NoSubcontrol)
{
auto* boxNode = rowNode->firstChild();
auto prepend = boxNode == nullptr;
const auto gradient = listView->gradientHint(subControl);
boxNode = updateBoxNode( listView, boxNode, boxRect, subControl );
if ( boxNode && prepend )
{
rowNode->prependChildNode( boxNode );
}
}
// current row should not be styled
else
{
rowNode->removeAllChildNodes();
}
}
2017-07-21 16:21:34 +00:00
void QskListViewSkinlet::updateBackgroundNodes(
const QskListView* listView, QskListViewNode* listViewNode ) const
{
QSGNode* backgroundNode = listViewNode->backgroundNode();
2017-12-07 16:12:52 +00:00
const qreal cellHeight = listView->rowHeight();
2017-07-21 16:21:34 +00:00
const QRectF viewRect = listView->viewContentsRect();
const QPointF scrolledPos = listView->scrollPos();
const int rowMin = qFloor( scrolledPos.y() / cellHeight );
int rowMax = qCeil( ( scrolledPos.y() + viewRect.height() ) / cellHeight );
if ( rowMax >= listView->rowCount() )
rowMax = listView->rowCount() - 1;
2023-04-28 08:47:36 +00:00
const double x0 = viewRect.left();
2017-07-21 16:21:34 +00:00
const double y0 = viewRect.top();
2023-04-28 08:47:36 +00:00
auto* rowNode = static_cast< QSGNode* >( backgroundNode->firstChild() );
2017-07-21 16:21:34 +00:00
if ( listView->alternatingRowColors() )
{
for ( int row = rowMin; row <= rowMax; row++ )
{
2023-04-28 08:47:36 +00:00
const auto offsetX = listView->rowOffset( row );
const QRectF boxRect{ x0 + offsetX, y0 + row * cellHeight, viewRect.width() - offsetX + scrolledPos.x(),
cellHeight };
if ( rowNode == nullptr )
2017-07-21 16:21:34 +00:00
{
2023-04-28 08:47:36 +00:00
rowNode = new QSGNode;
updateBackgroundNode( listView, rowNode, boxRect, row );
backgroundNode->appendChildNode( rowNode );
2017-07-21 16:21:34 +00:00
}
2023-04-28 08:47:36 +00:00
updateBackgroundNode( listView, rowNode, boxRect, row );
rowNode = rowNode->nextSibling();
2017-07-21 16:21:34 +00:00
}
}
QSGNode* nextNode = rowNode;
while ( nextNode != nullptr )
{
QSGNode* tmpNode = nextNode;
nextNode = nextNode->nextSibling();
delete tmpNode;
}
}
void QskListViewSkinlet::updateForegroundNodes(
const QskListView* listView, QskListViewNode* listViewNode ) const
{
auto parentNode = listViewNode->foregroundNode();
2017-07-21 16:21:34 +00:00
if ( listView->rowCount() <= 0 || listView->columnCount() <= 0 )
{
parentNode->removeAllChildNodes();
listViewNode->invalidate();
return;
}
2023-04-28 08:47:36 +00:00
const auto subControl = listView->rowSubControl(0);
const auto margins = listView->paddingHint( subControl );
2017-07-21 16:21:34 +00:00
const auto cr = listView->viewContentsRect();
const auto scrolledPos = listView->scrollPos();
2017-07-21 16:21:34 +00:00
const int rowMin = qFloor( scrolledPos.y() / listView->rowHeight() );
int rowMax = rowMin + qCeil( cr.height() / listView->rowHeight() );
if ( rowMax >= listView->rowCount() )
rowMax = listView->rowCount() - 1;
#if 1
// should be optimized for visible columns only
const int colMin = 0;
const int colMax = listView->columnCount() - 1;
#endif
bool forwards = true;
if ( listViewNode->intersects( rowMin, rowMax ) )
{
/*
We try to avoid reallcations when scrolling, by reusing
the nodes of the cells leaving the viewport for those becoming visible.
*/
forwards = ( rowMin >= listViewNode->rowMin() );
if ( forwards )
{
// usually scrolling down
for ( int row = listViewNode->rowMin(); row < rowMin; row++ )
{
for ( int col = 0; col < listView->columnCount(); col++ )
{
QSGNode* childNode = parentNode->firstChild();
parentNode->removeChildNode( childNode );
parentNode->appendChildNode( childNode );
}
}
}
else
{
// usually scrolling up
for ( int row = rowMax; row < listViewNode->rowMax(); row++ )
{
for ( int col = 0; col < listView->columnCount(); col++ )
{
QSGNode* childNode = parentNode->lastChild();
parentNode->removeChildNode( childNode );
parentNode->prependChildNode( childNode );
}
}
}
}
2023-04-28 08:47:36 +00:00
updateVisibleForegroundNodes(
listView, listViewNode, rowMin, rowMax, colMin, colMax, margins, forwards );
2017-07-21 16:21:34 +00:00
// finally putting the nodes into their position
auto node = parentNode->firstChild();
const qreal rowHeight = listView->rowHeight();
qreal y = cr.top() + rowMin * rowHeight;
for ( int row = rowMin; row <= rowMax; row++ )
{
qreal x = cr.left();
for ( int col = colMin; col <= colMax; col++ )
{
Q_ASSERT( node->type() == QSGNode::TransformNodeType );
auto transformNode = static_cast< QSGTransformNode* >( node );
2023-04-28 08:47:36 +00:00
const auto offsetX = col == 0 ? listView->rowOffset( row ) : 0.0;
2017-07-21 16:21:34 +00:00
QTransform transform;
2023-04-28 08:47:36 +00:00
transform.translate( x + margins.left() + offsetX, y + margins.top() );
2017-07-21 16:21:34 +00:00
transformNode->setMatrix( transform );
node = node->nextSibling();
x += listView->columnWidth( col );
}
2017-10-18 18:00:06 +00:00
2017-07-21 16:21:34 +00:00
y += rowHeight;
}
listViewNode->resetRows( rowMin, rowMax );
}
2023-04-28 08:47:36 +00:00
void QskListViewSkinlet::updateVisibleForegroundNodes( const QskListView* listView,
QskListViewNode* listViewNode, int rowMin, int rowMax, int colMin, int colMax,
const QMarginsF& margins, bool forward ) const
2017-07-21 16:21:34 +00:00
{
auto parentNode = listViewNode->foregroundNode();
2017-07-21 16:21:34 +00:00
const int rowCount = rowMax - rowMin + 1;
const int colCount = colMax - colMin + 1;
const int obsoleteNodesCount = listViewNode->nodeCount() - rowCount * colCount;
if ( forward )
{
for ( int i = 0; i < obsoleteNodesCount; i++ )
delete parentNode->lastChild();
auto node = parentNode->firstChild();
for ( int row = rowMin; row <= rowMax; row++ )
{
const qreal h = listView->rowHeight() - ( margins.top() + margins.bottom() );
for ( int col = 0; col < listView->columnCount(); col++ )
{
2023-04-28 08:47:36 +00:00
const auto offsetX = col == 0 ? listView->rowOffset( row ) : 0.0;
const qreal w =
listView->columnWidth( col ) - ( margins.left() + margins.right() ) - offsetX;
2017-07-21 16:21:34 +00:00
2023-04-28 08:47:36 +00:00
node = updateForegroundNode( listView, parentNode,
static_cast< QSGTransformNode* >( node ), row, col, QSizeF( w, h ), forward );
2017-07-21 16:21:34 +00:00
node = node->nextSibling();
}
}
}
else
{
for ( int i = 0; i < obsoleteNodesCount; i++ )
delete parentNode->firstChild();
auto* node = parentNode->lastChild();
for ( int row = rowMax; row >= rowMin; row-- )
{
const qreal h = listView->rowHeight() - ( margins.top() + margins.bottom() );
for ( int col = listView->columnCount() - 1; col >= 0; col-- )
{
2023-04-28 08:47:36 +00:00
const auto offsetX = col == 0 ? listView->rowOffset( row ) : 0.0;
const qreal w =
listView->columnWidth( col ) - ( margins.left() + margins.right() ) - offsetX;
2017-07-21 16:21:34 +00:00
2023-04-28 08:47:36 +00:00
node = updateForegroundNode( listView, parentNode,
static_cast< QSGTransformNode* >( node ), row, col, QSizeF( w, h ), forward );
2017-07-21 16:21:34 +00:00
node = node->previousSibling();
}
}
}
}
2023-04-28 08:47:36 +00:00
QSGTransformNode* QskListViewSkinlet::updateForegroundNode( const QskListView* listView,
QSGNode* parentNode, QSGTransformNode* cellNode, int row, int col, const QSizeF& size,
bool forward ) const
2017-07-21 16:21:34 +00:00
{
const QRectF cellRect( 0.0, 0.0, size.width(), size.height() );
/*
Text nodes already have a transform root node - to avoid inserting extra
transform nodes, the code below becomes a bit more complicated.
*/
QSGTransformNode* newCellNode = nullptr;
if ( cellNode && ( cellNode->type() == QSGNode::TransformNodeType ) )
{
QSGNode* oldNode = cellNode;
auto newNode = updateCellNode( listView, oldNode, cellRect, row, col );
if ( newNode )
{
if ( newNode->type() == QSGNode::TransformNodeType )
{
newCellNode = static_cast< QSGTransformNode* >( newNode );
}
else
{
newCellNode = new QSGTransformNode();
newCellNode->appendChildNode( newNode );
}
}
}
else
{
QSGNode* oldNode = cellNode ? cellNode->firstChild() : nullptr;
auto newNode = updateCellNode( listView, oldNode, cellRect, row, col );
if ( newNode )
{
if ( newNode->type() == QSGNode::TransformNodeType )
{
newCellNode = static_cast< QSGTransformNode* >( newNode );
}
else
{
if ( cellNode == nullptr )
{
newCellNode = new QSGTransformNode();
newCellNode->appendChildNode( newNode );
}
else
{
if ( newNode != oldNode )
{
delete cellNode->firstChild();
cellNode->appendChildNode( newNode );
newCellNode = cellNode;
}
}
}
}
}
if ( newCellNode == nullptr )
newCellNode = new QSGTransformNode();
if ( cellNode != newCellNode )
{
if ( cellNode )
{
parentNode->insertChildNodeAfter( newCellNode, cellNode );
delete cellNode;
}
else
{
if ( forward )
parentNode->appendChildNode( newCellNode );
else
parentNode->prependChildNode( newCellNode );
}
}
return newCellNode;
}
2023-04-28 08:47:36 +00:00
QSGNode* QskListViewSkinlet::updateCellNode(
const QskListView* listView, QSGNode* contentNode, const QRectF& rect, int row, int col ) const
2017-07-21 16:21:34 +00:00
{
2020-11-22 14:27:58 +00:00
using namespace QskSGNode;
auto rowStates = listView->skinStates();
2021-12-22 14:08:27 +00:00
if ( row == listView->selectedRow() )
rowStates |= QskListView::Selected;
2021-12-23 18:05:59 +00:00
2021-12-27 16:33:06 +00:00
QskSkinStateChanger stateChanger( listView );
stateChanger.setStates( rowStates );
2021-12-22 14:08:27 +00:00
2017-07-21 16:21:34 +00:00
QSGNode* newNode = nullptr;
#if 1
/*
Alignments, text options etc are user definable attributes and need
to be adjustable - at least individually for each column - from the
public API of QskListView TODO ...
*/
#endif
2023-04-28 08:47:36 +00:00
const auto subControl = listView->cellSubControl(row, col);
const auto alignment =
listView->alignmentHint( subControl, Qt::AlignVCenter | Qt::AlignLeft );
2017-07-21 16:21:34 +00:00
2020-12-18 17:26:32 +00:00
const auto value = listView->valueAt( row, col );
2017-07-21 16:21:34 +00:00
if ( value.canConvert< QskGraphic >() )
{
if ( nodeRole( contentNode ) == GraphicRole )
newNode = contentNode;
const auto colorFilter = listView->graphicFilterAt( row, col );
2023-04-28 08:47:36 +00:00
newNode = updateGraphicNode(
listView, newNode, value.value< QskGraphic >(), colorFilter, rect, alignment );
2017-07-21 16:21:34 +00:00
if ( newNode )
setNodeRole( newNode, GraphicRole );
}
else if ( value.canConvert< QString >() )
{
if ( nodeRole( contentNode ) == TextRole )
newNode = contentNode;
2023-04-28 08:47:36 +00:00
const auto subControl = listView->textSubControl( row, col );
const auto alignment = listView->alignmentHint( subControl );
newNode =
updateTextNode( listView, newNode, rect, alignment, value.toString(), subControl );
2017-07-21 16:21:34 +00:00
if ( newNode )
setNodeRole( newNode, TextRole );
}
else
{
2020-11-21 19:36:47 +00:00
qWarning() << "QskListViewSkinlet: got unsupported QVariant type" << value.typeName();
2017-07-21 16:21:34 +00:00
}
return newNode;
}
2023-04-28 08:47:36 +00:00
QSizeF QskListViewSkinlet::sizeHint(
const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const
{
const auto listView = static_cast< const QskListView* >( skinnable );
qreal w = -1.0; // shouldn't we return something ???
if ( which != Qt::MaximumSize )
{
if ( listView->preferredWidthFromColumns() )
{
w = listView->scrollableSize().width();
w += listView->metric( QskScrollView::VerticalScrollBar | QskAspect::Size );
}
}
return QSizeF( w, -1.0 );
}
2017-07-21 16:21:34 +00:00
#include "moc_QskListViewSkinlet.cpp"