qskinny/src/controls/QskMenuSkinlet.cpp

398 lines
10 KiB
C++
Raw Normal View History

2021-12-23 18:36:32 +01:00
#include "QskMenuSkinlet.h"
#include "QskMenu.h"
#include <QskBoxNode.h>
#include <QskGraphic.h>
#include <QskColorFilter.h>
#include <QskGraphicNode.h>
#include <QskTextNode.h>
#include <QskTextOptions.h>
#include <QskSGNode.h>
#include <QskFunctions.h>
#include <QskSkinStateChanger.h>
#include <QskMargins.h>
#include <qfontmetrics.h>
namespace
{
class StateChanger : public QskSkinStateChanger
{
public:
inline StateChanger( const QskSkinnable* skinnable, bool isSelected )
: QskSkinStateChanger( skinnable, isSelected
? QskMenu::Selected : QskAspect::NoState )
{
}
};
}
2021-12-27 09:50:14 +01:00
template< class T >
static T qskValueAt( const QskMenu* menu, int index )
{
const auto item = menu->itemAt( index );
if ( item.canConvert< T >() )
return item.value< T >();
if ( item.canConvert< QVariantList >() )
{
const auto list = item.value< QVariantList >();
for ( const auto& value : list )
{
if ( value.canConvert< T >() )
return value.value< T >();
}
}
return T();
}
2021-12-23 18:36:32 +01:00
static qreal qskMaxTextWidth( const QskMenu* menu )
{
const QFontMetricsF fm( menu->effectiveFont( QskMenu::Text ) );
auto maxWidth = 0.0;
for ( int i = 0; i < menu->count(); i++ )
{
2021-12-27 09:50:14 +01:00
const auto value = menu->itemAt( i );
const auto text = qskValueAt< QString >( menu, i );
if( !text.isEmpty() )
2021-12-23 18:36:32 +01:00
{
2021-12-27 09:50:14 +01:00
const auto w = qskHorizontalAdvance( fm, text );
2021-12-23 18:36:32 +01:00
if( w > maxWidth )
maxWidth = w;
}
}
return maxWidth;
}
2021-12-24 16:20:34 +01:00
static qreal qskGraphicWidth( const QskMenu* menu )
2021-12-23 18:36:32 +01:00
{
const auto hint = menu->strutSizeHint( QskMenu::Graphic );
2021-12-24 16:20:34 +01:00
const qreal textHeight = menu->effectiveFontHeight( QskMenu::Text );
2021-12-23 19:05:59 +01:00
2021-12-23 18:36:32 +01:00
const auto h = qMax( hint.height(), textHeight );
2021-12-23 19:05:59 +01:00
2021-12-23 18:36:32 +01:00
qreal maxW = 0.0;
for ( int i = 0; i < menu->count(); i++ )
{
2021-12-27 09:50:14 +01:00
const auto graphic = qskValueAt< QskGraphic >( menu, i );
const auto w = graphic.widthForHeight( h );
2021-12-23 19:05:59 +01:00
if( w > maxW )
2021-12-23 18:36:32 +01:00
maxW = w;
2021-12-23 19:05:59 +01:00
}
2021-12-24 16:20:34 +01:00
return qMax( hint.width(), maxW );
2021-12-23 18:36:32 +01:00
}
2021-12-24 16:20:34 +01:00
static qreal qskCellWidth( const QskMenu* menu )
2021-12-23 18:36:32 +01:00
{
2021-12-24 16:20:34 +01:00
using Q = QskMenu;
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
const auto spacing = menu->spacingHint( Q::Cell );
const auto padding = menu->paddingHint( Q::Cell );
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
auto w = qskGraphicWidth( menu )
+ spacing + qskMaxTextWidth( menu );
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
w += padding.left() + padding.right();
const auto minWidth = menu->strutSizeHint( Q::Cell ).width();
return qMax( w, minWidth );
2021-12-23 18:36:32 +01:00
}
2021-12-24 16:50:34 +01:00
static qreal qskCellHeight( const QskMenu* menu )
{
using Q = QskMenu;
const auto graphicHeight = menu->strutSizeHint( Q::Graphic ).height();
const auto textHeight = menu->effectiveFontHeight( Q::Text );
const auto padding = menu->paddingHint( Q::Cell );
qreal h = qMax( graphicHeight, textHeight );
h += padding.top() + padding.bottom();
const auto minHeight = menu->strutSizeHint( Q::Cell ).height();
h = qMax( h, minHeight );
return h;
}
2021-12-23 18:36:32 +01:00
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 );
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,
2021-12-23 19:05:59 +01:00
text, menu->textOptions(), QskMenu::Text );
2021-12-23 18:36:32 +01:00
}
2021-12-24 16:20:34 +01:00
static QSGNode* qskUpdateBackgroundNode( const QskMenu* menu, QSGNode* rootNode )
2021-12-23 18:36:32 +01:00
{
2021-12-24 16:20:34 +01:00
auto node = rootNode ? rootNode->firstChild() : nullptr;
QSGNode* lastNode = nullptr;
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
for( int i = 0; i < menu->count(); i++ )
2021-12-23 18:36:32 +01:00
{
2021-12-24 16:20:34 +01:00
QSGNode* newNode = nullptr;
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
{
const StateChanger stateChanger( menu, menu->currentIndex() == i );
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
newNode = QskSkinlet::updateBoxNode(
menu, node, menu->cellRect( i ), QskMenu::Cell );
}
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
if ( newNode )
{
if ( newNode == node )
{
node = node->nextSibling();
}
else
{
if ( rootNode == nullptr )
rootNode = new QSGNode();
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
if ( node )
rootNode->insertChildNodeBefore( newNode, node );
else
rootNode->appendChildNode( newNode );
}
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
lastNode = newNode;
}
2021-12-23 18:36:32 +01:00
}
2021-12-24 16:20:34 +01:00
QskSGNode::removeAllChildNodesAfter( rootNode, lastNode );
return rootNode;
2021-12-23 18:36:32 +01:00
}
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 )
{
const auto spacing = menu->spacingHint( QskMenu::Cell );
2021-12-24 16:20:34 +01:00
const auto graphicWidth = qskGraphicWidth( menu );
2021-12-23 18:36:32 +01:00
if ( rootNode == nullptr )
rootNode = new QSGNode();
QSGNode* node = nullptr;
for( int i = 0; i < menu->count(); i++ )
{
if ( node == nullptr )
node = rootNode->firstChild();
else
node = node->nextSibling();
if ( node == nullptr )
{
node = new QSGNode();
rootNode->appendChildNode( node );
}
{
const StateChanger stateChanger( menu, menu->currentIndex() == i );
2021-12-24 16:20:34 +01:00
const auto cellRect = menu->cellRect( i );
2021-12-23 18:36:32 +01:00
auto graphicRect = cellRect;
2021-12-24 16:20:34 +01:00
graphicRect.setWidth( graphicWidth );
2021-12-23 18:36:32 +01:00
auto textRect = cellRect;
textRect.setX( graphicRect.right() + spacing );
2021-12-23 19:05:59 +01:00
2021-12-27 09:50:14 +01:00
const auto graphic = qskValueAt< QskGraphic >( menu, i );
const auto text = qskValueAt< QString >( menu, i );
qskUpdateItemNode( menu, graphicRect, graphic,
textRect, text, node );
2021-12-23 18:36:32 +01:00
}
}
QskSGNode::removeAllChildNodesAfter( rootNode, node );
return rootNode;
}
QskMenuSkinlet::QskMenuSkinlet( QskSkin* skin )
: Inherited( skin )
{
appendNodeRoles( { PanelRole } );
}
QRectF QskMenuSkinlet::subControlRect(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl ) const
{
2021-12-24 16:20:34 +01:00
const auto menu = static_cast< const QskMenu* >( skinnable );
2021-12-23 18:36:32 +01:00
if( subControl == QskMenu::Panel )
{
return contentsRect;
}
2021-12-24 16:20:34 +01:00
if( subControl == QskMenu::Cursor )
{
return menu->cellRect( menu->currentIndex() );
}
2021-12-23 18:36:32 +01:00
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
2021-12-24 16:50:34 +01:00
QRectF QskMenuSkinlet::itemRect(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, int index ) const
{
if ( subControl == QskMenu::Cell )
{
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 );
return QRectF( r.x(), r.y() + index * h, r.width(), h );
}
return Inherited::itemRect(
skinnable, contentsRect, subControl, index );
}
2021-12-26 09:15:15 +01:00
int QskMenuSkinlet::itemIndexAt( const QskSkinnable* skinnable,
const QRectF& rect, QskAspect::Subcontrol subControl, const QPointF& pos ) const
{
if ( subControl == QskMenu::Cell )
{
const auto menu = static_cast< const QskMenu* >( skinnable );
const auto panelRect = menu->subControlContentsRect( QskMenu::Panel );
if ( !panelRect.contains( pos ) )
return -1;
/*
A menu never has many items and we can simply iterate
without being concerned about performance issues
*/
const auto h = qskCellHeight( menu );
auto r = panelRect;
r.setHeight( h );
for ( int i = 0; i < menu->count(); i++ )
{
if ( r.contains( pos ) )
return i;
r.moveTop( r.bottom() );
}
return -1;
}
return Inherited::itemIndexAt( skinnable, rect, subControl, pos );
}
2021-12-23 18:36:32 +01:00
QSGNode* QskMenuSkinlet::updateContentsNode(
const QskPopup* popup, QSGNode* contentsNode ) const
{
enum { Panel, Backgrounds, Cursor, Items };
static QVector< quint8 > roles = { Panel, Backgrounds, Cursor, Items };
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 );
2021-12-24 16:20:34 +01:00
QSGNode* newNode = nullptr;
2021-12-23 18:36:32 +01:00
switch( role )
{
case Panel:
{
newNode = updateBoxNode( menu, oldNode, QskMenu::Panel );
break;
}
case Backgrounds:
{
newNode = qskUpdateBackgroundNode( menu, oldNode );
break;
}
case Cursor:
{
2021-12-24 16:20:34 +01:00
newNode = updateBoxNode( menu, oldNode, QskMenu::Cursor );
2021-12-23 18:36:32 +01:00
break;
}
case Items:
{
newNode = qskUpdateItemsNode( menu, oldNode );
break;
2021-12-23 19:05:59 +01:00
}
2021-12-23 18:36:32 +01:00
}
QskSGNode::replaceChildNode( roles, role, contentsNode, oldNode, newNode );
}
return contentsNode;
}
QSizeF QskMenuSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& ) const
{
if ( which != Qt::PreferredSize )
return QSizeF();
const auto menu = static_cast< const QskMenu* >( skinnable );
2021-12-24 16:20:34 +01:00
const qreal w = qskCellWidth( menu );
2021-12-24 16:50:34 +01:00
const auto h = menu->count() * qskCellHeight( menu );
2021-12-23 18:36:32 +01:00
2021-12-24 16:20:34 +01:00
return menu->outerBoxSize( QskMenu::Panel, QSizeF( w, h ) );
2021-12-23 18:36:32 +01:00
}
#include "moc_QskMenuSkinlet.cpp"