From 958db91c7c40285ab9bab704736bce7227fc898a Mon Sep 17 00:00:00 2001 From: ShahzaibAhmad05 Date: Thu, 1 Jan 2026 22:42:06 +0500 Subject: [PATCH 1/4] Refactor public private vars in Simulation --- Models/Simulation.cs | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Models/Simulation.cs b/Models/Simulation.cs index 9959ec3..d10e564 100644 --- a/Models/Simulation.cs +++ b/Models/Simulation.cs @@ -19,10 +19,30 @@ namespace IRis.Models; // Contains all the data needed for a simulation public partial class Simulation : ObservableObject { + // -------------------- + // Private attributes + // -------------------- private Canvas? _canvas; private List _components; private List _selectedComponents; private List _movedWires; + [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; + private readonly CommandManager _commandManager = new(); + + // For simulation + private bool _simulating; + private DispatcherTimer? _updateTimer; + + + // -------------------- + // Public attributes + // -------------------- public CustomComponentData CustomComponent { get; set; } = null!; public List Components @@ -37,21 +57,8 @@ public List MovedWires set => _movedWires = value; } - [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; - private readonly CommandManager _commandManager = new(); - public CommandManager CommandManager => _commandManager; - // For simulation - private bool _simulating; - private DispatcherTimer? _updateTimer; - // Expose selected components public List SelectedComponents => _selectedComponents; @@ -113,8 +120,9 @@ private void SetupCanvas() private void SetupSimulation() { - // For updating the simulation - _updateTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; // Adjust to reduce CPU load + // For updating the simulation everytime after some time span + // Adjust time span from here to reduce CPU load + _updateTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; _updateTimer.Tick += (s, e) => SimulationStep(); Simulating = false; } From 9bce4c0ebf1443dc8c93faa4a1e8b1877871ede6 Mon Sep 17 00:00:00 2001 From: ShahzaibAhmad05 Date: Fri, 2 Jan 2026 13:05:28 +0500 Subject: [PATCH 2/4] Remove redundant vars for CommandManager --- Models/Simulation.cs | 54 +++++++++++++------------------ ViewModels/MainWindowViewModel.cs | 6 ++-- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/Models/Simulation.cs b/Models/Simulation.cs index d10e564..879759c 100644 --- a/Models/Simulation.cs +++ b/Models/Simulation.cs @@ -39,7 +39,6 @@ public partial class Simulation : ObservableObject private bool _simulating; private DispatcherTimer? _updateTimer; - // -------------------- // Public attributes // -------------------- @@ -57,20 +56,11 @@ public List MovedWires set => _movedWires = value; } + // Public access for CommandManager public CommandManager CommandManager => _commandManager; - - // Expose selected components public List SelectedComponents => _selectedComponents; - - // External access to selection state public bool HasSelectedComponents => _selectedComponents.Count > 0; - // Undo/Redo - public bool CanUndo => _commandManager.CanUndo; - public bool CanRedo => _commandManager.CanRedo; - public void Undo() => _commandManager.Undo(); - public void Redo() => _commandManager.Redo(); - public bool Simulating { get => _simulating; @@ -214,7 +204,9 @@ public bool GridEnabled } } - // Terminal Snapping + // ----------------------- + // Wire drawing Helpers + // ----------------------- public Terminal? FindClosestSnapTerminal(Point p) { Terminal? closestTerminal = null; @@ -500,25 +492,25 @@ public bool DoesWireHaveExtension(Wire wire) return false; } - private bool IsWireInMovedWires(Wire wire, List MovedWires) - { - foreach (var movedWire in MovedWires) - { - if (movedWire == wire) return true; - } - return false; - } - - Point SnapToGrid(Point pt) - { - double snapX = (int)Math.Round(Math.Round(pt.X / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); - double snapY = (int)Math.Round(Math.Round(pt.Y / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); - return new Point(snapX, snapY); - } - - // _______________________________________________ - // ____________ Pointer/key handling _____________ - // _______________________________________________ + // private bool IsWireInMovedWires(Wire wire, List MovedWires) + // { + // foreach (var movedWire in MovedWires) + // { + // if (movedWire == wire) return true; + // } + // return false; + // } + + // Point SnapToGrid(Point pt) + // { + // double snapX = (int)Math.Round(Math.Round(pt.X / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); + // double snapY = (int)Math.Round(Math.Round(pt.Y / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); + // return new Point(snapX, snapY); + // } + + // ------------------------- + // Handling Mouse Actions + // ------------------------- private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index 706b71a..e562b83 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -15,10 +15,12 @@ using Avalonia.Threading; using CommunityToolkit.Mvvm.Input; using IRis.Models; +using IRis.Models.Commands; using IRis.Models.Components; using IRis.Models.Core; using IRis.Services; using IRis.Views; +using ICommand = System.Windows.Input.ICommand; namespace IRis.ViewModels @@ -354,7 +356,7 @@ private void Undo() Console.WriteLine("Cannot undo while simulating"); return; } - _simulation.Undo(); + _simulation.CommandManager.Undo(); LastAction = "Undo"; } @@ -366,7 +368,7 @@ private void Redo() Console.WriteLine("Cannot redo while simulating"); return; } - _simulation.Redo(); + _simulation.CommandManager.Redo(); LastAction = "Redo"; } From 4b4b393decb778a759baf33a27fb574da0d28b5e Mon Sep 17 00:00:00 2001 From: ShahzaibAhmad05 Date: Fri, 2 Jan 2026 13:58:24 +0500 Subject: [PATCH 3/4] Cut wrapper functions for publicily accessible objects --- App.axaml.cs | 4 +- Models/PreviewManager.cs | 2 +- Models/SelectionManager.cs | 2 +- Models/Simulation.cs | 122 ++++++++++++++++++++++++------------- Views/MainWindow.axaml.cs | 16 ++--- 5 files changed, 90 insertions(+), 56 deletions(-) diff --git a/App.axaml.cs b/App.axaml.cs index 86edb6b..260a546 100644 --- a/App.axaml.cs +++ b/App.axaml.cs @@ -23,7 +23,9 @@ public override void OnFrameworkInitializationCompleted() // Set the default theme as 'Dark' // ThemeManager.ChangeTheme(Application.Current, "Dark"); - // Create a CanvasService object and pass its reference to both MainWindow and MainWindowViewModel + // Create a CanvasService object and pass its reference + // to both MainWindow and MainWindowViewModel + // This is the main service object for drawings and simulation Simulation simulation = new Simulation(); diff --git a/Models/PreviewManager.cs b/Models/PreviewManager.cs index 2325be5..5370b03 100644 --- a/Models/PreviewManager.cs +++ b/Models/PreviewManager.cs @@ -13,7 +13,7 @@ namespace IRis.Models; -internal class PreviewManager +public class PreviewManager { private string? _previewCompType; private Component? _previewComponent; diff --git a/Models/SelectionManager.cs b/Models/SelectionManager.cs index 11f4910..f6c4bf7 100644 --- a/Models/SelectionManager.cs +++ b/Models/SelectionManager.cs @@ -12,7 +12,7 @@ namespace IRis.Models; -internal class SelectionManager +public class SelectionManager { private Point _selectionStart; private Rectangle? _selectionRect; diff --git a/Models/Simulation.cs b/Models/Simulation.cs index 879759c..93506d8 100644 --- a/Models/Simulation.cs +++ b/Models/Simulation.cs @@ -13,6 +13,7 @@ using IRis.Models.Commands; using IRis.Services; using IRis.Views; +using System.ComponentModel.DataAnnotations; namespace IRis.Models; @@ -22,6 +23,8 @@ public partial class Simulation : ObservableObject // -------------------- // Private attributes // -------------------- + // NOTE: _canvas is set to nullable since it is not initialized in the constructor + // -------------------- private Canvas? _canvas; private List _components; private List _selectedComponents; @@ -56,11 +59,32 @@ public List MovedWires set => _movedWires = value; } - // Public access for CommandManager + // 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; public bool HasSelectedComponents => _selectedComponents.Count > 0; + // ------------------------------------------------- + // Contructor and public methods for construction + // ------------------------------------------------- + public Simulation() + { + // Initialize empty lists + _components = []; + _selectedComponents = []; + _movedWires = []; + + // Initialize managers + _selectionManager = new SelectionManager(); + _previewManager = new PreviewManager(); + _clipboardManager = new ClipboardManager(); + _gridManager = new GridManager(); + } + public bool Simulating { get => _simulating; @@ -78,20 +102,6 @@ public bool Simulating } } - public Simulation() - { - // Initialize lists - _components = new List(); - _selectedComponents = new List(); - _movedWires = new List(); - - // Initialize managers - _selectionManager = new SelectionManager(); - _previewManager = new PreviewManager(); - _clipboardManager = new ClipboardManager(); - _gridManager = new GridManager(); - } - public void Register(Canvas canvas) { _canvas = canvas; @@ -101,10 +111,30 @@ public void Register(Canvas canvas) _gridManager.DrawGrid(_canvas); // Draws the main grid } + // ------------------------------------------- + // Private helper Methods for constructors + // ------------------------------------------- + private void SimulationStep() + { + foreach (var component in _components) + { + // Compute outputs for everything first + if (component is IOutputProvider op) + op.ComputeOutput(); + + // Redraw Toggles and Probes + if (component is LogicProbe || component is LogicToggle) + component.InvalidateVisual(); + } + } + private void SetupCanvas() { + if (_canvas is null) throw new InvalidOperationException( + "Simulation.Register(Canvas) must be called before registering event handlers." + ); // Important: Enable keyboard focus - _canvas!.Focusable = true; + _canvas.Focusable = true; _canvas.Cursor = new Cursor(StandardCursorType.Arrow); } @@ -119,7 +149,10 @@ private void SetupSimulation() private void RegisterEventHandlers() { - _canvas!.PointerPressed += OnPointerPressed; + if (_canvas is null) throw new InvalidOperationException( + "Simulation.Register(Canvas) must be called before registering event handlers." + ); + _canvas.PointerPressed += OnPointerPressed; _canvas.PointerMoved += OnPointerMoved; _canvas.PointerReleased += OnPointerReleased; _canvas.PointerEntered += (s, e) => _previewManager.OnEnter(); @@ -128,21 +161,9 @@ private void RegisterEventHandlers() _canvas.PointerWheelChanged += OnPointerWheel; } - public void SimulationStep() - { - foreach (var component in _components) - { - // Compute outputs for everything first - if (component is IOutputProvider op) - op.ComputeOutput(); - - // Redraw Toggles and Probes - if (component is LogicProbe || component is LogicToggle) - component.InvalidateVisual(); - } - } - - // Component Management + // -------------------------------- + // Component Management Methods + // -------------------------------- public void DeleteSelectedComponents() { if (_selectedComponents.Count > 0) @@ -153,23 +174,29 @@ public void DeleteSelectedComponents() } } - public void UnselectComponents() => _selectionManager.UnselectAll(_selectedComponents); - // TODO: THESE METHODS ARE SHALLOW AND BAD! (probably) public void LoadComponents(List components) { + if (_canvas is null) throw new InvalidOperationException( + "Simulation.Register(Canvas) must be called before registering event handlers." + ); _components = components; - _canvas!.Children.AddRange(_components); + _canvas.Children.AddRange(_components); } // sus? amogus? public void DeleteAllComponents() { - _canvas!.Children.RemoveAll(_components); + if (_canvas is null) throw new InvalidOperationException( + "Simulation.Register(Canvas) must be called before registering event handlers." + ); + _canvas.Children.RemoveAll(_components); _components.Clear(); } - // Clipboard operations + // ---------------------------------------------- + // Important Wrappers for Clipboard Managers + // ---------------------------------------------- public void CopySelected(bool cutMode = false) => _clipboardManager.Copy(_selectedComponents, cutMode, DeleteSelectedComponents); public void CutSelected() => CopySelected(true); public void PasteSelected() => _clipboardManager.Paste(_canvas!, CurrentMousePos); @@ -514,6 +541,9 @@ public bool DoesWireHaveExtension(Wire wire) private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { + if (_canvas is null) throw new InvalidOperationException( + "Simulation.Register(Canvas) must be called before registering event handlers." + ); if (Simulating || !GridEnabled) { Console.WriteLine("Cannot edit while simulating"); @@ -555,7 +585,7 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) return; } Console.WriteLine("Registering Wire Extension..."); - _previewManager.StartWireExtension(_canvas!, CurrentMousePos, existingWire, this); + _previewManager.StartWireExtension(_canvas, CurrentMousePos, existingWire, this); return; } } @@ -581,35 +611,41 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { // NEW COMPONENT LOGIC Console.WriteLine("Registering New Component..."); - _previewManager.HandleComponentCommit(_canvas!, _components, CurrentMousePos, _commandManager, this); + _previewManager.HandleComponentCommit(_canvas, _components, CurrentMousePos, _commandManager, this); return; } } // Selection handling through selection manager _selectionManager.OnPointerPressed(sender, e, _selectedComponents); - _selectionManager.HandleStart(_canvas!, _selectedComponents, CurrentMousePos); + _selectionManager.HandleStart(_canvas, _selectedComponents, CurrentMousePos); } private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) { + if (_canvas is null) throw new InvalidOperationException( + "Simulation.Register(Canvas) must be called before registering event handlers." + ); // Handle preview manager drag completion first _selectionManager.OnPointerReleased(sender, e, _commandManager); // Then handle selection manager - _selectionManager.HandleEnd(_canvas!, _commandManager); + _selectionManager.HandleEnd(_canvas, _commandManager); } private void OnPointerMoved(object? sender, PointerEventArgs e) { + if (_canvas is null) throw new InvalidOperationException( + "Simulation.Register(Canvas) must be called before registering event handlers." + ); // Update the mouse pos CurrentMousePos = e.GetPosition(_canvas); - if (_previewManager.HandleUpdate(_canvas!, CurrentMousePos, _gridManager.SnapToGridEnabled, + if (_previewManager.HandleUpdate(_canvas, CurrentMousePos, _gridManager.SnapToGridEnabled, _gridManager.SnapToGrid, this)) return; // Pass the selectedComponents reference and grid functions - _selectionManager.HandleUpdate(_canvas!, _selectedComponents, CurrentMousePos, _components, this, + _selectionManager.HandleUpdate(_canvas, _selectedComponents, CurrentMousePos, _components, this, _gridManager.SnapToGridEnabled, _gridManager.SnapToGrid); } diff --git a/Views/MainWindow.axaml.cs b/Views/MainWindow.axaml.cs index f1d36f7..cb256ce 100644 --- a/Views/MainWindow.axaml.cs +++ b/Views/MainWindow.axaml.cs @@ -1,25 +1,21 @@ +// MainWindow.axaml.cs using Avalonia.Controls; -using Avalonia.Input; using IRis.Models; -using IRis.ViewModels; namespace IRis.Views; public partial class MainWindow : Window { + // This parameter-less constructor is not run from app.axaml.cs + // Otherwise it could cause errors public MainWindow() : this(new Simulation()) {} // fallback for preview/designer/runtime public MainWindow(Simulation simulation) { InitializeComponent(); + // "MainCanvas" from the XAML is used for all the drawing + // Register this to the simulation object simulation.Register(MainCanvas); - - - // DataContext = MainWindowViewModel(); - - // CircuitComponent component = new CircuitComponent(); - // - // MainCanvas.Children.Add(component); } -} \ No newline at end of file +} From 18c055554d7cab86e553f67d0acfd9cb8cedd86f Mon Sep 17 00:00:00 2001 From: ShahzaibAhmad05 Date: Fri, 2 Jan 2026 15:39:38 +0500 Subject: [PATCH 4/4] Move wire drawing logic out of simulation.cs --- Models/PreviewManager.cs | 11 +- Models/SelectionManager.cs | 2 +- Models/Simulation.Wires.cs | 318 +++++++++++++++++++++++++ Models/Simulation.cs | 466 ++++++------------------------------- 4 files changed, 393 insertions(+), 404 deletions(-) create mode 100644 Models/Simulation.Wires.cs diff --git a/Models/PreviewManager.cs b/Models/PreviewManager.cs index 5370b03..6d40b74 100644 --- a/Models/PreviewManager.cs +++ b/Models/PreviewManager.cs @@ -1,15 +1,12 @@ +// Models/PreviewManager.cs using System; using System.Collections.Generic; -using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using IRis.Models.Components; using IRis.Models.Core; using IRis.Models.Commands; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using System.Runtime.Serialization; namespace IRis.Models; @@ -207,9 +204,9 @@ private bool HandleWireUpdate(Wire wirePreview, Point mousePos, bool snapToGridE // If Wire is not snapped to terminal and overlaps another wire // The snapping handles used input terminals so this works. bool condition3 = !pointSnappedToTerminal && - !simulation.DoesWireHaveExtension(wirePreview) && + !Simulation.DoesWireHaveExtension(wirePreview) && simulation.DoesWireOverlapAnotherWire(wirePreview.Points); - bool condition4 = !simulation.DoesWireHaveExtension(wirePreview) && + bool condition4 = !Simulation.DoesWireHaveExtension(wirePreview) && simulation.DoesWireSelfOverlap(wirePreview.Points); // If snapping was rejected and wire crosses a terminal List tempPoints = [.. wirePreview.Points]; @@ -218,7 +215,7 @@ private bool HandleWireUpdate(Wire wirePreview, Point mousePos, bool snapToGridE bool condition2 = simulation.IsWireInsideAnyComponent(tempPoints); Terminal? exceptionCase = simulation.FindClosestSnapTerminal(wirePreview.Points[0]); bool condition5 = !pointSnappedToTerminal && - !simulation.DoesWireHaveExtension(wirePreview) && + !Simulation.DoesWireHaveExtension(wirePreview) && simulation.DoesWireCrossTerminal(tempPoints, exceptionCase); if (condition1 || condition2 || condition3 || condition4 || condition5) diff --git a/Models/SelectionManager.cs b/Models/SelectionManager.cs index f6c4bf7..a8af930 100644 --- a/Models/SelectionManager.cs +++ b/Models/SelectionManager.cs @@ -1,4 +1,4 @@ -// File: SelectionManager.cs +// Models/SelectionManager.cs using System; using System.Collections.Generic; using System.Linq; diff --git a/Models/Simulation.Wires.cs b/Models/Simulation.Wires.cs new file mode 100644 index 0000000..c2c70c9 --- /dev/null +++ b/Models/Simulation.Wires.cs @@ -0,0 +1,318 @@ +// Models/Simulation.Wires.cs +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using CommunityToolkit.Mvvm.ComponentModel; +using IRis.Models.Components; +using IRis.Models.Core; + +namespace IRis.Models; + +public partial class Simulation : ObservableObject +{ + // -------------------------------- + // Contains Wire drawing Helpers + // -------------------------------- + public Terminal? FindClosestSnapTerminal(Point p) + { + Terminal? closestTerminal = null; + double minDistance = double.MaxValue; + + foreach (Component component in _components) + { + if (component.Terminals == null) continue; + foreach (Terminal terminal in component.Terminals) + { + // Calculate absolute terminal position + Point absTerminalPos = new Point( + terminal.Position.X + Canvas.GetLeft(component), + terminal.Position.Y + Canvas.GetTop(component) + ); + + double distance = Point.Distance(p, absTerminalPos); + if (distance < minDistance && distance <= ComponentDefaults.TerminalSnappingRange) + { + minDistance = distance; + closestTerminal = terminal; + } + } + } + return closestTerminal; // Returns null if no terminal is within snapping range + } + + public Point GetAbsoluteTerminalPosition(Terminal terminal) + { + foreach (Component component in _components) + { + if (component.Terminals == null) continue; + foreach (Terminal compTerminal in component.Terminals) + { + if (compTerminal == terminal) + { + return new Point( + compTerminal.Position.X + Canvas.GetLeft(component), + compTerminal.Position.Y + Canvas.GetTop(component) + ); + } + } + } + return new Point(-2, -2); // Error case; should not occur + } + + public bool IsInputTerminal(Terminal terminal) + { + if (terminal == null) return false; + + foreach (Component component in _components) + { + if (component.Terminals == null) continue; + + // For Gate components, check if terminal is not the last one (output) + if (component is Gate gate) + { + for (int i = 0; i < gate.Terminals!.Length - 1; i++) // Exclude last terminal (output) + { + if (gate.Terminals[i] == terminal) + return true; + } + } + // For Multiplexer components + else if (component is Multiplexer mux) + { + // Selection lines (indices 0 to SelectionLineCount-1) and + // Input lines (indices SelectionLineCount to SelectionLineCount+InputLineCount-1) are inputs + // Only the last terminal (^1) is output + for (int i = 0; i < mux.Terminals!.Length - 1; i++) // Exclude last terminal (output) + { + if (mux.Terminals[i] == terminal) + return true; + } + } + // For CustomComponent + else if (component is CustomComponent customComp) + { + // First InputCount terminals are inputs, remaining are outputs + for (int i = 0; i < customComp.InputCount; i++) + { + if (i < customComp.Terminals!.Length && customComp.Terminals[i] == terminal) + return true; + } + } + } + + return false; + } + + public Wire? FindWireAtPosition(Point position) + { + return _components.OfType() + .FirstOrDefault(wire => wire.IsPointOnWire(position, 5.0)); // 5.0 is click tolerance + } + + private bool IsPointInsideAnyComponent(Point point) + { + return Components.Any(component => + { + if (component is Wire) return false; + + Rect bounds = component.Bounds; + + if (component.Rotation == 0) // PATCH: this is a safe check for when rotations are added + { + Rect adjustedBounds = new Rect( + bounds.X - 10, + bounds.Y, + bounds.Width + 20, // prevent negative width + bounds.Height + ); + return adjustedBounds.Contains(point); + } + else + { + return bounds.Contains(point); + } + }); + } + + public bool IsWireInsideAnyComponent(List points) + { + Point InvalidPoint = new Point(-1, -1); + for (int i = 0; i < points.Count - 1; i++) + { + if (points[i] == InvalidPoint || points[i + 1] == InvalidPoint) continue; + List checkablePoints = [points[i]]; + double dx = points[i].X - points[i + 1].X; + double dy = points[i].Y - points[i + 1].Y; + + if (dx != 0) + { + while (dx != 0) + { + checkablePoints.Add(new Point(points[i].X - dx, points[i].Y)); + if (dx > 0) dx -= 10; + else dx += 10; + } + } + else if (dy != 0) + { + while (dy != 0) + { + checkablePoints.Add(new Point(points[i].X, points[i].Y - dy)); + if (dy > 0) dy -= 10; + else dy += 10; + } + } + else + { + continue; // Handling for duplicate points + } + foreach (Point pt in checkablePoints) + { + if (IsPointInsideAnyComponent(pt)) return true; + } + } + return false; + } + + public bool DoesWireOverlapAnotherWire(List points) + { + var existingWirePoints = Components.OfType().ToList() + .SelectMany(w => w.Points.Where(p => p.X != -1 && p.Y != -1)) + .ToHashSet(); + List validPoints = [.. points]; + foreach (Point point in points) + if (FindClosestSnapTerminal(point) == null) validPoints.Add(point); + + return validPoints.Any(existingWirePoints.Contains); + } + + public bool IsWireSupersetOfAnotherWire(List wirePoints) + { + var wirePointsSet = wirePoints.Where(p => p.X != -1 && p.Y != -1).ToHashSet(); + bool IsWireSuperset = false; + // Check if wire is a superset of another wire + foreach (Component component in _components) + { + if (component is Wire existingWire) + { + if (Components.OfType().ToList() + .Any(existingWire => existingWire.Points.Where(p => p.X != -1 && p.Y != -1) + .All(wirePointsSet.Contains))) + { + IsWireSuperset = true; + break; + } + } + } + return IsWireSuperset; + } + + public bool DoesWireSelfOverlap(List points) + { + var allLinePoints = new HashSet(); + var validPoints = points.Where(p => p.X != -1 && p.Y != -1).ToList(); + + for (int i = 0; i < validPoints.Count - 1; i++) + { + int dx = (int)(validPoints[i + 1].X - validPoints[i].X); + int dy = (int)(validPoints[i + 1].Y - validPoints[i].Y); + int steps = (int)(Math.Max(Math.Abs(dx), Math.Abs(dy)) / ComponentDefaults.GridSpacing); + if (steps == 0) continue; + + for (int j = 1; j < steps; j++) // Skip endpoints to avoid false positives + { + var point = new Point((int)(validPoints[i].X + dx * j / steps), (int)(validPoints[i].Y + dy * j / steps)); + if (!allLinePoints.Add(point)) return true; + } + } + return false; + } + + public bool DoesWireCrossTerminal(List points, Terminal? exceptionCase=null) + { + var terminalPositions = Components.ToList().Where(c => c is not Wire && c.Terminals != null) + .SelectMany(c => c.Terminals!.Where(t => t != exceptionCase).Select(t => GetAbsoluteTerminalPosition(t))) + .ToHashSet(); + + Point InvalidPoint = new Point(-1, -1); + for (int i = 0; i < points.Count - 1; i++) + { + if (points[i] == InvalidPoint || points[i + 1] == InvalidPoint) continue; + List checkablePoints = []; + double dx = points[i].X - points[i + 1].X; + double dy = points[i].Y - points[i + 1].Y; + + if (dx != 0) + { + while (dx != 0) + { + checkablePoints.Add(new Point(points[i].X - dx, points[i].Y)); + if (dx > 0) dx -= 10; + else dx += 10; + } + } + else if (dy != 0) + { + while (dy != 0) + { + checkablePoints.Add(new Point(points[i].X, points[i].Y - dy)); + if (dy > 0) dy -= 10; + else dy += 10; + } + } + else + { + + continue; // Handling for duplicate points + } + + // PATCH: this needs a replacement later + checkablePoints.RemoveAll((point) => + { + return points.Contains(point); + }); + + foreach (Point pt in checkablePoints) + { + Console.WriteLine(pt); + foreach (Point pos in terminalPositions) + { + if (pt == pos) + { + Console.WriteLine("FOUND"); + Console.WriteLine(pt); + return true; + } + } + } + } + return false; + } + + public static bool DoesWireHaveExtension(Wire wire) + { + foreach (Point point in wire.Points) + { + if (point == new Point(-1, -1)) return true; + } + return false; + } + + // private bool IsWireInMovedWires(Wire wire, List MovedWires) + // { + // foreach (var movedWire in MovedWires) + // { + // if (movedWire == wire) return true; + // } + // return false; + // } + + // Point SnapToGrid(Point pt) + // { + // double snapX = (int)Math.Round(Math.Round(pt.X / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); + // double snapY = (int)Math.Round(Math.Round(pt.Y / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); + // return new Point(snapX, snapY); + // } +} \ No newline at end of file diff --git a/Models/Simulation.cs b/Models/Simulation.cs index 93506d8..ded6171 100644 --- a/Models/Simulation.cs +++ b/Models/Simulation.cs @@ -1,31 +1,30 @@ +// Models/Simulation.cs using System; using System.Collections.Generic; -using System.Linq; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; using Avalonia.Input; -using Avalonia.Media; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using IRis.Models.Components; using IRis.Models.Core; using IRis.Models.Commands; -using IRis.Services; using IRis.Views; -using System.ComponentModel.DataAnnotations; namespace IRis.Models; -// Contains all the data needed for a simulation +// Main section of the Simulation class public partial class Simulation : ObservableObject { // -------------------- // Private attributes // -------------------- - // NOTE: _canvas is set to nullable since it is not initialized in the constructor + // NOTE: _canvas is set to nullable to suppress warnings since it is not + // initialized in the constructor but by app.axaml.cs instead. // -------------------- - private Canvas? _canvas; + private Canvas? _canvas; // nullable attribute to store the canvas + private Canvas Canvas => // Use this private getter to access canvas + _canvas ?? throw new InvalidOperationException("Register(canvas) first."); private List _components; private List _selectedComponents; private List _movedWires; @@ -47,6 +46,15 @@ public partial class Simulation : ObservableObject // -------------------- public CustomComponentData CustomComponent { get; set; } = null!; + // 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; + public bool HasSelectedComponents => _selectedComponents.Count > 0; + public List Components { get => _components; @@ -59,17 +67,38 @@ public List MovedWires set => _movedWires = value; } - // Public access for Managers - public CommandManager CommandManager => _commandManager; - public PreviewManager PreviewManager => _previewManager; - public SelectionManager SelectionManager => _selectionManager; + // Preview management + public string? PreviewCompType + { + get => _previewManager.PreviewCompType; + set => _previewManager.SetPreviewComponent(value, Canvas, CurrentMousePos, this); + } - // Public access for Components-related - public List SelectedComponents => _selectedComponents; - public bool HasSelectedComponents => _selectedComponents.Count > 0; + // Grid management + public bool SnapToGridEnabled + { + get => _gridManager.SnapToGridEnabled; + set => _gridManager.SnapToGridEnabled = value; + } + + public bool GridEnabled + { + get => _gridManager.GridEnabled; + set + { + _gridManager.GridEnabled = value; + if (value) + _gridManager.DrawGrid(Canvas); + else + { + Canvas.Children.Clear(); + Canvas.Children.AddRange(_components); + } + } + } // ------------------------------------------------- - // Contructor and public methods for construction + // Public methods for simulation construction // ------------------------------------------------- public Simulation() { @@ -111,9 +140,9 @@ public void Register(Canvas canvas) _gridManager.DrawGrid(_canvas); // Draws the main grid } - // ------------------------------------------- - // Private helper Methods for constructors - // ------------------------------------------- + // ---------------------------------------------------- + // Private helper Methods for the above constructors + // ---------------------------------------------------- private void SimulationStep() { foreach (var component in _components) @@ -130,12 +159,9 @@ private void SimulationStep() private void SetupCanvas() { - if (_canvas is null) throw new InvalidOperationException( - "Simulation.Register(Canvas) must be called before registering event handlers." - ); // Important: Enable keyboard focus - _canvas.Focusable = true; - _canvas.Cursor = new Cursor(StandardCursorType.Arrow); + Canvas.Focusable = true; + Canvas.Cursor = new Cursor(StandardCursorType.Arrow); } private void SetupSimulation() @@ -149,16 +175,13 @@ private void SetupSimulation() private void RegisterEventHandlers() { - if (_canvas is null) throw new InvalidOperationException( - "Simulation.Register(Canvas) must be called before registering event handlers." - ); - _canvas.PointerPressed += OnPointerPressed; - _canvas.PointerMoved += OnPointerMoved; - _canvas.PointerReleased += OnPointerReleased; - _canvas.PointerEntered += (s, e) => _previewManager.OnEnter(); - _canvas.PointerExited += (s, e) => _previewManager.OnExit(); - _canvas.KeyDown += OnKeyDown; - _canvas.PointerWheelChanged += OnPointerWheel; + Canvas.PointerPressed += OnPointerPressed; + Canvas.PointerMoved += OnPointerMoved; + Canvas.PointerReleased += OnPointerReleased; + Canvas.PointerEntered += (s, e) => _previewManager.OnEnter(); + Canvas.PointerExited += (s, e) => _previewManager.OnExit(); + Canvas.KeyDown += OnKeyDown; + Canvas.PointerWheelChanged += OnPointerWheel; } // -------------------------------- @@ -168,7 +191,8 @@ public void DeleteSelectedComponents() { if (_selectedComponents.Count > 0) { - var deleteCommand = new DeleteComponentsCommand(_canvas!, _components, _selectedComponents); + var deleteCommand = new DeleteComponentsCommand( + Canvas, _components, _selectedComponents); _commandManager.ExecuteCommand(deleteCommand); _selectedComponents.Clear(); } @@ -177,20 +201,14 @@ public void DeleteSelectedComponents() // TODO: THESE METHODS ARE SHALLOW AND BAD! (probably) public void LoadComponents(List components) { - if (_canvas is null) throw new InvalidOperationException( - "Simulation.Register(Canvas) must be called before registering event handlers." - ); _components = components; - _canvas.Children.AddRange(_components); + Canvas.Children.AddRange(_components); } // sus? amogus? public void DeleteAllComponents() { - if (_canvas is null) throw new InvalidOperationException( - "Simulation.Register(Canvas) must be called before registering event handlers." - ); - _canvas.Children.RemoveAll(_components); + Canvas.Children.RemoveAll(_components); _components.Clear(); } @@ -199,351 +217,13 @@ public void DeleteAllComponents() // ---------------------------------------------- public void CopySelected(bool cutMode = false) => _clipboardManager.Copy(_selectedComponents, cutMode, DeleteSelectedComponents); public void CutSelected() => CopySelected(true); - public void PasteSelected() => _clipboardManager.Paste(_canvas!, CurrentMousePos); - - // Preview management - public string? PreviewCompType - { - get => _previewManager.PreviewCompType; - set => _previewManager.SetPreviewComponent(value, _canvas!, CurrentMousePos, this); - } - - // Grid management - public bool SnapToGridEnabled - { - get => _gridManager.SnapToGridEnabled; - set => _gridManager.SnapToGridEnabled = value; - } - - public bool GridEnabled - { - get => _gridManager.GridEnabled; - set - { - _gridManager.GridEnabled = value; - if (value) - _gridManager.DrawGrid(_canvas!); - else - { - _canvas!.Children.Clear(); - _canvas.Children.AddRange(_components); - } - } - } - - // ----------------------- - // Wire drawing Helpers - // ----------------------- - public Terminal? FindClosestSnapTerminal(Point p) - { - Terminal? closestTerminal = null; - double minDistance = double.MaxValue; - - foreach (Component component in _components) - { - if (component.Terminals == null) continue; - foreach (Terminal terminal in component.Terminals) - { - // Calculate absolute terminal position - Point absTerminalPos = new Point( - terminal.Position.X + Canvas.GetLeft(component), - terminal.Position.Y + Canvas.GetTop(component) - ); - - double distance = Point.Distance(p, absTerminalPos); - if (distance < minDistance && distance <= ComponentDefaults.TerminalSnappingRange) - { - minDistance = distance; - closestTerminal = terminal; - } - } - } - return closestTerminal; // Returns null if no terminal is within snapping range - } - - public Point GetAbsoluteTerminalPosition(Terminal terminal) - { - foreach (Component component in _components) - { - if (component.Terminals == null) continue; - foreach (Terminal compTerminal in component.Terminals) - { - if (compTerminal == terminal) - { - return new Point( - compTerminal.Position.X + Canvas.GetLeft(component), - compTerminal.Position.Y + Canvas.GetTop(component) - ); - } - } - } - return new Point(-2, -2); // Error case; should not occur - } - - public bool IsInputTerminal(Terminal terminal) - { - if (terminal == null) return false; - - foreach (Component component in _components) - { - if (component.Terminals == null) continue; - - // For Gate components, check if terminal is not the last one (output) - if (component is Gate gate) - { - for (int i = 0; i < gate.Terminals!.Length - 1; i++) // Exclude last terminal (output) - { - if (gate.Terminals[i] == terminal) - return true; - } - } - // For Multiplexer components - else if (component is Multiplexer mux) - { - // Selection lines (indices 0 to SelectionLineCount-1) and - // Input lines (indices SelectionLineCount to SelectionLineCount+InputLineCount-1) are inputs - // Only the last terminal (^1) is output - for (int i = 0; i < mux.Terminals!.Length - 1; i++) // Exclude last terminal (output) - { - if (mux.Terminals[i] == terminal) - return true; - } - } - // For CustomComponent - else if (component is CustomComponent customComp) - { - // First InputCount terminals are inputs, remaining are outputs - for (int i = 0; i < customComp.InputCount; i++) - { - if (i < customComp.Terminals!.Length && customComp.Terminals[i] == terminal) - return true; - } - } - } - - return false; - } - - public Wire? FindWireAtPosition(Point position) - { - return _components.OfType() - .FirstOrDefault(wire => wire.IsPointOnWire(position, 5.0)); // 5.0 is click tolerance - } - - private bool IsPointInsideAnyComponent(Point point) - { - return Components.Any(component => - { - if (component is Wire) return false; - - Rect bounds = component.Bounds; - - if (component.Rotation == 0) // PATCH: this is a safe check for when rotations are added - { - Rect adjustedBounds = new Rect( - bounds.X - 10, - bounds.Y, - bounds.Width + 20, // prevent negative width - bounds.Height - ); - return adjustedBounds.Contains(point); - } - else - { - return bounds.Contains(point); - } - }); - } - - public bool IsWireInsideAnyComponent(List points) - { - Point InvalidPoint = new Point(-1, -1); - for (int i = 0; i < points.Count - 1; i++) - { - if (points[i] == InvalidPoint || points[i + 1] == InvalidPoint) continue; - List checkablePoints = [points[i]]; - double dx = points[i].X - points[i + 1].X; - double dy = points[i].Y - points[i + 1].Y; - - if (dx != 0) - { - while (dx != 0) - { - checkablePoints.Add(new Point(points[i].X - dx, points[i].Y)); - if (dx > 0) dx -= 10; - else dx += 10; - } - } - else if (dy != 0) - { - while (dy != 0) - { - checkablePoints.Add(new Point(points[i].X, points[i].Y - dy)); - if (dy > 0) dy -= 10; - else dy += 10; - } - } - else - { - continue; // Handling for duplicate points - } - foreach (Point pt in checkablePoints) - { - if (IsPointInsideAnyComponent(pt)) return true; - } - } - return false; - } - - public bool DoesWireOverlapAnotherWire(List points) - { - var existingWirePoints = Components.OfType().ToList() - .SelectMany(w => w.Points.Where(p => p.X != -1 && p.Y != -1)) - .ToHashSet(); - List validPoints = [.. points]; - foreach (Point point in points) - if (FindClosestSnapTerminal(point) == null) validPoints.Add(point); - - return validPoints.Any(existingWirePoints.Contains); - } - - public bool IsWireSupersetOfAnotherWire(List wirePoints) - { - var wirePointsSet = wirePoints.Where(p => p.X != -1 && p.Y != -1).ToHashSet(); - bool IsWireSuperset = false; - // Check if wire is a superset of another wire - foreach (Component component in _components) - { - if (component is Wire existingWire) - { - if (Components.OfType().ToList() - .Any(existingWire => existingWire.Points.Where(p => p.X != -1 && p.Y != -1) - .All(wirePointsSet.Contains))) - { - IsWireSuperset = true; - break; - } - } - } - return IsWireSuperset; - } - - public bool DoesWireSelfOverlap(List points) - { - var allLinePoints = new HashSet(); - var validPoints = points.Where(p => p.X != -1 && p.Y != -1).ToList(); - - for (int i = 0; i < validPoints.Count - 1; i++) - { - int dx = (int)(validPoints[i + 1].X - validPoints[i].X); - int dy = (int)(validPoints[i + 1].Y - validPoints[i].Y); - int steps = (int)(Math.Max(Math.Abs(dx), Math.Abs(dy)) / ComponentDefaults.GridSpacing); - if (steps == 0) continue; - - for (int j = 1; j < steps; j++) // Skip endpoints to avoid false positives - { - var point = new Point((int)(validPoints[i].X + dx * j / steps), (int)(validPoints[i].Y + dy * j / steps)); - if (!allLinePoints.Add(point)) return true; - } - } - return false; - } - - public bool DoesWireCrossTerminal(List points, Terminal? exceptionCase=null) - { - var terminalPositions = Components.ToList().Where(c => c is not Wire && c.Terminals != null) - .SelectMany(c => c.Terminals!.Where(t => t != exceptionCase).Select(t => GetAbsoluteTerminalPosition(t))) - .ToHashSet(); - - Point InvalidPoint = new Point(-1, -1); - for (int i = 0; i < points.Count - 1; i++) - { - if (points[i] == InvalidPoint || points[i + 1] == InvalidPoint) continue; - List checkablePoints = []; - double dx = points[i].X - points[i + 1].X; - double dy = points[i].Y - points[i + 1].Y; - - if (dx != 0) - { - while (dx != 0) - { - checkablePoints.Add(new Point(points[i].X - dx, points[i].Y)); - if (dx > 0) dx -= 10; - else dx += 10; - } - } - else if (dy != 0) - { - while (dy != 0) - { - checkablePoints.Add(new Point(points[i].X, points[i].Y - dy)); - if (dy > 0) dy -= 10; - else dy += 10; - } - } - else - { - - continue; // Handling for duplicate points - } - - // PATCH: this needs a replacement later - checkablePoints.RemoveAll((point) => - { - return points.Contains(point); - }); - - foreach (Point pt in checkablePoints) - { - Console.WriteLine(pt); - foreach (Point pos in terminalPositions) - { - if (pt == pos) - { - Console.WriteLine("FOUND"); - Console.WriteLine(pt); - return true; - } - } - } - } - return false; - } - - public bool DoesWireHaveExtension(Wire wire) - { - foreach (Point point in wire.Points) - { - if (point == new Point(-1, -1)) return true; - } - return false; - } - - // private bool IsWireInMovedWires(Wire wire, List MovedWires) - // { - // foreach (var movedWire in MovedWires) - // { - // if (movedWire == wire) return true; - // } - // return false; - // } - - // Point SnapToGrid(Point pt) - // { - // double snapX = (int)Math.Round(Math.Round(pt.X / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); - // double snapY = (int)Math.Round(Math.Round(pt.Y / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing); - // return new Point(snapX, snapY); - // } + public void PasteSelected() => _clipboardManager.Paste(Canvas, CurrentMousePos); // ------------------------- // Handling Mouse Actions // ------------------------- - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { - if (_canvas is null) throw new InvalidOperationException( - "Simulation.Register(Canvas) must be called before registering event handlers." - ); if (Simulating || !GridEnabled) { Console.WriteLine("Cannot edit while simulating"); @@ -585,7 +265,7 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) return; } Console.WriteLine("Registering Wire Extension..."); - _previewManager.StartWireExtension(_canvas, CurrentMousePos, existingWire, this); + _previewManager.StartWireExtension(Canvas, CurrentMousePos, existingWire, this); return; } } @@ -611,48 +291,42 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { // NEW COMPONENT LOGIC Console.WriteLine("Registering New Component..."); - _previewManager.HandleComponentCommit(_canvas, _components, CurrentMousePos, _commandManager, this); + _previewManager.HandleComponentCommit(Canvas, _components, CurrentMousePos, _commandManager, this); return; } } // Selection handling through selection manager _selectionManager.OnPointerPressed(sender, e, _selectedComponents); - _selectionManager.HandleStart(_canvas, _selectedComponents, CurrentMousePos); + _selectionManager.HandleStart(Canvas, _selectedComponents, CurrentMousePos); } private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) { - if (_canvas is null) throw new InvalidOperationException( - "Simulation.Register(Canvas) must be called before registering event handlers." - ); // Handle preview manager drag completion first _selectionManager.OnPointerReleased(sender, e, _commandManager); // Then handle selection manager - _selectionManager.HandleEnd(_canvas, _commandManager); + _selectionManager.HandleEnd(Canvas, _commandManager); } private void OnPointerMoved(object? sender, PointerEventArgs e) { - if (_canvas is null) throw new InvalidOperationException( - "Simulation.Register(Canvas) must be called before registering event handlers." - ); // Update the mouse pos - CurrentMousePos = e.GetPosition(_canvas); + CurrentMousePos = e.GetPosition(Canvas); - if (_previewManager.HandleUpdate(_canvas, CurrentMousePos, _gridManager.SnapToGridEnabled, + if (_previewManager.HandleUpdate(Canvas, CurrentMousePos, _gridManager.SnapToGridEnabled, _gridManager.SnapToGrid, this)) return; // Pass the selectedComponents reference and grid functions - _selectionManager.HandleUpdate(_canvas, _selectedComponents, CurrentMousePos, _components, this, + _selectionManager.HandleUpdate(Canvas, _selectedComponents, CurrentMousePos, _components, this, _gridManager.SnapToGridEnabled, _gridManager.SnapToGrid); } private void OnPointerWheel(object? sender, PointerWheelEventArgs e) { // TODO: THIS IS ACCEPTABLE FOR NOW BUT 100% NEEDS POLISH LATER ON - CurrentMousePos = _gridManager.SnapToGrid(e.GetPosition(_canvas)); + CurrentMousePos = _gridManager.SnapToGrid(e.GetPosition(Canvas)); // Update the preview component if (_previewManager.PreviewComponent != null)