Skip to content

Instantly share code, notes, and snippets.

@pskink
Created January 27, 2024 08:42
Show Gist options
  • Save pskink/30eeb0bf7763a909bcc8c550cbb34ac5 to your computer and use it in GitHub Desktop.
Save pskink/30eeb0bf7763a909bcc8c550cbb34ac5 to your computer and use it in GitHub Desktop.
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
main() {
runApp(MaterialApp(home: Scaffold(body: InfiniteCanvas())));
}
const worldSize = Size(2000000, 2000000);
const grid = 100.0;
final initialMatrix = Matrix4.translationValues(-worldSize.width / 2, -worldSize.height / 2, 0.0);
const rects = [
(Rect.fromLTWH(310, 410, 50, 150), Colors.orange),
(Rect.fromLTWH(10, 110, 80, 200), Colors.red),
(Rect.fromLTWH(210, 310, 50, 300), Colors.blue),
(Rect.fromLTWH(110, 210, 50, 180), Colors.green),
(Rect.fromLTWH(60, 410, 80, 80), Colors.deepPurple),
];
extension on TransformationController {
Rect viewportToScene(Rect viewport) {
final inverted = Matrix4.inverted(value);
return MatrixUtils.transformRect(inverted, viewport);
}
}
class InfiniteCanvas extends StatelessWidget {
final controller = TransformationController(initialMatrix);
final colorLog = StreamController<List<Color>>();
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: [
InteractiveViewer(
constrained: false,
transformationController: controller,
child: SizedBox.fromSize(
size: worldSize,
child: CustomPaint(
painter: InfinitePainter(controller, Offset.zero & constraints.biggest, colorLog),
),
),
),
Align(
alignment: Alignment.topCenter,
child: Column(
children: [
ListenableBuilder(
listenable: controller,
builder: (context, child) {
final viewport = Offset.zero & constraints.biggest;
final r = controller.viewportToScene(viewport).translate(-worldSize.width / 2, -worldSize.height / 2);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('${r.topLeft}'),
Text('${r.size}'),
],
);
}
),
StreamBuilder<List<Color>>(
stream: colorLog.stream.distinct(listEquals),
initialData: const [],
builder: (ctx, snapshot) {
final colors = snapshot.data!;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(colors.isNotEmpty? 'rects drawn:' : 'no rects drawn'),
...rects.map((rect) {
final drawn = colors.contains(rect.$2);
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: EdgeInsets.all(drawn? 4 : 0),
decoration: BoxDecoration(
color: rect.$2,
border: Border.all(color: Colors.black26, width: drawn? 1 : 0),
),
width: drawn? 12 : 0,
height: drawn? 12 : 0,
);
}),
],
);
},
),
],
),
),
],
);
}
);
}
}
class InfinitePainter extends CustomPainter {
InfinitePainter(this.controller, this.viewport, this.colorLog);
final TransformationController controller;
final Rect viewport;
final StreamSink<List<Color>> colorLog;
@override
void paint(Canvas canvas, Size size) {
final rect = controller.viewportToScene(viewport);
// print(rect);
// draw grid
Paint gridPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1
..color = Colors.indigo;
final points = _makeGrid(rect).toList();
// print(points.length);
canvas.drawPoints(PointMode.lines, points, gridPaint);
// draw rects
final drawn = <Color>[];
for (final r in rects) {
final translated = r.$1.translate(worldSize.width / 2, worldSize.height / 2);
if (translated.overlaps(rect)) {
drawn.add(r.$2);
canvas.drawRect(translated, Paint()..color = r.$2);
}
}
colorLog.add(drawn);
}
@override
bool shouldRepaint(InfinitePainter oldDelegate) => false;
Iterable<Offset> _makeGrid(Rect rect) sync* {
final topLeft = rect.topLeft % grid;
final insets = EdgeInsets.only(left: topLeft.dx, top: topLeft.dy);
final r = insets.inflateRect(rect);
for (double x = r.left; x < r.right; x += grid) {
yield Offset(x, rect.top);
yield Offset(x, rect.bottom);
}
for (double y = r.top; y < r.bottom; y += grid) {
yield Offset(rect.left, y);
yield Offset(rect.right, y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment