simple view layer: static screenspace widgets with working hittest
This commit is contained in:
@@ -7,21 +7,21 @@ abstract class CanvasBackground {
|
|||||||
|
|
||||||
/// draw the backround on this context. Implement this to have
|
/// draw the backround on this context. Implement this to have
|
||||||
/// different kinds of backgrounds
|
/// different kinds of backgrounds
|
||||||
void paint(Canvas canvas, Offset offset, double scale, Size canvasSize);
|
void paint(Canvas canvas, Size canvasSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoBackground extends CanvasBackground {
|
class NoBackground extends CanvasBackground {
|
||||||
const NoBackground();
|
const NoBackground();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Offset offset, double scale, Size canvasSize) {}
|
void paint(Canvas canvas, Size canvasSize) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleColorBackround extends CanvasBackground {
|
class SingleColorBackround extends CanvasBackground {
|
||||||
const SingleColorBackround(Color backgroundColor) : super(bgColor: backgroundColor);
|
const SingleColorBackround(Color backgroundColor) : super(bgColor: backgroundColor);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Offset offset, double scale, Size canvasSize) {
|
void paint(Canvas canvas, Size canvasSize) {
|
||||||
final paint = Paint()
|
final paint = Paint()
|
||||||
..color = bgColor
|
..color = bgColor
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
|
|||||||
+56
-107
@@ -3,126 +3,84 @@ import 'package:flutter/rendering.dart';
|
|||||||
|
|
||||||
import 'background.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.
|
/// An infinite canvas that places all the children at the specified positions.
|
||||||
class CanvasView extends StatelessWidget {
|
class CanvasView extends StatelessWidget {
|
||||||
final Offset offset;
|
final List<Offset> ssPositions; // screen space positions;
|
||||||
final List<Offset> positions;
|
final List<Widget> children; // children, in the same order as positions
|
||||||
final List<Widget> children;
|
|
||||||
final CanvasBackground canvasBackground;
|
final CanvasBackground canvasBackground;
|
||||||
final double scale;
|
|
||||||
final void Function(ScaleUpdateDetails details) handleScaleUpdate;
|
|
||||||
final void Function(ScaleStartDetails details) handleScaleStart;
|
|
||||||
|
|
||||||
const CanvasView({
|
const CanvasView({required this.ssPositions, required this.children, required this.canvasBackground, super.key})
|
||||||
required this.children,
|
: assert(children.length == ssPositions.length, 'Children and positions must have the same length');
|
||||||
required this.positions,
|
|
||||||
required this.offset,
|
|
||||||
required this.scale,
|
|
||||||
required this.handleScaleUpdate,
|
|
||||||
required this.handleScaleStart,
|
|
||||||
required this.canvasBackground,
|
|
||||||
super.key,
|
|
||||||
}) : assert(children.length == positions.length, 'Children and positions must have the same length');
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
color: canvasBackground.bgColor,
|
color: canvasBackground.bgColor,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
child: _CanvasRenderObject(canvasBackground: canvasBackground, ssPositions: ssPositions, children: children),
|
||||||
onScaleUpdate: handleScaleUpdate,
|
|
||||||
onScaleStart: handleScaleStart,
|
|
||||||
child: _CanvasRenderObject(
|
|
||||||
offset: offset,
|
|
||||||
scale: scale,
|
|
||||||
positions: positions,
|
|
||||||
canvasBackground: canvasBackground,
|
|
||||||
children: children,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A combined widget for all the render object of the children + background.
|
||||||
class _CanvasRenderObject extends MultiChildRenderObjectWidget {
|
class _CanvasRenderObject extends MultiChildRenderObjectWidget {
|
||||||
final Offset offset;
|
final List<Offset> ssPositions;
|
||||||
final double scale;
|
|
||||||
|
|
||||||
final List<Offset> positions;
|
|
||||||
final CanvasBackground canvasBackground;
|
final CanvasBackground canvasBackground;
|
||||||
|
|
||||||
const _CanvasRenderObject({
|
const _CanvasRenderObject({
|
||||||
required this.offset,
|
required this.ssPositions,
|
||||||
required this.scale,
|
required super.children, // children go to the MultiChildRenderObjectWidget
|
||||||
required this.positions,
|
|
||||||
required super.children,
|
|
||||||
required this.canvasBackground,
|
required this.canvasBackground,
|
||||||
});
|
}) : assert(ssPositions.length == children.length, 'Children and positions must have the same length');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject createRenderObject(BuildContext context) {
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
return _CanvasRenderBox(offset: offset, scale: scale, positions: positions, canvasBackground: canvasBackground);
|
return _CanvasRenderBox(ssPositions: ssPositions, canvasBackground: canvasBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRenderObject(BuildContext context, _CanvasRenderBox renderObject) {
|
void updateRenderObject(BuildContext context, _CanvasRenderBox renderObject) {
|
||||||
renderObject
|
// static for now
|
||||||
..offset = offset
|
|
||||||
..positions = positions
|
|
||||||
..scale = scale;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CanvasParentData extends ContainerBoxParentData<RenderBox> {}
|
/// Dummy empty parent data for each child
|
||||||
|
class _WidgetParentData extends ContainerBoxParentData<RenderBox> {}
|
||||||
|
|
||||||
class _CanvasRenderBox extends RenderBox
|
class _CanvasRenderBox extends RenderBox
|
||||||
with
|
with
|
||||||
ContainerRenderObjectMixin<RenderBox, _CanvasParentData>,
|
ContainerRenderObjectMixin<RenderBox, _WidgetParentData>,
|
||||||
RenderBoxContainerDefaultsMixin<RenderBox, _CanvasParentData> {
|
RenderBoxContainerDefaultsMixin<RenderBox, _WidgetParentData> {
|
||||||
CanvasBackground canvasBackground;
|
final CanvasBackground canvasBackground;
|
||||||
Offset _offset;
|
final List<Offset> ssPositions;
|
||||||
double _scale;
|
|
||||||
List<Offset> _positions;
|
|
||||||
|
|
||||||
_CanvasRenderBox({required positions, required offset, required scale, required this.canvasBackground})
|
_CanvasRenderBox({required this.ssPositions, required this.canvasBackground});
|
||||||
: _offset = offset,
|
|
||||||
_positions = positions,
|
|
||||||
_scale = scale;
|
|
||||||
|
|
||||||
set offset(Offset value) {
|
|
||||||
if (_offset == value) return;
|
|
||||||
_offset = value;
|
|
||||||
markNeedsPaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
set positions(List<Offset> value) {
|
|
||||||
if (_positions == value) return;
|
|
||||||
_positions = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
set scale(double value) {
|
|
||||||
if (_scale == value) return;
|
|
||||||
_scale = value;
|
|
||||||
markNeedsPaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setupParentData(RenderBox child) {
|
void setupParentData(RenderBox child) {
|
||||||
if (child.parentData is! _CanvasParentData) {
|
if (child.parentData is! _WidgetParentData) {
|
||||||
child.parentData = _CanvasParentData();
|
child.parentData = _WidgetParentData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
size = constraints.biggest;
|
size = constraints.biggest; // expand as much as possible for the parent
|
||||||
|
|
||||||
RenderBox? child = firstChild;
|
RenderBox? child = firstChild;
|
||||||
|
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
final _CanvasParentData childParentData = child.parentData! as _CanvasParentData;
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
||||||
child.layout(constraints.loosen(), parentUsesSize: false); // TODO: parentUsesSize should be false, but idk
|
// loosen so like a stack can take it's own size inside parent
|
||||||
child = childParentData.nextSibling;
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,51 +88,42 @@ class _CanvasRenderBox extends RenderBox
|
|||||||
void paint(PaintingContext context, Offset canvasStartOffset) {
|
void paint(PaintingContext context, Offset canvasStartOffset) {
|
||||||
final canvas = context.canvas;
|
final canvas = context.canvas;
|
||||||
|
|
||||||
canvasBackground.paint(canvas, _offset, _scale, size);
|
// use the canvas background painter, pass it the canvas and that should handle drawing the background
|
||||||
|
canvasBackground.paint(canvas, size);
|
||||||
|
|
||||||
RenderBox? child = firstChild;
|
RenderBox? child = firstChild;
|
||||||
for (int idx = 0; child != null; idx++) {
|
for (int idx = 0; child != null; idx++) {
|
||||||
final _CanvasParentData childParentData = child.parentData! as _CanvasParentData;
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
||||||
final paintAt = _positions[idx] * _scale - _offset;
|
context.paintChild(child, ssPositions[idx]);
|
||||||
context.paintChild(child, paintAt);
|
child = parentData.nextSibling;
|
||||||
child = childParentData.nextSibling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do i need to override hitTest? yes I do
|
|
||||||
@override
|
@override
|
||||||
bool hitTest(BoxHitTestResult result, {required Offset position}) {
|
bool hitTest(BoxHitTestResult result, {required Offset position}) {
|
||||||
// args are propagating so this may happen, just ignore it
|
final ssHitPosition = position;
|
||||||
//is it a double render bug?
|
|
||||||
if (childCount != _positions.length) {
|
if (!size.contains(ssHitPosition)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// TODO: this is bit slow, so the tap is shown slightly after the actual tap
|
|
||||||
bool hitChild = false;
|
// Check children in reverse order (last painted = first hit)
|
||||||
RenderBox? child = lastChild;
|
RenderBox? child = lastChild;
|
||||||
for (int i = childCount - 1; i >= 0; i--) {
|
for (int idx = ssPositions.length - 1; child != null; idx--) {
|
||||||
if (child != null) {
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
||||||
final _CanvasParentData childParentData = child.parentData! as _CanvasParentData;
|
|
||||||
// child is at _positions[i] in grid space
|
// Transform the hit position relative to the child's position
|
||||||
final ssChildPosition = _positions[i] * _scale - _offset; // child position in screen space
|
final childPosition = ssHitPosition - ssPositions[idx];
|
||||||
final positionInChildSpace = position - ssChildPosition; // position in child space
|
|
||||||
if ((child.size * _scale).contains(positionInChildSpace)) {
|
if (child.hitTest(BoxHitTestResult.wrap(result), position: childPosition)) {
|
||||||
final isHit = result.addWithPaintOffset(
|
return true;
|
||||||
offset: null, // i convert it directly to child space
|
|
||||||
position: positionInChildSpace,
|
|
||||||
hitTest: (BoxHitTestResult result, Offset transformed) {
|
|
||||||
return child!.hitTest(result, position: transformed);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (isHit) {
|
|
||||||
hitChild = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
child = childParentData.previousSibling;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
child = parentData.previousSibling;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hitChild;
|
// If no child was hit, the canvas itself handles the hit
|
||||||
|
result.add(BoxHitTestEntry(this, ssHitPosition));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user