From 35b98e4b69e70643be8b2cdde60dc9621520c96d Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Fri, 30 Aug 2019 13:13:32 +0200 Subject: [PATCH] playing with constraint based layouting --- playground/anchors/AnchorBox.cpp | 256 +++++++ playground/anchors/AnchorBox.h | 38 + playground/anchors/Constraint.cpp | 285 ++++++++ playground/anchors/Constraint.h | 117 ++++ playground/anchors/Expression.cpp | 226 ++++++ playground/anchors/Expression.h | 68 ++ playground/anchors/Solver.cpp | 1090 +++++++++++++++++++++++++++++ playground/anchors/Solver.h | 33 + playground/anchors/Strength.h | 26 + playground/anchors/Term.h | 81 +++ playground/anchors/Variable.h | 46 ++ playground/anchors/anchors.pro | 21 + playground/anchors/main.cpp | 190 +++++ playground/playground.pro | 1 + 14 files changed, 2478 insertions(+) create mode 100644 playground/anchors/AnchorBox.cpp create mode 100644 playground/anchors/AnchorBox.h create mode 100644 playground/anchors/Constraint.cpp create mode 100644 playground/anchors/Constraint.h create mode 100644 playground/anchors/Expression.cpp create mode 100644 playground/anchors/Expression.h create mode 100644 playground/anchors/Solver.cpp create mode 100644 playground/anchors/Solver.h create mode 100644 playground/anchors/Strength.h create mode 100644 playground/anchors/Term.h create mode 100644 playground/anchors/Variable.h create mode 100644 playground/anchors/anchors.pro create mode 100644 playground/anchors/main.cpp diff --git a/playground/anchors/AnchorBox.cpp b/playground/anchors/AnchorBox.cpp new file mode 100644 index 00000000..74d680f4 --- /dev/null +++ b/playground/anchors/AnchorBox.cpp @@ -0,0 +1,256 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "AnchorBox.h" + +#include "Solver.h" +#include "Constraint.h" +#include "Variable.h" +#include "Expression.h" + +#include +#include +#include + +#include + +static inline Qt::Orientation qskOrientation( int edge ) +{ + return ( edge <= Qt::AnchorRight ) ? Qt::Horizontal : Qt::Vertical; +} + +namespace +{ + class Geometry + { + public: + Expression expressionAt( int anchorPoint ) + { + switch( anchorPoint ) + { + case Qt::AnchorLeft: + return Term( left ); + + case Qt::AnchorHorizontalCenter: + return centerH(); + + case Qt::AnchorRight: + return Term( right ); + + case Qt::AnchorTop: + return Term( top ); + + case Qt::AnchorVerticalCenter: + return centerV(); + + case Qt::AnchorBottom: + return Term( bottom ); + } + + return Expression(); + } + + Expression length( Qt::Orientation orientation ) + { + return ( orientation == Qt::Horizontal ) ? width() : height(); + } + + QRectF rect() const + { + return QRectF( left.value(), top.value(), + right.value() - left.value(), bottom.value() - top.value() ); + } + + Expression width() const { return right - left; } + Expression height() const { return bottom - top; } + + Expression centerH() const { return left + 0.5 * width(); } + Expression centerV() const { return top + 0.5 * height(); } + + Variable left, right, top, bottom; + }; + + class Anchor + { + public: + QQuickItem* item1 = nullptr; + Qt::AnchorPoint edge1; + + QQuickItem* item2 = nullptr; + Qt::AnchorPoint edge2; + }; +} + +class AnchorBox::PrivateData +{ + public: + void setupSolver( int type, Solver& ); + + QMap< QQuickItem*, Geometry > geometries; + QVector< Anchor > anchors; +}; + +void AnchorBox::PrivateData::setupSolver( int type, Solver& solver ) +{ + for ( const auto& anchor : anchors ) + { + auto& r1 = geometries[ anchor.item1 ]; + auto& r2 = geometries[ anchor.item2 ]; + + solver.addConstraint( r1.expressionAt( anchor.edge1 ) + == r2.expressionAt( anchor.edge2 ) ); + +#if 1 + if ( type < 0 ) + { + const auto s1 = 1.0; + const auto s2 = 1.0; + const auto o = qskOrientation( anchor.edge1 ); + + Constraint c( r1.length( o ) * s1 == r2.length( o ) * s2, Strength::medium ); + solver.addConstraint( c ); + } +#endif + } + + for ( auto it = geometries.begin(); it != geometries.end(); ++it ) + { + const auto item = it.key(); + auto& r = it.value(); + + if ( type < 0 || type == Qt::MinimumSize ) + { + const auto minSize = QskLayoutConstraint::sizeHint( item, Qt::MinimumSize ); + solver.addConstraint( r.right >= r.left + minSize.width() ); + solver.addConstraint( r.bottom >= r.top + minSize.height() ); + } + + if ( type < 0 || type == Qt::PreferredSize ) + { + const auto prefSize = QskLayoutConstraint::sizeHint( item, Qt::PreferredSize ); + + Constraint c1( r.right == r.left + prefSize.width(), Strength::strong ); + solver.addConstraint( c1 ); + + Constraint c2( r.bottom == r.top + prefSize.height(), Strength::strong ); + solver.addConstraint( c2 ); + } + + if ( type < 0 || type == Qt::MaximumSize ) + { + const auto maxSize = QskLayoutConstraint::sizeHint( item, Qt::MaximumSize ); + if ( maxSize.width() < QskLayoutConstraint::unlimited ) + solver.addConstraint( r.right <= r.left + maxSize.width() ); + + if ( maxSize.height() < QskLayoutConstraint::unlimited ) + solver.addConstraint( r.bottom <= r.top + maxSize.height() ); + } + } +} + +AnchorBox::AnchorBox( QQuickItem* parent ) + : QskControl( parent ) + , m_data( new PrivateData ) +{ + (void)m_data->geometries[ this ]; +} + +AnchorBox::~AnchorBox() +{ +} + +void AnchorBox::addAnchor( QQuickItem* item, + Qt::AnchorPoint edge1, Qt::AnchorPoint edge2 ) +{ + addAnchor( item, edge1, this, edge2 ); +} + +void AnchorBox::addAnchor( QQuickItem* item1, Qt::AnchorPoint edge1, + QQuickItem* item2, Qt::AnchorPoint edge2 ) +{ + if ( item1 == item2 || item1 == nullptr || item2 == nullptr ) + return; + + if ( item1 != this ) + { + if ( item1->parent() == nullptr ) + item1->setParent( this ); + + if ( item1->parentItem() != this ) + item1->setParentItem( this ); + } + + if ( item2 != this ) + { + if ( item2->parent() == nullptr ) + item2->setParent( this ); + + if ( item2->parentItem() != this ) + item2->setParentItem( this ); + } + + (void)m_data->geometries[ item1 ]; + (void)m_data->geometries[ item2 ]; + + Anchor anchor; + anchor.item1 = item1; + anchor.edge1 = edge1; + anchor.item2 = item2; + anchor.edge2 = edge2; + + m_data->anchors += anchor; +} + +QSizeF AnchorBox::boxHint( Qt::SizeHint which ) +{ + Solver solver; + m_data->setupSolver( which, solver ); + solver.updateVariables(); + + auto& r0 = m_data->geometries[ this ]; + + return QSizeF( r0.rect().size() ); +} + +void AnchorBox::setItemGeometries( const QRectF& rect ) +{ + Solver solver; + m_data->setupSolver( -1, solver ); + + auto& r0 = m_data->geometries[ this ]; + + solver.addConstraint( r0.left == rect.left() ); + solver.addConstraint( r0.right == rect.right() ); + solver.addConstraint( r0.top == rect.top() ); + solver.addConstraint( r0.bottom == rect.bottom() ); + + solver.updateVariables(); + + const auto& geometries = m_data->geometries; + for ( auto it = geometries.begin(); it != geometries.end(); ++it ) + qskSetItemGeometry( it.key(), it.value().rect() ); + +#if 0 + qDebug() << "=== Rect:" << rect; + for ( auto it = geometries.begin(); it != geometries.end(); ++it ) + qDebug() << it.key()->objectName() << it.value().rect(); +#endif +} + +void AnchorBox::geometryChangeEvent( QskGeometryChangeEvent* event ) +{ + Inherited::geometryChangeEvent( event ); + + if ( event->isResized() ) + polish(); +} + +void AnchorBox::updateLayout() +{ + if ( !maybeUnresized() ) + setItemGeometries( layoutRect() ); +} + +#include "moc_AnchorBox.cpp" diff --git a/playground/anchors/AnchorBox.h b/playground/anchors/AnchorBox.h new file mode 100644 index 00000000..cf6baa47 --- /dev/null +++ b/playground/anchors/AnchorBox.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef ANCHOR_BOX_H +#define ANCHOR_BOX_H + +#include "QskControl.h" + +class AnchorBox : public QskControl +{ + Q_OBJECT + + using Inherited = QskControl; + + public: + AnchorBox( QQuickItem* parent = nullptr ); + ~AnchorBox() override; + + void addAnchor( QQuickItem*, Qt::AnchorPoint, Qt::AnchorPoint ); + void addAnchor( QQuickItem*, Qt::AnchorPoint, QQuickItem*, Qt::AnchorPoint ); + + QSizeF boxHint( Qt::SizeHint ); + + protected: + void geometryChangeEvent( QskGeometryChangeEvent* ) override; + void updateLayout() override; + + private: + void setItemGeometries( const QRectF& ); + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif + diff --git a/playground/anchors/Constraint.cpp b/playground/anchors/Constraint.cpp new file mode 100644 index 00000000..fb70cdc4 --- /dev/null +++ b/playground/anchors/Constraint.cpp @@ -0,0 +1,285 @@ +#include "Constraint.h" +#include "Expression.h" + +#include + +static Expression reduce( const Expression& expr ) +{ + std::map< Variable, double > vars; + + for ( auto term : expr.terms() ) + vars[ term.variable() ] += term.coefficient(); + + const std::vector< Term > terms( vars.begin(), vars.end() ); + return Expression( terms, expr.constant() ); +} + +class Constraint::Data +{ + public: + Data( const Expression& expr, RelationalOperator op, double strength ) + : expression( reduce( expr ) ) + , strength( Strength::clip( strength ) ) + , op( op ) + { + } + + Expression expression; + double strength; + RelationalOperator op; +}; + + +Constraint::Constraint( const Expression& expr, RelationalOperator op, double strength ) + : m_data( std::make_shared< Data >( expr, op, strength ) ) +{ +} + +Constraint::Constraint( const Constraint& other, double strength ) + : Constraint( other.expression(), other.oper(), strength ) +{ +} + +Constraint::~Constraint() +{ +} + +const Expression& Constraint::expression() const +{ + return m_data->expression; +} + +RelationalOperator Constraint::oper() const +{ + return m_data->op; +} + +double Constraint::strength() const +{ + return m_data->strength; +} + +Constraint operator==( const Expression& first, const Expression& second ) +{ + return Constraint( first - second, OP_EQ ); +} + +Constraint operator==( const Expression& expression, const Term& term ) +{ + return expression == Expression( term ); +} + +Constraint operator==( const Expression& expression, const Variable& variable ) +{ + return expression == Term( variable ); +} + +Constraint operator==( const Expression& expression, double constant ) +{ + return expression == Expression( constant ); +} + +Constraint operator<=( const Expression& first, const Expression& second ) +{ + return Constraint( first - second, OP_LE ); +} + +Constraint operator<=( const Expression& expression, const Term& term ) +{ + return expression <= Expression( term ); +} + +Constraint operator<=( const Expression& expression, const Variable& variable ) +{ + return expression <= Term( variable ); +} + +Constraint operator<=( const Expression& expression, double constant ) +{ + return expression <= Expression( constant ); +} + +Constraint operator>=( const Expression& first, const Expression& second ) +{ + return Constraint( first - second, OP_GE ); +} + +Constraint operator>=( const Expression& expression, const Term& term ) +{ + return expression >= Expression( term ); +} + +Constraint operator>=( const Expression& expression, const Variable& variable ) +{ + return expression >= Term( variable ); +} + +Constraint operator>=( const Expression& expression, double constant ) +{ + return expression >= Expression( constant ); +} + +Constraint operator==( const Term& term, const Expression& expression ) +{ + return expression == term; +} + +Constraint operator==( const Term& first, const Term& second ) +{ + return Expression( first ) == second; +} + +Constraint operator==( const Term& term, const Variable& variable ) +{ + return Expression( term ) == variable; +} + +Constraint operator==( const Term& term, double constant ) +{ + return Expression( term ) == constant; +} + +Constraint operator<=( const Term& term, const Expression& expression ) +{ + return expression >= term; +} + +Constraint operator<=( const Term& first, const Term& second ) +{ + return Expression( first ) <= second; +} + +Constraint operator<=( const Term& term, const Variable& variable ) +{ + return Expression( term ) <= variable; +} + +Constraint operator<=( const Term& term, double constant ) +{ + return Expression( term ) <= constant; +} + +Constraint operator>=( const Term& term, const Expression& expression ) +{ + return expression <= term; +} + +Constraint operator>=( const Term& first, const Term& second ) +{ + return Expression( first ) >= second; +} + +Constraint operator>=( const Term& term, const Variable& variable ) +{ + return Expression( term ) >= variable; +} + +Constraint operator>=( const Term& term, double constant ) +{ + return Expression( term ) >= constant; +} + +Constraint operator==( const Variable& variable, const Expression& expression ) +{ + return expression == variable; +} + +Constraint operator==( const Variable& variable, const Term& term ) +{ + return term == variable; +} + +Constraint operator==( const Variable& first, const Variable& second ) +{ + return Term( first ) == second; +} + +Constraint operator==( const Variable& variable, double constant ) +{ + return Term( variable ) == constant; +} + +Constraint operator<=( const Variable& variable, const Expression& expression ) +{ + return expression >= variable; +} + +Constraint operator<=( const Variable& variable, const Term& term ) +{ + return term >= variable; +} + +Constraint operator<=( const Variable& first, const Variable& second ) +{ + return Term( first ) <= second; +} + +Constraint operator<=( const Variable& variable, double constant ) +{ + return Term( variable ) <= constant; +} + +Constraint operator>=( const Variable& variable, const Expression& expression ) +{ + return expression <= variable; +} + +Constraint operator>=( const Variable& variable, const Term& term ) +{ + return term <= variable; +} + +Constraint operator>=( const Variable& first, const Variable& second ) +{ + return Term( first ) >= second; +} + +Constraint operator>=( const Variable& variable, double constant ) +{ + return Term( variable ) >= constant; +} + +Constraint operator==( double constant, const Expression& expression ) +{ + return expression == constant; +} + +Constraint operator==( double constant, const Term& term ) +{ + return term == constant; +} + +Constraint operator==( double constant, const Variable& variable ) +{ + return variable == constant; +} + +Constraint operator<=( double constant, const Expression& expression ) +{ + return expression >= constant; +} + +Constraint operator<=( double constant, const Term& term ) +{ + return term >= constant; +} + +Constraint operator<=( double constant, const Variable& variable ) +{ + return variable >= constant; +} + +Constraint operator>=( double constant, const Expression& expression ) +{ + return expression <= constant; +} + +Constraint operator>=( double constant, const Term& term ) +{ + return term <= constant; +} + +Constraint operator>=( double constant, const Variable& variable ) +{ + return variable <= constant; +} diff --git a/playground/anchors/Constraint.h b/playground/anchors/Constraint.h new file mode 100644 index 00000000..acff90f0 --- /dev/null +++ b/playground/anchors/Constraint.h @@ -0,0 +1,117 @@ +#pragma once + +#include "Strength.h" +#include + +class Expression; +class Variable; +class Term; + +enum RelationalOperator { OP_LE, OP_GE, OP_EQ }; + +class Constraint +{ + public: + + Constraint() = default; + + Constraint( const Expression&, RelationalOperator, + double strength = Strength::required ); + + Constraint( const Constraint&, double strength ); + + ~Constraint(); + + const Expression& expression() const; + RelationalOperator oper() const; + double strength() const; + + bool operator!() const { return !m_data; } + + private: + class Data; + std::shared_ptr< Data > m_data; + + friend bool operator<( const Constraint&, const Constraint& ); + friend bool operator==( const Constraint&, const Constraint& ); + friend bool operator!=( const Constraint&, const Constraint& ); +}; + +inline bool operator<( const Constraint& lhs, const Constraint& rhs ) +{ + return lhs.m_data < rhs.m_data; +} + +inline bool operator==( const Constraint& lhs, const Constraint& rhs ) +{ + return lhs.m_data == rhs.m_data; +} + +inline bool operator!=( const Constraint& lhs, const Constraint& rhs ) +{ + return lhs.m_data != rhs.m_data; +} + +inline Constraint operator|( const Constraint& constraint, double strength ) +{ + return Constraint( constraint, strength ); +} + +inline Constraint operator|( double strength, const Constraint& constraint ) +{ + return constraint | strength; +} + +extern Constraint operator==( const Expression&, const Expression& ); +extern Constraint operator<=( const Expression&, const Expression& ); +extern Constraint operator>=( const Expression&, const Expression& ); + +extern Constraint operator==( const Expression&, const Term& ); +extern Constraint operator<=( const Expression&, const Term& ); +extern Constraint operator>=( const Expression&, const Term& ); +extern Constraint operator==( const Term&, const Expression& ); +extern Constraint operator<=( const Term&, const Expression& ); +extern Constraint operator>=( const Term&, const Expression& ); + +extern Constraint operator==( const Expression&, const Variable& ); +extern Constraint operator<=( const Expression&, const Variable& ); +extern Constraint operator>=( const Expression&, const Variable& ); +extern Constraint operator==( const Variable&, const Expression& ); +extern Constraint operator<=( const Variable&, const Expression& ); +extern Constraint operator>=( const Variable&, const Expression& ); + +extern Constraint operator==( const Expression&, double ); +extern Constraint operator<=( const Expression&, double ); +extern Constraint operator>=( const Expression&, double ); +extern Constraint operator==( double, const Expression& ); +extern Constraint operator<=( double, const Expression& ); +extern Constraint operator>=( double, const Expression& ); + +extern Constraint operator==( const Term&, const Term& ); +extern Constraint operator<=( const Term&, const Term& ); +extern Constraint operator>=( const Term&, const Term& ); + +extern Constraint operator==( const Term&, const Variable& ); +extern Constraint operator<=( const Term&, const Variable& ); +extern Constraint operator>=( const Term&, const Variable& ); +extern Constraint operator==( const Variable&, const Term& ); +extern Constraint operator<=( const Variable&, const Term& ); +extern Constraint operator>=( const Variable&, const Term& ); + +extern Constraint operator==( const Term&, double ); +extern Constraint operator<=( const Term&, double ); +extern Constraint operator>=( const Term&, double ); +extern Constraint operator==( double, const Term& ); +extern Constraint operator<=( double, const Term& ); +extern Constraint operator>=( double, const Term& ); + +extern Constraint operator==( const Variable&, const Variable& ); +extern Constraint operator<=( const Variable&, const Variable& ); +extern Constraint operator>=( const Variable&, const Variable& ); + +extern Constraint operator==( const Variable&, double ); +extern Constraint operator<=( const Variable&, double ); +extern Constraint operator>=( const Variable&, double ); +extern Constraint operator==( double, const Variable& ); +extern Constraint operator<=( double, const Variable& ); +extern Constraint operator>=( double, const Variable& ); diff --git a/playground/anchors/Expression.cpp b/playground/anchors/Expression.cpp new file mode 100644 index 00000000..a107dc95 --- /dev/null +++ b/playground/anchors/Expression.cpp @@ -0,0 +1,226 @@ +#include "Expression.h" +#include "Term.h" + +Expression::Expression( double constant ) + : m_constant( constant ) +{ +} + +Expression::Expression( const Term& term, double constant ) + : m_terms( 1, term ) + , m_constant( constant ) +{ +} + +Expression::Expression( const std::vector< Term >&& terms, double constant ) + : m_terms( std::move( terms ) ) + , m_constant( constant ) +{ +} + +Expression::Expression( const std::vector< Term >& terms, double constant ) + : m_terms( terms ) + , m_constant( constant ) +{ +} + +double Expression::value() const +{ + double result = m_constant; + + for ( const auto& term : m_terms ) + result += term.value(); + + return result; +} + +Expression operator*( const Expression& expression, double coefficient ) +{ + std::vector< Term > terms; + terms.reserve( expression.terms().size() ); + + for ( const auto& term : expression.terms() ) + terms.push_back( term * coefficient ); + + return Expression( std::move( terms ), expression.constant() * coefficient ); +} + +Expression operator/( const Expression& expression, double denominator ) +{ + return expression * ( 1.0 / denominator ); +} + +Expression operator-( const Expression& expression ) +{ + return expression * -1.0; +} + +Expression operator*( double coefficient, const Expression& expression ) +{ + return expression * coefficient; +} + +Expression operator+( const Expression& first, const Expression& second ) +{ + std::vector< Term > terms; + terms.reserve( first.terms().size() + second.terms().size() ); + terms.insert( terms.begin(), first.terms().begin(), first.terms().end() ); + terms.insert( terms.end(), second.terms().begin(), second.terms().end() ); + + return Expression( std::move( terms ), first.constant() + second.constant() ); +} + +Expression operator+( const Expression& expression, const Term& term ) +{ + std::vector< Term > terms; + terms.reserve( expression.terms().size() + 1 ); + terms.insert( terms.begin(), expression.terms().begin(), expression.terms().end() ); + terms.push_back( term ); + + return Expression( std::move( terms ), expression.constant() ); +} + +Expression operator+( const Expression& expression, const Variable& variable ) +{ + return expression + Term( variable ); +} + +Expression operator+( const Expression& expression, double constant ) +{ + return Expression( expression.terms(), expression.constant() + constant ); +} + +Expression operator-( const Expression& first, const Expression& second ) +{ + return first + -second; +} + +Expression operator-( const Expression& expression, const Term& term ) +{ + return expression + -term; +} + +Expression operator-( const Expression& expression, const Variable& variable ) +{ + return expression + -variable; +} + +Expression operator-( const Expression& expression, double constant ) +{ + return expression + -constant; +} + +Expression operator+( const Term& term, const Expression& expression ) +{ + return expression + term; +} + +Expression operator+( const Term& first, const Term& second ) +{ + std::vector< Term > terms; + terms.reserve( 2 ); + terms.push_back( first ); + terms.push_back( second ); + + return Expression( std::move( terms ) ); +} + +Expression operator+( const Term& term, const Variable& variable ) +{ + return term + Term( variable ); +} + +Expression operator+( const Term& term, double constant ) +{ + return Expression( term, constant ); +} + +Expression operator-( const Term& term, const Expression& expression ) +{ + return -expression + term; +} + +Expression operator-( const Term& first, const Term& second ) +{ + return first + -second; +} + +Expression operator-( const Term& term, const Variable& variable ) +{ + return term + -variable; +} + +Expression operator-( const Term& term, double constant ) +{ + return term + -constant; +} + +Expression operator+( const Variable& variable, const Expression& expression ) +{ + return expression + variable; +} + +Expression operator+( const Variable& variable, const Term& term ) +{ + return term + variable; +} + +Expression operator+( const Variable& first, const Variable& second ) +{ + return Term( first ) + second; +} + +Expression operator+( const Variable& variable, double constant ) +{ + return Term( variable ) + constant; +} + +Expression operator-( const Variable& variable, const Expression& expression ) +{ + return variable + -expression; +} + +Expression operator-( const Variable& variable, const Term& term ) +{ + return variable + -term; +} + +Expression operator-( const Variable& first, const Variable& second ) +{ + return first + -second; +} + +Expression operator-( const Variable& variable, double constant ) +{ + return variable + -constant; +} + +Expression operator+( double constant, const Expression& expression ) +{ + return expression + constant; +} + +Expression operator+( double constant, const Term& term ) +{ + return term + constant; +} + +Expression operator+( double constant, const Variable& variable ) +{ + return variable + constant; +} + +Expression operator-( double constant, const Expression& expression ) +{ + return -expression + constant; +} + +Expression operator-( double constant, const Term& term ) +{ + return -term + constant; +} + +Expression operator-( double constant, const Variable& variable ) +{ + return -variable + constant; +} diff --git a/playground/anchors/Expression.h b/playground/anchors/Expression.h new file mode 100644 index 00000000..62534da9 --- /dev/null +++ b/playground/anchors/Expression.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include "Term.h" + +class Expression +{ + public: + Expression( double constant = 0.0 ); + Expression( const Term&, double constant = 0.0 ); + + Expression( const std::vector< Term >&, double constant = 0.0 ); + Expression( const std::vector< Term >&&, double constant = 0.0 ); + + const std::vector< Term >& terms() const { return m_terms; } + double constant() const { return m_constant; } + + double value() const; + + private: + std::vector< Term > m_terms; + double m_constant; +}; + +extern Expression operator-( const Expression& ); + +extern Expression operator+( const Expression&, const Expression& ); +extern Expression operator-( const Expression&, const Expression& ); + +extern Expression operator+( const Expression&, const Term& ); +extern Expression operator-( const Expression&, const Term& ); +extern Expression operator+( const Term&, const Expression&); +extern Expression operator-( const Term&, const Expression&); + +extern Expression operator+( const Expression&, const Variable& ); +extern Expression operator-( const Expression&, const Variable& ); +extern Expression operator-( const Variable&, const Expression&); +extern Expression operator+( const Variable&, const Expression&); + +extern Expression operator*( const Expression&, double ); +extern Expression operator/( const Expression&, double ); +extern Expression operator+( const Expression&, double ); +extern Expression operator-( const Expression&, double ); +extern Expression operator*( double, const Expression&); +extern Expression operator+( double, const Expression&); +extern Expression operator-( double, const Expression&); + +extern Expression operator+( const Term&, const Term& ); +extern Expression operator-( const Term&, const Term& ); + +extern Expression operator+( const Term&, const Variable& ); +extern Expression operator-( const Term&, const Variable& ); +extern Expression operator-( const Variable&, const Term&); +extern Expression operator+( const Variable&, const Term&); + +extern Expression operator+( const Term&, double ); +extern Expression operator-( const Term&, double ); +extern Expression operator+( double, const Term&); +extern Expression operator-( double, const Term&); + +extern Expression operator+( const Variable&, const Variable& ); +extern Expression operator-( const Variable&, const Variable& ); + +extern Expression operator+( const Variable&, double ); +extern Expression operator-( const Variable&, double ); +extern Expression operator+( double, const Variable& ); +extern Expression operator-( double, const Variable& ); + diff --git a/playground/anchors/Solver.cpp b/playground/anchors/Solver.cpp new file mode 100644 index 00000000..4023b29e --- /dev/null +++ b/playground/anchors/Solver.cpp @@ -0,0 +1,1090 @@ +/****************************************************************************** + * This code is a stripped down version of the Cassowary constraint solving + * algorithm. The implementation has been taken from the Kiwi project: + * + * Copyright (c) 2013-2017, Nucleic Development Team. + * Distributed under the terms of the Modified BSD License. + * https://github.com/nucleic/kiwi/blob/master/LICENSE + *****************************************************************************/ + +#include "Solver.h" +#include "Constraint.h" +#include "Variable.h" +#include "Expression.h" +#include "Constraint.h" + +#include +#include +#include +#include + +template< typename T > +class FlatMap +{ + public: + using iterator = typename std::vector< T >::iterator; + using const_iterator = typename std::vector< T >::const_iterator; + using Key = typename std::remove_reference< decltype( std::declval< T >().key() ) >::type; + + iterator begin() { return m_entries.begin(); } + const_iterator begin() const { return m_entries.begin(); } + + iterator end() { return m_entries.end(); } + const_iterator end() const { return m_entries.end(); } + + iterator find( const Key& key ) + { + auto it = lowerBound( key ); + if ( ( it == m_entries.end() ) || key < it->key() ) + return m_entries.end(); + + return it; + } + + const_iterator find( const Key& key ) const + { + auto it = lowerBound( key ); + if ( ( it == m_entries.end() ) || key < it->key() ) + return m_entries.end(); + + return it; + } + + T& operator[]( const Key& key ) + { + auto it = lowerBound( key ); + if ( ( it == m_entries.end() ) || key < it->key() ) + it = m_entries.insert( it, T( key ) ); + + return *it; + } + + void erase( iterator it ) + { + m_entries.erase( it ); + } + + void erase( const Key& key ) + { + auto it = find( key ); + if ( it != m_entries.end() ) + m_entries.erase( it ); + } + + void insert( iterator it, const T& entry ) + { + m_entries.insert( it, entry ); + } + + void clear() + { + m_entries.clear(); + } + + bool empty() const + { + return m_entries.empty(); + } + + inline typename std::vector< T >::iterator lowerBound( const Key& key ) const + { + auto cmp = []( const T& entry, const Key& key ) + { return entry.key() < key; }; + + auto& entries = const_cast< std::vector< T >& >( m_entries ); + return std::lower_bound( entries.begin(), entries.end(), key, cmp ); + } + + private: + std::vector< T > m_entries; +}; + +namespace +{ + // qFuzzyIsNull ?? + inline bool nearZero( double value ) + { + const double eps = 1.0e-8; + return value < 0.0 ? -value < eps : value < eps; + } + + class Symbol + { + public: + enum Type + { + Invalid, + External, + Slack, + Error, + Dummy + }; + + Symbol() + : m_id( 0 ) + , m_type( Invalid ) + { + } + + uint32_t id() const { return m_id; } + Type type() const { return m_type; } + + static Symbol external() { return Symbol( Type::External ); } + static Symbol slack() { return Symbol( Type::Slack ); } + static Symbol error() { return Symbol( Type::Error ); } + static Symbol dummy() { return Symbol( Type::Dummy ); } + + private: + Symbol( Type t ) + : m_id( nextId() ) + , m_type( t ) + { + } + + static inline uint32_t nextId() + { + static uint32_t id = 0; + return ++id; + } + + friend bool operator<( const Symbol& lhs, const Symbol& rhs ) + { + return lhs.m_id < rhs.m_id; + } + + friend bool operator==( const Symbol& lhs, const Symbol& rhs ) + { + return lhs.m_id == rhs.m_id; + } + + friend bool operator!=( const Symbol& lhs, const Symbol& rhs ) + { + return lhs.m_id != rhs.m_id; + } + + uint32_t m_id; + Type m_type; + }; +} + +namespace +{ + class Cell + { + public: + Cell( const Symbol& symbol, double coefficient = 0.0 ) + : symbol( symbol ) + , coefficient( coefficient ) + { + } + + inline const Symbol& key() const { return symbol; } + + Symbol symbol; + double coefficient; + }; + + class Row + { + public: + + Row( double constant = 0.0 ) + : m_constant( constant ) + { + } + + const FlatMap< Cell >& cells() const + { + return m_cells; + } + + double constant() const + { + return m_constant; + } + + double add( double value ) + { + return m_constant += value; + } + + void insert( const Symbol& symbol, double coefficient = 1.0 ) + { + auto it = m_cells.lowerBound( symbol ); + if ( ( it == m_cells.end() ) || symbol < it->symbol ) + { + if ( !nearZero( coefficient ) ) + m_cells.insert( it, Cell( symbol, coefficient ) ); + } + else + { + if( nearZero( it->coefficient += coefficient ) ) + m_cells.erase( it ); + } + } + + void insert( const Row& other, double coefficient ) + { + m_constant += other.m_constant * coefficient; + + for ( auto& cell : other.m_cells ) + { + const double coeff = cell.coefficient * coefficient; + insert( cell.symbol, coeff ); + } + } + + void remove( const Symbol& symbol ) + { + auto it = m_cells.find( symbol ); + if( it != m_cells.end() ) + m_cells.erase( it ); + } + + void reverseSign() + { + m_constant = -m_constant; + + for ( auto& cell : m_cells ) + cell.coefficient = -cell.coefficient; + } + + void solveFor( const Symbol& symbol ) + { + /* + This method assumes the row is of the form a * x + b * y + c = 0 + and (assuming solve for x) will modify the row to represent the + right hand side of x = -b/a * y - c / a. The target symbol will + be removed from the row, and the constant and other cells will + be multiplied by the negative inverse of the target coefficient. + + The given symbol *must* exist in the row. + */ + auto it = m_cells.find( symbol ); + + const double coeff = -1.0 / it->coefficient; + m_cells.erase( it ); + + m_constant *= coeff; + + for ( auto& cell : m_cells ) + cell.coefficient *= coeff; + } + + void solveFor( const Symbol& lhs, const Symbol& rhs ) + { + /* + This method assumes the row is of the form x = b * y + c and will + solve the row such that y = x / b - c / b. The rhs symbol will be + removed from the row, the lhs added, and the result divided by the + negative inverse of the rhs coefficient. + + The lhs symbol *must not* exist in the row, and the rhs symbol + must exist in the row. + */ + insert( lhs, -1.0 ); + solveFor( rhs ); + } + + double coefficientFor( const Symbol& symbol ) const + { + const auto it = m_cells.find( symbol ); + return ( it == m_cells.end() ) ? 0.0 : it->coefficient; + } + + void substitute( const Symbol& symbol, const Row& row ) + { + /* + Given a row of the form a * x + b and a substitution of the + form x = 3 * y + c the row will be updated to reflect the + expression 3 * a * y + a * c + b. + */ + const auto it = m_cells.find( symbol ); + if( it != m_cells.end() ) + { + const double coefficient = it->coefficient; + m_cells.erase( it ); + insert( row, coefficient ); + } + } + + bool isDummyRow() const + { + for ( const auto& cell : m_cells ) + { + if( cell.symbol.type() != Symbol::Dummy ) + return false; + } + return true; + } + private: + FlatMap< Cell > m_cells; + double m_constant; + }; +} + +namespace +{ + struct Tag + { + Symbol marker; + Symbol other; + }; + + struct EditInfo + { + EditInfo( const Variable& variable ) + : variable( variable ) + , constant( 0.0 ) + { + } + + inline const Variable& key() const { return variable; } + + Variable variable; + Tag tag; + Constraint constraint; + double constant; + }; + + struct ConstraintInfo + { + ConstraintInfo( const Constraint& constraint ) + : constraint( constraint ) + { + } + + inline const Constraint& key() const { return constraint; } + + Constraint constraint; + Tag tag; + }; + + struct VariableInfo + { + VariableInfo( const Variable& variable ) + : variable( variable ) + { + } + + inline const Variable& key() const { return variable; } + + Variable variable; + Symbol symbol; + }; + + struct RowInfo + { + RowInfo( const Symbol& symbol ) + : symbol( symbol ) + , row( nullptr ) + { + } + + inline const Symbol& key() const { return symbol; } + + Symbol symbol; + Row* row; + }; +} + +class SimplexSolver +{ + public: + SimplexSolver(); + ~SimplexSolver(); + + void addConstraint( const Constraint& ); + void removeConstraint( const Constraint& ); + bool hasConstraint( const Constraint& ) const; + + void addEditVariable( const Variable&, double strength ); + void removeEditVariable( const Variable& ); + + bool hasEditVariable( const Variable& ) const; + void suggestValue( const Variable&, double ); + + void updateVariables(); + void reset(); + + private: + void clearRows(); + + Symbol getVarSymbol( const Variable& ); + Row* createRow( const Constraint& constraint, Tag& ); + Symbol chooseSubject( const Row&, const Tag& ); + bool addWithArtificialVariable( const Row& ); + + void substitute( const Symbol&, const Row& ); + bool optimize( const Row& ); + void dualOptimize(); + + Symbol getEnteringSymbol( const Row& ); + Symbol getDualEnteringSymbol( const Row& ); + Symbol anyPivotableSymbol( const Row& ); + + FlatMap< RowInfo >::iterator getLeavingRow( const Symbol& ); + FlatMap< RowInfo >::iterator getMarkerLeavingRow( const Symbol& ); + + void removeMarkerEffects( const Symbol&, double strength ); + + FlatMap< ConstraintInfo > m_constraints; + + FlatMap< RowInfo > m_rows; + FlatMap< VariableInfo > m_variables; + FlatMap< EditInfo > m_editVariables; + + std::vector< Symbol > m_infeasibleRows; + std::unique_ptr< Row > m_objective; + std::unique_ptr< Row > m_artificial; +}; + +SimplexSolver::SimplexSolver() + : m_objective( new Row() ) +{ +} + +SimplexSolver::~SimplexSolver() +{ + clearRows(); +} + +void SimplexSolver::addConstraint( const Constraint& constraint ) +{ + if( m_constraints.find( constraint ) != m_constraints.end() ) + { + qWarning( "The constraint has already been added to the solver." ); + return; + } + + // Creating a row causes symbols to be reserved for the variables + // in the constraint. If this method exits with an exception, + // then its possible those variables will linger in the var map. + // Since its likely that those variables will be used in other + // constraints and since exceptional conditions are uncommon, + // i'm not too worried about aggressive cleanup of the var map. + Tag tag; + + std::unique_ptr< Row > rowptr( createRow( constraint, tag ) ); + auto subject = chooseSubject( *rowptr, tag ); + + // If chooseSubject could not find a valid entering symbol, one + // last option is available if the entire row is composed of + // dummy variables. If the constant of the row is zero, then + // this represents redundant constraints and the new dummy + // marker can enter the basis. If the constant is non-zero, + // then it represents an unsatisfiable constraint. + if( subject.type() == Symbol::Invalid && rowptr->isDummyRow() ) + { + if( !nearZero( rowptr->constant() ) ) + { + qWarning( "The constraint can not be satisfied." ); + return; + } + + subject = tag.marker; + } + + // If an entering symbol still isn't found, then the row must + // be added using an artificial variable. If that fails, then + // the row represents an unsatisfiable constraint. + if( subject.type() == Symbol::Invalid ) + { + if( !addWithArtificialVariable( *rowptr ) ) + { + qWarning( "The constraint can not be satisfied." ); + return; + } + } + else + { + rowptr->solveFor( subject ); + substitute( subject, *rowptr ); + m_rows[ subject ].row = rowptr.release(); + } + + m_constraints[ constraint ].tag = tag; + + // Optimizing after each constraint is added performs less + // aggregate work due to a smaller average system size. It + // also ensures the solver remains in a consistent state. + optimize( *m_objective ); +} + +void SimplexSolver::removeConstraint( const Constraint& constraint ) +{ + auto cn_it = m_constraints.find( constraint ); + if( cn_it == m_constraints.end() ) + return; + + const Tag tag = cn_it->tag; + m_constraints.erase( cn_it ); + + // Remove the error effects from the objective function + // *before* pivoting, or substitutions into the objective + // will lead to incorrect solver results. + if( tag.marker.type() == Symbol::Error ) + removeMarkerEffects( tag.marker, constraint.strength() ); + + if( tag.other.type() == Symbol::Error ) + removeMarkerEffects( tag.other, constraint.strength() ); + + // If the marker is basic, simply drop the row. Otherwise, + // pivot the marker into the basis and then drop the row. + auto row_it = m_rows.find( tag.marker ); + if( row_it != m_rows.end() ) + { + std::unique_ptr< Row > rowptr( row_it->row ); + m_rows.erase( row_it ); + } + else + { + row_it = getMarkerLeavingRow( tag.marker ); + if( row_it == m_rows.end() ) + { + qWarning( "failed to find leaving row" ); + return; + } + + const auto leaving = row_it->symbol; + + std::unique_ptr< Row > rowptr( row_it->row ); + m_rows.erase( row_it ); + + rowptr->solveFor( leaving, tag.marker ); + substitute( tag.marker, *rowptr ); + } + + // Optimizing after each constraint is removed ensures that the + // solver remains consistent. It makes the solver api easier to + // use at a small tradeoff for speed. + optimize( *m_objective ); +} + +bool SimplexSolver::hasConstraint( const Constraint& constraint ) const +{ + return m_constraints.find( constraint ) != m_constraints.end(); +} + +void SimplexSolver::addEditVariable( const Variable& variable, double strength ) +{ + if( m_editVariables.find( variable ) != m_editVariables.end() ) + return; + + strength = Strength::clip( strength ); + if( strength == Strength::required ) + { + qWarning( "A required strength cannot be used in this context." ); + return; + } + + Constraint cn( Expression( variable ), OP_EQ, strength ); + addConstraint( cn ); + + EditInfo info( variable ); + info.tag = m_constraints[ cn ].tag; + info.constraint = cn; + + m_editVariables[ variable ] = info; +} + +void SimplexSolver::removeEditVariable( const Variable& variable ) +{ + auto it = m_editVariables.find( variable ); + if( it == m_editVariables.end() ) + return; + + removeConstraint( it->constraint ); + m_editVariables.erase( it ); +} + +bool SimplexSolver::hasEditVariable( const Variable& variable ) const +{ + return m_editVariables.find( variable ) != m_editVariables.end(); +} + +void SimplexSolver::suggestValue( const Variable& variable, double value ) +{ + auto it = m_editVariables.find( variable ); + if( it == m_editVariables.end() ) + { + qWarning( "The edit variable has not been added to the solver." ); + return; + } + + auto& editInfo = *it; + + const double delta = value - editInfo.constant; + editInfo.constant = value; + + /* + Check first if the positive error variable is basic. + Check next if the negative error variable is basic. + Otherwise update each row where the error variables exist. + */ + auto row_it = m_rows.find( editInfo.tag.marker ); + if( row_it != m_rows.end() ) + { + if( row_it->row->add( -delta ) < 0.0 ) + m_infeasibleRows.push_back( row_it->symbol ); + } + else + { + row_it = m_rows.find( editInfo.tag.other ); + if( row_it != m_rows.end() ) + { + if( row_it->row->add( delta ) < 0.0 ) + m_infeasibleRows.push_back( row_it->symbol ); + } + else + { + for ( const auto& row : m_rows ) + { + const double coeff = row.row->coefficientFor( editInfo.tag.marker ); + + if( coeff != 0.0 && row.row->add( delta * coeff ) < 0.0 && + row.symbol.type() != Symbol::External ) + { + m_infeasibleRows.push_back( row.symbol ); + } + } + } + } + + dualOptimize(); +} + +void SimplexSolver::updateVariables() +{ + for ( auto& info : m_variables ) + { + const auto it = m_rows.find( info.symbol ); + + if( it == m_rows.end() ) + info.variable.setValue( 0.0 ); + else + info.variable.setValue( it->row->constant() ); + } +} + +void SimplexSolver::reset() +{ + clearRows(); + + m_constraints.clear(); + m_variables.clear(); + m_editVariables.clear(); + m_infeasibleRows.clear(); + m_objective.reset( new Row() ); + m_artificial.reset(); + // nextId ! +} + +void SimplexSolver::clearRows() +{ + for ( auto& row : m_rows ) + delete row.row; + + m_rows.clear(); +} + +Symbol SimplexSolver::getVarSymbol( const Variable& variable ) +{ + auto it = m_variables.find( variable ); + if( it != m_variables.end() ) + return it->symbol; + + return m_variables[ variable ].symbol = Symbol::external(); +} + +Row* SimplexSolver::createRow( const Constraint& constraint, Tag& tag ) +{ + const auto& expr = constraint.expression(); + + auto row = new Row( expr.constant() ); + + // Substitute the current basic variables into the row. + for ( auto& term : expr.terms() ) + { + if( !nearZero( term.coefficient() ) ) + { + const auto symbol = getVarSymbol( term.variable() ); + + const auto it = m_rows.find( symbol ); + if( it != m_rows.end() ) + row->insert( *it->row, term.coefficient() ); + else + row->insert( symbol, term.coefficient() ); + } + } + + // Add the necessary slack, error, and dummy variables. + switch( constraint.oper() ) + { + case OP_LE: + case OP_GE: + { + double coeff = constraint.oper() == OP_LE ? 1.0 : -1.0; + auto slack = Symbol::slack(); + tag.marker = slack; + row->insert( slack, coeff ); + + if( constraint.strength() < Strength::required ) + { + const auto error = Symbol::error(); + tag.other = error; + row->insert( error, -coeff ); + m_objective->insert( error, constraint.strength() ); + } + break; + } + case OP_EQ: + { + if( constraint.strength() < Strength::required ) + { + const auto errplus = Symbol::error(); + const auto errminus = Symbol::error(); + + tag.marker = errplus; + tag.other = errminus; + row->insert( errplus, -1.0 ); // v = eplus - eminus + row->insert( errminus, 1.0 ); // v - eplus + eminus = 0 + m_objective->insert( errplus, constraint.strength() ); + m_objective->insert( errminus, constraint.strength() ); + } + else + { + const auto dummy = Symbol::dummy(); + tag.marker = dummy; + row->insert( dummy ); + } + break; + } + } + + // Ensure the row as a positive constant. + if( row->constant() < 0.0 ) + row->reverseSign(); + + return row; +} + +Symbol SimplexSolver::chooseSubject( const Row& row, const Tag& tag ) +{ + for ( const auto& cell : row.cells() ) + { + if( cell.symbol.type() == Symbol::External ) + return cell.symbol; + } + + if( tag.marker.type() == Symbol::Slack || tag.marker.type() == Symbol::Error ) + { + if( row.coefficientFor( tag.marker ) < 0.0 ) + return tag.marker; + } + + if( tag.other.type() == Symbol::Slack || tag.other.type() == Symbol::Error ) + { + if( row.coefficientFor( tag.other ) < 0.0 ) + return tag.other; + } + + return Symbol(); +} + +bool SimplexSolver::addWithArtificialVariable( const Row& row ) +{ + // Create and add the artificial variable to the tableau + auto art = Symbol::slack(); + m_rows[ art ].row = new Row( row ); + m_artificial.reset( new Row( row ) ); + + // Optimize the artificial objective. This is successful + // only if the artificial objective is optimized to zero. + bool success = optimize( *m_artificial ); + if ( !success ) + return false; + + success = nearZero( m_artificial->constant() ); + m_artificial.reset(); + + // If the artificial variable is not basic, pivot the row so that + // it becomes basic. If the row is constant, exit early. + auto it = m_rows.find( art ); + if( it != m_rows.end() ) + { + std::unique_ptr< Row > rowptr( it->row ); + m_rows.erase( it ); + if( rowptr->cells().empty() ) + return success; + + const auto entering = anyPivotableSymbol( *rowptr ); + if( entering.type() == Symbol::Invalid ) + return false; // unsatisfiable (will this ever happen?) + + rowptr->solveFor( art, entering ); + substitute( entering, *rowptr ); + m_rows[ entering ].row = rowptr.release(); + } + + // Remove the artificial variable from the tableau. + + for ( auto& rowInfo : m_rows ) + rowInfo.row->remove( art ); + + m_objective->remove( art ); + + return success; +} + +void SimplexSolver::substitute( const Symbol& symbol, const Row& row ) +{ + for ( auto& r : m_rows ) + { + r.row->substitute( symbol, row ); + if( r.symbol.type() != Symbol::External && r.row->constant() < 0.0 ) + m_infeasibleRows.push_back( r.symbol ); + } + + m_objective->substitute( symbol, row ); + + if( m_artificial.get() ) + m_artificial->substitute( symbol, row ); +} + +bool SimplexSolver::optimize( const Row& objective ) +{ + while( true ) + { + const auto entering = getEnteringSymbol( objective ); + if( entering.type() == Symbol::Invalid ) + return true; + + const auto it = getLeavingRow( entering ); + if( it == m_rows.end() ) + { + qWarning( "The objective is unbounded." ); + return false; + } + + // pivot the entering symbol into the basis + const auto leaving = it->symbol; + auto row = it->row; + + m_rows.erase( it ); + row->solveFor( leaving, entering ); + substitute( entering, *row ); + + m_rows[ entering ].row = row; + } +} + +void SimplexSolver::dualOptimize() +{ + while( !m_infeasibleRows.empty() ) + { + auto leaving = m_infeasibleRows.back(); + m_infeasibleRows.pop_back(); + + auto it = m_rows.find( leaving ); + if( it != m_rows.end() && !nearZero( it->row->constant() ) && + it->row->constant() < 0.0 ) + { + auto entering = getDualEnteringSymbol( *it->row ); + if( entering.type() == Symbol::Invalid ) + { + qWarning( "Dual optimize failed." ); + return; + } + + // Pivot the entering symbol into the basis + auto row = it->row; + m_rows.erase( it ); + + row->solveFor( leaving, entering ); + substitute( entering, *row ); + + m_rows[ entering ].row = row; + } + } +} + +Symbol SimplexSolver::getEnteringSymbol( const Row& objective ) +{ + for ( const auto& cell : objective.cells() ) + { + if( cell.symbol.type() != Symbol::Dummy && cell.coefficient < 0.0 ) + return cell.symbol; + } + return Symbol(); +} + +Symbol SimplexSolver::getDualEnteringSymbol( const Row& row ) +{ + Symbol entering; + double ratio = std::numeric_limits< double >::max(); + + for ( const auto& cell : row.cells() ) + { + if( cell.coefficient > 0.0 && cell.symbol.type() != Symbol::Dummy ) + { + const double coeff = m_objective->coefficientFor( cell.symbol ); + const double r = coeff / cell.coefficient; + + if( r < ratio ) + { + ratio = r; + entering = cell.symbol; + } + } + } + return entering; +} + +Symbol SimplexSolver::anyPivotableSymbol( const Row& row ) +{ + for ( const auto& cell : row.cells() ) + { + const auto& symbol = cell.symbol; + if( symbol.type() == Symbol::Slack || symbol.type() == Symbol::Error ) + return symbol; + } + return Symbol(); +} + +FlatMap< RowInfo >::iterator SimplexSolver::getLeavingRow( const Symbol& entering ) +{ + double ratio = std::numeric_limits< double >::max(); + auto found = m_rows.end(); + + for( auto it = m_rows.begin(); it != m_rows.end(); ++it ) + { + if( it->symbol.type() != Symbol::External ) + { + const double coeff = it->row->coefficientFor( entering ); + if( coeff < 0.0 ) + { + const double temp_ratio = -it->row->constant() / coeff; + if( temp_ratio < ratio ) + { + ratio = temp_ratio; + found = it; + } + } + } + } + return found; +} + +FlatMap< RowInfo >::iterator SimplexSolver::getMarkerLeavingRow( const Symbol& marker ) +{ + const double dmax = std::numeric_limits< double >::max(); + + double r1 = dmax; + double r2 = dmax; + + auto end = m_rows.end(); + auto first = end; + auto second = end; + auto third = end; + + for( auto it = m_rows.begin(); it != m_rows.end(); ++it ) + { + double coeff = it->row->coefficientFor( marker ); + if( coeff == 0.0 ) + continue; + + if( it->symbol.type() == Symbol::External ) + { + third = it; + } + else if( coeff < 0.0 ) + { + const double r = -it->row->constant() / coeff; + if( r < r1 ) + { + r1 = r; + first = it; + } + } + else + { + double r = it->row->constant() / coeff; + if( r < r2 ) + { + r2 = r; + second = it; + } + } + } + if( first != end ) + return first; + + if( second != end ) + return second; + + return third; +} + +void SimplexSolver::removeMarkerEffects( const Symbol& marker, double strength ) +{ + auto row_it = m_rows.find( marker ); + if( row_it != m_rows.end() ) + m_objective->insert( *row_it->row, -strength ); + else + m_objective->insert( marker, -strength ); +} + +Solver::Solver() + : m_solver( new SimplexSolver() ) +{ +} + +Solver::~Solver() +{ +} + +void Solver::addConstraint( const Constraint& constraint ) +{ + m_solver->addConstraint( constraint ); +} + +void Solver::removeConstraint( const Constraint& constraint ) +{ + m_solver->removeConstraint( constraint ); +} + +bool Solver::hasConstraint( const Constraint& constraint ) const +{ + return m_solver->hasConstraint( constraint ); +} + +void Solver::addEditVariable( const Variable& variable, double strength ) +{ + m_solver->addEditVariable( variable, strength ); +} + +void Solver::removeEditVariable( const Variable& variable ) +{ + m_solver->removeEditVariable( variable ); +} + +bool Solver::hasEditVariable( const Variable& variable ) const +{ + return m_solver->hasEditVariable( variable ); +} + +void Solver::suggestValue( const Variable& variable, double value ) +{ + m_solver->suggestValue( variable, value ); +} + +void Solver::updateVariables() +{ + m_solver->updateVariables(); +} + +void Solver::reset() +{ + m_solver->reset(); +} diff --git a/playground/anchors/Solver.h b/playground/anchors/Solver.h new file mode 100644 index 00000000..3f721550 --- /dev/null +++ b/playground/anchors/Solver.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +class Variable; +class Constraint; +class SimplexSolver; + +class Solver +{ + public: + + Solver(); + ~Solver(); + + void addConstraint( const Constraint& ); + void removeConstraint( const Constraint& ); + bool hasConstraint( const Constraint& ) const; + + void addEditVariable( const Variable&, double strength ); + void removeEditVariable( const Variable& ); + + bool hasEditVariable( const Variable& ) const; + void suggestValue( const Variable&, double value ); + + void updateVariables(); + void reset(); + + private: + Q_DISABLE_COPY( Solver ) + std::unique_ptr< SimplexSolver > m_solver; +}; diff --git a/playground/anchors/Strength.h b/playground/anchors/Strength.h new file mode 100644 index 00000000..d5788af2 --- /dev/null +++ b/playground/anchors/Strength.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Strength +{ + inline double create( double a, double b, double c, double w = 1.0 ) + { + double result = 0.0; + result += std::max( 0.0, std::min( 1000.0, a * w ) ) * 1000000.0; + result += std::max( 0.0, std::min( 1000.0, b * w ) ) * 1000.0; + result += std::max( 0.0, std::min( 1000.0, c * w ) ); + return result; + } + + + const double required = create( 1000.0, 1000.0, 1000.0 ); + const double strong = create( 1.0, 0.0, 0.0 ); + const double medium = create( 0.0, 1.0, 0.0 ); + const double weak = create( 0.0, 0.0, 1.0 ); + + inline double clip( double value ) + { + return std::max( 0.0, std::min( required, value ) ); + } +} diff --git a/playground/anchors/Term.h b/playground/anchors/Term.h new file mode 100644 index 00000000..889b60a8 --- /dev/null +++ b/playground/anchors/Term.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include "Variable.h" + +class Term +{ + public: + + Term( const Variable& variable, double coefficient = 1.0 ) + : m_variable( variable ) + , m_coefficient( coefficient ) + { + } + + // to facilitate efficient map -> vector copies + Term( const std::pair< const Variable, double >& pair ) + : m_variable( pair.first ) + , m_coefficient( pair.second ) + { + } + + const Variable& variable() const + { + return m_variable; + } + + double coefficient() const + { + return m_coefficient; + } + + double value() const + { + return m_coefficient * m_variable.value(); + } + + private: + Variable m_variable; + double m_coefficient; +}; + +inline Term operator*( const Variable& variable, double coefficient ) +{ + return Term( variable, coefficient ); +} + +inline Term operator/( const Variable& variable, double denominator ) +{ + return variable * ( 1.0 / denominator ); +} + +inline Term operator-( const Variable& variable ) +{ + return variable * -1.0; +} + +inline Term operator*( const Term& term, double coefficient ) +{ + return Term( term.variable(), term.coefficient() * coefficient ); +} + +inline Term operator/( const Term& term, double denominator ) +{ + return term * ( 1.0 / denominator ); +} + +inline Term operator-( const Term& term ) +{ + return term * -1.0; +} + +inline Term operator*( double coefficient, const Term& term ) +{ + return term * coefficient; +} + +inline Term operator*( double coefficient, const Variable& variable ) +{ + return variable * coefficient; +} diff --git a/playground/anchors/Variable.h b/playground/anchors/Variable.h new file mode 100644 index 00000000..37a85f9c --- /dev/null +++ b/playground/anchors/Variable.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +class Variable +{ + public: + Variable( double value = 0.0 ) + : m_value( std::make_shared< double >(value) ) + { + } + + Variable( const Variable& v ) + : m_value( v.m_value ) + { + } + + Variable& operator=( const Variable& v ) + { + m_value = v.m_value; + return *this; + } + + double value() const + { + return m_value ? *m_value : 0.0; + } + + void setValue(double x) + { + *m_value = x; + } + + bool equals( const Variable& other ) + { + return m_value == other.m_value; + } + + private: + std::shared_ptr< double > m_value; + + friend bool operator<( const Variable& lhs, const Variable& rhs ) + { + return lhs.m_value < rhs.m_value; + } +}; diff --git a/playground/anchors/anchors.pro b/playground/anchors/anchors.pro new file mode 100644 index 00000000..d5e25ce4 --- /dev/null +++ b/playground/anchors/anchors.pro @@ -0,0 +1,21 @@ +CONFIG += qskexample + +HEADERS += \ + Constraint.h \ + Expression.h \ + Solver.h \ + Strength.h \ + Term.h \ + Variable.h + +SOURCES += \ + Expression.cpp \ + Constraint.cpp \ + Solver.cpp + +HEADERS += \ + AnchorBox.h + +SOURCES += \ + AnchorBox.cpp \ + main.cpp diff --git a/playground/anchors/main.cpp b/playground/anchors/main.cpp new file mode 100644 index 00000000..00697574 --- /dev/null +++ b/playground/anchors/main.cpp @@ -0,0 +1,190 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "AnchorBox.h" + +#include +#include + +#include +#include +#include + +#include +#include + +class Rectangle : public QskControl +{ + public: + Rectangle( const char* colorName, QQuickItem* parent = nullptr ) + : QskControl( parent) + , m_colorName( colorName ) + { + setObjectName( colorName ); + + initSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Preferred ); + + setMinimumSize( 10, 10 ); + setPreferredSize( 100, 100 ); + setMaximumSize( 1000, 1000 ); + + setBackgroundColor( colorName ); + } + + protected: + void geometryChangeEvent( QskGeometryChangeEvent* event ) override + { + QskControl::geometryChangeEvent( event ); +#if 0 + qDebug() << m_colorName << size(); +#endif + } + + private: + QByteArray m_colorName; +}; + + +class MyBox : public AnchorBox +{ + public: + MyBox( QQuickItem* parent = nullptr ) + : AnchorBox( parent ) + { + setObjectName( "Box" ); + + setup1(); + } + + void setup1(); + void setup2(); + void setup3(); + + protected: + virtual void geometryChangeEvent( QskGeometryChangeEvent* event ) override + { + AnchorBox::geometryChangeEvent( event ); +#if 1 + qDebug() << boxHint( Qt::MinimumSize ); + qDebug() << boxHint( Qt::PreferredSize ); + qDebug() << boxHint( Qt::MaximumSize ); +#endif + } +}; + +void MyBox::setup1() +{ + auto a = new Rectangle( "PaleVioletRed" ); + auto b = new Rectangle( "DarkSeaGreen" ); + auto c = new Rectangle( "SkyBlue" ); + auto d = new Rectangle( "Coral" ); + auto e = new Rectangle( "NavajoWhite" ); + auto f = new Rectangle( "Peru" ); + auto g = new Rectangle( "Olive" ); + + addAnchor( a, Qt::AnchorTop, Qt::AnchorTop ); + addAnchor( b, Qt::AnchorTop, Qt::AnchorTop ); + + addAnchor( c, Qt::AnchorTop, a, Qt::AnchorBottom ); + addAnchor( c, Qt::AnchorTop, b, Qt::AnchorBottom ); + addAnchor( c, Qt::AnchorBottom, d, Qt::AnchorTop ); + addAnchor( c, Qt::AnchorBottom, e, Qt::AnchorTop ); + + addAnchor( d, Qt::AnchorBottom, Qt::AnchorBottom ); + addAnchor( e, Qt::AnchorBottom, Qt::AnchorBottom ); + + addAnchor( c, Qt::AnchorTop, f, Qt::AnchorTop ); + addAnchor( c, Qt::AnchorVerticalCenter, f, Qt::AnchorBottom ); + addAnchor( f, Qt::AnchorBottom, g, Qt::AnchorTop ); + addAnchor( c, Qt::AnchorBottom, g, Qt::AnchorBottom ); + + // horizontal + addAnchor( a, Qt::AnchorLeft, Qt::AnchorLeft ); + addAnchor( d, Qt::AnchorLeft, Qt::AnchorLeft ); + addAnchor( a, Qt::AnchorRight, b, Qt::AnchorLeft ); + + addAnchor( a, Qt::AnchorRight, c, Qt::AnchorLeft ); + addAnchor( c, Qt::AnchorRight, e, Qt::AnchorLeft ); + + addAnchor( b, Qt::AnchorRight, Qt::AnchorRight ); + addAnchor( e, Qt::AnchorRight, Qt::AnchorRight ); + addAnchor( d, Qt::AnchorRight, e, Qt::AnchorLeft ); + + addAnchor( f, Qt::AnchorLeft, Qt::AnchorLeft ); + addAnchor( g, Qt::AnchorLeft, Qt::AnchorLeft ); + addAnchor( f, Qt::AnchorRight, g, Qt::AnchorRight ); +} + +void MyBox::setup2() +{ + auto a = new Rectangle( "PaleVioletRed" ); + + addAnchor( a, Qt::AnchorLeft, Qt::AnchorLeft ); + addAnchor( a, Qt::AnchorTop, Qt::AnchorTop ); + + +#if 0 + auto b = new Rectangle( "DarkSeaGreen" ); + addAnchor( a, Qt::AnchorRight, b, Qt::AnchorLeft ); + addAnchor( b, Qt::AnchorRight, m_layout, Qt::AnchorRight ); +#endif + +#if 1 + auto c = new Rectangle( "SkyBlue" ); + addAnchor( a, Qt::AnchorBottom, c, Qt::AnchorTop ); + addAnchor( a, Qt::AnchorRight, c, Qt::AnchorRight ); + + auto d = new Rectangle( "Coral" ); + addAnchor( c, Qt::AnchorLeft, d, Qt::AnchorLeft ); + addAnchor( c, Qt::AnchorBottom, d, Qt::AnchorTop ); + addAnchor( d, Qt::AnchorRight, Qt::AnchorRight ); +#endif +} + +void MyBox::setup3() +{ + auto a = new Rectangle( "PaleVioletRed" ); + auto b = new Rectangle( "DarkSeaGreen" ); + auto c = new Rectangle( "SkyBlue" ); + auto d = new Rectangle( "Coral" ); + + addAnchor( a, Qt::AnchorTop, Qt::AnchorTop ); + + addAnchor( a, Qt::AnchorLeft, Qt::AnchorLeft ); + addAnchor( a, Qt::AnchorRight, b, Qt::AnchorLeft ); + addAnchor( b, Qt::AnchorRight, c, Qt::AnchorLeft ); + addAnchor( c, Qt::AnchorRight, d, Qt::AnchorLeft ); + addAnchor( d, Qt::AnchorRight, Qt::AnchorRight ); + + auto e = new Rectangle( "NavajoWhite" ); +#if 1 + e->setMinimumWidth( 100 ); +#endif + addAnchor( a, Qt::AnchorBottom, e, Qt::AnchorTop ); + addAnchor( c, Qt::AnchorRight, e, Qt::AnchorRight ); + addAnchor( b, Qt::AnchorLeft, e, Qt::AnchorLeft ); +} + +int main( int argc, char* argv[] ) +{ +#ifdef ITEM_STATISTICS + QskObjectCounter counter( true ); +#endif + + QGuiApplication app( argc, argv ); + + SkinnyFont::init( &app ); + SkinnyShortcut::enable( SkinnyShortcut::Quit | SkinnyShortcut::DebugShortcuts ); + + auto box = new MyBox(); + + QskWindow window; + window.addItem( box ); + window.resize( 600, 600 ); + window.show(); + + return app.exec(); +} + diff --git a/playground/playground.pro b/playground/playground.pro index 33a92754..3fc1d24b 100644 --- a/playground/playground.pro +++ b/playground/playground.pro @@ -1,6 +1,7 @@ TEMPLATE = subdirs SUBDIRS += \ + anchors \ dialogbuttons \ invoker \ inputpanel \