infinity_canvas 0.8.1
infinity_canvas: ^0.8.1 copied to clipboard
High-performance infinite canvas for Flutter with layered rendering and controller-driven camera/item APIs.
High-performance infinite canvas for Flutter
- Mixed layers: positioned widgets, painter passes, and overlays
- Programmatic camera/item control
- Built for large scenes (node editors, maps, strategy UIs, visual tooling)
Install #
dependencies:
infinity_canvas: ^0.8.0
Quickstart #
import 'package:flutter/material.dart';
import 'package:infinity_canvas/infinity_canvas.dart';
class MyCanvasPage extends StatefulWidget {
const MyCanvasPage({super.key});
@override
State<MyCanvasPage> createState() => _MyCanvasPageState();
}
class _MyCanvasPageState extends State<MyCanvasPage> {
late final CanvasController controller;
@override
void initState() {
super.initState();
controller = CanvasController(
initialWorldTopLeft: const Offset(-200, -120),
initialZoom: 1.1,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return InfinityCanvas(
controller: controller,
enableCulling: true,
layers: [
CanvasLayer.positionedItems(
id: 'nodes',
items: [
CanvasItem(
id: 'node-1',
worldPosition: const Offset(120, 100),
behavior: CanvasItemBehavior.nodeEditor(),
child: const _Card('Node 1'),
),
],
),
],
);
}
}
class _Card extends StatelessWidget {
final String title;
const _Card(this.title);
@override
Widget build(BuildContext context) {
return Container(
width: 180,
height: 92,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0x3347A3FF)),
),
child: Text(title, style: const TextStyle(color: Colors.white)),
);
}
}
Use Like This #
Move items #
controller.items.setWorldPosition('node-1', const Offset(300, 240));
controller.items.setWorldPositions({
'node-1': const Offset(300, 240),
'node-2': const Offset(620, 300),
});
To add another item, create another CanvasItem(...) with its own id,
worldPosition, and child, then include it in the items: [...] list.
Camera controls #
controller.camera.jumpToWorldTopLeft(const Offset(-500, -300), zoom: 1.2);
controller.camera.jumpToWorldCenter(const Offset(0, 0), zoom: 0.8);
await controller.camera.animateToWorldCenter(
const Offset(1200, 800),
zoom: 1.0,
duration: const Duration(milliseconds: 420),
);
controller.camera.fitAllItems();
Layer types #
InfinityCanvas(
controller: controller,
layers: [
CanvasLayer.painter(
id: 'bg',
painterBuilder: (transform) => MyBackgroundPainter(transform),
),
CanvasLayer.positionedItems(id: 'nodes', items: items),
CanvasLayer.overlay(
id: 'hud',
ignorePointer: false,
builder: (context, transform, controller) => const MyHudWidget(),
),
],
);
Hover, drag, and item transform #
CanvasItem(
id: 'node-3',
worldPosition: const Offset(200, 120),
onHoverChanged: (hovered) {
controller.items.setTransform(
'node-3',
hovered ? (Matrix4.identity()..scale(1.04)) : null,
);
},
onDragUpdate: (event) {
// event.worldPosition / event.worldDelta / event.pointerGlobalPosition
},
child: const _Card('Hover me'),
);
Performance Defaults #
- Use
enableCulling: truefor larger scenes - Give items a fixed
CanvasItemSizewhere possible - Prefer
CanvasLayer.painterfor very dense static visuals - Use
controller.items.setWorldPositions(...)for batch updates - Keep overlays lean (
CanvasLayer.overlay) for HUD/interaction logic
Example Demos #
See example/lib/main.dart:
- Minimal Items
- Painted Item Widgets
- Node Canvas (Clean)
- Grouped Nodes (Linear)
- Input Smoke
- Docking Windows
- Database Schema Designer
- Tower Defense
- Massive Multi-Widget Art Scene
- Galaxy Trade Map
- Orbital Constellation
API Appendix (Compact) #
CanvasController #
camera: transform, pan, zoom, fit, jump, animateitems: read diagnostics + mutate item statelayers: show/hide layers
controller.camera #
jumpToWorldTopLeft(...)jumpToWorldCenter(...)animateToWorldTopLeft(...)animateToWorldCenter(...)fitWorldRect(...)fitAllItems(...)setScale(...)translateWorld(...)screenToWorld(...)worldToScreen(...)renderStatsListenable
controller.items #
getDiagnostics(id)getWorldPosition(id)positionListenable(id)setWorldPosition(id, offset)setWorldPositions({id: offset})setTransform(id, matrixOrNull)mutateTransform(id, mutator)clearTransform(id)setDragEnabled(id, enabled)bringToFront(id)
CanvasLayer #
CanvasLayer.positionedItems(...)CanvasLayer.painter(...)CanvasLayer.overlay(...)
CanvasInputBehavior #
CanvasInputBehavior.desktop()CanvasInputBehavior.touch()CanvasInputBehavior.locked()