From 81a90986b37ac4e45e0b0b275d0ff89134c4b249 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Tue, 16 May 2023 12:49:46 +0200 Subject: [PATCH] Empty QskLabelData is interpreted as separator now. Not sure how much of an improvement this is as it adds the separators to the list of options. But at least this allows to implement a wrapper like QskMenuButton by copying options only. Definitely not the final word on this API. --- examples/gallery/main.cpp | 4 +- skins/squiek/QskSquiekSkin.cpp | 1 + src/controls/QskMenu.cpp | 110 ++++++++++++++++++++---------- src/controls/QskMenu.h | 14 ++-- src/controls/QskMenuSkinlet.cpp | 114 ++++++++++++++++++-------------- 5 files changed, 148 insertions(+), 95 deletions(-) diff --git a/examples/gallery/main.cpp b/examples/gallery/main.cpp index 95367894..6d09297a 100644 --- a/examples/gallery/main.cpp +++ b/examples/gallery/main.cpp @@ -187,7 +187,7 @@ namespace // see https://github.com/uwerat/qskinny/issues/192 connect( menu, &QskMenu::triggered, - []( int index ) { if ( index == 3 ) qApp->quit(); } ); + []( int index ) { if ( index == 4 ) qApp->quit(); } ); } }; @@ -224,7 +224,7 @@ namespace drawer->setEdge( Qt::RightEdge ); auto burger = new QskPushButton( "≡", this ); - burger->setEmphasis( QskPushButton::LowEmphasis ); + burger->setEmphasis( QskPushButton::LowEmphasis ); connect( burger, &QskPushButton::clicked, drawer, &QskPopup::open ); diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index e757e5b5..907d50a5 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -428,6 +428,7 @@ void Editor::setupMenu() setMetric( Q::Separator | A::Size, 2_dp ); setSeparator( Q::Separator ); + setMargin( Q::Separator, 2 ); setPadding( Q::Segment, QskMargins( 2, 10, 2, 10 ) ); setSpacing( Q::Segment, 5 ); diff --git a/src/controls/QskMenu.cpp b/src/controls/QskMenu.cpp index 4d2395fe..3e2c2cba 100644 --- a/src/controls/QskMenu.cpp +++ b/src/controls/QskMenu.cpp @@ -28,14 +28,24 @@ QSK_SUBCONTROL( QskMenu, Separator ) QSK_SYSTEM_STATE( QskMenu, Selected, QskAspect::FirstSystemState << 2 ) +static inline int qskActionIndex( const QVector< int >& actions, int index ) +{ + if ( index < 0 ) + return -1; + + auto it = std::lower_bound( actions.constBegin(), actions.constEnd(), index ); + return it - actions.constBegin(); +} + class QskMenu::PrivateData { public: QPointF origin; QVector< QskLabelData > options; - // QVector< bool > enabled; + QVector< int > separators; + QVector< int > actions; int triggeredIndex = -1; int currentIndex = -1; @@ -126,15 +136,22 @@ int QskMenu::addOption( const QUrl& graphicSource, const QString& text ) int QskMenu::addOption( const QskLabelData& option ) { + const int index = m_data->options.count(); + m_data->options += option; + if ( option.isEmpty() ) + m_data->separators += index; + else + m_data->actions += index; + resetImplicitSize(); update(); if ( isComponentComplete() ) Q_EMIT optionsChanged(); - return count() - 1; + return index; } void QskMenu::setOptions( const QStringList& options ) @@ -146,6 +163,14 @@ void QskMenu::setOptions( const QVector< QskLabelData >& options ) { m_data->options = options; + for ( int i = 0; i < options.count(); i++ ) + { + if ( options[i].isEmpty() ) + m_data->separators += i; + else + m_data->actions += i; + } + if ( m_data->currentIndex >= 0 ) { m_data->currentIndex = -1; @@ -163,7 +188,6 @@ void QskMenu::setOptions( const QVector< QskLabelData >& options ) void QskMenu::clear() { - m_data->separators.clear(); setOptions( QVector< QskLabelData >() ); } @@ -177,27 +201,19 @@ QskLabelData QskMenu::optionAt( int index ) const return m_data->options.value( index ); } -int QskMenu::count() const -{ - return m_data->options.count(); -} - void QskMenu::addSeparator() { - m_data->separators += m_data->options.count(); - - resetImplicitSize(); - update(); + addOption( QskLabelData() ); } -int QskMenu::separatorPosition( int index ) const +QVector< int > QskMenu::separators() const { - return m_data->separators.value( index, -1 ); + return m_data->separators; } -int QskMenu::separatorCount() const +QVector< int > QskMenu::actions() const { - return m_data->separators.count(); + return m_data->actions; } int QskMenu::currentIndex() const @@ -207,8 +223,15 @@ int QskMenu::currentIndex() const void QskMenu::setCurrentIndex( int index ) { - if( index < 0 || index >= count() ) + if( index < 0 || index >= m_data->options.count() ) + { index = -1; + } + else + { + if ( m_data->options[index].isEmpty() ) // a seperator + index = -1; + } if( index != m_data->currentIndex ) { @@ -308,26 +331,34 @@ void QskMenu::wheelEvent( QWheelEvent* event ) void QskMenu::traverse( int steps ) { - if ( count() == 0 || ( steps % count() == 0 ) ) + const auto& actions = m_data->actions; + const auto count = actions.count(); + + // -1 -> only one entry ? + if ( actions.isEmpty() || ( steps % count == 0 ) ) return; - auto index = m_data->currentIndex + steps; + int action1 = qskActionIndex( actions, m_data->currentIndex ); + int action2 = action1 + steps; - auto newIndex = index % count(); - if ( newIndex < 0 ) - newIndex += count(); + // when cycling we want to slide in + int index1; - // when cycling we want slide in + if ( action2 < 0 ) + index1 = m_data->options.count(); + else if ( action2 >= count ) + index1 = -1; + else + index1 = actions[ action1 ]; - int startIndex = m_data->currentIndex; + action2 %= count; + if ( action2 < 0 ) + action2 += count; - if ( index < 0 ) - startIndex = count(); - else if ( index >= count() ) - startIndex = -1; + const auto index2 = actions[ action2 ]; - movePositionHint( Cursor, startIndex, newIndex ); - setCurrentIndex( newIndex ); + movePositionHint( Cursor, index1, index2 ); + setCurrentIndex( index2 ); } void QskMenu::mousePressEvent( QMouseEvent* event ) @@ -384,7 +415,10 @@ void QskMenu::aboutToShow() setGeometry( QRectF( m_data->origin, sizeConstraint() ) ); if ( m_data->currentIndex < 0 ) - setCurrentIndex( 0 ); + { + if ( !m_data->actions.isEmpty() ) + setCurrentIndex( m_data->actions.first() ); + } Inherited::aboutToShow(); } @@ -396,8 +430,10 @@ QRectF QskMenu::focusIndicatorRect() const if( currentIndex() >= 0 ) { + auto actionIndex = qskActionIndex( m_data->actions, currentIndex() ); + return effectiveSkinlet()->sampleRect( this, - contentsRect(), Segment, currentIndex() ); + contentsRect(), Segment, actionIndex ); } return Inherited::focusIndicatorRect(); @@ -405,19 +441,23 @@ QRectF QskMenu::focusIndicatorRect() const QRectF QskMenu::cellRect( int index ) const { + const auto actionIndex = qskActionIndex( m_data->actions, index ); + return effectiveSkinlet()->sampleRect( - this, contentsRect(), QskMenu::Segment, index ); + this, contentsRect(), QskMenu::Segment, actionIndex ); } int QskMenu::indexAtPosition( const QPointF& pos ) const { - return effectiveSkinlet()->sampleIndexAt( + const auto index = effectiveSkinlet()->sampleIndexAt( this, contentsRect(), QskMenu::Segment, pos ); + + return m_data->actions.value( index, -1 ); } void QskMenu::trigger( int index ) { - if ( index >= 0 && index < m_data->options.count() ) + if ( index >= 0 && index < m_data->options.count() ) { m_data->triggeredIndex = index; Q_EMIT triggered( index ); diff --git a/src/controls/QskMenu.h b/src/controls/QskMenu.h index 5d46223a..9a4e6624 100644 --- a/src/controls/QskMenu.h +++ b/src/controls/QskMenu.h @@ -26,8 +26,6 @@ class QSK_EXPORT QskMenu : public QskPopup Q_PROPERTY( QVector< QskLabelData > options READ options WRITE setOptions NOTIFY optionsChanged ) - Q_PROPERTY( int count READ count ) - Q_PROPERTY( int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged ) @@ -58,6 +56,7 @@ class QSK_EXPORT QskMenu : public QskPopup int addOption( const QString&, const QString& ); int addOption( const QUrl&, const QString& ); int addOption( const QskLabelData& ); + void addSeparator(); void setOptions( const QVector< QskLabelData >& ); void setOptions( const QStringList& ); @@ -65,14 +64,8 @@ class QSK_EXPORT QskMenu : public QskPopup QVector< QskLabelData > options() const; QskLabelData optionAt( int ) const; - int count() const; - - void addSeparator(); - - int separatorPosition( int ) const; - int separatorCount() const; - - void clear(); + QVector< int > separators() const; + QVector< int > actions() const; int currentIndex() const; QString currentText() const; @@ -98,6 +91,7 @@ class QSK_EXPORT QskMenu : public QskPopup public Q_SLOTS: void setCurrentIndex( int ); + void clear(); protected: void keyPressEvent( QKeyEvent* ) override; diff --git a/src/controls/QskMenuSkinlet.cpp b/src/controls/QskMenuSkinlet.cpp index ccced571..b5e9b8e3 100644 --- a/src/controls/QskMenuSkinlet.cpp +++ b/src/controls/QskMenuSkinlet.cpp @@ -18,6 +18,29 @@ #include #include +static inline int qskActionIndex( const QskMenu* menu, int optionIndex ) +{ + if ( optionIndex < 0 ) + return -1; + + const auto& actions = menu->actions(); + + auto it = std::lower_bound( + actions.constBegin(), actions.constEnd(), optionIndex ); + + return it - actions.constBegin(); +} + +static inline qreal qskPaddedSeparatorHeight( const QskMenu* menu ) +{ + using Q = QskMenu; + + const auto margins = menu->marginHint( Q::Separator ); + + return menu->metric( Q::Separator | QskAspect::Size ) + + margins.top() + margins.bottom(); +} + class QskMenuSkinlet::PrivateData { public: @@ -45,18 +68,6 @@ class QskMenuSkinlet::PrivateData m_segmentHeight = m_segmentWidth = m_graphicWidth = m_textWidth = -1.0; } - inline int separatorsBefore( const QskMenu* menu, int index ) const - { - int i = 0; - for ( ; i < menu->separatorCount(); i++ ) - { - if ( menu->separatorPosition( i ) > index ) - break; - } - - return i; - } - inline qreal graphicWidth( const QskMenu* menu ) const { if ( m_isCaching ) @@ -112,8 +123,6 @@ class QskMenuSkinlet::PrivateData private: qreal graphicWidthInternal( const QskMenu* menu ) const { - const auto skinlet = menu->effectiveSkinlet(); - const auto hint = menu->strutSizeHint( QskMenu::Icon ); const qreal textHeight = menu->effectiveFontHeight( QskMenu::Text ); @@ -138,8 +147,6 @@ class QskMenuSkinlet::PrivateData qreal textWidthInternal( const QskMenu* menu ) const { - const auto skinlet = menu->effectiveSkinlet(); - const QFontMetricsF fm( menu->effectiveFont( QskMenu::Text ) ); auto maxWidth = 0.0; @@ -210,16 +217,29 @@ QskMenuSkinlet::~QskMenuSkinlet() = default; QRectF QskMenuSkinlet::cursorRect( const QskSkinnable* skinnable, const QRectF& contentsRect, int index ) const { - const auto count = sampleCount( skinnable, QskMenu::Segment ); + using Q = QskMenu; - auto rect = sampleRect( skinnable, contentsRect, - QskMenu::Segment, qBound( 0, index, count ) ); + const auto menu = static_cast< const QskMenu* >( skinnable ); + const auto actions = menu->actions(); + + index = qskActionIndex( menu, index ); + + QRectF rect; if ( index < 0 ) + { + rect = sampleRect( skinnable, contentsRect, Q::Segment, 0 ); rect.setBottom( rect.top() ); - - if ( index >= count ) + } + else if ( index >= actions.count() ) + { + rect = sampleRect( skinnable, contentsRect, Q::Segment, actions.count() - 1 ); rect.setTop( rect.bottom() ); + } + else + { + rect = sampleRect( skinnable, contentsRect, Q::Segment, index ); + } return rect; } @@ -273,19 +293,15 @@ QRectF QskMenuSkinlet::sampleRect( if ( subControl == Q::Segment ) { + const auto h = m_data->segmentHeight( menu ); + + auto dy = index * h; + + if ( const auto n = menu->actions()[ index ] - index ) + dy += n * qskPaddedSeparatorHeight( menu ); + const auto r = menu->subControlContentsRect( Q::Panel ); - - auto h = m_data->segmentHeight( menu ); - - if ( int n = m_data->separatorsBefore( menu, index ) ) - { - // spacing ??? - - const qreal separatorH = menu->metric( Q::Separator | QskAspect::Size ); - h += n * separatorH; - } - - return QRectF( r.x(), r.y() + index * h, r.width(), h ); + return QRectF( r.x(), r.y() + dy, r.width(), h ); } if ( subControl == QskMenu::Icon || subControl == QskMenu::Text ) @@ -318,22 +334,19 @@ QRectF QskMenuSkinlet::sampleRect( if ( subControl == QskMenu::Separator ) { - const int pos = menu->separatorPosition( index ); - if ( pos < 0 ) + const auto separators = menu->separators(); + if ( index >= separators.count() ) return QRectF(); - QRectF r = menu->subControlContentsRect( Q::Panel ); + const auto h = qskPaddedSeparatorHeight( menu ); - if ( pos < menu->count() ) - { - const auto segmentRect = sampleRect( skinnable, contentsRect, Q::Segment, pos ); - r.setBottom( segmentRect.top() ); // spacing ??? - } + auto y = index * h; - const qreal h = menu->metric( Q::Separator | QskAspect::Size ); - r.setTop( r.bottom() - h ); + if ( const auto n = qskActionIndex( menu, separators[ index ] ) ) + y += n * m_data->segmentHeight( menu ); - return r; + const auto r = menu->subControlContentsRect( Q::Panel ); + return QRectF( r.left(), y, r.width(), h ); } return Inherited::sampleRect( @@ -356,13 +369,13 @@ int QskMenuSkinlet::sampleCount( if ( subControl == Q::Segment || subControl == Q::Icon || subControl == Q::Text ) { const auto menu = static_cast< const QskMenu* >( skinnable ); - return menu->count(); + return menu->actions().count(); } if ( subControl == Q::Separator ) { const auto menu = static_cast< const QskMenu* >( skinnable ); - return menu->separatorCount(); + return menu->separators().count(); } return Inherited::sampleCount( skinnable, subControl ); @@ -378,7 +391,8 @@ QskAspect::States QskMenuSkinlet::sampleStates( if ( subControl == Q::Segment || subControl == Q::Icon || subControl == Q::Text ) { const auto menu = static_cast< const QskMenu* >( skinnable ); - if ( menu->currentIndex() == index ) + + if ( menu->currentIndex() == menu->actions()[ index ] ) states |= QskMenu::Selected; } @@ -483,6 +497,8 @@ QSGNode* QskMenuSkinlet::updateSampleNode( const QskSkinnable* skinnable, if ( subControl == Q::Icon ) { + index = menu->actions()[ index ]; + const auto graphic = menu->optionAt( index ).icon().graphic(); if ( graphic.isNull() ) return nullptr; @@ -496,6 +512,8 @@ QSGNode* QskMenuSkinlet::updateSampleNode( const QskSkinnable* skinnable, if ( subControl == Q::Text ) { + index = menu->actions()[ index ]; + const auto text = menu->optionAt( index ).text(); if ( text.isEmpty() ) return nullptr; @@ -541,7 +559,7 @@ QSizeF QskMenuSkinlet::sizeHint( const QskSkinnable* skinnable, if ( const auto count = sampleCount( skinnable, Q::Separator ) ) { - h += count * menu->metric( Q::Separator | QskAspect::Size ); + h += count * qskPaddedSeparatorHeight( menu ); } auto hint = skinnable->outerBoxSize( QskMenu::Panel, QSizeF( w, h ) );