blob: b1ab8fcbaf0fe37b304e1521e6c05b3e0a49318f [file] [log] [blame]
//! This crate contains an implementation of the Cassowary constraint solving algorithm, based upon the work by
//! G.J. Badros et al. in 2001. This algorithm is designed primarily for use constraining elements in user interfaces.
//! Constraints are linear combinations of the problem variables. The notable features of Cassowary that make it
//! ideal for user interfaces are that it is incremental (i.e. you can add and remove constraints at runtime
//! and it will perform the minimum work to update the result) and that the constraints can be violated if
//! necessary,
//! with the order in which they are violated specified by setting a "strength" for each constraint.
//! This allows the solution to gracefully degrade, which is useful for when a
//! user interface needs to compromise on its constraints in order to still be able to display something.
//!
//! ## Constraint syntax
//!
//! This crate aims to provide syntax for describing linear constraints as naturally as possible, within
//! the limitations of Rust's type system. Generally you can write constraints as you would naturally, however
//! the operator symbol (for greater-than, less-than, equals) is replaced with an instance of the
//! `WeightedRelation` enum wrapped in "pipe brackets".
//!
//! For example, for the constraint
//! `(a + b) * 2 + c >= d + 1` with strength `s`, the code to use is
//!
//! ```ignore
//! (a + b) * 2.0 + c |GE(s)| d + 1.0
//! ```
//!
//! # A simple example
//!
//! Imagine a layout consisting of two elements laid out horizontally. For small window widths the elements
//! should compress to fit, but if there is enough space they should display at their preferred widths. The
//! first element will align to the left, and the second to the right. For this example we will ignore
//! vertical layout.
//!
//! First we need to include the relevant parts of `cassowary`:
//!
//! ```
//! use cassowary::{ Solver, Variable };
//! use cassowary::WeightedRelation::*;
//! use cassowary::strength::{ WEAK, MEDIUM, STRONG, REQUIRED };
//! ```
//!
//! And we'll construct some conveniences for pretty printing (which should hopefully be self-explanatory):
//!
//! ```ignore
//! use std::collections::HashMap;
//! let mut names = HashMap::new();
//! fn print_changes(names: &HashMap<Variable, &'static str>, changes: &[(Variable, f64)]) {
//! println!("Changes:");
//! for &(ref var, ref val) in changes {
//! println!("{}: {}", names[var], val);
//! }
//! }
//! ```
//!
//! Let's define the variables required - the left and right edges of the elements, and the width of the window.
//!
//! ```ignore
//! let window_width = Variable::new();
//! names.insert(window_width, "window_width");
//!
//! struct Element {
//! left: Variable,
//! right: Variable
//! }
//! let box1 = Element {
//! left: Variable::new(),
//! right: Variable::new()
//! };
//! names.insert(box1.left, "box1.left");
//! names.insert(box1.right, "box1.right");
//!
//! let box2 = Element {
//! left: Variable::new(),
//! right: Variable::new()
//! };
//! names.insert(box2.left, "box2.left");
//! names.insert(box2.right, "box2.right");
//! ```
//!
//! Now to set up the solver and constraints.
//!
//! ```ignore
//! let mut solver = Solver::new();
//! solver.add_constraints(&[window_width |GE(REQUIRED)| 0.0, // positive window width
//! box1.left |EQ(REQUIRED)| 0.0, // left align
//! box2.right |EQ(REQUIRED)| window_width, // right align
//! box2.left |GE(REQUIRED)| box1.right, // no overlap
//! // positive widths
//! box1.left |LE(REQUIRED)| box1.right,
//! box2.left |LE(REQUIRED)| box2.right,
//! // preferred widths:
//! box1.right - box1.left |EQ(WEAK)| 50.0,
//! box2.right - box2.left |EQ(WEAK)| 100.0]).unwrap();
//! ```
//!
//! The window width is currently free to take any positive value. Let's constrain it to a particular value.
//! Since for this example we will repeatedly change the window width, it is most efficient to use an
//! "edit variable", instead of repeatedly removing and adding constraints (note that for efficiency
//! reasons we cannot edit a normal constraint that has been added to the solver).
//!
//! ```ignore
//! solver.add_edit_variable(window_width, STRONG).unwrap();
//! solver.suggest_value(window_width, 300.0).unwrap();
//! ```
//!
//! This value of 300 is enough to fit both boxes in with room to spare, so let's check that this is the case.
//! We can fetch a list of changes to the values of variables in the solver. Using the pretty printer defined
//! earlier we can see what values our variables now hold.
//!
//! ```ignore
//! print_changes(&names, solver.fetch_changes());
//! ```
//!
//! This should print (in a possibly different order):
//!
//! ```ignore
//! Changes:
//! window_width: 300
//! box1.right: 50
//! box2.left: 200
//! box2.right: 300
//! ```
//!
//! Note that the value of `box1.left` is not mentioned. This is because `solver.fetch_changes` only lists
//! *changes* to variables, and since each variable starts in the solver with a value of zero, any values that
//! have not changed from zero will not be reported.
//!
//! Now let's try compressing the window so that the boxes can't take up their preferred widths.
//!
//! ```ignore
//! solver.suggest_value(window_width, 75.0);
//! print_changes(&names, solver.fetch_changes);
//! ```
//!
//! Now the solver can't satisfy all of the constraints. It will pick at least one of the weakest constraints to
//! violate. In this case it will be one or both of the preferred widths. For efficiency reasons this is picked
//! nondeterministically, so there are two possible results. This could be
//!
//! ```ignore
//! Changes:
//! window_width: 75
//! box1.right: 0
//! box2.left: 0
//! box2.right: 75
//! ```
//!
//! or
//!
//! ```ignore
//! Changes:
//! window_width: 75
//! box2.left: 50
//! box2.right: 75
//! ```
//!
//! Due to the nature of the algorithm, "in-between" solutions, although just as valid, are not picked.
//!
//! In a user interface this is not likely a result we would prefer. The solution is to add another constraint
//! to control the behaviour when the preferred widths cannot both be satisfied. In this example we are going
//! to constrain the boxes to try to maintain a ratio between their widths.
//!
//! ```
//! # use cassowary::{ Solver, Variable };
//! # use cassowary::WeightedRelation::*;
//! # use cassowary::strength::{ WEAK, MEDIUM, STRONG, REQUIRED };
//! #
//! # use std::collections::HashMap;
//! # let mut names = HashMap::new();
//! # fn print_changes(names: &HashMap<Variable, &'static str>, changes: &[(Variable, f64)]) {
//! # println!("Changes:");
//! # for &(ref var, ref val) in changes {
//! # println!("{}: {}", names[var], val);
//! # }
//! # }
//! #
//! # let window_width = Variable::new();
//! # names.insert(window_width, "window_width");
//! # struct Element {
//! # left: Variable,
//! # right: Variable
//! # }
//! # let box1 = Element {
//! # left: Variable::new(),
//! # right: Variable::new()
//! # };
//! # names.insert(box1.left, "box1.left");
//! # names.insert(box1.right, "box1.right");
//! # let box2 = Element {
//! # left: Variable::new(),
//! # right: Variable::new()
//! # };
//! # names.insert(box2.left, "box2.left");
//! # names.insert(box2.right, "box2.right");
//! # let mut solver = Solver::new();
//! # solver.add_constraints(&[window_width |GE(REQUIRED)| 0.0, // positive window width
//! # box1.left |EQ(REQUIRED)| 0.0, // left align
//! # box2.right |EQ(REQUIRED)| window_width, // right align
//! # box2.left |GE(REQUIRED)| box1.right, // no overlap
//! # // positive widths
//! # box1.left |LE(REQUIRED)| box1.right,
//! # box2.left |LE(REQUIRED)| box2.right,
//! # // preferred widths:
//! # box1.right - box1.left |EQ(WEAK)| 50.0,
//! # box2.right - box2.left |EQ(WEAK)| 100.0]).unwrap();
//! # solver.add_edit_variable(window_width, STRONG).unwrap();
//! # solver.suggest_value(window_width, 300.0).unwrap();
//! # print_changes(&names, solver.fetch_changes());
//! # solver.suggest_value(window_width, 75.0);
//! # print_changes(&names, solver.fetch_changes());
//! solver.add_constraint(
//! (box1.right - box1.left) / 50.0 |EQ(MEDIUM)| (box2.right - box2.left) / 100.0
//! ).unwrap();
//! print_changes(&names, solver.fetch_changes());
//! ```
//!
//! Now the result gives values that maintain the ratio between the sizes of the two boxes:
//!
//! ```ignore
//! Changes:
//! box1.right: 25
//! box2.left: 25
//! ```
//!
//! This example may have appeared somewhat contrived, but hopefully it shows the power of the cassowary
//! algorithm for laying out user interfaces.
//!
//! One thing that this example exposes is that this crate is a rather low level library. It does not have
//! any inherent knowledge of user interfaces, directions or boxes. Thus for use in a user interface this
//! crate should ideally be wrapped by a higher level API, which is outside the scope of this crate.
use std::sync::Arc;
use std::collections::HashMap;
use std::collections::hash_map::{Entry};
mod solver_impl;
mod operators;
static VARIABLE_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
/// Identifies a variable for the constraint solver.
/// Each new variable is unique in the view of the solver, but copying or cloning the variable produces
/// a copy of the same variable.
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Variable(usize);
impl Variable {
/// Produces a new unique variable for use in constraint solving.
pub fn new() -> Variable {
Variable(VARIABLE_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed))
}
}
/// A variable and a coefficient to multiply that variable by. This is a sub-expression in
/// a constraint equation.
#[derive(Copy, Clone, Debug)]
pub struct Term {
pub variable: Variable,
pub coefficient: f64
}
impl Term {
/// Construct a new Term from a variable and a coefficient.
fn new(variable: Variable, coefficient: f64) -> Term {
Term {
variable: variable,
coefficient: coefficient
}
}
}
/// An expression that can be the left hand or right hand side of a constraint equation.
/// It is a linear combination of variables, i.e. a sum of variables weighted by coefficients, plus an optional constant.
#[derive(Clone, Debug)]
pub struct Expression {
pub terms: Vec<Term>,
pub constant: f64
}
impl Expression {
/// Constructs an expression of the form _n_, where n is a constant real number, not a variable.
pub fn from_constant(v: f64) -> Expression {
Expression {
terms: Vec::new(),
constant: v
}
}
/// Constructs an expression from a single term. Forms an expression of the form _n x_
/// where n is the coefficient, and x is the variable.
pub fn from_term(term: Term) -> Expression {
Expression {
terms: vec![term],
constant: 0.0
}
}
/// General constructor. Each `Term` in `terms` is part of the sum forming the expression, as well as `constant`.
pub fn new(terms: Vec<Term>, constant: f64) -> Expression {
Expression {
terms: terms,
constant: constant
}
}
/// Mutates this expression by multiplying it by minus one.
pub fn negate(&mut self) {
self.constant = -self.constant;
for t in &mut self.terms {
*t = -*t;
}
}
}
impl From<f64> for Expression {
fn from(v: f64) -> Expression {
Expression::from_constant(v)
}
}
impl From<Variable> for Expression {
fn from(v: Variable) -> Expression {
Expression::from_term(Term::new(v, 1.0))
}
}
impl From<Term> for Expression {
fn from(t: Term) -> Expression {
Expression::from_term(t)
}
}
/// Contains useful constants and functions for producing strengths for use in the constraint solver.
/// Each constraint added to the solver has an associated strength specifying the precedence the solver should
/// impose when choosing which constraints to enforce. It will try to enforce all constraints, but if that
/// is impossible the lowest strength constraints are the first to be violated.
///
/// Strengths are simply real numbers. The strongest legal strength is 1,001,001,000.0. The weakest is 0.0.
/// For convenience constants are declared for commonly used strengths. These are `REQUIRED`, `STRONG`,
/// `MEDIUM` and `WEAK`. Feel free to multiply these by other values to get intermediate strengths.
/// Note that the solver will clip given strengths to the legal range.
///
/// `REQUIRED` signifies a constraint that cannot be violated under any circumstance. Use this special strength
/// sparingly, as the solver will fail completely if it find that not all of the `REQUIRED` constraints
/// can be satisfied. The other strengths represent fallible constraints. These should be the most
/// commonly used strenghts for use cases where violating a constraint is acceptable or even desired.
///
/// The solver will try to get as close to satisfying the constraints it violates as possible, strongest first.
/// This behaviour can be used (for example) to provide a "default" value for a variable should no other
/// stronger constraints be put upon it.
pub mod strength {
/// Create a constraint as a linear combination of STRONG, MEDIUM and WEAK strengths, corresponding to `a`
/// `b` and `c` respectively. The result is further multiplied by `w`.
pub fn create(a: f64, b: f64, c: f64, w: f64) -> f64 {
(a * w).max(0.0).min(1000.0) * 1_000_000.0 +
(b * w).max(0.0).min(1000.0) * 1000.0 +
(c * w).max(0.0).min(1000.0)
}
pub const REQUIRED: f64 = 1_001_001_000.0;
pub const STRONG: f64 = 1_000_000.0;
pub const MEDIUM: f64 = 1_000.0;
pub const WEAK: f64 = 1.0;
/// Clips a strength value to the legal range
pub fn clip(s: f64) -> f64 {
s.min(REQUIRED).max(0.0)
}
}
/// The possible relations that a constraint can specify.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub enum RelationalOperator {
/// `<=`
LessOrEqual,
/// `==`
Equal,
/// `>=`
GreaterOrEqual
}
impl std::fmt::Display for RelationalOperator {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
RelationalOperator::LessOrEqual => write!(fmt, "<=") ?,
RelationalOperator::Equal => write!(fmt, "==") ?,
RelationalOperator::GreaterOrEqual => write!(fmt, ">=") ?,
};
Ok(())
}
}
#[derive(Debug)]
struct ConstraintData {
expression: Expression,
strength: f64,
op: RelationalOperator
}
/// A constraint, consisting of an equation governed by an expression and a relational operator,
/// and an associated strength.
#[derive(Clone, Debug)]
pub struct Constraint(Arc<ConstraintData>);
impl Constraint {
/// Construct a new constraint from an expression, a relational operator and a strength.
/// This corresponds to the equation `e op 0.0`, e.g. `x + y >= 0.0`. For equations with a non-zero
/// right hand side, subtract it from the equation to give a zero right hand side.
pub fn new(e: Expression, op: RelationalOperator, strength: f64) -> Constraint {
Constraint(Arc::new(ConstraintData {
expression: e,
op: op,
strength: strength
}))
}
/// The expression of the left hand side of the constraint equation.
pub fn expr(&self) -> &Expression {
&self.0.expression
}
/// The relational operator governing the constraint.
pub fn op(&self) -> RelationalOperator {
self.0.op
}
/// The strength of the constraint that the solver will use.
pub fn strength(&self) -> f64 {
self.0.strength
}
}
impl ::std::hash::Hash for Constraint {
fn hash<H: ::std::hash::Hasher>(&self, hasher: &mut H) {
use ::std::ops::Deref;
hasher.write_usize(self.0.deref() as *const _ as usize);
}
}
impl PartialEq for Constraint {
fn eq(&self, other: &Constraint) -> bool {
use ::std::ops::Deref;
self.0.deref() as *const _ == other.0.deref() as *const _
}
}
impl Eq for Constraint {}
/// This is part of the syntactic sugar used for specifying constraints. This enum should be used as part of a
/// constraint expression. See the module documentation for more information.
pub enum WeightedRelation {
/// `==`
EQ(f64),
/// `<=`
LE(f64),
/// `>=`
GE(f64)
}
impl From<WeightedRelation> for (RelationalOperator, f64) {
fn from(r: WeightedRelation) -> (RelationalOperator, f64) {
use WeightedRelation::*;
match r {
EQ(s) => (RelationalOperator::Equal, s),
LE(s) => (RelationalOperator::LessOrEqual, s),
GE(s) => (RelationalOperator::GreaterOrEqual, s),
}
}
}
/// This is an intermediate type used in the syntactic sugar for specifying constraints. You should not use it
/// directly.
pub struct PartialConstraint(Expression, WeightedRelation);
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum SymbolType {
Invalid,
External,
Slack,
Error,
Dummy
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Symbol(usize, SymbolType);
impl Symbol {
fn invalid() -> Symbol { Symbol(0, SymbolType::Invalid) }
fn type_(&self) -> SymbolType { self.1 }
}
#[derive(Clone)]
struct Row {
cells: HashMap<Symbol, f64>,
constant: f64
}
fn near_zero(value: f64) -> bool {
const EPS: f64 = 1E-8;
if value < 0.0 {
-value < EPS
} else {
value < EPS
}
}
impl Row {
fn new(constant: f64) -> Row {
Row {
cells: HashMap::new(),
constant: constant
}
}
fn add(&mut self, v: f64) -> f64 {
self.constant += v;
self.constant
}
fn insert_symbol(&mut self, s: Symbol, coefficient: f64) {
match self.cells.entry(s) {
Entry::Vacant(entry) => if !near_zero(coefficient) {
entry.insert(coefficient);
},
Entry::Occupied(mut entry) => {
*entry.get_mut() += coefficient;
if near_zero(*entry.get_mut()) {
entry.remove();
}
}
}
}
fn insert_row(&mut self, other: &Row, coefficient: f64) -> bool {
let constant_diff = other.constant * coefficient;
self.constant += constant_diff;
for (s, v) in &other.cells {
self.insert_symbol(*s, v * coefficient);
}
constant_diff != 0.0
}
fn remove(&mut self, s: Symbol) {
self.cells.remove(&s);
}
fn reverse_sign(&mut self) {
self.constant = -self.constant;
for (_, v) in &mut self.cells {
*v = -*v;
}
}
fn solve_for_symbol(&mut self, s: Symbol) {
let coeff = -1.0 / match self.cells.entry(s) {
Entry::Occupied(entry) => entry.remove(),
Entry::Vacant(_) => unreachable!()
};
self.constant *= coeff;
for (_, v) in &mut self.cells {
*v *= coeff;
}
}
fn solve_for_symbols(&mut self, lhs: Symbol, rhs: Symbol) {
self.insert_symbol(lhs, -1.0);
self.solve_for_symbol(rhs);
}
fn coefficient_for(&self, s: Symbol) -> f64 {
self.cells.get(&s).cloned().unwrap_or(0.0)
}
fn substitute(&mut self, s: Symbol, row: &Row) -> bool {
if let Some(coeff) = self.cells.remove(&s) {
self.insert_row(row, coeff)
} else {
false
}
}
}
/// The possible error conditions that `Solver::add_constraint` can fail with.
#[derive(Debug, Copy, Clone)]
pub enum AddConstraintError {
/// The constraint specified has already been added to the solver.
DuplicateConstraint,
/// The constraint is required, but it is unsatisfiable in conjunction with the existing constraints.
UnsatisfiableConstraint,
/// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
/// additional details as a string.
InternalSolverError(&'static str)
}
/// The possible error conditions that `Solver::remove_constraint` can fail with.
#[derive(Debug, Copy, Clone)]
pub enum RemoveConstraintError {
/// The constraint specified was not already in the solver, so cannot be removed.
UnknownConstraint,
/// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
/// additional details as a string.
InternalSolverError(&'static str)
}
/// The possible error conditions that `Solver::add_edit_variable` can fail with.
#[derive(Debug, Copy, Clone)]
pub enum AddEditVariableError {
/// The specified variable is already marked as an edit variable in the solver.
DuplicateEditVariable,
/// The specified strength was `REQUIRED`. This is illegal for edit variable strengths.
BadRequiredStrength
}
/// The possible error conditions that `Solver::remove_edit_variable` can fail with.
#[derive(Debug, Copy, Clone)]
pub enum RemoveEditVariableError {
/// The specified variable was not an edit variable in the solver, so cannot be removed.
UnknownEditVariable,
/// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
/// additional details as a string.
InternalSolverError(&'static str)
}
/// The possible error conditions that `Solver::suggest_value` can fail with.
#[derive(Debug, Copy, Clone)]
pub enum SuggestValueError {
/// The specified variable was not an edit variable in the solver, so cannot have its value suggested.
UnknownEditVariable,
/// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
/// additional details as a string.
InternalSolverError(&'static str)
}
#[derive(Debug, Copy, Clone)]
struct InternalSolverError(&'static str);
pub use solver_impl::Solver;