Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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<CircuitObject>`. 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.
Expand All @@ -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).

Expand Down
3 changes: 2 additions & 1 deletion src/Models/CircuitObjects/Components/Multiplexer.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using IRis.Models.Core;

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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()) { }

Expand All @@ -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) =>
Expand All @@ -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());
}

Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 5 additions & 6 deletions src/Views/Main/Canvas/CircuitObjects/Components/ClockView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@
<Grid.RenderTransform>
<RotateTransform Angle="{Binding TextRotation}" />
</Grid.RenderTransform>
<TextBlock
Text="~"
Foreground="{DynamicResource ComponentBorderBrush}"
<Path
Data="M0,6 L3,6 L3,0 L9,0 L9,6 L12,6"
Stroke="{DynamicResource ComponentBorderBrush}"
StrokeThickness="1.5"
Stretch="None"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="18"
FontWeight="ExtraBold"
IsHitTestVisible="False"
Margin="2,0,0,4"
/>
</Grid>
</Border>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
>
<Grid ClipToBounds="False">
<Rectangle Margin="-20,-5,-20,-20" Fill="Transparent" />
<Rectangle Margin="-20,-5,-20,-5" Fill="Transparent" />

<Border
<Path
Width="{Binding Width}"
Height="{Binding Height}"
Background="{DynamicResource ComponentFillBrush}"
BorderBrush="{DynamicResource ComponentBorderBrush}"
BorderThickness="2"
CornerRadius="2"
>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding TextRotation}" />
</Grid.RenderTransform>
</Grid>
</Border>
Fill="{DynamicResource ComponentFillBrush}"
Stroke="{DynamicResource ComponentBorderBrush}"
StrokeThickness="2"
Stretch="Fill"
Data="M 0,0 L 100,20 L 100,80 L 0,100 Z"
/>

<ItemsControl ItemsSource="{Binding Inputs}" ClipToBounds="False">
<ItemsControl.ItemsPanel>
Expand Down Expand Up @@ -53,35 +50,35 @@
</ItemsControl.ItemTemplate>
</ItemsControl>

<ItemsControl
ItemsSource="{Binding Selects}"
ClipToBounds="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
>
<ItemsControl ItemsSource="{Binding Selects}" ClipToBounds="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
<Canvas ClipToBounds="False" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

<!-- DISCLAIMER: this section is AI-generated -->
<!-- it involves relative.absolute positioning, DO NOT REMOVE -->
<ItemsControl.Styles>
<Style Selector="ContentPresenter" x:DataType="corevm:TerminalViewModel">
<Setter Property="Canvas.Left">
<MultiBinding Converter="{x:Static components:MultiplexerView.CanvasOffset}">
<Binding Path="X" />
<Binding Path="$parent[UserControl].((vm:MultiplexerViewModel)DataContext).X" />
</MultiBinding>
</Setter>
<Setter Property="Canvas.Top">
<MultiBinding Converter="{x:Static components:MultiplexerView.CanvasOffset}">
<Binding Path="Y" />
<Binding Path="$parent[UserControl].((vm:MultiplexerViewModel)DataContext).Y" />
</MultiBinding>
</Setter>
</Style>
</ItemsControl.Styles>

<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid ClipToBounds="False">
<Line
StartPoint="0,0"
EndPoint="0,10"
Stroke="{DynamicResource ComponentBorderBrush}"
StrokeThickness="2"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,-10"
/>
<core:TerminalView
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,-20"
/>
</Grid>
<DataTemplate x:DataType="corevm:TerminalViewModel">
<core:TerminalView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Expand All @@ -107,7 +104,7 @@
<Rectangle
Stroke="DodgerBlue"
StrokeThickness="1"
Margin="-20,-5,-20,-20"
Margin="-20,-5,-20,-5"
IsHitTestVisible="False"
Opacity="{Binding SelectionOpacity}"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<object?> 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;
}
}
}
38 changes: 14 additions & 24 deletions src/Views/Main/LeftSidebarView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@
BorderThickness="2"
CornerRadius="1"
>
<TextBlock
Text="~"
Foreground="{DynamicResource ComponentBorderBrush}"
<Path
Data="M0,6 L3,6 L3,0 L9,0 L9,6 L12,6"
Stroke="{DynamicResource ComponentBorderBrush}"
StrokeThickness="1.5"
Stretch="None"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,1"
FontSize="14"
FontWeight="ExtraBold"
IsHitTestVisible="False"
/>
</Border>
<Border
Expand Down Expand Up @@ -312,25 +312,15 @@
Margin="1,0,0,0"
/>
</Border>
<Border
x:Key="SampleMux"
Width="16"
Height="20"
Background="{DynamicResource ComponentFillBrush}"
BorderBrush="{DynamicResource ComponentBorderBrush}"
BorderThickness="2"
CornerRadius="2"
>
<TextBlock
Text="M"
Foreground="{DynamicResource ComponentBorderBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="9"
FontWeight="ExtraBold"
Margin="2,0,0,0"
<Grid x:Key="SampleMux" Width="16" Height="20">
<Path
Fill="{DynamicResource ComponentFillBrush}"
Stroke="{DynamicResource ComponentBorderBrush}"
StrokeThickness="2"
Stretch="Fill"
Data="M 0,0 L 100,20 L 100,80 L 0,100 Z"
/>
</Border>
</Grid>
<Border
x:Key="SampleDemux"
Width="16"
Expand Down
Loading