import 'package:flutter/widgets.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Column, Expanded, Row; typedef VirtualTreeItemBuilder = Widget Function(BuildContext context, VirtualTreeEntry entry); typedef VirtualTreeRowTransitionBuilder = Widget Function(BuildContext context, VirtualTreeEntry entry, Widget row); class VirtualTreeEntry { const VirtualTreeEntry({required this.node, required this.depth}); final TreeItemNode node; final List depth; } class VirtualTreeView extends StatelessWidget { const VirtualTreeView({ super.key, required this.nodes, required this.itemBuilder, this.branchLine = BranchLine.path, this.padding = const EdgeInsets.all(8), this.rowHeight = 28, this.cacheExtent = 280, this.controller, this.itemPadding, this.transitionBuilder, }); final List> nodes; final VirtualTreeItemBuilder itemBuilder; final BranchLine branchLine; final EdgeInsetsGeometry padding; final double rowHeight; final double cacheExtent; final ScrollController? controller; final EdgeInsetsGeometry? itemPadding; final VirtualTreeRowTransitionBuilder? transitionBuilder; @override Widget build(BuildContext context) { final entries = _flattenVisibleNodes(nodes); final theme = Theme.of(context); final densityGap = theme.density.baseGap * theme.scaling; return ListView.builder( controller: controller, padding: padding, cacheExtent: cacheExtent, itemCount: entries.length, itemBuilder: (context, index) { final entry = entries[index]; Widget row = SizedBox( height: rowHeight, child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ for (var depthIndex = 1; depthIndex < entry.depth.length; depthIndex++) ...[ SizedBox(width: densityGap), SizedBox(width: densityGap * 2, child: branchLine.build(context, entry.depth, depthIndex)), ], Expanded( child: Padding( padding: itemPadding ?? EdgeInsets.symmetric(horizontal: densityGap, vertical: densityGap * 0.5), child: itemBuilder(context, entry), ), ), ], ), ); final transition = transitionBuilder; if (transition != null) { row = transition(context, entry, row); } return row; }, ); } } List> _flattenVisibleNodes(List> nodes) { final entries = >[]; void walk(List> currentNodes, List depth) { for (var index = 0; index < currentNodes.length; index++) { final node = currentNodes[index]; if (node is TreeRootNode) { walk(node.children, depth); continue; } if (node is! TreeItemNode) continue; final currentDepth = [...depth, TreeNodeDepth(index, currentNodes.length)]; entries.add(VirtualTreeEntry(node: node, depth: currentDepth)); if (node.expanded) { walk(node.children, currentDepth); } } } walk(nodes, const []); return entries; }