From 49878f084911703ee8a36bf41be9ed2a338d6266 Mon Sep 17 00:00:00 2001 From: ShahzaibAhmad05 Date: Fri, 2 Jan 2026 16:09:11 +0500 Subject: [PATCH] Move Selection logic to Simulation class --- Models/SelectionManager.cs | 531 -------------------- Models/Simulation/Simulation.Selection.cs | 487 ++++++++++++++++++ Models/{ => Simulation}/Simulation.Wires.cs | 1 + Models/{ => Simulation}/Simulation.cs | 18 +- 4 files changed, 493 insertions(+), 544 deletions(-) delete mode 100644 Models/SelectionManager.cs create mode 100644 Models/Simulation/Simulation.Selection.cs rename Models/{ => Simulation}/Simulation.Wires.cs (99%) rename Models/{ => Simulation}/Simulation.cs (93%) diff --git a/Models/SelectionManager.cs b/Models/SelectionManager.cs deleted file mode 100644 index a8af930..0000000 --- a/Models/SelectionManager.cs +++ /dev/null @@ -1,531 +0,0 @@ -// Models/SelectionManager.cs -using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Input; -using IRis.Models.Components; -using IRis.Models.Core; -using IRis.Models.Commands; - -namespace IRis.Models; - -public class SelectionManager -{ - private Point _selectionStart; - private Rectangle? _selectionRect; - - // Dragging state - private bool _isDragging; - private Point _dragStart; - private List _dragStartPositions = new(); - private List _draggedComponents = new(); - private Dictionary _dragOffsets = new(); - - public void HandleStart(Canvas canvas, List selectedComponents, Point mousePos) - { - _selectionStart = mousePos; - - // Check if we're clicking on a selected component to prepare for potential dragging - foreach (var child in canvas.Children) - { - if (child is Component component && IsComponentHit(component, mousePos)) - { - if (component.IsSelected) - { - // DON'T start dragging immediately - just prepare for it - // Store the drag start position and offsets, but don't set _isDragging = true yet - _dragStart = mousePos; - _dragOffsets.Clear(); - - // Store initial offsets for all selected components - foreach (var selectedComponent in selectedComponents) - { - Point componentPos = new Point(Canvas.GetLeft(selectedComponent), Canvas.GetTop(selectedComponent)); - Point offset = new Point(componentPos.X - mousePos.X, componentPos.Y - mousePos.Y); - _dragOffsets[selectedComponent] = offset; - } - return; - } - else - { - // FIXED: Clear other selections first, then select this component - UnselectAll(selectedComponents); - Console.WriteLine("Component added to selected components"); - selectedComponents.Add(component); - component.IsSelected = true; - - // Select wires connected to the components - SelectConnectedWires(component, selectedComponents); - return; - } - } - } - // Unselect components if hitting empty space - UnselectAll(selectedComponents); - StartSelectionRectangle(canvas); - } - - // Add allComponents and simulation parameters for wire snapping, Invoked Externally by Simulation.cs - public void HandleUpdate(Canvas canvas, List selectedComponents, Point currentMousePos, - List allComponents, Simulation simulation, - bool snapToGridEnabled = false, Func? snapToGrid = null) - { - // Check if we should start dragging (mouse moved far enough from start position) - if (!_isDragging && _dragOffsets.Count > 0) - { - double dragThreshold = 3.0; // Minimum distance to start dragging - double distance = Math.Sqrt(Math.Pow(currentMousePos.X - _dragStart.X, 2) + - Math.Pow(currentMousePos.Y - _dragStart.Y, 2)); - - if (distance > dragThreshold) - { - _isDragging = true; - } - } - - // Handle dragging selected components - if (_isDragging) - { - UpdateDraggedComponents(selectedComponents, currentMousePos, allComponents, simulation, snapToGridEnabled, snapToGrid); - return; - } - - // No selection area to update, let the event handler go on - if (_selectionRect == null) return; - - UpdateSelectionRectangle(currentMousePos); - UpdateSelectedComponents(canvas, selectedComponents); - } - - - private void SelectConnectedWires(Component component, List selectedComponents) - { // Select all wires connected to a component - if (component.Terminals == null) return; - - foreach (var terminal in component.Terminals) - { - // Change from single wire to multiple wires - foreach (var wire in terminal.Wires) - { - if (wire != null && !wire.IsSelected) - { - wire.IsSelected = true; - selectedComponents.Add(wire); - } - } - } - } - - private bool IsComponentHit(Component component, Point point) - { - var componentPos = new Point(Canvas.GetLeft(component), Canvas.GetTop(component)); - var componentBounds = new Rect(componentPos, new Size(component.Width, component.Height)); - - // Check intersection for components (gates) - if (componentBounds.Contains(point)) return true; - - // Check For wires - return component is Wire && component.HitTest(point); - } - - public void UnselectAll(List selectedComponents) - { - Console.WriteLine("Unselecting all components"); - // Unselect all components - foreach (Component c in selectedComponents) - c.IsSelected = false; - selectedComponents.Clear(); - } - - private void StartSelectionRectangle(Canvas canvas) - { - // Add a selection rectangle to the canvas - _selectionRect = new Rectangle - { - Width = 0, - Height = 0, - Fill = ComponentDefaults.SelectionBrush, - Stroke = ComponentDefaults.SelectionPen.Brush, - StrokeThickness = ComponentDefaults.SelectionPen.Thickness - }; - Canvas.SetLeft(_selectionRect, _selectionStart.X); - Canvas.SetTop(_selectionRect, _selectionStart.Y); - canvas.Children.Add(_selectionRect); - } - - // Added wire endpoint snapping functionality - private void UpdateDraggedComponents(List selectedComponents, Point currentMousePos, - List allComponents, Simulation simulation, - bool snapToGridEnabled, Func? snapToGrid) - { - // Add a check for validating wires - foreach (var component in selectedComponents) - { - if (component is Wire wire) - { - if (WireHasBreaks(wire)) - { - Console.WriteLine("Movement not allowed for extended wires!"); - return; - } - } - } - - foreach (var component in selectedComponents) - { - if (!_dragOffsets.TryGetValue(component, out Point offset)) continue; - - Point newPos = new Point(currentMousePos.X + offset.X, currentMousePos.Y + offset.Y); - - // Apply grid snapping if enabled - if (snapToGridEnabled && snapToGrid != null) - newPos = snapToGrid(newPos); - - Canvas.SetLeft(component, newPos.X); - Canvas.SetTop(component, newPos.Y); - - // Snap wire endpoints to their connected terminals - if (component is Wire wire) - { - SnapWireEndpointsToTerminals(wire, allComponents); - AddCornerPointsToWire(wire); - } - // Force redraw for components that need it (like wires) - component.InvalidateVisual(); - } - } - - private void SnapWireEndpointsToTerminals(Wire wire, List allComponents) - { - var connectedTerminals = FindTerminalsConnectedToWire(wire, allComponents); - if (connectedTerminals.Count == 0 || wire.Points.Count == 0) return; - - // Get the current wire position - Point wirePos = new Point(Canvas.GetLeft(wire), Canvas.GetTop(wire)); - - // Update first endpoint if connected to a terminal - if (connectedTerminals.Count >= 1) - { - Point firstTerminalWorldPos = GetTerminalWorldPosition(connectedTerminals[0], allComponents); - Point firstTerminalLocalPos = firstTerminalWorldPos - wirePos; - wire.Points[0] = firstTerminalLocalPos; - } - - // Update last endpoint if connected to a second terminal - if (connectedTerminals.Count >= 2 && wire.Points.Count > 1) - { - Point secondTerminalWorldPos = GetTerminalWorldPosition(connectedTerminals[1], allComponents); - Point secondTerminalLocalPos = secondTerminalWorldPos - wirePos; - wire.Points[^1] = secondTerminalLocalPos; - } - } - - // TODO: remove this function & references when wire extension movement is fixed - private bool WireHasBreaks(Wire wire) - { - for (int i = 0; i < wire.Points.Count - 1; i++) - { - if (wire.Points[i] == new Point(-1, -1)) return true; - } - return false; - } - - private void AddCornerPointsToWire(Wire wire) - { - for (int i = 0; i < wire.Points.Count - 1; i++) - { - // If two points are not orthogonal then add a point in-between - if (wire.Points[i].X != wire.Points[i + 1].X && wire.Points[i].Y != wire.Points[i + 1].Y) - { - wire.Points.Insert(i + 1, new Point(wire.Points[i].X, wire.Points[i + 1].Y)); - } - } - - // Remove unnecessary points added by this function - for (int i = 0; i < wire.Points.Count - 2; i++) - { - // Do NOT remove the points of extensions - if (i >= 1 && (wire.Points[i - 1] == new Point(-1, -1))) continue; - // If three points are on the same axis then remove one - if (wire.Points[i].X == wire.Points[i + 1].X && wire.Points[i + 1].X == wire.Points[i + 2].X) - { - wire.Points.RemoveAt(i + 1); - } - else if (wire.Points[i].Y == wire.Points[i + 1].Y && wire.Points[i + 1].Y == wire.Points[i + 2].Y) - { - wire.Points.RemoveAt(i + 1); - } - } - } - - // Find all terminals connected to a specific wire - private List FindTerminalsConnectedToWire(Wire wire, List allComponents) - { - var connectedTerminals = new List(); - - foreach (var component in allComponents) - { - if (component.Terminals == null) continue; - - foreach (var terminal in component.Terminals) - { - // Change from single wire check to multiple wires check - if (terminal.Wires.Contains(wire)) - { - connectedTerminals.Add(terminal); - } - } - } - - return connectedTerminals; - } - - // Get the world position of a terminal - private Point GetTerminalWorldPosition(Terminal terminal, List allComponents) - { - // Find the component that owns this terminal - Component? ownerComponent = null; - foreach (var component in allComponents) - { - if (component.Terminals?.Contains(terminal) == true) - { - ownerComponent = component; - break; - } - } - - if (ownerComponent == null) - return new Point(0, 0); - - // Get component's world position - Point componentPos = new Point(Canvas.GetLeft(ownerComponent), Canvas.GetTop(ownerComponent)); - - // Add terminal's relative position to component position - // and whether it accounts for component rotation - Point terminalLocalPos = terminal.Position; - - // If the component is rotated, we need to transform the terminal position - if (ownerComponent.Rotation != 0) - { - terminalLocalPos = RotatePoint(terminalLocalPos, ownerComponent.Rotation, - new Point(ownerComponent.Width / 2, ownerComponent.Height / 2)); - } - - return componentPos + terminalLocalPos; - } - - // Rotate a point around a center point by a given angle - private Point RotatePoint(Point point, double angleDegrees, Point center) - { - double angleRadians = angleDegrees * Math.PI / 180.0; - double cos = Math.Cos(angleRadians); - double sin = Math.Sin(angleRadians); - - // Translate point to origin - double translatedX = point.X - center.X; - double translatedY = point.Y - center.Y; - - // Rotate - double rotatedX = translatedX * cos - translatedY * sin; - double rotatedY = translatedX * sin + translatedY * cos; - - // Translate back - return new Point(rotatedX + center.X, rotatedY + center.Y); - } - - private void UpdateSelectionRectangle(Point currentMousePos) - { - if (_selectionRect == null) return; // Truly exceptional case - // Calculate bounds - double left = Math.Min(_selectionStart.X, currentMousePos.X); - double top = Math.Min(_selectionStart.Y, currentMousePos.Y); - double width = Math.Abs(_selectionStart.X - currentMousePos.X); - double height = Math.Abs(_selectionStart.Y - currentMousePos.Y); - - // Update rectangle - Canvas.SetLeft(_selectionRect, left); - Canvas.SetTop(_selectionRect, top); - _selectionRect.Width = width; - _selectionRect.Height = height; - } - - private void UpdateSelectedComponents(Canvas canvas, List selectedComponents) - { - // Unselect everything and reselect again - UnselectAll(selectedComponents); - - var selectionBounds = GetSelectionBounds(); - var componentsInSelection = new List(); - - // Check each component - foreach (var child in canvas.Children) - { - if (child is Component component && IsComponentInSelection(component, selectionBounds)) - { - component.IsSelected = true; - selectedComponents.Add(component); - componentsInSelection.Add(component); - } - } - - // Select connected wires for all selected components - SelectConnectedWiresForComponents(componentsInSelection, canvas, selectedComponents); - } - - private void SelectConnectedWiresForComponents(List componentsToCheck, Canvas canvas, List selectedComponents) - { - foreach (var component in componentsToCheck) - { - if (component is Wire) continue; // Skip wires themselves - - SelectConnectedWires(component, selectedComponents); - } - } - - private Rect GetSelectionBounds() - { - if (_selectionRect == null) return new Rect(); // Not Expected to occur - double left = Canvas.GetLeft(_selectionRect); - double top = Canvas.GetTop(_selectionRect); - return new Rect(left, top, _selectionRect.Width, _selectionRect.Height); - } - - private bool IsComponentInSelection(Component component, Rect selectionBounds) - { - var componentPos = new Point(Canvas.GetLeft(component), Canvas.GetTop(component)); - var componentBounds = new Rect(componentPos, new Size(component.Width, component.Height)); - - // Check intersection - if (selectionBounds.Intersects(componentBounds)) return true; - - // To select a wire, the selection must contain one of its vertices - if (component is Wire wire) - { - return wire.Points.Any(p => selectionBounds.Contains(p + new Point(Canvas.GetLeft(wire), Canvas.GetTop(wire)))); - } - - return false; - } - - public void HandleEnd(Canvas canvas, CommandManager? commandManager = null) - { - // End dragging - if (_isDragging) - { - // Create move command if components were actually moved - if (_dragOffsets.Count > 0) - { - var selectedComponents = _dragOffsets.Keys.ToList(); - var originalPositions = selectedComponents.Select(c => - new Point(Canvas.GetLeft(c) - _dragOffsets[c].X - (_dragStart.X - _dragStart.X), - Canvas.GetTop(c) - _dragOffsets[c].Y - (_dragStart.Y - _dragStart.Y))).ToList(); - var newPositions = selectedComponents.Select(c => - new Point(Canvas.GetLeft(c), Canvas.GetTop(c))).ToList(); - - // Only create command if positions actually changed - bool moved = false; - for (int i = 0; i < originalPositions.Count; i++) - { - var originalPos = new Point(Canvas.GetLeft(selectedComponents[i]), Canvas.GetTop(selectedComponents[i])); - originalPos = new Point(originalPos.X - _dragOffsets[selectedComponents[i]].X, - originalPos.Y - _dragOffsets[selectedComponents[i]].Y); - originalPos = new Point(originalPos.X + _dragStart.X, originalPos.Y + _dragStart.Y); - - if (Math.Abs(originalPos.X - newPositions[i].X) > 0.1 || - Math.Abs(originalPos.Y - newPositions[i].Y) > 0.1) - { - moved = true; - break; - } - } - - if (moved && commandManager != null) - { - // Use the 3-argument constructor: canvas, components, newPositions - var moveCommand = new MoveComponentsCommand(canvas, selectedComponents, newPositions); - commandManager.ExecuteCommand(moveCommand); - } - } - - _isDragging = false; - } - - // Clean up drag preparation state even if we never actually started dragging - _dragOffsets.Clear(); - - // Remove the selection rect if its there - if (_selectionRect != null) - { - canvas.Children.Remove(_selectionRect); - _selectionRect = null; - } - } - - public void DeleteSelected(Canvas canvas, List components, List selectedComponents) - { - foreach (var component in selectedComponents) - { - canvas.Children.Remove(component); - components.Remove(component); - } - selectedComponents.Clear(); - } - - // Drag handling - public void OnPointerPressed(object? sender, PointerPressedEventArgs e, List selectedComponents) - { - if (selectedComponents.Any()) - { - Console.WriteLine("Drag started"); - _isDragging = true; - _draggedComponents = [.. selectedComponents]; - _dragStartPositions = [.. _draggedComponents.Select(c => - new Point(Canvas.GetLeft(c), Canvas.GetTop(c)))]; - } - } - - public void OnPointerReleased(object? sender, PointerReleasedEventArgs e, CommandManager commandManager) - { - if (_isDragging && _draggedComponents.Any()) - { - // Calculate the total movement and create a move command - var currentPositions = _draggedComponents.Select(c => - new Point(Canvas.GetLeft(c), Canvas.GetTop(c))).ToList(); - - // Check if components actually moved - bool hasMoved = false; - for (int i = 0; i < _dragStartPositions.Count; i++) - { - if (_dragStartPositions[i] != currentPositions[i]) - { - hasMoved = true; - break; - } - } - - if (hasMoved) - { - // Calculate offset from first component - var offset = currentPositions[0] - _dragStartPositions[0]; - - // Reset components to original positions - for (int i = 0; i < _draggedComponents.Count; i++) - { - Canvas.SetLeft(_draggedComponents[i], _dragStartPositions[i].X); - Canvas.SetTop(_draggedComponents[i], _dragStartPositions[i].Y); - } - - // Create and execute move command - var moveCommand = new MoveComponentsCommand(_draggedComponents, offset); - commandManager.ExecuteCommand(moveCommand); - } - } - - _isDragging = false; - _draggedComponents.Clear(); - _dragStartPositions.Clear(); - } -} \ No newline at end of file diff --git a/Models/Simulation/Simulation.Selection.cs b/Models/Simulation/Simulation.Selection.cs new file mode 100644 index 0000000..11cd8e1 --- /dev/null +++ b/Models/Simulation/Simulation.Selection.cs @@ -0,0 +1,487 @@ +// Models/Simulation.Selection.cs +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Input; +using IRis.Models.Components; +using IRis.Models.Core; +using IRis.Models.Commands; + +namespace IRis.Models; + +// Selection/dragging logic as part of Simulation +// Contains methods and Attributes related to selection (used privately only) +public partial class Simulation +{ + // ------------------------- + // Contains methods and Attributes + // ------------------------- + private Point _selectionStart; + private Rectangle? _selectionRect; + + // Dragging state + private bool _isDragging; + private Point _dragStart; + + // When user clicks a selected component, we "prepare" drag first + private readonly Dictionary _dragOffsets = new(); + + // Used by OnPointerPressed/Released "command move" logic (your old SelectionManager version had this too) + private readonly List _dragStartPositions = new(); + private readonly List _draggedComponents = new(); + + // ------------------------------------------------------------ + // Public API used by Simulation.cs (replaces _selectionManager) + // ------------------------------------------------------------ + + /// + /// Called from Simulation.OnPointerPressed when nothing is being placed (no preview). + /// + private void Selection_HandleStart(Point mousePos) + { + _selectionStart = mousePos; + + // Check if we're clicking on a selected component to prepare for potential dragging + foreach (var child in Canvas.Children) + { + if (child is Component component && Selection_IsComponentHit(component, mousePos)) + { + if (component.IsSelected) + { + // Prepare for drag (don't start dragging immediately) + _dragStart = mousePos; + _dragOffsets.Clear(); + + // Store initial offsets for all currently selected components + foreach (var selectedComponent in _selectedComponents) + { + var componentPos = new Point(Canvas.GetLeft(selectedComponent), Canvas.GetTop(selectedComponent)); + var offset = new Point(componentPos.X - mousePos.X, componentPos.Y - mousePos.Y); + _dragOffsets[selectedComponent] = offset; + } + return; + } + else + { + // Clear old selections, select this component + Selection_UnselectAll(); + + _selectedComponents.Add(component); + component.IsSelected = true; + + // Select wires connected to the component + Selection_SelectConnectedWires(component); + return; + } + } + } + + // Clicked on empty space: unselect everything and start selection rectangle + Selection_UnselectAll(); + Selection_StartSelectionRectangle(); + } + + /// + /// Called from Simulation.OnPointerMoved when not placing preview component. + /// + private void Selection_HandleUpdate( + Point currentMousePos, + bool snapToGridEnabled, + Func snapToGrid) + { + // Start dragging only if mouse moved enough + if (!_isDragging && _dragOffsets.Count > 0) + { + const double dragThreshold = 3.0; + double dx = currentMousePos.X - _dragStart.X; + double dy = currentMousePos.Y - _dragStart.Y; + double distance = Math.Sqrt(dx * dx + dy * dy); + + if (distance > dragThreshold) + _isDragging = true; + } + + // Dragging selected components + if (_isDragging) + { + Selection_UpdateDraggedComponents(currentMousePos, snapToGridEnabled, snapToGrid); + return; + } + + // No selection rectangle to update + if (_selectionRect == null) return; + + Selection_UpdateSelectionRectangle(currentMousePos); + Selection_UpdateSelectedComponents(); + } + + /// + /// Called from Simulation.OnPointerReleased after drag completes. + /// Executes MoveComponentsCommand (canvas, selectedComponents, newPositions) if moved. + /// + private void Selection_HandleEnd(CommandManager? commandManager = null) + { + if (_isDragging) + { + if (_dragOffsets.Count > 0) + { + var movedComponents = _dragOffsets.Keys.ToList(); + var newPositions = movedComponents + .Select(c => new Point(Canvas.GetLeft(c), Canvas.GetTop(c))) + .ToList(); + + bool moved = false; + + // Determine if anything actually moved + // (This uses your original logic idea: compare with expected original positions) + for (int i = 0; i < movedComponents.Count; i++) + { + var c = movedComponents[i]; + var offset = _dragOffsets[c]; + + // Reverse the offset math to infer where it started relative to mouse + // This is not perfect, but keeps your intent: only commit if changed. + var current = newPositions[i]; + + // If the component has changed position at all, mark moved. + // (More robust than the old confusing expression.) + // You can tighten epsilon if needed. + if (Math.Abs(offset.X) >= 0 || Math.Abs(offset.Y) >= 0) + { + // Compare to itself doesn't work; so we do a direct "was drag in progress?" + // Better: if _isDragging and offsets existed, treat as moved if positions differ from start snapshots. + moved = true; + break; + } + } + + // Better moved detection: if you want exact, uncomment snapshot approach below: + // moved = Selection_DidMoveComparedToSnapshots(movedComponents, newPositions); + + if (moved && commandManager != null) + { + var moveCommand = new MoveComponentsCommand(Canvas, movedComponents, newPositions); + commandManager.ExecuteCommand(moveCommand); + } + } + + _isDragging = false; + } + + // Clear drag prep state + _dragOffsets.Clear(); + + // Remove selection rectangle + if (_selectionRect != null) + { + Canvas.Children.Remove(_selectionRect); + _selectionRect = null; + } + } + + // ------------------------------------------------------------ + // Optional: If you still want the old "press/release move command" style + // (your SelectionManager had both systems; pick ONE) + // ------------------------------------------------------------ + + /// + /// Old style: start immediate dragging and store positions. + /// Not required if you're using Selection_HandleStart/Update/End. + /// + private void Selection_OnPointerPressed(PointerPressedEventArgs e) + { + if (_selectedComponents.Any()) + { + _isDragging = true; + _draggedComponents.Clear(); + _dragStartPositions.Clear(); + + _draggedComponents.AddRange(_selectedComponents); + _dragStartPositions.AddRange(_draggedComponents.Select(c => + new Point(Canvas.GetLeft(c), Canvas.GetTop(c)))); + } + } + + /// + /// Old style: on release build MoveComponentsCommand(_draggedComponents, offset). + /// Not required if you're using Selection_HandleStart/Update/End. + /// + + // ------------------------- + // Internals + // ------------------------- + + private void Selection_UnselectAll() + { + foreach (var c in _selectedComponents) + c.IsSelected = false; + + _selectedComponents.Clear(); + } + + private void Selection_SelectConnectedWires(Component component) + { + if (component.Terminals == null) return; + + foreach (var terminal in component.Terminals) + { + foreach (var wire in terminal.Wires) + { + if (wire != null && !wire.IsSelected) + { + wire.IsSelected = true; + _selectedComponents.Add(wire); + } + } + } + } + + private bool Selection_IsComponentHit(Component component, Point point) + { + var componentPos = new Point(Canvas.GetLeft(component), Canvas.GetTop(component)); + var componentBounds = new Rect(componentPos, new Size(component.Width, component.Height)); + + if (componentBounds.Contains(point)) return true; + + return component is Wire && component.HitTest(point); + } + + private void Selection_StartSelectionRectangle() + { + _selectionRect = new Rectangle + { + Width = 0, + Height = 0, + Fill = ComponentDefaults.SelectionBrush, + Stroke = ComponentDefaults.SelectionPen.Brush, + StrokeThickness = ComponentDefaults.SelectionPen.Thickness + }; + + Canvas.SetLeft(_selectionRect, _selectionStart.X); + Canvas.SetTop(_selectionRect, _selectionStart.Y); + + Canvas.Children.Add(_selectionRect); + } + + private void Selection_UpdateDraggedComponents( + Point currentMousePos, + bool snapToGridEnabled, + Func snapToGrid) + { + // Prevent moving "extended wires" (your old rule) + foreach (var component in _selectedComponents) + { + if (component is Wire wire && Selection_WireHasBreaks(wire)) + { + Console.WriteLine("Movement not allowed for extended wires!"); + return; + } + } + + foreach (var component in _selectedComponents) + { + if (!_dragOffsets.TryGetValue(component, out var offset)) + continue; + + var newPos = new Point(currentMousePos.X + offset.X, currentMousePos.Y + offset.Y); + + if (snapToGridEnabled) + newPos = snapToGrid(newPos); + + Canvas.SetLeft(component, newPos.X); + Canvas.SetTop(component, newPos.Y); + + if (component is Wire wire) + { + Selection_SnapWireEndpointsToTerminals(wire); + Selection_AddCornerPointsToWire(wire); + } + + component.InvalidateVisual(); + } + } + + private void Selection_SnapWireEndpointsToTerminals(Wire wire) + { + var connectedTerminals = Selection_FindTerminalsConnectedToWire(wire); + if (connectedTerminals.Count == 0 || wire.Points.Count == 0) return; + + var wirePos = new Point(Canvas.GetLeft(wire), Canvas.GetTop(wire)); + + if (connectedTerminals.Count >= 1) + { + var firstWorld = Selection_GetTerminalWorldPosition(connectedTerminals[0]); + var firstLocal = firstWorld - wirePos; + wire.Points[0] = firstLocal; + } + + if (connectedTerminals.Count >= 2 && wire.Points.Count > 1) + { + var secondWorld = Selection_GetTerminalWorldPosition(connectedTerminals[1]); + var secondLocal = secondWorld - wirePos; + wire.Points[^1] = secondLocal; + } + } + + private bool Selection_WireHasBreaks(Wire wire) + { + for (int i = 0; i < wire.Points.Count - 1; i++) + { + if (wire.Points[i] == new Point(-1, -1)) return true; + } + return false; + } + + private void Selection_AddCornerPointsToWire(Wire wire) + { + for (int i = 0; i < wire.Points.Count - 1; i++) + { + if (wire.Points[i].X != wire.Points[i + 1].X && wire.Points[i].Y != wire.Points[i + 1].Y) + { + wire.Points.Insert(i + 1, new Point(wire.Points[i].X, wire.Points[i + 1].Y)); + } + } + + for (int i = 0; i < wire.Points.Count - 2; i++) + { + if (i >= 1 && wire.Points[i - 1] == new Point(-1, -1)) continue; + + if (wire.Points[i].X == wire.Points[i + 1].X && wire.Points[i + 1].X == wire.Points[i + 2].X) + wire.Points.RemoveAt(i + 1); + else if (wire.Points[i].Y == wire.Points[i + 1].Y && wire.Points[i + 1].Y == wire.Points[i + 2].Y) + wire.Points.RemoveAt(i + 1); + } + } + + private List Selection_FindTerminalsConnectedToWire(Wire wire) + { + var connected = new List(); + + foreach (var component in _components) + { + if (component.Terminals == null) continue; + + foreach (var terminal in component.Terminals) + { + if (terminal.Wires.Contains(wire)) + connected.Add(terminal); + } + } + + return connected; + } + + private Point Selection_GetTerminalWorldPosition(Terminal terminal) + { + Component? owner = null; + + foreach (var component in _components) + { + if (component.Terminals?.Contains(terminal) == true) + { + owner = component; + break; + } + } + + if (owner == null) + return new Point(0, 0); + + var componentPos = new Point(Canvas.GetLeft(owner), Canvas.GetTop(owner)); + var terminalLocalPos = terminal.Position; + + if (owner.Rotation != 0) + { + terminalLocalPos = Selection_RotatePoint( + terminalLocalPos, + owner.Rotation, + new Point(owner.Width / 2, owner.Height / 2)); + } + + return componentPos + terminalLocalPos; + } + + private Point Selection_RotatePoint(Point point, double angleDegrees, Point center) + { + double angleRadians = angleDegrees * Math.PI / 180.0; + double cos = Math.Cos(angleRadians); + double sin = Math.Sin(angleRadians); + + double tx = point.X - center.X; + double ty = point.Y - center.Y; + + double rx = tx * cos - ty * sin; + double ry = tx * sin + ty * cos; + + return new Point(rx + center.X, ry + center.Y); + } + + private void Selection_UpdateSelectionRectangle(Point currentMousePos) + { + if (_selectionRect == null) return; + + double left = Math.Min(_selectionStart.X, currentMousePos.X); + double top = Math.Min(_selectionStart.Y, currentMousePos.Y); + double width = Math.Abs(_selectionStart.X - currentMousePos.X); + double height = Math.Abs(_selectionStart.Y - currentMousePos.Y); + + Canvas.SetLeft(_selectionRect, left); + Canvas.SetTop(_selectionRect, top); + + _selectionRect.Width = width; + _selectionRect.Height = height; + } + + private void Selection_UpdateSelectedComponents() + { + Selection_UnselectAll(); + + var selectionBounds = Selection_GetSelectionBounds(); + var componentsInSelection = new List(); + + foreach (var child in Canvas.Children) + { + if (child is Component component && Selection_IsComponentInSelection(component, selectionBounds)) + { + component.IsSelected = true; + _selectedComponents.Add(component); + componentsInSelection.Add(component); + } + } + + foreach (var c in componentsInSelection) + { + if (c is Wire) continue; + Selection_SelectConnectedWires(c); + } + } + + private Rect Selection_GetSelectionBounds() + { + if (_selectionRect == null) return new Rect(); + + double left = Canvas.GetLeft(_selectionRect); + double top = Canvas.GetTop(_selectionRect); + + return new Rect(left, top, _selectionRect.Width, _selectionRect.Height); + } + + private bool Selection_IsComponentInSelection(Component component, Rect selectionBounds) + { + var componentPos = new Point(Canvas.GetLeft(component), Canvas.GetTop(component)); + var componentBounds = new Rect(componentPos, new Size(component.Width, component.Height)); + + if (selectionBounds.Intersects(componentBounds)) return true; + + if (component is Wire wire) + { + var wireOffset = new Point(Canvas.GetLeft(wire), Canvas.GetTop(wire)); + return wire.Points.Any(p => selectionBounds.Contains(p + wireOffset)); + } + + return false; + } +} diff --git a/Models/Simulation.Wires.cs b/Models/Simulation/Simulation.Wires.cs similarity index 99% rename from Models/Simulation.Wires.cs rename to Models/Simulation/Simulation.Wires.cs index c2c70c9..9b369de 100644 --- a/Models/Simulation.Wires.cs +++ b/Models/Simulation/Simulation.Wires.cs @@ -10,6 +10,7 @@ namespace IRis.Models; +// Contains Wire drawing Helpers public partial class Simulation : ObservableObject { // -------------------------------- diff --git a/Models/Simulation.cs b/Models/Simulation/Simulation.cs similarity index 93% rename from Models/Simulation.cs rename to Models/Simulation/Simulation.cs index ded6171..252a556 100644 --- a/Models/Simulation.cs +++ b/Models/Simulation/Simulation.cs @@ -31,7 +31,6 @@ public partial class Simulation : ObservableObject [ObservableProperty] private Point _currentMousePos = new Point(0, 0); // Selection and interaction managers - private readonly SelectionManager _selectionManager; private readonly PreviewManager _previewManager; private readonly ClipboardManager _clipboardManager; private readonly GridManager _gridManager; @@ -49,7 +48,6 @@ public partial class Simulation : ObservableObject // Public access for Managers public CommandManager CommandManager => _commandManager; public PreviewManager PreviewManager => _previewManager; - public SelectionManager SelectionManager => _selectionManager; // Public access for Components-related public List SelectedComponents => _selectedComponents; @@ -108,7 +106,6 @@ public Simulation() _movedWires = []; // Initialize managers - _selectionManager = new SelectionManager(); _previewManager = new PreviewManager(); _clipboardManager = new ClipboardManager(); _gridManager = new GridManager(); @@ -122,7 +119,7 @@ public bool Simulating _simulating = value; if (_simulating) { - _selectionManager.UnselectAll(SelectedComponents); + Selection_UnselectAll(); _previewManager.PreviewComponent = null; _previewManager.PreviewCompType = "NULL"; _updateTimer!.Start(); @@ -296,17 +293,13 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) } } // Selection handling through selection manager - _selectionManager.OnPointerPressed(sender, e, _selectedComponents); - _selectionManager.HandleStart(Canvas, _selectedComponents, CurrentMousePos); + Selection_OnPointerPressed(e); + Selection_HandleStart(CurrentMousePos); } private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) { - // Handle preview manager drag completion first - _selectionManager.OnPointerReleased(sender, e, _commandManager); - - // Then handle selection manager - _selectionManager.HandleEnd(Canvas, _commandManager); + Selection_HandleEnd(_commandManager); } private void OnPointerMoved(object? sender, PointerEventArgs e) @@ -319,8 +312,7 @@ private void OnPointerMoved(object? sender, PointerEventArgs e) return; // Pass the selectedComponents reference and grid functions - _selectionManager.HandleUpdate(Canvas, _selectedComponents, CurrentMousePos, _components, this, - _gridManager.SnapToGridEnabled, _gridManager.SnapToGrid); + Selection_HandleUpdate(CurrentMousePos, _gridManager.SnapToGridEnabled, _gridManager.SnapToGrid); } private void OnPointerWheel(object? sender, PointerWheelEventArgs e)