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);
});
}