Skip to content

Instantly share code, notes, and snippets.

@pingbird
Last active September 25, 2022 01:42
Show Gist options
  • Save pingbird/3739dee678ee1b19e4d299c0025794b9 to your computer and use it in GitHub Desktop.
Save pingbird/3739dee678ee1b19e4d299c0025794b9 to your computer and use it in GitHub Desktop.
import 'dart:math';
import 'package:boxy/boxy.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: UnconstrainedBox(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.black12),
),
child: const Center(
child: TreeView(
root: TreeNode(
ExampleCard('A'),
[
TreeNode(ExampleCard('A.A')),
TreeNode(
ExampleCard('A.B'),
[
TreeNode(ExampleCard('A.B.A')),
TreeNode(ExampleCard('A.B.B')),
],
),
TreeNode(ExampleCard('A.C')),
],
),
horizontalSpacing: 8,
verticalSpacing: 32,
),
),
),
),
),
);
}
}
class ExampleCard extends StatelessWidget {
const ExampleCard(this.label, {Key? key}) : super(key: key);
final String label;
@override
Widget build(BuildContext context) {
return Container(
width: 150,
height: 100,
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 3.0),
borderRadius: BorderRadius.circular(16.0),
),
child: Center(
child: Text(
label,
style: const TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
);
}
}
class TreeNode {
const TreeNode(this.widget, [this.children = const []]);
final Widget widget;
final List<TreeNode> children;
Iterable<Widget> get allWidgets =>
[widget].followedBy(children.expand((e) => e.allWidgets));
}
class TreeView extends StatelessWidget {
const TreeView({
required this.root,
required this.verticalSpacing,
required this.horizontalSpacing,
super.key,
});
final TreeNode root;
final double verticalSpacing;
final double horizontalSpacing;
@override
Widget build(BuildContext context) {
return CustomBoxy(
delegate: _TreeViewBoxy(
root: root,
verticalSpacing: verticalSpacing,
horizontalSpacing: horizontalSpacing,
),
children: [...root.allWidgets],
);
}
}
class _TreeViewBoxy extends BoxyDelegate {
_TreeViewBoxy({
required this.root,
required this.verticalSpacing,
required this.horizontalSpacing,
});
final TreeNode root;
final double verticalSpacing;
final double horizontalSpacing;
@override
Size layout() {
var index = 0;
Size visit(TreeNode node, Offset offset) {
final nodeIndex = index++;
final child = children[nodeIndex];
final size = child.layout(const BoxConstraints());
final Size subtreeSize;
if (node.children.isEmpty) {
subtreeSize = size;
} else {
var width = 0.0;
var height = 0.0;
var x = 0.0;
final y = offset.dy + child.size.height + verticalSpacing;
for (final child in node.children) {
final childSize = visit(child, Offset(offset.dx + x, y));
height = max(height, childSize.height);
width += childSize.width;
x += childSize.width + horizontalSpacing;
}
width += (node.children.length - 1) * horizontalSpacing;
subtreeSize = Size(
max(width, size.width),
size.height + height + verticalSpacing,
);
}
child.position(
offset +
Offset(
subtreeSize.width / 2 - child.size.width / 2,
0,
),
);
return subtreeSize;
}
return visit(root, Offset.zero);
}
@override
void paint() {
var index = 0;
void paintLines(TreeNode node) {
final nodeOffset = children[index++].rect.bottomCenter;
for (final child in node.children) {
final childOffset = children[index].rect.topCenter;
canvas.drawPath(
Path()
..moveTo(nodeOffset.dx, nodeOffset.dy)
..cubicTo(
nodeOffset.dx,
nodeOffset.dy + verticalSpacing,
childOffset.dx,
childOffset.dy - verticalSpacing,
childOffset.dx,
childOffset.dy,
),
Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3.0,
);
paintLines(child);
}
}
paintLines(root);
}
@override
bool shouldRelayout(_TreeViewBoxy oldDelegate) =>
root != oldDelegate.root ||
verticalSpacing != oldDelegate.verticalSpacing ||
horizontalSpacing != oldDelegate.horizontalSpacing;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment