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 70b86074..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'; @@ -336,6 +338,7 @@ class _CommunityStructureAreaState extends State { } spaces.add(newSpace); _updateNodePosition(newSpace, newSpace.position); + realignTree(); }); }, ); @@ -450,7 +453,6 @@ class _CommunityStructureAreaState extends State { void _saveSpaces() { if (widget.selectedCommunity == null) { - debugPrint("No community selected for saving spaces."); return; } @@ -530,35 +532,83 @@ class _CommunityStructureAreaState extends State { } Offset getBalancedChildPosition(SpaceModel parent) { - int totalSiblings = parent.children.length + 1; - double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing - double startX = parent.position.dx - (totalWidth / 2); + const double nodeWidth = 200; + const double verticalGap = 180; - Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180); + if (parent.children.isEmpty) { + // First child → exactly center + return Offset(parent.position.dx, parent.position.dy + verticalGap); + } else { + // More children → arrange them spaced horizontally + double totalWidth = (parent.children.length) * (nodeWidth + 60); + double startX = parent.position.dx - (totalWidth / 2); - // Check for overlaps & adjust - while (spaces.any((s) => (s.position - position).distance < 250)) { - position = Offset(position.dx + 250, position.dy); + double childX = startX + (parent.children.length * (nodeWidth + 60)); + return Offset(childX, parent.position.dy + verticalGap); } - - return position; } void realignTree() { - void updatePositions(SpaceModel node, double x, double y) { - node.position = Offset(x, y); + const double nodeWidth = 200; + const double nodeHeight = 100; + const double horizontalGap = 60; + const double verticalGap = 180; + const double rootGap = 400; // extra space between different roots - int numChildren = node.children.length; - double childStartX = x - ((numChildren - 1) * 250) / 2; + double canvasRightEdge = 1000; + double canvasBottomEdge = 1000; - for (int i = 0; i < numChildren; i++) { - updatePositions(node.children[i], childStartX + (i * 250), y + 180); + 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 layoutSubtree(SpaceModel node, double startX, double y) { + double subtreeWidth = calculateSubtreeWidth(node); + double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; + node.position = Offset(centerX, y); + + canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth); + canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight); + + if (node.children.length == 1) { + final child = node.children.first; + layoutSubtree(child, centerX, y + verticalGap); + } else { + double childX = startX; + for (var child in node.children) { + double childWidth = calculateSubtreeWidth(child); + layoutSubtree(child, childX, y + verticalGap); + childX += childWidth + horizontalGap; + } } } - if (spaces.isNotEmpty) { - updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); + // ⚡ New: layout each root separately + final List roots = spaces + .where((s) => + s.parent == null && + s.status != SpaceStatus.deleted && + s.status != SpaceStatus.parentDeleted) + .toList(); + + double currentX = 100; // start some margin from left + double currentY = 100; // top margin + + 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) { @@ -642,63 +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) { - int totalSiblings = parent.children.length + 1; - double totalWidth = (totalSiblings - 1) * horizontalGap; - double startX = parent.position.dx - (totalWidth / 2); - Offset position = Offset( - startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap); - - // **Check for overlaps & adjust** - while (spaces.any((s) => (s.position - position).distance < horizontalGap)) { - position = Offset(position.dx + horizontalGap, position.dy); - } - - print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})"); - return position; - } - - /// **Realign the entire tree after duplication** - void realignTree() { - void updatePositions(SpaceModel node, double x, double y) { - node.position = Offset(x, y); - print("✅ Adjusted ${node.name} to (${x}, ${y})"); - - int numChildren = node.children.length; - double childStartX = x - ((numChildren - 1) * horizontalGap) / 2; - - for (int i = 0; i < numChildren; i++) { - updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap); - } - } - - if (spaces.isNotEmpty) { - print("🔄 Realigning tree..."); - updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); - } - } - - /// **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); - print( - "🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})"); - final duplicated = SpaceModel( name: duplicatedName, icon: original.icon, - position: newPosition, + position: Offset.zero, isPrivate: original.isPrivate, children: [], status: SpaceStatus.newSpace, @@ -708,28 +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); - print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}"); - } + 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); } @@ -737,21 +735,49 @@ class _CommunityStructureAreaState extends State { return duplicated; } - /// **Handle root duplication** - if (space.parent == null) { - print("🟠 Duplicating root node: ${space.name}"); - 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(); - }); + void assignPositions(SpaceModel n, double x, double y) { + double subtreeWidth = calculateSubtreeWidth(n); + double centerX = x + subtreeWidth / 2 - nodeWidth / 2; + n.position = Offset(centerX, y); - print("✅ Root duplication successful: ${duplicatedRoot.name}"); - } else { - duplicateRecursive(space, space.parent); + 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); } - print("🟢 Finished duplication process for: ${space.name}"); + /// 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(); + } + }); } }