Commit c7dfbc04 authored by Adam Barth's avatar Adam Barth

Merge branch 'cassowary'

parents 6fea7f58 d29a0b52
// 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.
library cassowary;
import 'dart:math';
part 'constraint.dart';
part 'expression.dart';
part 'term.dart';
part 'variable.dart';
part 'equation_member.dart';
part 'constant_member.dart';
part 'solver.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;
class ConstantMember extends _EquationMember {
final double value;
bool get isConstant => true;
ConstantMember(this.value);
Expression asExpression() => new Expression([], this.value);
}
ConstantMember cm(double value) {
return new ConstantMember(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.
part of cassowary;
enum Relation { equalTo, lessThanOrEqualTo, greaterThanOrEqualTo, }
class Constraint {
final Relation relation;
final Expression expression;
double priority = Priority.required;
Constraint(this.expression, this.relation);
Constraint operator |(double p) => this..priority = p;
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(expression.toString());
switch (relation) {
case Relation.equalTo:
buffer.write(" == 0 ");
break;
case Relation.greaterThanOrEqualTo:
buffer.write(" >= 0 ");
break;
case Relation.lessThanOrEqualTo:
buffer.write(" <= 0 ");
break;
}
buffer.write(" | priority = ${priority}");
if (priority == Priority.required) {
buffer.write(" (required)");
}
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.
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;
operator ==(_EquationMember m) => asExpression() == 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;
int get hashCode =>
throw "An equation member is not comparable and cannot be added to collections";
}
// 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 Expression extends _EquationMember {
final List<Term> terms;
final double constant;
bool get isConstant => terms.length == 0;
double get value => terms.fold(constant, (value, term) => value + term.value);
Expression(this.terms, this.constant);
Expression.fromExpression(Expression expr)
: this.terms = new List<Term>.from(expr.terms),
this.constant = expr.constant;
Expression asExpression() => this;
Constraint _createConstraint(
_EquationMember /* rhs */ value, Relation relation) {
if (value is ConstantMember) {
return new Constraint(
new Expression(new List.from(terms), constant - value.value),
relation);
}
if (value is Param) {
var newTerms = new List<Term>.from(terms)
..add(new Term(value.variable, -1.0));
return new Constraint(new Expression(newTerms, constant), relation);
}
if (value is Term) {
var newTerms = new List<Term>.from(terms)
..add(new Term(value.variable, -value.coefficient));
return new Constraint(new Expression(newTerms, constant), relation);
}
if (value is Expression) {
var newTerms = value.terms.fold(new List<Term>.from(terms),
(list, t) => list..add(new Term(t.variable, -t.coefficient)));
return new Constraint(
new Expression(newTerms, constant - value.constant), relation);
}
assert(false);
return null;
}
Constraint operator >=(_EquationMember value) =>
_createConstraint(value, Relation.greaterThanOrEqualTo);
Constraint operator <=(_EquationMember value) =>
_createConstraint(value, Relation.lessThanOrEqualTo);
operator ==(_EquationMember value) =>
_createConstraint(value, Relation.equalTo);
Expression operator +(_EquationMember m) {
if (m is ConstantMember) {
return new Expression(new List.from(terms), constant + m.value);
}
if (m is Param) {
return new Expression(
new List.from(terms)..add(new Term(m.variable, 1.0)), constant);
}
if (m is Term) {
return new Expression(new List.from(terms)..add(m), constant);
}
if (m is Expression) {
return new Expression(
new List.from(terms)..addAll(m.terms), constant + m.constant);
}
assert(false);
return null;
}
Expression operator -(_EquationMember m) {
if (m is ConstantMember) {
return new Expression(new List.from(terms), constant - m.value);
}
if (m is Param) {
return new Expression(
new List.from(terms)..add(new Term(m.variable, -1.0)), constant);
}
if (m is Term) {
return new Expression(new List.from(terms)
..add(new Term(m.variable, -m.coefficient)), constant);
}
if (m is Expression) {
var copiedTerms = new List<Term>.from(terms);
m.terms.forEach(
(t) => copiedTerms.add(new Term(t.variable, -t.coefficient)));
return new Expression(copiedTerms, constant - m.constant);
}
assert(false);
return null;
}
_EquationMember _applyMultiplicand(double m) {
var newTerms = terms.fold(new List<Term>(), (list, term) => 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(m.asExpression(), this.value);
}
if (m.isConstant) {
return new _Pair(this.asExpression(), m.value);
}
assert(false);
return null;
}
_EquationMember operator *(_EquationMember m) {
_Pair<Expression, double> args = _findMulitplierAndMultiplicand(m);
if (args == null) {
throw new ParserException(
"Could not find constant multiplicand or multiplier", [this, m]);
return null;
}
return args.first._applyMultiplicand(args.second);
}
_EquationMember operator /(_EquationMember m) {
if (!m.isConstant) {
throw new ParserException(
"The divisor was not a constant expression", [this, m]);
return null;
}
return this._applyMultiplicand(1.0 / m.value);
}
String toString() {
StringBuffer buffer = new StringBuffer();
terms.forEach((t) => buffer.write("${t}"));
if (constant != 0.0) {
buffer.write(constant.sign > 0.0 ? "+" : "-");
buffer.write(constant.abs());
}
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.
part of cassowary;
class Param extends _EquationMember {
final Variable variable;
dynamic context;
Param([double value = 0.0]) : variable = new Variable(value) {
variable._owner = this;
}
Param.withContext(ctx, [double value = 0.0])
: variable = new Variable(value),
context = ctx {
variable._owner = this;
}
bool get isConstant => false;
double get value => variable.value;
String get name => variable.name;
set name(String name) => variable.name = name;
Expression asExpression() => new Expression([new Term(variable, 1.0)], 0.0);
}
// 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 ParserException implements Exception {
final String message;
List<_EquationMember> members;
ParserException(this.message, this.members);
String toString() {
if (message == null) return "Error while parsing constraint or expression";
return "Error: '$message' while trying to parse constraint or expression";
}
}
// 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 Priority {
static final double required = create(1e3, 1e3, 1e3);
static final double strong = create(1.0, 0.0, 0.0);
static final double medium = create(0.0, 1.0, 0.0);
static final double weak = create(0.0, 0.0, 1.0);
static double create(double a, double b, double c) {
double result = 0.0;
result += max(0.0, min(1e3, a)) * 1e6;
result += max(0.0, min(1e3, b)) * 1e3;
result += max(0.0, min(1e3, c));
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.
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 = _elvis(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((s, v) => insertSymbol(s, v * coefficient));
}
void removeSymbol(_Symbol symbol) {
cells.remove(symbol);
}
void reverseSign() {
constant = -constant;
cells.forEach((s, 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((s, v) => cells[s] = v * coefficient);
}
void solveForSymbols(_Symbol lhs, _Symbol rhs) {
insertSymbol(lhs, -1.0);
solveForSymbol(rhs);
}
double coefficientForSymbol(_Symbol symbol) => _elvis(cells[symbol], 0.0);
void substitute(_Symbol symbol, _Row row) {
double coefficient = cells[symbol];
if (coefficient == null) {
return;
}
cells.remove(symbol);
insertRow(row, coefficient);
}
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(constant);
cells.forEach((symbol, value) =>
buffer.write(" + " + value.toString() + " * " + symbol.toString()));
return buffer.toString();
}
}
This diff is collapsed.
// 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);
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;
class Term extends _EquationMember {
final Variable variable;
final double coefficient;
bool get isConstant => false;
double get value => coefficient * variable.value;
Term(this.variable, this.coefficient);
Expression asExpression() =>
new Expression([new Term(this.variable, this.coefficient)], 0.0);
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(coefficient.sign > 0.0 ? "+" : "-");
if (coefficient.abs() != 1.0) {
buffer.write(coefficient.abs());
buffer.write("*");
}
buffer.write(variable);
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.
part of cassowary;
bool _nearZero(double value) {
const double epsilon = 1.0e-8;
return value < 0.0 ? -value < epsilon : value < epsilon;
}
// Workaround for the lack of a null coalescing operator. Uses a ternary
// instead. Sadly, due the lack of generic types on functions, we have to use
// dynamic instead.
_elvis(a, b) => a != null ? a : b;
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 => _elvis(name, "variable${_tick}");
String toString() => debugName;
}
This diff is collapsed.
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