Skip to content

Instantly share code, notes, and snippets.

@kubaracek
Last active February 20, 2024 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kubaracek/84a1776c65c3a86c92b19cce80ea4604 to your computer and use it in GitHub Desktop.
Save kubaracek/84a1776c65c3a86c92b19cce80ea4604 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Node Canvas',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NodeCanvas(),
);
}
}
class Node {
final int id;
final Node? parent;
final Offset position;
Node({
required this.id,
required this.parent,
required this.position,
});
}
class NodeCanvas extends StatefulWidget {
@override
_NodeCanvasState createState() => _NodeCanvasState();
}
class _NodeCanvasState extends State<NodeCanvas> {
List<Node> nodes = [];
Node? selectedNode;
Node? contextNode;
@override
void initState() {
super.initState();
// Initialize with a root node
nodes.add(Node(
id: 0,
parent: null,
position: Offset(750, 25),
));
}
void createChild(Offset position) {
if (!mounted || contextNode == null) return;
final parent = contextNode!;
setState(() {
nodes.add(Node(
id: nodes.length,
parent: parent,
position: position,
));
});
}
void deleteNode() {
if (!mounted || contextNode == null) return;
final nodeToRemove = contextNode!;
// Remove all children of the node
nodes.removeWhere((node) => isNodeUpstream(node, nodeToRemove));
setState(() {
nodes.removeWhere((node) => node == nodeToRemove);
contextNode = null;
selectedNode = null;
});
}
void handleNodeTap(Node node) {
print('Node tapped: ${node.id}');
setState(() {
selectedNode = node;
contextNode = node;
});
}
void handleCanvasTap(TapDownDetails details) {
final RenderBox box = context.findRenderObject() as RenderBox;
final Offset localOffset = box.globalToLocal(details.localPosition);
print('Canvas tapped at position: $localOffset');
createChild(localOffset);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Node Canvas'),
),
body: Column(
children: [
Expanded(
child: Stack(
alignment: Alignment.topLeft,
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTapDown: (details) => handleCanvasTap(details),
child: CustomPaint(
painter: LinePainter(nodes, selectedNode),
child: SizedBox.expand(),
),
),
for (final node in nodes)
Positioned(
left: node.position.dx - 20,
top: node.position.dy - 20,
child: GestureDetector(
onTap: () => handleNodeTap(node),
child: Icon(
Icons.circle,
size: 40,
color: selectedNode == node ? Colors.red : Colors.blue,
),
),
),
],
),
),
if (selectedNode != null) // Show toolbox if a node is selected
Container(
color: Colors.grey.shade200,
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: deleteNode,
child: Text('Delete Node'),
),
],
),
),
],
),
);
}
}
class LinePainter extends CustomPainter {
final List<Node> nodes;
final Node? selectedNode;
final Paint linePaint;
LinePainter(this.nodes, this.selectedNode)
: linePaint = Paint()
..strokeWidth = 3.0
..style = PaintingStyle.stroke;
@override
void paint(Canvas canvas, Size size) {
for (final node in nodes) {
// if node is downstream of selected node, draw line with green color.
// Node is downstream if any node recursively has the selected node as parent
final isDownstream = isNodeDownstream(node, selectedNode);
linePaint.color = isDownstream ? Colors.green : Colors.black87;
if (node.parent != null) {
canvas.drawLine(
node.position,
node.parent!.position,
linePaint,
);
}
canvas.drawCircle(node.position, 20, linePaint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
bool isNodeDownstream(Node node, Node? selectedNode) {
if (selectedNode == node) return true;
if (selectedNode?.parent == null) return false;
if (selectedNode?.parent == node) return true;
if (selectedNode == null) return false;
return isNodeDownstream(node, selectedNode.parent!);
}
bool isNodeUpstream(Node node, Node? selectedNode) {
if (selectedNode == node) return true;
if (node.parent == null) return false;
if (node.parent == selectedNode) return true;
if (selectedNode == null) return false;
return isNodeUpstream(node.parent!, selectedNode);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment