qskinny/src/controls/QskMenuSkinlet.cpp

314 lines
8.4 KiB
C++
Raw Normal View History

2021-12-23 17:36:32 +00: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 )
{
}
};
}
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++ )
{
const auto entry = menu->entryAt( i );
if( !entry.text.isEmpty() )
{
const auto w = qskHorizontalAdvance( fm, entry.text );
if( w > maxWidth )
maxWidth = w;
}
}
return maxWidth;
}
static QSizeF qskIconSize( const QskMenu* menu )
{
const auto hint = menu->strutSizeHint( QskMenu::Graphic );
const qreal textHeight = QFontMetrics(
menu->effectiveFont( QskMenu::Text ) ).height();
2021-12-23 18:05:59 +00:00
2021-12-23 17:36:32 +00:00
const auto h = qMax( hint.height(), textHeight );
2021-12-23 18:05:59 +00:00
2021-12-23 17:36:32 +00:00
qreal maxW = 0.0;
for ( int i = 0; i < menu->count(); i++ )
{
const auto w = menu->graphicAt( i ).widthForHeight( h );
2021-12-23 18:05:59 +00:00
if( w > maxW )
2021-12-23 17:36:32 +00:00
maxW = w;
2021-12-23 18:05:59 +00:00
}
2021-12-23 17:36:32 +00:00
const auto w = qMax( hint.width(), maxW );
2021-12-23 18:05:59 +00:00
2021-12-23 17:36:32 +00:00
return QSizeF( w, h );
}
static QSizeF qskItemSize( const QskMenu* menu )
{
const auto spacing = menu->spacingHint( QskMenu::Cell );
const QskMargins padding = menu->paddingHint( QskMenu::Cell );
const auto sz = qskIconSize( menu );
const qreal h = sz.height() + padding.height();
const qreal w = sz.width() + spacing + qskMaxTextWidth( menu ) + padding.width();
return QSizeF( w, 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 );
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 18:05:59 +00:00
text, menu->textOptions(), QskMenu::Text );
2021-12-23 17:36:32 +00:00
}
static QSGNode* qskUpdateBackgroundNode( const QskMenu*, QSGNode* )
{
return nullptr; // TODO
}
static QRectF qskCellRect( const QskMenu* menu, int index )
{
if ( index >= 0 )
{
auto r = menu->subControlRect( QskMenu::Panel );
r = menu->innerBox( QskMenu::Panel, r );
const auto sz = qskItemSize( menu );
const auto y = r.y() + index * sz.height();
return QRectF( r.x(), y, r.width(), sz.height() );
}
return QRectF();
}
static QSGNode* qskUpdateCursorNode( const QskMenu* menu, QSGNode* node )
{
const auto r = qskCellRect( menu, menu->currentIndex() );
if ( !r.isEmpty() )
{
const StateChanger stateChanger( menu, true );
auto cursorNode = QskSGNode::ensureNode< QskBoxNode >( node );
cursorNode->setBoxData( r, menu->gradientHint( QskMenu::Cell ) );
return cursorNode;
}
return nullptr;
}
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 padding = menu->paddingHint( QskMenu::Cell );
const auto spacing = menu->spacingHint( QskMenu::Cell );
const auto iconSize = qskIconSize( menu );
auto boundingRect = menu->subControlRect( QskMenu::Panel );
boundingRect = menu->innerBox( QskMenu::Panel, boundingRect );
auto itemSize = iconSize;
itemSize.rwidth() += spacing + qskMaxTextWidth( menu );
#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 )
itemSize = itemSize.grownBy( padding );
#else
itemSize.rwidth() += padding.left() + padding.right();
itemSize.rheight() += padding.top() + padding.bottom();
#endif
itemSize = itemSize.expandedTo( menu->strutSizeHint( QskMenu::Graphic ) );
if ( rootNode == nullptr )
rootNode = new QSGNode();
QSGNode* node = nullptr;
qreal y = boundingRect.y();
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 );
QRectF cellRect( boundingRect.x(), y,
boundingRect.width(), itemSize.height() );
cellRect = cellRect.marginsRemoved( padding );
auto graphicRect = cellRect;
graphicRect.setWidth( iconSize.width() );
auto textRect = cellRect;
textRect.setX( graphicRect.right() + spacing );
2021-12-23 18:05:59 +00:00
2021-12-23 17:36:32 +00:00
qskUpdateItemNode( menu, graphicRect, menu->graphicAt( i ),
textRect, menu->entryAt( i ).text, node );
}
y += itemSize.height();
}
// Remove trailing nodes
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
{
if( subControl == QskMenu::Panel )
{
return contentsRect;
}
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
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 );
QSGNode* newNode;
switch( role )
{
case Panel:
{
newNode = updateBoxNode( menu, oldNode, QskMenu::Panel );
break;
}
case Backgrounds:
{
newNode = qskUpdateBackgroundNode( menu, oldNode );
break;
}
case Cursor:
{
newNode = qskUpdateCursorNode( menu, oldNode );
break;
}
case Items:
{
newNode = qskUpdateItemsNode( menu, oldNode );
break;
2021-12-23 18:05:59 +00:00
}
2021-12-23 17:36:32 +00:00
}
QskSGNode::replaceChildNode( roles, role, contentsNode, oldNode, newNode );
}
return contentsNode;
}
QSizeF QskMenuSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& ) const
{
using Q = QskMenu;
if ( which != Qt::PreferredSize )
return QSizeF();
const auto menu = static_cast< const QskMenu* >( skinnable );
const auto itemSize = qskItemSize( menu );
const auto count = menu->count();
const qreal h = count * itemSize.height();
return menu->outerBoxSize( Q::Panel, QSizeF( itemSize.width(), h ) );
}
#include "moc_QskMenuSkinlet.cpp"