QskMenuSkinlet improved

This commit is contained in:
Uwe Rathmann 2021-12-28 15:33:13 +01:00
parent f1a324b216
commit 9fff09144a
4 changed files with 396 additions and 242 deletions

View File

@ -15,12 +15,12 @@
#include <qfontmetrics.h>
template< class T >
static T qskValueAt( const QskMenu* menu, int index )
static inline QVariant qskValueAt( const QskMenu* menu, int index )
{
const auto item = menu->itemAt( index );
if ( item.canConvert< T >() )
return item.value< T >();
return item;
if ( item.canConvert< QVariantList >() )
{
@ -28,37 +28,96 @@ static T qskValueAt( const QskMenu* menu, int index )
for ( const auto& value : list )
{
if ( value.canConvert< T >() )
return value.value< T >();
return value;
}
}
return T();
return QVariant();
}
static qreal qskMaxTextWidth( const QskMenu* menu )
class QskMenuSkinlet::PrivateData
{
const QFontMetricsF fm( menu->effectiveFont( QskMenu::Text ) );
auto maxWidth = 0.0;
for ( int i = 0; i < menu->count(); i++ )
public:
class CacheGuard
{
const auto value = menu->itemAt( i );
const auto text = qskValueAt< QString >( menu, i );
if( !text.isEmpty() )
public:
CacheGuard( QskMenuSkinlet::PrivateData* data )
: m_data( data )
{
const auto w = qskHorizontalAdvance( fm, text );
if( w > maxWidth )
maxWidth = w;
}
m_data->enableCache( true );
}
return maxWidth;
~CacheGuard()
{
m_data->enableCache( false );
}
private:
QskMenuSkinlet::PrivateData* m_data;
};
void enableCache( bool on )
{
m_isCaching = on;
m_cellHeight = m_cellWidth = m_graphicWidth = m_textWidth = -1.0;
}
static qreal qskGraphicWidth( const QskMenu* menu )
inline qreal graphicWidth( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_graphicWidth < 0.0 )
m_graphicWidth = graphicWidthInternal( menu );
return m_graphicWidth;
}
return graphicWidthInternal( menu );
}
inline qreal textWidth( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_textWidth < 0.0 )
m_textWidth = textWidthInternal( menu );
return m_textWidth;
}
return textWidthInternal( menu );
}
inline qreal cellWidth( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_cellWidth < 0.0 )
m_cellWidth = cellWidthInternal( menu );
return m_cellWidth;
}
return cellWidthInternal( menu );
}
inline qreal cellHeight( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_cellHeight < 0.0 )
m_cellHeight = cellHeightInternal( menu );
return m_cellHeight;
}
return cellHeightInternal( menu );
}
private:
qreal graphicWidthInternal( const QskMenu* menu ) const
{
const auto skinlet = menu->effectiveSkinlet();
const auto hint = menu->strutSizeHint( QskMenu::Graphic );
const qreal textHeight = menu->effectiveFontHeight( QskMenu::Text );
@ -67,24 +126,56 @@ static qreal qskGraphicWidth( const QskMenu* menu )
qreal maxW = 0.0;
for ( int i = 0; i < menu->count(); i++ )
{
const auto graphic = qskValueAt< QskGraphic >( menu, i );
const auto value = skinlet->valueAt( menu, QskMenu::Graphic, i );
if ( value.canConvert< QskGraphic >() )
{
const auto graphic = value.value< QskGraphic >();
if ( !graphic.isNull() )
{
const auto w = graphic.widthForHeight( h );
if( w > maxW )
maxW = w;
}
}
}
return qMax( hint.width(), maxW );
}
static qreal qskCellWidth( const QskMenu* menu )
qreal textWidthInternal( const QskMenu* menu ) const
{
const auto skinlet = menu->effectiveSkinlet();
const QFontMetricsF fm( menu->effectiveFont( QskMenu::Text ) );
auto maxWidth = 0.0;
for ( int i = 0; i < menu->count(); i++ )
{
const auto value = skinlet->valueAt( menu, QskMenu::Text, i );
if ( value.canConvert< QString >() )
{
const auto text = value.value< QString >();
if( !text.isEmpty() )
{
const auto w = qskHorizontalAdvance( fm, text );
if( w > maxWidth )
maxWidth = w;
}
}
}
return maxWidth;
}
qreal cellWidthInternal( const QskMenu* menu ) const
{
using Q = QskMenu;
const auto spacing = menu->spacingHint( Q::Cell );
const auto padding = menu->paddingHint( Q::Cell );
auto w = qskGraphicWidth( menu )
+ spacing + qskMaxTextWidth( menu );
auto w = graphicWidth( menu ) + spacing + textWidth( menu );
w += padding.left() + padding.right();
@ -92,7 +183,7 @@ static qreal qskCellWidth( const QskMenu* menu )
return qMax( w, minWidth );
}
static qreal qskCellHeight( const QskMenu* menu )
qreal cellHeightInternal( const QskMenu* menu ) const
{
using Q = QskMenu;
@ -109,153 +200,17 @@ static qreal qskCellHeight( const QskMenu* menu )
return h;
}
static QSGNode* qskUpdateGraphicNode( const QskMenu* menu,
const QRectF& rect, const QskGraphic& graphic, QSGNode* node )
{
const auto alignment = menu->alignmentHint( QskMenu::Graphic, Qt::AlignCenter );
const auto colorFilter = menu->effectiveGraphicFilter( QskMenu::Graphic );
bool m_isCaching;
return QskSkinlet::updateGraphicNode(
menu, node, graphic, colorFilter, rect, alignment );
}
static QSGNode* qskUpdateTextNode( const QskMenu* menu,
const QRectF& rect, const QString& text, QSGNode* node )
{
const auto alignment = menu->alignmentHint(
QskMenu::Text, Qt::AlignVCenter | Qt::AlignLeft );
return QskSkinlet::updateTextNode( menu, node, rect, alignment,
text, menu->textOptions(), QskMenu::Text );
}
static QSGNode* qskUpdateBackgroundNode( const QskMenu* menu, QSGNode* rootNode )
{
using Q = QskMenu;
const auto skinlet = menu->effectiveSkinlet();
auto node = rootNode ? rootNode->firstChild() : nullptr;
QSGNode* lastNode = nullptr;
for( int i = 0; i < menu->count(); i++ )
{
QSGNode* newNode = nullptr;
{
QskSkinStateChanger stateChanger( menu );
stateChanger.setStates(
skinlet->subControlCellStates( menu, Q::Cell, i ) );
newNode = QskSkinlet::updateBoxNode(
menu, node, menu->cellRect( i ), Q::Cell );
}
if ( newNode )
{
if ( newNode == node )
{
node = node->nextSibling();
}
else
{
if ( rootNode == nullptr )
rootNode = new QSGNode();
if ( node )
rootNode->insertChildNodeBefore( newNode, node );
else
rootNode->appendChildNode( newNode );
}
lastNode = newNode;
}
}
QskSGNode::removeAllChildNodesAfter( rootNode, lastNode );
return rootNode;
}
static void qskUpdateItemNode(
const QskMenu* menu, const QRectF& graphicRect, const QskGraphic& graphic,
const QRectF& textRect, const QString& text, QSGNode* itemNode )
{
enum { GraphicRole, TextRole };
static QVector< quint8 > roles = { GraphicRole, TextRole };
for ( const auto role : roles )
{
auto oldNode = QskSGNode::findChildNode( itemNode, role );
QSGNode* newNode = nullptr;
if( role == GraphicRole )
newNode = qskUpdateGraphicNode( menu, graphicRect, graphic, oldNode );
else
newNode = qskUpdateTextNode( menu, textRect, text, oldNode );
QskSGNode::replaceChildNode( roles, role, itemNode, oldNode, newNode );
}
}
static QSGNode* qskUpdateItemsNode( const QskMenu* menu, QSGNode* rootNode )
{
using Q = QskMenu;
const auto skinlet = menu->effectiveSkinlet();
const auto spacing = menu->spacingHint( Q::Cell );
const auto graphicWidth = qskGraphicWidth( menu );
const auto contentsRect = menu->contentsRect();
if ( rootNode == nullptr )
rootNode = new QSGNode();
QSGNode* node = nullptr;
const auto count = skinlet->subControlCellCount( menu, Q::Cell );
for( int i = 0; i < count; i++ )
{
if ( node == nullptr )
node = rootNode->firstChild();
else
node = node->nextSibling();
if ( node == nullptr )
{
node = new QSGNode();
rootNode->appendChildNode( node );
}
{
QskSkinStateChanger stateChanger( menu );
stateChanger.setStates(
skinlet->subControlCellStates( menu, Q::Cell, i ) );
const auto cellRect = skinlet->subControlCell(
menu, contentsRect, Q::Cell, i );
auto graphicRect = cellRect;
graphicRect.setWidth( graphicWidth );
auto textRect = cellRect;
textRect.setX( graphicRect.right() + spacing );
const auto graphic = qskValueAt< QskGraphic >( menu, i );
const auto text = qskValueAt< QString >( menu, i );
qskUpdateItemNode( menu, graphicRect, graphic,
textRect, text, node );
}
}
QskSGNode::removeAllChildNodesAfter( rootNode, node );
return rootNode;
}
mutable qreal m_graphicWidth = -1.0;
mutable qreal m_textWidth = -1.0;
mutable qreal m_cellHeight = -1.0;
mutable qreal m_cellWidth = -1.0;
};
QskMenuSkinlet::QskMenuSkinlet( QskSkin* skin )
: Inherited( skin )
, m_data( new PrivateData() )
{
appendNodeRoles( { PanelRole } );
}
@ -283,29 +238,62 @@ QRectF QskMenuSkinlet::subControlCell(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, int index ) const
{
// QskMenu::Text, QskMenu::Graphic ???
if ( subControl == QskMenu::Cell )
{
using Q = QskMenu;
const auto menu = static_cast< const QskMenu* >( skinnable );
if ( index < 0 || index >= menu->count() )
return QRectF();
const auto r = menu->subControlContentsRect( QskMenu::Panel );
const auto h = qskCellHeight( menu );
if ( subControl == Q::Cell )
{
const auto r = menu->subControlContentsRect( Q::Panel );
const auto h = m_data->cellHeight( menu );
return QRectF( r.x(), r.y() + index * h, r.width(), h );
}
if ( subControl == QskMenu::Graphic || subControl == QskMenu::Text )
{
const auto r = subControlCell( menu, contentsRect, Q::Cell, index );
const auto graphicWidth = m_data->graphicWidth( menu );
if ( subControl == QskMenu::Graphic )
{
auto graphicRect = r;
graphicRect.setWidth( graphicWidth );
return QRectF( r.x(), r.y(), graphicWidth, r.height() );
}
else
{
auto textRect = r;
if ( graphicWidth > 0.0 )
{
const auto spacing = skinnable->spacingHint( Q::Cell );
textRect.setX( r.x() + graphicWidth + spacing );
}
return textRect;
}
}
return Inherited::subControlCell(
skinnable, contentsRect, subControl, index );
}
int QskMenuSkinlet::subControlCellIndexAt(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, const QPointF& pos ) const
{
const PrivateData::CacheGuard guard( m_data.get() );
return Inherited::subControlCellIndexAt( skinnable, contentsRect, subControl, pos );
}
int QskMenuSkinlet::subControlCellCount(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const
{
// QskMenu::Text, QskMenu::Graphic ???
if ( subControl == QskMenu::Cell )
using Q = QskMenu;
if ( subControl == Q::Cell || subControl == Q::Graphic || subControl == Q::Text )
{
const auto menu = static_cast< const QskMenu* >( skinnable );
return menu->count();
@ -317,10 +305,11 @@ int QskMenuSkinlet::subControlCellCount(
QskAspect::States QskMenuSkinlet::subControlCellStates(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const
{
using Q = QskMenu;
auto states = Inherited::subControlCellStates( skinnable, subControl, index );
// QskMenu::Text, QskMenu::Graphic ???
if ( subControl == QskMenu::Cell )
if ( subControl == Q::Cell || subControl == Q::Graphic || subControl == Q::Text )
{
const auto menu = static_cast< const QskMenu* >( skinnable );
if ( menu->currentIndex() == index )
@ -330,17 +319,38 @@ QskAspect::States QskMenuSkinlet::subControlCellStates(
return states;
}
QVariant QskMenuSkinlet::valueAt( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl, int index ) const
{
using Q = QskMenu;
const auto menu = static_cast< const QskMenu* >( skinnable );
if ( subControl == Q::Graphic )
return qskValueAt< QskGraphic >( menu, index );
if ( subControl == Q::Text )
return qskValueAt< QString >( menu, index );
return Inherited::valueAt( skinnable, subControl, index );
}
QSGNode* QskMenuSkinlet::updateContentsNode(
const QskPopup* popup, QSGNode* contentsNode ) const
{
enum { Panel, Backgrounds, Cursor, Items };
static QVector< quint8 > roles = { Panel, Backgrounds, Cursor, Items };
const PrivateData::CacheGuard guard( m_data.get() );
return updateMenuNode( popup, contentsNode );
}
QSGNode* QskMenuSkinlet::updateMenuNode(
const QskSkinnable* skinnable, QSGNode* contentsNode ) const
{
enum { Panel, Background, Cursor, Graphic, Text };
static QVector< quint8 > roles = { Panel, Background, Cursor, Graphic, Text };
if ( contentsNode == nullptr )
contentsNode = new QSGNode();
const auto menu = static_cast< const QskMenu* >( popup );
for ( const auto role : roles )
{
auto oldNode = QskSGNode::findChildNode( contentsNode, role );
@ -351,22 +361,27 @@ QSGNode* QskMenuSkinlet::updateContentsNode(
{
case Panel:
{
newNode = updateBoxNode( menu, oldNode, QskMenu::Panel );
newNode = updateBoxNode( skinnable, oldNode, QskMenu::Panel );
break;
}
case Backgrounds:
case Background:
{
newNode = qskUpdateBackgroundNode( menu, oldNode );
newNode = updateSeriesNode( skinnable, QskMenu::Cell, oldNode );
break;
}
case Cursor:
{
newNode = updateBoxNode( menu, oldNode, QskMenu::Cursor );
newNode = updateBoxNode( skinnable, oldNode, QskMenu::Cursor );
break;
}
case Items:
case Graphic:
{
newNode = qskUpdateItemsNode( menu, oldNode );
newNode = updateSeriesNode( skinnable, QskMenu::Graphic, oldNode );
break;
}
case Text:
{
newNode = updateSeriesNode( skinnable, QskMenu::Text, oldNode );
break;
}
}
@ -377,18 +392,71 @@ QSGNode* QskMenuSkinlet::updateContentsNode(
return contentsNode;
}
QSGNode* QskMenuSkinlet::updateSeriesSubNode( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
{
using Q = QskMenu;
auto menu = static_cast< const QskMenu* >( skinnable );
const auto rect = subControlCell( menu, menu->contentsRect(), subControl, index );
if ( subControl == Q::Cell )
{
return updateBoxNode( menu, node, rect, subControl );
}
if ( subControl == Q::Graphic )
{
const auto v = qskValueAt< QskGraphic >( menu, index );
const auto alignment = menu->alignmentHint( subControl, Qt::AlignCenter );
const auto filter = menu->effectiveGraphicFilter( subControl );
return QskSkinlet::updateGraphicNode(
menu, node, v.value< QskGraphic >(), filter, rect, alignment );
}
if ( subControl == Q::Text )
{
const auto v = qskValueAt< QString >( menu, index );
const auto alignment = menu->alignmentHint(
subControl, Qt::AlignVCenter | Qt::AlignLeft );
return QskSkinlet::updateTextNode( menu, node, rect, alignment,
v.value< QString>(), menu->textOptions(), QskMenu::Text );
}
return nullptr;
}
QSizeF QskMenuSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& ) const
{
if ( which != Qt::PreferredSize )
return QSizeF();
const PrivateData::CacheGuard guard( m_data.get() );
const auto count = subControlCellCount( skinnable, QskMenu::Cell );
qreal w = 0.0;
qreal h = 0.0;
if ( count > 0 )
{
const auto menu = static_cast< const QskMenu* >( skinnable );
const qreal w = qskCellWidth( menu );
const auto h = menu->count() * qskCellHeight( menu );
w = m_data->cellWidth( menu );
h = count * m_data->cellHeight( menu );
}
return menu->outerBoxSize( QskMenu::Panel, QSizeF( w, h ) );
auto hint = skinnable->outerBoxSize( QskMenu::Panel, QSizeF( w, h ) );
hint = hint.expandedTo( skinnable->strutSizeHint( QskMenu::Panel ) );
return hint;
}
#include "moc_QskMenuSkinlet.cpp"

View File

@ -1,6 +1,7 @@
#pragma once
#include <QskPopupSkinlet.h>
#include <memory>
class QskMenu;
@ -26,14 +27,28 @@ class QSK_EXPORT QskMenuSkinlet : public QskPopupSkinlet
QRectF subControlCell( const QskSkinnable*,
const QRectF&, QskAspect::Subcontrol, int index ) const override;
int subControlCellIndexAt( const QskSkinnable*,
const QRectF&, QskAspect::Subcontrol, const QPointF& ) const override;
int subControlCellCount( const QskSkinnable*, QskAspect::Subcontrol ) const override;
QskAspect::States subControlCellStates( const QskSkinnable*,
QskAspect::Subcontrol, int index ) const override;
QVariant valueAt( const QskSkinnable*,
QskAspect::Subcontrol, int index ) const override;
QSizeF sizeHint( const QskSkinnable*,
Qt::SizeHint, const QSizeF& ) const override;
private:
protected:
QSGNode* updateContentsNode( const QskPopup*, QSGNode* ) const override;
QSGNode* updateMenuNode( const QskSkinnable*, QSGNode* ) const;
QSGNode* updateSeriesSubNode( const QskSkinnable*,
QskAspect::Subcontrol, int index, QSGNode* ) const override;
private:
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};

View File

@ -22,6 +22,7 @@
#include "QskTextColors.h"
#include "QskTextNode.h"
#include "QskTextOptions.h"
#include "QskSkinStateChanger.h"
#include <qquickwindow.h>
#include <qsgsimplerectnode.h>
@ -563,24 +564,6 @@ QSGNode* QskSkinlet::updateGraphicNode(
return qskUpdateGraphicNode( skinnable, node, graphic, colorFilter, rect, mirrored );
}
QSizeF QskSkinlet::hintWithoutConstraint(
const QSizeF& hint, const QSizeF& constraint ) const
{
/*
This method is useful in situations, where a hint has been calculated
from a constraint and we want to return the calculated part only
*/
QSizeF h;
if ( constraint.width() < 0.0 )
h.setWidth( hint.width() );
if ( constraint.height() < 0.0 )
h.setHeight( hint.height() );
return h;
}
int QskSkinlet::subControlCellIndexAt( const QskSkinnable* skinnable,
const QRectF& rect, QskAspect::Subcontrol subControl, const QPointF& pos ) const
{
@ -601,6 +584,60 @@ int QskSkinlet::subControlCellIndexAt( const QskSkinnable* skinnable,
return -1;
}
QSGNode* QskSkinlet::updateSeriesNode( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl, QSGNode* rootNode ) const
{
auto node = rootNode ? rootNode->firstChild() : nullptr;
QSGNode* lastNode = nullptr;
const auto count = subControlCellCount( skinnable, subControl );
for( int i = 0; i < count; i++ )
{
QSGNode* newNode = nullptr;
{
const auto newStates = subControlCellStates( skinnable, subControl, i );
QskSkinStateChanger stateChanger( skinnable );
stateChanger.setStates( newStates );
newNode = updateSeriesSubNode( skinnable, subControl, i, node );
}
if ( newNode )
{
if ( newNode == node )
{
node = node->nextSibling();
}
else
{
if ( rootNode == nullptr )
rootNode = new QSGNode();
if ( node )
rootNode->insertChildNodeBefore( newNode, node );
else
rootNode->appendChildNode( newNode );
}
lastNode = newNode;
}
}
QskSGNode::removeAllChildNodesAfter( rootNode, lastNode );
return rootNode;
}
QSGNode* QskSkinlet::updateSeriesSubNode( const QskSkinnable*,
QskAspect::Subcontrol, int index, QSGNode* ) const
{
Q_UNUSED( index )
return nullptr;
}
QskAspect::States QskSkinlet::subControlCellStates(
const QskSkinnable* skinnable, QskAspect::Subcontrol, int index ) const
{
@ -608,4 +645,29 @@ QskAspect::States QskSkinlet::subControlCellStates(
return skinnable->skinStates();
}
QVariant QskSkinlet::valueAt( const QskSkinnable*,
QskAspect::Subcontrol, int index ) const
{
Q_UNUSED( index )
return QVariant();
}
QSizeF QskSkinlet::hintWithoutConstraint(
const QSizeF& hint, const QSizeF& constraint ) const
{
/*
This method is useful in situations, where a hint has been calculated
from a constraint and we want to return the calculated part only
*/
QSizeF h;
if ( constraint.width() < 0.0 )
h.setWidth( hint.width() );
if ( constraint.height() < 0.0 )
h.setHeight( hint.height() );
return h;
}
#include "moc_QskSkinlet.cpp"

View File

@ -53,6 +53,9 @@ class QSK_EXPORT QskSkinlet
virtual QskAspect::States subControlCellStates( const QskSkinnable*,
QskAspect::Subcontrol, int index ) const;
virtual QVariant valueAt( const QskSkinnable*,
QskAspect::Subcontrol, int index ) const;
const QVector< quint8 >& nodeRoles() const;
void setOwnedBySkinnable( bool on );
@ -130,6 +133,12 @@ class QSK_EXPORT QskSkinlet
const QskGraphic&, QskAspect::Subcontrol,
Qt::Orientations mirrored = Qt::Orientations() ) const;
QSGNode* updateSeriesNode( const QskSkinnable*,
QskAspect::Subcontrol, QSGNode* ) const;
virtual QSGNode* updateSeriesSubNode( const QskSkinnable*,
QskAspect::Subcontrol, int index, QSGNode* ) const;
void replaceChildNode( quint8 nodeRole, QSGNode* parentNode,
QSGNode* oldNode, QSGNode* newNode ) const;