From b84dfc8226be89c64d24a7e8f4b4571ac1bb7643 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Fri, 26 Aug 2022 12:56:12 +0200 Subject: [PATCH] QskSubcontrolLayoutEngine introduced --- examples/buttons/buttons.qml | 8 +- skins/material3/QskMaterial3Skin.cpp | 7 +- skins/squiek/QskSquiekSkin.cpp | 6 +- src/controls/QskPushButtonSkinlet.cpp | 240 +++++-------- src/controls/QskPushButtonSkinlet.h | 2 - src/layouts/QskLayoutElement.h | 10 +- src/layouts/QskSubcontrolLayoutEngine.cpp | 398 ++++++++++++++++++++++ src/layouts/QskSubcontrolLayoutEngine.h | 139 ++++++++ src/src.pro | 6 +- 9 files changed, 643 insertions(+), 173 deletions(-) create mode 100644 src/layouts/QskSubcontrolLayoutEngine.cpp create mode 100644 src/layouts/QskSubcontrolLayoutEngine.h diff --git a/examples/buttons/buttons.qml b/examples/buttons/buttons.qml index 66fbb160..3112b1ef 100644 --- a/examples/buttons/buttons.qml +++ b/examples/buttons/buttons.qml @@ -115,11 +115,17 @@ Qsk.Window enabled: false } - TestButton { graphicSource: "image://shapes/Diamond/SandyBrown" + graphicStrutSize + { + // no strutSize, so that the graphic is adjustd + width: -1 + height : -1 + } + shape { sizeMode: Qt.RelativeSize diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index d2f9267b..dc60d1bf 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -490,16 +490,15 @@ void Editor::setupPushButton() setBoxShape( Q::Panel, 100, Qt::RelativeSize ); - setAlignment( Q::Graphic | A::Alignment, Qt::AlignCenter ); + setAlignment( Q::Graphic, Qt::AlignCenter ); setStrutSize( Q::Graphic, 24_dp, 24_dp ); setPadding( Q::Graphic, 0 ); setFontRole( Q::Text, QskMaterial3Skin::M3LabelLarge ); setPadding( Q::Text, 0 ); - setAlignment( Q::Text | A::Alignment, Qt::AlignCenter ); - setAlignment( Q::Text | A::Alignment | A::Horizontal, - Qt::AlignLeft | Qt::AlignVCenter ); + setAlignment( Q::Text | A::Vertical, Qt::AlignCenter ); + setAlignment( Q::Text | A::Horizontal, Qt::AlignLeft | Qt::AlignVCenter ); // normal buttons (i.e. Filled): diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index 21d79194..6886ab27 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -607,13 +607,15 @@ void Editor::setupPushButton() setTextOptions( Q::Text, Qt::ElideMiddle, QskTextOptions::NoWrap ); setFlagHint( Q::Text | Q::Disabled | A::Style, Qsk::Sunken ); - setAlignment( Q::Text, Qt::AlignCenter ); + + setAlignment( Q::Text | A::Vertical, Qt::AlignCenter ); + setAlignment( Q::Text | A::Horizontal, Qt::AlignLeft | Qt::AlignVCenter ); setColor( Q::Text, m_pal.themeForeground ); setColor( Q::Text | Q::Disabled, m_pal.darker200 ); // Graphic - setPadding( Q::Graphic, 2 ); + setAlignment( Q::Graphic, Qt::AlignCenter ); } void Editor::setupDialogButton() diff --git a/src/controls/QskPushButtonSkinlet.cpp b/src/controls/QskPushButtonSkinlet.cpp index 3b3612cc..b687bb20 100644 --- a/src/controls/QskPushButtonSkinlet.cpp +++ b/src/controls/QskPushButtonSkinlet.cpp @@ -8,13 +8,9 @@ #include "QskAnimationHint.h" #include "QskGraphic.h" -#include "QskTextOptions.h" -#include "QskFunctions.h" +#include "QskSubcontrolLayoutEngine.h" #include "QskSGNode.h" -#include -#include - static inline Qt::Orientation qskOrientation( const QskPushButton* button ) { // For the moment we only handle the orientation TODO ... @@ -28,6 +24,76 @@ static inline Qt::Orientation qskOrientation( const QskPushButton* button ) return Qt::Vertical; } +namespace +{ + class LayoutEngine : public QskSubcontrolLayoutEngine + { + public: + LayoutEngine( const QskPushButton* button ) + : QskSubcontrolLayoutEngine( qskOrientation( button ) ) + { + using Q = QskPushButton; + + const auto graphicSourceSize = button->graphic().defaultSize(); + + const bool hasText = !button->text().isEmpty(); + const bool hasGraphic = !graphicSourceSize.isEmpty(); + + auto graphicElement = new GraphicElement( button, Q::Graphic ); + graphicElement->setSourceSize( graphicSourceSize ); + graphicElement->setIgnored( !hasGraphic ); + + auto textElement = new TextElement( button, Q::Text ); + textElement->setText( button->text() ); + textElement->setIgnored( !hasText ); + + using SP = QskSizePolicy; + + if ( hasText && !hasGraphic ) + { + textElement->setSizePolicy( SP::Preferred, SP::Constrained ); + } + else if ( hasGraphic && !hasText ) + { + const auto size = graphicElement->effectiveStrutSize(); + + if ( !size.isEmpty() ) + graphicElement->setFixedSize( size ); + else + graphicElement->setSizePolicy( SP::Ignored, SP::ConstrainedExpanding ); + } + else if ( hasText && hasGraphic ) + { + if ( orientation() == Qt::Horizontal ) + { + graphicElement->setSizePolicy( SP::Constrained, SP::Fixed ); + textElement->setSizePolicy( SP::Preferred, SP::Preferred ); + } + else + { + graphicElement->setSizePolicy( SP::Fixed, SP::Fixed ); + textElement->setSizePolicy( SP::Preferred, SP::Constrained ); + } + + auto size = graphicElement->effectiveStrutSize(); + + if ( size.isEmpty() ) + { + const auto h = 1.5 * button->effectiveFontHeight( Q::Text ); + + size.setWidth( graphicElement->widthForHeight( h ) ); + size.setHeight( h ); + } + + graphicElement->setPreferredSize( size ); + } + + setElementAt( 0, graphicElement ); + setElementAt( 1, textElement ); + } + }; +} + QskPushButtonSkinlet::QskPushButtonSkinlet( QskSkin* skin ) : Inherited( skin ) { @@ -101,29 +167,12 @@ QRectF QskPushButtonSkinlet::textRect( { using Q = QskPushButton; - auto r = button->subControlContentsRect( contentsRect, Q::Panel ); - if ( r.isEmpty() || !button->hasGraphic() ) - return r; + const auto r = button->subControlContentsRect( contentsRect, Q::Panel ); - if ( qskOrientation( button ) == Qt::Horizontal ) - { - /* - For horizontal layouts Text depends on Graphic, while - for vertical Graphic depends on Text. Confusing ... - */ - const auto graphicsRect = subControlRect( button, contentsRect, Q::Graphic ); - const auto spacing = button->spacingHint( Q::Panel ); + LayoutEngine layoutEngine( button ); + layoutEngine.setGeometries( r ); - r.setX( r.x() + graphicsRect.width() + spacing ); - } - else - { - const qreal h = button->effectiveFontHeight( Q::Text ); - if ( h < r.height() ) - r.setTop( r.bottom() - h ); - } - - return r; + return layoutEngine.elementAt( 1 )->geometry(); } QRectF QskPushButtonSkinlet::graphicRect( @@ -131,81 +180,12 @@ QRectF QskPushButtonSkinlet::graphicRect( { using Q = QskPushButton; - auto r = button->subControlContentsRect( contentsRect, Q::Panel ); - if ( r.isEmpty() || button->text().isEmpty() ) - return r; + const auto r = button->subControlContentsRect( contentsRect, Q::Panel ); - const auto orientation = qskOrientation( button ); - const auto maxSize = button->strutSizeHint( Q::Graphic ); + LayoutEngine layoutEngine( button ); + layoutEngine.setGeometries( r ); - const qreal maxW = maxSize.width(); - const qreal maxH = maxSize.height(); - - if ( orientation == Qt::Vertical ) - { - /* - For horizontal layouts Text depends on Graphic, while - for vertical Graphic depends on Text. Confusing ... - */ - const auto textRect = subControlRect( button, contentsRect, Q::Text ); - const auto h = textRect.height() + button->spacingHint( Q::Panel ); - - if ( h > r.height() ) - return QRectF(); - - r.setBottom( r.bottom() - h ); - - if ( maxW >= 0 || maxH >= 0 ) - { - // limiting the size by maxSize - - if ( maxW >= 0.0 && maxW < r.width() ) - { - r.setX( r.center().x() - 0.5 * maxW ); - r.setWidth( maxW ); - } - - if ( maxH >= 0.0 && maxH < r.height() ) - { - r.setY( r.center().y() - 0.5 * maxH ); - r.setHeight( maxH ); - } - } - } - else - { - if ( maxW >= 0 || maxH >= 0 ) - { - if ( maxW >= 0.0 && maxW < r.width() ) - { - r.setWidth( maxW ); - } - - if ( maxH >= 0.0 && maxH < r.height() ) - { - r.setY( r.center().y() - 0.5 * maxH ); - r.setHeight( maxH ); - } - } - } - - r = r.marginsRemoved( button->paddingHint( Q::Graphic ) ); - - if ( !r.isEmpty() ) - { - auto sz = button->graphic().defaultSize(); - if ( !sz.isEmpty() ) - { - sz.scale( r.size(), Qt::KeepAspectRatio ); - - if ( orientation == Qt::Vertical ) - r = qskAlignedRectF( r, sz, Qt::AlignCenter ); - else - r = qskAlignedRectF( r, sz, Qt::AlignLeft | Qt::AlignVCenter ); - } - } - - return r; + return layoutEngine.elementAt( 0 )->geometry(); } QRectF QskPushButtonSkinlet::rippleRect( @@ -226,7 +206,7 @@ QRectF QskPushButtonSkinlet::rippleRect( rect.setSize( 2.0 * panelRect.size() * ratio ); rect.moveCenter( pos ); } - + return rect; } @@ -271,7 +251,7 @@ QSGNode* QskPushButtonSkinlet::updateRippleNode( if ( boxNode->parent() != clipNode ) clipNode->appendChildNode( boxNode ); } - + return clipNode; } @@ -285,35 +265,9 @@ QSizeF QskPushButtonSkinlet::sizeHint( const QskSkinnable* skinnable, const auto button = static_cast< const QskPushButton* >( skinnable ); - QSizeF size( 0, 0 ); + LayoutEngine layoutEngine( button ); - const QFontMetricsF fm( button->effectiveFont( Q::Text ) ); - - if ( !button->text().isEmpty() ) - { - // in elide mode we might want to ignore the text width ??? - - size += fm.size( Qt::TextShowMnemonic, button->text() ); - } - - if ( button->hasGraphic() ) - { - const auto hint = graphicSizeHint( button ); - - const auto padding = button->paddingHint( Q::Graphic ); - const auto orientation = qskOrientation( button ); - - if( orientation == Qt::Horizontal ) - { - size.rwidth() += padding.left() + hint.width() + padding.right(); - size.rheight() = qMax( size.height(), hint.height() ); - } - else - { - size.rheight() += padding.top() + hint.height() + padding.bottom(); - size.rwidth() = qMax( size.width(), hint.width() ); - } - } + auto size = layoutEngine.sizeHint( which, QSizeF() ); size = size.expandedTo( button->strutSizeHint( Q::Panel ) ); size = button->outerBoxSize( Q::Panel, size ); @@ -321,32 +275,4 @@ QSizeF QskPushButtonSkinlet::sizeHint( const QskSkinnable* skinnable, return size; } -QSizeF QskPushButtonSkinlet::graphicSizeHint( const QskPushButton* button ) const -{ - using Q = QskPushButton; - - auto size = button->strutSizeHint( Q::Graphic ); - if ( !size.isEmpty() ) - return size; - - const auto& graphic = button->graphic(); - - auto w = size.width(); - auto h = size.height(); - - if ( w > 0.0 ) - { - h = graphic.heightForWidth( w ); - } - else - { - if ( h <= 0.0 ) - h = 1.5 * button->effectiveFontHeight( Q::Text ); - - w = graphic.widthForHeight( h ); - } - - return QSizeF( w, h ); -} - #include "moc_QskPushButtonSkinlet.cpp" diff --git a/src/controls/QskPushButtonSkinlet.h b/src/controls/QskPushButtonSkinlet.h index 9a85195b..2147a1d1 100644 --- a/src/controls/QskPushButtonSkinlet.h +++ b/src/controls/QskPushButtonSkinlet.h @@ -45,8 +45,6 @@ class QSK_EXPORT QskPushButtonSkinlet : public QskSkinlet QRectF graphicRect( const QskPushButton*, const QRectF& ) const; QRectF rippleRect( const QskPushButton*, const QRectF& ) const; - QSizeF graphicSizeHint( const QskPushButton* ) const; - QSGNode* updateTextNode( const QskPushButton*, QSGNode* ) const; QSGNode* updateRippleNode( const QskPushButton*, QSGNode* ) const; }; diff --git a/src/layouts/QskLayoutElement.h b/src/layouts/QskLayoutElement.h index a465e7b7..c66b0226 100644 --- a/src/layouts/QskLayoutElement.h +++ b/src/layouts/QskLayoutElement.h @@ -28,17 +28,17 @@ class QskLayoutElement QSizeF sizeConstraint( Qt::SizeHint, const QSizeF& constraint ) const; - private: - Q_DISABLE_COPY( QskLayoutElement ) - - qreal metric( Qt::Orientation, Qt::SizeHint, qreal constraint ) const; - virtual QSizeF sizeHint( Qt::SizeHint, const QSizeF& constraint = QSizeF() ) const = 0; qreal heightForWidth( qreal ) const; qreal widthForHeight( qreal ) const; + private: + Q_DISABLE_COPY( QskLayoutElement ) + + qreal metric( Qt::Orientation, Qt::SizeHint, qreal constraint ) const; + qreal boundedSize( Qt::Orientation, qreal ) const; }; diff --git a/src/layouts/QskSubcontrolLayoutEngine.cpp b/src/layouts/QskSubcontrolLayoutEngine.cpp new file mode 100644 index 00000000..607f6b06 --- /dev/null +++ b/src/layouts/QskSubcontrolLayoutEngine.cpp @@ -0,0 +1,398 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskSubcontrolLayoutEngine.h" +#include "QskLayoutElement.h" +#include "QskLayoutChain.h" +#include "QskTextRenderer.h" +#include "QskSkinnable.h" +#include "QskMargins.h" +#include "QskTextOptions.h" + +#include +#include +#include + +QskSubcontrolLayoutEngine::LayoutElement::LayoutElement( + const QskSkinnable* skinnable, const QskAspect::Subcontrol subControl ) + : m_skinnable( skinnable ) + , m_subControl( subControl ) +{ +} + +Qt::Alignment QskSubcontrolLayoutEngine::LayoutElement::alignment() const +{ + return m_skinnable->alignmentHint( m_subControl ); +} + +void QskSubcontrolLayoutEngine::LayoutElement::setMaximumSize( const QSizeF& size ) +{ + setExplicitSizeHint( Qt::MaximumSize, size ); +} + +void QskSubcontrolLayoutEngine::LayoutElement::setMinimumSize( const QSizeF& size ) +{ + setExplicitSizeHint( Qt::MinimumSize, size ); +} + +void QskSubcontrolLayoutEngine::LayoutElement::setPreferredSize( const QSizeF& size ) +{ + setExplicitSizeHint( Qt::PreferredSize, size ); +} + +void QskSubcontrolLayoutEngine::LayoutElement::setFixedSize( const QSizeF& size ) +{ + setSizePolicy( QskSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ) ); + + const auto newSize = size.expandedTo( QSizeF( 0, 0 ) ); + setExplicitSizeHint( Qt::PreferredSize, newSize ); +} + +void QskSubcontrolLayoutEngine::LayoutElement::setExplicitSizeHint( + Qt::SizeHint which, const QSizeF& size ) +{ + if ( which >= Qt::MinimumSize && which <= Qt::MaximumSize ) + { + const QSizeF newSize( ( size.width() < 0 ) ? -1.0 : size.width(), + ( size.height() < 0 ) ? -1.0 : size.height() ); + + m_explicitSizeHints[ which ] = size; + } +} + +QSizeF QskSubcontrolLayoutEngine::LayoutElement::sizeHint( + Qt::SizeHint which, const QSizeF& constraint ) const +{ + if ( which < Qt::MinimumSize || which > Qt::MaximumSize ) + return QSizeF( 0, 0 ); + + if ( constraint.isValid() ) + return constraint; + + const bool isConstrained = + constraint.width() >= 0 || constraint.height() >= 0; + + QSizeF hint; + + if ( !isConstrained ) + { + // explicit size hints are never constrained + hint = m_explicitSizeHints[ which ]; + } + + if ( which == Qt::PreferredSize ) + { + if ( isConstrained ) + { + const QskMargins padding = m_skinnable->paddingHint( m_subControl ); + + auto innerConstraint = constraint; + + if ( constraint.width() > 0 ) + { + const auto w = constraint.width() - padding.width(); + if ( w <= 0 ) + return QSizeF(); + + innerConstraint.setWidth( w ); + } + else if ( constraint.height() > 0 ) + { + const auto h = constraint.height() - padding.height(); + if ( h <= 0 ) + return QSizeF(); + + innerConstraint.setHeight( h ); + } + + hint = implicitSize( innerConstraint ); + + if ( hint.width() >= 0 ) + hint.setWidth( hint.width() + padding.width() ); + + if ( hint.height() >= 0 ) + hint.setHeight( hint.height() + padding.height() ); + } + else + { + if ( !hint.isValid() ) + { + const auto sz = implicitSize( constraint ); + + if ( hint.width() < 0 ) + hint.setWidth( sz.width() ); + + if ( hint.height() < 0 ) + hint.setHeight( sz.height() ); + } + } + } + else + { + if ( isConstrained ) + hint = QSizeF(); // not implemented + } + + return hint; +} + +QSizeF QskSubcontrolLayoutEngine::TextElement::implicitSize( const QSizeF& constraint ) const +{ + const auto font = skinnable()->effectiveFont( subControl() ); + const auto textOptions = skinnable()->textOptionsHint( subControl() ); + + QSizeF hint; + + const qreal lineHeight = QFontMetricsF( font ).height(); + + if ( m_text.isEmpty() ) + { + if ( constraint.height() < 0.0 ) + hint.setHeight( qCeil( lineHeight ) ); + } + else if ( constraint.width() >= 0.0 ) + { + if ( textOptions.effectiveElideMode() != Qt::ElideNone ) + { + hint.setHeight( qCeil( lineHeight ) ); + } + else + { + /* + In case of QskTextOptions::NoWrap we could count + the line numbers and calculate the height from + lineHeight. TODO ... + */ + + qreal maxHeight = std::numeric_limits< qreal >::max(); + if ( maxHeight / lineHeight > textOptions.maximumLineCount() ) + { + // be careful with overflows + maxHeight = textOptions.maximumLineCount() * lineHeight; + } + + QSizeF size( constraint.width(), maxHeight ); + size = QskTextRenderer::textSize( m_text, font, textOptions, size ); + + hint.setHeight( qCeil( size.height() ) ); + } + } + else if ( constraint.height() >= 0.0 ) + { + const qreal maxWidth = std::numeric_limits< qreal >::max(); + + QSizeF size( maxWidth, constraint.height() ); + size = QskTextRenderer::textSize( m_text, font, textOptions, size ); + + hint.setWidth( qCeil( size.width() ) ); + } + else + { + hint = QskTextRenderer::textSize( m_text, font, textOptions ); + } + + return hint; +} + +QSizeF QskSubcontrolLayoutEngine::GraphicElement::effectiveStrutSize() const +{ + auto size = skinnable()->strutSizeHint( subControl() ); + + if ( size.isEmpty() ) + { + const qreal aspectRatio = m_sourceSize.width() / m_sourceSize.height(); + + if ( size.width() > 0 ) + { + size.setHeight( size.width() / aspectRatio ); + } + else if ( size.height() > 0 ) + { + size.setWidth( size.height() * aspectRatio ); + } + } + + return size; +} + + +QSizeF QskSubcontrolLayoutEngine::GraphicElement::implicitSize( const QSizeF& constraint ) const +{ + auto hint = m_sourceSize; + + if ( !hint.isEmpty() ) + { + const qreal aspectRatio = hint.width() / hint.height(); + + if ( constraint.width() >= 0.0 ) + { + hint.setHeight( constraint.width() / aspectRatio ); + hint.setWidth( -1.0 ); + } + else if ( constraint.height() > 0.0 ) + { + hint.setWidth( constraint.height() * aspectRatio ); + hint.setHeight( -1.0 ); + } + } + + return hint; +} + + +static QskLayoutChain::CellData qskCell( const QskSubcontrolLayoutEngine::LayoutElement* element, + Qt::Orientation orientation, bool isLayoutOrientation, qreal constraint ) +{ + QskLayoutChain::CellData cell; + cell.isValid = true; + + const auto policy = element->sizePolicy().policy( orientation ); + + if ( isLayoutOrientation ) + { + const auto stretch = element->stretch(); + + if ( stretch < 0 ) + cell.stretch = ( policy & QskSizePolicy::ExpandFlag ) ? 1 : 0; + else + cell.stretch = stretch; + } + + cell.canGrow = policy & QskSizePolicy::GrowFlag; + cell.metrics = element->metrics( orientation, constraint ); + + return cell; +} + +class QskSubcontrolLayoutEngine::PrivateData +{ + public: + PrivateData( Qt::Orientation orientation ) + : orientation( orientation ) + { + } + + Qt::Orientation orientation; + LayoutElement* elements[2] = {}; +}; + +QskSubcontrolLayoutEngine::QskSubcontrolLayoutEngine( Qt::Orientation orientation ) + : m_data( new PrivateData( orientation ) ) +{ + setExtraSpacingAt( Qt::TopEdge | Qt::BottomEdge | Qt::LeftEdge | Qt::RightEdge ); +} + +QskSubcontrolLayoutEngine::~QskSubcontrolLayoutEngine() +{ + for ( auto element : m_data->elements ) + delete element; +} + +bool QskSubcontrolLayoutEngine::setOrientation( Qt::Orientation orientation ) +{ + if ( m_data->orientation != orientation ) + { + m_data->orientation = orientation; + invalidate( LayoutCache ); + + return true; + } + + return false; +} + +Qt::Orientation QskSubcontrolLayoutEngine::orientation() const +{ + return m_data->orientation; +} + +void QskSubcontrolLayoutEngine::setElementAt( int index, LayoutElement* element ) +{ + if ( index >= 0 && index < count() ) + { + delete m_data->elements[ index ]; + m_data->elements[ index ] = element; + } +} + +QskSubcontrolLayoutEngine::LayoutElement* QskSubcontrolLayoutEngine::elementAt( int index ) const +{ + if ( index >= 0 && index < count() ) + return m_data->elements[ index ]; + + return nullptr; +} + +QskSizePolicy QskSubcontrolLayoutEngine::sizePolicyAt( int index ) const +{ + if ( index >= 0 && index < count() ) + { + if ( auto element = m_data->elements[ index ] ) + return element->sizePolicy(); + } + + return QskSizePolicy(); +} + +int QskSubcontrolLayoutEngine::count() const +{ + return sizeof( m_data->elements ) / sizeof( m_data->elements[0] ); +} + +void QskSubcontrolLayoutEngine::layoutItems() +{ + int row = 0; + int col = 0; + + for ( auto element : m_data->elements ) + { + if ( element && !element->isIgnored() ) + { + const auto rect = geometryAt( element, QRect( col, row, 1, 1 ) ); + element->setGeometry( rect ); + + if ( m_data->orientation == Qt::Horizontal ) + col++; + else + row++; + } + } +} + +int QskSubcontrolLayoutEngine::effectiveCount( Qt::Orientation orientation ) const +{ + return ( orientation == m_data->orientation ) ? 2 : 1; +} + +void QskSubcontrolLayoutEngine::invalidateElementCache() +{ +} + +void QskSubcontrolLayoutEngine::setupChain( Qt::Orientation orientation, + const QskLayoutChain::Segments& constraints, QskLayoutChain& chain ) const +{ + uint index1 = 0; + uint index2 = 0; + + const bool isLayoutOrientation = ( orientation == m_data->orientation ); + + for ( auto element : m_data->elements ) + { + if ( element == nullptr || element->isIgnored() ) + continue; + + qreal constraint = -1.0; + if ( !constraints.isEmpty() ) + constraint = constraints[index1].length; + + const auto cell = qskCell( element, orientation, isLayoutOrientation, constraint ); + chain.expandCell( index2, cell ); + + if ( isLayoutOrientation ) + index2++; + else + index1++; + } +} diff --git a/src/layouts/QskSubcontrolLayoutEngine.h b/src/layouts/QskSubcontrolLayoutEngine.h new file mode 100644 index 00000000..81c7da59 --- /dev/null +++ b/src/layouts/QskSubcontrolLayoutEngine.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_SUBCONTROL_LAYOUT_ENGINE_H +#define QSK_SUBCONTROL_LAYOUT_ENGINE_H + +#include "QskGlobal.h" +#include "QskLayoutEngine2D.h" +#include "QskLayoutElement.h" +#include "QskAspect.h" + +#include +#include + +class QskSkinnable; + +/* + For the moment this layout is tailored for arranging one text and one graphic + horizontally/vertically. Candidate for becoming something more general in the future.. + */ +class QskSubcontrolLayoutEngine : public QskLayoutEngine2D +{ + public: + class LayoutElement : public QskLayoutElement + { + public: + LayoutElement( const QskSkinnable*, const QskAspect::Subcontrol ); + + inline const QskSkinnable* skinnable() const { return m_skinnable; } + inline QskAspect::Subcontrol subControl() const { return m_subControl; } + + inline void setSizePolicy( + QskSizePolicy::Policy horizontalPolicy, + QskSizePolicy::Policy verticalPolicy ) + { + setSizePolicy( QskSizePolicy( horizontalPolicy, verticalPolicy ) ); + } + + inline void setSizePolicy( QskSizePolicy policy ) { m_sizePolicy = policy; } + inline QskSizePolicy sizePolicy() const override { return m_sizePolicy; } + + virtual Qt::Alignment alignment() const override; + + inline void setIgnored( bool on ) { m_ignored = on; } + inline bool isIgnored() const { return m_ignored; } + + inline void setStretch( int stretch ) { m_stretch = stretch; } + inline int stretch() const { return m_stretch; } + + inline void setGeometry( const QRectF& rect ) { m_geometry = rect; } + inline const QRectF& geometry() const { return m_geometry; } + + void setMaximumSize( const QSizeF& ); + void setMinimumSize( const QSizeF& ); + void setPreferredSize( const QSizeF& ); + void setFixedSize( const QSizeF& ); + + void setExplicitSizeHint( Qt::SizeHint, const QSizeF& ); + + private: + QSizeF sizeHint( Qt::SizeHint, const QSizeF& ) const override; + virtual QSizeF implicitSize( const QSizeF& ) const = 0; + + int m_stretch = -1; + bool m_ignored = false; + + QskSizePolicy m_sizePolicy; + + QSizeF m_explicitSizeHints[3]; + QRectF m_geometry; + + const QskSkinnable* m_skinnable; + const QskAspect::Subcontrol m_subControl; + }; + + class TextElement : public LayoutElement + { + public: + TextElement( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) + : LayoutElement( skinnable, subControl ) + { + } + + inline void setText( const QString& text ) { m_text = text; } + inline QString text() const { return m_text; } + + private: + QSizeF implicitSize( const QSizeF& ) const override; + QString m_text; + }; + + class GraphicElement : public LayoutElement + { + public: + GraphicElement( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) + : LayoutElement( skinnable, subControl ) + { + } + + inline void setSourceSize( const QSizeF& size ) { m_sourceSize = size; } + inline QSizeF sourceSize() const { return m_sourceSize; } + + QSizeF effectiveStrutSize() const; + + private: + QSizeF implicitSize( const QSizeF& ) const override; + + QSizeF m_sourceSize; + }; + + explicit QskSubcontrolLayoutEngine( Qt::Orientation ); + ~QskSubcontrolLayoutEngine() override; + + Qt::Orientation orientation() const; + bool setOrientation( Qt::Orientation ); + + void setElementAt( int index, LayoutElement* ); + LayoutElement* elementAt( int ) const; + + int count() const override final; + + private: + QskSizePolicy sizePolicyAt( int index ) const override; + void layoutItems() override; + + int effectiveCount( Qt::Orientation ) const override; + + void invalidateElementCache() override; + + virtual void setupChain( Qt::Orientation, + const QskLayoutChain::Segments&, QskLayoutChain& ) const override; + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/src.pro b/src/src.pro index f2ea9187..959b2c36 100644 --- a/src/src.pro +++ b/src/src.pro @@ -322,7 +322,8 @@ HEADERS += \ layouts/QskLinearBox.h \ layouts/QskLinearLayoutEngine.h \ layouts/QskStackBoxAnimator.h \ - layouts/QskStackBox.h + layouts/QskStackBox.h \ + layouts/QskSubcontrolLayoutEngine.h SOURCES += \ layouts/QskGridBox.cpp \ @@ -335,7 +336,8 @@ SOURCES += \ layouts/QskLinearBox.cpp \ layouts/QskLinearLayoutEngine.cpp \ layouts/QskStackBoxAnimator.cpp \ - layouts/QskStackBox.cpp + layouts/QskStackBox.cpp \ + layouts/QskSubcontrolLayoutEngine.cpp HEADERS += \ dialogs/QskDialog.h \