From 0b4ffc1ade2f4daf885e97203e639a68fcb5d672 Mon Sep 17 00:00:00 2001 From: ruinivist Date: Sun, 27 Jul 2025 20:03:13 +0000 Subject: [PATCH] define a type for id and use uuids instead of existing int --- example/lib/dynamic_widget_example.dart | 13 +++-- example/lib/simple_example.dart | 6 +- example/pubspec.lock | 40 ++++++++++++++ lib/core/controller/controller.dart | 37 +++++++------ lib/core/controller/debug.dart | 2 +- lib/core/controller/types.dart | 4 +- lib/core/render.dart | 8 +-- pubspec.yaml | 73 +++++++++++++------------ 8 files changed, 117 insertions(+), 66 deletions(-) diff --git a/example/lib/dynamic_widget_example.dart b/example/lib/dynamic_widget_example.dart index dae6783..83a7d47 100644 --- a/example/lib/dynamic_widget_example.dart +++ b/example/lib/dynamic_widget_example.dart @@ -12,6 +12,11 @@ class DynamicWidgetExample extends StatefulWidget { class _DynamicWidgetExampleState extends State { final LazyCanvasController controller = LazyCanvasController(debug: false); + // Store child IDs for later reference + late CanvasChildId selfManagedId; + late CanvasChildId externalDataId; + late CanvasChildId reactiveId; + // Simulate some dynamic data int counter = 0; String message = "Hello World"; @@ -25,16 +30,16 @@ class _DynamicWidgetExampleState extends State { void _setupWidgets() { // Approach 1: Using StatefulWidget with state inside the widget getting lost if unmounted - controller.addChild(const Offset(0, 0), SelfManagedCounterWidget(label: "Self-Managed")); + selfManagedId = controller.addChild(const Offset(0, 0), SelfManagedCounterWidget(label: "Self-Managed")); // Approach 2: Using updateChildWidget, you need this since the children dep tree is direcly managed by the controller - controller.addChild( + externalDataId = controller.addChild( const Offset(200, 0), ExternalDataWidget(counter: counter, message: message, color: selectedColor), ); // Approach 3: Using ValueListenableBuilder for reactive updates - controller.addChild( + reactiveId = controller.addChild( const Offset(400, 0), ValueListenableBuilder( valueListenable: _counterNotifier, @@ -71,7 +76,7 @@ class _DynamicWidgetExampleState extends State { // Approach 2: Update the widget when external data changes // Use ValueKey to ensure Flutter recognizes this as a different widget controller.updateChildWidget( - 1, // Widget ID (second widget added) + externalDataId, // Use the stored widget ID ExternalDataWidget( // key: ValueKey('external_$counter'), // Force rebuild with unique key counter: counter, diff --git a/example/lib/simple_example.dart b/example/lib/simple_example.dart index 7328ef5..e33730b 100644 --- a/example/lib/simple_example.dart +++ b/example/lib/simple_example.dart @@ -12,21 +12,23 @@ class SimpleExample extends StatefulWidget { class _SimpleExampleState extends State { final LazyCanvasController controller = LazyCanvasController(debug: true); + final List childIds = []; @override void initState() { super.initState(); for (int i = 0; i < 10; i++) { - controller.addChild( + final id = controller.addChild( Offset((i % 80) * 100.0, (i ~/ 80) * 100.0), GestureDetector( onTap: () { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Tapped a container'))); - controller.focusOnChild(i); + controller.focusOnChild(childIds[i]); }, child: InfoContainer(color: Colors.primaries[i % Colors.primaries.length]), ), ); + childIds.add(id); } controller.addChild( const Offset(0, 200), diff --git a/example/pubspec.lock b/example/pubspec.lock index 59f9fe2..967c0ea 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -159,6 +175,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -199,6 +223,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: diff --git a/lib/core/controller/controller.dart b/lib/core/controller/controller.dart index 9202e78..b4ec27e 100644 --- a/lib/core/controller/controller.dart +++ b/lib/core/controller/controller.dart @@ -9,26 +9,27 @@ import '../../utils/size_extensions.dart'; import '../../utils/conversions.dart'; import '../../utils/styles.dart'; import '../render.dart'; +import 'package:uuid/uuid.dart'; part 'debug.dart'; part 'types.dart'; /// Controller for [LazyCanvas] class LazyCanvasController with ChangeNotifier { - int _nextId = 0; // surely we won't run out of IDs, Clueless + final Uuid _uuid = Uuid(); Offset _gsTopLeftOffset = Offset.zero; double _baseScale = 1, _scale = 1; late Size _canvasSize; - final HashMap _children = HashMap(); // int for IDs + final HashMap _children = HashMap(); // CanvasChildId for IDs final Offset? _buildCacheExtent; late Offset _buildExtent; final Size _hashCellSize; bool _init = false; - late final SpatialHashing _spatialHash; + late final SpatialHashing _spatialHash; TickerProvider? _ticker; late BuildContext _context; bool _firstBuild = true; - int? _focusChildOnInit; // if set, will focus on this child on first build + CanvasChildId? _focusChildOnInit; // if set, will focus on this child on first build CanvasBackground background; // these are used to cache result of widgetsWithScreenPositions List _lastRenderedWidgets = []; @@ -50,7 +51,7 @@ class LazyCanvasController with ChangeNotifier { // only top left is considered so if a widget has long width, it'll not be rendered // unless the cache extent is sufficient { - _spatialHash = SpatialHashing(cellSize: _hashCellSize); + _spatialHash = SpatialHashing(cellSize: _hashCellSize); } // ==================== Getters ==================== @@ -79,7 +80,7 @@ class LazyCanvasController with ChangeNotifier { } /// Called when a child widget's size changes. - void onChildSizeChange(int id, Size size) { + void onChildSizeChange(CanvasChildId id, Size size) { _children[id]!.lastRenderedSize = size; } @@ -107,23 +108,24 @@ class LazyCanvasController with ChangeNotifier { /// Add a child at a given position with a widget. Returns the child ID. /// You need the child size for optimising the focus on child - int addChild(Offset position, Widget widget, {bool focusOnInit = false, Size? childSize}) { + CanvasChildId addChild(Offset position, Widget widget, {bool focusOnInit = false, Size? childSize}) { + final id = _uuid.v4(); if (focusOnInit) { assert( childSize != null, 'Child size must be provided when focusing on init (to know positions before any build). Else, you must call focusOnChild in a post frame callback.', ); assert(_focusChildOnInit == null, 'Focus child on init is already set. Cannot set it again.'); - _focusChildOnInit = _nextId; + _focusChildOnInit = id; } - _children[_nextId] = _ChildInfo(gsPosition: position, widget: widget, lastRenderedSize: childSize); - _spatialHash.add(position.toPoint(), _nextId); // add to spatial hash + _children[id] = _ChildInfo(gsPosition: position, widget: widget, lastRenderedSize: childSize); + _spatialHash.add(position.toPoint(), id); // add to spatial hash markDirty(); - return _nextId++; + return id; } /// Remove a child by its ID. - void removeChild(int id) { + void removeChild(CanvasChildId id) { if (!_children.containsKey(id)) { throw _ChildNotFoundException; } @@ -136,12 +138,11 @@ class LazyCanvasController with ChangeNotifier { void clear() { _children.clear(); _spatialHash.clear(); - _nextId = 0; markDirty(); } /// Update the position of a child by its ID. - int updatePosition(int id, Offset newPosition) { + CanvasChildId updatePosition(CanvasChildId id, Offset newPosition) { if (_children.containsKey(id)) { _children[id]!.gsPosition = newPosition; markDirty(); @@ -152,7 +153,7 @@ class LazyCanvasController with ChangeNotifier { } /// Update a child's widget. - void updateChildWidget(int id, Widget newWidget) { + void updateChildWidget(CanvasChildId id, Widget newWidget) { if (_children.containsKey(id)) { final child = _children[id]!; // Create a new _ChildInfo with the new widget @@ -165,7 +166,7 @@ class LazyCanvasController with ChangeNotifier { } /// Get the position of a child by its ID. - Offset getPosition(int id) { + Offset getPosition(CanvasChildId id) { if (_children.containsKey(id)) { return _children[id]!.gsPosition; } else { @@ -244,7 +245,7 @@ class LazyCanvasController with ChangeNotifier { }).toList(); } - List _childrenWithinBuildArea(Offset center, Offset extent) { + List _childrenWithinBuildArea(Offset center, Offset extent) { Offset halfExtent = Offset((extent.dx / (2 * _scale)).ceilToDouble(), (extent.dy / (2 * _scale)).ceilToDouble()); final items = _spatialHash.getPointsAround(center.toPoint(), halfExtent); return items.map((item) => item.data).toList(); // data is the child id here @@ -274,7 +275,7 @@ class LazyCanvasController with ChangeNotifier { /// an offstage rendering will be used ( double render ) /// Preferred horizontal margin used for [ScalingMode.fitInViewport]. void focusOnChild( - int id, { + CanvasChildId id, { ScalingMode scalingMode = ScalingMode.keepScale, bool animate = true, double preferredHorizontalMargin = 16, diff --git a/lib/core/controller/debug.dart b/lib/core/controller/debug.dart index 43068f4..76c0c14 100644 --- a/lib/core/controller/debug.dart +++ b/lib/core/controller/debug.dart @@ -1,7 +1,7 @@ part of 'controller.dart'; class _Debug extends StatelessWidget { - final int id; + final CanvasChildId id; final Offset gs, ss; final Widget child; diff --git a/lib/core/controller/types.dart b/lib/core/controller/types.dart index 4460b71..eb8cef6 100644 --- a/lib/core/controller/types.dart +++ b/lib/core/controller/types.dart @@ -15,7 +15,7 @@ class _ChildInfo { } class ChildInfo { - int id; + CanvasChildId id; Offset gsPosition; Offset ssPosition; Widget child; @@ -23,3 +23,5 @@ class ChildInfo { } enum ScalingMode { resetScale, keepScale, fitInViewport } + +typedef CanvasChildId = String; diff --git a/lib/core/render.dart b/lib/core/render.dart index 4670d1b..80ff4b0 100644 --- a/lib/core/render.dart +++ b/lib/core/render.dart @@ -81,7 +81,7 @@ class _LazyCanvasState extends State with TickerProviderStateMixin childrenIds; + final List childrenIds; final List ssPositions; final double scale; final Offset gridSpaceOffset; @@ -127,7 +127,7 @@ class _CanvasRenderObject extends MultiChildRenderObjectWidget { class _CanvasWidgetParentData extends ContainerBoxParentData { // there is already an "offset" defined in BoxParentdata that is exactly what I want - late int id; + late CanvasChildId id; late double scale; // scale is same for all rn but this makes it trivial to expand to children with diff scales } @@ -136,7 +136,7 @@ class _CanvasRenderBox extends RenderBox ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { CanvasBackground _canvasBackground; - List _childrenIds; + List _childrenIds; List _ssPositions; Offset _gridSpaceOffset; double _scale; @@ -175,7 +175,7 @@ class _CanvasRenderBox extends RenderBox } } - set childrenIds(List childrenIds) { + set childrenIds(List childrenIds) { if (_childrenIds != childrenIds) { _childrenIds = childrenIds; } diff --git a/pubspec.yaml b/pubspec.yaml index 1affd9c..6695a3c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,17 +4,18 @@ version: 0.0.1 homepage: environment: - sdk: ^3.8.1 - flutter: ">=1.17.0" + sdk: ^3.8.1 + flutter: ">=1.17.0" dependencies: - flutter: - sdk: flutter + flutter: + sdk: flutter + uuid: ^4.5.1 dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -22,33 +23,33 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/to/asset-from-package - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/to/font-from-package + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package