mirror of
https://github.com/Stone-Red-Code/StoneRed.LogicSimulator.git
synced 2026-09-04 00:56:23 +02:00
Add save and loader classes and some QoL improvements
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<Project StylesheetPath="C:\Users\David\My Drive\Programming\StoneRed.LogicSimulator\StoneRed.LogicSimulator\Content\stylesheet\default_ui_skin.xmms">
|
<Project StylesheetPath="C:\Users\David\My Drive\Programming\StoneRed.LogicSimulator\StoneRed.LogicSimulator\Content\stylesheet\default_ui_skin.xmms">
|
||||||
<Project.ExportOptions />
|
<Project.ExportOptions />
|
||||||
<VerticalStackPanel BorderThickness="1" Padding="0, 10, 0, 5" Background="#333130FF" Border="#1BA1E2FF">
|
<VerticalStackPanel BorderThickness="1" Padding="0, 10, 0, 5" Background="#333130FF" Border="#1BA1E2FF">
|
||||||
<Label Text="rteterert" Margin="10, 0" ClipToBounds="True" Id="title" />
|
<Label Text="Context Menu" Margin="10, 0" ClipToBounds="True" Id="title" />
|
||||||
<TextButton Text="Settings" Margin="10, 10, 10, 0" BorderThickness="1" Padding="5" HorizontalAlignment="Center" Scale="0.9, 0.9" Border="#5BC6FAFF" />
|
<TextButton Text="Settings" Margin="10, 10, 10, 0" BorderThickness="1" Padding="5" HorizontalAlignment="Center" Scale="0.9, 0.9" Border="#5BC6FAFF" Id="button" />
|
||||||
<VerticalMenu HorizontalAlignment="Stretch" Margin="0, 10, 0, 0" BorderThickness="0, 1, 0, 0" Padding="0, 10, 0, 5" Background="#333130FF" Id="menu" />
|
<VerticalMenu HorizontalAlignment="Stretch" Margin="0, 10, 0, 0" BorderThickness="0, 1, 0, 0" Padding="0, 10, 0, 5" Background="#333130FF" Id="menu" />
|
||||||
</VerticalStackPanel>
|
</VerticalStackPanel>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<Project StylesheetPath="stylesheet/default_ui_skin.xmms">
|
<Project StylesheetPath="stylesheet/default_ui_skin.xmms">
|
||||||
<Project.ExportOptions />
|
<Project.ExportOptions />
|
||||||
<Window Title="Quick Menu" Left="394" Top="177">
|
<Window Title="Quick Menu" Left="376" Top="149">
|
||||||
<VerticalMenu HorizontalAlignment="Center" Margin="0, 0, 0, 15" Padding="0, 10" Id="menu" >
|
<VerticalMenu HorizontalAlignment="Center" Margin="0, 0, 0, 15" Padding="0, 10" Id="menu">
|
||||||
<MenuItem Text="Main Menu" Id="menuMainMenu" />
|
<MenuItem Text="Main Menu" Id="menuMainMenu" />
|
||||||
<MenuItem Text="Settings" Id="menuSettings" />
|
<MenuItem Text="Settings" Id="menuSettings" />
|
||||||
|
<MenuItem Text="Save" Id="menuSave" />
|
||||||
|
<MenuItem Text="Load" Id="menuLoad" />
|
||||||
<MenuItem Text="Quit" Id="menuQuit" />
|
<MenuItem Text="Quit" Id="menuQuit" />
|
||||||
</VerticalMenu>
|
</VerticalMenu>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project StylesheetPath="stylesheet/default_ui_skin.xmms">
|
<Project StylesheetPath="stylesheet/default_ui_skin.xmms">
|
||||||
<Project.ExportOptions />
|
<Project.ExportOptions />
|
||||||
<Grid>
|
<Grid CanSelectNothing="True" >
|
||||||
<Grid.ColumnsProportions>
|
<Grid.ColumnsProportions>
|
||||||
<Proportion Type="Part" />
|
<Proportion Type="Part" />
|
||||||
<Proportion Type="Part" Value="3" />
|
<Proportion Type="Part" Value="3" />
|
||||||
@@ -8,13 +8,20 @@
|
|||||||
</Grid.ColumnsProportions>
|
</Grid.ColumnsProportions>
|
||||||
<VerticalStackPanel Id="info">
|
<VerticalStackPanel Id="info">
|
||||||
<Label Text="FPS:" TextColor="#CF56BFFF" Id="fps" />
|
<Label Text="FPS:" TextColor="#CF56BFFF" Id="fps" />
|
||||||
<Label Text="FRQ: " TextColor="#CF56BFFF" Id="ups" />
|
<Label Text="FRQ: " TextColor="#CF56BFFF" Id="ups" />
|
||||||
|
<Label Text="X/Y: " TextColor="#CF56BFFF" Id="position" />
|
||||||
</VerticalStackPanel>
|
</VerticalStackPanel>
|
||||||
<Panel GridColumn="2" Background="#4BD961FF">
|
<Panel GridColumn="2" Background="#4BD961FF">
|
||||||
<VerticalStackPanel Id="settings">
|
<VerticalStackPanel Id="settings">
|
||||||
<Label Text="Frequency:" />
|
<Label Text="Frequency:" />
|
||||||
<SpinButton Maximum="1E+09" Minimum="1" HorizontalAlignment="Stretch" Value="100" Integer="True" Id="frequency" />
|
<SpinButton Maximum="1E+09" Minimum="1" HorizontalAlignment="Stretch" Value="100" Integer="True" Id="frequency" />
|
||||||
<CheckBox Text="High Performance" Id="highPerformance" />
|
<CheckBox Text="High Performance" Id="highPerformance" />
|
||||||
|
<Label Text="Native components:" Wrap="True" />
|
||||||
|
<ListBox HorizontalAlignment="Stretch" Id="nativeComponents" />
|
||||||
|
<Label Text="Custom components:" Wrap="True" />
|
||||||
|
<ListBox HorizontalAlignment="Stretch">
|
||||||
|
<ListItem Text="This is a test 2" />
|
||||||
|
</ListBox>
|
||||||
</VerticalStackPanel>
|
</VerticalStackPanel>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// This file is used by Code Analysis to maintain SuppressMessage
|
||||||
|
// attributes that are applied to this project.
|
||||||
|
// Project-level suppressions either have no target or are given
|
||||||
|
// a specific target and scoped to a namespace, type, member, etc.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
[assembly: SuppressMessage("Minor Code Smell", "S1643:Strings should not be concatenated using '+' in a loop", Justification = "<Pending>", Scope = "member", Target = "~M:StoneRed.LogicSimulator.Utilities.LogicGatesManager.LoadLogicGates")]
|
||||||
@@ -10,8 +10,10 @@ namespace StoneRed.LogicSimulator.Simulation;
|
|||||||
|
|
||||||
internal class LogicGateSimulator
|
internal class LogicGateSimulator
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<int, LogicGate> logicGates = new ConcurrentDictionary<int, LogicGate>();
|
private readonly ConcurrentDictionary<ulong, LogicGate> logicGates = new ConcurrentDictionary<ulong, LogicGate>();
|
||||||
private DateTime dateTime;
|
private DateTime dateTime;
|
||||||
|
private bool logicGatesUpdated = false;
|
||||||
|
private ulong logicGateId = 0;
|
||||||
public int TargetTicksPerSecond { get; set; } = 100;
|
public int TargetTicksPerSecond { get; set; } = 100;
|
||||||
|
|
||||||
public int ActualTicksPerSecond { get; private set; }
|
public int ActualTicksPerSecond { get; private set; }
|
||||||
@@ -22,7 +24,7 @@ internal class LogicGateSimulator
|
|||||||
|
|
||||||
public bool HighPerformanceClock { get; set; }
|
public bool HighPerformanceClock { get; set; }
|
||||||
|
|
||||||
public LogicGateSimulator(List<LogicGate> logicGates)
|
public LogicGateSimulator(IEnumerable<LogicGate> logicGates)
|
||||||
{
|
{
|
||||||
foreach (LogicGate logicGate in logicGates)
|
foreach (LogicGate logicGate in logicGates)
|
||||||
{
|
{
|
||||||
@@ -49,11 +51,11 @@ internal class LogicGateSimulator
|
|||||||
|
|
||||||
public void AddLogicGate(LogicGate gate)
|
public void AddLogicGate(LogicGate gate)
|
||||||
{
|
{
|
||||||
gate.Id = logicGates.Count;
|
gate.Id = logicGateId++;
|
||||||
_ = logicGates.TryAdd(gate.Id, gate);
|
logicGatesUpdated = logicGates.TryAdd(gate.Id, gate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogicGate GetLogicGate(int id)
|
public LogicGate GetLogicGate(ulong id)
|
||||||
{
|
{
|
||||||
return logicGates[id];
|
return logicGates[id];
|
||||||
}
|
}
|
||||||
@@ -65,7 +67,12 @@ internal class LogicGateSimulator
|
|||||||
|
|
||||||
public void RemoveLogicGate(LogicGate logicGate)
|
public void RemoveLogicGate(LogicGate logicGate)
|
||||||
{
|
{
|
||||||
_ = logicGates.TryRemove(logicGate.Id, out _);
|
foreach (LogicGate otherLogicGate in logicGates.Values.Where(l => l.IsConnectedTo(logicGate)))
|
||||||
|
{
|
||||||
|
otherLogicGate.Disconnect(logicGate);
|
||||||
|
}
|
||||||
|
|
||||||
|
logicGatesUpdated = logicGates.TryRemove(logicGate.Id, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SimulationThread()
|
public void SimulationThread()
|
||||||
@@ -79,6 +86,12 @@ internal class LogicGateSimulator
|
|||||||
{
|
{
|
||||||
tps++;
|
tps++;
|
||||||
|
|
||||||
|
if (logicGatesUpdated)
|
||||||
|
{
|
||||||
|
logicGatesArray = logicGates.Values.ToArray();
|
||||||
|
logicGatesUpdated = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (DateTime.Now > dateTime.AddSeconds(1))
|
if (DateTime.Now > dateTime.AddSeconds(1))
|
||||||
{
|
{
|
||||||
ActualTicksPerSecond = tps;
|
ActualTicksPerSecond = tps;
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
internal class LogicGateDescriptionAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
public LogicGateDescriptionAttribute(string description)
|
||||||
|
{
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
internal class LogicGateNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public LogicGateNameAttribute(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,25 +2,23 @@
|
|||||||
|
|
||||||
using MonoGame.Extended.Input;
|
using MonoGame.Extended.Input;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
|
[LogicGateName("Button")]
|
||||||
|
[LogicGateDescription("A button is a momentary switch that can be turned on and off.")]
|
||||||
internal class Button : LogicGate, IInteractable, IColorable
|
internal class Button : LogicGate, IInteractable, IColorable
|
||||||
{
|
{
|
||||||
public override int OutputCount => 1;
|
public override int OutputCount { get; set; } = 1;
|
||||||
|
|
||||||
public override int InputCount => 0;
|
public override int InputCount { get; set; } = 0;
|
||||||
|
|
||||||
public bool IsPressed { get; set; }
|
public bool IsPressed { get; set; }
|
||||||
public Color Color { get; set; } = Color.Purple;
|
public Color Color { get; set; } = Color.Purple;
|
||||||
public string Info { get; set; } = "OFF";
|
public string Info { get; set; } = "OFF";
|
||||||
|
|
||||||
public Button()
|
|
||||||
{
|
|
||||||
Metadata.Name = "Button";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
||||||
{
|
{
|
||||||
IsPressed = mouseState.IsButtonDown(MouseButton.Left);
|
IsPressed = mouseState.IsButtonDown(MouseButton.Left);
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
using MonoGame.Extended.Input;
|
using MonoGame.Extended.Input;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
|
[LogicGateName("Clock")]
|
||||||
|
[LogicGateDescription("A clock is a circuit that oscillates between a high and a low state.")]
|
||||||
internal class Clock : LogicGate, IInteractable
|
internal class Clock : LogicGate, IInteractable
|
||||||
{
|
{
|
||||||
private int count = 0;
|
private int count = 0;
|
||||||
private int tickRate = 0;
|
private int tickRate = 0;
|
||||||
public override int OutputCount => 1;
|
public override int OutputCount { get; set; } = 1;
|
||||||
|
|
||||||
public override int InputCount => 0;
|
public override int InputCount { get; set; } = 0;
|
||||||
|
|
||||||
public string Info
|
public string Info
|
||||||
{
|
{
|
||||||
@@ -27,11 +30,6 @@ internal class Clock : LogicGate, IInteractable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Clock()
|
|
||||||
{
|
|
||||||
Metadata.Name = "Clock";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
||||||
{
|
{
|
||||||
if (mouseState.DeltaScrollWheelValue == 0 || !keyboardStateExtended.IsShiftDown())
|
if (mouseState.DeltaScrollWheelValue == 0 || !keyboardStateExtended.IsShiftDown())
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
#pragma warning disable S112 // General exceptions should never be thrown
|
#pragma warning disable S112 // General exceptions should never be thrown
|
||||||
@@ -14,12 +15,12 @@ internal abstract class LogicGate
|
|||||||
private int output;
|
private int output;
|
||||||
private int cachedOutput;
|
private int cachedOutput;
|
||||||
|
|
||||||
public LogicGateMetadata Metadata { get; set; } = new LogicGateMetadata();
|
public LogicGateWorldData WorldData { get; init; } = new LogicGateWorldData();
|
||||||
public int Id { get; set; }
|
public ulong Id { get; internal set; }
|
||||||
public IReadOnlyList<LogicGateConnection> LogicGateConnections => logicGateConnections.AsReadOnly();
|
public IReadOnlyList<LogicGateConnection> LogicGateConnections => logicGateConnections.AsReadOnly();
|
||||||
public abstract int OutputCount { get; }
|
public abstract int OutputCount { get; set; }
|
||||||
|
|
||||||
public abstract int InputCount { get; }
|
public abstract int InputCount { get; set; }
|
||||||
|
|
||||||
public void NextTick()
|
public void NextTick()
|
||||||
{
|
{
|
||||||
@@ -58,6 +59,13 @@ internal abstract class LogicGate
|
|||||||
logicGateConnections.Add(new LogicGateConnection(logicGate, inputIndex, outputIndex));
|
logicGateConnections.Add(new LogicGateConnection(logicGate, inputIndex, outputIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsConnectedTo(LogicGate logicGate)
|
||||||
|
{
|
||||||
|
#pragma warning disable S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension
|
||||||
|
return logicGateConnections.Any(c => c.LogicGate.Id == logicGate.Id);
|
||||||
|
#pragma warning restore S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension
|
||||||
|
}
|
||||||
|
|
||||||
public void Disconnect(LogicGate logicGate)
|
public void Disconnect(LogicGate logicGate)
|
||||||
{
|
{
|
||||||
int index = logicGateConnections.FindIndex(c => c.LogicGate.Id == logicGate.Id);
|
int index = logicGateConnections.FindIndex(c => c.LogicGate.Id == logicGate.Id);
|
||||||
@@ -81,6 +89,17 @@ internal abstract class LogicGate
|
|||||||
return cachedOutput.GetBit(index);
|
return cachedOutput.GetBit(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is LogicGate gate &&
|
||||||
|
Id == gate.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract void Execute();
|
protected abstract void Execute();
|
||||||
|
|
||||||
protected int GetInputBit(int index)
|
protected int GetInputBit(int index)
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
|
[LogicGateName("Lamp")]
|
||||||
|
[LogicGateDescription("A lamp is a light source that can be turned on and off.")]
|
||||||
internal class Lamp : LogicGate, IColorable
|
internal class Lamp : LogicGate, IColorable
|
||||||
{
|
{
|
||||||
public override int OutputCount => 0;
|
public override int OutputCount { get; set; } = 0;
|
||||||
|
|
||||||
public override int InputCount => 1;
|
public override int InputCount { get; set; } = 1;
|
||||||
|
|
||||||
public Color Color { get; set; } = Color.CadetBlue;
|
public Color Color { get; set; } = Color.CadetBlue;
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
internal record LogicGateMetadata
|
internal record LogicGateWorldData
|
||||||
{
|
{
|
||||||
public Vector2 Position { get; set; }
|
public Vector2 Position { get; set; }
|
||||||
|
|
||||||
public Vector2 Size { get; set; } = new Vector2(100, 100);
|
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
|
[LogicGateName("Not Gate")]
|
||||||
|
[LogicGateDescription("A not gate is a gate that inverts the input.")]
|
||||||
|
internal class NotGate : LogicGate
|
||||||
|
{
|
||||||
|
public override int InputCount { get; set; } = 1;
|
||||||
|
|
||||||
|
public override int OutputCount { get; set; } = 1;
|
||||||
|
|
||||||
|
protected override void Execute()
|
||||||
|
{
|
||||||
|
SetOutputBit(GetInputBit(0) == 1 ? 0 : 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
|
[LogicGateName("Pin")]
|
||||||
|
[LogicGateDescription("A pin is a input or output.")]
|
||||||
internal class Pin : LogicGate
|
internal class Pin : LogicGate
|
||||||
{
|
{
|
||||||
public override int OutputCount => 1;
|
public override int OutputCount { get; set; } = 1;
|
||||||
|
|
||||||
public override int InputCount => 1;
|
public override int InputCount { get; set; } = 1;
|
||||||
|
|
||||||
protected override void Execute()
|
protected override void Execute()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,25 +2,23 @@
|
|||||||
|
|
||||||
using MonoGame.Extended.Input;
|
using MonoGame.Extended.Input;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
|
[LogicGateName("Switch")]
|
||||||
|
[LogicGateDescription("A switch is a toggleable switch that can be turned on and off.")]
|
||||||
internal class Switch : LogicGate, IInteractable, IColorable
|
internal class Switch : LogicGate, IInteractable, IColorable
|
||||||
{
|
{
|
||||||
public override int OutputCount => 1;
|
public override int OutputCount { get; set; } = 1;
|
||||||
|
|
||||||
public override int InputCount => 0;
|
public override int InputCount { get; set; } = 0;
|
||||||
|
|
||||||
public bool IsPressed { get; set; }
|
public bool IsPressed { get; set; }
|
||||||
public Color Color { get; set; } = Color.Purple;
|
public Color Color { get; set; } = Color.Purple;
|
||||||
public string Info { get; set; } = "OFF";
|
public string Info { get; set; } = "OFF";
|
||||||
|
|
||||||
public Switch()
|
|
||||||
{
|
|
||||||
Metadata.Name = "Switch";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
||||||
{
|
{
|
||||||
if (mouseState.IsButtonDown(MouseButton.Left) && !previousMouseState.IsButtonDown(MouseButton.Left))
|
if (mouseState.IsButtonDown(MouseButton.Left) && !previousMouseState.IsButtonDown(MouseButton.Left))
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
|
||||||
|
|
||||||
internal class TestLogicGate : LogicGate
|
|
||||||
{
|
|
||||||
public override int InputCount => 1;
|
|
||||||
|
|
||||||
public override int OutputCount => 1;
|
|
||||||
|
|
||||||
public TestLogicGate()
|
|
||||||
{
|
|
||||||
Metadata.Name = "Test";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Execute()
|
|
||||||
{
|
|
||||||
SetOutputBit(GetInputBit(0) == 1 ? 0 : 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using Microsoft.Xna.Framework.Input;
|
|
||||||
|
|
||||||
using MonoGame.Extended.Screens;
|
using MonoGame.Extended.Screens;
|
||||||
|
|
||||||
@@ -33,6 +32,8 @@ internal class Srls : Game
|
|||||||
public FontSystem FontSystem { get; private set; } = null!;
|
public FontSystem FontSystem { get; private set; } = null!;
|
||||||
public AssetManager AssetManager { get; private set; } = null!;
|
public AssetManager AssetManager { get; private set; } = null!;
|
||||||
|
|
||||||
|
public LogicGatesManager LogicGatesManager { get; private set; } = null!;
|
||||||
|
|
||||||
public Settings Settings { get; set; }
|
public Settings Settings { get; set; }
|
||||||
|
|
||||||
public string ContentPath { get; }
|
public string ContentPath { get; }
|
||||||
@@ -98,13 +99,17 @@ internal class Srls : Game
|
|||||||
currentSrlsWindow = window;
|
currentSrlsWindow = window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowContextMenu(string title, Point position, params MenuItem[] menuItems)
|
public TextButton ShowContextMenu(string title, Point position, MenuItem[] menuItems, bool showButton = false)
|
||||||
{
|
{
|
||||||
string data = File.ReadAllText(Path.Combine(ContentPath, "ContextMenu.xmmp"));
|
string data = File.ReadAllText(Path.Combine(ContentPath, "ContextMenu.xmmp"));
|
||||||
VerticalStackPanel contextMenu = (VerticalStackPanel)Project.LoadFromXml(data, AssetManager).Root;
|
VerticalStackPanel contextMenu = (VerticalStackPanel)Project.LoadFromXml(data, AssetManager).Root;
|
||||||
|
|
||||||
contextMenu.FindChildById<Label>("title").Text = title;
|
contextMenu.FindChildById<Label>("title").Text = title;
|
||||||
|
|
||||||
|
TextButton button = contextMenu.FindChildById<TextButton>("button");
|
||||||
|
|
||||||
|
button.Visible = showButton;
|
||||||
|
|
||||||
VerticalMenu menu = contextMenu.FindChildById<VerticalMenu>("menu");
|
VerticalMenu menu = contextMenu.FindChildById<VerticalMenu>("menu");
|
||||||
|
|
||||||
foreach (MenuItem menuItem in menuItems)
|
foreach (MenuItem menuItem in menuItems)
|
||||||
@@ -114,6 +119,8 @@ internal class Srls : Game
|
|||||||
|
|
||||||
Desktop.ShowContextMenu(contextMenu, position);
|
Desktop.ShowContextMenu(contextMenu, position);
|
||||||
Desktop.ContextMenu.Scale = new Vector2(Scale / 3, Scale / 3);
|
Desktop.ContextMenu.Scale = new Vector2(Scale / 3, Scale / 3);
|
||||||
|
|
||||||
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update(GameTime gameTime)
|
protected override void Update(GameTime gameTime)
|
||||||
@@ -131,12 +138,6 @@ internal class Srls : Game
|
|||||||
currentSrlsWindow.Scale = currentSrlsWindow.ScalingEnabled ? new Vector2(Scale / 2, Scale / 2) : Vector2.One;
|
currentSrlsWindow.Scale = currentSrlsWindow.ScalingEnabled ? new Vector2(Scale / 2, Scale / 2) : Vector2.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardState keyboardState = Keyboard.GetState();
|
|
||||||
if (keyboardState.IsKeyDown(Keys.Q))
|
|
||||||
{
|
|
||||||
ShowWindow<QuickMenu>();
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Update(gameTime);
|
base.Update(gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +169,9 @@ internal class Srls : Game
|
|||||||
FileAssetResolver assetResolver = new FileAssetResolver(ContentPath);
|
FileAssetResolver assetResolver = new FileAssetResolver(ContentPath);
|
||||||
AssetManager = new AssetManager(assetResolver);
|
AssetManager = new AssetManager(assetResolver);
|
||||||
|
|
||||||
|
LogicGatesManager = new LogicGatesManager();
|
||||||
|
LogicGatesManager.LoadLogicGates();
|
||||||
|
|
||||||
LoadScreen<StartScreen>();
|
LoadScreen<StartScreen>();
|
||||||
|
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<EmbeddedResource Include="Icon.bmp" />
|
<EmbeddedResource Include="Icon.bmp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentResults" Version="3.15.2" />
|
||||||
<PackageReference Include="MonoGame.Extended" Version="3.8.0" />
|
<PackageReference Include="MonoGame.Extended" Version="3.8.0" />
|
||||||
<PackageReference Include="MonoGame.Extended.Entities" Version="3.8.0" />
|
<PackageReference Include="MonoGame.Extended.Entities" Version="3.8.0" />
|
||||||
<PackageReference Include="MonoGame.Extended.Graphics" Version="3.8.0" />
|
<PackageReference Include="MonoGame.Extended.Graphics" Version="3.8.0" />
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using FluentResults;
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
|
|
||||||
using StoneRed.LogicSimulator.Simulation.LogicGates;
|
using StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
using StoneRed.LogicSimulator.WorldSaveSystem;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
||||||
|
|
||||||
internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
||||||
{
|
{
|
||||||
|
private readonly string filePath;
|
||||||
private Label label = null!;
|
private Label label = null!;
|
||||||
private HorizontalProgressBar horizontalProgressBar = null!;
|
private HorizontalProgressBar horizontalProgressBar = null!;
|
||||||
|
|
||||||
protected override string XmmpPath => "LoadingScreen.xmmp";
|
protected override string XmmpPath => "LoadingScreen.xmmp";
|
||||||
|
|
||||||
|
public LoadingScreen(string filePath)
|
||||||
|
{
|
||||||
|
this.filePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
label = Root.FindChildById<Label>("label");
|
label = Root.FindChildById<Label>("label");
|
||||||
@@ -27,25 +36,32 @@ internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
|||||||
|
|
||||||
protected override void LoadContent()
|
protected override void LoadContent()
|
||||||
{
|
{
|
||||||
|
Progress<WorldSaveLoadProgress> progress = new Progress<WorldSaveLoadProgress>();
|
||||||
|
progress.ProgressChanged += Progress_ProgressChanged;
|
||||||
|
|
||||||
|
WorldLoader worldLoader = new WorldLoader(srls);
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 100; i++)
|
Result<IEnumerable<LogicGate>> result = await worldLoader.LoadWorld(filePath, progress);
|
||||||
|
if (result.IsFailed)
|
||||||
{
|
{
|
||||||
horizontalProgressBar.Value++;
|
Dialog.CreateMessageBox("Error", string.Join(',', result.Errors.Select(e => e.Message))).Show(srls.Desktop);
|
||||||
await Task.Delay(1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update(GameTime gameTime)
|
protected override void Update(GameTime gameTime)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
|
||||||
label.Text = $"Loading... {Math.Round(100 / horizontalProgressBar.Maximum * horizontalProgressBar.Value, 0)}%";
|
label.Text = $"Loading... {Math.Round(100 / horizontalProgressBar.Maximum * horizontalProgressBar.Value, 0)}%";
|
||||||
if (horizontalProgressBar.Value >= horizontalProgressBar.Maximum)
|
if (horizontalProgressBar.Value >= horizontalProgressBar.Maximum)
|
||||||
{
|
{
|
||||||
Clock button = new Clock()
|
Clock button = new Clock()
|
||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Metadata = new LogicGateMetadata()
|
WorldData = new LogicGateWorldData()
|
||||||
{
|
{
|
||||||
Name = "Clock",
|
Name = "Clock",
|
||||||
Position = new Vector2(0, 0)
|
Position = new Vector2(0, 0)
|
||||||
@@ -55,50 +71,50 @@ internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
|||||||
Switch @switch = new Switch()
|
Switch @switch = new Switch()
|
||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Metadata = new LogicGateMetadata()
|
WorldData = new LogicGateWorldData()
|
||||||
{
|
{
|
||||||
Name = "Switch",
|
Name = "Switch",
|
||||||
Position = new Vector2(0, 150)
|
Position = new Vector2(0, 200)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TestLogicGate testLogicGate1 = new TestLogicGate()
|
NotGate testLogicGate1 = new NotGate()
|
||||||
{
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Metadata = new LogicGateMetadata()
|
WorldData = new LogicGateWorldData()
|
||||||
{
|
{
|
||||||
Name = "L1",
|
Name = "Not Gate",
|
||||||
Position = new Vector2(150, 150)
|
Position = new Vector2(200, 200)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Pin testLogicGate2 = new Pin()
|
Pin testLogicGate2 = new Pin()
|
||||||
{
|
{
|
||||||
Id = 3,
|
Id = 3,
|
||||||
Metadata = new LogicGateMetadata()
|
WorldData = new LogicGateWorldData()
|
||||||
{
|
{
|
||||||
Name = "Pin",
|
Name = "Pin",
|
||||||
Position = new Vector2(300, 300)
|
Position = new Vector2(400, 400)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Pin testLogicGate3 = new Pin()
|
Pin testLogicGate3 = new Pin()
|
||||||
{
|
{
|
||||||
Id = 4,
|
Id = 4,
|
||||||
Metadata = new LogicGateMetadata()
|
WorldData = new LogicGateWorldData()
|
||||||
{
|
{
|
||||||
Name = "Pin",
|
Name = "Pin",
|
||||||
Position = new Vector2(450, 450)
|
Position = new Vector2(500, 500)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Lamp lamp = new Lamp()
|
Lamp lamp = new Lamp()
|
||||||
{
|
{
|
||||||
Id = 5,
|
Id = 5,
|
||||||
Metadata = new LogicGateMetadata()
|
WorldData = new LogicGateWorldData()
|
||||||
{
|
{
|
||||||
Name = "Lamp",
|
Name = "Lamp",
|
||||||
Position = new Vector2(600, 450)
|
Position = new Vector2(600, 600)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,4 +141,10 @@ internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
|||||||
protected override void Draw(GameTime gameTime)
|
protected override void Draw(GameTime gameTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Progress_ProgressChanged(object? sender, WorldSaveLoadProgress e)
|
||||||
|
{
|
||||||
|
horizontalProgressBar.Value = e.Percentage;
|
||||||
|
label.Text = e.Message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,9 @@ internal abstract class SrlsScreen
|
|||||||
|
|
||||||
public event EventHandler<GameScreenWrapperEventArgs>? OnDraw;
|
public event EventHandler<GameScreenWrapperEventArgs>? OnDraw;
|
||||||
|
|
||||||
|
private bool initialized;
|
||||||
|
private bool loadedContent;
|
||||||
|
|
||||||
public GameScreenWrapper(Game game) : base(game)
|
public GameScreenWrapper(Game game) : base(game)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -69,12 +72,20 @@ internal abstract class SrlsScreen
|
|||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
OnInitialize?.Invoke(this, EventArgs.Empty);
|
if (!initialized)
|
||||||
|
{
|
||||||
|
initialized = true;
|
||||||
|
OnInitialize?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LoadContent()
|
public override void LoadContent()
|
||||||
{
|
{
|
||||||
OnLoadContent?.Invoke(this, EventArgs.Empty);
|
if (!loadedContent)
|
||||||
|
{
|
||||||
|
loadedContent = true;
|
||||||
|
OnLoadContent?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UnloadContent()
|
public override void UnloadContent()
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
|
using Myra.Graphics2D.UI.File;
|
||||||
|
|
||||||
using StoneRed.LogicSimulator.UserInterface.Windows;
|
using StoneRed.LogicSimulator.UserInterface.Windows;
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
||||||
|
|
||||||
internal class StartScreen : SrlsScreen<VerticalStackPanel>
|
internal class StartScreen : SrlsScreen<VerticalStackPanel>
|
||||||
{
|
{
|
||||||
protected override string XmmpPath => "StartScreen.xmmp";
|
protected override string XmmpPath => "StartScreen.xmmp";
|
||||||
|
|
||||||
protected override void LoadContent()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
VerticalMenu menu = Root.FindChildById<VerticalMenu>("menu");
|
VerticalMenu menu = Root.FindChildById<VerticalMenu>("menu");
|
||||||
menu.FindMenuItemById("menuQuit").Selected += (s, a) => srls.Exit();
|
menu.FindMenuItemById("menuQuit").Selected += (s, a) => srls.Exit();
|
||||||
menu.FindMenuItemById("menuSettings").Selected += (s, a) => srls.ShowWindow<SettingsWindow>();
|
menu.FindMenuItemById("menuSettings").Selected += (s, a) => srls.ShowWindow<SettingsWindow>();
|
||||||
menu.FindMenuItemById("menuStartNew").Selected += (s, a) => srls.LoadScreen<WorldScreen>();
|
menu.FindMenuItemById("menuStartNew").Selected += (s, a) => srls.LoadScreen<WorldScreen>();
|
||||||
menu.FindMenuItemById("menuLoad").Selected += (s, a) => srls.LoadScreen(new LoadingScreen());
|
menu.FindMenuItemById("menuLoad").Selected += MenuLoad_Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(GameTime gameTime)
|
protected override void Draw(GameTime gameTime)
|
||||||
@@ -26,4 +29,24 @@ internal class StartScreen : SrlsScreen<VerticalStackPanel>
|
|||||||
protected override void Update(GameTime gameTime)
|
protected override void Update(GameTime gameTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MenuLoad_Selected(object? sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
FileDialog fileDialog = new FileDialog(FileDialogMode.OpenFile);
|
||||||
|
|
||||||
|
fileDialog.Show(srls.Desktop);
|
||||||
|
fileDialog.Scale = new Vector2(srls.Scale / 3);
|
||||||
|
fileDialog.Closed += async (s, a) =>
|
||||||
|
{
|
||||||
|
if (!fileDialog.Result)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very cheap workaround to make sure the file dialog is closed before loading the screen
|
||||||
|
await Task.Delay(100);
|
||||||
|
|
||||||
|
srls.LoadScreen(new LoadingScreen(fileDialog.FilePath));
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,9 +11,12 @@ using Myra.Graphics2D.UI;
|
|||||||
|
|
||||||
using StoneRed.LogicSimulator.Simulation;
|
using StoneRed.LogicSimulator.Simulation;
|
||||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
using StoneRed.LogicSimulator.UserInterface.Windows;
|
||||||
|
using StoneRed.LogicSimulator.Utilities;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
using IColorable = StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces.IColorable;
|
using IColorable = StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces.IColorable;
|
||||||
|
|
||||||
@@ -22,26 +25,34 @@ namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
|||||||
internal class WorldScreen : SrlsScreen<Grid>
|
internal class WorldScreen : SrlsScreen<Grid>
|
||||||
{
|
{
|
||||||
private readonly LogicGateSimulator simulator;
|
private readonly LogicGateSimulator simulator;
|
||||||
|
private readonly Vector2 logicGateSize = new Vector2(100, 100);
|
||||||
private OrthographicCamera camera = null!;
|
private OrthographicCamera camera = null!;
|
||||||
|
private RichTextLayout richTextLayout = null!;
|
||||||
|
|
||||||
private Label fpsLabel = null!;
|
private Label fpsLabel = null!;
|
||||||
private Label upsLabel = null!;
|
private Label upsLabel = null!;
|
||||||
|
private Label positionLabel = null!;
|
||||||
private SpinButton frequency = null!;
|
private SpinButton frequency = null!;
|
||||||
private CheckBox highPerformance = null!;
|
private CheckBox highPerformance = null!;
|
||||||
|
private ListBox nativeComponentsListBox = null!;
|
||||||
private float fps;
|
private float fps;
|
||||||
|
|
||||||
private MouseStateExtended previousMouseState;
|
private MouseStateExtended previousMouseState;
|
||||||
public override bool ScalingEnabled => false;
|
|
||||||
|
|
||||||
|
private ConnectionContext? connectionContext = null;
|
||||||
|
|
||||||
|
private LogicGate? selectedLogicGate = null;
|
||||||
|
public override bool ScalingEnabled => false;
|
||||||
protected override string XmmpPath => "WorldScreen.xmmp";
|
protected override string XmmpPath => "WorldScreen.xmmp";
|
||||||
|
|
||||||
public WorldScreen(List<LogicGate> logicGates)
|
public WorldScreen(IEnumerable<LogicGate> logicGates)
|
||||||
{
|
{
|
||||||
simulator = new LogicGateSimulator(logicGates);
|
simulator = new LogicGateSimulator(logicGates);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldScreen()
|
public WorldScreen()
|
||||||
{
|
{
|
||||||
simulator = new LogicGateSimulator(new List<LogicGate>());
|
simulator = new LogicGateSimulator(Enumerable.Empty<LogicGate>());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
@@ -49,11 +60,26 @@ internal class WorldScreen : SrlsScreen<Grid>
|
|||||||
VerticalStackPanel infoPanel = Root.FindChildById<VerticalStackPanel>("info");
|
VerticalStackPanel infoPanel = Root.FindChildById<VerticalStackPanel>("info");
|
||||||
fpsLabel = infoPanel.FindChildById<Label>("fps");
|
fpsLabel = infoPanel.FindChildById<Label>("fps");
|
||||||
upsLabel = infoPanel.FindChildById<Label>("ups");
|
upsLabel = infoPanel.FindChildById<Label>("ups");
|
||||||
|
positionLabel = infoPanel.FindChildById<Label>("position");
|
||||||
|
|
||||||
VerticalStackPanel settingsPanel = Root.FindChildById<VerticalStackPanel>("settings");
|
VerticalStackPanel settingsPanel = Root.FindChildById<VerticalStackPanel>("settings");
|
||||||
frequency = settingsPanel.FindChildById<SpinButton>("frequency");
|
frequency = settingsPanel.FindChildById<SpinButton>("frequency");
|
||||||
highPerformance = settingsPanel.FindChildById<CheckBox>("highPerformance");
|
highPerformance = settingsPanel.FindChildById<CheckBox>("highPerformance");
|
||||||
|
|
||||||
|
nativeComponentsListBox = Root.FindChildById<ListBox>("nativeComponents");
|
||||||
|
|
||||||
|
foreach (LogicGateInfo logicGateInfo in srls.LogicGatesManager.GetNativeLogicGatesInfos())
|
||||||
|
{
|
||||||
|
nativeComponentsListBox.Items.Add(new ListItem(logicGateInfo.TypeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeComponentsListBox.SelectedIndexChanged += NativeComponentsListBox_SelectedIndexChanged;
|
||||||
|
|
||||||
|
richTextLayout = new RichTextLayout
|
||||||
|
{
|
||||||
|
Font = srls.FontSystem.GetFont(15)
|
||||||
|
};
|
||||||
|
|
||||||
camera = new OrthographicCamera(srls.GraphicsDevice)
|
camera = new OrthographicCamera(srls.GraphicsDevice)
|
||||||
{
|
{
|
||||||
MinimumZoom = 0.1f,
|
MinimumZoom = 0.1f,
|
||||||
@@ -68,11 +94,6 @@ internal class WorldScreen : SrlsScreen<Grid>
|
|||||||
{
|
{
|
||||||
fps = 1f / gameTime.GetElapsedSeconds();
|
fps = 1f / gameTime.GetElapsedSeconds();
|
||||||
|
|
||||||
RichTextLayout richTextLayout = new RichTextLayout
|
|
||||||
{
|
|
||||||
Font = srls.FontSystem.GetFont(15)
|
|
||||||
};
|
|
||||||
|
|
||||||
Matrix transformMatrix = camera.GetViewMatrix();
|
Matrix transformMatrix = camera.GetViewMatrix();
|
||||||
srls.SpriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: transformMatrix);
|
srls.SpriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: transformMatrix);
|
||||||
|
|
||||||
@@ -87,22 +108,37 @@ internal class WorldScreen : SrlsScreen<Grid>
|
|||||||
if (logicGate is IInteractable interactable)
|
if (logicGate is IInteractable interactable)
|
||||||
{
|
{
|
||||||
richTextLayout.Text = interactable.Info;
|
richTextLayout.Text = interactable.Info;
|
||||||
richTextLayout.Draw(srls.SpriteBatch, (new Vector2(0, 15) * srls.Scale) + (logicGate.Metadata.Position * srls.Scale), Color.Black, new Vector2(srls.Scale, srls.Scale), layerDepth: 0);
|
richTextLayout.Draw(srls.SpriteBatch, (new Vector2(0, 15) * srls.Scale) + (logicGate.WorldData.Position * srls.Scale), Color.Black, new Vector2(srls.Scale, srls.Scale), layerDepth: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
richTextLayout.Text = logicGate.Metadata.Name;
|
// Draw text for components
|
||||||
|
richTextLayout.Text = logicGate.WorldData.Name;
|
||||||
|
richTextLayout.Draw(srls.SpriteBatch, logicGate.WorldData.Position * srls.Scale, Color.Black, new Vector2(srls.Scale, srls.Scale), layerDepth: 0);
|
||||||
|
|
||||||
richTextLayout.Draw(srls.SpriteBatch, logicGate.Metadata.Position * srls.Scale, Color.Black, new Vector2(srls.Scale, srls.Scale), layerDepth: 0);
|
// Draw logic gate
|
||||||
|
srls.SpriteBatch.FillRectangle(logicGate.WorldData.Position * srls.Scale, logicGateSize * srls.Scale, color, 0.2f);
|
||||||
srls.SpriteBatch.FillRectangle(logicGate.Metadata.Position * srls.Scale, logicGate.Metadata.Size * srls.Scale, color, 0.2f);
|
|
||||||
|
|
||||||
|
// Draw logic gate connections
|
||||||
foreach (LogicGateConnection connection in logicGate.LogicGateConnections)
|
foreach (LogicGateConnection connection in logicGate.LogicGateConnections)
|
||||||
{
|
{
|
||||||
Color lineColor = logicGate.GetCachedOutputBit(connection.OutputIndex) == 1 ? Color.Red : Color.LightBlue;
|
Color lineColor = logicGate.GetCachedOutputBit(connection.OutputIndex) == 1 ? Color.Red : Color.LightBlue;
|
||||||
srls.SpriteBatch.DrawLine((logicGate.Metadata.Position * srls.Scale) + (logicGate.Metadata.Size / 2 * srls.Scale), (connection.LogicGate.Metadata.Position * srls.Scale) + (logicGate.Metadata.Size / 2 * srls.Scale), lineColor, 5 * srls.Scale, 0.1f);
|
srls.SpriteBatch.DrawLine((logicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), (connection.LogicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), lineColor, 5 * srls.Scale, 0.1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (connectionContext is not null)
|
||||||
|
{
|
||||||
|
LogicGate logicGate = connectionContext.LogicGate;
|
||||||
|
srls.SpriteBatch.DrawLine((logicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), camera.ScreenToWorld(previousMouseState.Position.ToVector2()), Color.Purple, 5 * srls.Scale, 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedLogicGate is not null)
|
||||||
|
{
|
||||||
|
richTextLayout.Text = selectedLogicGate.WorldData.Name;
|
||||||
|
richTextLayout.Draw(srls.SpriteBatch, selectedLogicGate.WorldData.Position, Color.White, new Vector2(srls.Scale, srls.Scale), layerDepth: 0);
|
||||||
|
srls.SpriteBatch.DrawRectangle(selectedLogicGate.WorldData.Position, logicGateSize * srls.Scale, Color.Red, 2 * srls.Scale);
|
||||||
|
}
|
||||||
|
|
||||||
srls.SpriteBatch.End();
|
srls.SpriteBatch.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +161,15 @@ internal class WorldScreen : SrlsScreen<Grid>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fpsLabel.Text = $"FPS: {Math.Round(fps)}";
|
fpsLabel.Text = $"FPS: {Math.Round(fps)}";
|
||||||
upsLabel.Text = $"FRQ: {CalculateFrequency(simulator.ActualTicksPerSecond)}/{CalculateFrequency(simulator.TargetTicksPerSecond)}{(simulator.HighPerformanceClock ? "*" : string.Empty)} {(simulator.ClockCalibrating ? $"[Calibrating... {calibrationPercentage}%]" : string.Empty)}";
|
upsLabel.Text = $"FRQ: {FrequencyCalculator.CalculateFrequency(simulator.ActualTicksPerSecond)}/{FrequencyCalculator.CalculateFrequency(simulator.TargetTicksPerSecond)}{(simulator.HighPerformanceClock ? "*" : string.Empty)} {(simulator.ClockCalibrating ? $"[Calibrating... {calibrationPercentage}%]" : string.Empty)}";
|
||||||
|
positionLabel.Text = $"X/Y: {(long)Math.Round(camera.Position.X / logicGateSize.X / srls.Scale)}/{(long)Math.Round(camera.Position.Y / logicGateSize.Y / srls.Scale)}";
|
||||||
|
|
||||||
simulator.TargetTicksPerSecond = (int)frequency.Value.GetValueOrDefault();
|
simulator.TargetTicksPerSecond = (int)frequency.Value.GetValueOrDefault();
|
||||||
simulator.HighPerformanceClock = highPerformance.IsChecked;
|
simulator.HighPerformanceClock = highPerformance.IsChecked;
|
||||||
|
|
||||||
fpsLabel.Font = srls.FontSystem.GetFont(10 * srls.Scale);
|
fpsLabel.Font = srls.FontSystem.GetFont(10 * srls.Scale);
|
||||||
upsLabel.Font = srls.FontSystem.GetFont(10 * srls.Scale);
|
upsLabel.Font = srls.FontSystem.GetFont(10 * srls.Scale);
|
||||||
|
positionLabel.Font = srls.FontSystem.GetFont(10 * srls.Scale);
|
||||||
|
|
||||||
MouseStateExtended mouseState = MouseExtended.GetState();
|
MouseStateExtended mouseState = MouseExtended.GetState();
|
||||||
KeyboardStateExtended keyboardState = KeyboardExtended.GetState();
|
KeyboardStateExtended keyboardState = KeyboardExtended.GetState();
|
||||||
@@ -139,14 +178,18 @@ internal class WorldScreen : SrlsScreen<Grid>
|
|||||||
|
|
||||||
foreach (LogicGate logicGate in simulator.GetLogicGates())
|
foreach (LogicGate logicGate in simulator.GetLogicGates())
|
||||||
{
|
{
|
||||||
Rectangle rectangle = new Rectangle((logicGate.Metadata.Position * srls.Scale).ToPoint(), (logicGate.Metadata.Size * srls.Scale).ToPoint());
|
Rectangle rectangle = new Rectangle((logicGate.WorldData.Position * srls.Scale).ToPoint(), (logicGateSize * srls.Scale).ToPoint());
|
||||||
|
|
||||||
if (rectangle.Contains(camera.ScreenToWorld(mouseState.Position.ToVector2())))
|
if (rectangle.Contains(camera.ScreenToWorld(mouseState.Position.ToVector2())))
|
||||||
{
|
{
|
||||||
if (mouseState.IsButtonDown(MouseButton.Right))
|
if (mouseState.IsButtonDown(MouseButton.Right) && selectedLogicGate is null)
|
||||||
{
|
{
|
||||||
ShowConnectionContextMenu(logicGate, mouseState.Position, keyboardState.IsShiftDown());
|
ShowConnectionContextMenu(logicGate, mouseState.Position, keyboardState.IsShiftDown());
|
||||||
}
|
}
|
||||||
|
else if (!srls.Desktop.IsMouseOverGUI && keyboardState.IsKeyDown(Keys.X) && connectionContext is null)
|
||||||
|
{
|
||||||
|
simulator.RemoveLogicGate(logicGate);
|
||||||
|
}
|
||||||
else if (!srls.Desktop.IsMouseOverGUI && logicGate is IInteractable interactable)
|
else if (!srls.Desktop.IsMouseOverGUI && logicGate is IInteractable interactable)
|
||||||
{
|
{
|
||||||
interactable.OnInteraction(mouseState, previousMouseState, keyboardState);
|
interactable.OnInteraction(mouseState, previousMouseState, keyboardState);
|
||||||
@@ -161,13 +204,42 @@ internal class WorldScreen : SrlsScreen<Grid>
|
|||||||
srls.Desktop.HideContextMenu();
|
srls.Desktop.HideContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedLogicGate is not null)
|
||||||
|
{
|
||||||
|
if (mouseState.IsButtonDown(MouseButton.Left) && !mouseOverGate && !srls.Desktop.IsMouseOverGUI)
|
||||||
|
{
|
||||||
|
selectedLogicGate.WorldData.Position /= srls.Scale;
|
||||||
|
simulator.AddLogicGate(selectedLogicGate);
|
||||||
|
selectedLogicGate = null;
|
||||||
|
nativeComponentsListBox.SelectedIndex = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vector2 position = camera.ScreenToWorld(mouseState.Position.ToVector2());
|
||||||
|
position = new Vector2(position.X - RealMod(position.X, 100 * srls.Scale), position.Y - RealMod(position.Y, 100 * srls.Scale));
|
||||||
|
|
||||||
|
selectedLogicGate.WorldData.Position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyboardState.IsKeyDown(Keys.C))
|
||||||
|
{
|
||||||
|
connectionContext = null;
|
||||||
|
selectedLogicGate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyboardState.IsKeyDown(Keys.Q))
|
||||||
|
{
|
||||||
|
srls.ShowWindow<QuickMenu>();
|
||||||
|
}
|
||||||
|
|
||||||
previousMouseState = mouseState;
|
previousMouseState = mouseState;
|
||||||
|
|
||||||
float movementSpeed = (float)Math.Pow(200, 2 - camera.Zoom);
|
float movementSpeed = (float)Math.Pow(200, 2 - camera.Zoom);
|
||||||
|
|
||||||
movementSpeed = Math.Clamp(movementSpeed, 1000, 20000);
|
movementSpeed = Math.Clamp(movementSpeed, 1000, 20000);
|
||||||
|
|
||||||
camera.Move(GetMovementDirection(keyboardState) * movementSpeed * gameTime.GetElapsedSeconds());
|
camera.Move(keyboardState.GetMovementDirection() * movementSpeed * gameTime.GetElapsedSeconds());
|
||||||
|
|
||||||
if (!keyboardState.IsShiftDown())
|
if (!keyboardState.IsShiftDown())
|
||||||
{
|
{
|
||||||
@@ -187,64 +259,131 @@ internal class WorldScreen : SrlsScreen<Grid>
|
|||||||
simulator.Stop();
|
simulator.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector2 GetMovementDirection(KeyboardStateExtended keyboardState)
|
private static float RealMod(float x, float m)
|
||||||
{
|
{
|
||||||
Vector2 movementDirection = Vector2.Zero;
|
float r = x % m;
|
||||||
if (keyboardState.IsKeyDown(Keys.S))
|
return r < 0 ? r + m : r;
|
||||||
{
|
|
||||||
movementDirection += Vector2.UnitY;
|
|
||||||
}
|
|
||||||
if (keyboardState.IsKeyDown(Keys.W))
|
|
||||||
{
|
|
||||||
movementDirection -= Vector2.UnitY;
|
|
||||||
}
|
|
||||||
if (keyboardState.IsKeyDown(Keys.A))
|
|
||||||
{
|
|
||||||
movementDirection -= Vector2.UnitX;
|
|
||||||
}
|
|
||||||
if (keyboardState.IsKeyDown(Keys.D))
|
|
||||||
{
|
|
||||||
movementDirection += Vector2.UnitX;
|
|
||||||
}
|
|
||||||
return movementDirection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string CalculateFrequency(double hz)
|
private void NativeComponentsListBox_SelectedIndexChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
double khz = hz / 1000d;
|
if (nativeComponentsListBox.SelectedItem is not null)
|
||||||
double mhz = khz / 1000d;
|
|
||||||
double ghz = mhz / 1000d;
|
|
||||||
|
|
||||||
if (ghz >= 1)
|
|
||||||
{
|
{
|
||||||
return $"{Math.Round(ghz)}GHz";
|
connectionContext = null;
|
||||||
}
|
selectedLogicGate = srls.LogicGatesManager.CreateLogicGate(nativeComponentsListBox.SelectedItem.Text);
|
||||||
else if (mhz >= 1)
|
selectedLogicGate.WorldData.Name = nativeComponentsListBox.SelectedItem.Text;
|
||||||
{
|
|
||||||
return $"{Math.Round(mhz)}MHz";
|
|
||||||
}
|
|
||||||
else if (khz >= 1)
|
|
||||||
{
|
|
||||||
return $"{Math.Round(khz)}kHz";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $"{Math.Round(hz)}Hz";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowConnectionContextMenu(LogicGate logicGate, Point position, bool showInputs)
|
private void ShowConnectionContextMenu(LogicGate logicGate, Point position, bool showInputs)
|
||||||
{
|
{
|
||||||
showInputs = logicGate.OutputCount <= 0 || (showInputs && logicGate.InputCount > 0);
|
showInputs = logicGate.OutputCount <= 0 || (showInputs && logicGate.InputCount > 0);
|
||||||
|
|
||||||
|
string buttonText = "Settings";
|
||||||
|
|
||||||
|
if (connectionContext is not null)
|
||||||
|
{
|
||||||
|
showInputs = !connectionContext.IsInput;
|
||||||
|
buttonText = "Cancel";
|
||||||
|
}
|
||||||
|
|
||||||
int count = showInputs ? logicGate.InputCount : logicGate.OutputCount;
|
int count = showInputs ? logicGate.InputCount : logicGate.OutputCount;
|
||||||
|
|
||||||
MenuItem[] menuItems = new MenuItem[count];
|
MenuItem[] menuItems = new MenuItem[count];
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
menuItems[i] = new MenuItem(i.ToString(), showInputs ? $"Input {i}" : $"Output {i}");
|
int index = i;
|
||||||
|
|
||||||
|
string connectionStatus = string.Empty;
|
||||||
|
|
||||||
|
if (connectionContext is not null)
|
||||||
|
{
|
||||||
|
bool isConnected = false;
|
||||||
|
|
||||||
|
if (connectionContext.IsInput)
|
||||||
|
{
|
||||||
|
isConnected = logicGate.IsConnectedTo(connectionContext.LogicGate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isConnected = connectionContext.LogicGate.IsConnectedTo(logicGate);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionStatus = isConnected ? "[Disconnect]" : "[Connect]";
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems[i] = new MenuItem(i.ToString(), $"{connectionStatus} " + (showInputs ? $"Input {i}" : $"Output {i}"));
|
||||||
|
menuItems[i].Selected += (_, _) => OnConnectionClicked(logicGate, index, showInputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
srls.ShowContextMenu(logicGate.Metadata.Name, position, menuItems);
|
TextButton button = srls.ShowContextMenu(logicGate.WorldData.Name, position, menuItems, true);
|
||||||
|
button.Text = buttonText;
|
||||||
|
button.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
srls.Desktop.HideContextMenu();
|
||||||
|
|
||||||
|
if (connectionContext is not null)
|
||||||
|
{
|
||||||
|
connectionContext = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Show settings window
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConnectionClicked(LogicGate logicGate, int index, bool isInput)
|
||||||
|
{
|
||||||
|
if (connectionContext is null)
|
||||||
|
{
|
||||||
|
connectionContext = new ConnectionContext(logicGate)
|
||||||
|
{
|
||||||
|
Index = index,
|
||||||
|
IsInput = isInput
|
||||||
|
};
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionContext.IsInput)
|
||||||
|
{
|
||||||
|
if (logicGate.IsConnectedTo(connectionContext.LogicGate))
|
||||||
|
{
|
||||||
|
logicGate.Disconnect(connectionContext.LogicGate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logicGate.Connect(connectionContext.LogicGate, connectionContext.Index, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (connectionContext.LogicGate.IsConnectedTo(logicGate))
|
||||||
|
{
|
||||||
|
connectionContext.LogicGate.Disconnect(logicGate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connectionContext.LogicGate.Connect(logicGate, index, connectionContext.Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ConnectionContext
|
||||||
|
{
|
||||||
|
public bool IsInput { get; set; }
|
||||||
|
|
||||||
|
public LogicGate LogicGate { get; set; }
|
||||||
|
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
public ConnectionContext(LogicGate logicGate)
|
||||||
|
{
|
||||||
|
LogicGate = logicGate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
|
|
||||||
using StoneRed.LogicSimulator.UserInterface.Screens;
|
using StoneRed.LogicSimulator.UserInterface.Screens;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.UserInterface.Windows;
|
namespace StoneRed.LogicSimulator.UserInterface.Windows;
|
||||||
@@ -13,6 +14,8 @@ internal class QuickMenu : SrlsWindow
|
|||||||
|
|
||||||
menu.FindMenuItemById("menuMainMenu").Selected += (s, a) => srls.LoadScreen<StartScreen>();
|
menu.FindMenuItemById("menuMainMenu").Selected += (s, a) => srls.LoadScreen<StartScreen>();
|
||||||
menu.FindMenuItemById("menuSettings").Selected += (s, a) => srls.ShowWindow<SettingsWindow>();
|
menu.FindMenuItemById("menuSettings").Selected += (s, a) => srls.ShowWindow<SettingsWindow>();
|
||||||
|
menu.FindMenuItemById("menuSave").Selected += (s, a) => srls.ShowWindow<SettingsWindow>();
|
||||||
|
menu.FindMenuItemById("menuLoad").Selected += (s, a) => srls.ShowWindow<SettingsWindow>();
|
||||||
menu.FindMenuItemById("menuQuit").Selected += QuickMenu_Selected;
|
menu.FindMenuItemById("menuQuit").Selected += QuickMenu_Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Utilities;
|
||||||
|
|
||||||
|
internal static class FrequencyCalculator
|
||||||
|
{
|
||||||
|
public static string CalculateFrequency(double hz)
|
||||||
|
{
|
||||||
|
double khz = hz / 1000d;
|
||||||
|
double mhz = khz / 1000d;
|
||||||
|
double ghz = mhz / 1000d;
|
||||||
|
|
||||||
|
if (ghz >= 1)
|
||||||
|
{
|
||||||
|
return $"{Math.Round(ghz)}GHz";
|
||||||
|
}
|
||||||
|
else if (mhz >= 1)
|
||||||
|
{
|
||||||
|
return $"{Math.Round(mhz)}MHz";
|
||||||
|
}
|
||||||
|
else if (khz >= 1)
|
||||||
|
{
|
||||||
|
return $"{Math.Round(khz)}kHz";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{Math.Round(hz)}Hz";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
|
||||||
|
using MonoGame.Extended.Input;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Utilities;
|
||||||
|
|
||||||
|
internal static class InputHelper
|
||||||
|
{
|
||||||
|
public static Vector2 GetMovementDirection(this KeyboardStateExtended keyboardState)
|
||||||
|
{
|
||||||
|
Vector2 movementDirection = Vector2.Zero;
|
||||||
|
if (keyboardState.IsKeyDown(Keys.S))
|
||||||
|
{
|
||||||
|
movementDirection += Vector2.UnitY;
|
||||||
|
}
|
||||||
|
if (keyboardState.IsKeyDown(Keys.W))
|
||||||
|
{
|
||||||
|
movementDirection -= Vector2.UnitY;
|
||||||
|
}
|
||||||
|
if (keyboardState.IsKeyDown(Keys.A))
|
||||||
|
{
|
||||||
|
movementDirection -= Vector2.UnitX;
|
||||||
|
}
|
||||||
|
if (keyboardState.IsKeyDown(Keys.D))
|
||||||
|
{
|
||||||
|
movementDirection += Vector2.UnitX;
|
||||||
|
}
|
||||||
|
return movementDirection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Utilities;
|
||||||
|
|
||||||
|
internal class LogicGatesManager
|
||||||
|
{
|
||||||
|
private readonly Dictionary<LogicGateInfo, Type> logicGates = new Dictionary<LogicGateInfo, Type>();
|
||||||
|
|
||||||
|
public void LoadLogicGates()
|
||||||
|
{
|
||||||
|
IEnumerable<Type> logicGateTypes = typeof(LogicGate)
|
||||||
|
.Assembly.GetTypes()
|
||||||
|
.Where(t => t.IsSubclassOf(typeof(LogicGate)) && !t.IsAbstract);
|
||||||
|
|
||||||
|
foreach (Type type in logicGateTypes)
|
||||||
|
{
|
||||||
|
LogicGateDescriptionAttribute? descriptionAttribute = type.GetCustomAttributes(typeof(LogicGateDescriptionAttribute), false).FirstOrDefault() as LogicGateDescriptionAttribute;
|
||||||
|
if (type.GetCustomAttributes(typeof(LogicGateNameAttribute), false).FirstOrDefault() is LogicGateNameAttribute nameAttribute)
|
||||||
|
{
|
||||||
|
LogicGateInfo logicGateInfo = new LogicGateInfo(nameAttribute.Name, descriptionAttribute?.Description);
|
||||||
|
|
||||||
|
if (logicGates.ContainsKey(logicGateInfo))
|
||||||
|
{
|
||||||
|
logicGateInfo.TypeName += "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
logicGates.Add(logicGateInfo, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<LogicGateInfo> GetNativeLogicGatesInfos()
|
||||||
|
{
|
||||||
|
return logicGates.Keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogicGate CreateLogicGate(string typeName)
|
||||||
|
{
|
||||||
|
Type type = logicGates[new LogicGateInfo(typeName, null)];
|
||||||
|
|
||||||
|
return (LogicGate)Activator.CreateInstance(type)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetTypeName(Type type)
|
||||||
|
{
|
||||||
|
LogicGateNameAttribute nameAttribute = (LogicGateNameAttribute)type.GetCustomAttributes(typeof(LogicGateNameAttribute), false)[0];
|
||||||
|
return nameAttribute.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetTypeName(Type type, [NotNullWhen(true)] out string? typeName)
|
||||||
|
{
|
||||||
|
if (type.GetCustomAttributes(typeof(LogicGateNameAttribute), false).FirstOrDefault() is not LogicGateNameAttribute nameAttribute)
|
||||||
|
{
|
||||||
|
typeName = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeName = nameAttribute.Name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryCreateLogicGate(string typeName, [NotNullWhen(true)] out LogicGate? logicGate)
|
||||||
|
{
|
||||||
|
if (!logicGates.ContainsKey(new LogicGateInfo(typeName, null)))
|
||||||
|
{
|
||||||
|
logicGate = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type = logicGates[new LogicGateInfo(typeName, null)];
|
||||||
|
|
||||||
|
logicGate = (LogicGate)Activator.CreateInstance(type)!;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LogicGateInfo
|
||||||
|
{
|
||||||
|
public string TypeName { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public LogicGateInfo(string name, string? description)
|
||||||
|
{
|
||||||
|
TypeName = name;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return TypeName.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is LogicGateInfo logicGateInfo)
|
||||||
|
{
|
||||||
|
return logicGateInfo.TypeName == TypeName;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
using StoneRed.LogicSimulator.WorldSaveSystem.WorldReaders;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.WorldSaveSystem;
|
||||||
|
|
||||||
|
internal class WorldLoader
|
||||||
|
{
|
||||||
|
private readonly Srls srls;
|
||||||
|
|
||||||
|
public WorldLoader(Srls srls)
|
||||||
|
{
|
||||||
|
this.srls = srls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<IEnumerable<LogicGate>>> LoadWorld(string filePath, IProgress<WorldSaveLoadProgress> progress)
|
||||||
|
{
|
||||||
|
BinaryReader binaryReader = new BinaryReader(File.OpenRead(filePath));
|
||||||
|
ushort fileVersion = binaryReader.ReadUInt16();
|
||||||
|
binaryReader.Close();
|
||||||
|
|
||||||
|
IWorldReader? worldReader = fileVersion switch
|
||||||
|
{
|
||||||
|
1 => new WorldReaderV1(srls),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (worldReader is null)
|
||||||
|
{
|
||||||
|
return Task.FromResult(Result.Fail<IEnumerable<LogicGate>>("Invalid file version!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return worldReader.ReadWorld(filePath, progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.WorldSaveSystem.WorldReaders;
|
||||||
|
|
||||||
|
internal interface IWorldReader
|
||||||
|
{
|
||||||
|
Task<Result<IEnumerable<LogicGate>>> ReadWorld(string filePath, IProgress<WorldSaveLoadProgress> progress);
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.WorldSaveSystem.WorldReaders;
|
||||||
|
|
||||||
|
internal class WorldReaderV1 : IWorldReader
|
||||||
|
{
|
||||||
|
private readonly Srls srls;
|
||||||
|
|
||||||
|
public WorldReaderV1(Srls srls)
|
||||||
|
{
|
||||||
|
this.srls = srls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<IEnumerable<LogicGate>>> ReadWorld(string filePath, IProgress<WorldSaveLoadProgress> progress)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return Result.Fail($"File \"{filePath}\" does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Task.Run(() => InternalReadWorld(filePath, progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<IEnumerable<LogicGate>> InternalReadWorld(string filePath, IProgress<WorldSaveLoadProgress> progress)
|
||||||
|
{
|
||||||
|
BinaryReader? reader = null;
|
||||||
|
Dictionary<LogicGate, List<(ulong GateRefId, int inputIndex, int outputIndex)>> connections = new();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
progress.Report(new(0, "Opening file"));
|
||||||
|
|
||||||
|
reader = new BinaryReader(File.OpenRead(filePath));
|
||||||
|
_ = reader.ReadUInt16(); // File version
|
||||||
|
|
||||||
|
int numberOfLogicGates = reader.ReadInt32();
|
||||||
|
|
||||||
|
for (int gateNumber = 0; gateNumber < numberOfLogicGates; gateNumber++)
|
||||||
|
{
|
||||||
|
progress.Report(new((int)((double)gateNumber / numberOfLogicGates * 50d), $"Loading logic gates ({gateNumber / numberOfLogicGates})"));
|
||||||
|
|
||||||
|
ulong id = reader.ReadUInt64();
|
||||||
|
string typeName = reader.ReadString();
|
||||||
|
int inputCount = reader.ReadInt32();
|
||||||
|
int outputCount = reader.ReadInt32();
|
||||||
|
int numberOfConnections = reader.ReadInt32();
|
||||||
|
|
||||||
|
if (srls.LogicGatesManager.TryCreateLogicGate(typeName, out LogicGate? logicGate))
|
||||||
|
{
|
||||||
|
logicGate.Id = id;
|
||||||
|
logicGate.InputCount = inputCount;
|
||||||
|
logicGate.OutputCount = outputCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Result.Fail($"Logic gate type \"{typeName}\" does not exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
connections.Add(logicGate, new());
|
||||||
|
|
||||||
|
for (int connectionNumber = 0; connectionNumber < numberOfConnections; connectionNumber++)
|
||||||
|
{
|
||||||
|
ulong gateRefId = reader.ReadUInt64();
|
||||||
|
int inputIndex = reader.ReadInt32();
|
||||||
|
int outputIndex = reader.ReadInt32();
|
||||||
|
|
||||||
|
connections[logicGate].Add((gateRefId, inputIndex, outputIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int connectionCount = 0;
|
||||||
|
|
||||||
|
foreach (KeyValuePair<LogicGate, List<(ulong gateRefId, int inputIndex, int outputIndex)>> connectionPair in connections)
|
||||||
|
{
|
||||||
|
progress.Report(new((int)((double)connections.Count / connectionCount * 50d), $"Connecting logic gates ({connections.Count / connectionCount})"));
|
||||||
|
|
||||||
|
LogicGate logicGate = connectionPair.Key;
|
||||||
|
|
||||||
|
foreach ((ulong gateRefId, int inputIndex, int outputIndex) in connectionPair.Value)
|
||||||
|
{
|
||||||
|
LogicGate? connectedGate = connections.Keys.FirstOrDefault(g => g.Id == gateRefId);
|
||||||
|
|
||||||
|
if (connectedGate is null)
|
||||||
|
{
|
||||||
|
return Result.Fail($"Logic gate connection {logicGate.Id} -> {gateRefId} does not exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
logicGate.Connect(connectedGate, inputIndex, outputIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
reader?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connections.Keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace StoneRed.LogicSimulator.WorldSaveSystem;
|
||||||
|
|
||||||
|
internal class WorldSaveLoadProgress
|
||||||
|
{
|
||||||
|
public int Percentage { get; }
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
|
public WorldSaveLoadProgress(int percentage, string message)
|
||||||
|
{
|
||||||
|
Percentage = percentage;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace StoneRed.LogicSimulator.WorldSaveSystem;
|
||||||
|
|
||||||
|
internal class WorldSaver
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.WorldSaveSystem.WorldWriters;
|
||||||
|
|
||||||
|
internal interface IWorldWriter
|
||||||
|
{
|
||||||
|
Task<Result> WriteWorld(string filePath, IEnumerable<LogicGate> logicGates, IProgress<WorldSaveLoadProgress> progress);
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||||
|
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.WorldSaveSystem.WorldWriters;
|
||||||
|
|
||||||
|
internal class WorldWriterV1 : IWorldWriter
|
||||||
|
{
|
||||||
|
private readonly Srls srls;
|
||||||
|
|
||||||
|
public WorldWriterV1(Srls srls)
|
||||||
|
{
|
||||||
|
this.srls = srls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result> WriteWorld(string filePath, IEnumerable<LogicGate> logicGates, IProgress<WorldSaveLoadProgress> progress)
|
||||||
|
{
|
||||||
|
return Task.Run(() => InternalWriteWorld(filePath, logicGates, progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result InternalWriteWorld(string filePath, IEnumerable<LogicGate> logicGates, IProgress<WorldSaveLoadProgress> progress)
|
||||||
|
{
|
||||||
|
BinaryWriter? writer = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
writer = new BinaryWriter(File.OpenWrite(filePath));
|
||||||
|
|
||||||
|
progress.Report(new(0, "Counting logic gates"));
|
||||||
|
|
||||||
|
int numberOfLogicGates = logicGates.Count();
|
||||||
|
|
||||||
|
writer.Write(1); // Write file version
|
||||||
|
writer.Write(numberOfLogicGates);
|
||||||
|
|
||||||
|
foreach (LogicGate logicGate in logicGates)
|
||||||
|
{
|
||||||
|
writer.Write(logicGate.Id);
|
||||||
|
|
||||||
|
if (!srls.LogicGatesManager.TryGetTypeName(logicGate.GetType(), out string? typeName))
|
||||||
|
{
|
||||||
|
return Result.Fail($"Logic gate type \"{logicGate.GetType().FullName}\" has no \"{nameof(LogicGateNameAttribute)}\" attribute!");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(typeName);
|
||||||
|
writer.Write(logicGate.InputCount);
|
||||||
|
writer.Write(logicGate.OutputCount);
|
||||||
|
writer.Write(logicGate.LogicGateConnections.Count);
|
||||||
|
|
||||||
|
foreach (LogicGateConnection connection in logicGate.LogicGateConnections)
|
||||||
|
{
|
||||||
|
writer.Write(connection.LogicGate.Id);
|
||||||
|
writer.Write(connection.InputIndex);
|
||||||
|
writer.Write(connection.OutputIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
writer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user