cassowary/
lib.rs

1//! This crate contains an implementation of the Cassowary constraint solving algorithm, based upon the work by
2//! G.J. Badros et al. in 2001. This algorithm is designed primarily for use constraining elements in user interfaces.
3//! Constraints are linear combinations of the problem variables. The notable features of Cassowary that make it
4//! ideal for user interfaces are that it is incremental (i.e. you can add and remove constraints at runtime
5//! and it will perform the minimum work to update the result) and that the constraints can be violated if
6//! necessary,
7//! with the order in which they are violated specified by setting a "strength" for each constraint.
8//! This allows the solution to gracefully degrade, which is useful for when a
9//! user interface needs to compromise on its constraints in order to still be able to display something.
10//!
11//! ## Constraint syntax
12//!
13//! This crate aims to provide syntax for describing linear constraints as naturally as possible, within
14//! the limitations of Rust's type system. Generally you can write constraints as you would naturally, however
15//! the operator symbol (for greater-than, less-than, equals) is replaced with an instance of the
16//! `WeightedRelation` enum wrapped in "pipe brackets".
17//!
18//! For example, for the constraint
19//! `(a + b) * 2 + c >= d + 1` with strength `s`, the code to use is
20//!
21//! ```ignore
22//! (a + b) * 2.0 + c |GE(s)| d + 1.0
23//! ```
24//!
25//! # A simple example
26//!
27//! Imagine a layout consisting of two elements laid out horizontally. For small window widths the elements
28//! should compress to fit, but if there is enough space they should display at their preferred widths. The
29//! first element will align to the left, and the second to the right. For  this example we will ignore
30//! vertical layout.
31//!
32//! First we need to include the relevant parts of `cassowary`:
33//!
34//! ```
35//! use cassowary::{ Solver, Variable };
36//! use cassowary::WeightedRelation::*;
37//! use cassowary::strength::{ WEAK, MEDIUM, STRONG, REQUIRED };
38//! ```
39//!
40//! And we'll construct some conveniences for pretty printing (which should hopefully be self-explanatory):
41//!
42//! ```ignore
43//! use std::collections::HashMap;
44//! let mut names = HashMap::new();
45//! fn print_changes(names: &HashMap<Variable, &'static str>, changes: &[(Variable, f64)]) {
46//!     println!("Changes:");
47//!     for &(ref var, ref val) in changes {
48//!         println!("{}: {}", names[var], val);
49//!     }
50//! }
51//! ```
52//!
53//! Let's define the variables required - the left and right edges of the elements, and the width of the window.
54//!
55//! ```ignore
56//! let window_width = Variable::new();
57//! names.insert(window_width, "window_width");
58//!
59//! struct Element {
60//!     left: Variable,
61//!     right: Variable
62//! }
63//! let box1 = Element {
64//!     left: Variable::new(),
65//!     right: Variable::new()
66//! };
67//! names.insert(box1.left, "box1.left");
68//! names.insert(box1.right, "box1.right");
69//!
70//! let box2 = Element {
71//!     left: Variable::new(),
72//!     right: Variable::new()
73//! };
74//! names.insert(box2.left, "box2.left");
75//! names.insert(box2.right, "box2.right");
76//! ```
77//!
78//! Now to set up the solver and constraints.
79//!
80//! ```ignore
81//! let mut solver = Solver::new();
82//! solver.add_constraints(&[window_width |GE(REQUIRED)| 0.0, // positive window width
83//!                          box1.left |EQ(REQUIRED)| 0.0, // left align
84//!                          box2.right |EQ(REQUIRED)| window_width, // right align
85//!                          box2.left |GE(REQUIRED)| box1.right, // no overlap
86//!                          // positive widths
87//!                          box1.left |LE(REQUIRED)| box1.right,
88//!                          box2.left |LE(REQUIRED)| box2.right,
89//!                          // preferred widths:
90//!                          box1.right - box1.left |EQ(WEAK)| 50.0,
91//!                          box2.right - box2.left |EQ(WEAK)| 100.0]).unwrap();
92//! ```
93//!
94//! The window width is currently free to take any positive value. Let's constrain it to a particular value.
95//! Since for this example we will repeatedly change the window width, it is most efficient to use an
96//! "edit variable", instead of repeatedly removing and adding constraints (note that for efficiency
97//! reasons we cannot edit a normal constraint that has been added to the solver).
98//!
99//! ```ignore
100//! solver.add_edit_variable(window_width, STRONG).unwrap();
101//! solver.suggest_value(window_width, 300.0).unwrap();
102//! ```
103//!
104//! This value of 300 is enough to fit both boxes in with room to spare, so let's check that this is the case.
105//! We can fetch a list of changes to the values of variables in the solver. Using the pretty printer defined
106//! earlier we can see what values our variables now hold.
107//!
108//! ```ignore
109//! print_changes(&names, solver.fetch_changes());
110//! ```
111//!
112//! This should print (in a possibly different order):
113//!
114//! ```ignore
115//! Changes:
116//! window_width: 300
117//! box1.right: 50
118//! box2.left: 200
119//! box2.right: 300
120//! ```
121//!
122//! Note that the value of `box1.left` is not mentioned. This is because `solver.fetch_changes` only lists
123//! *changes* to variables, and since each variable starts in the solver with a value of zero, any values that
124//! have not changed from zero will not be reported.
125//!
126//! Now let's try compressing the window so that the boxes can't take up their preferred widths.
127//!
128//! ```ignore
129//! solver.suggest_value(window_width, 75.0);
130//! print_changes(&names, solver.fetch_changes);
131//! ```
132//!
133//! Now the solver can't satisfy all of the constraints. It will pick at least one of the weakest constraints to
134//! violate. In this case it will be one or both of the preferred widths. For efficiency reasons this is picked
135//! nondeterministically, so there are two possible results. This could be
136//!
137//! ```ignore
138//! Changes:
139//! window_width: 75
140//! box1.right: 0
141//! box2.left: 0
142//! box2.right: 75
143//! ```
144//!
145//! or
146//!
147//! ```ignore
148//! Changes:
149//! window_width: 75
150//! box2.left: 50
151//! box2.right: 75
152//! ```
153//!
154//! Due to the nature of the algorithm, "in-between" solutions, although just as valid, are not picked.
155//!
156//! In a user interface this is not likely a result we would prefer. The solution is to add another constraint
157//! to control the behaviour when the preferred widths cannot both be satisfied. In this example we are going
158//! to constrain the boxes to try to maintain a ratio between their widths.
159//!
160//! ```
161//! # use cassowary::{ Solver, Variable };
162//! # use cassowary::WeightedRelation::*;
163//! # use cassowary::strength::{ WEAK, MEDIUM, STRONG, REQUIRED };
164//! #
165//! # use std::collections::HashMap;
166//! # let mut names = HashMap::new();
167//! # fn print_changes(names: &HashMap<Variable, &'static str>, changes: &[(Variable, f64)]) {
168//! #     println!("Changes:");
169//! #     for &(ref var, ref val) in changes {
170//! #         println!("{}: {}", names[var], val);
171//! #     }
172//! # }
173//! #
174//! # let window_width = Variable::new();
175//! # names.insert(window_width, "window_width");
176//! # struct Element {
177//! #    left: Variable,
178//! #    right: Variable
179//! # }
180//! # let box1 = Element {
181//! #     left: Variable::new(),
182//! #     right: Variable::new()
183//! # };
184//! # names.insert(box1.left, "box1.left");
185//! # names.insert(box1.right, "box1.right");
186//! # let box2 = Element {
187//! #     left: Variable::new(),
188//! #     right: Variable::new()
189//! # };
190//! # names.insert(box2.left, "box2.left");
191//! # names.insert(box2.right, "box2.right");
192//! # let mut solver = Solver::new();
193//! # solver.add_constraints(&[window_width |GE(REQUIRED)| 0.0, // positive window width
194//! #                          box1.left |EQ(REQUIRED)| 0.0, // left align
195//! #                          box2.right |EQ(REQUIRED)| window_width, // right align
196//! #                          box2.left |GE(REQUIRED)| box1.right, // no overlap
197//! #                          // positive widths
198//! #                          box1.left |LE(REQUIRED)| box1.right,
199//! #                          box2.left |LE(REQUIRED)| box2.right,
200//! #                          // preferred widths:
201//! #                          box1.right - box1.left |EQ(WEAK)| 50.0,
202//! #                          box2.right - box2.left |EQ(WEAK)| 100.0]).unwrap();
203//! # solver.add_edit_variable(window_width, STRONG).unwrap();
204//! # solver.suggest_value(window_width, 300.0).unwrap();
205//! # print_changes(&names, solver.fetch_changes());
206//! # solver.suggest_value(window_width, 75.0);
207//! # print_changes(&names, solver.fetch_changes());
208//! solver.add_constraint(
209//!     (box1.right - box1.left) / 50.0 |EQ(MEDIUM)| (box2.right - box2.left) / 100.0
210//!     ).unwrap();
211//! print_changes(&names, solver.fetch_changes());
212//! ```
213//!
214//! Now the result gives values that maintain the ratio between the sizes of the two boxes:
215//!
216//! ```ignore
217//! Changes:
218//! box1.right: 25
219//! box2.left: 25
220//! ```
221//!
222//! This example may have appeared somewhat contrived, but hopefully it shows the power of the cassowary
223//! algorithm for laying out user interfaces.
224//!
225//! One thing that this example exposes is that this crate is a rather low level library. It does not have
226//! any inherent knowledge of user interfaces, directions or boxes. Thus for use in a user interface this
227//! crate should ideally be wrapped by a higher level API, which is outside the scope of this crate.
228use std::sync::Arc;
229use std::collections::HashMap;
230use std::collections::hash_map::{Entry};
231
232mod solver_impl;
233mod operators;
234
235static VARIABLE_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
236
237/// Identifies a variable for the constraint solver.
238/// Each new variable is unique in the view of the solver, but copying or cloning the variable produces
239/// a copy of the same variable.
240#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
241pub struct Variable(usize);
242
243impl Variable {
244    /// Produces a new unique variable for use in constraint solving.
245    pub fn new() -> Variable {
246        Variable(VARIABLE_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed))
247    }
248}
249
250/// A variable and a coefficient to multiply that variable by. This is a sub-expression in
251/// a constraint equation.
252#[derive(Copy, Clone, Debug)]
253pub struct Term {
254    pub variable: Variable,
255    pub coefficient: f64
256}
257
258impl Term {
259    /// Construct a new Term from a variable and a coefficient.
260    fn new(variable: Variable, coefficient: f64) -> Term {
261        Term {
262            variable: variable,
263            coefficient: coefficient
264        }
265    }
266}
267
268/// An expression that can be the left hand or right hand side of a constraint equation.
269/// It is a linear combination of variables, i.e. a sum of variables weighted by coefficients, plus an optional constant.
270#[derive(Clone, Debug)]
271pub struct Expression {
272    pub terms: Vec<Term>,
273    pub constant: f64
274}
275
276impl Expression {
277    /// Constructs an expression of the form _n_, where n is a constant real number, not a variable.
278    pub fn from_constant(v: f64) -> Expression {
279        Expression {
280            terms: Vec::new(),
281            constant: v
282        }
283    }
284    /// Constructs an expression from a single term. Forms an expression of the form _n x_
285    /// where n is the coefficient, and x is the variable.
286    pub fn from_term(term: Term) -> Expression {
287        Expression {
288            terms: vec![term],
289            constant: 0.0
290        }
291    }
292    /// General constructor. Each `Term` in `terms` is part of the sum forming the expression, as well as `constant`.
293    pub fn new(terms: Vec<Term>, constant: f64) -> Expression {
294        Expression {
295            terms: terms,
296            constant: constant
297        }
298    }
299    /// Mutates this expression by multiplying it by minus one.
300    pub fn negate(&mut self) {
301        self.constant = -self.constant;
302        for t in &mut self.terms {
303            *t = -*t;
304        }
305    }
306}
307
308impl From<f64> for Expression {
309    fn from(v: f64) -> Expression {
310        Expression::from_constant(v)
311    }
312}
313
314impl From<Variable> for Expression {
315    fn from(v: Variable) -> Expression {
316        Expression::from_term(Term::new(v, 1.0))
317    }
318}
319
320impl From<Term> for Expression {
321    fn from(t: Term) -> Expression {
322        Expression::from_term(t)
323    }
324}
325
326/// Contains useful constants and functions for producing strengths for use in the constraint solver.
327/// Each constraint added to the solver has an associated strength specifying the precedence the solver should
328/// impose when choosing which constraints to enforce. It will try to enforce all constraints, but if that
329/// is impossible the lowest strength constraints are the first to be violated.
330///
331/// Strengths are simply real numbers. The strongest legal strength is 1,001,001,000.0. The weakest is 0.0.
332/// For convenience constants are declared for commonly used strengths. These are `REQUIRED`, `STRONG`,
333/// `MEDIUM` and `WEAK`. Feel free to multiply these by other values to get intermediate strengths.
334/// Note that the solver will clip given strengths to the legal range.
335///
336/// `REQUIRED` signifies a constraint that cannot be violated under any circumstance. Use this special strength
337/// sparingly, as the solver will fail completely if it find that not all of the `REQUIRED` constraints
338/// can be satisfied. The other strengths represent fallible constraints. These should be the most
339/// commonly used strenghts for use cases where violating a constraint is acceptable or even desired.
340///
341/// The solver will try to get as close to satisfying the constraints it violates as possible, strongest first.
342/// This behaviour can be used (for example) to provide a "default" value for a variable should no other
343/// stronger constraints be put upon it.
344pub mod strength {
345    /// Create a constraint as a linear combination of STRONG, MEDIUM and WEAK strengths, corresponding to `a`
346    /// `b` and `c` respectively. The result is further multiplied by `w`.
347    pub fn create(a: f64, b: f64, c: f64, w: f64) -> f64 {
348        (a * w).max(0.0).min(1000.0) * 1_000_000.0 +
349            (b * w).max(0.0).min(1000.0) * 1000.0 +
350            (c * w).max(0.0).min(1000.0)
351    }
352    pub const REQUIRED: f64 = 1_001_001_000.0;
353    pub const STRONG: f64 = 1_000_000.0;
354    pub const MEDIUM: f64 = 1_000.0;
355    pub const WEAK: f64 = 1.0;
356
357    /// Clips a strength value to the legal range
358    pub fn clip(s: f64) -> f64 {
359        s.min(REQUIRED).max(0.0)
360    }
361}
362
363/// The possible relations that a constraint can specify.
364#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
365pub enum RelationalOperator {
366    /// `<=`
367    LessOrEqual,
368    /// `==`
369    Equal,
370    /// `>=`
371    GreaterOrEqual
372}
373
374impl std::fmt::Display for RelationalOperator {
375    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
376        match *self {
377            RelationalOperator::LessOrEqual => write!(fmt, "<=") ?,
378            RelationalOperator::Equal => write!(fmt, "==") ?,
379            RelationalOperator::GreaterOrEqual => write!(fmt, ">=") ?,
380        };
381        Ok(())
382    }
383}
384
385#[derive(Debug)]
386struct ConstraintData {
387    expression: Expression,
388    strength: f64,
389    op: RelationalOperator
390}
391
392/// A constraint, consisting of an equation governed by an expression and a relational operator,
393/// and an associated strength.
394#[derive(Clone, Debug)]
395pub struct Constraint(Arc<ConstraintData>);
396
397impl Constraint {
398    /// Construct a new constraint from an expression, a relational operator and a strength.
399    /// This corresponds to the equation `e op 0.0`, e.g. `x + y >= 0.0`. For equations with a non-zero
400    /// right hand side, subtract it from the equation to give a zero right hand side.
401    pub fn new(e: Expression, op: RelationalOperator, strength: f64) -> Constraint {
402        Constraint(Arc::new(ConstraintData {
403            expression: e,
404            op: op,
405            strength: strength
406        }))
407    }
408    /// The expression of the left hand side of the constraint equation.
409    pub fn expr(&self) -> &Expression {
410        &self.0.expression
411    }
412    /// The relational operator governing the constraint.
413    pub fn op(&self) -> RelationalOperator {
414        self.0.op
415    }
416    /// The strength of the constraint that the solver will use.
417    pub fn strength(&self) -> f64 {
418        self.0.strength
419    }
420}
421
422impl ::std::hash::Hash for Constraint {
423    fn hash<H: ::std::hash::Hasher>(&self, hasher: &mut H) {
424        use ::std::ops::Deref;
425        hasher.write_usize(self.0.deref() as *const _ as usize);
426    }
427}
428
429impl PartialEq for Constraint {
430    fn eq(&self, other: &Constraint) -> bool {
431        use ::std::ops::Deref;
432        self.0.deref() as *const _ == other.0.deref() as *const _
433    }
434}
435
436impl Eq for Constraint {}
437
438/// This is part of the syntactic sugar used for specifying constraints. This enum should be used as part of a
439/// constraint expression. See the module documentation for more information.
440pub enum WeightedRelation {
441    /// `==`
442    EQ(f64),
443    /// `<=`
444    LE(f64),
445    /// `>=`
446    GE(f64)
447}
448impl From<WeightedRelation> for (RelationalOperator, f64) {
449    fn from(r: WeightedRelation) -> (RelationalOperator, f64) {
450        use WeightedRelation::*;
451        match r {
452            EQ(s) => (RelationalOperator::Equal, s),
453            LE(s) => (RelationalOperator::LessOrEqual, s),
454            GE(s) => (RelationalOperator::GreaterOrEqual, s),
455        }
456    }
457}
458
459/// This is an intermediate type used in the syntactic sugar for specifying constraints. You should not use it
460/// directly.
461pub struct PartialConstraint(Expression, WeightedRelation);
462
463#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
464enum SymbolType {
465    Invalid,
466    External,
467    Slack,
468    Error,
469    Dummy
470}
471
472#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
473struct Symbol(usize, SymbolType);
474
475impl Symbol {
476    fn invalid() -> Symbol { Symbol(0, SymbolType::Invalid) }
477    fn type_(&self) -> SymbolType { self.1 }
478}
479
480#[derive(Clone)]
481struct Row {
482    cells: HashMap<Symbol, f64>,
483    constant: f64
484}
485
486fn near_zero(value: f64) -> bool {
487    const EPS: f64 = 1E-8;
488    if value < 0.0 {
489        -value < EPS
490    } else {
491        value < EPS
492    }
493}
494
495impl Row {
496    fn new(constant: f64) -> Row {
497        Row {
498            cells: HashMap::new(),
499            constant: constant
500        }
501    }
502    fn add(&mut self, v: f64) -> f64 {
503        self.constant += v;
504        self.constant
505    }
506    fn insert_symbol(&mut self, s: Symbol, coefficient: f64) {
507        match self.cells.entry(s) {
508            Entry::Vacant(entry) => if !near_zero(coefficient) {
509                entry.insert(coefficient);
510            },
511            Entry::Occupied(mut entry) => {
512                *entry.get_mut() += coefficient;
513                if near_zero(*entry.get_mut()) {
514                    entry.remove();
515                }
516            }
517        }
518    }
519
520    fn insert_row(&mut self, other: &Row, coefficient: f64) -> bool {
521        let constant_diff = other.constant * coefficient;
522        self.constant += constant_diff;
523        for (s, v) in &other.cells {
524            self.insert_symbol(*s, v * coefficient);
525        }
526        constant_diff != 0.0
527    }
528
529    fn remove(&mut self, s: Symbol) {
530        self.cells.remove(&s);
531    }
532
533    fn reverse_sign(&mut self) {
534        self.constant = -self.constant;
535        for (_, v) in &mut self.cells {
536            *v = -*v;
537        }
538    }
539
540    fn solve_for_symbol(&mut self, s: Symbol) {
541        let coeff = -1.0 / match self.cells.entry(s) {
542            Entry::Occupied(entry) => entry.remove(),
543            Entry::Vacant(_) => unreachable!()
544        };
545        self.constant *= coeff;
546        for (_, v) in &mut self.cells {
547            *v *= coeff;
548        }
549    }
550
551    fn solve_for_symbols(&mut self, lhs: Symbol, rhs: Symbol) {
552        self.insert_symbol(lhs, -1.0);
553        self.solve_for_symbol(rhs);
554    }
555
556    fn coefficient_for(&self, s: Symbol) -> f64 {
557        self.cells.get(&s).cloned().unwrap_or(0.0)
558    }
559
560    fn substitute(&mut self, s: Symbol, row: &Row) -> bool {
561        if let Some(coeff) = self.cells.remove(&s) {
562            self.insert_row(row, coeff)
563        } else {
564            false
565        }
566    }
567}
568
569/// The possible error conditions that `Solver::add_constraint` can fail with.
570#[derive(Debug, Copy, Clone)]
571pub enum AddConstraintError {
572    /// The constraint specified has already been added to the solver.
573    DuplicateConstraint,
574    /// The constraint is required, but it is unsatisfiable in conjunction with the existing constraints.
575    UnsatisfiableConstraint,
576    /// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
577    /// additional details as a string.
578    InternalSolverError(&'static str)
579}
580
581/// The possible error conditions that `Solver::remove_constraint` can fail with.
582#[derive(Debug, Copy, Clone)]
583pub enum RemoveConstraintError {
584    /// The constraint specified was not already in the solver, so cannot be removed.
585    UnknownConstraint,
586    /// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
587    /// additional details as a string.
588    InternalSolverError(&'static str)
589}
590
591/// The possible error conditions that `Solver::add_edit_variable` can fail with.
592#[derive(Debug, Copy, Clone)]
593pub enum AddEditVariableError {
594    /// The specified variable is already marked as an edit variable in the solver.
595    DuplicateEditVariable,
596    /// The specified strength was `REQUIRED`. This is illegal for edit variable strengths.
597    BadRequiredStrength
598}
599
600/// The possible error conditions that `Solver::remove_edit_variable` can fail with.
601#[derive(Debug, Copy, Clone)]
602pub enum RemoveEditVariableError {
603    /// The specified variable was not an edit variable in the solver, so cannot be removed.
604    UnknownEditVariable,
605    /// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
606    /// additional details as a string.
607    InternalSolverError(&'static str)
608}
609
610/// The possible error conditions that `Solver::suggest_value` can fail with.
611#[derive(Debug, Copy, Clone)]
612pub enum SuggestValueError {
613    /// The specified variable was not an edit variable in the solver, so cannot have its value suggested.
614    UnknownEditVariable,
615    /// The solver entered an invalid state. If this occurs please report the issue. This variant specifies
616    /// additional details as a string.
617    InternalSolverError(&'static str)
618}
619
620#[derive(Debug, Copy, Clone)]
621struct InternalSolverError(&'static str);
622
623pub use solver_impl::Solver;