couple view and controller + offset working
This commit is contained in:
+13
-11
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:infinite_lazy_2d_grid/core/controller.dart';
|
||||||
import 'package:infinite_lazy_2d_grid/infinite_lazy_2d_grid.dart';
|
import 'package:infinite_lazy_2d_grid/infinite_lazy_2d_grid.dart';
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
@@ -9,20 +10,21 @@ class App extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppState extends State<App> {
|
class _AppState extends State<App> {
|
||||||
|
final CanvasController controller = CanvasController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
controller.addChild(const Offset(100, 100), () => const InfoContainer(color: Colors.red));
|
||||||
|
controller.addChild(const Offset(200, 200), () => const InfoContainer(color: Colors.green));
|
||||||
|
controller.addChild(const Offset(300, 300), () => const InfoContainer(color: Colors.blue));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SizedBox.expand(
|
body: CanvasView(controller: controller, canvasBackground: SingleColorBackround(Colors.white)),
|
||||||
child: CanvasView(
|
|
||||||
ssPositions: [Offset(100, 100), Offset(200, 200), Offset(300, 300)],
|
|
||||||
canvasBackground: SingleColorBackround(Colors.white),
|
|
||||||
children: [
|
|
||||||
InfoContainer(color: Colors.red),
|
|
||||||
InfoContainer(color: Colors.green),
|
|
||||||
InfoContainer(color: Colors.blue),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:infinite_lazy_2d_grid/utils/conversions.dart';
|
||||||
|
|
||||||
|
// ------------------------------ Public Types -------------------------------
|
||||||
|
|
||||||
|
typedef WidgetBuilder = Widget Function();
|
||||||
|
|
||||||
|
// ------------------------------ Private Types ------------------------------
|
||||||
|
|
||||||
|
// some simple exceptions
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final _ChildNotFoundException = Exception('Child with the given ID does not exist');
|
||||||
|
|
||||||
|
class _CanvasItem {
|
||||||
|
Offset gsPosition;
|
||||||
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
|
_CanvasItem({required this.gsPosition, required this.builder});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ Controller ------------------------------
|
||||||
|
|
||||||
|
/// Every canvas view needs one and this handles the positioning logic
|
||||||
|
class CanvasController with ChangeNotifier {
|
||||||
|
int _nextId = 0; // surely we won't run out of IDs, Clueless
|
||||||
|
Offset _gsTopLeftOffset = Offset.zero;
|
||||||
|
final Map<int, _CanvasItem> _children = {}; // int for IDs
|
||||||
|
|
||||||
|
// -------------------- public functions --------------------
|
||||||
|
|
||||||
|
// -------- child manip --------
|
||||||
|
|
||||||
|
int addChild(Offset position, WidgetBuilder builder) {
|
||||||
|
_children[_nextId] = _CanvasItem(gsPosition: position, builder: builder);
|
||||||
|
return _nextId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeChild(int id) {
|
||||||
|
_children.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
int updatePosition(int id, Offset newPosition) {
|
||||||
|
if (_children.containsKey(id)) {
|
||||||
|
_children[id]!.gsPosition = newPosition;
|
||||||
|
return id;
|
||||||
|
} else {
|
||||||
|
throw _ChildNotFoundException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset getPosition(int id) {
|
||||||
|
if (_children.containsKey(id)) {
|
||||||
|
return _children[id]!.gsPosition;
|
||||||
|
} else {
|
||||||
|
throw _ChildNotFoundException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- gesture handling --------
|
||||||
|
void handleGesture(ScaleUpdateDetails details) {
|
||||||
|
// handle gestures like pinch to zoom, pan, etc.
|
||||||
|
|
||||||
|
// convention is that if I drag from right to left, dx is negative
|
||||||
|
// for top to bottom, dy is postive ( so usual display conventions and final vector postion - initial vector position )
|
||||||
|
final delta = details.focalPointDelta;
|
||||||
|
|
||||||
|
_gsTopLeftOffset -= delta;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<(Offset, Widget)> widgetsWithScreenPositions() {
|
||||||
|
return _children.values.map((item) {
|
||||||
|
final ssPosition = gsToSs(item.gsPosition, _gsTopLeftOffset);
|
||||||
|
return (ssPosition, item.builder());
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
-13
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:infinite_lazy_2d_grid/core/controller.dart';
|
||||||
|
|
||||||
import 'background.dart';
|
import 'background.dart';
|
||||||
|
|
||||||
@@ -10,19 +11,30 @@ import 'background.dart';
|
|||||||
|
|
||||||
/// 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 List<Offset> ssPositions; // screen space positions;
|
|
||||||
final List<Widget> children; // children, in the same order as positions
|
|
||||||
final CanvasBackground canvasBackground;
|
final CanvasBackground canvasBackground;
|
||||||
|
final CanvasController controller;
|
||||||
|
|
||||||
const CanvasView({required this.ssPositions, required this.children, required this.canvasBackground, super.key})
|
const CanvasView({required this.controller, required this.canvasBackground, super.key});
|
||||||
: assert(children.length == ssPositions.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(
|
||||||
child: _CanvasRenderObject(canvasBackground: canvasBackground, ssPositions: ssPositions, children: children),
|
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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,7 +58,9 @@ class _CanvasRenderObject extends MultiChildRenderObjectWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRenderObject(BuildContext context, _CanvasRenderBox renderObject) {
|
void updateRenderObject(BuildContext context, _CanvasRenderBox renderObject) {
|
||||||
// static for now
|
renderObject
|
||||||
|
..ssPositions = ssPositions
|
||||||
|
..canvasBackground = canvasBackground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +71,12 @@ class _CanvasRenderBox extends RenderBox
|
|||||||
with
|
with
|
||||||
ContainerRenderObjectMixin<RenderBox, _WidgetParentData>,
|
ContainerRenderObjectMixin<RenderBox, _WidgetParentData>,
|
||||||
RenderBoxContainerDefaultsMixin<RenderBox, _WidgetParentData> {
|
RenderBoxContainerDefaultsMixin<RenderBox, _WidgetParentData> {
|
||||||
final CanvasBackground canvasBackground;
|
CanvasBackground _canvasBackground;
|
||||||
final List<Offset> ssPositions;
|
List<Offset> _ssPositions;
|
||||||
|
|
||||||
_CanvasRenderBox({required this.ssPositions, required this.canvasBackground});
|
_CanvasRenderBox({required ssPositions, required canvasBackground})
|
||||||
|
: _ssPositions = ssPositions,
|
||||||
|
_canvasBackground = canvasBackground;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setupParentData(RenderBox child) {
|
void setupParentData(RenderBox child) {
|
||||||
@@ -69,6 +85,20 @@ class _CanvasRenderBox extends RenderBox
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set ssPositions(List<Offset> ssPositions) {
|
||||||
|
if (_ssPositions != ssPositions) {
|
||||||
|
_ssPositions = ssPositions;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set canvasBackground(CanvasBackground canvasBackground) {
|
||||||
|
if (_canvasBackground != canvasBackground) {
|
||||||
|
_canvasBackground = canvasBackground;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
size = constraints.biggest; // expand as much as possible for the parent
|
size = constraints.biggest; // expand as much as possible for the parent
|
||||||
@@ -89,12 +119,12 @@ class _CanvasRenderBox extends RenderBox
|
|||||||
final canvas = context.canvas;
|
final canvas = context.canvas;
|
||||||
|
|
||||||
// use the canvas background painter, pass it the canvas and that should handle drawing the background
|
// use the canvas background painter, pass it the canvas and that should handle drawing the background
|
||||||
canvasBackground.paint(canvas, size);
|
_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 _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
||||||
context.paintChild(child, ssPositions[idx]);
|
context.paintChild(child, _ssPositions[idx]);
|
||||||
child = parentData.nextSibling;
|
child = parentData.nextSibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,11 +139,11 @@ class _CanvasRenderBox extends RenderBox
|
|||||||
|
|
||||||
// Check children in reverse order (last painted = first hit)
|
// Check children in reverse order (last painted = first hit)
|
||||||
RenderBox? child = lastChild;
|
RenderBox? child = lastChild;
|
||||||
for (int idx = ssPositions.length - 1; child != null; idx--) {
|
for (int idx = _ssPositions.length - 1; child != null; idx--) {
|
||||||
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
final _WidgetParentData parentData = child.parentData! as _WidgetParentData;
|
||||||
|
|
||||||
// Transform the hit position relative to the child's position
|
// Transform the hit position relative to the child's position
|
||||||
final childPosition = ssHitPosition - ssPositions[idx];
|
final childPosition = ssHitPosition - _ssPositions[idx];
|
||||||
|
|
||||||
if (child.hitTest(BoxHitTestResult.wrap(result), position: childPosition)) {
|
if (child.hitTest(BoxHitTestResult.wrap(result), position: childPosition)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
/// Convert grid space coordinates to screen space coordinates
|
||||||
|
Offset gsToSs(Offset gsPosition, Offset gsTopLeft) {
|
||||||
|
return gsPosition - gsTopLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert screen space coordinates to grid space coordinates
|
||||||
|
Offset ssToGs(Offset ssPosition, Offset gsTopLeft) {
|
||||||
|
return ssPosition + gsTopLeft;
|
||||||
|
}
|
||||||
@@ -6,4 +6,8 @@ extension OffsetCartesionOps on Offset {
|
|||||||
final dy = this.dy - other.dy;
|
final dy = this.dy - other.dy;
|
||||||
return dx * dx + dy * dy;
|
return dx * dx + dy * dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Offset operator -(Offset other) {
|
||||||
|
return Offset(dx - other.dx, dy - other.dy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user