add caching to widgetsWithScreenPositions()

This commit is contained in:
2025-07-27 02:25:10 +00:00
parent e5f33c6212
commit 61d0ec4e67
+38 -12
View File
@@ -30,6 +30,11 @@ class LazyCanvasController with ChangeNotifier {
bool _firstBuild = true; bool _firstBuild = true;
int? _focusChildOnInit; // if set, will focus on this child on first build int? _focusChildOnInit; // if set, will focus on this child on first build
CanvasBackground background; CanvasBackground background;
// these are used to cache result of widgetsWithScreenPositions
List<ChildInfo> _lastRenderedWidgets = [];
Offset? _lastProcessedOffset;
double? _lastProcessedScale;
bool _markDirty = false; // do any of the non scale or offset changes require a rebuild?
bool debug; bool debug;
final Duration defaultAnimationDuration; final Duration defaultAnimationDuration;
@@ -54,6 +59,7 @@ class LazyCanvasController with ChangeNotifier {
Size get canvasSize => _canvasSize; Size get canvasSize => _canvasSize;
Offset get _ssCenter => Offset(_canvasSize.width / 2, _canvasSize.height / 2); Offset get _ssCenter => Offset(_canvasSize.width / 2, _canvasSize.height / 2);
Offset get _gsCenter => ssToGs(_ssCenter, _gsTopLeftOffset, _scale); Offset get _gsCenter => ssToGs(_ssCenter, _gsTopLeftOffset, _scale);
bool get _renderCacheDirty => _lastProcessedOffset != _gsTopLeftOffset || _lastProcessedScale != _scale || _markDirty;
// ==================== Callback Functions ==================== // ==================== Callback Functions ====================
@@ -68,7 +74,7 @@ class LazyCanvasController with ChangeNotifier {
// if the first init, re-render as I don't have the canvas size to build widgets // if the first init, re-render as I don't have the canvas size to build widgets
if (!_init) { if (!_init) {
_init = true; _init = true;
Future.microtask(notifyListeners); Future.microtask(markDirty);
} }
} }
@@ -86,6 +92,17 @@ class LazyCanvasController with ChangeNotifier {
_context = context; _context = context;
} }
// ==================== Utils ====================
void markDirty() {
_markDirty = true;
// this is done instead of just notifyListeners() so as to differentiate
// betweena adhoc calls to widgetsWithScreenPositions
// if you need to call notifyListeners() from within this class,
// it should always be with markDirty()
notifyListeners();
}
// ==================== Child Management ==================== // ==================== Child Management ====================
/// Add a child at a given position with a widget. Returns the child ID. /// Add a child at a given position with a widget. Returns the child ID.
@@ -101,7 +118,7 @@ class LazyCanvasController with ChangeNotifier {
} }
_children[_nextId] = _ChildInfo(gsPosition: position, widget: widget, lastRenderedSize: childSize); _children[_nextId] = _ChildInfo(gsPosition: position, widget: widget, lastRenderedSize: childSize);
_spatialHash.add(position.toPoint(), _nextId); // add to spatial hash _spatialHash.add(position.toPoint(), _nextId); // add to spatial hash
notifyListeners(); markDirty();
return _nextId++; return _nextId++;
} }
@@ -112,7 +129,7 @@ class LazyCanvasController with ChangeNotifier {
} }
_spatialHash.remove(_children[id]!.gsPosition.toPoint()); _spatialHash.remove(_children[id]!.gsPosition.toPoint());
_children.remove(id); _children.remove(id);
notifyListeners(); markDirty();
} }
/// Remove all children. Does not change where you are on the canvas. /// Remove all children. Does not change where you are on the canvas.
@@ -120,14 +137,14 @@ class LazyCanvasController with ChangeNotifier {
_children.clear(); _children.clear();
_spatialHash.clear(); _spatialHash.clear();
_nextId = 0; _nextId = 0;
notifyListeners(); markDirty();
} }
/// Update the position of a child by its ID. /// Update the position of a child by its ID.
int updatePosition(int id, Offset newPosition) { int updatePosition(int id, Offset newPosition) {
if (_children.containsKey(id)) { if (_children.containsKey(id)) {
_children[id]!.gsPosition = newPosition; _children[id]!.gsPosition = newPosition;
notifyListeners(); markDirty();
return id; return id;
} else { } else {
throw _ChildNotFoundException; throw _ChildNotFoundException;
@@ -141,7 +158,7 @@ class LazyCanvasController with ChangeNotifier {
// Create a new _ChildInfo with the new widget // Create a new _ChildInfo with the new widget
_children[id] = _ChildInfo(gsPosition: child.gsPosition, widget: newWidget) _children[id] = _ChildInfo(gsPosition: child.gsPosition, widget: newWidget)
..lastRenderedSize = child.lastRenderedSize; ..lastRenderedSize = child.lastRenderedSize;
notifyListeners(); markDirty();
} else { } else {
throw _ChildNotFoundException; throw _ChildNotFoundException;
} }
@@ -183,7 +200,7 @@ class LazyCanvasController with ChangeNotifier {
_gsTopLeftOffset -= details.focalPointDelta / _scale; _gsTopLeftOffset -= details.focalPointDelta / _scale;
} }
notifyListeners(); markDirty();
} }
/// Increment or decrement the scale by an additive delta value. /// Increment or decrement the scale by an additive delta value.
@@ -192,7 +209,7 @@ class LazyCanvasController with ChangeNotifier {
final newScale = _scale + delta; final newScale = _scale + delta;
_gsTopLeftOffset = newGsTopLeftOnScaling(_gsTopLeftOffset, focalPoint, _scale, newScale); _gsTopLeftOffset = newGsTopLeftOnScaling(_gsTopLeftOffset, focalPoint, _scale, newScale);
_scale = newScale; _scale = newScale;
notifyListeners(); markDirty();
} }
// ==================== Positioning Logic ==================== // ==================== Positioning Logic ====================
@@ -207,9 +224,18 @@ class LazyCanvasController with ChangeNotifier {
_firstBuild = false; _firstBuild = false;
} }
if (!_renderCacheDirty && !forceRebuild) {
// if the render cache is not dirty, we can use the cached result
return _lastRenderedWidgets;
}
_lastProcessedOffset = _gsTopLeftOffset;
_lastProcessedScale = _scale;
_markDirty = false;
final idsToBuild = _childrenWithinBuildArea(_gsCenter, _buildExtent); final idsToBuild = _childrenWithinBuildArea(_gsCenter, _buildExtent);
return idsToBuild.map((id) { return _lastRenderedWidgets = idsToBuild.map((id) {
final item = _children[id]!; final item = _children[id]!;
final ssPosition = gsToSs(item.gsPosition, _gsTopLeftOffset, _scale); final ssPosition = gsToSs(item.gsPosition, _gsTopLeftOffset, _scale);
var child = item.widget; var child = item.widget;
@@ -239,7 +265,7 @@ class LazyCanvasController with ChangeNotifier {
animateToOffsetAndScale(offset: newGsTopLeft, duration: duration, scale: _scale); animateToOffsetAndScale(offset: newGsTopLeft, duration: duration, scale: _scale);
} else { } else {
_gsTopLeftOffset = newGsTopLeft; _gsTopLeftOffset = newGsTopLeft;
notifyListeners(); markDirty();
} }
} }
@@ -299,7 +325,7 @@ class LazyCanvasController with ChangeNotifier {
} else { } else {
_gsTopLeftOffset = newGsTopLeft; _gsTopLeftOffset = newGsTopLeft;
_scale = newScale; _scale = newScale;
notifyListeners(); markDirty();
} }
} }
@@ -322,7 +348,7 @@ class LazyCanvasController with ChangeNotifier {
anim.addListener(() { anim.addListener(() {
_gsTopLeftOffset = offsetAnimation.value; _gsTopLeftOffset = offsetAnimation.value;
_scale = scaleAnimation.value; _scale = scaleAnimation.value;
notifyListeners(); markDirty();
}); });
await anim.forward(); await anim.forward();