160 lines
5.0 KiB
Dart
160 lines
5.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:infinite_lazy_2d_grid/core/controller.dart';
|
|
|
|
import 'background.dart';
|
|
|
|
/**
|
|
* Working of some of the flutter code used here:
|
|
|
|
*/
|
|
|
|
/// An infinite canvas that places all the children at the specified positions.
|
|
class CanvasView extends StatelessWidget {
|
|
final CanvasBackground canvasBackground;
|
|
final CanvasController controller;
|
|
|
|
const CanvasView({required this.controller, required this.canvasBackground, super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
color: canvasBackground.bgColor,
|
|
child: GestureDetector(
|
|
onScaleUpdate: controller.handleGesture,
|
|
child: ListenableBuilder(
|
|
listenable: controller,
|
|
builder: (_, _) {
|
|
final childrenWithPositions = controller.widgetsWithScreenPositions();
|
|
final ssPositions = childrenWithPositions.map((e) => e.$1).toList();
|
|
final children = childrenWithPositions.map((e) => e.$2).toList();
|
|
return _CanvasRenderObject(
|
|
canvasBackground: canvasBackground,
|
|
ssPositions: ssPositions,
|
|
children: children,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A combined widget for all the render object of the children + background.
|
|
class _CanvasRenderObject extends MultiChildRenderObjectWidget {
|
|
final List<Offset> ssPositions;
|
|
final CanvasBackground canvasBackground;
|
|
|
|
const _CanvasRenderObject({
|
|
required this.ssPositions,
|
|
required super.children, // children go to the MultiChildRenderObjectWidget
|
|
required this.canvasBackground,
|
|
}) : assert(ssPositions.length == children.length, 'Children and positions must have the same length');
|
|
|
|
@override
|
|
RenderObject createRenderObject(BuildContext context) {
|
|
return _CanvasRenderBox(ssPositions: ssPositions, canvasBackground: canvasBackground);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(BuildContext context, _CanvasRenderBox renderObject) {
|
|
renderObject
|
|
..ssPositions = ssPositions
|
|
..canvasBackground = canvasBackground;
|
|
}
|
|
}
|
|
|
|
/// Dummy empty parent data for each child
|
|
class _WidgetParentData extends ContainerBoxParentData<RenderBox> {}
|
|
|
|
class _CanvasRenderBox extends RenderBox
|
|
with
|
|
ContainerRenderObjectMixin<RenderBox, _WidgetParentData>,
|
|
RenderBoxContainerDefaultsMixin<RenderBox, _WidgetParentData> {
|
|
CanvasBackground _canvasBackground;
|
|
List<Offset> _ssPositions;
|
|
|
|
_CanvasRenderBox({required ssPositions, required canvasBackground})
|
|
: _ssPositions = ssPositions,
|
|
_canvasBackground = canvasBackground;
|
|
|
|
@override
|
|
void setupParentData(RenderBox child) {
|
|
if (child.parentData is! _WidgetParentData) {
|
|
child.parentData = _WidgetParentData();
|
|
}
|
|
}
|
|
|
|
set ssPositions(List<Offset> ssPositions) {
|
|
if (_ssPositions != ssPositions) {
|
|
_ssPositions = ssPositions;
|
|
markNeedsPaint();
|
|
}
|
|
}
|
|
|
|
set canvasBackground(CanvasBackground canvasBackground) {
|
|
if (_canvasBackground != canvasBackground) {
|
|
_canvasBackground = canvasBackground;
|
|
markNeedsPaint();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
size = constraints.biggest; // expand as much as possible for the parent
|
|
|
|
RenderBox? child = firstChild;
|
|
|
|
while (child != null) {
|
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
|
// loosen so like a stack can take it's own size inside parent
|
|
// like a stack parent is not using size and is always expanded hence the second arg
|
|
child.layout(constraints.loosen(), parentUsesSize: false);
|
|
child = parentData.nextSibling;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset canvasStartOffset) {
|
|
final canvas = context.canvas;
|
|
|
|
// use the canvas background painter, pass it the canvas and that should handle drawing the background
|
|
_canvasBackground.paint(canvas, size);
|
|
|
|
RenderBox? child = firstChild;
|
|
for (int idx = 0; child != null; idx++) {
|
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
|
context.paintChild(child, _ssPositions[idx]);
|
|
child = parentData.nextSibling;
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool hitTest(BoxHitTestResult result, {required Offset position}) {
|
|
final ssHitPosition = position;
|
|
|
|
if (!size.contains(ssHitPosition)) {
|
|
return false;
|
|
}
|
|
|
|
// Check children in reverse order (last painted = first hit)
|
|
RenderBox? child = lastChild;
|
|
for (int idx = _ssPositions.length - 1; child != null; idx--) {
|
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
|
|
|
// Transform the hit position relative to the child's position
|
|
final childPosition = ssHitPosition - _ssPositions[idx];
|
|
|
|
if (child.hitTest(BoxHitTestResult.wrap(result), position: childPosition)) {
|
|
return true;
|
|
}
|
|
|
|
child = parentData.previousSibling;
|
|
}
|
|
|
|
// If no child was hit, the canvas itself handles the hit
|
|
result.add(BoxHitTestEntry(this, ssHitPosition));
|
|
return true;
|
|
}
|
|
}
|