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;