diff --git a/CHANGELOG.md b/CHANGELOG.md index a22ea63..89323e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ ## 1.0.0 - Initial release + +## 1.0.1 + +- Update README + +## 1.0.2 + +- Again update README ( -\_-) diff --git a/README.md b/README.md index 90550fa..d5597ad 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ Infinite zoomable, pannable 2D canvas using spatial hash for only rendering what's visible. +Example: https://infinite-lazy-grid.pages.dev/ + +

+ +

+ ## Quick Start ```dart @@ -18,7 +24,7 @@ class _DemoCanvasState extends State { // all interactions go through the controller final controller = LazyCanvasController( background: const DotGridBackground(), - debug: true // wrapps each child with debug info visible on screen (positions, id) + debug: true, // wraps each child with debug info visible on screen (positions, id) ); @override @@ -28,6 +34,13 @@ class _DemoCanvasState extends State { for (int i = 0; i < 50; i++) { controller.addChild( Offset((i % 10) * 140.0, (i ~/ 10) * 140.0), + Container( + width: 100, + height: 100, + color: Colors.primaries[i % Colors.primaries.length], + alignment: Alignment.center, + child: Text('${i + 1}', style: const TextStyle(color: Colors.white)), + ), ); } } @@ -51,6 +64,7 @@ class _DemoCanvasState extends State { // one child, returns its id which is just a uuid string CanvasChildId oneChild = controller.addChild( const Offset(500, 1200), + const Icon(Icons.place, size: 32), ); // with custom widget @@ -115,13 +129,13 @@ LazyCanvasController( ); ``` -### Widet updates +### Widget updates -Since the args aren't directly available for you to place in the build tree. Child rebuilds can be handled in three ways: +Since the args aren't directly available for you to place in the build tree, child rebuilds can be handled in three ways: -1. _Stateful widget child_: Child handles it's own updates but state is lost when unmounted. -2. _Manual update_: `updateChildWidget(id, newWidget)`. -3. _Child listens to external state_: Some `Listenable` or some state management library like `Provider` etc that rebuild child when data changes. +1. Stateful widget child: Child handles its own updates but state is lost when unmounted. +2. Manual update: `updateChildWidget(id, newWidget)`. +3. Child listens to external state: Some `Listenable` or a state management library like Provider, etc., that rebuilds the child when data changes. ### Size based optimisations diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..1173b75 Binary files /dev/null and b/demo.gif differ diff --git a/example/README.md b/example/README.md index 887b2a7..29dc254 100644 --- a/example/README.md +++ b/example/README.md @@ -1,8 +1,8 @@ # Demos -Contains 4 small focused demos. +Contains four small, focused demos. -- `Simple Example` – minimal setup, add nodes, pan, zoom, focus. -- `Build Counts Example` – shows build / culling behavior; watch console for build counts. -- `Widget State Updates Example` – three patterns for dynamic / reactive child content. -- `Render Callbacks Example` – logs enter / exit render area callbacks and overlays debug info. +- Simple Example – minimal setup, add nodes, pan, zoom, focus. +- Build Counts Example – shows build/culling behavior; watch console for build counts. +- Widget State Updates Example – three patterns for dynamic/reactive child content. +- Render Callbacks Example – logs enter/exit render area callbacks and overlays debug info. diff --git a/lib/core/background.dart b/lib/core/background.dart index 63bd207..0404be1 100644 --- a/lib/core/background.dart +++ b/lib/core/background.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'dart:ui' as ui show FragmentProgram; // for runtime shader -import 'package:flutter/rendering.dart' show RendererBinding; // to mark repaint after async load +import 'package:flutter/rendering.dart' + show RendererBinding; // to mark repaint after async load /// Abstract definition for a [LazyCanvas] background. abstract class CanvasBackground { @@ -12,26 +13,53 @@ abstract class CanvasBackground { /// different kinds of backgrounds /// [screenOffset] is the screen space offset for clipping /// [canvasOffset] is the grid space offset from the controller - void paint(Canvas canvas, Offset screenOffset, Offset canvasOffset, double scale, Size canvasSize); + void paint( + Canvas canvas, + Offset screenOffset, + Offset canvasOffset, + double scale, + Size canvasSize, + ); } class NoBackground extends CanvasBackground { const NoBackground(); @override - void paint(Canvas canvas, Offset screenOffset, Offset canvasOffset, double scale, Size canvasSize) {} + void paint( + Canvas canvas, + Offset screenOffset, + Offset canvasOffset, + double scale, + Size canvasSize, + ) {} } -class SingleColorBackround extends CanvasBackground { - const SingleColorBackround(Color backgroundColor) : super(bgColor: backgroundColor); +class SingleColorBackground extends CanvasBackground { + const SingleColorBackground(Color backgroundColor) + : super(bgColor: backgroundColor); @override - void paint(Canvas canvas, Offset screenOffset, Offset canvasOffset, double scale, Size canvasSize) { + void paint( + Canvas canvas, + Offset screenOffset, + Offset canvasOffset, + double scale, + Size canvasSize, + ) { final paint = Paint() ..color = bgColor ..style = PaintingStyle.fill; - canvas.drawRect(Rect.fromLTWH(screenOffset.dx, screenOffset.dy, canvasSize.width, canvasSize.height), paint); + canvas.drawRect( + Rect.fromLTWH( + screenOffset.dx, + screenOffset.dy, + canvasSize.width, + canvasSize.height, + ), + paint, + ); } } @@ -59,18 +87,21 @@ class DotGridBackground extends CanvasBackground { static void _ensureProgramLoaded() { if (_program != null || _programFuture != null) return; try { - _programFuture = ui.FragmentProgram.fromAsset('packages/infinite_lazy_grid/shaders/dot_grid.frag') - ..then((p) { - _program = p; - // Request a repaint when shader becomes available - try { - for (final renderView in RendererBinding.instance.renderViews) { - renderView.markNeedsPaint(); - } - } catch (_) {} - }).catchError((_) { - // keep _program null; we'll fallback to CPU/simple paint - }); + _programFuture = + ui.FragmentProgram.fromAsset( + 'packages/infinite_lazy_grid/shaders/dot_grid.frag', + ) + ..then((p) { + _program = p; + // Request a repaint when shader becomes available + try { + for (final renderView in RendererBinding.instance.renderViews) { + renderView.markNeedsPaint(); + } + } catch (_) {} + }).catchError((_) { + // keep _program null; we'll fallback to CPU/simple paint + }); } catch (_) { // fromAsset may throw synchronously on unsupported platforms _programFuture = null; @@ -79,12 +110,23 @@ class DotGridBackground extends CanvasBackground { } @override - void paint(Canvas canvas, Offset screenOffset, Offset canvasOffset, double scale, Size canvasSize) { + void paint( + Canvas canvas, + Offset screenOffset, + Offset canvasOffset, + double scale, + Size canvasSize, + ) { // Always draw background fill (also serves as fallback while shader loads) final bgPaint = Paint() ..color = backgroundColor ..style = PaintingStyle.fill; - final rect = Rect.fromLTWH(screenOffset.dx, screenOffset.dy, canvasSize.width, canvasSize.height); + final rect = Rect.fromLTWH( + screenOffset.dx, + screenOffset.dy, + canvasSize.width, + canvasSize.height, + ); // Ensure shader load started _ensureProgramLoaded(); @@ -103,8 +145,14 @@ class DotGridBackground extends CanvasBackground { // Phase to align grid with world origin under pan/zoom double modPositive(double a, double m) => ((a % m) + m) % m; final sign = naturalPan ? 1.0 : -1.0; - final phaseXPx = modPositive(sign * canvasOffset.dx * scale, scaledSpacingPx == 0 ? 1 : scaledSpacingPx); - final phaseYPx = modPositive(sign * canvasOffset.dy * scale, scaledSpacingPx == 0 ? 1 : scaledSpacingPx); + final phaseXPx = modPositive( + sign * canvasOffset.dx * scale, + scaledSpacingPx == 0 ? 1 : scaledSpacingPx, + ); + final phaseYPx = modPositive( + sign * canvasOffset.dy * scale, + scaledSpacingPx == 0 ? 1 : scaledSpacingPx, + ); // Snap origin: use exact logical origin (no rounding) to match CPU path final originXPx = screenOffset.dx; @@ -164,9 +212,20 @@ class DotGridBackgroundCpu extends CanvasBackground { }) : super(); @override - void paint(Canvas canvas, Offset screenOffset, Offset canvasOffset, double scale, Size canvasSize) { + void paint( + Canvas canvas, + Offset screenOffset, + Offset canvasOffset, + double scale, + Size canvasSize, + ) { // Fill background - final rect = Rect.fromLTWH(screenOffset.dx, screenOffset.dy, canvasSize.width, canvasSize.height); + final rect = Rect.fromLTWH( + screenOffset.dx, + screenOffset.dy, + canvasSize.width, + canvasSize.height, + ); final bgPaint = Paint() ..color = backgroundColor ..style = PaintingStyle.fill; @@ -194,9 +253,11 @@ class DotGridBackgroundCpu extends CanvasBackground { final marginGSY = radiusSS / scale; final xGsMin = sign * canvasOffset.dx - marginGSX; - final xGsMax = sign * canvasOffset.dx + (canvasSize.width + 2 * radiusSS) / scale; + final xGsMax = + sign * canvasOffset.dx + (canvasSize.width + 2 * radiusSS) / scale; final yGsMin = sign * canvasOffset.dy - marginGSY; - final yGsMax = sign * canvasOffset.dy + (canvasSize.height + 2 * radiusSS) / scale; + final yGsMax = + sign * canvasOffset.dy + (canvasSize.height + 2 * radiusSS) / scale; int nStartX = (xGsMin / spacing).floor(); int nEndX = (xGsMax / spacing).ceil(); @@ -207,12 +268,14 @@ class DotGridBackgroundCpu extends CanvasBackground { for (int nx = nStartX; nx <= nEndX; nx++) { final xGS = nx * spacing; final xSS = screenOffset.dx + (xGS - sign * canvasOffset.dx) * scale; - if (xSS + radiusSS < rect.left || xSS - radiusSS > rect.right) continue; // skip out-of-bounds columns + if (xSS + radiusSS < rect.left || xSS - radiusSS > rect.right) + continue; // skip out-of-bounds columns for (int ny = nStartY; ny <= nEndY; ny++) { final yGS = ny * spacing; final ySS = screenOffset.dy + (yGS - sign * canvasOffset.dy) * scale; - if (ySS + radiusSS < rect.top || ySS - radiusSS > rect.bottom) continue; // skip out-of-bounds rows + if (ySS + radiusSS < rect.top || ySS - radiusSS > rect.bottom) + continue; // skip out-of-bounds rows canvas.drawCircle(Offset(xSS, ySS), radiusSS, dotPaint); } diff --git a/lib/core/render.dart b/lib/core/render.dart index e77f93b..6ce7dbc 100644 --- a/lib/core/render.dart +++ b/lib/core/render.dart @@ -27,12 +27,6 @@ class _LazyCanvasState extends State with TickerProviderStateMixin