cassowary-0.3.0/.gitignore 0000644 0000000 0000000 00000000043 13156036206 0013702 0 ustar 00 0000000 0000000 target
Cargo.lock
/.idea/
/*.iml cassowary-0.3.0/.travis.yml 0000644 0000000 0000000 00000002174 13156036206 0014032 0 ustar 00 0000000 0000000 language: rust
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
cargo doc --no-deps &&
echo "" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import &&
./ghp-import/ghp_import.py target/doc &&
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
env:
global:
secure: sNoiSPMBWfD++mEIcCnrYQBJOETZ/MrHGU3Ft6ByFgDGMN1zG4SvrS2/nmjbnjNPEfv8rEN5mRe6NDUg4AGWGZkmKmA3DXKAZ204pXxA+w9atxqUjHYF8rxbiKqZEVJf+VCwDLmu8+sIOllz4OEXngd2ORGKYJOjhcHy6c1LWIX8bwF9UViasYBbqA8TmKRRo2xNN8Yp0Xphl6XsqjE36Z2wu31Aj1mJGLS3w5+HGBOVSJMG2zZhDQhsMDMPDutV6P9dWL2f24Cmiinr062nujB9aOjUSn2GMvGZLBYyE2fmmaUqgam8kYGLee7URMCLScSOV0sOorjdXUz0TOgzr5tn6T/x5kLcc2SmGWvLfh5jsSbn2i44oqodSZQZuQF765nHZfNCjmy4wWaIz9QyoxUIebbC50Wi4e3H6Xf1Ms5eFfOz6WpH95BCc3VAPN7Nc7mjsddFO6xZkuo/duDO6v1pA7gtyeX80qsE2IfOq9dF046vwMsxgmAEzOxXSkHbXWLtdL/KsFr8shy5hZ4C+u5NIAqP2GgimJlwel+Tz1P0qkI53hD9mBH6Z11v733JsqpGhsrP3QJXq0gaczPhYDlVDTww5nSZjvsAwEABXwk/ZzXk1+WKlP3nyPsVJ/YD0/aPlP9vJd3vcGY3yJYiSjxve0CkRNxTF3gRZzi+TMA=
cassowary-0.3.0/Cargo.toml.orig 0000644 0000000 0000000 00000001237 13156306117 0014610 0 ustar 00 0000000 0000000 [package]
name = "cassowary"
version = "0.3.0"
authors = ["Dylan Ede "]
description = """
A Rust implementation of the Cassowary linear constraint solving algorithm.
The Cassowary algorithm is designed for naturally laying out user interfaces using linear constraints,
like 'this button must line up with this text box'.
"""
documentation = "https://dylanede.github.io/cassowary-rs"
homepage = "https://github.com/dylanede/cassowary-rs"
repository = "https://github.com/dylanede/cassowary-rs"
readme = "README.md"
license = "MIT / Apache-2.0"
keywords = ["constraint", "simplex", "user", "interface", "layout"]
[dependencies]
cassowary-0.3.0/Cargo.toml 0000644 00000002230 0010061 0 ustar 00 # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g. crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
[package]
name = "cassowary"
version = "0.3.0"
authors = ["Dylan Ede "]
description = "A Rust implementation of the Cassowary linear constraint solving algorithm.\n\nThe Cassowary algorithm is designed for naturally laying out user interfaces using linear constraints,\nlike 'this button must line up with this text box'.\n"
homepage = "https://github.com/dylanede/cassowary-rs"
documentation = "https://dylanede.github.io/cassowary-rs"
readme = "README.md"
keywords = ["constraint", "simplex", "user", "interface", "layout"]
license = "MIT / Apache-2.0"
repository = "https://github.com/dylanede/cassowary-rs"
[dependencies]
cassowary-0.3.0/CHANGELOG.md 0000644 0000000 0000000 00000001053 13156306510 0013523 0 ustar 00 0000000 0000000 # 0.3
* Various fixes (PR #4) from @christolliday.
Main breaking change is that variables no longer silently initialise to zero and will report
their initial value in the first call to `fetch_changes`, also `has_edit_variable` now takes `&self`
instead of `&mut self`.
## 0.2.1
* Fixed crash under certain use cases. See PR #1 (Thanks @christolliday!).
# 0.2.0
* Changed API to only report changes to the values of variables. This allows for more efficient use of the library in typical applications.
# 0.1
Initial release
cassowary-0.3.0/LICENSE-APACHE 0000644 0000000 0000000 00000026446 13156036206 0013655 0 ustar 00 0000000 0000000 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
cassowary-0.3.0/LICENSE-MIT 0000644 0000000 0000000 00000002111 13156036206 0013344 0 ustar 00 0000000 0000000 The MIT License (MIT)
Copyright (c) 2016 Dylan Ede
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
cassowary-0.3.0/README.md 0000644 0000000 0000000 00000004106 13156306117 0013176 0 ustar 00 0000000 0000000 # cassowary-rs
[](https://travis-ci.org/dylanede/cassowary-rs)
This is a Rust implementation of the Cassowary constraint solving algorithm
([Badros et. al 2001](https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf)).
It is based heavily on the implementation for C++ at
[nucleic/kiwi](https://github.com/nucleic/kiwi). The implementation does
however differ in some details.
Cassowary is designed for solving constraints to lay out user interfaces.
Constraints typically take the form "this button must line up with this
text box", or "this box should try to be 3 times the size of this other box".
Its most popular incarnation by far is in Apple's Autolayout
system for Mac OS X and iOS user interfaces. UI libraries using the Cassowary
algorithm manage to achieve a much more natural approach to specifying UI
layouts than traditional approaches like those found in HTML.
This library is a low level interface to the solving algorithm, though it
tries to be as convenient as possible. As a result it does not have any
intrinsic knowledge of common user interface conventions like rectangular
regions or even two dimensions. These abstractions belong in a higher level
crate.
For more information, please read
**[the documentation](https://dylanede.github.io/cassowary-rs)**.
## Getting Started
Add the following to your Cargo.toml:
```toml
[dependencies]
cassowary = "^0.3.0"
```
Please read the documentation (linked above) for how to best use this crate.
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
cassowary-0.3.0/src/lib.rs 0000644 0000000 0000000 00000055276 13156306557 0013650 0 ustar 00 0000000 0000000 //! 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, 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, 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,
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, 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 for Expression {
fn from(v: f64) -> Expression {
Expression::from_constant(v)
}
}
impl From for Expression {
fn from(v: Variable) -> Expression {
Expression::from_term(Term::new(v, 1.0))
}
}
impl From 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);
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(&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 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,
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;
cassowary-0.3.0/src/operators.rs 0000644 0000000 0000000 00000031170 13156306557 0015103 0 ustar 00 0000000 0000000 use ::std::ops;
use {
Term,
Variable,
Expression,
WeightedRelation,
PartialConstraint,
Constraint
};
// Relation
impl ops::BitOr for f64 {
type Output = PartialConstraint;
fn bitor(self, r: WeightedRelation) -> PartialConstraint {
PartialConstraint(self.into(), r)
}
}
impl ops::BitOr for f32 {
type Output = PartialConstraint;
fn bitor(self, r: WeightedRelation) -> PartialConstraint {
(self as f64).bitor(r)
}
}
impl ops::BitOr for Variable {
type Output = PartialConstraint;
fn bitor(self, r: WeightedRelation) -> PartialConstraint {
PartialConstraint(self.into(), r)
}
}
impl ops::BitOr for Term {
type Output = PartialConstraint;
fn bitor(self, r: WeightedRelation) -> PartialConstraint {
PartialConstraint(self.into(), r)
}
}
impl ops::BitOr for Expression {
type Output = PartialConstraint;
fn bitor(self, r: WeightedRelation) -> PartialConstraint {
PartialConstraint(self.into(), r)
}
}
impl ops::BitOr for PartialConstraint {
type Output = Constraint;
fn bitor(self, rhs: f64) -> Constraint {
let (op, s) = self.1.into();
Constraint::new(self.0 - rhs, op, s)
}
}
impl ops::BitOr for PartialConstraint {
type Output = Constraint;
fn bitor(self, rhs: f32) -> Constraint {
self.bitor(rhs as f64)
}
}
impl ops::BitOr for PartialConstraint {
type Output = Constraint;
fn bitor(self, rhs: Variable) -> Constraint {
let (op, s) = self.1.into();
Constraint::new(self.0 - rhs, op, s)
}
}
impl ops::BitOr for PartialConstraint {
type Output = Constraint;
fn bitor(self, rhs: Term) -> Constraint {
let (op, s) = self.1.into();
Constraint::new(self.0 - rhs, op, s)
}
}
impl ops::BitOr for PartialConstraint {
type Output = Constraint;
fn bitor(self, rhs: Expression) -> Constraint {
let (op, s) = self.1.into();
Constraint::new(self.0 - rhs, op, s)
}
}
// Variable
impl ops::Add for Variable {
type Output = Expression;
fn add(self, v: f64) -> Expression {
Expression::new(vec![Term::new(self, 1.0)], v)
}
}
impl ops::Add for Variable {
type Output = Expression;
fn add(self, v: f32) -> Expression {
self.add(v as f64)
}
}
impl ops::Add for f64 {
type Output = Expression;
fn add(self, v: Variable) -> Expression {
Expression::new(vec![Term::new(v, 1.0)], self)
}
}
impl ops::Add for f32 {
type Output = Expression;
fn add(self, v: Variable) -> Expression {
(self as f64).add(v)
}
}
impl ops::Add for Variable {
type Output = Expression;
fn add(self, v: Variable) -> Expression {
Expression::new(vec![Term::new(self, 1.0), Term::new(v, 1.0)], 0.0)
}
}
impl ops::Add for Variable {
type Output = Expression;
fn add(self, t: Term) -> Expression {
Expression::new(vec![Term::new(self, 1.0), t], 0.0)
}
}
impl ops::Add for Term {
type Output = Expression;
fn add(self, v: Variable) -> Expression {
Expression::new(vec![self, Term::new(v, 1.0)], 0.0)
}
}
impl ops::Add for Variable {
type Output = Expression;
fn add(self, mut e: Expression) -> Expression {
e.terms.push(Term::new(self, 1.0));
e
}
}
impl ops::Add for Expression {
type Output = Expression;
fn add(mut self, v: Variable) -> Expression {
self.terms.push(Term::new(v, 1.0));
self
}
}
impl ops::Neg for Variable {
type Output = Term;
fn neg(self) -> Term {
Term::new(self, -1.0)
}
}
impl ops::Sub for Variable {
type Output = Expression;
fn sub(self, v: f64) -> Expression {
Expression::new(vec![Term::new(self, 1.0)], -v)
}
}
impl ops::Sub for Variable {
type Output = Expression;
fn sub(self, v: f32) -> Expression {
self.sub(v as f64)
}
}
impl ops::Sub for f64 {
type Output = Expression;
fn sub(self, v: Variable) -> Expression {
Expression::new(vec![Term::new(v, -1.0)], self)
}
}
impl ops::Sub for f32 {
type Output = Expression;
fn sub(self, v: Variable) -> Expression {
(self as f64).sub(v)
}
}
impl ops::Sub for Variable {
type Output = Expression;
fn sub(self, v: Variable) -> Expression {
Expression::new(vec![Term::new(self, 1.0), Term::new(v, -1.0)], 0.0)
}
}
impl ops::Sub for Variable {
type Output = Expression;
fn sub(self, t: Term) -> Expression {
Expression::new(vec![Term::new(self, 1.0), -t], 0.0)
}
}
impl ops::Sub for Term {
type Output = Expression;
fn sub(self, v: Variable) -> Expression {
Expression::new(vec![self, Term::new(v, -1.0)], 0.0)
}
}
impl ops::Sub for Variable {
type Output = Expression;
fn sub(self, mut e: Expression) -> Expression {
e.negate();
e.terms.push(Term::new(self, 1.0));
e
}
}
impl ops::Sub for Expression {
type Output = Expression;
fn sub(mut self, v: Variable) -> Expression {
self.terms.push(Term::new(v, -1.0));
self
}
}
impl ops::Mul for Variable {
type Output = Term;
fn mul(self, v: f64) -> Term {
Term::new(self, v)
}
}
impl ops::Mul for Variable {
type Output = Term;
fn mul(self, v: f32) -> Term {
self.mul(v as f64)
}
}
impl ops::Mul for f64 {
type Output = Term;
fn mul(self, v: Variable) -> Term {
Term::new(v, self)
}
}
impl ops::Mul for f32 {
type Output = Term;
fn mul(self, v: Variable) -> Term {
(self as f64).mul(v)
}
}
impl ops::Div for Variable {
type Output = Term;
fn div(self, v: f64) -> Term {
Term::new(self, 1.0 / v)
}
}
impl ops::Div for Variable {
type Output = Term;
fn div(self, v: f32) -> Term {
self.div(v as f64)
}
}
// Term
impl ops::Mul for Term {
type Output = Term;
fn mul(mut self, v: f64) -> Term {
self.coefficient *= v;
self
}
}
impl ops::Mul for Term {
type Output = Term;
fn mul(self, v: f32) -> Term {
self.mul(v as f64)
}
}
impl ops::Mul for f64 {
type Output = Term;
fn mul(self, mut t: Term) -> Term {
t.coefficient *= self;
t
}
}
impl ops::Mul for f32 {
type Output = Term;
fn mul(self, t: Term) -> Term {
(self as f64).mul(t)
}
}
impl ops::Div for Term {
type Output = Term;
fn div(mut self, v: f64) -> Term {
self.coefficient /= v;
self
}
}
impl ops::Div for Term {
type Output = Term;
fn div(self, v: f32) -> Term {
self.div(v as f64)
}
}
impl ops::Add for Term {
type Output = Expression;
fn add(self, v: f64) -> Expression {
Expression::new(vec![self], v)
}
}
impl ops::Add for Term {
type Output = Expression;
fn add(self, v: f32) -> Expression {
self.add(v as f64)
}
}
impl ops::Add for f64 {
type Output = Expression;
fn add(self, t: Term) -> Expression {
Expression::new(vec![t], self)
}
}
impl ops::Add for f32 {
type Output = Expression;
fn add(self, t: Term) -> Expression {
(self as f64).add(t)
}
}
impl ops::Add for Term {
type Output = Expression;
fn add(self, t: Term) -> Expression {
Expression::new(vec![self, t], 0.0)
}
}
impl ops::Add for Term {
type Output = Expression;
fn add(self, mut e: Expression) -> Expression {
e.terms.push(self);
e
}
}
impl ops::Add for Expression {
type Output = Expression;
fn add(mut self, t: Term) -> Expression {
self.terms.push(t);
self
}
}
impl ops::Neg for Term {
type Output = Term;
fn neg(mut self) -> Term {
self.coefficient = -self.coefficient;
self
}
}
impl ops::Sub for Term {
type Output = Expression;
fn sub(self, v: f64) -> Expression {
Expression::new(vec![self], -v)
}
}
impl ops::Sub for Term {
type Output = Expression;
fn sub(self, v: f32) -> Expression {
self.sub(v as f64)
}
}
impl ops::Sub for f64 {
type Output = Expression;
fn sub(self, t: Term) -> Expression {
Expression::new(vec![-t], self)
}
}
impl ops::Sub for f32 {
type Output = Expression;
fn sub(self, t: Term) -> Expression {
(self as f64).sub(t)
}
}
impl ops::Sub for Term {
type Output = Expression;
fn sub(self, t: Term) -> Expression {
Expression::new(vec![self, -t], 0.0)
}
}
impl ops::Sub for Term {
type Output = Expression;
fn sub(self, mut e: Expression) -> Expression {
e.negate();
e.terms.push(self);
e
}
}
impl ops::Sub for Expression {
type Output = Expression;
fn sub(mut self, t: Term) -> Expression {
self.terms.push(-t);
self
}
}
// Expression
impl ops::Mul for Expression {
type Output = Expression;
fn mul(mut self, v: f64) -> Expression {
self.constant *= v;
for t in &mut self.terms {
*t = *t * v;
}
self
}
}
impl ops::Mul for Expression {
type Output = Expression;
fn mul(self, v: f32) -> Expression {
self.mul(v as f64)
}
}
impl ops::Mul for f64 {
type Output = Expression;
fn mul(self, mut e: Expression) -> Expression {
e.constant *= self;
for t in &mut e.terms {
*t = *t * self;
}
e
}
}
impl ops::Mul for f32 {
type Output = Expression;
fn mul(self, e: Expression) -> Expression {
(self as f64).mul(e)
}
}
impl ops::Div for Expression {
type Output = Expression;
fn div(mut self, v: f64) -> Expression {
self.constant /= v;
for t in &mut self.terms {
*t = *t / v;
}
self
}
}
impl ops::Div for Expression {
type Output = Expression;
fn div(self, v: f32) -> Expression {
self.div(v as f64)
}
}
impl ops::Add for Expression {
type Output = Expression;
fn add(mut self, v: f64) -> Expression {
self.constant += v;
self
}
}
impl ops::Add for Expression {
type Output = Expression;
fn add(self, v: f32) -> Expression {
self.add(v as f64)
}
}
impl ops::Add for f64 {
type Output = Expression;
fn add(self, mut e: Expression) -> Expression {
e.constant += self;
e
}
}
impl ops::Add for f32 {
type Output = Expression;
fn add(self, e: Expression) -> Expression {
(self as f64).add(e)
}
}
impl ops::Add for Expression {
type Output = Expression;
fn add(mut self, mut e: Expression) -> Expression {
self.terms.append(&mut e.terms);
self.constant += e.constant;
self
}
}
impl ops::Neg for Expression {
type Output = Expression;
fn neg(mut self) -> Expression {
self.negate();
self
}
}
impl ops::Sub for Expression {
type Output = Expression;
fn sub(mut self, v: f64) -> Expression {
self.constant -= v;
self
}
}
impl ops::Sub for Expression {
type Output = Expression;
fn sub(self, v: f32) -> Expression {
self.sub(v as f64)
}
}
impl ops::Sub for f64 {
type Output = Expression;
fn sub(self, mut e: Expression) -> Expression {
e.negate();
e.constant += self;
e
}
}
impl ops::Sub for f32 {
type Output = Expression;
fn sub(self, e: Expression) -> Expression {
(self as f64).sub(e)
}
}
impl ops::Sub for Expression {
type Output = Expression;
fn sub(mut self, mut e: Expression) -> Expression {
e.negate();
self.terms.append(&mut e.terms);
self.constant += e.constant;
self
}
}
cassowary-0.3.0/src/solver_impl.rs 0000644 0000000 0000000 00000077063 13156306557 0015433 0 ustar 00 0000000 0000000 use {
Symbol,
SymbolType,
Constraint,
Variable,
Expression,
Term,
Row,
AddConstraintError,
RemoveConstraintError,
InternalSolverError,
SuggestValueError,
AddEditVariableError,
RemoveEditVariableError,
RelationalOperator,
near_zero
};
use ::std::rc::Rc;
use ::std::cell::RefCell;
use ::std::collections::{ HashMap, HashSet };
use ::std::collections::hash_map::Entry;
#[derive(Copy, Clone)]
struct Tag {
marker: Symbol,
other: Symbol
}
#[derive(Clone)]
struct EditInfo {
tag: Tag,
constraint: Constraint,
constant: f64
}
/// A constraint solver using the Cassowary algorithm. For proper usage please see the top level crate documentation.
pub struct Solver {
cns: HashMap,
var_data: HashMap,
var_for_symbol: HashMap,
public_changes: Vec<(Variable, f64)>,
changed: HashSet,
should_clear_changes: bool,
rows: HashMap>,
edits: HashMap,
infeasible_rows: Vec, // never contains external symbols
objective: Rc>,
artificial: Option>>,
id_tick: usize
}
impl Solver {
/// Construct a new solver.
pub fn new() -> Solver {
Solver {
cns: HashMap::new(),
var_data: HashMap::new(),
var_for_symbol: HashMap::new(),
public_changes: Vec::new(),
changed: HashSet::new(),
should_clear_changes: false,
rows: HashMap::new(),
edits: HashMap::new(),
infeasible_rows: Vec::new(),
objective: Rc::new(RefCell::new(Row::new(0.0))),
artificial: None,
id_tick: 1
}
}
pub fn add_constraints<'a, I: IntoIterator- >(
&mut self,
constraints: I) -> Result<(), AddConstraintError>
{
for constraint in constraints {
try!(self.add_constraint(constraint.clone()));
}
Ok(())
}
/// Add a constraint to the solver.
pub fn add_constraint(&mut self, constraint: Constraint) -> Result<(), AddConstraintError> {
if self.cns.contains_key(&constraint) {
return Err(AddConstraintError::DuplicateConstraint);
}
// Creating a row causes symbols to 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.
let (mut row, tag) = self.create_row(&constraint);
let mut subject = Solver::choose_subject(&row, &tag);
// If chooseSubject could 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_() == SymbolType::Invalid && Solver::all_dummies(&row) {
if !near_zero(row.constant) {
return Err(AddConstraintError::UnsatisfiableConstraint);
} else {
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_() == SymbolType::Invalid {
if !try!(self.add_with_artificial_variable(&row)
.map_err(|e| AddConstraintError::InternalSolverError(e.0))) {
return Err(AddConstraintError::UnsatisfiableConstraint);
}
} else {
row.solve_for_symbol(subject);
self.substitute(subject, &row);
if subject.type_() == SymbolType::External && row.constant != 0.0 {
let v = self.var_for_symbol[&subject];
self.var_changed(v);
}
self.rows.insert(subject, row);
}
self.cns.insert(constraint, 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.
let objective = self.objective.clone();
try!(self.optimise(&objective).map_err(|e| AddConstraintError::InternalSolverError(e.0)));
Ok(())
}
/// Remove a constraint from the solver.
pub fn remove_constraint(&mut self, constraint: &Constraint) -> Result<(), RemoveConstraintError> {
let tag = try!(self.cns.remove(constraint).ok_or(RemoveConstraintError::UnknownConstraint));
// Remove the error effects from the objective function
// *before* pivoting, or substitutions into the objective
// will lead to incorrect solver results.
self.remove_constraint_effects(constraint, &tag);
// If the marker is basic, simply drop the row. Otherwise,
// pivot the marker into the basis and then drop the row.
if let None = self.rows.remove(&tag.marker) {
let (leaving, mut row) = try!(self.get_marker_leaving_row(tag.marker)
.ok_or(
RemoveConstraintError::InternalSolverError(
"Failed to find leaving row.")));
row.solve_for_symbols(leaving, tag.marker);
self.substitute(tag.marker, &row);
}
// 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.
let objective = self.objective.clone();
try!(self.optimise(&objective).map_err(|e| RemoveConstraintError::InternalSolverError(e.0)));
// Check for and decrease the reference count for variables referenced by the constraint
// If the reference count is zero remove the variable from the variable map
for term in &constraint.expr().terms {
if !near_zero(term.coefficient) {
let mut should_remove = false;
if let Some(&mut (_, _, ref mut count)) = self.var_data.get_mut(&term.variable) {
*count -= 1;
should_remove = *count == 0;
}
if should_remove {
self.var_for_symbol.remove(&self.var_data[&term.variable].1);
self.var_data.remove(&term.variable);
}
}
}
Ok(())
}
/// Test whether a constraint has been added to the solver.
pub fn has_constraint(&self, constraint: &Constraint) -> bool {
self.cns.contains_key(constraint)
}
/// Add an edit variable to the solver.
///
/// This method should be called before the `suggest_value` method is
/// used to supply a suggested value for the given edit variable.
pub fn add_edit_variable(&mut self, v: Variable, strength: f64) -> Result<(), AddEditVariableError> {
if self.edits.contains_key(&v) {
return Err(AddEditVariableError::DuplicateEditVariable);
}
let strength = ::strength::clip(strength);
if strength == ::strength::REQUIRED {
return Err(AddEditVariableError::BadRequiredStrength);
}
let cn = Constraint::new(Expression::from_term(Term::new(v.clone(), 1.0)),
RelationalOperator::Equal,
strength);
self.add_constraint(cn.clone()).unwrap();
self.edits.insert(v.clone(), EditInfo {
tag: self.cns[&cn].clone(),
constraint: cn,
constant: 0.0
});
Ok(())
}
/// Remove an edit variable from the solver.
pub fn remove_edit_variable(&mut self, v: Variable) -> Result<(), RemoveEditVariableError> {
if let Some(constraint) = self.edits.remove(&v).map(|e| e.constraint) {
try!(self.remove_constraint(&constraint)
.map_err(|e| match e {
RemoveConstraintError::UnknownConstraint =>
RemoveEditVariableError::InternalSolverError("Edit constraint not in system"),
RemoveConstraintError::InternalSolverError(s) =>
RemoveEditVariableError::InternalSolverError(s)
}));
Ok(())
} else {
Err(RemoveEditVariableError::UnknownEditVariable)
}
}
/// Test whether an edit variable has been added to the solver.
pub fn has_edit_variable(&self, v: &Variable) -> bool {
self.edits.contains_key(v)
}
/// Suggest a value for the given edit variable.
///
/// This method should be used after an edit variable has been added to
/// the solver in order to suggest the value for that variable.
pub fn suggest_value(&mut self, variable: Variable, value: f64) -> Result<(), SuggestValueError> {
let (info_tag_marker, info_tag_other, delta) = {
let info = try!(self.edits.get_mut(&variable).ok_or(SuggestValueError::UnknownEditVariable));
let delta = value - info.constant;
info.constant = value;
(info.tag.marker, info.tag.other, delta)
};
// tag.marker and tag.other are never external symbols
// The nice version of the following code runs into non-lexical borrow issues.
// Ideally the `if row...` code would be in the body of the if. Pretend that it is.
{
let infeasible_rows = &mut self.infeasible_rows;
if self.rows.get_mut(&info_tag_marker)
.map(|row|
if row.add(-delta) < 0.0 {
infeasible_rows.push(info_tag_marker);
}).is_some()
{
} else if self.rows.get_mut(&info_tag_other)
.map(|row|
if row.add(delta) < 0.0 {
infeasible_rows.push(info_tag_other);
}).is_some()
{
} else {
for (symbol, row) in &mut self.rows {
let coeff = row.coefficient_for(info_tag_marker);
let diff = delta * coeff;
if diff != 0.0 && symbol.type_() == SymbolType::External {
let v = self.var_for_symbol[symbol];
// inline var_changed - borrow checker workaround
if self.should_clear_changes {
self.changed.clear();
self.should_clear_changes = false;
}
self.changed.insert(v);
}
if coeff != 0.0 &&
row.add(diff) < 0.0 &&
symbol.type_() != SymbolType::External
{
infeasible_rows.push(*symbol);
}
}
}
}
try!(self.dual_optimise().map_err(|e| SuggestValueError::InternalSolverError(e.0)));
return Ok(());
}
fn var_changed(&mut self, v: Variable) {
if self.should_clear_changes {
self.changed.clear();
self.should_clear_changes = false;
}
self.changed.insert(v);
}
/// Fetches all changes to the values of variables since the last call to this function.
///
/// The list of changes returned is not in a specific order. Each change comprises the variable changed and
/// the new value of that variable.
pub fn fetch_changes(&mut self) -> &[(Variable, f64)] {
if self.should_clear_changes {
self.changed.clear();
self.should_clear_changes = false;
} else {
self.should_clear_changes = true;
}
self.public_changes.clear();
for &v in &self.changed {
if let Some(var_data) = self.var_data.get_mut(&v) {
let new_value = self.rows.get(&var_data.1).map(|r| r.constant).unwrap_or(0.0);
let old_value = var_data.0;
if old_value != new_value {
self.public_changes.push((v, new_value));
var_data.0 = new_value;
}
}
}
&self.public_changes
}
/// Reset the solver to the empty starting condition.
///
/// This method resets the internal solver state to the empty starting
/// condition, as if no constraints or edit variables have been added.
/// This can be faster than deleting the solver and creating a new one
/// when the entire system must change, since it can avoid unnecessary
/// heap (de)allocations.
pub fn reset(&mut self) {
self.rows.clear();
self.cns.clear();
self.var_data.clear();
self.var_for_symbol.clear();
self.changed.clear();
self.should_clear_changes = false;
self.edits.clear();
self.infeasible_rows.clear();
*self.objective.borrow_mut() = Row::new(0.0);
self.artificial = None;
self.id_tick = 1;
}
/// Get the symbol for the given variable.
///
/// If a symbol does not exist for the variable, one will be created.
fn get_var_symbol(&mut self, v: Variable) -> Symbol {
let id_tick = &mut self.id_tick;
let var_for_symbol = &mut self.var_for_symbol;
let value = self.var_data.entry(v).or_insert_with(|| {
let s = Symbol(*id_tick, SymbolType::External);
var_for_symbol.insert(s, v);
*id_tick += 1;
(::std::f64::NAN, s, 0)
});
value.2 += 1;
value.1
}
/// Create a new Row object for the given constraint.
///
/// The terms in the constraint will be converted to cells in the row.
/// Any term in the constraint with a coefficient of zero is ignored.
/// This method uses the `getVarSymbol` method to get the symbol for
/// the variables added to the row. If the symbol for a given cell
/// variable is basic, the cell variable will be substituted with the
/// basic row.
///
/// The necessary slack and error variables will be added to the row.
/// If the constant for the row is negative, the sign for the row
/// will be inverted so the constant becomes positive.
///
/// The tag will be updated with the marker and error symbols to use
/// for tracking the movement of the constraint in the tableau.
fn create_row(&mut self, constraint: &Constraint) -> (Box
, Tag) {
let expr = constraint.expr();
let mut row = Row::new(expr.constant);
// Substitute the current basic variables into the row.
for term in &expr.terms {
if !near_zero(term.coefficient) {
let symbol = self.get_var_symbol(term.variable);
if let Some(other_row) = self.rows.get(&symbol) {
row.insert_row(other_row, term.coefficient);
} else {
row.insert_symbol(symbol, term.coefficient);
}
}
}
let mut objective = self.objective.borrow_mut();
// Add the necessary slack, error, and dummy variables.
let tag = match constraint.op() {
RelationalOperator::GreaterOrEqual |
RelationalOperator::LessOrEqual => {
let coeff = if constraint.op() == RelationalOperator::LessOrEqual {
1.0
} else {
-1.0
};
let slack = Symbol(self.id_tick, SymbolType::Slack);
self.id_tick += 1;
row.insert_symbol(slack, coeff);
if constraint.strength() < ::strength::REQUIRED {
let error = Symbol(self.id_tick, SymbolType::Error);
self.id_tick += 1;
row.insert_symbol(error, -coeff);
objective.insert_symbol(error, constraint.strength());
Tag {
marker: slack,
other: error
}
} else {
Tag {
marker: slack,
other: Symbol::invalid()
}
}
}
RelationalOperator::Equal => {
if constraint.strength() < ::strength::REQUIRED {
let errplus = Symbol(self.id_tick, SymbolType::Error);
self.id_tick += 1;
let errminus = Symbol(self.id_tick, SymbolType::Error);
self.id_tick += 1;
row.insert_symbol(errplus, -1.0); // v = eplus - eminus
row.insert_symbol(errminus, 1.0); // v - eplus + eminus = 0
objective.insert_symbol(errplus, constraint.strength());
objective.insert_symbol(errminus, constraint.strength());
Tag {
marker: errplus,
other: errminus
}
} else {
let dummy = Symbol(self.id_tick, SymbolType::Dummy);
self.id_tick += 1;
row.insert_symbol(dummy, 1.0);
Tag {
marker: dummy,
other: Symbol::invalid()
}
}
}
};
// Ensure the row has a positive constant.
if row.constant < 0.0 {
row.reverse_sign();
}
(Box::new(row), tag)
}
/// Choose the subject for solving for the row.
///
/// This method will choose the best subject for using as the solve
/// target for the row. An invalid symbol will be returned if there
/// is no valid target.
///
/// The symbols are chosen according to the following precedence:
///
/// 1) The first symbol representing an external variable.
/// 2) A negative slack or error tag variable.
///
/// If a subject cannot be found, an invalid symbol will be returned.
fn choose_subject(row: &Row, tag: &Tag) -> Symbol {
for s in row.cells.keys() {
if s.type_() == SymbolType::External {
return *s
}
}
if tag.marker.type_() == SymbolType::Slack || tag.marker.type_() == SymbolType::Error {
if row.coefficient_for(tag.marker) < 0.0 {
return tag.marker;
}
}
if tag.other.type_() == SymbolType::Slack || tag.other.type_() == SymbolType::Error {
if row.coefficient_for(tag.other) < 0.0 {
return tag.other;
}
}
Symbol::invalid()
}
/// Add the row to the tableau using an artificial variable.
///
/// This will return false if the constraint cannot be satisfied.
fn add_with_artificial_variable(&mut self, row: &Row) -> Result {
// Create and add the artificial variable to the tableau
let art = Symbol(self.id_tick, SymbolType::Slack);
self.id_tick += 1;
self.rows.insert(art, Box::new(row.clone()));
self.artificial = Some(Rc::new(RefCell::new(row.clone())));
// Optimize the artificial objective. This is successful
// only if the artificial objective is optimized to zero.
let artificial = self.artificial.as_ref().unwrap().clone();
try!(self.optimise(&artificial));
let success = near_zero(artificial.borrow().constant);
self.artificial = None;
// If the artificial variable is basic, pivot the row so that
// it becomes basic. If the row is constant, exit early.
if let Some(mut row) = self.rows.remove(&art) {
if row.cells.is_empty() {
return Ok(success);
}
let entering = Solver::any_pivotable_symbol(&row); // never External
if entering.type_() == SymbolType::Invalid {
return Ok(false); // unsatisfiable (will this ever happen?)
}
row.solve_for_symbols(art, entering);
self.substitute(entering, &row);
self.rows.insert(entering, row);
}
// Remove the artificial row from the tableau
for (_, row) in &mut self.rows {
row.remove(art);
}
self.objective.borrow_mut().remove(art);
Ok(success)
}
/// Substitute the parametric symbol with the given row.
///
/// This method will substitute all instances of the parametric symbol
/// in the tableau and the objective function with the given row.
fn substitute(&mut self, symbol: Symbol, row: &Row) {
for (&other_symbol, other_row) in &mut self.rows {
let constant_changed = other_row.substitute(symbol, row);
if other_symbol.type_() == SymbolType::External && constant_changed {
let v = self.var_for_symbol[&other_symbol];
// inline var_changed
if self.should_clear_changes {
self.changed.clear();
self.should_clear_changes = false;
}
self.changed.insert(v);
}
if other_symbol.type_() != SymbolType::External && other_row.constant < 0.0 {
self.infeasible_rows.push(other_symbol);
}
}
self.objective.borrow_mut().substitute(symbol, row);
if let Some(artificial) = self.artificial.as_ref() {
artificial.borrow_mut().substitute(symbol, row);
}
}
/// Optimize the system for the given objective function.
///
/// This method performs iterations of Phase 2 of the simplex method
/// until the objective function reaches a minimum.
fn optimise(&mut self, objective: &RefCell) -> Result<(), InternalSolverError> {
loop {
let entering = Solver::get_entering_symbol(&objective.borrow());
if entering.type_() == SymbolType::Invalid {
return Ok(());
}
let (leaving, mut row) = try!(self.get_leaving_row(entering)
.ok_or(InternalSolverError("The objective is unbounded")));
// pivot the entering symbol into the basis
row.solve_for_symbols(leaving, entering);
self.substitute(entering, &row);
if entering.type_() == SymbolType::External && row.constant != 0.0 {
let v = self.var_for_symbol[&entering];
self.var_changed(v);
}
self.rows.insert(entering, row);
}
}
/// Optimize the system using the dual of the simplex method.
///
/// The current state of the system should be such that the objective
/// function is optimal, but not feasible. This method will perform
/// an iteration of the dual simplex method to make the solution both
/// optimal and feasible.
fn dual_optimise(&mut self) -> Result<(), InternalSolverError> {
while !self.infeasible_rows.is_empty() {
let leaving = self.infeasible_rows.pop().unwrap();
let row = if let Entry::Occupied(entry) = self.rows.entry(leaving) {
if entry.get().constant < 0.0 {
Some(entry.remove())
} else {
None
}
} else {
None
};
if let Some(mut row) = row {
let entering = self.get_dual_entering_symbol(&row);
if entering.type_() == SymbolType::Invalid {
return Err(InternalSolverError("Dual optimise failed."));
}
// pivot the entering symbol into the basis
row.solve_for_symbols(leaving, entering);
self.substitute(entering, &row);
if entering.type_() == SymbolType::External && row.constant != 0.0 {
let v = self.var_for_symbol[&entering];
self.var_changed(v);
}
self.rows.insert(entering, row);
}
}
Ok(())
}
/// Compute the entering variable for a pivot operation.
///
/// This method will return first symbol in the objective function which
/// is non-dummy and has a coefficient less than zero. If no symbol meets
/// the criteria, it means the objective function is at a minimum, and an
/// invalid symbol is returned.
/// Could return an External symbol
fn get_entering_symbol(objective: &Row) -> Symbol {
for (symbol, value) in &objective.cells {
if symbol.type_() != SymbolType::Dummy && *value < 0.0 {
return *symbol;
}
}
Symbol::invalid()
}
/// Compute the entering symbol for the dual optimize operation.
///
/// This method will return the symbol in the row which has a positive
/// coefficient and yields the minimum ratio for its respective symbol
/// in the objective function. The provided row *must* be infeasible.
/// If no symbol is found which meats the criteria, an invalid symbol
/// is returned.
/// Could return an External symbol
fn get_dual_entering_symbol(&self, row: &Row) -> Symbol {
let mut entering = Symbol::invalid();
let mut ratio = ::std::f64::INFINITY;
let objective = self.objective.borrow();
for (symbol, value) in &row.cells {
if *value > 0.0 && symbol.type_() != SymbolType::Dummy {
let coeff = objective.coefficient_for(*symbol);
let r = coeff / *value;
if r < ratio {
ratio = r;
entering = *symbol;
}
}
}
entering
}
/// Get the first Slack or Error symbol in the row.
///
/// If no such symbol is present, and Invalid symbol will be returned.
/// Never returns an External symbol
fn any_pivotable_symbol(row: &Row) -> Symbol {
for symbol in row.cells.keys() {
if symbol.type_() == SymbolType::Slack || symbol.type_() == SymbolType::Error {
return *symbol;
}
}
Symbol::invalid()
}
/// Compute the row which holds the exit symbol for a pivot.
///
/// This method will return an iterator to the row in the row map
/// which holds the exit symbol. If no appropriate exit symbol is
/// found, the end() iterator will be returned. This indicates that
/// the objective function is unbounded.
/// Never returns a row for an External symbol
fn get_leaving_row(&mut self, entering: Symbol) -> Option<(Symbol, Box)> {
let mut ratio = ::std::f64::INFINITY;
let mut found = None;
for (symbol, row) in &self.rows {
if symbol.type_() != SymbolType::External {
let temp = row.coefficient_for(entering);
if temp < 0.0 {
let temp_ratio = -row.constant / temp;
if temp_ratio < ratio {
ratio = temp_ratio;
found = Some(*symbol);
}
}
}
}
found.map(|s| (s, self.rows.remove(&s).unwrap()))
}
/// Compute the leaving row for a marker variable.
///
/// This method will return an iterator to the row in the row map
/// which holds the given marker variable. The row will be chosen
/// according to the following precedence:
///
/// 1) The row with a restricted basic varible and a negative coefficient
/// for the marker with the smallest ratio of -constant / coefficient.
///
/// 2) The row with a restricted basic variable and the smallest ratio
/// of constant / coefficient.
///
/// 3) The last unrestricted row which contains the marker.
///
/// If the marker does not exist in any row, the row map end() iterator
/// will be returned. This indicates an internal solver error since
/// the marker *should* exist somewhere in the tableau.
fn get_marker_leaving_row(&mut self, marker: Symbol) -> Option<(Symbol, Box)> {
let mut r1 = ::std::f64::INFINITY;
let mut r2 = r1;
let mut first = None;
let mut second = None;
let mut third = None;
for (symbol, row) in &self.rows {
let c = row.coefficient_for(marker);
if c == 0.0 {
continue;
}
if symbol.type_() == SymbolType::External {
third = Some(*symbol);
} else if c < 0.0 {
let r = -row.constant / c;
if r < r1 {
r1 = r;
first = Some(*symbol);
}
} else {
let r = row.constant / c;
if r < r2 {
r2 = r;
second = Some(*symbol);
}
}
}
first
.or(second)
.or(third)
.and_then(|s| {
if s.type_() == SymbolType::External && self.rows[&s].constant != 0.0 {
let v = self.var_for_symbol[&s];
self.var_changed(v);
}
self.rows
.remove(&s)
.map(|r| (s, r))
})
}
/// Remove the effects of a constraint on the objective function.
fn remove_constraint_effects(&mut self, cn: &Constraint, tag: &Tag) {
if tag.marker.type_() == SymbolType::Error {
self.remove_marker_effects(tag.marker, cn.strength());
} else if tag.other.type_() == SymbolType::Error {
self.remove_marker_effects(tag.other, cn.strength());
}
}
/// Remove the effects of an error marker on the objective function.
fn remove_marker_effects(&mut self, marker: Symbol, strength: f64) {
if let Some(row) = self.rows.get(&marker) {
self.objective.borrow_mut().insert_row(row, -strength);
} else {
self.objective.borrow_mut().insert_symbol(marker, -strength);
}
}
/// Test whether a row is composed of all dummy variables.
fn all_dummies(row: &Row) -> bool {
for symbol in row.cells.keys() {
if symbol.type_() != SymbolType::Dummy {
return false;
}
}
true
}
/// Get the stored value for a variable.
///
/// Normally values should be retrieved and updated using `fetch_changes`, but
/// this method can be used for debugging or testing.
pub fn get_value(&self, v: Variable) -> f64 {
self.var_data.get(&v).and_then(|s| {
self.rows.get(&s.1).map(|r| r.constant)
}).unwrap_or(0.0)
}
}
cassowary-0.3.0/tests/common/mod.rs 0000644 0000000 0000000 00000001772 13156306557 0015514 0 ustar 00 0000000 0000000 use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
use cassowary::Variable;
#[derive(Clone, Default)]
struct Values(Rc>>);
impl Values {
fn value_of(&self, var: Variable) -> f64 {
*self.0.borrow().get(&var).unwrap_or(&0.0)
}
fn update_values(&self, changes: &[(Variable, f64)]) {
for &(ref var, ref value) in changes {
println!("{:?} changed to {:?}", var, value);
self.0.borrow_mut().insert(*var, *value);
}
}
}
pub fn new_values() -> (Box f64>, Box) {
let values = Values(Rc::new(RefCell::new(HashMap::new())));
let value_of = {
let values = values.clone();
move |v| values.value_of(v)
};
let update_values = {
let values = values.clone();
move |changes: &[_]| {
values.update_values(changes);
}
};
(Box::new(value_of), Box::new(update_values))
} cassowary-0.3.0/tests/quadrilateral.rs 0000644 0000000 0000000 00000007720 13156306557 0016276 0 ustar 00 0000000 0000000 extern crate cassowary;
use cassowary::{ Solver, Variable };
use cassowary::WeightedRelation::*;
mod common;
use common::new_values;
#[test]
fn test_quadrilateral() {
use cassowary::strength::{WEAK, STRONG, REQUIRED};
struct Point {
x: Variable,
y: Variable
}
impl Point {
fn new() -> Point {
Point {
x: Variable::new(),
y: Variable::new()
}
}
}
let (value_of, update_values) = new_values();
let points = [Point::new(),
Point::new(),
Point::new(),
Point::new()];
let point_starts = [(10.0, 10.0), (10.0, 200.0), (200.0, 200.0), (200.0, 10.0)];
let midpoints = [Point::new(),
Point::new(),
Point::new(),
Point::new()];
let mut solver = Solver::new();
let mut weight = 1.0;
let multiplier = 2.0;
for i in 0..4 {
solver.add_constraints(&[points[i].x |EQ(WEAK * weight)| point_starts[i].0,
points[i].y |EQ(WEAK * weight)| point_starts[i].1])
.unwrap();
weight *= multiplier;
}
for (start, end) in vec![(0, 1), (1, 2), (2, 3), (3, 0)] {
solver.add_constraints(&[midpoints[start].x |EQ(REQUIRED)| (points[start].x + points[end].x) / 2.0,
midpoints[start].y |EQ(REQUIRED)| (points[start].y + points[end].y) / 2.0])
.unwrap();
}
solver.add_constraints(&[points[0].x + 20.0 |LE(STRONG)| points[2].x,
points[0].x + 20.0 |LE(STRONG)| points[3].x,
points[1].x + 20.0 |LE(STRONG)| points[2].x,
points[1].x + 20.0 |LE(STRONG)| points[3].x,
points[0].y + 20.0 |LE(STRONG)| points[1].y,
points[0].y + 20.0 |LE(STRONG)| points[2].y,
points[3].y + 20.0 |LE(STRONG)| points[1].y,
points[3].y + 20.0 |LE(STRONG)| points[2].y])
.unwrap();
for point in &points {
solver.add_constraints(&[point.x |GE(REQUIRED)| 0.0,
point.y |GE(REQUIRED)| 0.0,
point.x |LE(REQUIRED)| 500.0,
point.y |LE(REQUIRED)| 500.0]).unwrap()
}
update_values(solver.fetch_changes());
assert_eq!([(value_of(midpoints[0].x), value_of(midpoints[0].y)),
(value_of(midpoints[1].x), value_of(midpoints[1].y)),
(value_of(midpoints[2].x), value_of(midpoints[2].y)),
(value_of(midpoints[3].x), value_of(midpoints[3].y))],
[(10.0, 105.0),
(105.0, 200.0),
(200.0, 105.0),
(105.0, 10.0)]);
solver.add_edit_variable(points[2].x, STRONG).unwrap();
solver.add_edit_variable(points[2].y, STRONG).unwrap();
solver.suggest_value(points[2].x, 300.0).unwrap();
solver.suggest_value(points[2].y, 400.0).unwrap();
update_values(solver.fetch_changes());
assert_eq!([(value_of(points[0].x), value_of(points[0].y)),
(value_of(points[1].x), value_of(points[1].y)),
(value_of(points[2].x), value_of(points[2].y)),
(value_of(points[3].x), value_of(points[3].y))],
[(10.0, 10.0),
(10.0, 200.0),
(300.0, 400.0),
(200.0, 10.0)]);
assert_eq!([(value_of(midpoints[0].x), value_of(midpoints[0].y)),
(value_of(midpoints[1].x), value_of(midpoints[1].y)),
(value_of(midpoints[2].x), value_of(midpoints[2].y)),
(value_of(midpoints[3].x), value_of(midpoints[3].y))],
[(10.0, 105.0),
(155.0, 300.0),
(250.0, 205.0),
(105.0, 10.0)]);
}
cassowary-0.3.0/tests/removal.rs 0000644 0000000 0000000 00000001362 13156306557 0015105 0 ustar 00 0000000 0000000 extern crate cassowary;
use cassowary::{Variable, Solver, Constraint};
use cassowary::WeightedRelation::*;
use cassowary::strength::*;
mod common;
use common::new_values;
#[test]
fn remove_constraint() {
let (value_of, update_values) = new_values();
let mut solver = Solver::new();
let val = Variable::new();
let constraint: Constraint = val | EQ(REQUIRED) | 100.0;
solver.add_constraint(constraint.clone()).unwrap();
update_values(solver.fetch_changes());
assert_eq!(value_of(val), 100.0);
solver.remove_constraint(&constraint).unwrap();
solver.add_constraint(val | EQ(REQUIRED) | 0.0).unwrap();
update_values(solver.fetch_changes());
assert_eq!(value_of(val), 0.0);
}