diff --git a/App.axaml.cs b/App.axaml.cs index 260a546..4aea8cb 100644 --- a/App.axaml.cs +++ b/App.axaml.cs @@ -1,12 +1,11 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; -using System.Linq; using Avalonia.Markup.Xaml; using IRis.Models; using IRis.ViewModels; using IRis.Views; +using System.Linq; namespace IRis; @@ -22,13 +21,13 @@ public override void OnFrameworkInitializationCompleted() // NOTE: IMPLEMENT A THEME SYSTEM // Set the default theme as 'Dark' // ThemeManager.ChangeTheme(Application.Current, "Dark"); - + // 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(); - + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { // Avoid duplicate validations from both Avalonia and the CommunityToolkit. diff --git a/Models/Commands.cs b/Models/Commands.cs index 845481f..c6e43e5 100644 --- a/Models/Commands.cs +++ b/Models/Commands.cs @@ -1,10 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Avalonia; using Avalonia.Controls; -using IRis.Models.Core; using IRis.Models.Components; +using IRis.Models.Core; +using System; +using System.Collections.Generic; +using System.Linq; namespace IRis.Models.Commands { @@ -56,7 +56,7 @@ public DeleteComponentsCommand(Canvas canvas, List components, List(selectedComponents); - _originalPositions = selectedComponents.Select(c => + _originalPositions = selectedComponents.Select(c => new Point(Canvas.GetLeft(c), Canvas.GetTop(c))).ToList(); } @@ -75,7 +75,7 @@ public void Undo() { var component = _deletedComponents[i]; var position = _originalPositions[i]; - + Canvas.SetLeft(component, position.X); Canvas.SetTop(component, position.Y); _canvas.Children.Add(component); @@ -147,11 +147,11 @@ public MoveComponentsCommand(List components, Point offset) { _components = new List(components); _canvas = null!; - + // Calculate original and new positions based on offset - _originalPositions = _components.Select(c => + _originalPositions = _components.Select(c => new Point(Canvas.GetLeft(c), Canvas.GetTop(c))).ToList(); - _newPositions = _originalPositions.Select(pos => + _newPositions = _originalPositions.Select(pos => new Point(pos.X + offset.X, pos.Y + offset.Y)).ToList(); } @@ -161,9 +161,9 @@ public MoveComponentsCommand(Canvas canvas, List components, List(components); _newPositions = new List(newPositions); - + // Store original positions for undo - _originalPositions = _components.Select(c => + _originalPositions = _components.Select(c => new Point(Canvas.GetLeft(c), Canvas.GetTop(c))).ToList(); } diff --git a/Models/ComponentManagers.cs b/Models/ComponentManagers.cs index 715500e..4bc02ec 100644 --- a/Models/ComponentManagers.cs +++ b/Models/ComponentManagers.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Shapes; using IRis.Models.Components; using IRis.Models.Core; +using System; +using System.Collections.Generic; +using System.Linq; namespace IRis.Models; diff --git a/Models/Components/AndGate.cs b/Models/Components/AndGate.cs index fa6603d..d036b1e 100644 --- a/Models/Components/AndGate.cs +++ b/Models/Components/AndGate.cs @@ -1,11 +1,10 @@ -using Avalonia; using Avalonia.Media; using IRis.Models.Core; using System; using System.Linq; -namespace IRis.Models.Components; +namespace IRis.Models.Components; public class AndGate : Gate @@ -20,7 +19,7 @@ public override void Draw(DrawingContext ctx) // 3. Draw terminals (lines + circles) DrawTerminals(ctx); - this.DrawAnd(ctx); + DrawAnd(ctx); base.Draw(ctx); } @@ -36,10 +35,10 @@ public override void ComputeOutput() if (!inputTerminals.All(t => t.Wires.Any()) || !outputTerminal.Wires.Any()) return; // For each input terminal, OR together all connected wire values - var inputValues = inputTerminals.Select(terminal => + var inputValues = inputTerminals.Select(terminal => terminal.Wires.Any(w => w.Value == LogicState.High)).ToList(); - if (inputValues.All(value => value == true)) // All inputs must be high + if (inputValues.All(value => value)) // All inputs must be high { foreach (Wire wire in outputTerminal.Wires) { diff --git a/Models/Components/CustomComponent.cs b/Models/Components/CustomComponent.cs index c5cb21e..65df451 100644 --- a/Models/Components/CustomComponent.cs +++ b/Models/Components/CustomComponent.cs @@ -1,23 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using Avalonia; using Avalonia.Media; using IRis.Models.Core; -using IRis.Models; using IRis.Services; +using System; +using System.Collections.Generic; namespace IRis.Models.Components; -public class CustomComponent : Component, IOutputProvider +public class CustomComponent : CircuitComponent, IOutputProvider { protected string ComponentName; public int InputCount; protected int OutputCount; protected List OutputFormulas; - - public CustomComponent(string name, int inputCount = 2, int outputCount = 1, + + public CustomComponent(string name, int inputCount = 2, int outputCount = 1, List? outputFormulas = null, double width = ComponentDefaults.DefaultMuxWidth, double height = ComponentDefaults.DefaultMuxHeight) @@ -28,7 +25,7 @@ public CustomComponent(string name, int inputCount = 2, int outputCount = 1, InputCount = inputCount; OutputCount = outputCount; OutputFormulas = outputFormulas ?? []; - + // Calculate dimensions based on input/output count int maxTerminals = Math.Max(InputCount, OutputCount); // maxTerminals = (maxTerminals % 2 == 1) ? maxTerminals + 1 : maxTerminals; @@ -55,13 +52,13 @@ public void ComputeOutput() { // Get the formula for this output var formula = OutputFormulas[outputIndex]; - + // Build input dictionary with actual values from terminals var inputs = GetInputValues(); - + // Evaluate the formula using the service method bool result = CircuitFormulaConversionService.EvaluateFormula(formula.Formula, inputs); - + // Set the output terminal value int outputTerminalIndex = InputCount + outputIndex; if (Terminals![outputTerminalIndex].Wire != null) @@ -84,26 +81,26 @@ public void ComputeOutput() private Dictionary GetInputValues() { var inputs = new Dictionary(); - + // Get values from input terminals and map them to formula variables for (int i = 0; i < InputCount; i++) { bool inputValue = false; - + // Check if terminal has a wire and get its value if (i < Terminals!.Length && Terminals[i].Wire != null) { inputValue = Terminals[i].Wire!.Value == LogicState.High; } - + // Add both Input_X format (used by formula service) and A,B,C format inputs[$"Input_{i + 1}"] = inputValue; - + // Also support A, B, C... format for compatibility char inputVar = (char)('A' + i); inputs[inputVar.ToString()] = inputValue; } - + return inputs; } @@ -111,11 +108,11 @@ public override void Draw(DrawingContext ctx) { // Component Body ctx.DrawRectangle(ComponentDefaults.GateFillBrush, - ComponentDefaults.GatePen, + ComponentDefaults.GatePen, new Rect(0, 0, Width, Height)); - + DrawTerminalsAndLabels(ctx); - + base.Draw(ctx); } @@ -123,10 +120,10 @@ public override void DrawSelection(DrawingContext ctx) { double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; double expandY = ComponentDefaults.TerminalRadius; - + ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, + ComponentDefaults.SelectionBrush, + ComponentDefaults.SelectionPen, new Rect( -expandX, -expandY, @@ -189,14 +186,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx) // Draw input label char inputLabel = (char)('A' + i); - var text = new FormattedText( - inputLabel.ToString(), - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = inputLabel.ToString().CreateFormattedText(); + ctx.DrawText(text, new Point(4.5, Terminals[i].Position.Y - 6)); } @@ -216,14 +207,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx) // Draw output label (Y for single output, Y0, Y1, etc. for multiple) string outputLabel = OutputCount == 1 ? "Y" : $"Y{i}"; - var text = new FormattedText( - outputLabel, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = outputLabel.CreateFormattedText(); + ctx.DrawText(text, new Point(Width - 20, Terminals[terminalIndex].Position.Y - 6)); } @@ -232,14 +217,7 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx) { string displayText = ComponentName; - var formulaText = new FormattedText( - displayText, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var formulaText = displayText.CreateFormattedText(); double textX = (Width - formulaText.Width) / 2; double textY = (Height - formulaText.Height) / 2; diff --git a/Models/Components/DLatch.cs b/Models/Components/DLatch.cs index 9b3543e..f9d55d7 100644 --- a/Models/Components/DLatch.cs +++ b/Models/Components/DLatch.cs @@ -1,12 +1,14 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class DLatch : Component, IOutputProvider +public class DLatch( + double width = ComponentDefaults.DefaultMuxWidth * 5, // x5 width as you mentioned + double height = ComponentDefaults.DefaultMuxHeight) + : LatchBase(width, height), IOutputProvider { // Terminals: // 0: D (left, top) @@ -15,24 +17,9 @@ public class DLatch : Component, IOutputProvider // 3: Q' (right, bottom) - public DLatch(double width = ComponentDefaults.DefaultMuxWidth * 5, // x5 width as you mentioned - double height = ComponentDefaults.DefaultMuxHeight) - : base(width, height) - { - Width = 5 * ComponentDefaults.TerminalSpacing; - Height = 3 * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; - - Terminals = new Terminal[4]; - AddTerminalPoints(); - IsHitTestVisible = true; - - // Dictionary entry for state - StoredStates["Q"] = LogicState.Low; - } - public void ComputeOutput() { - var d = Terminals![0].Wire!.Value; + var d = Terminals![0].Wire!.Value; var en = Terminals![1].Wire!.Value; if (en == LogicState.High) @@ -46,32 +33,6 @@ public void ComputeOutput() Terminals[3].Wire!.Value = StoredStates["Q"] == LogicState.High ? LogicState.Low : LogicState.High; } - public override void Draw(DrawingContext ctx) - { - ctx.DrawRectangle(ComponentDefaults.GateFillBrush, - ComponentDefaults.GatePen, - new Rect(0, 0, Width, Height)); - - DrawTerminalsAndLabels(ctx); - base.Draw(ctx); - } - - public override void DrawSelection(DrawingContext ctx) - { - double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; - double expandY = ComponentDefaults.TerminalRadius; - - ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, - new Rect( - -expandX, - -expandY, - Bounds.Width + 2 * expandX, - Bounds.Height + 2 * expandY) - ); - } - public override void AddTerminalPoints(bool notMode = false) { Point SnapToGrid(Point pt) @@ -82,23 +43,23 @@ Point SnapToGrid(Point pt) } // Inputs (left) - var dPos = new Point(-ComponentDefaults.TerminalWireLength, ComponentDefaults.TerminalSpacing * 1); + var dPos = new Point(-ComponentDefaults.TerminalWireLength, ComponentDefaults.TerminalSpacing * 1); var enPos = new Point(-ComponentDefaults.TerminalWireLength, ComponentDefaults.TerminalSpacing * 2); Terminals![0] = new Terminal(SnapToGrid(dPos), null!); Terminals![1] = new Terminal(SnapToGrid(enPos), null!); // Outputs (right) - var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); + var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); var nqPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 2); - var qSnap = SnapToGrid(qPos); + var qSnap = SnapToGrid(qPos); var nqSnap = SnapToGrid(nqPos); - Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); + Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); Terminals![3] = new Terminal(new Point(nqPos.X, nqSnap.Y), null!); } - private void DrawTerminalsAndLabels(DrawingContext ctx) + internal override void DrawTerminalsAndLabels(DrawingContext ctx) { // Inputs string[] labels = { "D", "EN" }; @@ -109,14 +70,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - labels[i], - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = labels[i].CreateFormattedText(); + ctx.DrawText(text, new Point(4.5, Terminals[i].Position.Y - 6)); } @@ -129,14 +84,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[j].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - outLabels[j - 2], - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = outLabels[j - 2].CreateFormattedText(); + ctx.DrawText(text, new Point(Width - 18, Terminals[j].Position.Y - 6)); } } diff --git a/Models/Components/Decoder.cs b/Models/Components/Decoder.cs index 7cce6a3..f344946 100644 --- a/Models/Components/Decoder.cs +++ b/Models/Components/Decoder.cs @@ -1,14 +1,13 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class Decoder : Component, IOutputProvider +public class Decoder : CircuitComponent, IOutputProvider { - + public Decoder(int selectionLineCount, double width = ComponentDefaults.DefaultMuxWidth, double height = ComponentDefaults.DefaultMuxHeight) @@ -17,8 +16,8 @@ public Decoder(int selectionLineCount, SelectionLineCount = selectionLineCount; OutputLineCount = (int)Math.Pow(2, SelectionLineCount); - Width = (SelectionLineCount + 1) * ComponentDefaults.TerminalSpacing; - Height = (OutputLineCount + 1) * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; + Width = (SelectionLineCount + 1) * ComponentDefaults.TerminalSpacing; + Height = (OutputLineCount + 1) * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; // Selection inputs + data outputs Terminals = new Terminal[SelectionLineCount + OutputLineCount]; @@ -114,14 +113,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char selectionLabel, c ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{selectionLabel}{SelectionLineCount - i - 1}", // MSB first - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{selectionLabel}{SelectionLineCount - i - 1}".CreateFormattedText(); + ctx.DrawText(text, new Point(4.5, Terminals[i].Position.Y - 6)); } @@ -135,14 +128,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char selectionLabel, c ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[outIdx].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{outputLabel}{k}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{outputLabel}{k}".CreateFormattedText(); + ctx.DrawText(text, new Point(Width - 18, Terminals[outIdx].Position.Y - 6)); } } diff --git a/Models/Components/Demultiplexer.cs b/Models/Components/Demultiplexer.cs index 3233c34..62446b8 100644 --- a/Models/Components/Demultiplexer.cs +++ b/Models/Components/Demultiplexer.cs @@ -1,12 +1,11 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class Demultiplexer : Component, IOutputProvider +public class Demultiplexer : CircuitComponent, IOutputProvider { public Demultiplexer(int selectionLineCount, double width = ComponentDefaults.DefaultMuxWidth, @@ -17,8 +16,8 @@ public Demultiplexer(int selectionLineCount, double width = ComponentDefaults.De OutputLineCount = (int)Math.Pow(2, SelectionLineCount); // Geometry consistent with Multiplexer, but mirrored (1 input, many outputs) - Width = (SelectionLineCount + 1) * ComponentDefaults.TerminalSpacing; - Height = (OutputLineCount + 1) * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; + Width = (SelectionLineCount + 1) * ComponentDefaults.TerminalSpacing; + Height = (OutputLineCount + 1) * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; // n selection lines + 1 data input + 2^n data outputs Terminals = new Terminal[SelectionLineCount + 1 + OutputLineCount]; @@ -126,14 +125,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{selectionLabel}{SelectionLineCount - i - 1}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{selectionLabel}{SelectionLineCount - i - 1}".CreateFormattedText(); + ctx.DrawText(text, new Point(Terminals[i].Position.X - 7, Height - 18.5)); } @@ -144,14 +137,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[inputIndex].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{inputLabel}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{inputLabel}".CreateFormattedText(); + // Slight nudge right of the left edge, vertically centered on the input terminal ctx.DrawText(text, new Point(4.5, Terminals[inputIndex].Position.Y - 6)); } @@ -169,14 +156,7 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[outIdx].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{outputLabel}{k}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{outputLabel}{k}".CreateFormattedText(); // Place label just left inside the body near the right edge ctx.DrawText( diff --git a/Models/Components/Encoder.cs b/Models/Components/Encoder.cs index 80a630d..d2d7f73 100644 --- a/Models/Components/Encoder.cs +++ b/Models/Components/Encoder.cs @@ -1,12 +1,11 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class Encoder : Component, IOutputProvider +public class Encoder : CircuitComponent, IOutputProvider { public Encoder(int selectionLineCount, @@ -17,8 +16,8 @@ public Encoder(int selectionLineCount, SelectionLineCount = selectionLineCount; InputLineCount = (int)Math.Pow(2, SelectionLineCount); - Width = (SelectionLineCount + 1) * ComponentDefaults.TerminalSpacing; - Height = (InputLineCount + 1) * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; + Width = (SelectionLineCount + 1) * ComponentDefaults.TerminalSpacing; + Height = (InputLineCount + 1) * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; // Inputs + encoded outputs Terminals = new Terminal[InputLineCount + SelectionLineCount]; @@ -122,14 +121,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{inputLabel}{i}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{inputLabel}{i}".CreateFormattedText(); + ctx.DrawText(text, new Point(4.5, Terminals[i].Position.Y - 6)); } @@ -144,14 +137,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[outIdx].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{outputLabel}{SelectionLineCount - j - 1}", // MSB left, LSB right - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{outputLabel}{SelectionLineCount - j - 1}".CreateFormattedText(); + ctx.DrawText(text, new Point(Width - 18, Terminals[outIdx].Position.Y - 6)); } } diff --git a/Models/Components/FormaterExt.cs b/Models/Components/FormaterExt.cs new file mode 100644 index 0000000..91b7c22 --- /dev/null +++ b/Models/Components/FormaterExt.cs @@ -0,0 +1,21 @@ +using Avalonia.Media; +using IRis.Models.Core; +using System.Globalization; + +namespace IRis.Models.Components +{ + public static class FormaterExt + { + public static FormattedText CreateFormattedText(this string label) + { + return new FormattedText( + label, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + ComponentDefaults.LabelTypeface, + ComponentDefaults.LabelSize, + ComponentDefaults.LabelBrush + ); + } + } +} diff --git a/Models/Components/JKLatch.cs b/Models/Components/JKLatch.cs index 29b831c..77037b7 100644 --- a/Models/Components/JKLatch.cs +++ b/Models/Components/JKLatch.cs @@ -1,12 +1,14 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class JKLatch : Component, IOutputProvider +public class JKLatch( + double width = ComponentDefaults.DefaultMuxWidth * 5, + double height = ComponentDefaults.DefaultMuxHeight) + : LatchBase(width, height), IOutputProvider { // Terminals: // 0: J (left, top) @@ -15,21 +17,6 @@ public class JKLatch : Component, IOutputProvider // 3: Q' (right, bottom) - public JKLatch(double width = ComponentDefaults.DefaultMuxWidth * 5, - double height = ComponentDefaults.DefaultMuxHeight) - : base(width, height) - { - Width = 5 * ComponentDefaults.TerminalSpacing; - Height = 3 * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; - - Terminals = new Terminal[4]; - AddTerminalPoints(); - IsHitTestVisible = true; - - // Dictionary entry for state - StoredStates["Q"] = LogicState.Low; - } - public void ComputeOutput() { var j = Terminals![0].Wire!.Value; @@ -57,32 +44,6 @@ public void ComputeOutput() Terminals[3].Wire!.Value = StoredStates["Q"] == LogicState.High ? LogicState.Low : LogicState.High; } - public override void Draw(DrawingContext ctx) - { - ctx.DrawRectangle(ComponentDefaults.GateFillBrush, - ComponentDefaults.GatePen, - new Rect(0, 0, Width, Height)); - - DrawTerminalsAndLabels(ctx); - base.Draw(ctx); - } - - public override void DrawSelection(DrawingContext ctx) - { - double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; - double expandY = ComponentDefaults.TerminalRadius; - - ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, - new Rect( - -expandX, - -expandY, - Bounds.Width + 2 * expandX, - Bounds.Height + 2 * expandY) - ); - } - public override void AddTerminalPoints(bool notMode = false) { Point SnapToGrid(Point pt) @@ -99,17 +60,17 @@ Point SnapToGrid(Point pt) Terminals![1] = new Terminal(SnapToGrid(kPos), null!); // Outputs: Q (top-right), Q' (bottom-right) - var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); + var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); var nqPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 2); - var qSnap = SnapToGrid(qPos); + var qSnap = SnapToGrid(qPos); var nqSnap = SnapToGrid(nqPos); - Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); + Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); Terminals![3] = new Terminal(new Point(nqPos.X, nqSnap.Y), null!); } - private void DrawTerminalsAndLabels(DrawingContext ctx) + internal override void DrawTerminalsAndLabels(DrawingContext ctx) { // Inputs string[] inLabels = { "J", "K" }; @@ -120,14 +81,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - inLabels[i], - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = inLabels[i].CreateFormattedText(); + ctx.DrawText(text, new Point(4.5, Terminals[i].Position.Y - 6)); } @@ -140,14 +95,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[j].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - outLabels[j - 2], - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = outLabels[j - 2].CreateFormattedText(); + ctx.DrawText(text, new Point(Width - 18, Terminals[j].Position.Y - 6)); } } diff --git a/Models/Components/LatchBase.cs b/Models/Components/LatchBase.cs new file mode 100644 index 0000000..1e9453f --- /dev/null +++ b/Models/Components/LatchBase.cs @@ -0,0 +1,50 @@ +using Avalonia; +using Avalonia.Media; +using IRis.Models.Core; + +namespace IRis.Models.Components; + +public abstract class LatchBase : CircuitComponent +{ + public LatchBase(double width, double height) + : base(width, height) + { + Width = 5 * ComponentDefaults.TerminalSpacing; + Height = 3 * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; + + Terminals = new Terminal[4]; + AddTerminalPoints(); + IsHitTestVisible = true; + + // Dictionary entry for state + StoredStates["Q"] = LogicState.Low; + } + + public override void Draw(DrawingContext ctx) + { + ctx.DrawRectangle(ComponentDefaults.GateFillBrush, + ComponentDefaults.GatePen, + new Rect(0, 0, Width, Height)); + + DrawTerminalsAndLabels(ctx); + base.Draw(ctx); + } + + public override void DrawSelection(DrawingContext ctx) + { + double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; + double expandY = ComponentDefaults.TerminalRadius; + + ctx.DrawRectangle( + ComponentDefaults.SelectionBrush, + ComponentDefaults.SelectionPen, + new Rect( + -expandX, + -expandY, + Bounds.Width + 2 * expandX, + Bounds.Height + 2 * expandY) + ); + } + + internal abstract void DrawTerminalsAndLabels(DrawingContext ctx); +} \ No newline at end of file diff --git a/Models/Components/LogicProbe.cs b/Models/Components/LogicProbe.cs index 014b342..79de86a 100644 --- a/Models/Components/LogicProbe.cs +++ b/Models/Components/LogicProbe.cs @@ -1,53 +1,51 @@ -using System.Globalization; using Avalonia; using Avalonia.Media; -using Avalonia.Media.Immutable; -using Avalonia.Styling; using IRis.Models.Core; using System; +using System.Globalization; namespace IRis.Models.Components; -public class LogicProbe : Component +public class LogicProbe : CircuitComponent { public LogicProbe(double width = ComponentDefaults.DefaultWidth, double height = ComponentDefaults.DefaultHeight) : base(width, height) { Width = width * 1 / 2; Height = height * 1 / 2; - + Terminals = new Terminal[1]; // Helper Method for snapping to grid static double Snap(double val) => Math.Round(val / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing; double x = Snap(-ComponentDefaults.TerminalWireLength); double y = Snap(Height / 2); - + // Left-oriented Terminals[0] = new Terminal(new Point(x, y), null!); - + } - + public override object Clone() { LogicProbe clone = new LogicProbe(); - + // Copy all base properties - clone.Width = this.Width; - clone.Height = this.Height; - clone.Rotation = this.Rotation; - clone.IsSelected = this.IsSelected; - + clone.Width = Width; + clone.Height = Height; + clone.Rotation = Rotation; + clone.IsSelected = IsSelected; + // Component-specific things - clone.Terminals![0] = new Terminal(clone.Terminals[0].Position, this.Terminals![0].Wire!); - + clone.Terminals![0] = new Terminal(clone.Terminals[0].Position, Terminals![0].Wire!); + // Reset visual state clone.VisualChildren.Clear(); clone.InvalidateVisual(); return clone; } - + public override void Draw(DrawingContext ctx) { IImmutableSolidColorBrush fill; @@ -58,8 +56,6 @@ public override void Draw(DrawingContext ctx) { LogicState.High => ComponentDefaults.TrueBrush, LogicState.Low => ComponentDefaults.FalseBrush, - LogicState.DontCare => ComponentDefaults.DontCareBrush, - null => ComponentDefaults.DontCareBrush, _ => ComponentDefaults.DontCareBrush }; @@ -68,7 +64,6 @@ public override void Draw(DrawingContext ctx) LogicState.High => "1", LogicState.Low => "0", LogicState.DontCare => "X", - null => "?", _ => "?" }; Console.WriteLine(Terminals[0].Wire!.Value == null); @@ -119,9 +114,9 @@ public override void Draw(DrawingContext ctx) public override void DrawSelection(DrawingContext ctx) { ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, - new Rect(-10,-10, Width + 20, Height + 20) + ComponentDefaults.SelectionBrush, + ComponentDefaults.SelectionPen, + new Rect(-10, -10, Width + 20, Height + 20) ); } } \ No newline at end of file diff --git a/Models/Components/LogicToggle.cs b/Models/Components/LogicToggle.cs index 40ed736..732aeba 100644 --- a/Models/Components/LogicToggle.cs +++ b/Models/Components/LogicToggle.cs @@ -1,15 +1,13 @@ -using System.Collections.Generic; -using System.Globalization; using Avalonia; using Avalonia.Media; -using Avalonia.Media.Immutable; using IRis.Models.Core; using System; +using System.Globalization; namespace IRis.Models.Components; -public class LogicToggle : Component, IOutputProvider +public class LogicToggle : CircuitComponent, IOutputProvider { public LogicState Value { @@ -46,9 +44,9 @@ public LogicToggle(double width = ComponentDefaults.DefaultWidth, double height // Create a dictionary entry for its value StoredStates["Value"] = LogicState.Low; - + // Register an event handler for DoubleClicks - this.DoubleTapped += (s, e) => + DoubleTapped += (_, _) => { Toggle(); }; @@ -68,25 +66,25 @@ private void Toggle() } - + public override object Clone() { LogicToggle clone = new LogicToggle(); - + // Copy all base properties - clone.Width = this.Width; - clone.Height = this.Height; - clone.Rotation = this.Rotation; - clone.IsSelected = this.IsSelected; - + clone.Width = Width; + clone.Height = Height; + clone.Rotation = Rotation; + clone.IsSelected = IsSelected; + // Component-specific things - if (clone.Terminals is not null && this.Terminals is not null) + if (clone.Terminals is not null && Terminals is not null) { - clone.Terminals[0] = new Terminal(clone.Terminals[0].Position, this.Terminals[0].Wire!); + clone.Terminals[0] = new Terminal(clone.Terminals[0].Position, Terminals[0].Wire!); } - clone.Value = this.Value; - + clone.Value = Value; + // Reset visual state clone.VisualChildren.Clear(); clone.InvalidateVisual(); @@ -99,31 +97,23 @@ public void ComputeOutput() // Propagate the toggle value to ALL connected wires foreach (var wire in Terminals![0].Wires) { - if (wire != null) - { - wire.Value = this.Value; - } + wire.Value = Value; } } public override void Draw(DrawingContext ctx) { - IImmutableSolidColorBrush fill = ComponentDefaults.DontCareBrush; - string content = "X"; - - fill = Value switch + var fill = Value switch { LogicState.High => ComponentDefaults.TrueBrush, LogicState.Low => ComponentDefaults.FalseBrush, - LogicState.DontCare => ComponentDefaults.DontCareBrush, _ => ComponentDefaults.DontCareBrush }; - content = Value switch + var content = Value switch { LogicState.High => "1", LogicState.Low => "0", - LogicState.DontCare => "X", _ => "X" }; diff --git a/Models/Components/Multiplexer.cs b/Models/Components/Multiplexer.cs index e304631..d5f3b16 100644 --- a/Models/Components/Multiplexer.cs +++ b/Models/Components/Multiplexer.cs @@ -1,25 +1,24 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class Multiplexer : Component, IOutputProvider +public class Multiplexer : CircuitComponent, IOutputProvider { - + public Multiplexer(int selectionLineCount, double width = ComponentDefaults.DefaultMuxWidth, double height = ComponentDefaults.DefaultMuxHeight) : base(width, height) { SelectionLineCount = selectionLineCount; InputLineCount = (int)Math.Pow(2, SelectionLineCount); - + Width = (SelectionLineCount + 1) * ComponentDefaults.TerminalSpacing; Height = (InputLineCount + 1) * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; - + // n selection lines map to 2^n lines and 1 output Terminals = new Terminal[SelectionLineCount + (int)Math.Pow(2, SelectionLineCount) + 1]; @@ -46,30 +45,30 @@ public void ComputeOutput() public override void Draw(DrawingContext ctx) { - + // Component Body ctx.DrawRectangle(ComponentDefaults.GateFillBrush, - ComponentDefaults.GatePen, - new Rect(0,0, Width, Height)); - + ComponentDefaults.GatePen, + new Rect(0, 0, Width, Height)); + DrawTerminalsAndLabels(ctx, 'D', 'S'); - + base.Draw(ctx); } public override void DrawSelection(DrawingContext ctx) { - double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius ; - double expandY = ComponentDefaults.TerminalRadius ; + double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; + double expandY = ComponentDefaults.TerminalRadius; // Subtle fill ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, + ComponentDefaults.SelectionBrush, + ComponentDefaults.SelectionPen, new Rect( -expandX, -expandY, - Bounds.Width + 2 * expandX , + Bounds.Width + 2 * expandX, Bounds.Height + 2 * expandY) ); } @@ -84,14 +83,14 @@ Point SnapToGrid(Point pt) double snapY = Math.Round(pt.Y / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing; return new Point(snapX, snapY); } - + // For selection lines for (int i = 0; i < SelectionLineCount; i++) { Point pos = new Point((i + 1) * ComponentDefaults.TerminalSpacing, Height + ComponentDefaults.TerminalWireLength); Terminals![i] = new Terminal(SnapToGrid(pos), null!); } - + // For input lines for (int i = SelectionLineCount; i < InputLineCount + SelectionLineCount; i++) { @@ -101,7 +100,7 @@ Point SnapToGrid(Point pt) // For outputoutputPos // Fix: do not snap the X value of the outputPos - Point outputPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, Height/2); + Point outputPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, Height / 2); Terminals![^1] = new Terminal(new Point(outputPos.X, SnapToGrid(outputPos).Y), null!); } @@ -114,14 +113,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{selectionLabel}{SelectionLineCount - i - 1}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{selectionLabel}{SelectionLineCount - i - 1}".CreateFormattedText(); + ctx.DrawText( text, new Point(Terminals[i].Position.X - 7, Height - 18.5) @@ -136,14 +129,8 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - $"{inputLabel}{i - SelectionLineCount}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = $"{inputLabel}{i - SelectionLineCount}".CreateFormattedText(); + ctx.DrawText( text, new Point(4.5, Terminals[i].Position.Y - 6) @@ -156,6 +143,6 @@ protected void DrawTerminalsAndLabels(DrawingContext ctx, char inputLabel, char ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[^1].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); } - - + + } \ No newline at end of file diff --git a/Models/Components/NandGate.cs b/Models/Components/NandGate.cs index 6086e48..05cea86 100644 --- a/Models/Components/NandGate.cs +++ b/Models/Components/NandGate.cs @@ -1,10 +1,10 @@ -using System.Linq; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System.Linq; -namespace IRis.Models.Components; +namespace IRis.Models.Components; public class NandGate : Gate @@ -19,7 +19,7 @@ public override void Draw(DrawingContext ctx) // 3. Draw terminals (lines + circles) DrawTerminals(ctx); - this.DrawAnd(ctx); + DrawAnd(ctx); // 3. Draw the bubble at the end ctx.DrawEllipse( @@ -30,9 +30,9 @@ public override void Draw(DrawingContext ctx) ComponentDefaults.BubbleRadius); base.Draw(ctx); - + } - + public override void ComputeOutput() { // For inputs: check if ANY input terminal has at least one wire @@ -44,11 +44,11 @@ public override void ComputeOutput() if (!inputTerminals.All(t => t.Wires.Any()) || !outputTerminal.Wires.Any()) return; // For each input terminal, OR together all connected wire values - var inputValues = inputTerminals.Select(terminal => + var inputValues = inputTerminals.Select(terminal => terminal.Wires.Any(w => w.Value == LogicState.High)).ToList(); // NAND logic: NOT(AND) - output is LOW only when ALL inputs are HIGH - LogicState outputValue = inputValues.All(value => value == true) ? LogicState.Low : LogicState.High; + LogicState outputValue = inputValues.All(value => value) ? LogicState.Low : LogicState.High; // Set output on ALL connected wires foreach (var wire in outputTerminal.Wires) @@ -56,6 +56,6 @@ public override void ComputeOutput() wire.Value = outputValue; } } - - + + } \ No newline at end of file diff --git a/Models/Components/NorGate.cs b/Models/Components/NorGate.cs index 9440750..e8122c5 100644 --- a/Models/Components/NorGate.cs +++ b/Models/Components/NorGate.cs @@ -1,10 +1,10 @@ -using System.Linq; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System.Linq; -namespace IRis.Models.Components; +namespace IRis.Models.Components; public class NorGate : Gate @@ -19,7 +19,7 @@ public override void Draw(DrawingContext ctx) // 3. Draw terminals (input left, output right) DrawTerminals(ctx); - this.DrawOr(ctx); + DrawOr(ctx); // 3. Draw the bubble at the end ctx.DrawEllipse( @@ -32,7 +32,7 @@ public override void Draw(DrawingContext ctx) base.Draw(ctx); } - + public override void ComputeOutput() { // For inputs: check if ANY input terminal has at least one wire @@ -44,7 +44,7 @@ public override void ComputeOutput() if (!inputTerminals.All(t => t.Wires.Any()) || !outputTerminal.Wires.Any()) return; // For each input terminal, OR together all connected wire values, then OR all inputs - bool anyInputHigh = inputTerminals.Any(terminal => + bool anyInputHigh = inputTerminals.Any(terminal => terminal.Wires.Any(w => w.Value == LogicState.High)); // NOR logic: NOT(OR) - output is HIGH only when ALL inputs are LOW @@ -57,7 +57,6 @@ public override void ComputeOutput() } } } - - - \ No newline at end of file + + diff --git a/Models/Components/NotGate.cs b/Models/Components/NotGate.cs index 05d017e..96b7789 100644 --- a/Models/Components/NotGate.cs +++ b/Models/Components/NotGate.cs @@ -1,9 +1,9 @@ -using System.Linq; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System.Linq; -namespace IRis.Models.Components; +namespace IRis.Models.Components; public class NotGate : Gate diff --git a/Models/Components/OrGate.cs b/Models/Components/OrGate.cs index 4c87ad2..858a552 100644 --- a/Models/Components/OrGate.cs +++ b/Models/Components/OrGate.cs @@ -1,10 +1,9 @@ -using Avalonia; using Avalonia.Media; using IRis.Models.Core; using System.Linq; -namespace IRis.Models.Components; +namespace IRis.Models.Components; public class OrGate : Gate @@ -20,13 +19,13 @@ public override void Draw(DrawingContext ctx) // 3. Draw terminals (input left, output right) DrawTerminals(ctx); - this.DrawOr(ctx); + DrawOr(ctx); base.Draw(ctx); } - + public override void ComputeOutput() { // For inputs: check if ANY input terminal has at least one wire @@ -38,7 +37,7 @@ public override void ComputeOutput() if (!inputTerminals.All(t => t.Wires.Any()) || !outputTerminal.Wires.Any()) return; // For each input terminal, OR together all connected wire values, then OR all inputs - bool anyInputHigh = inputTerminals.Any(terminal => + bool anyInputHigh = inputTerminals.Any(terminal => terminal.Wires.Any(w => w.Value == LogicState.High)); // Set output on ALL connected wires @@ -48,15 +47,4 @@ public override void ComputeOutput() wire.Value = outputValue; } } - - - - // public override void UpdateOutputValue() - // { - // var values = Inputs.Select(input => input.Wire.Value).ToArray(); - // - // // Logic applies for any no. of Inputs - // if (values.Any(v => v == null)) Output.Wire.Value = null; - // else Output.Wire.Value = values.Any(v => v == true); - // } } \ No newline at end of file diff --git a/Models/Components/SRLatch.cs b/Models/Components/SRLatch.cs index 885f518..8cc4222 100644 --- a/Models/Components/SRLatch.cs +++ b/Models/Components/SRLatch.cs @@ -1,12 +1,14 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class SRLatch : Component, IOutputProvider +public class SRLatch( + double width = ComponentDefaults.DefaultMuxWidth, + double height = ComponentDefaults.DefaultMuxHeight) + : LatchBase(width, height), IOutputProvider { // Terminals layout (indexes): // 0: S (left, top) @@ -15,21 +17,6 @@ public class SRLatch : Component, IOutputProvider // 3: Q' (right, bottom) - public SRLatch(double width = ComponentDefaults.DefaultMuxWidth, - double height = ComponentDefaults.DefaultMuxHeight) - : base(width, height) - { - Width = 5 * ComponentDefaults.TerminalSpacing; - Height = 3 * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; - - Terminals = new Terminal[4]; - AddTerminalPoints(); - IsHitTestVisible = true; - - // Dictionary entry for state - StoredStates["Q"] = LogicState.Low; - } - public void ComputeOutput() { var s = Terminals![0].Wire!.Value; @@ -57,32 +44,6 @@ public void ComputeOutput() Terminals[3].Wire!.Value = StoredStates["Q"] == LogicState.High ? LogicState.Low : LogicState.High; } - public override void Draw(DrawingContext ctx) - { - ctx.DrawRectangle(ComponentDefaults.GateFillBrush, - ComponentDefaults.GatePen, - new Rect(0, 0, Width, Height)); - - DrawTerminalsAndLabels(ctx); - base.Draw(ctx); - } - - public override void DrawSelection(DrawingContext ctx) - { - double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; - double expandY = ComponentDefaults.TerminalRadius; - - ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, - new Rect( - -expandX, - -expandY, - Bounds.Width + 2 * expandX, - Bounds.Height + 2 * expandY) - ); - } - public override void AddTerminalPoints(bool notMode = false) { Point SnapToGrid(Point pt) @@ -99,17 +60,17 @@ Point SnapToGrid(Point pt) Terminals![1] = new Terminal(SnapToGrid(rPos), null!); // Outputs: Q (top-right), Q' (bottom-right) - var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); + var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); var nqPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 2); - var qSnap = SnapToGrid(qPos); + var qSnap = SnapToGrid(qPos); var nqSnap = SnapToGrid(nqPos); - Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); + Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); Terminals![3] = new Terminal(new Point(nqPos.X, nqSnap.Y), null!); } - private void DrawTerminalsAndLabels(DrawingContext ctx) + internal override void DrawTerminalsAndLabels(DrawingContext ctx) { // Inputs for (int i = 0; i < 2; i++) @@ -120,14 +81,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); string label = i == 0 ? "S" : "R"; - var text = new FormattedText( - label, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = label.CreateFormattedText(); + ctx.DrawText(text, new Point(4.5, Terminals[i].Position.Y - 6)); } @@ -140,14 +95,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) Terminals[j].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); string label = j == 2 ? "Q" : "Q'"; - var text = new FormattedText( - label, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = label.CreateFormattedText(); + ctx.DrawText(text, new Point(Width - 18, Terminals[j].Position.Y - 6)); } } diff --git a/Models/Components/TLatch.cs b/Models/Components/TLatch.cs index 61bb9bd..4544aff 100644 --- a/Models/Components/TLatch.cs +++ b/Models/Components/TLatch.cs @@ -1,12 +1,14 @@ -using System; -using System.Globalization; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System; namespace IRis.Models.Components; -public class TLatch : Component, IOutputProvider +public class TLatch( + double width = ComponentDefaults.DefaultMuxWidth * 5, + double height = ComponentDefaults.DefaultMuxHeight) + : LatchBase(width, height), IOutputProvider { // Terminals: // 0: T (left, top) @@ -15,24 +17,9 @@ public class TLatch : Component, IOutputProvider // 3: Q' (right, bottom) - public TLatch(double width = ComponentDefaults.DefaultMuxWidth * 5, - double height = ComponentDefaults.DefaultMuxHeight) - : base(width, height) - { - Width = 5 * ComponentDefaults.TerminalSpacing; - Height = 3 * ComponentDefaults.TerminalSpacing + ComponentDefaults.GridSpacing; - - Terminals = new Terminal[4]; - AddTerminalPoints(); - IsHitTestVisible = true; - - // Dictionary entry for state - StoredStates["Q"] = LogicState.Low; - } - public void ComputeOutput() { - var t = Terminals![0].Wire!.Value; + var t = Terminals![0].Wire!.Value; var en = Terminals![1].Wire!.Value; if (en == LogicState.High) @@ -50,32 +37,6 @@ public void ComputeOutput() Terminals[3].Wire!.Value = StoredStates["Q"] == LogicState.High ? LogicState.Low : LogicState.High; } - public override void Draw(DrawingContext ctx) - { - ctx.DrawRectangle(ComponentDefaults.GateFillBrush, - ComponentDefaults.GatePen, - new Rect(0, 0, Width, Height)); - - DrawTerminalsAndLabels(ctx); - base.Draw(ctx); - } - - public override void DrawSelection(DrawingContext ctx) - { - double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; - double expandY = ComponentDefaults.TerminalRadius; - - ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, - new Rect( - -expandX, - -expandY, - Bounds.Width + 2 * expandX, - Bounds.Height + 2 * expandY) - ); - } - public override void AddTerminalPoints(bool notMode = false) { Point SnapToGrid(Point pt) @@ -86,23 +47,23 @@ Point SnapToGrid(Point pt) } // Inputs: T (top-left), EN (bottom-left) - var tPos = new Point(-ComponentDefaults.TerminalWireLength, ComponentDefaults.TerminalSpacing * 1); + var tPos = new Point(-ComponentDefaults.TerminalWireLength, ComponentDefaults.TerminalSpacing * 1); var enPos = new Point(-ComponentDefaults.TerminalWireLength, ComponentDefaults.TerminalSpacing * 2); Terminals![0] = new Terminal(SnapToGrid(tPos), null!); Terminals![1] = new Terminal(SnapToGrid(enPos), null!); // Outputs: Q (top-right), Q' (bottom-right) - var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); + var qPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 1); var nqPos = new Point(Width + ComponentDefaults.TerminalWireLength - 5, ComponentDefaults.TerminalSpacing * 2); - var qSnap = SnapToGrid(qPos); + var qSnap = SnapToGrid(qPos); var nqSnap = SnapToGrid(nqPos); - Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); + Terminals![2] = new Terminal(new Point(qPos.X, qSnap.Y), null!); Terminals![3] = new Terminal(new Point(nqPos.X, nqSnap.Y), null!); } - private void DrawTerminalsAndLabels(DrawingContext ctx) + internal override void DrawTerminalsAndLabels(DrawingContext ctx) { // Inputs string[] inLabels = { "T", "EN" }; @@ -113,14 +74,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - inLabels[i], - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = inLabels[i].CreateFormattedText(); + ctx.DrawText(text, new Point(4.5, Terminals[i].Position.Y - 6)); } @@ -133,14 +88,8 @@ private void DrawTerminalsAndLabels(DrawingContext ctx) ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[j].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); - var text = new FormattedText( - outLabels[j - 2], - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - ComponentDefaults.LabelTypeface, - ComponentDefaults.LabelSize, - ComponentDefaults.LabelBrush - ); + var text = outLabels[j - 2].CreateFormattedText(); + ctx.DrawText(text, new Point(Width - 18, Terminals[j].Position.Y - 6)); } } diff --git a/Models/Components/Wire.cs b/Models/Components/Wire.cs index 4bb79b2..2180ad2 100644 --- a/Models/Components/Wire.cs +++ b/Models/Components/Wire.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; using Avalonia; -using Avalonia.Controls; using Avalonia.Media; -using Vector = Avalonia.Vector; using IRis.Models.Core; +using System; +using System.Collections.Generic; +using Vector = Avalonia.Vector; namespace IRis.Models.Components; @@ -14,13 +11,13 @@ namespace IRis.Models.Components; // THIS WORKS, THEY GET DRAWN public class Wire : Component, ICloneable { - - + + // To identify wires in serialization public Guid Id { get; set; } - - // private Component? _lastSetter = null; + + // private Component? _lastSetter = null; // This value is propagated to everything connected to this wire public bool IsCommitted { get; set; } = false; @@ -32,28 +29,28 @@ public class Wire : Component, ICloneable public List Points { get; set; } = new List(); - public Wire() : base(0, 0) + public Wire() { Id = Guid.NewGuid(); IsCommitted = false; } - - + + public void AddPoint(Point point) { Points.Add(point); // Reset the visuals - this.InvalidateVisual(); + InvalidateVisual(); } public void PopPoint() { - Points.RemoveAt(Points.Count - 1); // Removes last element - - // Reset the visuals - this.InvalidateVisual(); + Points.RemoveAt(Points.Count - 1); // Removes last element + + // Reset the visuals + InvalidateVisual(); } - + public bool IsPointOnWire(Point point, double tolerance) { // Check if point is close to any line segment of the wire @@ -69,23 +66,23 @@ private double DistanceToLineSegment(Point point, Point lineStart, Point lineEnd { double dx = lineEnd.X - lineStart.X; double dy = lineEnd.Y - lineStart.Y; - + // If line segment is actually a point if (dx == 0 && dy == 0) return Point.Distance(point, lineStart); - + // Calculate the parameter t for the closest point on the line double t = ((point.X - lineStart.X) * dx + (point.Y - lineStart.Y) * dy) / (dx * dx + dy * dy); - + // Clamp t to [0, 1] to stay within the line segment t = Math.Max(0, Math.Min(1, t)); - + // Find the closest point on the line segment Point closestPoint = new Point( lineStart.X + t * dx, lineStart.Y + t * dy ); - + return Point.Distance(point, closestPoint); } @@ -110,19 +107,19 @@ public override bool HitTest(Point point) private bool IsPointNearLineSegment(Point point, Point lineStart, Point lineEnd, double maxDistance) { // Vector from line start to end - + Vector lineVector = lineEnd - lineStart; double lineLengthSquared = lineVector.SquaredLength; - + // Project point onto the line segment Vector pointVector = point - lineStart; double t = Vector.Dot(pointVector, lineVector) / lineLengthSquared; t = Math.Max(0, Math.Min(1, t)); // Clamp to segment - + // Find nearest point on segment Point nearestPoint = lineStart + t * lineVector; - + // Check distance return (new Vector(point.X, point.Y) - nearestPoint).Length <= maxDistance; } @@ -135,7 +132,7 @@ public override void Draw(DrawingContext ctx) bool useGhostStyling = IsBeingEdited && !IsCommitted; var penToUse = useGhostStyling ? ComponentDefaults.GhostWirePen : ComponentDefaults.WirePen; if (!IsValid) penToUse = ComponentDefaults.InvalidWirePen; - + if (Points.Count == 1) { var brushToUse = useGhostStyling ? ComponentDefaults.GhostTerminalBrush : ComponentDefaults.TerminalBrush; @@ -164,7 +161,7 @@ public override void Draw(DrawingContext ctx) } continue; } - + // Start new figure or continue current one if (!figureStarted) { @@ -184,12 +181,12 @@ public override void Draw(DrawingContext ctx) } } ctx.DrawGeometry(null, penToUse, polyline); - + for (int i = 0; i < Points.Count; i++) { // Draw first point, last point, extension point if (i == 0 || Points[i - 1] == new Point(-1, -1) || - (i < Points.Count-1 && Points[i + 1] == new Point(-1, -1)) || + (i < Points.Count - 1 && Points[i + 1] == new Point(-1, -1)) || i == Points.Count - 1) { var brushToUse = useGhostStyling ? ComponentDefaults.GhostTerminalBrush : ComponentDefaults.TerminalBrush; @@ -199,7 +196,7 @@ public override void Draw(DrawingContext ctx) } } } - + public override void DrawSelection(DrawingContext ctx) { if (Points.Count < 2) return; @@ -217,11 +214,11 @@ public override void DrawSelection(DrawingContext ctx) { Point start = Points[i]; Point end = Points[i + 1]; - + // Skip if either point is a break point if (start == new Point(-1, -1) || end == new Point(-1, -1)) continue; - + // Calculate segment vector and perpendicular Vector segment = end - start; Vector normal = new Vector(-segment.Y, segment.X); @@ -246,7 +243,7 @@ public override void DrawSelection(DrawingContext ctx) { if (point == new Point(-1, -1)) continue; - + ctx.DrawEllipse( ComponentDefaults.SelectionBrush, ComponentDefaults.SelectionPen, @@ -260,13 +257,13 @@ public override void DrawSelection(DrawingContext ctx) public override object Clone() { var clone = new Wire(); - clone.Value = this.Value; - clone.Id = this.Id; + clone.Value = Value; + clone.Id = Id; for (int i = 0; i < Points.Count; i++) { clone.AddPoint(Points[i]); } - + // // Copy source and sinks by value (Terminal is a struct) // clone.source = source; // clone.sinks = new List(sinks); diff --git a/Models/Components/XnorGate.cs b/Models/Components/XnorGate.cs index f2c1ea0..f7b477a 100644 --- a/Models/Components/XnorGate.cs +++ b/Models/Components/XnorGate.cs @@ -1,10 +1,10 @@ -using System.Linq; using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System.Linq; -namespace IRis.Models.Components; +namespace IRis.Models.Components; public class XnorGate : Gate @@ -20,7 +20,7 @@ public override void Draw(DrawingContext ctx) // 3. Draw terminals (input left, output right) DrawTerminals(ctx); - this.DrawOr(ctx, true); + DrawOr(ctx, true); // 3. Draw the bubble at the end ctx.DrawEllipse( @@ -31,9 +31,9 @@ public override void Draw(DrawingContext ctx) ComponentDefaults.BubbleRadius); base.Draw(ctx); - + } - + public override void ComputeOutput() { // For inputs: check if ANY input terminal has at least one wire @@ -45,11 +45,11 @@ public override void ComputeOutput() if (!inputTerminals.All(t => t.Wires.Any()) || !outputTerminal.Wires.Any()) return; // For each input terminal, OR together all connected wire values - var inputValues = inputTerminals.Select(terminal => + var inputValues = inputTerminals.Select(terminal => terminal.Wires.Any(w => w.Value == LogicState.High)).ToList(); // XNOR logic: output is HIGH when an even number of inputs are HIGH (inverted XOR) - int highInputCount = inputValues.Count(value => value == true); + int highInputCount = inputValues.Count(value => value); LogicState outputValue = (highInputCount % 2 != 0) ? LogicState.Low : LogicState.High; // Set output on ALL connected wires diff --git a/Models/Components/XorGate.cs b/Models/Components/XorGate.cs index d4401b2..9e860fc 100644 --- a/Models/Components/XorGate.cs +++ b/Models/Components/XorGate.cs @@ -1,10 +1,9 @@ -using System.Linq; -using Avalonia; using Avalonia.Media; using IRis.Models.Core; +using System.Linq; -namespace IRis.Models.Components; +namespace IRis.Models.Components; public class XorGate : Gate @@ -20,12 +19,12 @@ public override void Draw(DrawingContext ctx) // 3. Draw terminals (input left, output right) DrawTerminals(ctx); - this.DrawOr(ctx, true); + DrawOr(ctx, true); base.Draw(ctx); } - + public override void ComputeOutput() { // For inputs: check if ANY input terminal has at least one wire @@ -37,11 +36,11 @@ public override void ComputeOutput() if (!inputTerminals.All(t => t.Wires.Any()) || !outputTerminal.Wires.Any()) return; // For each input terminal, OR together all connected wire values - var inputValues = inputTerminals.Select(terminal => + var inputValues = inputTerminals.Select(terminal => terminal.Wires.Any(w => w.Value == LogicState.High)).ToList(); // XOR logic: output is HIGH when an odd number of inputs are HIGH - int highInputCount = inputValues.Count(value => value == true); + int highInputCount = inputValues.Count(value => value); LogicState outputValue = (highInputCount % 2 != 0) ? LogicState.High : LogicState.Low; // Set output on ALL connected wires @@ -50,7 +49,7 @@ public override void ComputeOutput() wire.Value = outputValue; } } - - + + } \ No newline at end of file diff --git a/Models/Core/Component.cs b/Models/Core/Component.cs index d84774d..f7cd8ec 100644 --- a/Models/Core/Component.cs +++ b/Models/Core/Component.cs @@ -1,37 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Media; using Avalonia.Rendering; using IRis.Models.Components; +using System; +using System.Collections.Generic; namespace IRis.Models.Core; -public abstract class Component : Control, ICustomHitTest +public abstract class CircuitComponent : Component { - - private bool _isSelected = false; private double _rotation = 0; - protected RotateTransform RotateTransform; - - // Last one is output - public Terminal[]? Terminals = null; - // Most components won't use all 3 public int InputLineCount { get; protected set; } = 0; public int SelectionLineCount { get; protected set; } = 0; public int OutputLineCount { get; protected set; } = 0; - + // Used by Latches/Flip-Flops public Dictionary StoredStates { get; set; } = new(); - - - public double Rotation { @@ -39,31 +26,47 @@ public double Rotation set { _rotation = value; - RotateTransform = new RotateTransform(value, Width/2, Height/2); + RotateTransform = new RotateTransform(value, Width / 2, Height / 2); InvalidateVisual(); } } + public CircuitComponent(double width, double height) + { + Width = width; + Height = height; + + RotateTransform = new RotateTransform(_rotation, Width, Height); + } + + public virtual void AddTerminalPoints(bool notMode = false) + { + + } +} + + +public abstract class Component : Control, ICustomHitTest +{ + // Last one is output + public Terminal[]? Terminals = null; + protected RotateTransform RotateTransform; + private bool _isSelected = false; + + protected Component() + { + RotateTransform = new RotateTransform(0, 0, 0); + } + public bool IsSelected { get => _isSelected; set { _isSelected = value; - InvalidateVisual(); + InvalidateVisual(); } } - public Component(double width , double height) - { - Width = width; - Height = height; - - RotateTransform = new RotateTransform(_rotation, Width, Height); - - //Rotation = 100; - - - } // Implement ICloneable for copies // Override for child classes @@ -74,15 +77,10 @@ public virtual object Clone() // Override for wires public virtual bool HitTest(Point point) - { + { point = RotateTransform.Value.Transform(point); - - return new Rect(0,0,Width,Height).Contains(point); - } - public virtual void AddTerminalPoints(bool notMode = false) - { - + return new Rect(0, 0, Width, Height).Contains(point); } public override void Render(DrawingContext context) @@ -108,22 +106,27 @@ public override void Render(DrawingContext context) DrawSelection(context); } + base.Render(context); } } + // Can be overriden for custom implementations public virtual void Draw(DrawingContext ctx) { - + } public virtual void DrawSelection(DrawingContext ctx) { - + } - +} +public static class ComponentFactory +{ + // A method for making components by type - public static Component Create(string componentType, Simulation simulation, int numInputs=2) + public static Component Create(string componentType, Simulation simulation, int numInputs = 2) { switch (componentType) { @@ -167,13 +170,9 @@ public static Component Create(string componentType, Simulation simulation, int simulation.CustomComponent.OutputCount, simulation.CustomComponent.Formulas); case "WIRE": return new Wire(); - + default: - return null!; // TODO: DANGEROUS, THIS IS A FUCKING NULLPO WAITING TO HAPPEN + throw new ArgumentException($"Unknown component type: {componentType}"); } } - - - - } diff --git a/Models/Core/ComponentDefaults.cs b/Models/Core/ComponentDefaults.cs index 10546e7..a51d2f9 100644 --- a/Models/Core/ComponentDefaults.cs +++ b/Models/Core/ComponentDefaults.cs @@ -1,4 +1,3 @@ -using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.Media.Immutable; @@ -28,12 +27,12 @@ public static class ComponentDefaults // Selection public static Pen SelectionPen = new Pen(Brushes.DodgerBlue, 2); public static SolidColorBrush SelectionBrush = new SolidColorBrush(Colors.DodgerBlue, 0.2); - + // For probes/toggles public static IImmutableSolidColorBrush TrueBrush = Brushes.ForestGreen; public static IImmutableSolidColorBrush FalseBrush = Brushes.DarkRed; public static IImmutableSolidColorBrush DontCareBrush = Brushes.Gray; - + // For the grid public const double GridSpacing = 10; // pixels between grid lines (non-decimals only) public static IBrush GridBrush = new SolidColorBrush(Colors.Black, 0.3); @@ -47,14 +46,14 @@ public static class ComponentDefaults // For labels public static IBrush LabelBrush = new ImmutableSolidColorBrush(Color.FromRgb(40, 40, 40)); public static double LabelSize = 12; - public static Typeface LabelTypeface = new Typeface(fontFamily: "Source Code Pro", weight: FontWeight.SemiBold); + public static Typeface LabelTypeface = new Typeface(fontFamily: "Source Code Pro", weight: FontWeight.SemiBold); // For NOT-derived Gates public static double BubbleRadius = DefaultWidth / 15; // Higher makes the arc on OR gates steeper public static double OrArcFactor = 6; - + // High brings the 2nd arc closer to the main arc on the gate public static double XorArcDistFactor = 3; diff --git a/Models/Core/Gate.cs b/Models/Core/Gate.cs index f1458e4..2c82371 100644 --- a/Models/Core/Gate.cs +++ b/Models/Core/Gate.cs @@ -1,70 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; using Avalonia; -using Avalonia.Input; using Avalonia.Media; using IRis.Models.Components; -using Avalonia.Threading; -using System.Linq; +using System; namespace IRis.Models.Core; // Contains the position and truth value of a connection point -public class Terminal -{ - public Point Position { get; } - - // Change from single Wire to List of Wires - public List Wires { get; set; } = new List(); - - // Keep this for backward compatibility if needed - public Wire? Wire - { - get => Wires.FirstOrDefault(); - set - { - if (value != null && !Wires.Contains(value)) - { - Wires.Add(value); - } - } - } - - public Terminal(Point position) - { - Position = position; - } - - public Terminal(Point position, Wire wire) : this(position) - { - if (wire != null) - Wires.Add(wire); - } - - // Add a wire to this terminal - public void AddWire(Wire wire) - { - if (wire != null && !Wires.Contains(wire)) - { - Wires.Add(wire); - } - } - - // Remove a wire from this terminal - public void RemoveWire(Wire wire) - { - Wires.Remove(wire); - } - - -} // Has some gate specific things -public abstract class Gate : Component, IOutputProvider +public abstract class Gate : CircuitComponent, IOutputProvider { - + // Uses default values if none are given public Gate(int numInputs, double width = ComponentDefaults.DefaultWidth, @@ -85,41 +32,41 @@ public Gate(int numInputs, double width = ComponentDefaults.DefaultWidth, } public abstract void ComputeOutput(); - - + + // Implement ICloneable for copies public override object Clone() { // Create new instance based on concrete type Gate clone = this switch { - AndGate _ => new AndGate(this.InputLineCount), - OrGate _ => new OrGate(this.InputLineCount), + AndGate _ => new AndGate(InputLineCount), + OrGate _ => new OrGate(InputLineCount), NotGate _ => new NotGate(), // Special constructor - NandGate _ => new NandGate(this.InputLineCount), - NorGate _ => new NorGate(this.InputLineCount), - XorGate _ => new XorGate(this.InputLineCount), - XnorGate _ => new XnorGate(this.InputLineCount), - _ => throw new NotSupportedException($"Unsupported gate type: {this.GetType().Name}") + NandGate _ => new NandGate(InputLineCount), + NorGate _ => new NorGate(InputLineCount), + XorGate _ => new XorGate(InputLineCount), + XnorGate _ => new XnorGate(InputLineCount), + _ => throw new NotSupportedException($"Unsupported gate type: {GetType().Name}") }; // Copy all base properties - clone.Width = this.Width; - clone.Height = this.Height; - clone.Rotation = this.Rotation; - clone.IsSelected = this.IsSelected; + clone.Width = Width; + clone.Height = Height; + clone.Rotation = Rotation; + clone.IsSelected = IsSelected; // Copy terminal values (positions are set in constructor) - for (int i = 0; i < this.InputLineCount; i++) + for (int i = 0; i < InputLineCount; i++) { clone.Terminals![i] = new Terminal( clone.Terminals[i].Position, // Use new position - this.Terminals![i].Wire! // Copy original value + Terminals![i].Wire! // Copy original value ); } clone.Terminals![^1] = new Terminal( clone.Terminals[^1].Position, - this.Terminals![^1].Wire! + Terminals![^1].Wire! ); // Reset visual state @@ -132,16 +79,16 @@ public override object Clone() // Draws a translucent box around the gate public override void DrawSelection(DrawingContext ctx) { - double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius ; - double expandY = ComponentDefaults.TerminalRadius ; + double expandX = ComponentDefaults.TerminalWireLength + ComponentDefaults.TerminalRadius; + double expandY = ComponentDefaults.TerminalRadius; // Subtle fill ctx.DrawRectangle( - ComponentDefaults.SelectionBrush, - ComponentDefaults.SelectionPen, + ComponentDefaults.SelectionBrush, + ComponentDefaults.SelectionPen, new Rect( -expandX, -expandY, - Bounds.Width + 2 * expandX , + Bounds.Width + 2 * expandX, Bounds.Height + 2 * expandY) ); } @@ -168,7 +115,7 @@ Point SnapToGrid(Point pt) } // Output terminal - double outputX = Width + ComponentDefaults.TerminalWireLength -5; // WARNING: this is a Patch + double outputX = Width + ComponentDefaults.TerminalWireLength - 5; // WARNING: this is a Patch if (notMode) outputX += ComponentDefaults.BubbleRadius * 2; var outputPos = SnapToGrid(new Point(outputX, Height / 2)); @@ -180,26 +127,26 @@ Point SnapToGrid(Point pt) // notMode: a bubble drawn with the output terminal protected void DrawTerminals(DrawingContext ctx) { - + // Input lines extend into the gate and covered up by the fill color for (int i = 0; i < InputLineCount; i++) { if (Terminals![i] == null) continue; ctx.DrawLine(ComponentDefaults.WirePen, Terminals[i].Position, new Point(Width / ComponentDefaults.OrArcFactor, Terminals[i].Position.Y)); - ctx.DrawEllipse(ComponentDefaults.TerminalBrush , null, + ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[i].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); } // For Terminals[^1] // SUSPEND: I STOPPED HERE - + ctx.DrawLine(ComponentDefaults.WirePen, Terminals![^1].Position, new Point(Terminals[^1].Position.X - ComponentDefaults.TerminalWireLength, Terminals[^1].Position.Y)); - ctx.DrawEllipse(ComponentDefaults.TerminalBrush , null, + ctx.DrawEllipse(ComponentDefaults.TerminalBrush, null, Terminals[^1].Position, ComponentDefaults.TerminalRadius, ComponentDefaults.TerminalRadius); } - + // Methods for drawing the body of AND and OR // xorMode: Adds an extra arc protected void DrawOr(DrawingContext ctx, bool xorMode = false) @@ -216,7 +163,7 @@ protected void DrawOr(DrawingContext ctx, bool xorMode = false) figure.Segments!.Add(new ArcSegment { Point = new Point(0, Height), - Size = new Size(Width / ComponentDefaults.OrArcFactor, Height /2), + Size = new Size(Width / ComponentDefaults.OrArcFactor, Height / 2), SweepDirection = SweepDirection.Clockwise, IsLargeArc = false }); @@ -225,7 +172,7 @@ protected void DrawOr(DrawingContext ctx, bool xorMode = false) figure.Segments.Add(new ArcSegment { Point = new Point(Width, Height * 0.5), - Size = new Size(Width , Height /2), + Size = new Size(Width, Height / 2), SweepDirection = SweepDirection.CounterClockwise, IsLargeArc = false }); @@ -234,16 +181,16 @@ protected void DrawOr(DrawingContext ctx, bool xorMode = false) figure.Segments.Add(new ArcSegment { Point = new Point(0, 0), - Size = new Size(Width , Height /2), + Size = new Size(Width, Height / 2), SweepDirection = SweepDirection.CounterClockwise, IsLargeArc = false }); gatePath.Figures!.Add(figure); - + // 2. Draw the complete gate - ctx.DrawGeometry(ComponentDefaults.GateFillBrush, ComponentDefaults.GatePen, gatePath); - + ctx.DrawGeometry(ComponentDefaults.GateFillBrush, ComponentDefaults.GatePen, gatePath); + // Draw the XOR arc, if in xorMode if (xorMode) { @@ -252,19 +199,19 @@ protected void DrawOr(DrawingContext ctx, bool xorMode = false) { StartPoint = new Point(-ComponentDefaults.TerminalWireLength / ComponentDefaults.XorArcDistFactor, Height * 0.02), IsClosed = false - + }; arcFigure.Segments!.Add(new ArcSegment { Point = new Point(-ComponentDefaults.TerminalWireLength / ComponentDefaults.XorArcDistFactor, Height * 0.98), - Size = new Size(Width / ComponentDefaults.OrArcFactor, Height /2), + Size = new Size(Width / ComponentDefaults.OrArcFactor, Height / 2), SweepDirection = SweepDirection.Clockwise, IsLargeArc = false }); - + xorArc.Figures!.Add(arcFigure); - + ctx.DrawGeometry(null, ComponentDefaults.GatePen, xorArc); } } @@ -301,7 +248,7 @@ protected void DrawAnd(DrawingContext ctx) figure.Segments.Add(new LineSegment { Point = new Point(0, 0) }); gatePath.Figures!.Add(figure); - + // 2. Draw the complete gate ctx.DrawGeometry(ComponentDefaults.GateFillBrush, ComponentDefaults.GatePen, gatePath); diff --git a/Models/Core/IOutputProvider.cs b/Models/Core/IOutputProvider.cs index 750aa03..2c1c68e 100644 --- a/Models/Core/IOutputProvider.cs +++ b/Models/Core/IOutputProvider.cs @@ -1,12 +1,5 @@ namespace IRis.Models.Core; -public enum LogicState -{ - High, - Low, - DontCare, -} - interface IOutputProvider { public void ComputeOutput(); diff --git a/Models/Core/LogicState.cs b/Models/Core/LogicState.cs new file mode 100644 index 0000000..de540a7 --- /dev/null +++ b/Models/Core/LogicState.cs @@ -0,0 +1,8 @@ +namespace IRis.Models.Core; + +public enum LogicState +{ + High, + Low, + DontCare, +} \ No newline at end of file diff --git a/Models/Core/Terminal.cs b/Models/Core/Terminal.cs new file mode 100644 index 0000000..fad278e --- /dev/null +++ b/Models/Core/Terminal.cs @@ -0,0 +1,55 @@ +using Avalonia; +using IRis.Models.Components; +using System.Collections.Generic; +using System.Linq; + +namespace IRis.Models.Core; + +public class Terminal +{ + public Point Position { get; } + + // Change from single Wire to List of Wires + public List Wires { get; set; } = new List(); + + // Keep this for backward compatibility if needed + public Wire? Wire + { + get => Wires.FirstOrDefault(); + set + { + if (value != null && !Wires.Contains(value)) + { + Wires.Add(value); + } + } + } + + public Terminal(Point position) + { + Position = position; + } + + public Terminal(Point position, Wire wire) : this(position) + { + if (wire != null) + Wires.Add(wire); + } + + // Add a wire to this terminal + public void AddWire(Wire wire) + { + if (wire != null && !Wires.Contains(wire)) + { + Wires.Add(wire); + } + } + + // Remove a wire from this terminal + public void RemoveWire(Wire wire) + { + Wires.Remove(wire); + } + + +} \ No newline at end of file diff --git a/Models/DTOs/CircuitDto.cs b/Models/DTOs/CircuitDto.cs index 46973ad..eaef63c 100644 --- a/Models/DTOs/CircuitDto.cs +++ b/Models/DTOs/CircuitDto.cs @@ -1,36 +1,35 @@ +using IRis.Models.Components; +using IRis.Models.Core; using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Net; -using IRis.Models.Components; -using IRis.Models.Core; namespace IRis.Models.DTOs; public class CircuitDto { - public List Components {get; set;} - public List Wires {get; set;} + public List Components { get; set; } + public List Wires { get; set; } public static List ToCircuit(CircuitDto circuit) { List wires = circuit.Wires .Select(p => WireDto.ToWire(p)) .ToList(); - + List components = circuit.Components - .Select(p => ComponentDto.ToComponent(p)) + .Select(ComponentDto.ToComponent) .Where(p => p != null) + .Select(c => c as Component) .ToList(); - + // Make a dictionary of wires for fast lookups Dictionary wireDict = new Dictionary(); foreach (var wire in wires) { wireDict.Add(wire.Id, wire); } - + // Assign wire references to terminals based on ID foreach (var c in components) { @@ -38,11 +37,11 @@ public static List ToCircuit(CircuitDto circuit) { terminal.Wires = terminal.Wires.Select(w => wireDict[w.Id]).ToList(); } - + } - - - + + + // Pool them together components.AddRange(wires); return components; diff --git a/Models/DTOs/ComponentDto.cs b/Models/DTOs/ComponentDto.cs index c5cb3ae..6817680 100644 --- a/Models/DTOs/ComponentDto.cs +++ b/Models/DTOs/ComponentDto.cs @@ -1,95 +1,94 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Avalonia; using Avalonia.Controls; using IRis.Models.Components; using IRis.Models.Core; +using System.Collections.Generic; +using System.Linq; namespace IRis.Models.DTOs; + public class ComponentDto { public string Type { get; set; } public List Terminals { get; set; } - + public int InputLineCount { get; set; } public int SelectionLineCount { get; set; } - + public double X { get; set; } public double Y { get; set; } - + public Dictionary StoredStates { get; set; } - + public double Rotation { get; set; } public bool IsSelected { get; set; } - - public static ComponentDto ToDto(Component c) + + public static ComponentDto ToDto(CircuitComponent c) { return new ComponentDto() { // Get only the type name, without the full namespace Type = c.ToString().Split(".")[^1], - + InputLineCount = c.InputLineCount, SelectionLineCount = c.SelectionLineCount, Rotation = c.Rotation, IsSelected = c.IsSelected, - + X = Canvas.GetLeft(c), Y = Canvas.GetTop(c), - + StoredStates = c.StoredStates, Terminals = c.Terminals.Select(p => TerminalDto.ToDto(p)).ToList(), - + }; } - - public static Component? ToComponent(ComponentDto dto) + + public static CircuitComponent? ToComponent(ComponentDto dto) { // Pattern matching to ensure that the relevant constructor is always called - Component? result = dto.Type switch - { + CircuitComponent? result = dto.Type switch + { "AndGate" => new AndGate(dto.InputLineCount), "OrGate" => new OrGate(dto.InputLineCount), - "NotGate" => new NotGate(), + "NotGate" => new NotGate(), "XorGate" => new XorGate(dto.InputLineCount), "NandGate" => new NandGate(dto.InputLineCount), "NorGate" => new NorGate(dto.InputLineCount), "XnorGate" => new XnorGate(dto.InputLineCount), - + "LogicToggle" => new LogicToggle(), "LogicProbe" => new LogicProbe(), - + "Multiplexer" => new Multiplexer(dto.SelectionLineCount), "Demultiplexer" => new Demultiplexer(dto.SelectionLineCount), - + "Encoder" => new Encoder(dto.SelectionLineCount), "Decoder" => new Decoder(dto.SelectionLineCount), - + "DLatch" => new DLatch(), "JKLatch" => new JKLatch(), "SRLatch" => new SRLatch(), "TLatch" => new TLatch(), - + _ => null, }; - if(result == null) return null; - + if (result == null) return null; + result.Rotation = dto.Rotation; result.IsSelected = dto.IsSelected; result.StoredStates = dto.StoredStates; - + // Add terminal positions here by calling the function that generates them result.AddTerminalPoints(); for (int i = 0; i < dto.Terminals.Count; i++) { result.Terminals[i].Wires = TerminalDto.ToTerminal(dto.Terminals[i]).Wires; } - + Canvas.SetLeft(result, dto.X); Canvas.SetTop(result, dto.Y); - + return result; } diff --git a/Models/DTOs/TerminalDto.cs b/Models/DTOs/TerminalDto.cs index 6a8a297..3e3e074 100644 --- a/Models/DTOs/TerminalDto.cs +++ b/Models/DTOs/TerminalDto.cs @@ -1,15 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Avalonia; using IRis.Models.Components; using IRis.Models.Core; +using System; +using System.Collections.Generic; +using System.Linq; namespace IRis.Models.DTOs; public class TerminalDto { - + public List ConnectedWireIds { get; set; } public static TerminalDto ToDto(Terminal t) @@ -25,9 +25,9 @@ public static TerminalDto ToDto(Terminal t) public static Terminal ToTerminal(TerminalDto dto) { // Placeholder position - return new Terminal(new Point(1,1)) + return new Terminal(new Point(1, 1)) { - Wires = dto.ConnectedWireIds.Select(p => new Wire(){Id = p}).ToList() + Wires = dto.ConnectedWireIds.Select(p => new Wire() { Id = p }).ToList() }; } } \ No newline at end of file diff --git a/Models/DTOs/WireDto.cs b/Models/DTOs/WireDto.cs index c01e5b9..6953228 100644 --- a/Models/DTOs/WireDto.cs +++ b/Models/DTOs/WireDto.cs @@ -1,25 +1,25 @@ -using System; -using System.Collections.Generic; using Avalonia; using IRis.Models.Components; using IRis.Models.Core; +using System; +using System.Collections.Generic; namespace IRis.Models.DTOs; public class WireDto { public Guid Id { get; set; } - + public LogicState? Value { get; set; } public List Points { get; set; } - + public static WireDto ToDto(Wire w) { return new WireDto() { Id = w.Id, - + Value = w.Value, Points = w.Points }; @@ -32,7 +32,7 @@ public static Wire ToWire(WireDto dto) Id = dto.Id, Value = dto.Value, Points = dto.Points, - + IsCommitted = true, IsBeingEdited = false, IsValid = true, diff --git a/Models/KeyGestureConfig.cs b/Models/KeyGestureConfig.cs index 9224432..0a6d05d 100644 --- a/Models/KeyGestureConfig.cs +++ b/Models/KeyGestureConfig.cs @@ -1,9 +1,8 @@ +using Avalonia.Input; +using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using Avalonia.Input; -using Newtonsoft.Json; namespace IRis.Models; @@ -29,11 +28,11 @@ public KeyGestureConfig() Entries["Undo"] = new KeyGesture(Key.Z, KeyModifiers.Control); Entries["Redo"] = new KeyGesture(Key.Y, KeyModifiers.Control); - + Entries["Delete"] = new KeyGesture(Key.Delete); Entries["Unselect"] = new KeyGesture(Key.Escape); - + Entries["RotateClockwise"] = new KeyGesture(Key.A); Entries["RotateCounterClockwise"] = new KeyGesture(Key.D); @@ -51,10 +50,10 @@ public static void SaveKeyGestureConfig(KeyGestureConfig config) Directory.CreateDirectory(directory); } // SERIALIZATION - string json = JsonConvert.SerializeObject (config, Formatting.Indented); + string json = JsonConvert.SerializeObject(config, Formatting.Indented); json = json.Replace("KeyModifiers", "Modifiers"); // I hate this but it has to be this way File.WriteAllText(ConfigPath, json); - + Console.WriteLine("Saved KeyConfig to: " + ConfigPath); } catch (Exception ex) diff --git a/Models/PreviewManager.cs b/Models/PreviewManager.cs index 6d40b74..72ad2f7 100644 --- a/Models/PreviewManager.cs +++ b/Models/PreviewManager.cs @@ -1,12 +1,12 @@ // Models/PreviewManager.cs -using System; -using System.Collections.Generic; using Avalonia; using Avalonia.Controls; using Avalonia.Input; +using IRis.Models.Commands; using IRis.Models.Components; using IRis.Models.Core; -using IRis.Models.Commands; +using System; +using System.Collections.Generic; namespace IRis.Models; @@ -19,7 +19,7 @@ public class PreviewManager { get => _previewCompType; set => _previewCompType = value; - } + } public Component? PreviewComponent { get => _previewComponent; @@ -40,7 +40,7 @@ public void SetPreviewComponent(string? value, Canvas canvas, Point mousePos, Si // Create and add new component if value is provided if (!string.IsNullOrEmpty(value)) { - _previewComponent = Component.Create(value, simulation); + _previewComponent = ComponentFactory.Create(value, simulation); if (_previewComponent != null) { PositionPreviewComponent(mousePos); @@ -75,7 +75,7 @@ private void PositionPreviewComponent(Point mousePos) public void HandleWireCommit(object? sender, PointerPressedEventArgs? e, Point CurrentMousePos, Simulation simulation) { // Returns true if event is handled // Explicit type conversion to avoid exception - if (e == null) return; + if (e == null) return; Wire wirePreview = (Wire)_previewComponent!; if (!wirePreview.IsValid) return; @@ -97,7 +97,7 @@ public void HandleWireCommit(object? sender, PointerPressedEventArgs? e, Point C var addPointCommand2 = new AddWirePointCommand(wirePreview, pointToAdd); simulation.CommandManager.ExecuteCommand(addPointCommand2); - + // Commits the WIRE ON DOUBLE-CLICK, or RIGHT-CLICK if (wirePreview.Points.Count >= 2 && ((target != null && target != startingTerminal) || e.ClickCount == 2)) @@ -121,14 +121,16 @@ public void HandleWireCommit(object? sender, PointerPressedEventArgs? e, Point C public bool HandleComponentCommit(Canvas canvas, List components, Point mousePos, CommandManager commandManager, Simulation simulation) { if (string.IsNullOrEmpty(_previewCompType)) return true; - Component? component = Component.Create(_previewCompType, simulation); - if (component == null) return true; + var component = ComponentFactory.Create(_previewCompType, simulation); if (_previewComponent != null) { - component.Rotation = _previewComponent.Rotation; + if (component is CircuitComponent circuitComponent && _previewComponent is CircuitComponent previewComponent) + { + circuitComponent.Rotation = previewComponent.Rotation; + } } - + Point position = new Point( Math.Round(mousePos.X / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing, Math.Round(mousePos.Y / ComponentDefaults.GridSpacing) * ComponentDefaults.GridSpacing @@ -276,7 +278,7 @@ private bool HandleWireUpdate(Wire wirePreview, Point mousePos, bool snapToGridE wirePreview.Points[^1] = targetPoint; } // foreach (Point point in wirePreview.Points) Console.WriteLine(point); - + wirePreview.InvalidateVisual(); return true; } @@ -288,7 +290,7 @@ Point SnapToGrid(Point pt) return new Point(snapX, snapY); } - public void StartWireExtension(Canvas canvas, Point clickPoint, Wire existingWire, Simulation simulation) + public void StartWireExtension(Canvas canvas, Point clickPoint, Wire existingWire, Simulation simulation) { // if (_previewComponent is Wire wire) // { @@ -300,7 +302,7 @@ public void StartWireExtension(Canvas canvas, Point clickPoint, Wire existingWir canvas.Children.Remove(_previewComponent!); _previewComponent = existingWire; Wire wirePreview = (Wire)_previewComponent!; - + // Add a break point wirePreview.Points.Add(new Point(-1, -1)); // Get the closest point on the line segment instead of using click point diff --git a/Models/Simulation/Simulation.Selection.cs b/Models/Simulation/Simulation.Selection.cs index 11cd8e1..519265f 100644 --- a/Models/Simulation/Simulation.Selection.cs +++ b/Models/Simulation/Simulation.Selection.cs @@ -1,14 +1,14 @@ // 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.Commands; using IRis.Models.Components; using IRis.Models.Core; -using IRis.Models.Commands; +using System; +using System.Collections.Generic; +using System.Linq; namespace IRis.Models; @@ -376,9 +376,9 @@ private List Selection_FindTerminalsConnectedToWire(Wire wire) private Point Selection_GetTerminalWorldPosition(Terminal terminal) { - Component? owner = null; + CircuitComponent? owner = null; - foreach (var component in _components) + foreach (var component in _components.OfType()) { if (component.Terminals?.Contains(terminal) == true) { diff --git a/Models/Simulation/Simulation.Wires.cs b/Models/Simulation/Simulation.Wires.cs index 9b369de..6b64221 100644 --- a/Models/Simulation/Simulation.Wires.cs +++ b/Models/Simulation/Simulation.Wires.cs @@ -1,12 +1,12 @@ // 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; +using System; +using System.Collections.Generic; +using System.Linq; namespace IRis.Models; @@ -65,11 +65,11 @@ public Point GetAbsoluteTerminalPosition(Terminal terminal) 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) { @@ -102,7 +102,7 @@ public bool IsInputTerminal(Terminal terminal) } } } - + return false; } @@ -114,10 +114,10 @@ public bool IsInputTerminal(Terminal terminal) private bool IsPointInsideAnyComponent(Point point) { - return Components.Any(component => + return Components + .OfType() + .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 @@ -188,7 +188,7 @@ public bool DoesWireOverlapAnotherWire(List points) return validPoints.Any(existingWirePoints.Contains); } - + public bool IsWireSupersetOfAnotherWire(List wirePoints) { var wirePointsSet = wirePoints.Where(p => p.X != -1 && p.Y != -1).ToHashSet(); @@ -231,7 +231,7 @@ public bool DoesWireSelfOverlap(List points) return false; } - public bool DoesWireCrossTerminal(List points, Terminal? exceptionCase=null) + 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))) @@ -300,7 +300,7 @@ public static bool DoesWireHaveExtension(Wire wire) } return false; } - + // private bool IsWireInMovedWires(Wire wire, List MovedWires) // { // foreach (var movedWire in MovedWires) diff --git a/Models/Simulation/Simulation.cs b/Models/Simulation/Simulation.cs index 252a556..3f3f89a 100644 --- a/Models/Simulation/Simulation.cs +++ b/Models/Simulation/Simulation.cs @@ -1,15 +1,15 @@ // Models/Simulation.cs -using System; -using System.Collections.Generic; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using IRis.Models.Commands; using IRis.Models.Components; using IRis.Models.Core; -using IRis.Models.Commands; using IRis.Views; +using System; +using System.Collections.Generic; namespace IRis.Models; @@ -110,7 +110,7 @@ public Simulation() _clipboardManager = new ClipboardManager(); _gridManager = new GridManager(); } - + public bool Simulating { get => _simulating; @@ -165,7 +165,7 @@ private void SetupSimulation() { // 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 = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; _updateTimer.Tick += (s, e) => SimulationStep(); Simulating = false; } @@ -227,71 +227,71 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) return; } if (_previewManager.PreviewCompType != "NULL") // If a component is being previewed + { + if (_previewManager.PreviewCompType == "WIRE") // If component is wire { - if (_previewManager.PreviewCompType == "WIRE") // If component is wire + if (FindWireAtPosition(CurrentMousePos) != null) // if Created on an already present wire { - if (FindWireAtPosition(CurrentMousePos) != null) // if Created on an already present wire + Terminal? terminal = FindClosestSnapTerminal(CurrentMousePos); + if (terminal != null && !IsInputTerminal(terminal)) // if no terminal is present { - Terminal? terminal = FindClosestSnapTerminal(CurrentMousePos); - if (terminal != null && !IsInputTerminal(terminal)) // if no terminal is present - { - Console.WriteLine("Registering new wire ()..."); - _previewManager.HandleWireCommit(sender, e, CurrentMousePos, this); - return; - } - else if (terminal != null && IsInputTerminal(terminal)) // If wire & terminal are present bellow - { - Console.WriteLine("Terminal is Already connected to a wire!"); - return; - } - else // Only wire is present bellow - { - if (_previewManager.PreviewComponent is Wire temp && - temp.Points.Count > 1) // if trying to put a checkpoint on an existing wire - { // Trying to put a checkpoint on a wire - Console.WriteLine("Cannot Put a Checkpoint on a Wire!"); - return; - } - else - { - // WIRE EXTENSION LOGIC - Wire existingWire = FindWireAtPosition(CurrentMousePos)!; - if (FindClosestSnapTerminal(CurrentMousePos) != null) - { - Console.WriteLine("Invalid Extension! Please choose a distance away from the terminals."); - return; - } - Console.WriteLine("Registering Wire Extension..."); - _previewManager.StartWireExtension(Canvas, CurrentMousePos, existingWire, this); - return; - } - } + Console.WriteLine("Registering new wire ()..."); + _previewManager.HandleWireCommit(sender, e, CurrentMousePos, this); + return; } - else // if Created on some empty space + else if (terminal != null && IsInputTerminal(terminal)) // If wire & terminal are present bellow { - if (_previewManager.PreviewComponent is not null) - { - Console.WriteLine("Registering a Checkpoint..."); - _previewManager.HandleWireCommit(sender, e, CurrentMousePos, this); + Console.WriteLine("Terminal is Already connected to a wire!"); + return; + } + else // Only wire is present bellow + { + if (_previewManager.PreviewComponent is Wire temp && + temp.Points.Count > 1) // if trying to put a checkpoint on an existing wire + { // Trying to put a checkpoint on a wire + Console.WriteLine("Cannot Put a Checkpoint on a Wire!"); return; } else { - // NEW WIRE LOGIC - Console.WriteLine("Registering New Wire..."); - _previewManager.HandleWireCommit(sender, e, CurrentMousePos, this); + // WIRE EXTENSION LOGIC + Wire existingWire = FindWireAtPosition(CurrentMousePos)!; + if (FindClosestSnapTerminal(CurrentMousePos) != null) + { + Console.WriteLine("Invalid Extension! Please choose a distance away from the terminals."); + return; + } + Console.WriteLine("Registering Wire Extension..."); + _previewManager.StartWireExtension(Canvas, CurrentMousePos, existingWire, this); return; } } } - else + else // if Created on some empty space { - // NEW COMPONENT LOGIC - Console.WriteLine("Registering New Component..."); - _previewManager.HandleComponentCommit(Canvas, _components, CurrentMousePos, _commandManager, this); - return; + if (_previewManager.PreviewComponent is not null) + { + Console.WriteLine("Registering a Checkpoint..."); + _previewManager.HandleWireCommit(sender, e, CurrentMousePos, this); + return; + } + else + { + // NEW WIRE LOGIC + Console.WriteLine("Registering New Wire..."); + _previewManager.HandleWireCommit(sender, e, CurrentMousePos, this); + return; + } } } + else + { + // NEW COMPONENT LOGIC + Console.WriteLine("Registering New Component..."); + _previewManager.HandleComponentCommit(Canvas, _components, CurrentMousePos, _commandManager, this); + return; + } + } // Selection handling through selection manager Selection_OnPointerPressed(e); Selection_HandleStart(CurrentMousePos); @@ -301,7 +301,7 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) { Selection_HandleEnd(_commandManager); } - + private void OnPointerMoved(object? sender, PointerEventArgs e) { // Update the mouse pos @@ -319,7 +319,7 @@ 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)); - + // Update the preview component if (_previewManager.PreviewComponent != null) { diff --git a/Services/AiImageAnalysisService.cs b/Services/AiImageAnalysisService.cs index cb70897..01e9f4f 100644 --- a/Services/AiImageAnalysisService.cs +++ b/Services/AiImageAnalysisService.cs @@ -15,40 +15,40 @@ public class AiImageAnalysisService : IAiImageAnalysisService // Read server URL from text file string url = await File.ReadAllTextAsync("server-link.txt"); url = url.Trim(); // Remove any whitespace/newlines - + // Ensure URL ends with /process if (!url.EndsWith("/process")) { url = url.TrimEnd('/') + "/process"; } - + using (var httpClient = new HttpClient()) { // Specify the single file to upload // THIS NEEDS TO BE GIVEN HERE // string imagePath = "circuits/image.jpg"; // Change this to your actual file path - + if (!File.Exists(imagePath)) { Console.WriteLine($"File '{imagePath}' not found!"); return null; } - + // Create multipart form data using (var form = new MultipartFormDataContent()) { // Read file and add to form byte[] fileBytes = await File.ReadAllBytesAsync(imagePath); var fileContent = new ByteArrayContent(fileBytes); - + // Add file to form with the name "image" string fileName = Path.GetFileName(imagePath); form.Add(fileContent, "image", fileName); - + // Send POST request var response = await httpClient.PostAsync(url, form); string responseText = await response.Content.ReadAsStringAsync(); - + Console.WriteLine($"Response: {responseText}"); return responseText; diff --git a/Services/CircuitFomulaConversionService.cs b/Services/CircuitFomulaConversionService.cs index f39b46d..11e2dbd 100644 --- a/Services/CircuitFomulaConversionService.cs +++ b/Services/CircuitFomulaConversionService.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; using System.Text.RegularExpressions; +using System.Xml.Linq; namespace IRis.Services; + public class CircuitFormulaConversionService { public class CircuitComponent @@ -32,14 +33,14 @@ public static List ConvertXmlToFormulas(string xmlFilePath) public static List ConvertXmlContentToFormulas(string xmlContent) { XDocument doc = XDocument.Parse(xmlContent); - + var components = new Dictionary(); var wireToOutputComponent = new Dictionary(); var wireToInputComponents = new Dictionary>(); var inputToggleNames = new Dictionary(); var allWireIds = new HashSet(); var connectedWireIds = new HashSet(); - + // First pass: collect all wire IDs and identify connected wires foreach (var componentEl in doc.Descendants("Component")) { @@ -50,11 +51,11 @@ public static List ConvertXmlContentToFormulas(string xmlContent foreach (var wireId in wireIds) { allWireIds.Add(wireId); - + // Count how many terminals this wire connects to int connectionCount = doc.Descendants("Terminal") .Count(t => t.Descendants("guid").Any(g => g.Value == wireId)); - + // A wire is considered connected if it connects to more than one terminal if (connectionCount > 1) { @@ -63,23 +64,23 @@ public static List ConvertXmlContentToFormulas(string xmlContent } } } - + // Parse components, but only process those connected to valid wires foreach (var componentEl in doc.Descendants("Component")) { string type = componentEl.Attribute("Type")?.Value!; string componentId = Guid.NewGuid().ToString(); // Generate unique ID for component - + var component = new CircuitComponent { Id = componentId, Type = type }; - + // Get terminals and connected wires var terminals = componentEl.Descendants("Terminal").ToList(); bool hasValidConnections = false; - + if (type == "LogicToggle") { // For input toggles, the wire is an output @@ -88,7 +89,7 @@ public static List ConvertXmlContentToFormulas(string xmlContent { component.OutputWire = wireIds.First(); wireToOutputComponent[wireIds.First()] = componentId; - + // Create a meaningful name for this input string inputName = $"Input_{inputToggleNames.Count + 1}"; inputToggleNames[componentId] = inputName; @@ -126,7 +127,7 @@ public static List ConvertXmlContentToFormulas(string xmlContent } } } - + // Last terminal is output if (terminals.Count > 0) { @@ -139,45 +140,45 @@ public static List ConvertXmlContentToFormulas(string xmlContent } } } - + // Only add component if it has valid connections if (hasValidConnections) { components[componentId] = component; } } - + // Generate formulas for each output probe var formulas = new List(); var outputProbes = components.Values.Where(c => c.Type == "LogicProbe").ToList(); - + for (int i = 0; i < outputProbes.Count; i++) { var probe = outputProbes[i]; string outputName = $"Output_{i + 1}"; - + // Add safety check here if (!probe.InputWires.Any()) { Console.WriteLine($"Warning: Output probe {i + 1} has no input wires connected."); continue; // Skip this probe or provide a default formula } - + string formula = BuildFormula(probe.InputWires.First(), components, wireToOutputComponent, inputToggleNames); - + // Simplify the formula to remove redundant operations formula = SimplifyFormula(formula); - + var circuitFormula = new CircuitFormula { OutputName = outputName, Formula = formula, InputVariables = ExtractInputVariables(formula) }; - + formulas.Add(circuitFormula); } - + return formulas; } @@ -244,13 +245,13 @@ private static bool EvaluateBooleanExpression(string expression) { int lastOpen = expression.LastIndexOf('('); int firstClose = expression.IndexOf(')', lastOpen); - + if (firstClose == -1) throw new ArgumentException("Mismatched parentheses in expression"); string subExpression = expression.Substring(lastOpen + 1, firstClose - lastOpen - 1); bool subResult = EvaluateBooleanExpression(subExpression); - + expression = expression.Substring(0, lastOpen) + (subResult ? "1" : "0") + expression.Substring(firstClose + 1); } @@ -263,7 +264,7 @@ private static bool EvaluateBooleanExpression(string expression) char nextChar = expression[notIndex + 1]; bool valueToNegate = nextChar == '1'; - + expression = expression.Substring(0, notIndex) + (valueToNegate ? "0" : "1") + expression.Substring(notIndex + 2); } @@ -326,96 +327,96 @@ private static string SimplifyFormula(string formula) string simplified = formula; string previous; - + // Keep simplifying until no more changes occur do { previous = simplified; - + // Remove operations with 0 that result in known values - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(([^()]+)&0\)", "0"); // X & 0 = 0 - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(0&([^()]+)\)", "0"); // 0 & X = 0 - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(([^()]+)\|0\)", "$1"); // X | 0 = X - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(0\|([^()]+)\)", "$1"); // 0 | X = X - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(([^()]+)\^0\)", "$1"); // X ^ 0 = X - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(0\^([^()]+)\)", "$1"); // 0 ^ X = X - + simplified = Regex.Replace(simplified, @"\(([^()]+)&0\)", "0"); // X & 0 = 0 + simplified = Regex.Replace(simplified, @"\(0&([^()]+)\)", "0"); // 0 & X = 0 + simplified = Regex.Replace(simplified, @"\(([^()]+)\|0\)", "$1"); // X | 0 = X + simplified = Regex.Replace(simplified, @"\(0\|([^()]+)\)", "$1"); // 0 | X = X + simplified = Regex.Replace(simplified, @"\(([^()]+)\^0\)", "$1"); // X ^ 0 = X + simplified = Regex.Replace(simplified, @"\(0\^([^()]+)\)", "$1"); // 0 ^ X = X + // Remove operations with 1 that result in known values - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(([^()]+)&1\)", "$1"); // X & 1 = X - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(1&([^()]+)\)", "$1"); // 1 & X = X - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(([^()]+)\|1\)", "1"); // X | 1 = 1 - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(1\|([^()]+)\)", "1"); // 1 | X = 1 - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(([^()]+)\^1\)", "(!$1)"); // X ^ 1 = !X - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(1\^([^()]+)\)", "(!$1)"); // 1 ^ X = !X - + simplified = Regex.Replace(simplified, @"\(([^()]+)&1\)", "$1"); // X & 1 = X + simplified = Regex.Replace(simplified, @"\(1&([^()]+)\)", "$1"); // 1 & X = X + simplified = Regex.Replace(simplified, @"\(([^()]+)\|1\)", "1"); // X | 1 = 1 + simplified = Regex.Replace(simplified, @"\(1\|([^()]+)\)", "1"); // 1 | X = 1 + simplified = Regex.Replace(simplified, @"\(([^()]+)\^1\)", "(!$1)"); // X ^ 1 = !X + simplified = Regex.Replace(simplified, @"\(1\^([^()]+)\)", "(!$1)"); // 1 ^ X = !X + // Simplify double negation - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"!\(!([^()]+)\)", "$1"); // !!X = X - + simplified = Regex.Replace(simplified, @"!\(!([^()]+)\)", "$1"); // !!X = X + // Remove unnecessary parentheses around single terms - simplified = System.Text.RegularExpressions.Regex.Replace(simplified, @"\(([^()&|^!]+)\)", "$1"); - + simplified = Regex.Replace(simplified, @"\(([^()&|^!]+)\)", "$1"); + } while (simplified != previous && !string.IsNullOrEmpty(simplified)); - + return string.IsNullOrEmpty(simplified) ? "0" : simplified; } - - private static string BuildFormula(string wireId, Dictionary components, + + private static string BuildFormula(string wireId, Dictionary components, Dictionary wireToOutputComponent, Dictionary inputToggleNames) { if (!wireToOutputComponent.ContainsKey(wireId)) return "0"; // Wire not connected to any output - + string componentId = wireToOutputComponent[wireId]; var component = components[componentId]; - + switch (component.Type) { case "LogicToggle": return inputToggleNames[componentId]; - + case "XorGate": // XOR: A ^ B var xorInputs = component.InputWires .Select(w => BuildFormula(w, components, wireToOutputComponent, inputToggleNames)) .Where(f => f != "0") // Filter out unconnected inputs .ToList(); - + if (xorInputs.Count == 0) return "0"; if (xorInputs.Count == 1) return xorInputs[0]; - + var xorResult = xorInputs[0]; for (int i = 1; i < xorInputs.Count; i++) { xorResult = $"({xorResult}^{xorInputs[i]})"; } return xorResult; - + case "AndGate": var andInputs = component.InputWires .Select(w => BuildFormula(w, components, wireToOutputComponent, inputToggleNames)) .Where(f => f != "0") // Filter out unconnected inputs .ToList(); - + if (andInputs.Count == 0) return "0"; if (andInputs.Count == 1) return andInputs[0]; - + return $"({string.Join("&", andInputs)})"; - + case "OrGate": var orInputs = component.InputWires .Select(w => BuildFormula(w, components, wireToOutputComponent, inputToggleNames)) .Where(f => f != "0") // Filter out unconnected inputs .ToList(); - + if (orInputs.Count == 0) return "0"; if (orInputs.Count == 1) return orInputs[0]; - + return $"({string.Join("|", orInputs)})"; case "NotGate": if (!component.InputWires.Any()) return "1"; // NOT gate with no input defaults to 1 (NOT 0 = 1) - + var notInput = BuildFormula(component.InputWires.First(), components, wireToOutputComponent, inputToggleNames); if (notInput == "0") return "1"; return $"(!{notInput})"; @@ -426,10 +427,10 @@ private static string BuildFormula(string wireId, Dictionary BuildFormula(w, components, wireToOutputComponent, inputToggleNames)) .Where(f => f != "0") // Filter out unconnected inputs .ToList(); - + if (nandInputs.Count == 0) return "1"; // NAND with no inputs = !0 = 1 if (nandInputs.Count == 1) return $"(!{nandInputs[0]})"; - + return $"!({string.Join("&", nandInputs)})"; case "NorGate": @@ -438,10 +439,10 @@ private static string BuildFormula(string wireId, Dictionary BuildFormula(w, components, wireToOutputComponent, inputToggleNames)) .Where(f => f != "0") // Filter out unconnected inputs .ToList(); - + if (norInputs.Count == 0) return "1"; // NOR with no inputs = !0 = 1 if (norInputs.Count == 1) return $"(!{norInputs[0]})"; - + return $"!({string.Join("|", norInputs)})"; case "XnorGate": @@ -450,27 +451,27 @@ private static string BuildFormula(string wireId, Dictionary BuildFormula(w, components, wireToOutputComponent, inputToggleNames)) .Where(f => f != "0") // Filter out unconnected inputs .ToList(); - + if (xnorInputs.Count == 0) return "1"; // XNOR with no inputs = !0 = 1 if (xnorInputs.Count == 1) return $"(!{xnorInputs[0]})"; // XNOR of single input = NOT input - + var xnorResult = xnorInputs[0]; for (int i = 1; i < xnorInputs.Count; i++) { xnorResult = $"({xnorResult}^{xnorInputs[i]})"; } return $"!({xnorResult})"; - + default: return "Unknown"; } } - + private static List ExtractInputVariables(string formula) { var inputs = new HashSet(); var tokens = formula.Split(new[] { ' ', '(', ')', '&', '|', '!', '^' }, StringSplitOptions.RemoveEmptyEntries); - + foreach (var token in tokens) { if (token.StartsWith("Input_")) @@ -478,7 +479,7 @@ private static List ExtractInputVariables(string formula) inputs.Add(token); } } - + return inputs.OrderBy(x => x).ToList(); } } \ No newline at end of file diff --git a/Services/GPTAiAnalysisService.cs b/Services/GPTAiAnalysisService.cs index 184f39a..e5f0908 100644 --- a/Services/GPTAiAnalysisService.cs +++ b/Services/GPTAiAnalysisService.cs @@ -1,11 +1,10 @@ +using Azure.AI.OpenAI; +using OpenAI.Chat; using System; using System.ClientModel; using System.IO; -using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Azure.AI.OpenAI; -using OpenAI.Chat; namespace IRis.Services; @@ -14,12 +13,12 @@ public class GptAiAnalysisService : IAiPromptAnalysisService public async Task GetSerializedCircuit(string prompt, string systemPromptPath) { string systemPrompt; - + using (StreamReader reader = new StreamReader(systemPromptPath)) { systemPrompt = await reader.ReadToEndAsync(); } - + // Get the API keys from a file string json = await File.ReadAllTextAsync("config.json"); var config = JsonSerializer.Deserialize(json); @@ -33,8 +32,8 @@ public async Task GetSerializedCircuit(string prompt, string systemPromp ChatClient chatClient = openAIClient.GetChatClient("gpt-4.1"); - - + + ChatCompletion completion = chatClient.CompleteChat( [ new SystemChatMessage(systemPrompt), @@ -42,7 +41,7 @@ public async Task GetSerializedCircuit(string prompt, string systemPromp ]); //Console.WriteLine($"{completion.Role}: {completion.Content[0].Text}"); - + return completion.Content[0].Text; } } diff --git a/Services/ISerializationService.cs b/Services/ISerializationService.cs index 5117ff8..9843f63 100644 --- a/Services/ISerializationService.cs +++ b/Services/ISerializationService.cs @@ -1,12 +1,8 @@ -using System; +using IRis.Models; +using IRis.Models.Core; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading.Tasks; -using Avalonia.Controls; -using IRis.Models; -using IRis.Models.Components; -using IRis.Models.Core; namespace IRis.Services; @@ -21,6 +17,6 @@ async Task> DeserializeFromFileAsync(string filePath) string data = await File.ReadAllTextAsync(filePath); return DeserializeComponentsAsync(data); } - + } \ No newline at end of file diff --git a/Services/JsonSerializationService.cs b/Services/JsonSerializationService.cs index 836bbda..d032b81 100644 --- a/Services/JsonSerializationService.cs +++ b/Services/JsonSerializationService.cs @@ -1,14 +1,13 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization; -using System.Xml.Serialization; using IRis.Models; using IRis.Models.Components; using IRis.Models.Core; using IRis.Models.DTOs; using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; namespace IRis.Services; @@ -26,17 +25,17 @@ public void SerializeComponents(Simulation simulation, string? filePath) CircuitDto circuit = new CircuitDto() { Components = simulation.Components - .Where(p => p is not Wire) - .Select(p => ComponentDto.ToDto(p)) + .OfType() + .Select(ComponentDto.ToDto) .ToList(), - + Wires = simulation.Components - .Where(p => p is Wire) - .Select(p => WireDto.ToDto(p as Wire)) + .OfType() + .Select(WireDto.ToDto) .ToList() - + }; - + // TODO: This file write might need to be async later on string json = JsonConvert.SerializeObject(circuit, Formatting.Indented); File.WriteAllText(filePath, json); @@ -47,7 +46,7 @@ public List DeserializeComponentsAsync(string jsonContent) try { CircuitDto? circuit = JsonConvert.DeserializeObject(jsonContent); - + if (circuit == null) throw new SerializationException(); return CircuitDto.ToCircuit(circuit); @@ -61,6 +60,6 @@ public List DeserializeComponentsAsync(string jsonContent) return new List(); } - - + + } \ No newline at end of file diff --git a/ViewLocator.cs b/ViewLocator.cs index 8a731a8..8c1734d 100644 --- a/ViewLocator.cs +++ b/ViewLocator.cs @@ -1,7 +1,7 @@ -using System; using Avalonia.Controls; using Avalonia.Controls.Templates; using IRis.ViewModels; +using System; namespace IRis; @@ -12,7 +12,7 @@ public class ViewLocator : IDataTemplate { if (param is null) return null; - + var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); var type = Type.GetType(name); @@ -20,7 +20,7 @@ public class ViewLocator : IDataTemplate { return (Control)Activator.CreateInstance(type)!; } - + return new TextBlock { Text = "Not Found: " + name }; } diff --git a/ViewModels/AIGenerationWindowViewModel.cs b/ViewModels/AIGenerationWindowViewModel.cs index 824611a..1eb208f 100644 --- a/ViewModels/AIGenerationWindowViewModel.cs +++ b/ViewModels/AIGenerationWindowViewModel.cs @@ -1,32 +1,19 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Input; -using System.Xml.Linq; -using System.Xml.Serialization; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Input; -using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using IRis.Models; -using IRis.Models.Components; -using IRis.Models.Core; using IRis.Services; using IRis.Views; // just ignore these useless uses lol +using System; +using System.Threading.Tasks; +using System.Windows.Input; namespace IRis.ViewModels { public partial class AIGenerationWindowViewModel : ViewModelBase { public event Action? XmlGenerated; - + private IAiPromptAnalysisService aiPromptAnalysisService = new GptAiAnalysisService(); - + AIGenerationWindow promptWindow; [ObservableProperty] @@ -35,12 +22,12 @@ public partial class AIGenerationWindowViewModel : ViewModelBase public AIGenerationWindowViewModel(AIGenerationWindow promptWindow) { GenerateCommand = new AsyncRelayCommand(Generate); - - + + this.promptWindow = promptWindow; // get the prompt window for use in this scope } - + public ICommand GenerateCommand { get; } public async Task Generate() { @@ -49,14 +36,14 @@ public async Task Generate() // Relative Path XmlGenerated.Invoke(xml); string xml = await aiPromptAnalysisService.GetSerializedCircuit(PromptText, "circuit-gen-prompt.txt"); - + // Invoke event when Xml is done XmlGenerated?.Invoke(xml); - + Console.WriteLine("\n\nXML:\n" + xml); - - promptWindow.Close(); + + promptWindow.Close(); } } - + } \ No newline at end of file diff --git a/ViewModels/ComponentPropertiesViewModel.cs b/ViewModels/ComponentPropertiesViewModel.cs index db9aee7..49abe6b 100644 --- a/ViewModels/ComponentPropertiesViewModel.cs +++ b/ViewModels/ComponentPropertiesViewModel.cs @@ -5,12 +5,12 @@ namespace IRis.ViewModels; public class ComponentPropertiesViewModel : ViewModelBase { private readonly Simulation _simulation; - + public ComponentPropertiesViewModel(Simulation simulation) { _simulation = simulation; } - + // public string ComponentType // { diff --git a/ViewModels/ExportComponentWindowViewModel.cs b/ViewModels/ExportComponentWindowViewModel.cs index 5a14cb6..742caa1 100644 --- a/ViewModels/ExportComponentWindowViewModel.cs +++ b/ViewModels/ExportComponentWindowViewModel.cs @@ -1,8 +1,7 @@ +using CommunityToolkit.Mvvm.Input; using System; using System.ComponentModel; using System.Runtime.CompilerServices; -using System.Windows.Input; -using CommunityToolkit.Mvvm.Input; namespace IRis.ViewModels { diff --git a/ViewModels/ImageProcessingWindowViewModel.cs b/ViewModels/ImageProcessingWindowViewModel.cs index 2805112..a65eebf 100644 --- a/ViewModels/ImageProcessingWindowViewModel.cs +++ b/ViewModels/ImageProcessingWindowViewModel.cs @@ -1,25 +1,24 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using System.Windows.Input; using Avalonia.Controls; using Avalonia.Media.Imaging; using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.Input; -using IRis.Models; using IRis.Services; using IRis.ViewModels; +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Input; public class ImageProcessingWindowViewModel : ViewModelBase { - IAiImageAnalysisService _aiImageAnalysisService = new AiImageAnalysisService(); - + IAiImageAnalysisService _aiImageAnalysisService = new AiImageAnalysisService(); + public event Action? XmlGenerated; - + private bool _hasImage; - public bool HasImage + public bool HasImage { get => _hasImage; set => SetProperty(ref _hasImage, value); @@ -29,7 +28,7 @@ public bool HasImage public Bitmap? SelectedImage { get => _selectedImage; - set + set { if (SetProperty(ref _selectedImage, value)) { @@ -37,24 +36,24 @@ public Bitmap? SelectedImage } } } - + private string? _filePath; public string? FilePath { get => _filePath; set => SetProperty(ref _filePath, value); } - + // Commands public ICommand SelectImageCommand { get; } public ICommand GenerateCommand { get; } - + private readonly Window _hostWindow; public ImageProcessingWindowViewModel(Window hostWindow) { - _hostWindow = hostWindow; + _hostWindow = hostWindow; SelectImageCommand = new RelayCommand(SelectImage); GenerateCommand = new AsyncRelayCommand(GenerateCircuit); } @@ -79,27 +78,27 @@ private async void SelectImage() }); if (files.Count == 0) return; - + var file = files[0]; - + FilePath = file.Path.AbsolutePath.Replace("%20", " "); - + await using var stream = await file.OpenReadAsync(); SelectedImage = new Bitmap(stream); - + } private async Task GenerateCircuit() { if (SelectedImage == null || FilePath == null) return; - + try { // Convert image to byte array await using var memoryStream = new MemoryStream(); SelectedImage.Save(memoryStream); byte[] imageBytes = memoryStream.ToArray(); - + // Send to server string? xmlResponse = await _aiImageAnalysisService.GetSerializedCircuit(FilePath); @@ -117,9 +116,9 @@ private async Task GenerateCircuit() return; } - + XmlGenerated?.Invoke(xmlResponse); - + _hostWindow.Close(); } catch (Exception ex) diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index e562b83..93fece0 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -1,25 +1,17 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using System.Windows.Input; -using System.Xml.Linq; -using System.Xml.Serialization; -using Avalonia; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Input; using Avalonia.Platform.Storage; -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 System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using ICommand = System.Windows.Input.ICommand; @@ -34,7 +26,7 @@ public partial class MainWindowViewModel : ViewModelBase private string? _openedFileName = null; private string _lastAction = " - "; - + public KeyGestureConfig KeyConfig { get; set; } public string? OpenedFileName @@ -83,7 +75,7 @@ public MainWindowViewModel(Simulation simulation) OnPropertyChanged(nameof(CursorPosition)); } }; - + // TODO: A mismatch between a property name and constructor name is preventing deserialization // Fix it // Load Key Config @@ -140,7 +132,7 @@ public void SimulationToggle() SimulationToggleText = _simulation.Simulating ? "Simulation: ON" : "Simulation: OFF"; OnPropertyChanged(nameof(SimulationButtonColor)); // Changes the color of the button - + // Reset the probes to null foreach (var component in _simulation.Components) { @@ -481,7 +473,7 @@ private async Task OtherComponents() } // Console.WriteLine($"Inputs: {result.InputCount}, Outputs: {result.OutputCount}"); _simulation.CustomComponent = result; - + Console.WriteLine($"Adding component: {result.Name}"); _simulation.PreviewCompType = "CUSTOM"; LastAction = $"Selected Component [{result.Name}]"; @@ -498,7 +490,7 @@ private void ExportCircuit() { } - + public ICommand ExportComponentCommand { get; } private async Task ExportComponent() { diff --git a/Views/AI/AIGenerationWindow.axaml.cs b/Views/AI/AIGenerationWindow.axaml.cs index 693d9c2..f0e704a 100644 --- a/Views/AI/AIGenerationWindow.axaml.cs +++ b/Views/AI/AIGenerationWindow.axaml.cs @@ -1,6 +1,5 @@ // AIGenerationWindow.axaml.cs -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using IRis.ViewModels; diff --git a/Views/AI/ImageProcessingWindow.cs b/Views/AI/ImageProcessingWindow.cs index 5e55fb1..1a964d3 100644 --- a/Views/AI/ImageProcessingWindow.cs +++ b/Views/AI/ImageProcessingWindow.cs @@ -1,9 +1,7 @@ // ImageProcessingWindow.axaml.cs -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -using IRis.ViewModels; namespace IRis.Views; diff --git a/Views/About/AboutWindow.axaml.cs b/Views/About/AboutWindow.axaml.cs index fd5f234..7ada21e 100644 --- a/Views/About/AboutWindow.axaml.cs +++ b/Views/About/AboutWindow.axaml.cs @@ -1,6 +1,5 @@ // AboutWindow.axaml.cs -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; diff --git a/Views/Components/OtherComponentsWindow.axaml.cs b/Views/Components/OtherComponentsWindow.axaml.cs index 7d37513..43dde43 100644 --- a/Views/Components/OtherComponentsWindow.axaml.cs +++ b/Views/Components/OtherComponentsWindow.axaml.cs @@ -1,18 +1,14 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; -using System; -using System.IO; -using System.Linq; -using Avalonia; +using Avalonia.Layout; +using Avalonia.Media; using IRis.Services; -using System.Collections.Generic; -using Tmds.DBus.Protocol; using MsBox.Avalonia; using MsBox.Avalonia.Enums; -using System.Threading.Tasks; -using Avalonia.Layout; -using Avalonia.Media; -using IRis.Models.Core; +using System; +using System.Collections.Generic; +using System.IO; namespace IRis.Views { @@ -53,57 +49,81 @@ private void LoadComponentList() // Add Multiplexer var stackPanel2 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel2.Children.Add(new TextBlock{ Text = "Multiplexer", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel2.Children.Add(new TextBlock + { + Text = "Multiplexer", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel2.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel2 }); // Add Demultiplexer var stackPanel3 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel3.Children.Add(new TextBlock{ Text = "Demultiplexer", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel3.Children.Add(new TextBlock + { + Text = "Demultiplexer", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel3.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel3 }); // Add Encoder var stackPanel4 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel4.Children.Add(new TextBlock{ Text = "Encoder", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel4.Children.Add(new TextBlock + { + Text = "Encoder", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel4.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel4 }); // Add Decoder var stackPanel5 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel5.Children.Add(new TextBlock{ Text = "Decoder", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel5.Children.Add(new TextBlock + { + Text = "Decoder", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel5.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel5 }); // Add SRLatch var stackPanel6 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel6.Children.Add(new TextBlock{ Text = "SR Latch", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel6.Children.Add(new TextBlock + { + Text = "SR Latch", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel6.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel6 }); // Add DLatch var stackPanel7 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel7.Children.Add(new TextBlock{ Text = "D Latch", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel7.Children.Add(new TextBlock + { + Text = "D Latch", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel7.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel7 }); // Add JKLatch var stackPanel8 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel8.Children.Add(new TextBlock{ Text = "JK Latch", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel8.Children.Add(new TextBlock + { + Text = "JK Latch", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel8.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel8 }); // Add TLatch var stackPanel9 = new StackPanel { Orientation = Orientation.Horizontal }; - stackPanel9.Children.Add(new TextBlock{ Text = "T Latch", - VerticalAlignment = VerticalAlignment.Center }); + stackPanel9.Children.Add(new TextBlock + { + Text = "T Latch", + VerticalAlignment = VerticalAlignment.Center + }); stackPanel9.Children.Add(createDefaultLabel()); ComponentListBox.Items.Add(new ListBoxItem { Content = stackPanel9 }); @@ -150,6 +170,18 @@ private void LoadComponentList() } } + private Dictionary DefaultComponentMapping = new Dictionary() + { + { "Multiplexer", "MUX" }, + { "Demultiplexer", "DEMUX" }, + { "Encoder", "ENCODER" }, + { "Decoder", "DECODER" }, + { "SR Latch", "SRL" }, + { "D Latch", "DL" }, + { "JK Latch", "JKL" }, + { "T Latch", "TL" } + }; + private void OnAddClick(object? sender, RoutedEventArgs e) { if (ComponentListBox.SelectedItem is ListBoxItem item) @@ -157,30 +189,20 @@ private void OnAddClick(object? sender, RoutedEventArgs e) if (item.Content is StackPanel stackPanel && stackPanel.Children[0] is TextBlock textBlock) { string componentName = textBlock.Text!; - + // Check the label type from the second child (Border -> TextBlock) if (stackPanel.Children[1] is Border border && border.Child is TextBlock labelText) { string labelType = labelText.Text!; - + if (labelType == "default") { Console.WriteLine("Default component implementation placeholder"); // defaultComponentName will be set as previewComponent on closing - string defaultComponentName = null!; - if (componentName == "Multiplexer") defaultComponentName = "MUX"; - else if (componentName == "Demultiplexer") defaultComponentName = "DEMUX"; - else if (componentName == "Encoder") defaultComponentName = "ENCODER"; - else if (componentName == "Decoder") defaultComponentName = "DECODER"; - else if (componentName == "SR Latch") defaultComponentName = "SRL"; - else if (componentName == "D Latch") defaultComponentName = "DL"; - else if (componentName == "JK Latch") defaultComponentName = "JKL"; - else if (componentName == "T Latch") defaultComponentName = "TL"; - else - { - throw new InvalidOperationException($"the operation for {componentName} is invalid."); - } - var result = new CustomComponentData { Name=defaultComponentName }; + + var defaultComponentName = DefaultComponentMapping[componentName]; + + var result = new CustomComponentData { Name = defaultComponentName }; Close(result); } else @@ -190,9 +212,9 @@ private void OnAddClick(object? sender, RoutedEventArgs e) var result = new CustomComponentData { Name = componentName, - InputCount = this.InputCount, - OutputCount = this.OutputCount, - Formulas = this.Formulas + InputCount = InputCount, + OutputCount = OutputCount, + Formulas = Formulas }; Close(result); } @@ -264,7 +286,7 @@ private async void OnDeleteClick(object? sender, RoutedEventArgs e) } } - + public class CustomComponentData { public required string Name { get; set; } diff --git a/Views/Export/ExportComponentWindow.axaml.cs b/Views/Export/ExportComponentWindow.axaml.cs index 7b37311..88a63f1 100644 --- a/Views/Export/ExportComponentWindow.axaml.cs +++ b/Views/Export/ExportComponentWindow.axaml.cs @@ -9,7 +9,7 @@ public ExportComponentWindow() { InitializeComponent(); DataContext = new ExportComponentWindowViewModel(); - + // Subscribe to view model events if (DataContext is ExportComponentWindowViewModel vm) { @@ -21,7 +21,7 @@ public ExportComponentWindow(ExportComponentWindowViewModel viewModel) { InitializeComponent(); DataContext = viewModel; - + // Subscribe to view model events if (DataContext is ExportComponentWindowViewModel vm) { @@ -41,7 +41,7 @@ protected override void OnClosed(System.EventArgs e) { vm.RequestClose -= OnRequestClose; } - + base.OnClosed(e); } } diff --git a/Views/Main/MainWindow.axaml.cs b/Views/Main/MainWindow.axaml.cs index a13b028..712758e 100644 --- a/Views/Main/MainWindow.axaml.cs +++ b/Views/Main/MainWindow.axaml.cs @@ -8,7 +8,7 @@ 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() : this(new Simulation()) { } // fallback for preview/designer/runtime public MainWindow(Simulation simulation) {