diff --git a/README.md b/README.md index ddf101a..e16e93e 100644 --- a/README.md +++ b/README.md @@ -34,18 +34,6 @@ The user is expected to draw a logic circuit on paper, take a picture and open i --- -## Key Controls For Circuit Design - -- `R` for rotating components or entire circuits. -- `A` for adding one line of pins to the component. -- `S` for removing one line of pins from the component. -- `Esc` for dropping a component preview. -- `Ctrl+X`, `Ctrl+C`, `Ctrl+V` as shortcuts for cutting, copying, pasting components respectively. -- `Ctrl+Z`, `Ctrl+Y` for undo/redo commands. -- `Arrow keys` or `Ctrl+LeftClick` for canvas control and movement. - ---- - ## Try it Supports & Tested on: _Windows, Linux, and MacOS._ @@ -56,6 +44,14 @@ Now for the Sketch to Simulation feature, you would have to download the latest --- +## Component Shaping & Controls + +In general, each component has it's own keyboard controls and properties as displayed on the `RightSidebar` that appears when it is selected. As for the visuals, we are following IEEE/ANSII standards being consistent with 91/91a-1984 and using rounded/distinctive shapes as well as labels on pins (including dependency labels such as EN1, 1D). + +It is to be noted that the pins for some components, such as the set reset pins in flipflops which are optional, as well as the selection lines for MUX/DEMUX are drawn close to the component body rather than at a distance. + +--- + ## How does the Simulation Work? Everything in a canvas is described by one `AvaloniaList`. This list contains components and wires. All of them have a `Simulate()` function through which they access their logical layer, compute output and update their visual states. @@ -75,7 +71,7 @@ Simulation frequency can be adjusted for: ## Do we have limitations? -Yes, seriously. There are some sections of the code that involve AI-generated mathematics (which have the model and "reason for use" explicitly specified with comments) but this approach is deprecated and planned to be replaced. Also, we are currently not handling cycle detection, bad wiring and race-conditions. +Yes, seriously. There are some sections of the code that involve AI-generated mathematics (which have the model and "reason for use" explicitly specified with comments) but this approach is discouraged. Also, we are currently not handling cycle detection, bad wiring and race-conditions. Furthermore, we are missing some industry-grade features such as VHDL export support, built-in circuit libraries, and a few essential components (just a few). diff --git a/src/Models/CircuitObjects/Components/Multiplexer.cs b/src/Models/CircuitObjects/Components/Multiplexer.cs index b30bfd2..9251d35 100644 --- a/src/Models/CircuitObjects/Components/Multiplexer.cs +++ b/src/Models/CircuitObjects/Components/Multiplexer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using IRis.Models.Core; @@ -22,7 +23,7 @@ public override void Simulate() } if (Selects[i].State == LogicState.High) - index |= 1 << i; + index += (int)Math.Pow(2, i); } if (index >= Inputs.Count) diff --git a/src/ViewModels/Main/Canvas/CircuitObjects/Components/MultiplexerViewModel.cs b/src/ViewModels/Main/Canvas/CircuitObjects/Components/MultiplexerViewModel.cs index fe1819c..e55c035 100644 --- a/src/ViewModels/Main/Canvas/CircuitObjects/Components/MultiplexerViewModel.cs +++ b/src/ViewModels/Main/Canvas/CircuitObjects/Components/MultiplexerViewModel.cs @@ -1,10 +1,10 @@ +using System; using System.Collections.ObjectModel; using System.Collections.Specialized; using Avalonia; using CommunityToolkit.Mvvm.ComponentModel; using IRis.Models.CircuitObjects.Components; using IRis.Models.Core; -using IRis.Services; using IRis.ViewModels.Main.Canvas.Core; namespace IRis.ViewModels.Main.Canvas.CircuitObjects.Components; @@ -17,12 +17,6 @@ public partial class MultiplexerViewModel : ComponentViewModel [ObservableProperty] private TerminalViewModel _output = null!; - partial void OnOutputChanged(TerminalViewModel value) - { - value.Type = TerminalType.Output; - (Model as Multiplexer)!.Output = value.GetModel(); - } - public MultiplexerViewModel() : this(new Multiplexer()) { } @@ -46,7 +40,8 @@ private MultiplexerViewModel(Multiplexer model) (Model as Multiplexer)!.Inputs.Remove(vm.GetModel()); } - UpdateDimensions(); + Height = Inputs.Count * 20; + Width = Selects.Count * 20; }; Selects.CollectionChanged += (sender, e) => @@ -66,16 +61,24 @@ private MultiplexerViewModel(Multiplexer model) (Model as Multiplexer)!.Selects.Remove(vm.GetModel()); } - UpdateDimensions(); + Height = Inputs.Count * 20; + Width = Selects.Count * 20; }; } + partial void OnOutputChanged(TerminalViewModel value) + { + value.Type = TerminalType.Output; + (Model as Multiplexer)!.Output = value.GetModel(); + } + public void AddSelectLine() { - Selects.Add(new TerminalViewModel()); + if (Selects.Count >= 10) + return; - int target = 1 << Selects.Count; - while (Inputs.Count < target) + Selects.Add(new TerminalViewModel()); + while (Inputs.Count < Math.Pow(2, Selects.Count)) Inputs.Add(new TerminalViewModel()); } @@ -85,21 +88,10 @@ public void RemoveSelectLine() return; Selects.Remove(Selects[^1]); - - int target = 1 << Selects.Count; - while (Inputs.Count > target) + while (Inputs.Count > Math.Pow(2, Selects.Count)) Inputs.Remove(Inputs[^1]); } - private void UpdateDimensions() - { - if (Inputs.Count > 0) - Height = Inputs.Count * 20; - - if (Selects.Count > 0) - Width = Selects.Count * 20; - } - public override void UpdateTerminals() { if (Output is null) @@ -134,12 +126,15 @@ public override void UpdateTerminals() for (int i = 0; i < Selects.Count; i++) { - double unrotatedX = X + ((i + 0.5) * (Width / Selects.Count)); - double unrotatedY = Y + Height + 10; + // DISCLAIMER: this calculation for placing the select lines on the + // bottom of the trapezium was done by Cursor Grok 4.5 + // it works, so we keep it. + + double u = (i + 0.5) / Selects.Count; Point selectPos = _simulationService.RotateTerminalPosition( - unrotatedX, - unrotatedY, + X + (u * Width), + Y + (Height * (1.0 - (0.2 * u))), Rotation, Width, Height, diff --git a/src/Views/Main/Canvas/CircuitObjects/Components/ClockView.axaml b/src/Views/Main/Canvas/CircuitObjects/Components/ClockView.axaml index 5841ce7..33a1575 100644 --- a/src/Views/Main/Canvas/CircuitObjects/Components/ClockView.axaml +++ b/src/Views/Main/Canvas/CircuitObjects/Components/ClockView.axaml @@ -22,15 +22,14 @@ - diff --git a/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml b/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml index 427928c..11a12e7 100644 --- a/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml +++ b/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml @@ -5,25 +5,22 @@ x:DataType="vm:MultiplexerViewModel" x:Class="IRis.Views.Main.Canvas.CircuitObjects.Components.MultiplexerView" xmlns:core="using:IRis.Views.Main.Canvas.Core" + xmlns:corevm="using:IRis.ViewModels.Main.Canvas.Core" + xmlns:components="using:IRis.Views.Main.Canvas.CircuitObjects.Components" ClipToBounds="False" > - + - - - - - - - + Fill="{DynamicResource ComponentFillBrush}" + Stroke="{DynamicResource ComponentBorderBrush}" + StrokeThickness="2" + Stretch="Fill" + Data="M 0,0 L 100,20 L 100,80 L 0,100 Z" + /> @@ -53,35 +50,35 @@ - + - + + + + + + + + - - - - - + + @@ -107,7 +104,7 @@ diff --git a/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml.cs b/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml.cs index 1e2884f..8f02814 100644 --- a/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml.cs +++ b/src/Views/Main/Canvas/CircuitObjects/Components/MultiplexerView.axaml.cs @@ -1,11 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using Avalonia.Controls; +using Avalonia.Data.Converters; namespace IRis.Views.Main.Canvas.CircuitObjects.Components; public partial class MultiplexerView : UserControl { + public static readonly IMultiValueConverter CanvasOffset = new SelectCanvasOffsetConverter(); + public MultiplexerView() { InitializeComponent(); } + + private sealed class SelectCanvasOffsetConverter : IMultiValueConverter + { + public object? Convert( + IList values, + Type targetType, + object? parameter, + CultureInfo culture + ) + { + if (values.Count < 2 || values.Any(v => v is not double)) + return 0.0; + + return (double)values[0]! - (double)values[1]! - 5; + } + } } diff --git a/src/Views/Main/LeftSidebarView.axaml b/src/Views/Main/LeftSidebarView.axaml index 8f2d165..12a21dc 100644 --- a/src/Views/Main/LeftSidebarView.axaml +++ b/src/Views/Main/LeftSidebarView.axaml @@ -36,14 +36,14 @@ BorderThickness="2" CornerRadius="1" > - - - + - +