6#include "QskMenuSkinlet.h"
10#include "QskColorFilter.h"
11#include "QskTextOptions.h"
12#include "QskFunctions.h"
13#include "QskMargins.h"
14#include "QskFunctions.h"
15#include "QskLabelData.h"
19#include <qfontmetrics.h>
22static inline int qskActionIndex(
const QskMenu* menu,
int optionIndex )
24 if ( optionIndex < 0 )
27 const auto& actions = menu->actions();
29 auto it = std::lower_bound(
30 actions.constBegin(), actions.constEnd(), optionIndex );
32 return it - actions.constBegin();
35static inline qreal qskPaddedSeparatorHeight(
const QskMenu* menu )
39 const auto margins = menu->
marginHint( Q::Separator );
42 + margins.top() + margins.bottom();
45class QskMenuSkinlet::PrivateData
51 CacheGuard( PrivateData* data )
54 m_data->enableCache(
true );
59 m_data->enableCache(
false );
66 void enableCache(
bool on )
69 m_segmentHeight = m_segmentWidth = m_graphicWidth = m_textWidth = -1.0;
72 inline qreal graphicWidth(
const QskMenu* menu )
const
76 if ( m_graphicWidth < 0.0 )
77 m_graphicWidth = graphicWidthInternal( menu );
79 return m_graphicWidth;
82 return graphicWidthInternal( menu );
85 inline qreal textWidth(
const QskMenu* menu )
const
89 if ( m_textWidth < 0.0 )
90 m_textWidth = textWidthInternal( menu );
95 return textWidthInternal( menu );
98 inline qreal segmentWidth(
const QskMenu* menu )
const
102 if ( m_segmentWidth < 0.0 )
103 m_segmentWidth = segmentWidthInternal( menu );
105 return m_segmentWidth;
108 return segmentWidthInternal( menu );
111 inline qreal segmentHeight(
const QskMenu* menu )
const
115 if ( m_segmentHeight < 0.0 )
116 m_segmentHeight = segmentHeightInternal( menu );
118 return m_segmentHeight;
121 return segmentHeightInternal( menu );
125 qreal graphicWidthInternal(
const QskMenu* menu )
const
128 const qreal textHeight = menu->effectiveFontHeight( QskMenu::Text );
130 const auto h = qMax( hint.height(), textHeight );
134 const auto options = menu->options();
135 for (
auto& option : options )
137 const auto graphic = option.icon().graphic();
138 if ( !graphic.isNull() )
140 const auto w = graphic.widthForHeight( h );
146 return qMax( hint.width(), maxW );
149 qreal textWidthInternal(
const QskMenu* menu )
const
151 const QFontMetricsF fm( menu->
effectiveFont( QskMenu::Text ) );
155 const auto options = menu->options();
156 for (
auto& option : options )
158 if( !option.text().isEmpty() )
160 const auto w = qskHorizontalAdvance( fm, option.text() );
169 qreal segmentWidthInternal(
const QskMenu* menu )
const
173 const auto spacing = menu->
spacingHint( Q::Segment );
174 const auto padding = menu->
paddingHint( Q::Segment );
176 auto w = graphicWidth( menu ) + spacing + textWidth( menu );
178 w += padding.left() + padding.right();
180 const auto minWidth = menu->
strutSizeHint( Q::Segment ).width();
181 return qMax( w, minWidth );
184 qreal segmentHeightInternal(
const QskMenu* menu )
const
188 const auto graphicHeight = menu->
strutSizeHint( Q::Icon ).height();
189 const auto textHeight = menu->effectiveFontHeight( Q::Text );
190 const auto padding = menu->
paddingHint( Q::Segment );
192 qreal h = qMax( graphicHeight, textHeight );
193 h += padding.top() + padding.bottom();
195 const auto minHeight = menu->
strutSizeHint( Q::Segment ).height();
196 h = qMax( h, minHeight );
203 mutable qreal m_graphicWidth = -1.0;
204 mutable qreal m_textWidth = -1.0;
205 mutable qreal m_segmentHeight = -1.0;
206 mutable qreal m_segmentWidth = -1.0;
209QskMenuSkinlet::QskMenuSkinlet(
QskSkin* skin )
211 , m_data( new PrivateData() )
213 appendNodeRoles( { ContentsRole, PanelRole } );
216QskMenuSkinlet::~QskMenuSkinlet() =
default;
218QSGNode* QskMenuSkinlet::updateSubNode(
219 const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node )
const
225 const auto popup =
static_cast< const QskPopup*
>( skinnable );
228 if ( rect.isEmpty() )
231 return updateContentsNode( popup, node );
235 return Inherited::updateSubNode( skinnable, nodeRole, node );
238QRectF QskMenuSkinlet::cursorRect(
239 const QskSkinnable* skinnable,
const QRectF& contentsRect,
int index )
const
243 const auto menu =
static_cast< const QskMenu*
>( skinnable );
244 const auto actions = menu->actions();
246 index = qskActionIndex( menu, index );
252 rect = sampleRect( skinnable, contentsRect, Q::Segment, 0 );
253 rect.setBottom( rect.top() );
255 else if ( index >= actions.count() )
257 rect = sampleRect( skinnable, contentsRect, Q::Segment, actions.count() - 1 );
258 rect.setTop( rect.bottom() );
262 rect = sampleRect( skinnable, contentsRect, Q::Segment, index );
268QRectF QskMenuSkinlet::subControlRect(
269 const QskSkinnable* skinnable,
const QRectF& contentsRect,
274 const auto menu =
static_cast< const QskMenu*
>( skinnable );
276 if( subControl == Q::Panel )
281 if( subControl == Q::Cursor )
283 if ( menu->currentIndex() < 0 )
286 const qreal pos = menu->positionHint( Q::Cursor );
288 const int pos1 = qFloor( pos );
289 const int pos2 = qCeil( pos );
291 auto rect = cursorRect( skinnable, contentsRect, pos1 );
295 const auto r = cursorRect( skinnable, contentsRect, pos2 );
297 const qreal ratio = ( pos - pos1 ) / ( pos2 - pos1 );
298 rect = qskInterpolatedRect( rect, r, ratio );
304 return Inherited::subControlRect( skinnable, contentsRect, subControl );
307QRectF QskMenuSkinlet::sampleRect(
308 const QskSkinnable* skinnable,
const QRectF& contentsRect,
313 const auto menu =
static_cast< const QskMenu*
>( skinnable );
315 if ( subControl == Q::Segment )
317 const auto h = m_data->segmentHeight( menu );
321 if (
const auto n = menu->actions()[ index ] - index )
322 dy += n * qskPaddedSeparatorHeight( menu );
325 return QRectF( r.x(), r.y() + dy, r.width(), h );
328 if ( subControl == QskMenu::Icon || subControl == QskMenu::Text )
330 const auto r = sampleRect( menu, contentsRect, Q::Segment, index );
331 const auto graphicWidth = m_data->graphicWidth( menu );
333 if ( subControl == QskMenu::Icon )
335 auto graphicRect = r;
336 graphicRect.setWidth( graphicWidth );
337 const auto padding = menu->
paddingHint( QskMenu::Icon );
338 graphicRect = graphicRect.marginsRemoved( padding );
346 if ( graphicWidth > 0.0 )
348 const auto spacing = skinnable->
spacingHint( Q::Segment );
349 textRect.setX( r.x() + graphicWidth + spacing );
356 if ( subControl == QskMenu::Separator )
358 const auto separators = menu->separators();
359 if ( index >= separators.count() )
362 const auto h = qskPaddedSeparatorHeight( menu );
366 if (
const auto n = qskActionIndex( menu, separators[ index ] ) )
367 y += n * m_data->segmentHeight( menu );
370 return QRectF( r.left(), y, r.width(), h );
373 return Inherited::sampleRect(
374 skinnable, contentsRect, subControl, index );
377int QskMenuSkinlet::sampleIndexAt(
378 const QskSkinnable* skinnable,
const QRectF& contentsRect,
381 const PrivateData::CacheGuard guard( m_data.get() );
382 return Inherited::sampleIndexAt( skinnable, contentsRect, subControl, pos );
385int QskMenuSkinlet::sampleCount(
390 if ( subControl == Q::Segment || subControl == Q::Icon || subControl == Q::Text )
392 const auto menu =
static_cast< const QskMenu*
>( skinnable );
393 return menu->actions().count();
396 if ( subControl == Q::Separator )
398 const auto menu =
static_cast< const QskMenu*
>( skinnable );
399 return menu->separators().count();
402 return Inherited::sampleCount( skinnable, subControl );
405QskAspect::States QskMenuSkinlet::sampleStates(
411 auto states = Inherited::sampleStates( skinnable, subControl, index );
413 if ( subControl == Q::Segment || subControl == Q::Icon || subControl == Q::Text )
415 const auto menu =
static_cast< const QskMenu*
>( skinnable );
417 if ( menu->currentIndex() == menu->actions()[ index ] )
419 states |= Q::Selected;
421 if( menu->isPressed() )
423 states |= Q::Pressed;
427 states &=
~Q::Pressed;
432 Q::Segment |
Q::Hovered | A::Metric | A::Position ).toPointF();
434 if( !cursorPos.isNull() && menu->indexAtPosition( cursorPos ) == index )
440 states &=
~Q::Hovered;
447QVariant QskMenuSkinlet::sampleAt(
const QskSkinnable* skinnable,
452 if ( subControl == Q::Icon || subControl == Q::Text )
454 const auto menu =
static_cast< const QskMenu*
>( skinnable );
456 const auto option = menu->optionAt( index );
458 if ( subControl == Q::Icon )
459 return QVariant::fromValue( option.icon().graphic() );
461 return QVariant::fromValue( option.text() );
464 return Inherited::sampleAt( skinnable, subControl, index );
467QSGNode* QskMenuSkinlet::updateContentsNode(
468 const QskPopup* popup, QSGNode* contentsNode )
const
470 const PrivateData::CacheGuard guard( m_data.get() );
471 return updateMenuNode( popup, contentsNode );
474QSGNode* QskMenuSkinlet::updateMenuNode(
475 const QskSkinnable* skinnable, QSGNode* contentsNode )
const
477 enum { Panel, Segment, Cursor, Icon, Text, Separator };
478 static QVector< quint8 > roles = { Panel, Separator, Segment, Cursor, Icon, Text };
480 if ( contentsNode ==
nullptr )
481 contentsNode =
new QSGNode();
483 for (
const auto role : roles )
485 auto oldNode = QskSGNode::findChildNode( contentsNode, role );
487 QSGNode* newNode =
nullptr;
493 newNode = updateBoxNode( skinnable, oldNode, QskMenu::Panel );
498 newNode = updateSeriesNode( skinnable, QskMenu::Segment, oldNode );
503 newNode = updateBoxNode( skinnable, oldNode, QskMenu::Cursor );
508 newNode = updateSeriesNode( skinnable, QskMenu::Icon, oldNode );
513 newNode = updateSeriesNode( skinnable, QskMenu::Text, oldNode );
518 newNode = updateSeriesNode( skinnable, QskMenu::Separator, oldNode );
523 QskSGNode::replaceChildNode( roles, role, contentsNode, oldNode, newNode );
529QSGNode* QskMenuSkinlet::updateSampleNode(
const QskSkinnable* skinnable,
534 auto menu =
static_cast< const QskMenu*
>( skinnable );
536 const auto rect = sampleRect( menu, menu->
contentsRect(), subControl, index );
538 if ( subControl == Q::Segment )
540 return updateBoxNode( menu, node, rect, subControl );
543 if ( subControl == Q::Icon )
545 index = menu->actions()[ index ];
547 const auto graphic = menu->optionAt( index ).icon().graphic();
548 if ( graphic.isNull() )
551 const auto alignment = menu->
alignmentHint( subControl, Qt::AlignCenter );
554 return QskSkinlet::updateGraphicNode(
555 menu, node, graphic, filter, rect, alignment );
558 if ( subControl == Q::Text )
560 index = menu->actions()[ index ];
562 const auto text = menu->optionAt( index ).text();
563 if ( text.isEmpty() )
567 subControl, Qt::AlignVCenter | Qt::AlignLeft );
569 return QskSkinlet::updateTextNode( menu, node, rect,
570 alignment, text, Q::Text );
573 if ( subControl == Q::Separator )
576 if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() )
577 gradient.setLinearDirection( Qt::Vertical );
579 return updateBoxNode( menu, node, rect, gradient, subControl );
585QSizeF QskMenuSkinlet::sizeHint(
const QskSkinnable* skinnable,
586 Qt::SizeHint which,
const QSizeF& )
const
588 if ( which != Qt::PreferredSize )
592 const auto menu =
static_cast< const QskMenu*
>( skinnable );
594 const PrivateData::CacheGuard guard( m_data.get() );
599 if (
const auto count = sampleCount( skinnable, Q::Segment ) )
601 w = m_data->segmentWidth( menu );
602 h = count * m_data->segmentHeight( menu );
605 if (
const auto count = sampleCount( skinnable, Q::Separator ) )
607 h += count * qskPaddedSeparatorHeight( menu );
610 auto hint = skinnable->
outerBoxSize( QskMenu::Panel, QSizeF( w, h ) );
611 hint = hint.expandedTo( skinnable->
strutSizeHint( QskMenu::Panel ) );
616#include "moc_QskMenuSkinlet.cpp"
Lookup key for a QskSkinHintTable.
Subcontrol
For use within the rendering or lay-outing of a specific QskSkinnable.
static const QskAspect::State Hovered
QRectF subControlContentsRect(QskAspect::Subcontrol) const
QRectF contentsRect() const
QVariant effectiveSkinHint(QskAspect, QskSkinHintStatus *=nullptr) const
Find the value for a specific aspect.
qreal spacingHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a spacing hint.
QMarginsF paddingHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a padding hint.
QMarginsF marginHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a margin hint.
QskGradient gradientHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a color hint as gradient.
QFont effectiveFont(QskAspect) const
QSizeF strutSizeHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a strut size hint.
QSizeF outerBoxSize(QskAspect, const QSizeF &innerBoxSize) const
Calculate the size, when being expanded by paddings, indentations.
QskColorFilter effectiveGraphicFilter(QskAspect::Subcontrol) const
qreal metric(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a metric hint.
Qt::Alignment alignmentHint(QskAspect, Qt::Alignment=Qt::Alignment()) const
Retrieves an alignment hint.