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>
|
|
|
|
|
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
|
|
|
{
|
2021-12-27 17:33:06 +01:00
|
|
|
QskSkinStateChanger stateChanger( menu );
|
|
|
|
if ( menu->currentIndex() == i )
|
|
|
|
stateChanger.addStates( QskMenu::Selected );
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2021-12-27 17:33:06 +01:00
|
|
|
QskSkinStateChanger stateChanger( menu );
|
|
|
|
if ( menu->currentIndex() == i )
|
|
|
|
stateChanger.addStates( QskMenu::Selected );
|
2021-12-23 18:36:32 +01:00
|
|
|
|
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"
|