PanBehaviour class
PanBehaviour
controls animated panning of a map viewport.
class PanBehaviour { const double ACCELERATION = -2.0; // px / (100ms)² const double SPEED = 20.0; // px / 100ms const int DELTA_T = 20; // ms, duration of animation step /// the map viewport controlled by this behaviour final MapViewport viewport; /// Creates a new behaviour controlling [viewport]. PanBehaviour(this.viewport); /** * Animate the panning of the map viewport by a vector given * by [panBy]. */ void animate(Point2D panBy) { var dist = math.sqrt(math.pow(panBy.x,2) + math.pow(panBy.y,2)); var theta = math.asin(panBy.y / dist); if (panBy.x <= 0) { theta = math.PI - theta; } var fx = math.cos(theta); //y-axis is inverted, therefore not // -math.sin(theta) var fy = math.sin(theta); now() => new DateTime.now().millisecondsSinceEpoch; pan(dx,dy) { var p = new Point2D(dx,dy).toInt(); if (p != new Point2D.origin()) viewport.pan(p); } bounded(num v, num bound) => bound < 0 ? math.max(v, bound) : math.min(v, bound); // pan long distance (fast) as much and possible, the complete // the future with the remaining pan distance Future<Point2D> panLongDistance(Point2D panBy) { var completer = new Completer<Point2D>(); var pannedX = 0; var pannedY = 0; bool isLongDistance() => (panBy.x - pannedX).abs() > 100 || (panBy.y - pannedY).abs() > 100; // animation step step(Timer timer){ if (!isLongDistance()) { timer.cancel(); var rest = new Point2D(panBy.x - pannedX, panBy.y - pannedY); completer.complete(rest); } else { var dx = bounded(40 * fx, panBy.x).toInt(); var dy = bounded(40 * fy, panBy.y).toInt(); pannedX += dx; pannedY += dy; pan(dx, dy); } } new Timer.periodic(new Duration(milliseconds: DELTA_T), step); return completer.future; } // pan a short distance, deaccelerate and make sure the final // point is reached panShortDistance(Point2D panBy) { panBy = panBy.toInt(); var lastX = 0; var lastY = 0; var initialTime = now(); // animation step step(Timer timer) { // scale time by 100 for kinetics calcuations var t = (now() - initialTime) / 100; var p = ACCELERATION * math.pow(t,2) / 2 + SPEED * t; var x = bounded(p * fx, panBy.x).toInt(); var y = bounded(p * fy, panBy.y).toInt(); var v = ACCELERATION * t + SPEED; if (v <= 0 || panBy == new Point2D(x,y)){ // last step - pan to the end point x = panBy.x; y = panBy.y; timer.cancel(); } var dx = x - lastX; var dy = y - lastY; lastX = x; lastY = y; pan(dx, dy); } new Timer.periodic(new Duration(milliseconds: DELTA_T), step); } panLongDistance(panBy).then((rest) { panShortDistance(rest); }); } }
Constructors
new PanBehaviour(MapViewport viewport) #
Creates a new behaviour controlling viewport.
PanBehaviour(this.viewport);
Properties
final MapViewport viewport #
the map viewport controlled by this behaviour
final MapViewport viewport
Methods
void animate(Point2D panBy) #
Animate the panning of the map viewport by a vector given by panBy.
void animate(Point2D panBy) { var dist = math.sqrt(math.pow(panBy.x,2) + math.pow(panBy.y,2)); var theta = math.asin(panBy.y / dist); if (panBy.x <= 0) { theta = math.PI - theta; } var fx = math.cos(theta); //y-axis is inverted, therefore not // -math.sin(theta) var fy = math.sin(theta); now() => new DateTime.now().millisecondsSinceEpoch; pan(dx,dy) { var p = new Point2D(dx,dy).toInt(); if (p != new Point2D.origin()) viewport.pan(p); } bounded(num v, num bound) => bound < 0 ? math.max(v, bound) : math.min(v, bound); // pan long distance (fast) as much and possible, the complete // the future with the remaining pan distance Future<Point2D> panLongDistance(Point2D panBy) { var completer = new Completer<Point2D>(); var pannedX = 0; var pannedY = 0; bool isLongDistance() => (panBy.x - pannedX).abs() > 100 || (panBy.y - pannedY).abs() > 100; // animation step step(Timer timer){ if (!isLongDistance()) { timer.cancel(); var rest = new Point2D(panBy.x - pannedX, panBy.y - pannedY); completer.complete(rest); } else { var dx = bounded(40 * fx, panBy.x).toInt(); var dy = bounded(40 * fy, panBy.y).toInt(); pannedX += dx; pannedY += dy; pan(dx, dy); } } new Timer.periodic(new Duration(milliseconds: DELTA_T), step); return completer.future; } // pan a short distance, deaccelerate and make sure the final // point is reached panShortDistance(Point2D panBy) { panBy = panBy.toInt(); var lastX = 0; var lastY = 0; var initialTime = now(); // animation step step(Timer timer) { // scale time by 100 for kinetics calcuations var t = (now() - initialTime) / 100; var p = ACCELERATION * math.pow(t,2) / 2 + SPEED * t; var x = bounded(p * fx, panBy.x).toInt(); var y = bounded(p * fy, panBy.y).toInt(); var v = ACCELERATION * t + SPEED; if (v <= 0 || panBy == new Point2D(x,y)){ // last step - pan to the end point x = panBy.x; y = panBy.y; timer.cancel(); } var dx = x - lastX; var dy = y - lastY; lastX = x; lastY = y; pan(dx, dy); } new Timer.periodic(new Duration(milliseconds: DELTA_T), step); } panLongDistance(panBy).then((rest) { panShortDistance(rest); }); }