Merge branch 'newton'
diff --git a/packages/newton/README.md b/packages/newton/README.md
new file mode 100644
index 0000000..ccd16ea
--- /dev/null
+++ b/packages/newton/README.md
@@ -0,0 +1,9 @@
+# Newton
+[![Build Status](https://travis-ci.org/flutter/newton.svg?branch=master)](https://travis-ci.org/flutter/newton)
+[![Coverage Status](https://coveralls.io/repos/domokit/newton/badge.svg?branch=master)](https://coveralls.io/r/domokit/newton?branch=master)
+
+Simple Physics Simulations for Dart. Springs, friction, gravity, etc.
+
+To run the tests:
+pub get
+dart test/newton_test.dart
diff --git a/packages/newton/lib/newton.dart b/packages/newton/lib/newton.dart
new file mode 100644
index 0000000..c70ca3c
--- /dev/null
+++ b/packages/newton/lib/newton.dart
@@ -0,0 +1,18 @@
+// 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 newton;
+
+import 'dart:math' as math;
+
+part 'src/simulation.dart';
+part 'src/simulation_group.dart';
+part 'src/tolerance.dart';
+part 'src/utils.dart';
+
+part 'src/friction_simulation.dart';
+part 'src/gravity_simulation.dart';
+part 'src/scroll_simulation.dart';
+part 'src/spring_simulation.dart';
+part 'src/spring_solution.dart';
diff --git a/packages/newton/lib/src/friction_simulation.dart b/packages/newton/lib/src/friction_simulation.dart
new file mode 100644
index 0000000..38671b9
--- /dev/null
+++ b/packages/newton/lib/src/friction_simulation.dart
@@ -0,0 +1,68 @@
+// 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 newton;
+
+class FrictionSimulation extends Simulation {
+  final double _drag;
+  final double _dragLog;
+  final double _x;
+  final double _v;
+
+  FrictionSimulation(double drag, double position, double velocity)
+      : _drag = drag,
+        _dragLog = math.log(drag),
+        _x = position,
+        _v = velocity;
+
+  // Return the drag value for a FrictionSimulation whose x() and dx() values pass
+  // through the specified start and end position/velocity values.
+  //
+  // Total time to reach endVelocity is just: (log(endVelocity) / log(startVelocity)) / log(_drag)
+  // or (log(v1) - log(v0)) / log(D), given v = v0 * D^t per the dx() function below.
+  // Solving for D given x(time) is trickier. Algebra courtesy of Wolfram Alpha:
+  // x1 = x0 + (v0 * D^((log(v1) - log(v0)) / log(D))) / log(D) - v0 / log(D), find D
+  static double _dragFor(double startPosition, double endPosition, double startVelocity, double endVelocity) {
+    return math.pow(math.E, (startVelocity - endVelocity) / (startPosition - endPosition));
+  }
+
+  // A friction simulation that starts and ends at the specified positions
+  // and velocities.
+  factory FrictionSimulation.through(double startPosition, double endPosition, double startVelocity, double endVelocity) {
+    return new FrictionSimulation(
+      _dragFor(startPosition, endPosition, startVelocity, endVelocity),
+      startPosition,
+      startVelocity)
+      .. tolerance = new Tolerance(velocity: endVelocity.abs());
+  }
+
+  double x(double time) => _x + _v * math.pow(_drag, time) / _dragLog - _v / _dragLog;
+
+  double dx(double time) => _v * math.pow(_drag, time);
+
+  @override
+  bool isDone(double time) => dx(time).abs() < this.tolerance.velocity;
+}
+
+class BoundedFrictionSimulation extends FrictionSimulation {
+  BoundedFrictionSimulation(
+    double drag,
+    double position,
+    double velocity,
+    double this._minX,
+    double this._maxX) : super(drag, position, velocity);
+
+  final double _minX;
+  final double _maxX;
+
+  double x(double time) {
+    return super.x(time).clamp(_minX, _maxX);
+  }
+
+  bool isDone(double time) {
+    return super.isDone(time) ||
+      (x(time) - _minX).abs() < tolerance.distance ||
+      (x(time) - _maxX).abs() < tolerance.distance;
+  }
+}
diff --git a/packages/newton/lib/src/gravity_simulation.dart b/packages/newton/lib/src/gravity_simulation.dart
new file mode 100644
index 0000000..2f002b7
--- /dev/null
+++ b/packages/newton/lib/src/gravity_simulation.dart
@@ -0,0 +1,26 @@
+// 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 newton;
+
+class GravitySimulation extends Simulation {
+  final double _x;
+  final double _v;
+  final double _a;
+  final double _end;
+
+  GravitySimulation(
+      double acceleration, double distance, double endDistance, double velocity)
+      : _a = acceleration,
+        _x = distance,
+        _v = velocity,
+        _end = endDistance;
+
+  double x(double time) => _x + _v * time + 0.5 * _a * time * time;
+
+  double dx(double time) => _v + time * _a;
+
+  @override
+  bool isDone(double time) => x(time).abs() >= _end;
+}
diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart
new file mode 100644
index 0000000..64e0a45
--- /dev/null
+++ b/packages/newton/lib/src/scroll_simulation.dart
@@ -0,0 +1,67 @@
+// 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 newton;
+
+/// Simulates kinetic scrolling behavior between a leading and trailing
+/// boundary. Friction is applied within the extends and a spring action applied
+/// at the boundaries. This simulation can only step forward.
+class ScrollSimulation extends SimulationGroup {
+  ScrollSimulation(
+    double position,
+    double velocity,
+    this._leadingExtent,
+    this._trailingExtent,
+    this._spring,
+    this._drag) {
+    _chooseSimulation(position, velocity, 0.0);
+  }
+
+  final double _leadingExtent;
+  final double _trailingExtent;
+  final SpringDescription _spring;
+  final double _drag;
+
+  bool _isSpringing = false;
+  Simulation _currentSimulation;
+  double _offset = 0.0;
+
+  @override
+  bool step(double time) => _chooseSimulation(
+      _currentSimulation.x(time - _offset),
+      _currentSimulation.dx(time - _offset), time);
+
+  @override
+  Simulation get currentSimulation => _currentSimulation;
+
+  @override
+  double get currentIntervalOffset => _offset;
+
+  bool _chooseSimulation(double position, double velocity, double intervalOffset) {
+    if (_spring == null && (position > _trailingExtent || position < _leadingExtent))
+      return false;
+
+    /// This simulation can only step forward.
+    if (!_isSpringing) {
+      if (position > _trailingExtent) {
+        _isSpringing = true;
+        _offset = intervalOffset;
+        _currentSimulation = new ScrollSpringSimulation(_spring, position, _trailingExtent, velocity);
+        return true;
+      } else if (position < _leadingExtent) {
+        _isSpringing = true;
+        _offset = intervalOffset;
+        _currentSimulation = new ScrollSpringSimulation(_spring, position, _leadingExtent, velocity);
+        return true;
+      }
+    }
+
+    if (_currentSimulation == null) {
+      _currentSimulation = new FrictionSimulation(_drag, position, velocity);
+      return true;
+    }
+
+    return false;
+  }
+}
diff --git a/packages/newton/lib/src/simulation.dart b/packages/newton/lib/src/simulation.dart
new file mode 100644
index 0000000..e374c9d
--- /dev/null
+++ b/packages/newton/lib/src/simulation.dart
@@ -0,0 +1,23 @@
+// 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 newton;
+
+abstract class Simulatable {
+  /// The current position of the object in the simulation
+  double x(double time);
+
+  /// The current velocity of the object in the simulation
+  double dx(double time);
+}
+
+/// The base class for all simulations. The user is meant to instantiate an
+/// instance of a simulation and query the same for the position and velocity
+/// of the body at a given interval.
+abstract class Simulation implements Simulatable {
+  Tolerance tolerance = toleranceDefault;
+
+  /// Returns if the simulation is done at a given time
+  bool isDone(double time);
+}
diff --git a/packages/newton/lib/src/simulation_group.dart b/packages/newton/lib/src/simulation_group.dart
new file mode 100644
index 0000000..a7359da
--- /dev/null
+++ b/packages/newton/lib/src/simulation_group.dart
@@ -0,0 +1,58 @@
+// 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 newton;
+
+/// The abstract base class for all composite simulations. Concrete subclasses
+/// must implement the appropriate methods to select the appropriate simulation
+/// at a given time interval. The simulation group takes care to call the `step`
+/// method at appropriate intervals. If more fine grained control over the the
+/// step is necessary, subclasses may override `Simulatable` methods.
+abstract class SimulationGroup extends Simulation {
+
+  /// The currently active simulation
+  Simulation get currentSimulation;
+
+  /// The time offset applied to the currently active simulation;
+  double get currentIntervalOffset;
+
+  /// Called when a significant change in the interval is detected. Subclasses
+  /// must decide if the the current simulation must be switched (or updated).
+  /// The result is whether the simulation was switched in this step.
+  bool step(double time);
+
+  double x(double time) {
+    _stepIfNecessary(time);
+    return currentSimulation.x(time - currentIntervalOffset);
+  }
+
+  double dx(double time) {
+    _stepIfNecessary(time);
+    return currentSimulation.dx(time - currentIntervalOffset);
+  }
+
+  @override
+  void set tolerance(Tolerance t) {
+    this.currentSimulation.tolerance = t;
+    super.tolerance = t;
+  }
+
+  @override
+  bool isDone(double time) {
+    _stepIfNecessary(time);
+    return currentSimulation.isDone(time - currentIntervalOffset);
+  }
+
+  double _lastStep = -1.0;
+  void _stepIfNecessary(double time) {
+    if (_nearEqual(_lastStep, time, toleranceDefault.time)) {
+      return;
+    }
+
+    _lastStep = time;
+    if (step(time)) {
+      this.currentSimulation.tolerance = this.tolerance;
+    }
+  }
+}
diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart
new file mode 100644
index 0000000..143f96c
--- /dev/null
+++ b/packages/newton/lib/src/spring_simulation.dart
@@ -0,0 +1,83 @@
+// 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 newton;
+
+class SpringDescription {
+  /// The mass of the spring (m)
+  final double mass;
+
+  /// The spring constant (k)
+  final double springConstant;
+
+  /// The damping coefficient.
+  /// Note: Not to be confused with the damping ratio. Use the separate
+  ///       constructor provided for this purpose
+  final double damping;
+
+  SpringDescription(
+      {double this.mass, double this.springConstant, double this.damping}) {
+    assert(mass != null);
+    assert(springConstant != null);
+    assert(damping != null);
+  }
+
+  /// Create a spring given the mass, spring constant and the damping ratio. The
+  /// damping ratio is especially useful trying to determing the type of spring
+  /// to create. A ratio of 1.0 creates a critically damped spring, > 1.0
+  /// creates an overdamped spring and < 1.0 an underdamped one.
+  SpringDescription.withDampingRatio(
+      {double mass, double springConstant, double ratio: 1.0})
+      : mass = mass,
+        springConstant = springConstant,
+        damping = ratio * 2.0 * math.sqrt(mass * springConstant);
+}
+
+enum SpringType { unknown, criticallyDamped, underDamped, overDamped, }
+
+/// Creates a spring simulation. Depending on the spring description, a
+/// critically, under or overdamped spring will be created.
+class SpringSimulation extends Simulation {
+  final double _endPosition;
+
+  final _SpringSolution _solution;
+
+  /// A spring description with the provided spring description, start distance,
+  /// end distance and velocity.
+  SpringSimulation(
+      SpringDescription desc, double start, double end, double velocity)
+      : this._endPosition = end,
+        _solution = new _SpringSolution(desc, start - end, velocity);
+
+  SpringType get type => _solution.type;
+
+  double x(double time) => _endPosition + _solution.x(time);
+
+  double dx(double time) => _solution.dx(time);
+
+  @override
+  bool isDone(double time) =>
+      _nearEqual(x(time), _endPosition, this.tolerance.distance) &&
+          _nearZero(dx(time), this.tolerance.velocity);
+}
+
+/// A SpringSimulation where the value of x() is guaranteed to have exactly the
+/// end value when the simulation isDone().
+class ScrollSpringSimulation extends SpringSimulation {
+  ScrollSpringSimulation(SpringDescription desc, double start, double end, double velocity)
+    : super(desc, start, end, velocity);
+
+  bool _isDone(double position, double velocity) {
+    return _nearEqual(position, _endPosition, tolerance.distance) && _nearZero(velocity, tolerance.velocity);
+  }
+
+  @override
+  double x(double time) {
+    double xAtTime = super.x(time);
+    return _isDone(xAtTime, dx(time)) ? _endPosition : xAtTime;
+  }
+
+  @override
+  bool isDone(double time) => _isDone(x(time), dx(time));
+}
diff --git a/packages/newton/lib/src/spring_solution.dart b/packages/newton/lib/src/spring_solution.dart
new file mode 100644
index 0000000..3241311
--- /dev/null
+++ b/packages/newton/lib/src/spring_solution.dart
@@ -0,0 +1,118 @@
+// 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 newton;
+
+abstract class _SpringSolution implements Simulatable {
+  factory _SpringSolution(
+      SpringDescription desc, double initialPosition, double initialVelocity) {
+    double cmk =
+        desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
+
+    if (cmk == 0.0) {
+      return new _CriticalSolution(desc, initialPosition, initialVelocity);
+    } else if (cmk > 0.0) {
+      return new _OverdampedSolution(desc, initialPosition, initialVelocity);
+    } else {
+      return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
+    }
+
+    return null;
+  }
+
+  SpringType get type;
+}
+
+class _CriticalSolution implements _SpringSolution {
+  final double _r, _c1, _c2;
+
+  factory _CriticalSolution(
+      SpringDescription desc, double distance, double velocity) {
+    final double r = -desc.damping / (2.0 * desc.mass);
+    final double c1 = distance;
+    final double c2 = velocity / (r * distance);
+    return new _CriticalSolution.withArgs(r, c1, c2);
+  }
+
+  SpringType get type => SpringType.criticallyDamped;
+
+  _CriticalSolution.withArgs(double r, double c1, double c2)
+      : _r = r,
+        _c1 = c1,
+        _c2 = c2;
+
+  double x(double time) => (_c1 + _c2 * time) * math.pow(math.E, _r * time);
+
+  double dx(double time) {
+    final double power = math.pow(math.E, _r * time);
+    return _r * (_c1 + _c2 * time) * power + _c2 * power;
+  }
+}
+
+class _OverdampedSolution implements _SpringSolution {
+  final double _r1, _r2, _c1, _c2;
+
+  factory _OverdampedSolution(
+      SpringDescription desc, double distance, double velocity) {
+    final double cmk =
+        desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
+
+    final double r1 = (-desc.damping - math.sqrt(cmk)) / (2.0 * desc.mass);
+    final double r2 = (-desc.damping + math.sqrt(cmk)) / (2.0 * desc.mass);
+    final double c2 = (velocity - r1 * distance) / (r2 - r1);
+    final double c1 = distance - c2;
+
+    return new _OverdampedSolution.withArgs(r1, r2, c1, c2);
+  }
+
+  _OverdampedSolution.withArgs(double r1, double r2, double c1, double c2)
+      : _r1 = r1,
+        _r2 = r2,
+        _c1 = c1,
+        _c2 = c2;
+
+  SpringType get type => SpringType.overDamped;
+
+  double x(double time) =>
+      (_c1 * math.pow(math.E, _r1 * time) + _c2 * math.pow(math.E, _r2 * time));
+
+  double dx(double time) => (_c1 * _r1 * math.pow(math.E, _r1 * time) +
+      _c2 * _r2 * math.pow(math.E, _r2 * time));
+}
+
+class _UnderdampedSolution implements _SpringSolution {
+  final double _w, _r, _c1, _c2;
+
+  factory _UnderdampedSolution(
+      SpringDescription desc, double distance, double velocity) {
+    final double w = math.sqrt(4.0 * desc.mass * desc.springConstant -
+            desc.damping * desc.damping) /
+        (2.0 * desc.mass);
+    final double r = -(desc.damping / 2.0 * desc.mass);
+    final double c1 = distance;
+    final double c2 = (velocity - r * distance) / w;
+
+    return new _UnderdampedSolution.withArgs(w, r, c1, c2);
+  }
+
+  _UnderdampedSolution.withArgs(double w, double r, double c1, double c2)
+      : _w = w,
+        _r = r,
+        _c1 = c1,
+        _c2 = c2;
+
+  SpringType get type => SpringType.underDamped;
+
+  double x(double time) => math.pow(math.E, _r * time) *
+      (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
+
+  double dx(double time) {
+    final double power = math.pow(math.E, _r * time);
+    final double cosine = math.cos(_w * time);
+    final double sine = math.sin(_w * time);
+
+    return power * (_c2 * _w * cosine - _c1 * _w * sine) +
+        _r * power * (_c2 * sine + _c1 * cosine);
+  }
+}
diff --git a/packages/newton/lib/src/tolerance.dart b/packages/newton/lib/src/tolerance.dart
new file mode 100644
index 0000000..b52d075
--- /dev/null
+++ b/packages/newton/lib/src/tolerance.dart
@@ -0,0 +1,17 @@
+// 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 newton;
+
+class Tolerance {
+  final double distance;
+  final double time;
+  final double velocity;
+
+  const Tolerance({this.distance: epsilonDefault, this.time: epsilonDefault,
+      this.velocity: epsilonDefault});
+}
+
+const double epsilonDefault = 1e-3;
+const Tolerance toleranceDefault = const Tolerance();
diff --git a/packages/newton/lib/src/utils.dart b/packages/newton/lib/src/utils.dart
new file mode 100644
index 0000000..75709a3
--- /dev/null
+++ b/packages/newton/lib/src/utils.dart
@@ -0,0 +1,10 @@
+// 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 newton;
+
+bool _nearEqual(double a, double b, double epsilon) =>
+    (a > (b - epsilon)) && (a < (b + epsilon));
+
+bool _nearZero(double a, double epsilon) => _nearEqual(a, 0.0, epsilon);
diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart
new file mode 100644
index 0000000..20efa21
--- /dev/null
+++ b/packages/newton/test/newton_test.dart
@@ -0,0 +1,252 @@
+// 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 simple_physics.test;
+
+import 'package:test/test.dart';
+
+import 'package:newton/newton.dart';
+
+void main() {
+  test('test_friction', () {
+    var friction = new FrictionSimulation(0.3, 100.0, 400.0);
+
+    friction.tolerance = const Tolerance(velocity: 1.0);
+
+    expect(friction.isDone(0.0), false);
+    expect(friction.x(0.0), 100);
+    expect(friction.dx(0.0), 400.0);
+
+    expect(friction.x(1.0) > 330 && friction.x(1.0) < 335, true);
+
+    expect(friction.dx(1.0), 120.0);
+    expect(friction.dx(2.0), 36.0);
+    expect(friction.dx(3.0), 10.8);
+    expect(friction.dx(4.0) < 3.5, true);
+
+    expect(friction.isDone(5.0), true);
+    expect(friction.x(5.0) > 431 && friction.x(5.0) < 432, true);
+  });
+
+  test('test_friction_through', () {
+    // Use a normal FrictionSimulation to generate start and end
+    // velocity and positions with drag = 0.025.
+    var startPosition = 10.0;
+    var startVelocity = 600.0;
+    var f = new FrictionSimulation(0.025, startPosition, startVelocity);
+    var endPosition = f.x(1.0);
+    var endVelocity = f.dx(1.0);
+    expect(endPosition, greaterThan(startPosition));
+    expect(endVelocity, lessThan(startVelocity));
+
+    // Verify that that the "through" FrictionSimulation ends up at
+    // endPosition and endVelocity; implies that it computed the right
+    // value for _drag.
+    var friction = new FrictionSimulation.through(
+        startPosition, endPosition, startVelocity, endVelocity);
+    expect(friction.isDone(0.0), false);
+    expect(friction.x(0.0), 10.0);
+    expect(friction.dx(0.0), 600.0);
+
+    double epsilon = 1e-4;
+    expect(friction.isDone(1.0 + epsilon), true);
+    expect(friction.x(1.0), closeTo(endPosition, epsilon));
+    expect(friction.dx(1.0), closeTo(endVelocity, epsilon));
+
+    // Same scenario as above except that the velocities are
+    // are negative.
+    startPosition = 1000.0;
+    startVelocity = -500.0;
+    f = new FrictionSimulation(0.025, 1000.0, -500.0);
+    endPosition = f.x(1.0);
+    endVelocity = f.dx(1.0);
+    expect(endPosition, lessThan(startPosition));
+    expect(endVelocity, greaterThan(startVelocity));
+
+    friction = new FrictionSimulation.through(
+        startPosition, endPosition, startVelocity, endVelocity);
+    expect(friction.isDone(1.0 + epsilon), true);
+    expect(friction.x(1.0), closeTo(endPosition, epsilon));
+    expect(friction.dx(1.0), closeTo(endVelocity, epsilon));
+  });
+
+  test('test_gravity', () {
+    var gravity = new GravitySimulation(200.0, 100.0, 600.0, 0.0);
+
+    expect(gravity.isDone(0.0), false);
+    expect(gravity.x(0.0), 100.0);
+    expect(gravity.dx(0.0), 0.0);
+
+    // Starts at 100
+    expect(gravity.x(0.25), 106.25);
+    expect(gravity.x(0.50), 125);
+    expect(gravity.x(0.75), 156.25);
+    expect(gravity.x(1.00), 200);
+    expect(gravity.x(1.25), 256.25);
+    expect(gravity.x(1.50), 325);
+    expect(gravity.x(1.75), 406.25);
+
+    // Starts at 0.0
+    expect(gravity.dx(0.25), 50.0);
+    expect(gravity.dx(0.50), 100);
+    expect(gravity.dx(0.75), 150.00);
+    expect(gravity.dx(1.00), 200.0);
+    expect(gravity.dx(1.25), 250.0);
+    expect(gravity.dx(1.50), 300);
+    expect(gravity.dx(1.75), 350);
+
+    expect(gravity.isDone(2.5), true);
+    expect(gravity.x(2.5), 725);
+    expect(gravity.dx(2.5), 500.0);
+  });
+
+  test('spring_types', () {
+    var crit = new SpringSimulation(new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 100.0), 0.0, 300.0, 0.0);
+    expect(crit.type, SpringType.criticallyDamped);
+
+    crit = new SpringSimulation(new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 100.0, ratio: 1.0), 0.0, 300.0, 0.0);
+    expect(crit.type, SpringType.criticallyDamped);
+
+    var under = new SpringSimulation(new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 100.0, ratio: 0.75), 0.0, 300.0, 0.0);
+    expect(under.type, SpringType.underDamped);
+
+    var over = new SpringSimulation(new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 100.0, ratio: 1.25), 0.0, 300.0, 0.0);
+    expect(over.type, SpringType.overDamped);
+
+    // Just so we don't forget how to create a desc without the ratio.
+    var other = new SpringSimulation(
+        new SpringDescription(mass: 1.0, springConstant: 100.0, damping: 20.0),
+        0.0, 20.0, 20.0);
+    expect(other.type, SpringType.criticallyDamped);
+  });
+
+  test('crit_spring', () {
+    var crit = new SpringSimulation(new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 100.0, ratio: 1.0), 0.0, 500.0, 0.0);
+
+    crit.tolerance = const Tolerance(distance: 0.01, velocity: 0.01);
+
+    expect(crit.type, SpringType.criticallyDamped);
+
+    expect(crit.isDone(0.0), false);
+    expect(crit.x(0.0), 0.0);
+    expect(crit.dx(0.0), 5000.0);
+
+    expect(crit.x(0.25).floor(), 458.0);
+    expect(crit.x(0.50).floor(), 496.0);
+    expect(crit.x(0.75).floor(), 499.0);
+
+    expect(crit.dx(0.25).floor(), 410);
+    expect(crit.dx(0.50).floor(), 33);
+    expect(crit.dx(0.75).floor(), 2);
+
+    expect(crit.isDone(1.50), true);
+    expect(crit.x(1.5) > 499.0 && crit.x(1.5) < 501.0, true);
+    expect(crit.dx(1.5) < 0.1, true /* basically within tolerance */);
+  });
+
+  test('overdamped_spring', () {
+    var over = new SpringSimulation(new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 100.0, ratio: 1.25), 0.0, 500.0, 0.0);
+
+    over.tolerance = const Tolerance(distance: 0.01, velocity: 0.01);
+
+    expect(over.type, SpringType.overDamped);
+
+    expect(over.isDone(0.0), false);
+    expect(over.x(0.0), 0.0);
+
+    expect(over.x(0.5).floor(), 445.0);
+    expect(over.x(1.0).floor(), 495.0);
+    expect(over.x(1.5).floor(), 499.0);
+
+    expect(over.dx(0.5).floor(), 273.0);
+    expect(over.dx(1.0).floor(), 22.0);
+    expect(over.dx(1.5).floor(), 1.0);
+
+    expect(over.isDone(3.0), true);
+  });
+
+  test('underdamped_spring', () {
+    var under = new SpringSimulation(new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 100.0, ratio: 0.25), 0.0, 300.0, 0.0);
+    expect(under.type, SpringType.underDamped);
+
+    expect(under.isDone(0.0), false);
+
+    // Overshot with negative velocity
+    expect(under.x(1.0).floor(), 325);
+    expect(under.dx(1.0).floor(), -65);
+
+    expect(under.dx(6.0).floor(), 0.0);
+    expect(under.x(6.0).floor(), 299);
+
+    expect(under.isDone(6.0), true);
+  });
+
+  test('test_kinetic_scroll', () {
+    var spring = new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 50.0, ratio: 0.5);
+
+    var scroll = new ScrollSimulation(100.0, 800.0, 0.0, 300.0, spring, 0.3);
+    scroll.tolerance = const Tolerance(velocity: 0.5, distance: 0.1);
+    expect(scroll.isDone(0.0), false);
+    expect(scroll.isDone(0.5), false); // switch from friction to spring
+    expect(scroll.isDone(3.5), true);
+
+    var scroll2 = new ScrollSimulation(100.0, -800.0, 0.0, 300.0, spring, 0.3);
+    scroll2.tolerance = const Tolerance(velocity: 0.5, distance: 0.1);
+    expect(scroll2.isDone(0.0), false);
+    expect(scroll2.isDone(0.5), false); // switch from friction to spring
+    expect(scroll2.isDone(3.5), true);
+  });
+
+  test('scroll_with_inf_edge_ends', () {
+    var spring = new SpringDescription.withDampingRatio(
+        mass: 1.0, springConstant: 50.0, ratio: 0.5);
+
+    var scroll =
+        new ScrollSimulation(100.0, 400.0, 0.0, double.INFINITY, spring, 0.3);
+    scroll.tolerance = const Tolerance(velocity: 1.0);
+
+    expect(scroll.isDone(0.0), false);
+    expect(scroll.x(0.0), 100);
+    expect(scroll.dx(0.0), 400.0);
+
+    expect(scroll.x(1.0) > 330 && scroll.x(1.0) < 335, true);
+
+    expect(scroll.dx(1.0), 120.0);
+    expect(scroll.dx(2.0), 36.0);
+    expect(scroll.dx(3.0), 10.8);
+    expect(scroll.dx(4.0) < 3.5, true);
+
+    expect(scroll.isDone(5.0), true);
+    expect(scroll.x(5.0) > 431 && scroll.x(5.0) < 432, true);
+
+    // We should never switch
+    expect(scroll.currentIntervalOffset, 0.0);
+  });
+
+  test('over/under scroll spring', () {
+    var spring = new SpringDescription.withDampingRatio(mass: 1.0, springConstant: 170.0, ratio: 1.1);
+    var scroll = new ScrollSimulation(500.0, -7500.0, 0.0, 1000.0, spring, 0.025);
+    scroll.tolerance = new Tolerance(velocity: 45.0, distance: 1.5);
+
+    expect(scroll.isDone(0.0), false);
+    expect(scroll.x(0.0), closeTo(500.0, .0001));
+    expect(scroll.dx(0.0), closeTo(-7500.0, .0001));
+
+    expect(scroll.isDone(0.025), false);
+    expect(scroll.x(0.025), closeTo(320.0, 1.0));
+    expect(scroll.dx(0.25), closeTo(-2982, 1.0));
+
+    expect(scroll.isDone(2.0), true);
+    expect(scroll.x(2.0), 0.0);
+    expect(scroll.dx(2.0), closeTo(0.0, 45.0));
+  });
+}