qskinny/src/controls/QskMenuSkinlet.cpp

512 lines
14 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>
2021-12-29 16:23:19 +01:00
#include <QskFunctions.h>
2021-12-23 18:36:32 +01:00
#include <qfontmetrics.h>
2021-12-27 09:50:14 +01:00
template< class T >
static inline QVariant qskSampleAt( const QskMenu* menu, int index )
2021-12-27 09:50:14 +01:00
{
const auto item = menu->itemAt( index );
if ( item.canConvert< T >() )
2021-12-28 15:33:13 +01:00
return item;
2021-12-27 09:50:14 +01:00
if ( item.canConvert< QVariantList >() )
{
const auto list = item.value< QVariantList >();
for ( const auto& value : list )
{
if ( value.canConvert< T >() )
2021-12-28 15:33:13 +01:00
return value;
2021-12-27 09:50:14 +01:00
}
}
2021-12-28 15:33:13 +01:00
return QVariant();
2021-12-27 09:50:14 +01:00
}
template< class T >
static inline T qskValueAt( const QskMenu* menu, int index )
{
const auto sample = qskSampleAt< T >( menu, index );
return sample.template value< T >();
}
2021-12-28 15:33:13 +01:00
class QskMenuSkinlet::PrivateData
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
public:
class CacheGuard
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
public:
CacheGuard( QskMenuSkinlet::PrivateData* data )
: m_data( data )
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
m_data->enableCache( true );
2021-12-23 18:36:32 +01:00
}
2021-12-23 19:05:59 +01:00
2021-12-28 15:33:13 +01:00
~CacheGuard()
{
m_data->enableCache( false );
}
private:
QskMenuSkinlet::PrivateData* m_data;
};
2021-12-23 19:05:59 +01:00
2021-12-28 15:33:13 +01:00
void enableCache( bool on )
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
m_isCaching = on;
m_cellHeight = m_cellWidth = m_graphicWidth = m_textWidth = -1.0;
2021-12-23 19:05:59 +01:00
}
2021-12-28 15:33:13 +01:00
inline qreal graphicWidth( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_graphicWidth < 0.0 )
m_graphicWidth = graphicWidthInternal( menu );
2021-12-24 16:50:34 +01:00
2021-12-28 15:33:13 +01:00
return m_graphicWidth;
}
2021-12-24 16:50:34 +01:00
2021-12-28 15:33:13 +01:00
return graphicWidthInternal( menu );
}
2021-12-24 16:50:34 +01:00
2021-12-28 15:33:13 +01:00
inline qreal textWidth( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_textWidth < 0.0 )
m_textWidth = textWidthInternal( menu );
2021-12-24 16:50:34 +01:00
2021-12-28 15:33:13 +01:00
return m_textWidth;
}
2021-12-24 16:50:34 +01:00
2021-12-28 15:33:13 +01:00
return textWidthInternal( menu );
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
inline qreal cellWidth( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_cellWidth < 0.0 )
m_cellWidth = cellWidthInternal( menu );
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
return m_cellWidth;
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
return cellWidthInternal( menu );
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
inline qreal cellHeight( const QskMenu* menu ) const
{
if ( m_isCaching )
{
if ( m_cellHeight < 0.0 )
m_cellHeight = cellHeightInternal( menu );
2021-12-28 15:33:13 +01:00
return m_cellHeight;
}
2021-12-28 15:33:13 +01:00
return cellHeightInternal( menu );
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
private:
qreal graphicWidthInternal( const QskMenu* menu ) const
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
const auto skinlet = menu->effectiveSkinlet();
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
const auto hint = menu->strutSizeHint( QskMenu::Graphic );
const qreal textHeight = menu->effectiveFontHeight( QskMenu::Text );
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
const auto h = qMax( hint.height(), textHeight );
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
qreal maxW = 0.0;
for ( int i = 0; i < menu->count(); i++ )
2021-12-24 16:20:34 +01:00
{
const auto sample = skinlet->sampleAt( menu, QskMenu::Graphic, i );
if ( sample.canConvert< QskGraphic >() )
2021-12-24 16:20:34 +01:00
{
const auto graphic = sample.value< QskGraphic >();
2021-12-28 15:33:13 +01:00
if ( !graphic.isNull() )
{
const auto w = graphic.widthForHeight( h );
if( w > maxW )
maxW = w;
}
2021-12-24 16:20:34 +01:00
}
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
return qMax( hint.width(), maxW );
}
2021-12-24 16:20:34 +01:00
2021-12-28 15:33:13 +01:00
qreal textWidthInternal( const QskMenu* menu ) const
{
const auto skinlet = menu->effectiveSkinlet();
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
const QFontMetricsF fm( menu->effectiveFont( QskMenu::Text ) );
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
auto maxWidth = 0.0;
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
for ( int i = 0; i < menu->count(); i++ )
{
const auto sample = skinlet->sampleAt( menu, QskMenu::Text, i );
if ( sample.canConvert< QString >() )
2021-12-28 15:33:13 +01:00
{
const auto text = sample.value< QString >();
2021-12-28 15:33:13 +01:00
if( !text.isEmpty() )
{
const auto w = qskHorizontalAdvance( fm, text );
if( w > maxWidth )
maxWidth = w;
}
}
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
return maxWidth;
2021-12-23 18:36:32 +01:00
}
2021-12-28 15:33:13 +01:00
qreal cellWidthInternal( const QskMenu* menu ) const
{
using Q = QskMenu;
2021-12-28 15:33:13 +01:00
const auto spacing = menu->spacingHint( Q::Cell );
const auto padding = menu->paddingHint( Q::Cell );
2021-12-28 15:33:13 +01:00
auto w = graphicWidth( menu ) + spacing + textWidth( menu );
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
w += padding.left() + padding.right();
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
const auto minWidth = menu->strutSizeHint( Q::Cell ).width();
return qMax( w, minWidth );
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
qreal cellHeightInternal( const QskMenu* menu ) const
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
using Q = QskMenu;
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
const auto graphicHeight = menu->strutSizeHint( Q::Graphic ).height();
const auto textHeight = menu->effectiveFontHeight( Q::Text );
const auto padding = menu->paddingHint( Q::Cell );
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
qreal h = qMax( graphicHeight, textHeight );
h += padding.top() + padding.bottom();
2021-12-23 19:05:59 +01:00
2021-12-28 15:33:13 +01:00
const auto minHeight = menu->strutSizeHint( Q::Cell ).height();
h = qMax( h, minHeight );
2021-12-27 09:50:14 +01:00
2021-12-28 15:33:13 +01:00
return h;
2021-12-23 18:36:32 +01:00
}
2021-12-28 15:33:13 +01:00
bool m_isCaching;
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
mutable qreal m_graphicWidth = -1.0;
mutable qreal m_textWidth = -1.0;
mutable qreal m_cellHeight = -1.0;
mutable qreal m_cellWidth = -1.0;
};
2021-12-23 18:36:32 +01:00
QskMenuSkinlet::QskMenuSkinlet( QskSkin* skin )
: Inherited( skin )
2021-12-28 15:33:13 +01:00
, m_data( new PrivateData() )
2021-12-23 18:36:32 +01:00
{
appendNodeRoles( { PanelRole } );
}
2021-12-29 16:23:19 +01:00
QRectF QskMenuSkinlet::cursorRect(
const QskSkinnable* skinnable, const QRectF& contentsRect, int index ) const
{
const auto count = sampleCount( skinnable, QskMenu::Cell );
auto rect = sampleRect( skinnable, contentsRect,
QskMenu::Cell, qBound( 0, index, count ) );
if ( index < 0 )
rect.setBottom( rect.top() );
if ( index >= count )
rect.setTop( rect.bottom() );
return rect;
}
2021-12-23 18:36:32 +01:00
QRectF QskMenuSkinlet::subControlRect(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl ) const
{
2021-12-29 16:23:19 +01:00
using Q = QskMenu;
2021-12-24 16:20:34 +01:00
const auto menu = static_cast< const QskMenu* >( skinnable );
2021-12-29 16:23:19 +01:00
if( subControl == Q::Panel )
2021-12-23 18:36:32 +01:00
{
return contentsRect;
}
2021-12-29 16:23:19 +01:00
if( subControl == Q::Cursor )
2021-12-24 16:20:34 +01:00
{
2021-12-29 16:23:19 +01:00
if ( menu->currentIndex() < 0 )
return QRectF();
const qreal pos = menu->positionHint( Q::Cursor );
const int pos1 = qFloor( pos );
const int pos2 = qCeil( pos );
auto rect = cursorRect( skinnable, contentsRect, pos1 );
if ( pos1 != pos2 )
{
const auto r = cursorRect( skinnable, contentsRect, pos2 );
const qreal ratio = ( pos - pos1 ) / ( pos2 - pos1 );
rect = qskInterpolatedRect( rect, r, ratio );
}
return rect;
2021-12-24 16:20:34 +01:00
}
2021-12-23 18:36:32 +01:00
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
QRectF QskMenuSkinlet::sampleRect(
2021-12-24 16:50:34 +01:00
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, int index ) const
{
2021-12-28 15:33:13 +01:00
using Q = QskMenu;
2021-12-24 16:50:34 +01:00
2021-12-28 15:33:13 +01:00
const auto menu = static_cast< const QskMenu* >( skinnable );
2021-12-24 16:50:34 +01:00
2021-12-28 15:33:13 +01:00
if ( subControl == Q::Cell )
{
const auto r = menu->subControlContentsRect( Q::Panel );
const auto h = m_data->cellHeight( menu );
2021-12-24 16:50:34 +01:00
return QRectF( r.x(), r.y() + index * h, r.width(), h );
}
2021-12-28 15:33:13 +01:00
if ( subControl == QskMenu::Graphic || subControl == QskMenu::Text )
{
const auto r = sampleRect( menu, contentsRect, Q::Cell, index );
2021-12-28 15:33:13 +01:00
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::sampleRect(
2021-12-24 16:50:34 +01:00
skinnable, contentsRect, subControl, index );
}
int QskMenuSkinlet::sampleIndexAt(
2021-12-28 15:33:13 +01:00
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, const QPointF& pos ) const
{
const PrivateData::CacheGuard guard( m_data.get() );
return Inherited::sampleIndexAt( skinnable, contentsRect, subControl, pos );
2021-12-28 15:33:13 +01:00
}
int QskMenuSkinlet::sampleCount(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const
2021-12-26 09:15:15 +01:00
{
2021-12-28 15:33:13 +01:00
using Q = QskMenu;
if ( subControl == Q::Cell || subControl == Q::Graphic || subControl == Q::Text )
2021-12-26 09:15:15 +01:00
{
const auto menu = static_cast< const QskMenu* >( skinnable );
return menu->count();
}
2021-12-26 09:15:15 +01:00
return Inherited::sampleCount( skinnable, subControl );
}
2021-12-26 09:15:15 +01:00
QskAspect::States QskMenuSkinlet::sampleStates(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const
{
2021-12-28 15:33:13 +01:00
using Q = QskMenu;
auto states = Inherited::sampleStates( skinnable, subControl, index );
2021-12-26 09:15:15 +01:00
2021-12-28 15:33:13 +01:00
if ( subControl == Q::Cell || subControl == Q::Graphic || subControl == Q::Text )
{
const auto menu = static_cast< const QskMenu* >( skinnable );
if ( menu->currentIndex() == index )
states |= QskMenu::Selected;
2021-12-26 09:15:15 +01:00
}
return states;
2021-12-26 09:15:15 +01:00
}
QVariant QskMenuSkinlet::sampleAt( const QskSkinnable* skinnable,
2021-12-28 15:33:13 +01:00
QskAspect::Subcontrol subControl, int index ) const
{
using Q = QskMenu;
const auto menu = static_cast< const QskMenu* >( skinnable );
if ( subControl == Q::Graphic )
return qskSampleAt< QskGraphic >( menu, index );
2021-12-28 15:33:13 +01:00
if ( subControl == Q::Text )
return qskSampleAt< QString >( menu, index );
2021-12-28 15:33:13 +01:00
return Inherited::sampleAt( skinnable, subControl, index );
2021-12-28 15:33:13 +01:00
}
2021-12-23 18:36:32 +01:00
QSGNode* QskMenuSkinlet::updateContentsNode(
const QskPopup* popup, QSGNode* contentsNode ) const
{
2021-12-28 15:33:13 +01:00
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 };
2021-12-23 18:36:32 +01:00
if ( contentsNode == nullptr )
contentsNode = new QSGNode();
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:
{
2021-12-28 15:33:13 +01:00
newNode = updateBoxNode( skinnable, oldNode, QskMenu::Panel );
2021-12-23 18:36:32 +01:00
break;
}
2021-12-28 15:33:13 +01:00
case Background:
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
newNode = updateSeriesNode( skinnable, QskMenu::Cell, oldNode );
2021-12-23 18:36:32 +01:00
break;
}
case Cursor:
{
2021-12-28 15:33:13 +01:00
newNode = updateBoxNode( skinnable, oldNode, QskMenu::Cursor );
2021-12-23 18:36:32 +01:00
break;
}
2021-12-28 15:33:13 +01:00
case Graphic:
2021-12-23 18:36:32 +01:00
{
2021-12-28 15:33:13 +01:00
newNode = updateSeriesNode( skinnable, QskMenu::Graphic, oldNode );
break;
}
case Text:
{
newNode = updateSeriesNode( skinnable, QskMenu::Text, oldNode );
2021-12-23 18:36:32 +01:00
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;
}
QSGNode* QskMenuSkinlet::updateSampleNode( const QskSkinnable* skinnable,
2021-12-28 15:33:13 +01:00
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
{
using Q = QskMenu;
auto menu = static_cast< const QskMenu* >( skinnable );
const auto rect = sampleRect( menu, menu->contentsRect(), subControl, index );
2021-12-28 15:33:13 +01:00
if ( subControl == Q::Cell )
{
return updateBoxNode( menu, node, rect, subControl );
}
if ( subControl == Q::Graphic )
{
const auto graphic = qskValueAt< QskGraphic >( menu, index );
if ( graphic.isNull() )
return nullptr;
2021-12-28 15:33:13 +01:00
const auto alignment = menu->alignmentHint( subControl, Qt::AlignCenter );
const auto filter = menu->effectiveGraphicFilter( subControl );
return QskSkinlet::updateGraphicNode(
menu, node, graphic, filter, rect, alignment );
2021-12-28 15:33:13 +01:00
}
if ( subControl == Q::Text )
{
const auto text = qskValueAt< QString >( menu, index );
if ( text.isEmpty() )
return nullptr;
2021-12-28 15:33:13 +01:00
const auto alignment = menu->alignmentHint(
subControl, Qt::AlignVCenter | Qt::AlignLeft );
return QskSkinlet::updateTextNode( menu, node, rect, alignment,
text, menu->textOptions(), QskMenu::Text );
2021-12-28 15:33:13 +01:00
}
return nullptr;
}
2021-12-23 18:36:32 +01:00
QSizeF QskMenuSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& ) const
{
if ( which != Qt::PreferredSize )
return QSizeF();
2021-12-28 15:33:13 +01:00
const PrivateData::CacheGuard guard( m_data.get() );
const auto count = sampleCount( skinnable, QskMenu::Cell );
2021-12-28 15:33:13 +01:00
qreal w = 0.0;
qreal h = 0.0;
if ( count > 0 )
{
const auto menu = static_cast< const QskMenu* >( skinnable );
w = m_data->cellWidth( menu );
h = count * m_data->cellHeight( menu );
}
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
auto hint = skinnable->outerBoxSize( QskMenu::Panel, QSizeF( w, h ) );
hint = hint.expandedTo( skinnable->strutSizeHint( QskMenu::Panel ) );
2021-12-23 18:36:32 +01:00
2021-12-28 15:33:13 +01:00
return hint;
2021-12-23 18:36:32 +01:00
}
#include "moc_QskMenuSkinlet.cpp"