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..6d40b74 100644 --- a/Models/PreviewManager.cs +++ b/Models/PreviewManager.cs @@ -1,19 +1,16 @@ +// 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; -internal class PreviewManager +public class PreviewManager { private string? _previewCompType; private Component? _previewComponent; @@ -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 11f4910..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; @@ -12,7 +12,7 @@ namespace IRis.Models; -internal class SelectionManager +public class SelectionManager { private Point _selectionStart; private Rectangle? _selectionRect; 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 9959ec3..ded6171 100644 --- a/Models/Simulation.cs +++ b/Models/Simulation.cs @@ -1,42 +1,33 @@ +// 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; namespace IRis.Models; -// Contains all the data needed for a simulation +// Main section of the Simulation class public partial class Simulation : ObservableObject { - private Canvas? _canvas; + // -------------------- + // Private attributes + // -------------------- + // 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; // 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; - public CustomComponentData CustomComponent { get; set; } = null!; - - public List Components - { - get => _components; - set => _components = value; - } - - public List MovedWires - { - get => _movedWires; - set => _movedWires = value; - } - [ObservableProperty] private Point _currentMousePos = new Point(0, 0); // Selection and interaction managers @@ -46,141 +37,41 @@ public List MovedWires 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; - - // 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; - set - { - _simulating = value; - if (_simulating) - { - _selectionManager.UnselectAll(SelectedComponents); - _previewManager.PreviewComponent = null; - _previewManager.PreviewCompType = "NULL"; - _updateTimer!.Start(); - } - else _updateTimer!.Stop(); - } - } - - 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; - SetupCanvas(); - SetupSimulation(); - RegisterEventHandlers(); - _gridManager.DrawGrid(_canvas); // Draws the main grid - } - - private void SetupCanvas() - { - // Important: Enable keyboard focus - _canvas!.Focusable = true; - _canvas.Cursor = new Cursor(StandardCursorType.Arrow); - } - - private void SetupSimulation() - { - // For updating the simulation - _updateTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; // Adjust to reduce CPU load - _updateTimer.Tick += (s, e) => SimulationStep(); - Simulating = false; - } - - private void RegisterEventHandlers() - { - _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; - } - - 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(); - } - } + // -------------------- + // Public attributes + // -------------------- + public CustomComponentData CustomComponent { get; set; } = null!; - // Component Management - public void DeleteSelectedComponents() - { - if (_selectedComponents.Count > 0) - { - var deleteCommand = new DeleteComponentsCommand(_canvas!, _components, _selectedComponents); - _commandManager.ExecuteCommand(deleteCommand); - _selectedComponents.Clear(); - } - } + // Public access for Managers + public CommandManager CommandManager => _commandManager; + public PreviewManager PreviewManager => _previewManager; + public SelectionManager SelectionManager => _selectionManager; - public void UnselectComponents() => _selectionManager.UnselectAll(_selectedComponents); + // Public access for Components-related + public List SelectedComponents => _selectedComponents; + public bool HasSelectedComponents => _selectedComponents.Count > 0; - // TODO: THESE METHODS ARE SHALLOW AND BAD! (probably) - public void LoadComponents(List components) + public List Components { - _components = components; - _canvas!.Children.AddRange(_components); + get => _components; + set => _components = value; } - // sus? amogus? - public void DeleteAllComponents() + public List MovedWires { - _canvas!.Children.RemoveAll(_components); - _components.Clear(); + get => _movedWires; + set => _movedWires = value; } - // Clipboard operations - 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); + set => _previewManager.SetPreviewComponent(value, Canvas, CurrentMousePos, this); } // Grid management @@ -197,321 +88,140 @@ public bool GridEnabled { _gridManager.GridEnabled = value; if (value) - _gridManager.DrawGrid(_canvas!); + _gridManager.DrawGrid(Canvas); else { - _canvas!.Children.Clear(); - _canvas.Children.AddRange(_components); + Canvas.Children.Clear(); + Canvas.Children.AddRange(_components); } } } - // Terminal Snapping - public Terminal? FindClosestSnapTerminal(Point p) + // ------------------------------------------------- + // Public methods for simulation construction + // ------------------------------------------------- + public Simulation() { - Terminal? closestTerminal = null; - double minDistance = double.MaxValue; + // Initialize empty lists + _components = []; + _selectedComponents = []; + _movedWires = []; - 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 + // Initialize managers + _selectionManager = new SelectionManager(); + _previewManager = new PreviewManager(); + _clipboardManager = new ClipboardManager(); + _gridManager = new GridManager(); } - - public bool IsInputTerminal(Terminal terminal) + + public bool Simulating { - if (terminal == null) return false; - - foreach (Component component in _components) + get => _simulating; + set { - 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) + _simulating = value; + if (_simulating) { - // 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; - } + _selectionManager.UnselectAll(SelectedComponents); + _previewManager.PreviewComponent = null; + _previewManager.PreviewCompType = "NULL"; + _updateTimer!.Start(); } + else _updateTimer!.Stop(); } - - 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) + public void Register(Canvas canvas) { - 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); - } - }); + _canvas = canvas; + SetupCanvas(); + SetupSimulation(); + RegisterEventHandlers(); + _gridManager.DrawGrid(_canvas); // Draws the main grid } - public bool IsWireInsideAnyComponent(List points) + // ---------------------------------------------------- + // Private helper Methods for the above constructors + // ---------------------------------------------------- + private void SimulationStep() { - Point InvalidPoint = new Point(-1, -1); - for (int i = 0; i < points.Count - 1; i++) + foreach (var component in _components) { - 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; + // Compute outputs for everything first + if (component is IOutputProvider op) + op.ComputeOutput(); - 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; - } + // Redraw Toggles and Probes + if (component is LogicProbe || component is LogicToggle) + component.InvalidateVisual(); } - 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) + private void SetupCanvas() { - 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; + // Important: Enable keyboard focus + Canvas.Focusable = true; + Canvas.Cursor = new Cursor(StandardCursorType.Arrow); } - public bool DoesWireSelfOverlap(List points) + private void SetupSimulation() { - 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; + // 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; } - public bool DoesWireCrossTerminal(List points, Terminal? exceptionCase=null) + private void RegisterEventHandlers() { - 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; + 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; } - public bool DoesWireHaveExtension(Wire wire) + // -------------------------------- + // Component Management Methods + // -------------------------------- + public void DeleteSelectedComponents() { - foreach (Point point in wire.Points) + if (_selectedComponents.Count > 0) { - if (point == new Point(-1, -1)) return true; + var deleteCommand = new DeleteComponentsCommand( + Canvas, _components, _selectedComponents); + _commandManager.ExecuteCommand(deleteCommand); + _selectedComponents.Clear(); } - return false; } - - private bool IsWireInMovedWires(Wire wire, List MovedWires) + + // TODO: THESE METHODS ARE SHALLOW AND BAD! (probably) + public void LoadComponents(List components) { - foreach (var movedWire in MovedWires) - { - if (movedWire == wire) return true; - } - return false; + _components = components; + Canvas.Children.AddRange(_components); } - Point SnapToGrid(Point pt) + // sus? amogus? + public void DeleteAllComponents() { - 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); + Canvas.Children.RemoveAll(_components); + _components.Clear(); } - // _______________________________________________ - // ____________ Pointer/key handling _____________ - // _______________________________________________ + // ---------------------------------------------- + // 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); + // ------------------------- + // Handling Mouse Actions + // ------------------------- private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { if (Simulating || !GridEnabled) @@ -555,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; } } @@ -581,13 +291,13 @@ 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) @@ -596,27 +306,27 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) _selectionManager.OnPointerReleased(sender, e, _commandManager); // Then handle selection manager - _selectionManager.HandleEnd(_canvas!, _commandManager); + _selectionManager.HandleEnd(Canvas, _commandManager); } private void OnPointerMoved(object? sender, PointerEventArgs e) { // 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) 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"; } 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 +}