update readme
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
## 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
- Update README
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
- Again update README ( -\_-)
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
Infinite zoomable, pannable 2D canvas using spatial hash for only rendering what's visible.
|
Infinite zoomable, pannable 2D canvas using spatial hash for only rendering what's visible.
|
||||||
|
|
||||||
|
Example: https://infinite-lazy-grid.pages.dev/
|
||||||
|
|
||||||
|
<p align='center'>
|
||||||
|
<img loading="lazy" src="https://raw.githubusercontent.com/ruinivist/infinite_lazy_grid/main/demo.gif" />
|
||||||
|
</p>
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
@@ -18,7 +24,7 @@ class _DemoCanvasState extends State<DemoCanvas> {
|
|||||||
// all interactions go through the controller
|
// all interactions go through the controller
|
||||||
final controller = LazyCanvasController(
|
final controller = LazyCanvasController(
|
||||||
background: const DotGridBackground(),
|
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
|
@override
|
||||||
@@ -28,6 +34,13 @@ class _DemoCanvasState extends State<DemoCanvas> {
|
|||||||
for (int i = 0; i < 50; i++) {
|
for (int i = 0; i < 50; i++) {
|
||||||
controller.addChild(
|
controller.addChild(
|
||||||
Offset((i % 10) * 140.0, (i ~/ 10) * 140.0),
|
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<DemoCanvas> {
|
|||||||
// one child, returns its id which is just a uuid string
|
// one child, returns its id which is just a uuid string
|
||||||
CanvasChildId oneChild = controller.addChild(
|
CanvasChildId oneChild = controller.addChild(
|
||||||
const Offset(500, 1200),
|
const Offset(500, 1200),
|
||||||
|
const Icon(Icons.place, size: 32),
|
||||||
);
|
);
|
||||||
|
|
||||||
// with custom widget
|
// 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.
|
1. Stateful widget child: Child handles its own updates but state is lost when unmounted.
|
||||||
2. _Manual update_: `updateChildWidget(id, newWidget)`.
|
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.
|
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
|
### Size based optimisations
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,8 @@
|
|||||||
# Demos
|
# Demos
|
||||||
|
|
||||||
Contains 4 small focused demos.
|
Contains four small, focused demos.
|
||||||
|
|
||||||
- `Simple Example` – minimal setup, add nodes, pan, zoom, focus.
|
- Simple Example – minimal setup, add nodes, pan, zoom, focus.
|
||||||
- `Build Counts Example` – shows build / culling behavior; watch console for build counts.
|
- Build Counts Example – shows build/culling behavior; watch console for build counts.
|
||||||
- `Widget State Updates Example` – three patterns for dynamic / reactive child content.
|
- Widget State Updates Example – three patterns for dynamic/reactive child content.
|
||||||
- `Render Callbacks Example` – logs enter / exit render area callbacks and overlays debug info.
|
- Render Callbacks Example – logs enter/exit render area callbacks and overlays debug info.
|
||||||
|
|||||||
+92
-29
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:ui' as ui show FragmentProgram; // for runtime shader
|
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 definition for a [LazyCanvas] background.
|
||||||
abstract class CanvasBackground {
|
abstract class CanvasBackground {
|
||||||
@@ -12,26 +13,53 @@ abstract class CanvasBackground {
|
|||||||
/// different kinds of backgrounds
|
/// different kinds of backgrounds
|
||||||
/// [screenOffset] is the screen space offset for clipping
|
/// [screenOffset] is the screen space offset for clipping
|
||||||
/// [canvasOffset] is the grid space offset from the controller
|
/// [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 {
|
class NoBackground extends CanvasBackground {
|
||||||
const NoBackground();
|
const NoBackground();
|
||||||
|
|
||||||
@override
|
@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 {
|
class SingleColorBackground extends CanvasBackground {
|
||||||
const SingleColorBackround(Color backgroundColor) : super(bgColor: backgroundColor);
|
const SingleColorBackground(Color backgroundColor)
|
||||||
|
: super(bgColor: backgroundColor);
|
||||||
|
|
||||||
@override
|
@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()
|
final paint = Paint()
|
||||||
..color = bgColor
|
..color = bgColor
|
||||||
..style = PaintingStyle.fill;
|
..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() {
|
static void _ensureProgramLoaded() {
|
||||||
if (_program != null || _programFuture != null) return;
|
if (_program != null || _programFuture != null) return;
|
||||||
try {
|
try {
|
||||||
_programFuture = ui.FragmentProgram.fromAsset('packages/infinite_lazy_grid/shaders/dot_grid.frag')
|
_programFuture =
|
||||||
..then((p) {
|
ui.FragmentProgram.fromAsset(
|
||||||
_program = p;
|
'packages/infinite_lazy_grid/shaders/dot_grid.frag',
|
||||||
// Request a repaint when shader becomes available
|
)
|
||||||
try {
|
..then((p) {
|
||||||
for (final renderView in RendererBinding.instance.renderViews) {
|
_program = p;
|
||||||
renderView.markNeedsPaint();
|
// Request a repaint when shader becomes available
|
||||||
}
|
try {
|
||||||
} catch (_) {}
|
for (final renderView in RendererBinding.instance.renderViews) {
|
||||||
}).catchError((_) {
|
renderView.markNeedsPaint();
|
||||||
// keep _program null; we'll fallback to CPU/simple paint
|
}
|
||||||
});
|
} catch (_) {}
|
||||||
|
}).catchError((_) {
|
||||||
|
// keep _program null; we'll fallback to CPU/simple paint
|
||||||
|
});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// fromAsset may throw synchronously on unsupported platforms
|
// fromAsset may throw synchronously on unsupported platforms
|
||||||
_programFuture = null;
|
_programFuture = null;
|
||||||
@@ -79,12 +110,23 @@ class DotGridBackground extends CanvasBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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)
|
// Always draw background fill (also serves as fallback while shader loads)
|
||||||
final bgPaint = Paint()
|
final bgPaint = Paint()
|
||||||
..color = backgroundColor
|
..color = backgroundColor
|
||||||
..style = PaintingStyle.fill;
|
..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
|
// Ensure shader load started
|
||||||
_ensureProgramLoaded();
|
_ensureProgramLoaded();
|
||||||
@@ -103,8 +145,14 @@ class DotGridBackground extends CanvasBackground {
|
|||||||
// Phase to align grid with world origin under pan/zoom
|
// Phase to align grid with world origin under pan/zoom
|
||||||
double modPositive(double a, double m) => ((a % m) + m) % m;
|
double modPositive(double a, double m) => ((a % m) + m) % m;
|
||||||
final sign = naturalPan ? 1.0 : -1.0;
|
final sign = naturalPan ? 1.0 : -1.0;
|
||||||
final phaseXPx = modPositive(sign * canvasOffset.dx * scale, scaledSpacingPx == 0 ? 1 : scaledSpacingPx);
|
final phaseXPx = modPositive(
|
||||||
final phaseYPx = modPositive(sign * canvasOffset.dy * scale, scaledSpacingPx == 0 ? 1 : scaledSpacingPx);
|
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
|
// Snap origin: use exact logical origin (no rounding) to match CPU path
|
||||||
final originXPx = screenOffset.dx;
|
final originXPx = screenOffset.dx;
|
||||||
@@ -164,9 +212,20 @@ class DotGridBackgroundCpu extends CanvasBackground {
|
|||||||
}) : super();
|
}) : super();
|
||||||
|
|
||||||
@override
|
@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
|
// 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()
|
final bgPaint = Paint()
|
||||||
..color = backgroundColor
|
..color = backgroundColor
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
@@ -194,9 +253,11 @@ class DotGridBackgroundCpu extends CanvasBackground {
|
|||||||
final marginGSY = radiusSS / scale;
|
final marginGSY = radiusSS / scale;
|
||||||
|
|
||||||
final xGsMin = sign * canvasOffset.dx - marginGSX;
|
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 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 nStartX = (xGsMin / spacing).floor();
|
||||||
int nEndX = (xGsMax / spacing).ceil();
|
int nEndX = (xGsMax / spacing).ceil();
|
||||||
@@ -207,12 +268,14 @@ class DotGridBackgroundCpu extends CanvasBackground {
|
|||||||
for (int nx = nStartX; nx <= nEndX; nx++) {
|
for (int nx = nStartX; nx <= nEndX; nx++) {
|
||||||
final xGS = nx * spacing;
|
final xGS = nx * spacing;
|
||||||
final xSS = screenOffset.dx + (xGS - sign * canvasOffset.dx) * scale;
|
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++) {
|
for (int ny = nStartY; ny <= nEndY; ny++) {
|
||||||
final yGS = ny * spacing;
|
final yGS = ny * spacing;
|
||||||
final ySS = screenOffset.dy + (yGS - sign * canvasOffset.dy) * scale;
|
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);
|
canvas.drawCircle(Offset(xSS, ySS), radiusSS, dotPaint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,6 @@ class _LazyCanvasState extends State<LazyCanvas> with TickerProviderStateMixin<L
|
|||||||
widget.controller.setTickerProvider(this);
|
widget.controller.setTickerProvider(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
widget.controller.dispose(); // for the change notifier
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant LazyCanvas oldWidget) {
|
void didUpdateWidget(covariant LazyCanvas oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
name: infinite_lazy_grid
|
name: infinite_lazy_grid
|
||||||
description: "infinitely scrollable and zoomable grid layout with lazy loading capabilities."
|
description: "infinitely scrollable and zoomable grid layout with lazy loading capabilities."
|
||||||
version: 1.0.0
|
version: 1.0.2
|
||||||
homepage: https://github.com/ruinivist/infinite_lazy_grid
|
homepage: https://github.com/ruinivist/infinite_lazy_grid
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Reference in New Issue
Block a user