Commit 552896af authored by Ian Hickson's avatar Ian Hickson

Refactor cassowary so it uses imports rather than parts.

Also misc cleanup:
 - reorder members to be more consistent and fit the style guide
 - remove use of _Pair
 - made Variable.applyUpdate and Variable.owner public
 - added docs to Priority, tweaked the code a bit
 - added some docs to Result
 - removed the internal-error Result (replaced with asserts)
 - removed unused Results
 - made Result const
 - merged some files together since they had used privates a lot

I'm sorry this is completely unreviewable. I did the move from `lib/*`
to `lib/src/*` first, then did the `part`-to-`import` change, and then
found out how many of the files involved privates, which I wasn't
expecting. I can redo this as multiple commits if that would make it
easier to review.
parent 366385b3
...@@ -27,8 +27,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { ...@@ -27,8 +27,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
// The widths of the first and the third boxes should be equal // The widths of the first and the third boxes should be equal
p1.width.equals(p3.width), p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first // The width of the first box should be twice as much as that of the second
// and third
p1.width.equals(p2.width * al.cm(2.0)), p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container // The height of the three boxes should be equal to that of the container
......
...@@ -26,8 +26,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { ...@@ -26,8 +26,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
// The widths of the first and the third boxes should be equal // The widths of the first and the third boxes should be equal
p1.width.equals(p3.width), p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first // The width of the first box should be twice as much as that of the second
// and third
p1.width.equals(p2.width * al.cm(2.0)), p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container // The height of the three boxes should be equal to that of the container
......
...@@ -3,22 +3,23 @@ ...@@ -3,22 +3,23 @@
// found in the LICENSE file. // found in the LICENSE file.
/// An implementation of the Cassowary constraint solving algorithm in Dart. /// An implementation of the Cassowary constraint solving algorithm in Dart.
///
/// This is used by the [RenderAutoLayout] render object in the rendering
/// library and by the [AutoLayout] widget in the widget library.
///
/// See also:
///
/// * <https://en.wikipedia.org/wiki/Cassowary_(software)>
/// * <https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf>
library cassowary; library cassowary;
import 'dart:collection'; export 'src/constraint.dart';
import 'dart:math'; export 'src/expression.dart';
export 'src/term.dart';
part 'constraint.dart'; export 'src/equation_member.dart';
part 'expression.dart'; export 'src/constant_member.dart';
part 'term.dart'; export 'src/solver.dart';
part 'variable.dart'; export 'src/result.dart';
part 'equation_member.dart'; export 'src/parser_exception.dart';
part 'constant_member.dart'; export 'src/param.dart';
part 'solver.dart'; export 'src/priority.dart';
part 'symbol.dart';
part 'row.dart';
part 'utils.dart';
part 'result.dart';
part 'parser_exception.dart';
part 'param.dart';
part 'priority.dart';
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
abstract class _EquationMember {
Expression asExpression();
bool get isConstant;
double get value;
Constraint operator >=(_EquationMember m) => asExpression() >= m;
Constraint operator <=(_EquationMember m) => asExpression() <= m;
Constraint equals(_EquationMember m) => asExpression().equals(m);
Expression operator +(_EquationMember m) => asExpression() + m;
Expression operator -(_EquationMember m) => asExpression() - m;
Expression operator *(_EquationMember m) => asExpression() * m;
Expression operator /(_EquationMember m) => asExpression() / m;
}
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
class Result {
final String message;
final bool error;
const Result(this.message, { bool isError: true }) : error = isError;
static final Result success = const Result("Success", isError: false);
static final Result unimplemented = const Result("Unimplemented");
static final Result duplicateConstraint =
const Result("Duplicate Constraint");
static final Result unsatisfiableConstraint =
const Result("Unsatisfiable Constraint");
static final Result unknownConstraint =
const Result("Unknown Constraint");
static final Result duplicateEditVariable =
const Result("Duplicate Edit Variable");
static final Result badRequiredStrength =
const Result("Bad Required Strength");
static final Result unknownEditVariable =
const Result("Unknown Edit Variable");
static final Result internalSolverError =
const Result("Internal Solver Error");
}
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
class _Row {
final Map<_Symbol, double> cells;
double constant = 0.0;
_Row(this.constant) : this.cells = new Map<_Symbol, double>();
_Row.fromRow(_Row row)
: this.cells = new Map<_Symbol, double>.from(row.cells),
this.constant = row.constant;
double add(double value) => constant += value;
void insertSymbol(_Symbol symbol, [double coefficient = 1.0]) {
double val = cells[symbol] ?? 0.0;
if (_nearZero(val + coefficient)) {
cells.remove(symbol);
} else {
cells[symbol] = val + coefficient;
}
}
void insertRow(_Row other, [double coefficient = 1.0]) {
constant += other.constant * coefficient;
other.cells.forEach((_Symbol s, double v) => insertSymbol(s, v * coefficient));
}
void removeSymbol(_Symbol symbol) {
cells.remove(symbol);
}
void reverseSign() {
constant = -constant;
cells.forEach((_Symbol s, double v) => cells[s] = -v);
}
void solveForSymbol(_Symbol symbol) {
assert(cells.containsKey(symbol));
double coefficient = -1.0 / cells[symbol];
cells.remove(symbol);
constant *= coefficient;
cells.forEach((_Symbol s, double v) => cells[s] = v * coefficient);
}
void solveForSymbols(_Symbol lhs, _Symbol rhs) {
insertSymbol(lhs, -1.0);
solveForSymbol(rhs);
}
double coefficientForSymbol(_Symbol symbol) => cells[symbol] ?? 0.0;
void substitute(_Symbol symbol, _Row row) {
double coefficient = cells[symbol];
if (coefficient == null) {
return;
}
cells.remove(symbol);
insertRow(row, coefficient);
}
@override
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(constant);
cells.forEach((_Symbol symbol, double value) {
buffer.write(" + " + value.toString() + " * " + symbol.toString());
});
return buffer.toString();
}
}
...@@ -2,21 +2,27 @@ ...@@ -2,21 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'expression.dart';
import 'equation_member.dart';
class ConstantMember extends _EquationMember { class ConstantMember extends EquationMember {
/// Creates a [ConstantMember] object.
///
/// The [cm] convenience method may be a more convenient way to create a
/// [ConstantMember] object.
ConstantMember(this.value); ConstantMember(this.value);
@override @override
final double value; Expression asExpression() => new Expression([], this.value);
@override @override
bool get isConstant => true; final double value;
@override @override
Expression asExpression() => new Expression([], this.value); bool get isConstant => true;
} }
ConstantMember cm(double value) { /// Creates a [ConstantMember].
return new ConstantMember(value); ///
} /// This is a convenience method to make cassowary expressions less verbose.
ConstantMember cm(double value) => new ConstantMember(value);
...@@ -2,16 +2,23 @@ ...@@ -2,16 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'priority.dart';
import 'expression.dart';
enum Relation { equalTo, lessThanOrEqualTo, greaterThanOrEqualTo, } enum Relation {
equalTo,
lessThanOrEqualTo,
greaterThanOrEqualTo,
}
class Constraint { class Constraint {
Constraint(this.expression, this.relation);
final Relation relation; final Relation relation;
final Expression expression; final Expression expression;
double priority = Priority.required;
Constraint(this.expression, this.relation); double priority = Priority.required;
Constraint operator |(double p) => this..priority = p; Constraint operator |(double p) => this..priority = p;
...@@ -34,9 +41,8 @@ class Constraint { ...@@ -34,9 +41,8 @@ class Constraint {
buffer.write(' | priority = $priority'); buffer.write(' | priority = $priority');
if (priority == Priority.required) { if (priority == Priority.required)
buffer.write(' (required)'); buffer.write(' (required)');
}
return buffer.toString(); return buffer.toString();
} }
......
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'expression.dart';
import 'constraint.dart';
/// Base class for the various parts of cassowary equations.
abstract class EquationMember {
Expression asExpression();
bool get isConstant;
double get value;
Constraint operator >=(EquationMember m) => asExpression() >= m;
Constraint operator <=(EquationMember m) => asExpression() <= m;
Constraint equals(EquationMember m) => asExpression().equals(m);
Expression operator +(EquationMember m) => asExpression() + m;
Expression operator -(EquationMember m) => asExpression() - m;
Expression operator *(EquationMember m) => asExpression() * m;
Expression operator /(EquationMember m) => asExpression() / m;
}
...@@ -2,10 +2,22 @@ ...@@ -2,10 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'constant_member.dart';
import 'constraint.dart';
import 'equation_member.dart';
import 'param.dart';
import 'parser_exception.dart';
import 'term.dart';
class _Multiplication {
const _Multiplication(this.multiplier, this.multiplicand);
final Expression multiplier;
final double multiplicand;
}
class Expression extends _EquationMember { class Expression extends EquationMember {
Expression(this.terms, this.constant); Expression(this.terms, this.constant);
Expression.fromExpression(Expression expr) Expression.fromExpression(Expression expr)
: this.terms = new List<Term>.from(expr.terms), : this.terms = new List<Term>.from(expr.terms),
this.constant = expr.constant; this.constant = expr.constant;
...@@ -14,6 +26,9 @@ class Expression extends _EquationMember { ...@@ -14,6 +26,9 @@ class Expression extends _EquationMember {
final double constant; final double constant;
@override
Expression asExpression() => this;
@override @override
bool get isConstant => terms.length == 0; bool get isConstant => terms.length == 0;
...@@ -21,9 +36,21 @@ class Expression extends _EquationMember { ...@@ -21,9 +36,21 @@ class Expression extends _EquationMember {
double get value => terms.fold(constant, (double value, Term term) => value + term.value); double get value => terms.fold(constant, (double value, Term term) => value + term.value);
@override @override
Expression asExpression() => this; Constraint operator >=(EquationMember value) {
return _createConstraint(value, Relation.greaterThanOrEqualTo);
}
@override
Constraint operator <=(EquationMember value) {
return _createConstraint(value, Relation.lessThanOrEqualTo);
}
Constraint _createConstraint(_EquationMember /* rhs */ value, Relation relation) { @override
Constraint equals(EquationMember value) {
return _createConstraint(value, Relation.equalTo);
}
Constraint _createConstraint(EquationMember /* rhs */ value, Relation relation) {
if (value is ConstantMember) { if (value is ConstantMember) {
return new Constraint( return new Constraint(
new Expression(new List<Term>.from(terms), constant - value.value), new Expression(new List<Term>.from(terms), constant - value.value),
...@@ -60,22 +87,7 @@ class Expression extends _EquationMember { ...@@ -60,22 +87,7 @@ class Expression extends _EquationMember {
} }
@override @override
Constraint operator >=(_EquationMember value) { Expression operator +(EquationMember m) {
return _createConstraint(value, Relation.greaterThanOrEqualTo);
}
@override
Constraint operator <=(_EquationMember value) {
return _createConstraint(value, Relation.lessThanOrEqualTo);
}
@override
Constraint equals(_EquationMember value) {
return _createConstraint(value, Relation.equalTo);
}
@override
Expression operator +(_EquationMember m) {
if (m is ConstantMember) if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant + m.value); return new Expression(new List<Term>.from(terms), constant + m.value);
...@@ -100,7 +112,7 @@ class Expression extends _EquationMember { ...@@ -100,7 +112,7 @@ class Expression extends _EquationMember {
} }
@override @override
Expression operator -(_EquationMember m) { Expression operator -(EquationMember m) {
if (m is ConstantMember) if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant - m.value); return new Expression(new List<Term>.from(terms), constant - m.value);
...@@ -126,49 +138,22 @@ class Expression extends _EquationMember { ...@@ -126,49 +138,22 @@ class Expression extends _EquationMember {
return null; return null;
} }
_EquationMember _applyMultiplicand(double m) {
List<Term> newTerms = terms.fold(
new List<Term>(),
(List<Term> list, Term term) {
return list..add(new Term(term.variable, term.coefficient * m));
}
);
return new Expression(newTerms, constant * m);
}
_Pair<Expression, double> _findMulitplierAndMultiplicand(_EquationMember m) {
// At least on of the the two members must be constant for the resulting
// expression to be linear
if (!this.isConstant && !m.isConstant)
return null;
if (this.isConstant)
return new _Pair<Expression, double>(m.asExpression(), this.value);
if (m.isConstant)
return new _Pair<Expression, double>(this.asExpression(), m.value);
assert(false);
return null;
}
@override @override
_EquationMember operator *(_EquationMember m) { EquationMember operator *(EquationMember m) {
_Pair<Expression, double> args = _findMulitplierAndMultiplicand(m); _Multiplication args = _findMulitplierAndMultiplicand(m);
if (args == null) { if (args == null) {
throw new ParserException( throw new ParserException(
'Could not find constant multiplicand or multiplier', 'Could not find constant multiplicand or multiplier',
<_EquationMember>[this, m] <EquationMember>[this, m]
); );
} }
return args.first._applyMultiplicand(args.second); return args.multiplier._applyMultiplicand(args.multiplicand);
} }
@override @override
_EquationMember operator /(_EquationMember m) { EquationMember operator /(EquationMember m) {
if (!m.isConstant) { if (!m.isConstant) {
throw new ParserException( throw new ParserException(
'The divisor was not a constant expression', [this, m]); 'The divisor was not a constant expression', [this, m]);
...@@ -178,6 +163,32 @@ class Expression extends _EquationMember { ...@@ -178,6 +163,32 @@ class Expression extends _EquationMember {
return this._applyMultiplicand(1.0 / m.value); return this._applyMultiplicand(1.0 / m.value);
} }
_Multiplication _findMulitplierAndMultiplicand(EquationMember m) {
// At least one of the the two members must be constant for the resulting
// expression to be linear
if (!this.isConstant && !m.isConstant)
return null;
if (this.isConstant)
return new _Multiplication(m.asExpression(), this.value);
if (m.isConstant)
return new _Multiplication(this.asExpression(), m.value);
assert(false);
return null;
}
EquationMember _applyMultiplicand(double m) {
List<Term> newTerms = terms.fold(
new List<Term>(),
(List<Term> list, Term term) {
return list..add(new Term(term.variable, term.coefficient * m));
}
);
return new Expression(newTerms, constant * m);
}
@override @override
String toString() { String toString() {
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
......
...@@ -2,12 +2,41 @@ ...@@ -2,12 +2,41 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'equation_member.dart';
import 'expression.dart';
import 'term.dart';
class Param extends _EquationMember { class Variable {
static int _total = 0;
Variable(this.value) : _tick = _total++;
final int _tick;
double value;
String name;
Param get owner => _owner;
Param _owner;
bool applyUpdate(double updated) {
bool res = updated != value;
value = updated;
return res;
}
String get debugName => name ?? 'variable$_tick';
@override
String toString() => debugName;
}
class Param extends EquationMember {
Param([double value = 0.0]) : variable = new Variable(value) { Param([double value = 0.0]) : variable = new Variable(value) {
variable._owner = this; variable._owner = this;
} }
Param.withContext(dynamic context, [double value = 0.0]) Param.withContext(dynamic context, [double value = 0.0])
: variable = new Variable(value), : variable = new Variable(value),
context = context { context = context {
...@@ -18,6 +47,9 @@ class Param extends _EquationMember { ...@@ -18,6 +47,9 @@ class Param extends _EquationMember {
dynamic context; dynamic context;
@override
Expression asExpression() => new Expression(<Term>[new Term(variable, 1.0)], 0.0);
@override @override
bool get isConstant => false; bool get isConstant => false;
...@@ -26,7 +58,4 @@ class Param extends _EquationMember { ...@@ -26,7 +58,4 @@ class Param extends _EquationMember {
String get name => variable.name; String get name => variable.name;
void set name(String name) { variable.name = name; } void set name(String name) { variable.name = name; }
@override
Expression asExpression() => new Expression(<Term>[new Term(variable, 1.0)], 0.0);
} }
...@@ -2,16 +2,19 @@ ...@@ -2,16 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'equation_member.dart';
class ParserException implements Exception { class ParserException implements Exception {
final String message;
List<_EquationMember> members;
ParserException(this.message, this.members); ParserException(this.message, this.members);
final String message;
List<EquationMember> members;
@override @override
String toString() { String toString() {
if (message == null) return "Error while parsing constraint or expression"; if (message == null)
return "Error: '$message' while trying to parse constraint or expression"; return 'Error while parsing constraint or expression';
return 'Error: "$message" while trying to parse constraint or expression';
} }
} }
...@@ -2,14 +2,35 @@ ...@@ -2,14 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'dart:math';
/// Utility functions for managing cassowary priorities.
///
/// Priorities in cassowary expressions are internally expressed as a number
/// between 0 and 1,000,000,000. These numbers can be created by using the
/// [Priority.create] static method.
class Priority { class Priority {
/// The priority level that, by convention, is the highest allowed priority level (1,000,000,000).
static final double required = create(1e3, 1e3, 1e3); static final double required = create(1e3, 1e3, 1e3);
/// A priority level that is below the [required] level but still near it (1,000,000).
static final double strong = create(1.0, 0.0, 0.0); static final double strong = create(1.0, 0.0, 0.0);
/// A priority level logarithmically in the middle of [strong] and [weak] (1,000).
static final double medium = create(0.0, 1.0, 0.0); static final double medium = create(0.0, 1.0, 0.0);
/// A priority level that, by convention, is the lowest allowed priority level (1).
static final double weak = create(0.0, 0.0, 1.0); static final double weak = create(0.0, 0.0, 1.0);
/// Computes a priority level by combining three numbers in the range 0..1000.
///
/// The first number is a multiple of [strong].
///
/// The second number is a multiple of [medium].
///
/// The third number is a multiple of [weak].
///
/// By convention, at least one of these numbers should be equal to or greater than 1.
static double create(double a, double b, double c) { static double create(double a, double b, double c) {
double result = 0.0; double result = 0.0;
result += max(0.0, min(1e3, a)) * 1e6; result += max(0.0, min(1e3, a)) * 1e6;
...@@ -17,8 +38,4 @@ class Priority { ...@@ -17,8 +38,4 @@ class Priority {
result += max(0.0, min(1e3, c)); result += max(0.0, min(1e3, c));
return result; return result;
} }
static double clamp(double value) {
return max(0.0, min(required, value));
}
} }
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'solver.dart';
/// Return values used by methods on the cassowary [Solver].
class Result {
const Result._(this.message, { bool isError: true }) : error = isError;
/// The human-readable string associated with this result.
///
/// This message is typically brief and intended for developers to help debug
/// erroneous expressions.
final String message;
/// Whether this [Result] represents an error (true) or not (false).
final bool error;
static const Result success =
const Result._('Success', isError: false);
static const Result duplicateConstraint =
const Result._('Duplicate constraint');
static const Result unsatisfiableConstraint =
const Result._('Unsatisfiable constraint');
static const Result unknownConstraint =
const Result._('Unknown constraint');
static const Result duplicateEditVariable =
const Result._('Duplicate edit variable');
static const Result badRequiredStrength =
const Result._('Bad required strength');
static const Result unknownEditVariable =
const Result._('Unknown edit variable');
}
...@@ -2,23 +2,26 @@ ...@@ -2,23 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'equation_member.dart';
import 'expression.dart';
import 'param.dart';
class Term extends _EquationMember { class Term extends EquationMember {
Term(this.variable, this.coefficient); Term(this.variable, this.coefficient);
final Variable variable; final Variable variable;
final double coefficient; final double coefficient;
@override @override
bool get isConstant => false; Expression asExpression() =>
new Expression([new Term(this.variable, this.coefficient)], 0.0);
@override @override
double get value => coefficient * variable.value; bool get isConstant => false;
@override @override
Expression asExpression() => double get value => coefficient * variable.value;
new Expression([new Term(this.variable, this.coefficient)], 0.0);
@override @override
String toString() { String toString() {
......
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
enum _SymbolType { invalid, external, slack, error, dummy, }
class _Symbol {
final _SymbolType type;
final int tick;
_Symbol(this.type, this.tick);
@override
String toString() {
String typeString = 'unknown';
switch (type) {
case _SymbolType.invalid:
typeString = 'i';
break;
case _SymbolType.external:
typeString = 'v';
break;
case _SymbolType.slack:
typeString = 's';
break;
case _SymbolType.error:
typeString = 'e';
break;
case _SymbolType.dummy:
typeString = 'd';
break;
}
return '$typeString$tick';
}
}
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
bool _nearZero(double value) {
const double epsilon = 1.0e-8;
return value < 0.0 ? -value < epsilon : value < epsilon;
}
class _Pair<X, Y> {
X first;
Y second;
_Pair(this.first, this.second);
}
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
class Variable {
double value;
String name;
Param _owner;
final int _tick;
static int _total = 0;
Variable(this.value) : _tick = _total++;
bool _applyUpdate(double updated) {
bool res = updated != value;
value = updated;
return res;
}
String get debugName => name ?? 'variable$_tick';
@override
String toString() => debugName;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment