diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 5fb648e3..6d742e69 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -1,4 +1,6 @@ // Flutter imports +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -551,62 +553,62 @@ class _CommunityStructureAreaState extends State { const double nodeHeight = 100; const double horizontalGap = 60; const double verticalGap = 180; + const double rootGap = 400; // extra space between different roots double canvasRightEdge = 1000; double canvasBottomEdge = 1000; - double _calculateSubtreeWidth(SpaceModel node) { + double calculateSubtreeWidth(SpaceModel node) { if (node.children.isEmpty) return nodeWidth; double totalWidth = 0; for (var child in node.children) { - totalWidth += _calculateSubtreeWidth(child) + horizontalGap; + totalWidth += calculateSubtreeWidth(child) + horizontalGap; } return totalWidth - horizontalGap; } - void _layoutTree(SpaceModel node, double startX, double y) { - double subtreeWidth = _calculateSubtreeWidth(node); + void layoutSubtree(SpaceModel node, double startX, double y) { + double subtreeWidth = calculateSubtreeWidth(node); double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; node.position = Offset(centerX, y); - canvasRightEdge = - centerX + nodeWidth > canvasRightEdge ? centerX + nodeWidth : canvasRightEdge; - canvasBottomEdge = y + nodeHeight > canvasBottomEdge ? y + nodeHeight : canvasBottomEdge; + canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth); + canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight); if (node.children.length == 1) { final child = node.children.first; - double parentCenterX = node.position.dx + nodeWidth / 2; - double childX = parentCenterX - nodeWidth / 2; - double childY = y + verticalGap; - - child.position = Offset(childX, childY); - _layoutTree(child, childX, childY); + layoutSubtree(child, centerX, y + verticalGap); } else { double childX = startX; for (var child in node.children) { - double childWidth = _calculateSubtreeWidth(child); - _layoutTree(child, childX, y + verticalGap); + double childWidth = calculateSubtreeWidth(child); + layoutSubtree(child, childX, y + verticalGap); childX += childWidth + horizontalGap; } } } - final SpaceModel? root = spaces.firstWhere( - (s) => s.parent == null, - orElse: () => spaces.first, - ); + // ⚡ New: layout each root separately + final List roots = spaces + .where((s) => + s.parent == null && + s.status != SpaceStatus.deleted && + s.status != SpaceStatus.parentDeleted) + .toList(); - if (root != null) { - double totalTreeWidth = _calculateSubtreeWidth(root); - double startingX = (canvasWidth / 2) - (totalTreeWidth / 2); + double currentX = 100; // start some margin from left + double currentY = 100; // top margin - _layoutTree(root, startingX, 100); - - setState(() { - canvasWidth = canvasRightEdge + 200; - canvasHeight = canvasBottomEdge + 200; - }); + for (var root in roots) { + layoutSubtree(root, currentX, currentY); + double rootWidth = calculateSubtreeWidth(root); + currentX += rootWidth + rootGap; } + + setState(() { + canvasWidth = canvasRightEdge + 400; + canvasHeight = canvasBottomEdge + 400; + }); } void _onDuplicate(BuildContext parentContext) { @@ -690,110 +692,19 @@ class _CommunityStructureAreaState extends State { } void _duplicateSpace(SpaceModel space) { - final Map originalToDuplicate = {}; - double horizontalGap = 250.0; // Increased spacing - double verticalGap = 180.0; // Adjusted for better visualization + final double horizontalGap = 250.0; + final double verticalGap = 180.0; + final double nodeWidth = 200; + final double nodeHeight = 100; + final double breathingSpace = 300.0; // extra gap after original tree - print("🟢 Duplicating: ${space.name}"); - - /// **Find a new position ensuring no overlap** - Offset getBalancedChildPosition(SpaceModel parent) { - const double nodeWidth = 200; - const double horizontalGap = 60; - const double verticalGap = 180; - - int numSiblings = parent.children.length; - double totalWidth = numSiblings * (nodeWidth + horizontalGap); - - // Calculate position directly beneath the parent, spaced horizontally - double x = parent.position.dx - (totalWidth / 2) + numSiblings * (nodeWidth + horizontalGap); - double y = parent.position.dy + verticalGap; - - return Offset(x, y); - } - - /// **Realign the entire tree after duplication** - void realignTree() { - const double nodeWidth = 200; - const double nodeHeight = 100; - const double horizontalGap = 60; - const double verticalGap = 180; - - double canvasRightEdge = 1000; - double canvasBottomEdge = 1000; - - // Step 1: Calculate the width of any subtree - double _calculateSubtreeWidth(SpaceModel node) { - if (node.children.isEmpty) return nodeWidth; - double totalWidth = 0; - for (var child in node.children) { - totalWidth += _calculateSubtreeWidth(child) + horizontalGap; - } - return totalWidth - horizontalGap; - } - - void _layoutTree(SpaceModel node, double startX, double y) { - double subtreeWidth = _calculateSubtreeWidth(node); - double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; - node.position = Offset(centerX, y); - - canvasRightEdge = - centerX + nodeWidth > canvasRightEdge ? centerX + nodeWidth : canvasRightEdge; - canvasBottomEdge = y + nodeHeight > canvasBottomEdge ? y + nodeHeight : canvasBottomEdge; - - if (node.children.length == 1) { - final child = node.children.first; - - double parentCenterX = node.position.dx + nodeWidth / 2; - double childX = parentCenterX - nodeWidth / 2; - double childY = y + verticalGap; - - child.position = Offset(childX, childY); - - _layoutTree(child, childX, childY); - } else { - double childX = startX; - for (var child in node.children) { - double childWidth = _calculateSubtreeWidth(child); - _layoutTree(child, childX, y + verticalGap); - childX += childWidth + horizontalGap; - } - } - } - - // Step 3: Start centered - final SpaceModel? root = spaces.firstWhere( - (s) => s.parent == null, - orElse: () => spaces.first, - ); - - if (root != null) { - // Calculate total width of full tree - double totalTreeWidth = _calculateSubtreeWidth(root); - - double startingX = (canvasWidth / 2) - (totalTreeWidth / 2); - - _layoutTree(root, startingX, 100); - - setState(() { - canvasWidth = canvasRightEdge + 200; // give breathing space - canvasHeight = canvasBottomEdge + 200; - }); - } - } - - /// **Recursive duplication logic** + /// Helper to recursively duplicate a node and its children SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { - Offset newPosition = duplicatedParent == null - ? Offset(original.position.dx + horizontalGap, original.position.dy) - : getBalancedChildPosition(duplicatedParent); - final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); - final duplicated = SpaceModel( name: duplicatedName, icon: original.icon, - position: newPosition, + position: Offset.zero, isPrivate: original.isPrivate, children: [], status: SpaceStatus.newSpace, @@ -803,27 +714,20 @@ class _CommunityStructureAreaState extends State { tags: original.tags, ); - setState(() { - spaces.add(duplicated); - _updateNodePosition(duplicated, duplicated.position); + spaces.add(duplicated); - if (duplicatedParent != null) { - final newConnection = Connection( - startSpace: duplicatedParent, - endSpace: duplicated, - direction: "down", - ); - connections.add(newConnection); - duplicated.incomingConnection = newConnection; - duplicatedParent.addOutgoingConnection(newConnection); - duplicatedParent.children.add(duplicated); - } + if (duplicatedParent != null) { + final newConnection = Connection( + startSpace: duplicatedParent, + endSpace: duplicated, + direction: "down", + ); + connections.add(newConnection); + duplicated.incomingConnection = newConnection; + duplicatedParent.addOutgoingConnection(newConnection); + duplicatedParent.children.add(duplicated); + } - // **Recalculate the whole tree to avoid overlaps** - realignTree(); - }); - - // Recursively duplicate children for (var child in original.children) { duplicateRecursive(child, duplicated); } @@ -831,16 +735,49 @@ class _CommunityStructureAreaState extends State { return duplicated; } - /// **Handle root duplication** - if (space.parent == null) { - SpaceModel duplicatedRoot = duplicateRecursive(space, null); + /// Layout a subtree rooted at node + void layoutSubtree(SpaceModel node, double startX, double startY) { + double calculateSubtreeWidth(SpaceModel n) { + if (n.children.isEmpty) return nodeWidth; + double width = 0; + for (var child in n.children) { + width += calculateSubtreeWidth(child) + horizontalGap; + } + return width - horizontalGap; + } - setState(() { - spaces.add(duplicatedRoot); - realignTree(); - }); - } else { - duplicateRecursive(space, space.parent); + void assignPositions(SpaceModel n, double x, double y) { + double subtreeWidth = calculateSubtreeWidth(n); + double centerX = x + subtreeWidth / 2 - nodeWidth / 2; + n.position = Offset(centerX, y); + + if (n.children.length == 1) { + assignPositions(n.children.first, centerX, y + verticalGap); + } else { + double childX = x; + for (var child in n.children) { + double childWidth = calculateSubtreeWidth(child); + assignPositions(child, childX, y + verticalGap); + childX += childWidth + horizontalGap; + } + } + } + + double totalSubtreeWidth = calculateSubtreeWidth(node); + assignPositions(node, startX, startY); } + + /// Actual duplication process + setState(() { + if (space.parent == null) { + // Duplicating a ROOT node + SpaceModel duplicatedRoot = duplicateRecursive(space, null); + realignTree(); + } else { + // Duplicating a CHILD node inside its parent + SpaceModel duplicated = duplicateRecursive(space, space.parent); + realignTree(); + } + }); } }