diff --git a/playground/anchors/AnchorBox.cpp b/playground/anchors/AnchorBox.cpp index 5b6ebd68..1f385794 100644 --- a/playground/anchors/AnchorBox.cpp +++ b/playground/anchors/AnchorBox.cpp @@ -12,9 +12,9 @@ #include #include -#include #include +#include static inline Qt::Orientation qskOrientation( int edge ) { @@ -100,14 +100,158 @@ namespace QQuickItem* item2 = nullptr; Qt::AnchorPoint edge2; }; + + class LayoutSolver : public Solver + { + public: + void setup( bool layoutChildren, + const QVector< Anchor >&, std::map< QQuickItem*, Geometry >& ); + + void addSizeConstraints(); + + QSizeF resolvedSize(); + QSizeF resolvedSize( qreal width, qreal height ); + + void resolve( qreal width, qreal height ); + + private: + void addSizeConstraints( Geometry& rect, const QSizeF& size, + RelationalOperator op, double strength ); + + Variable m_width, m_height; + }; +} + +void LayoutSolver::setup( bool layoutChildren, + const QVector< Anchor >& anchors, + std::map< QQuickItem*, Geometry >& geometries ) +{ + for ( const auto& anchor : anchors ) + { + auto& r1 = geometries[ anchor.item1 ]; + + const auto expr1 = r1.expressionAt( anchor.edge1 ); + + Expression expr2; + + if ( anchor.item2 == nullptr ) + { + switch( anchor.edge2 ) + { + case Qt::AnchorLeft: + case Qt::AnchorTop: + expr2 = 0; + break; + + case Qt::AnchorHorizontalCenter: + expr2 = Term( 0.5 * m_width ); + break; + + case Qt::AnchorRight: + expr2 = Term( m_width ); + break; + + case Qt::AnchorVerticalCenter: + expr2 = Term( 0.5 * m_height ); + break; + + case Qt::AnchorBottom: + expr2 = Term( m_height ); + break; + } + + addConstraint( expr1 == expr2 ); + } + else + { + auto& r2 = geometries[ anchor.item2 ]; + const auto expr2 = r2.expressionAt( anchor.edge2 ); + + addConstraint( expr1 == expr2 ); + + if ( layoutChildren ) + { + const auto o = qskOrientation( anchor.edge1 ); + + /* + A constraint with medium strength to make anchored item + being stretched according to their stretch factors s1, s2. + ( For the moment we don't support having specific factors. ) + */ + const auto s1 = 1.0; + const auto s2 = 1.0; + + Constraint c( r1.length( o ) * s1 == r2.length( o ) * s2, Strength::medium ); + addConstraint( c ); + } + } + } + + for ( auto it = geometries.begin(); it != geometries.end(); ++it ) + { + const auto minSize = qskSizeConstraint( it->first, Qt::MinimumSize ); + addSizeConstraints( it->second, minSize, OP_GE, Strength::required ); + + const auto maxSize = qskSizeConstraint( it->first, Qt::MaximumSize ); + addSizeConstraints( it->second, maxSize, OP_LE, Strength::required ); + + const auto prefSize = qskSizeConstraint( it->first, Qt::PreferredSize ); + addSizeConstraints( it->second, prefSize, OP_EQ, Strength::strong ); + } +} + +void LayoutSolver::addSizeConstraints() +{ + const double strength = 0.9 * Strength::required; + + addEditVariable( m_width, strength ); + addEditVariable( m_height, strength ); +} + +void LayoutSolver::resolve( qreal width, qreal height ) +{ + suggestValue( m_width, width ); + suggestValue( m_height, height ); + updateVariables(); +} + +QSizeF LayoutSolver::resolvedSize() +{ + updateVariables(); + return QSizeF( m_width.value(), m_height.value() ); +} + +QSizeF LayoutSolver::resolvedSize( qreal width, qreal height ) +{ + resolve( width, height ); + return QSizeF( m_width.value(), m_height.value() ); +} + +void LayoutSolver::addSizeConstraints( Geometry& rect, const QSizeF& size, + RelationalOperator op, double strength ) +{ + if ( size.width() >= 0.0 ) + { + const Constraint c( rect.width() - size.width(), op, strength ); + addConstraint( c ); + } + + if ( size.height() >= 0.0 ) + { + const Constraint c( rect.height() - size.height(), op, strength ); + addConstraint( c ); + } } class AnchorBox::PrivateData { public: - QMap< QQuickItem*, Geometry > geometries; + ~PrivateData() { delete solver; } + + std::map< QQuickItem*, Geometry > geometries; + QVector< Anchor > anchors; - Solver solver; + LayoutSolver* solver = nullptr; QSizeF hints[3]; bool hasValidHints = false; @@ -117,7 +261,6 @@ AnchorBox::AnchorBox( QQuickItem* parent ) : QskControl( parent ) , m_data( new PrivateData ) { - (void)m_data->geometries[ this ]; } AnchorBox::~AnchorBox() @@ -137,6 +280,7 @@ void AnchorBox::addAnchors( QQuickItem* item1, addAnchor( item1, Qt::AnchorLeft, item2, Qt::AnchorLeft ); addAnchor( item1, Qt::AnchorRight, item2, Qt::AnchorRight ); } + if ( orientations & Qt::Vertical ) { addAnchor( item1, Qt::AnchorTop, item2, Qt::AnchorTop ); @@ -174,26 +318,27 @@ void AnchorBox::addAnchor( QQuickItem* item1, Qt::AnchorPoint edge1, if ( item1 == this ) std::swap( item1, item2 ); - if ( item1 != this ) - { - if ( item1->parent() == nullptr ) - item1->setParent( this ); + if ( item2 == this ) + item2 = nullptr; - if ( item1->parentItem() != this ) - item1->setParentItem( this ); - } + if ( item1->parent() == nullptr ) + item1->setParent( this ); - if ( item2 != this ) + if ( item1->parentItem() != this ) + item1->setParentItem( this ); + + (void)m_data->geometries[ item1 ]; + + if ( item2 ) { if ( item2->parent() == nullptr ) item2->setParent( this ); if ( item2->parentItem() != this ) item2->setParentItem( this ); - } - (void)m_data->geometries[ item1 ]; - (void)m_data->geometries[ item2 ]; + (void)m_data->geometries[ item2 ]; + } Anchor anchor; anchor.item1 = item1; @@ -204,6 +349,20 @@ void AnchorBox::addAnchor( QQuickItem* item1, Qt::AnchorPoint edge1, m_data->anchors += anchor; } +void AnchorBox::geometryChangeEvent( QskGeometryChangeEvent* event ) +{ + Inherited::geometryChangeEvent( event ); + + if ( event->isResized() ) + polish(); +} + +void AnchorBox::updateLayout() +{ + if ( !maybeUnresized() ) + updateGeometries( layoutRect() ); +} + QSizeF AnchorBox::layoutSizeHint( Qt::SizeHint which, const QSizeF& constraint ) const { if ( constraint.width() >= 0.0 || constraint.height() >= 0.0 ) @@ -225,211 +384,43 @@ QSizeF AnchorBox::layoutSizeHint( Qt::SizeHint which, const QSizeF& constraint ) void AnchorBox::updateHints() { - const auto& r0 = m_data->geometries[ const_cast< AnchorBox* >( this ) ]; + /* + The solver seems to run into overflows with + std::numeric_limits< unsigned float >::max() + */ + const qreal max = std::numeric_limits< unsigned int >::max(); - Solver solver; + LayoutSolver solver; + solver.setup( false, m_data->anchors, m_data->geometries ); - setupAnchorConstraints( false, solver ); - setupSizeConstraints( true, solver ); + m_data->hints[ Qt::PreferredSize ] = solver.resolvedSize(); - { - solver.updateVariables(); - m_data->hints[ Qt::PreferredSize ] = r0.size(); - } + solver.addSizeConstraints(); - const double strength = 0.9 * Strength::required; - - solver.addEditVariable( r0.width(), strength ); - solver.addEditVariable( r0.height(), strength ); - - { - solver.suggestValue( r0.width(), 0.0 ); - solver.suggestValue( r0.height(), 0.0 ); - - solver.updateVariables(); - m_data->hints[ Qt::MinimumSize ] = r0.size(); - } - - { - /* - The solver seems to run into overflows with - std::numeric_limits< unsigned float >::max() - */ - const qreal max = std::numeric_limits< unsigned int >::max(); - - solver.suggestValue( r0.width(), max ); - solver.suggestValue( r0.height(), max ); - - solver.updateVariables(); - m_data->hints[ Qt::MaximumSize ] = r0.size(); - } + m_data->hints[ Qt::MinimumSize ] = solver.resolvedSize( 0.0, 0.0 ); + m_data->hints[ Qt::MaximumSize ] = solver.resolvedSize( max, max ); } -void AnchorBox::geometryChangeEvent( QskGeometryChangeEvent* event ) +void AnchorBox::updateGeometries( const QRectF& rect ) { - Inherited::geometryChangeEvent( event ); + auto& solver = m_data->solver; - if ( event->isResized() ) - polish(); -} + if ( solver == nullptr ) + { + solver = new LayoutSolver(); + solver->setup( true, m_data->anchors, m_data->geometries ); + solver->addSizeConstraints(); + } -void AnchorBox::updateLayout() -{ - if ( maybeUnresized() ) - return; - - const auto rect = layoutRect(); - - updateVariables( rect.width(), rect.height() ); + solver->resolve( rect.width(), rect.height() ); const auto& geometries = m_data->geometries; for ( auto it = geometries.begin(); it != geometries.end(); ++it ) { - auto item = it.key(); - if ( item != this ) - { - auto r = it.value().rect(); - r.translate( rect.left(), rect.top() ); + auto r = it->second.rect(); + r.translate( rect.left(), rect.top() ); - qskSetItemGeometry( item, r ); - } - } - -} - -void AnchorBox::updateVariables( qreal width, qreal height ) -{ - const auto& r0 = m_data->geometries[ this ]; - - Solver& solver = m_data->solver; - - if ( !solver.hasConstraints() ) - { - setupAnchorConstraints( true, solver ); - setupSizeConstraints( true, solver ); - - const double strength = 0.9 * Strength::required; - - solver.addEditVariable( r0.width(), strength ); - solver.addEditVariable( r0.height(), strength ); - } - - solver.suggestValue( r0.width(), width ); - solver.suggestValue( r0.height(), height ); - - solver.updateVariables(); -} - -void AnchorBox::setupAnchorConstraints( bool layoutChildren, Solver& solver ) -{ - auto& geometries = m_data->geometries; - - for ( const auto& anchor : m_data->anchors ) - { - auto& r1 = geometries[ anchor.item1 ]; - auto& r2 = geometries[ anchor.item2 ]; - - const auto expr1 = r1.expressionAt( anchor.edge1 ); - - Expression expr2; - - if ( anchor.item2 == this ) - { - switch( anchor.edge2 ) - { - case Qt::AnchorLeft: - case Qt::AnchorTop: - expr2 = 0; - break; - - case Qt::AnchorHorizontalCenter: - expr2 = Term( 0.5 * r2.width() ); - break; - - case Qt::AnchorRight: - expr2 = Term( r2.width() ); - break; - - case Qt::AnchorVerticalCenter: - expr2 = Term( 0.5 * r2.height() ); - break; - - case Qt::AnchorBottom: - expr2 = Term( r2.height() ); - break; - } - } - else - { - expr2 = r2.expressionAt( anchor.edge2 ); - } - - solver.addConstraint( expr1 == expr2 ); - -#if 1 - if ( layoutChildren && anchor.item2 != this ) - { - const auto o = qskOrientation( anchor.edge1 ); - - /* - A constraint with medium strength to make anchored item - being stretched according to their stretch factors s1, s2. - ( For the moment we don't support having specific factors. ) - */ - const auto s1 = 1.0; - const auto s2 = 1.0; - - Constraint c( r1.length( o ) * s1 == r2.length( o ) * s2, Strength::medium ); - solver.addConstraint( c ); - } -#endif - } -} - -void AnchorBox::setupSizeConstraints( bool layoutChildren, Solver& solver ) -{ - auto& geometries = m_data->geometries; - - for ( auto it = geometries.begin(); it != geometries.end(); ++it ) - { - const auto item = it.key(); - if ( item == this ) - continue; - - auto& r = it.value(); - - { - // minimum size - const auto minSize = qskSizeConstraint( item, Qt::MinimumSize ); - - if ( minSize.width() >= 0.0 ) - solver.addConstraint( r.width() >= minSize.width() ); - - if ( minSize.height() >= 0.0 ) - solver.addConstraint( r.height() >= minSize.height() ); - } - - if ( layoutChildren ) - { - // preferred size - const auto prefSize = qskSizeConstraint( item, Qt::PreferredSize ); - - Constraint c1( r.width() == prefSize.width(), Strength::strong ); - solver.addConstraint( c1 ); - - Constraint c2( r.height() == prefSize.height(), Strength::strong ); - solver.addConstraint( c2 ); - } - - { - // maximum size - const auto maxSize = qskSizeConstraint( item, Qt::MaximumSize ); - if ( maxSize.width() >= 0.0 ) - solver.addConstraint( r.width() <= maxSize.width() ); - - if ( maxSize.height() >= 0.0 ) - solver.addConstraint( r.height() <= maxSize.height() ); - } + qskSetItemGeometry( it->first, r ); } } diff --git a/playground/anchors/AnchorBox.h b/playground/anchors/AnchorBox.h index 57fe526d..dbc603bf 100644 --- a/playground/anchors/AnchorBox.h +++ b/playground/anchors/AnchorBox.h @@ -8,8 +8,6 @@ #include "QskControl.h" -class Solver; - class AnchorBox : public QskControl { Q_OBJECT @@ -39,9 +37,7 @@ class AnchorBox : public QskControl private: void updateHints(); - void updateVariables( qreal width, qreal height ); - void setupAnchorConstraints( bool layoutChildren, Solver& ); - void setupSizeConstraints( bool layoutChildren, Solver& ); + void updateGeometries( const QRectF& ); class PrivateData; std::unique_ptr< PrivateData > m_data;