add animation on focus and center

This commit is contained in:
2025-07-07 21:04:31 +00:00
parent 10bb69a15f
commit 5d7152ee05
3 changed files with 84 additions and 22 deletions
+55 -10
View File
@@ -26,6 +26,7 @@ class CanvasController with ChangeNotifier {
bool _init = false;
late final SpatialHashing<int> _spatialHash;
List<ChildInfo> _renderedChildren = [];
TickerProvider? _ticker;
bool debug;
@@ -46,7 +47,7 @@ class CanvasController with ChangeNotifier {
Offset get _gsCenter => ssToGs(_ssCenter, _gsTopLeftOffset, _scale);
bool get _cleanRenderState => _init && _lastProcessedOffset == _gsTopLeftOffset && _lastProcessedScale == _scale;
// ==================== Public Functions ====================
// ==================== Callback Functions ====================
/// Update the canvas size when the widget size changes.
void onCanvasSizeChange(Size size) {
@@ -67,6 +68,10 @@ class CanvasController with ChangeNotifier {
_children[id]!.lastRenderedSize = size;
}
void setTickerProvider(TickerProvider ticker) {
_ticker = ticker;
}
// ==================== Child Management ====================
/// Add a child at a given position with a builder. Returns the child ID.
@@ -176,15 +181,20 @@ class CanvasController with ChangeNotifier {
// ==================== Centering & Focus Functions ====================
/// Center the canvas so that the given screen-space offset is at the center of the viewport.
void centerOnScreenOffset(Offset ssOffset) {
centerOnGridOffset(ssToGs(ssOffset, _gsTopLeftOffset, _scale));
void centerOnScreenOffset(Offset ssOffset, {bool animate = true}) {
centerOnGridOffset(ssToGs(ssOffset, _gsTopLeftOffset, _scale), animate: animate);
}
/// Center the canvas so that the given grid-space offset is at the center of the viewport.
void centerOnGridOffset(Offset gsOffset) {
void centerOnGridOffset(Offset gsOffset, {bool animate = true}) {
// if 2x scale you need to adjust lesser
_gsTopLeftOffset = gsOffset + (canvasSize * (2 * scale)).toOffset();
notifyListeners();
final newGsTopLeft = gsOffset + (canvasSize * (2 * scale)).toOffset();
if (animate) {
animateToOffsetAndScale(offset: newGsTopLeft, scale: _scale);
} else {
_gsTopLeftOffset = newGsTopLeft;
notifyListeners();
}
}
/// Focus the viewport on a child by its ID, with a margin in screen-space.
@@ -195,6 +205,7 @@ class CanvasController with ChangeNotifier {
BuildContext context,
int id, {
ScalingMode scalingMode = ScalingMode.keepScale,
bool animate = true,
double preferredHorizontalMargin = 16,
Size? childSize,
forceRedraw = false,
@@ -218,22 +229,56 @@ class CanvasController with ChangeNotifier {
where c is child size in screen space and m is margin
*/
double newScale = _scale;
Offset newGsTopLeft = _gsTopLeftOffset;
switch (scalingMode) {
case ScalingMode.keepScale:
// do nothing
break;
case ScalingMode.resetScale:
_scale = 1;
newScale = 1;
case ScalingMode.fitInViewport:
// the scale needs to be determined in this case
// and hence a margin is needed to constrain on x, to get the scale, we then center it along y
_scale = (canvasSize.width - 2 * preferredHorizontalMargin) / childSize!.width;
newScale = (canvasSize.width - 2 * preferredHorizontalMargin) / childSize!.width;
break;
}
final marginOffset = ((canvasSize.toOffset() - (childSize! * scale).toOffset()) / (2 * scale)).makeAtleast(0);
_gsTopLeftOffset = childInfo.gsPosition - marginOffset;
newGsTopLeft = childInfo.gsPosition - marginOffset;
notifyListeners();
if (animate) {
animateToOffsetAndScale(offset: newGsTopLeft, scale: newScale); // anim will notifyListeners
} else {
_gsTopLeftOffset = newGsTopLeft;
_scale = newScale;
notifyListeners();
}
}
// ==================== Animation ====================
Future<void> animateToOffsetAndScale({
required Offset offset,
required double scale,
Duration duration = const Duration(milliseconds: 300),
Curve curve = Curves.easeInOut,
}) async {
final anim = AnimationController(vsync: _ticker!, duration: duration);
final offsetTween = Tween<Offset>(begin: _gsTopLeftOffset, end: offset);
final scaleTween = Tween<double>(begin: _scale, end: scale);
final offsetAnimation = offsetTween.animate(CurvedAnimation(parent: anim, curve: curve));
final scaleAnimation = scaleTween.animate(CurvedAnimation(parent: anim, curve: curve));
anim.addListener(() {
_gsTopLeftOffset = offsetAnimation.value;
_scale = scaleAnimation.value;
notifyListeners();
});
await anim.forward();
anim.dispose();
}
}