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.ExportOptions />
|
||||
<VerticalStackPanel BorderThickness="1" Padding="0, 10, 0, 5" Background="#333130FF" Border="#1BA1E2FF">
|
||||
<Label Text="rteterert" 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" />
|
||||
<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" Id="button" />
|
||||
<VerticalMenu HorizontalAlignment="Stretch" Margin="0, 10, 0, 0" BorderThickness="0, 1, 0, 0" Padding="0, 10, 0, 5" Background="#333130FF" Id="menu" />
|
||||
</VerticalStackPanel>
|
||||
</Project>
|
||||
@@ -1,9 +1,11 @@
|
||||
<Project StylesheetPath="stylesheet/default_ui_skin.xmms">
|
||||
<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">
|
||||
<MenuItem Text="Main Menu" Id="menuMainMenu" />
|
||||
<MenuItem Text="Settings" Id="menuSettings" />
|
||||
<MenuItem Text="Save" Id="menuSave" />
|
||||
<MenuItem Text="Load" Id="menuLoad" />
|
||||
<MenuItem Text="Quit" Id="menuQuit" />
|
||||
</VerticalMenu>
|
||||
</Window>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project StylesheetPath="stylesheet/default_ui_skin.xmms">
|
||||
<Project.ExportOptions />
|
||||
<Grid>
|
||||
<Grid CanSelectNothing="True" >
|
||||
<Grid.ColumnsProportions>
|
||||
<Proportion Type="Part" />
|
||||
<Proportion Type="Part" Value="3" />
|
||||
@@ -9,12 +9,19 @@
|
||||
<VerticalStackPanel Id="info">
|
||||
<Label Text="FPS:" TextColor="#CF56BFFF" Id="fps" />
|
||||
<Label Text="FRQ: " TextColor="#CF56BFFF" Id="ups" />
|
||||
<Label Text="X/Y: " TextColor="#CF56BFFF" Id="position" />
|
||||
</VerticalStackPanel>
|
||||
<Panel GridColumn="2" Background="#4BD961FF">
|
||||
<VerticalStackPanel Id="settings">
|
||||
<Label Text="Frequency:" />
|
||||
<SpinButton Maximum="1E+09" Minimum="1" HorizontalAlignment="Stretch" Value="100" Integer="True" Id="frequency" />
|
||||
<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>
|
||||
</Panel>
|
||||
</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
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, LogicGate> logicGates = new ConcurrentDictionary<int, LogicGate>();
|
||||
private readonly ConcurrentDictionary<ulong, LogicGate> logicGates = new ConcurrentDictionary<ulong, LogicGate>();
|
||||
private DateTime dateTime;
|
||||
private bool logicGatesUpdated = false;
|
||||
private ulong logicGateId = 0;
|
||||
public int TargetTicksPerSecond { get; set; } = 100;
|
||||
|
||||
public int ActualTicksPerSecond { get; private set; }
|
||||
@@ -22,7 +24,7 @@ internal class LogicGateSimulator
|
||||
|
||||
public bool HighPerformanceClock { get; set; }
|
||||
|
||||
public LogicGateSimulator(List<LogicGate> logicGates)
|
||||
public LogicGateSimulator(IEnumerable<LogicGate> logicGates)
|
||||
{
|
||||
foreach (LogicGate logicGate in logicGates)
|
||||
{
|
||||
@@ -49,11 +51,11 @@ internal class LogicGateSimulator
|
||||
|
||||
public void AddLogicGate(LogicGate gate)
|
||||
{
|
||||
gate.Id = logicGates.Count;
|
||||
_ = logicGates.TryAdd(gate.Id, gate);
|
||||
gate.Id = logicGateId++;
|
||||
logicGatesUpdated = logicGates.TryAdd(gate.Id, gate);
|
||||
}
|
||||
|
||||
public LogicGate GetLogicGate(int id)
|
||||
public LogicGate GetLogicGate(ulong id)
|
||||
{
|
||||
return logicGates[id];
|
||||
}
|
||||
@@ -65,7 +67,12 @@ internal class LogicGateSimulator
|
||||
|
||||
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()
|
||||
@@ -79,6 +86,12 @@ internal class LogicGateSimulator
|
||||
{
|
||||
tps++;
|
||||
|
||||
if (logicGatesUpdated)
|
||||
{
|
||||
logicGatesArray = logicGates.Values.ToArray();
|
||||
logicGatesUpdated = false;
|
||||
}
|
||||
|
||||
if (DateTime.Now > dateTime.AddSeconds(1))
|
||||
{
|
||||
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 StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||
|
||||
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
|
||||
{
|
||||
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 Color Color { get; set; } = Color.Purple;
|
||||
public string Info { get; set; } = "OFF";
|
||||
|
||||
public Button()
|
||||
{
|
||||
Metadata.Name = "Button";
|
||||
}
|
||||
|
||||
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
||||
{
|
||||
IsPressed = mouseState.IsButtonDown(MouseButton.Left);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
using MonoGame.Extended.Input;
|
||||
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||
|
||||
using System;
|
||||
|
||||
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
|
||||
{
|
||||
private int count = 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
|
||||
{
|
||||
@@ -27,11 +30,6 @@ internal class Clock : LogicGate, IInteractable
|
||||
}
|
||||
}
|
||||
|
||||
public Clock()
|
||||
{
|
||||
Metadata.Name = "Clock";
|
||||
}
|
||||
|
||||
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
||||
{
|
||||
if (mouseState.DeltaScrollWheelValue == 0 || !keyboardStateExtended.IsShiftDown())
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||
#pragma warning disable S112 // General exceptions should never be thrown
|
||||
@@ -14,12 +15,12 @@ internal abstract class LogicGate
|
||||
private int output;
|
||||
private int cachedOutput;
|
||||
|
||||
public LogicGateMetadata Metadata { get; set; } = new LogicGateMetadata();
|
||||
public int Id { get; set; }
|
||||
public LogicGateWorldData WorldData { get; init; } = new LogicGateWorldData();
|
||||
public ulong Id { get; internal set; }
|
||||
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()
|
||||
{
|
||||
@@ -58,6 +59,13 @@ internal abstract class LogicGate
|
||||
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)
|
||||
{
|
||||
int index = logicGateConnections.FindIndex(c => c.LogicGate.Id == logicGate.Id);
|
||||
@@ -81,6 +89,17 @@ internal abstract class LogicGate
|
||||
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 int GetInputBit(int index)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
+1
-3
@@ -2,12 +2,10 @@
|
||||
|
||||
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||
|
||||
internal record LogicGateMetadata
|
||||
internal record LogicGateWorldData
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public Vector2 Size { get; set; } = new Vector2(100, 100);
|
||||
|
||||
public string Name { 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;
|
||||
|
||||
[LogicGateName("Pin")]
|
||||
[LogicGateDescription("A pin is a input or output.")]
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -2,25 +2,23 @@
|
||||
|
||||
using MonoGame.Extended.Input;
|
||||
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes;
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||
|
||||
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
|
||||
{
|
||||
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 Color Color { get; set; } = Color.Purple;
|
||||
public string Info { get; set; } = "OFF";
|
||||
|
||||
public Switch()
|
||||
{
|
||||
Metadata.Name = "Switch";
|
||||
}
|
||||
|
||||
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
||||
{
|
||||
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.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
using MonoGame.Extended.Screens;
|
||||
|
||||
@@ -33,6 +32,8 @@ internal class Srls : Game
|
||||
public FontSystem FontSystem { get; private set; } = null!;
|
||||
public AssetManager AssetManager { get; private set; } = null!;
|
||||
|
||||
public LogicGatesManager LogicGatesManager { get; private set; } = null!;
|
||||
|
||||
public Settings Settings { get; set; }
|
||||
|
||||
public string ContentPath { get; }
|
||||
@@ -98,13 +99,17 @@ internal class Srls : Game
|
||||
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"));
|
||||
VerticalStackPanel contextMenu = (VerticalStackPanel)Project.LoadFromXml(data, AssetManager).Root;
|
||||
|
||||
contextMenu.FindChildById<Label>("title").Text = title;
|
||||
|
||||
TextButton button = contextMenu.FindChildById<TextButton>("button");
|
||||
|
||||
button.Visible = showButton;
|
||||
|
||||
VerticalMenu menu = contextMenu.FindChildById<VerticalMenu>("menu");
|
||||
|
||||
foreach (MenuItem menuItem in menuItems)
|
||||
@@ -114,6 +119,8 @@ internal class Srls : Game
|
||||
|
||||
Desktop.ShowContextMenu(contextMenu, position);
|
||||
Desktop.ContextMenu.Scale = new Vector2(Scale / 3, Scale / 3);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
KeyboardState keyboardState = Keyboard.GetState();
|
||||
if (keyboardState.IsKeyDown(Keys.Q))
|
||||
{
|
||||
ShowWindow<QuickMenu>();
|
||||
}
|
||||
|
||||
base.Update(gameTime);
|
||||
}
|
||||
|
||||
@@ -168,6 +169,9 @@ internal class Srls : Game
|
||||
FileAssetResolver assetResolver = new FileAssetResolver(ContentPath);
|
||||
AssetManager = new AssetManager(assetResolver);
|
||||
|
||||
LogicGatesManager = new LogicGatesManager();
|
||||
LogicGatesManager.LoadLogicGates();
|
||||
|
||||
LoadScreen<StartScreen>();
|
||||
|
||||
base.Initialize();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<EmbeddedResource Include="Icon.bmp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentResults" Version="3.15.2" />
|
||||
<PackageReference Include="MonoGame.Extended" Version="3.8.0" />
|
||||
<PackageReference Include="MonoGame.Extended.Entities" 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 StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||
using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces;
|
||||
using StoneRed.LogicSimulator.WorldSaveSystem;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
||||
|
||||
internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
||||
{
|
||||
private readonly string filePath;
|
||||
private Label label = null!;
|
||||
private HorizontalProgressBar horizontalProgressBar = null!;
|
||||
|
||||
protected override string XmmpPath => "LoadingScreen.xmmp";
|
||||
|
||||
public LoadingScreen(string filePath)
|
||||
{
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
label = Root.FindChildById<Label>("label");
|
||||
@@ -27,25 +36,32 @@ internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
||||
|
||||
protected override void LoadContent()
|
||||
{
|
||||
Progress<WorldSaveLoadProgress> progress = new Progress<WorldSaveLoadProgress>();
|
||||
progress.ProgressChanged += Progress_ProgressChanged;
|
||||
|
||||
WorldLoader worldLoader = new WorldLoader(srls);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
Result<IEnumerable<LogicGate>> result = await worldLoader.LoadWorld(filePath, progress);
|
||||
if (result.IsFailed)
|
||||
{
|
||||
horizontalProgressBar.Value++;
|
||||
await Task.Delay(1);
|
||||
Dialog.CreateMessageBox("Error", string.Join(',', result.Errors.Select(e => e.Message))).Show(srls.Desktop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update(GameTime gameTime)
|
||||
{
|
||||
return;
|
||||
|
||||
label.Text = $"Loading... {Math.Round(100 / horizontalProgressBar.Maximum * horizontalProgressBar.Value, 0)}%";
|
||||
if (horizontalProgressBar.Value >= horizontalProgressBar.Maximum)
|
||||
{
|
||||
Clock button = new Clock()
|
||||
{
|
||||
Id = 0,
|
||||
Metadata = new LogicGateMetadata()
|
||||
WorldData = new LogicGateWorldData()
|
||||
{
|
||||
Name = "Clock",
|
||||
Position = new Vector2(0, 0)
|
||||
@@ -55,50 +71,50 @@ internal class LoadingScreen : SrlsScreen<VerticalStackPanel>
|
||||
Switch @switch = new Switch()
|
||||
{
|
||||
Id = 1,
|
||||
Metadata = new LogicGateMetadata()
|
||||
WorldData = new LogicGateWorldData()
|
||||
{
|
||||
Name = "Switch",
|
||||
Position = new Vector2(0, 150)
|
||||
Position = new Vector2(0, 200)
|
||||
}
|
||||
};
|
||||
|
||||
TestLogicGate testLogicGate1 = new TestLogicGate()
|
||||
NotGate testLogicGate1 = new NotGate()
|
||||
{
|
||||
Id = 2,
|
||||
Metadata = new LogicGateMetadata()
|
||||
WorldData = new LogicGateWorldData()
|
||||
{
|
||||
Name = "L1",
|
||||
Position = new Vector2(150, 150)
|
||||
Name = "Not Gate",
|
||||
Position = new Vector2(200, 200)
|
||||
}
|
||||
};
|
||||
|
||||
Pin testLogicGate2 = new Pin()
|
||||
{
|
||||
Id = 3,
|
||||
Metadata = new LogicGateMetadata()
|
||||
WorldData = new LogicGateWorldData()
|
||||
{
|
||||
Name = "Pin",
|
||||
Position = new Vector2(300, 300)
|
||||
Position = new Vector2(400, 400)
|
||||
}
|
||||
};
|
||||
|
||||
Pin testLogicGate3 = new Pin()
|
||||
{
|
||||
Id = 4,
|
||||
Metadata = new LogicGateMetadata()
|
||||
WorldData = new LogicGateWorldData()
|
||||
{
|
||||
Name = "Pin",
|
||||
Position = new Vector2(450, 450)
|
||||
Position = new Vector2(500, 500)
|
||||
}
|
||||
};
|
||||
|
||||
Lamp lamp = new Lamp()
|
||||
{
|
||||
Id = 5,
|
||||
Metadata = new LogicGateMetadata()
|
||||
WorldData = new LogicGateWorldData()
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
private bool initialized;
|
||||
private bool loadedContent;
|
||||
|
||||
public GameScreenWrapper(Game game) : base(game)
|
||||
{
|
||||
}
|
||||
@@ -69,13 +72,21 @@ internal abstract class SrlsScreen
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = true;
|
||||
OnInitialize?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadContent()
|
||||
{
|
||||
if (!loadedContent)
|
||||
{
|
||||
loadedContent = true;
|
||||
OnLoadContent?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UnloadContent()
|
||||
{
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
using Myra.Graphics2D.UI;
|
||||
using Myra.Graphics2D.UI.File;
|
||||
|
||||
using StoneRed.LogicSimulator.UserInterface.Windows;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
||||
|
||||
internal class StartScreen : SrlsScreen<VerticalStackPanel>
|
||||
{
|
||||
protected override string XmmpPath => "StartScreen.xmmp";
|
||||
|
||||
protected override void LoadContent()
|
||||
protected override void Initialize()
|
||||
{
|
||||
VerticalMenu menu = Root.FindChildById<VerticalMenu>("menu");
|
||||
menu.FindMenuItemById("menuQuit").Selected += (s, a) => srls.Exit();
|
||||
menu.FindMenuItemById("menuSettings").Selected += (s, a) => srls.ShowWindow<SettingsWindow>();
|
||||
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)
|
||||
@@ -26,4 +29,24 @@ internal class StartScreen : SrlsScreen<VerticalStackPanel>
|
||||
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.LogicGates.Interfaces;
|
||||
using StoneRed.LogicSimulator.UserInterface.Windows;
|
||||
using StoneRed.LogicSimulator.Utilities;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using IColorable = StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces.IColorable;
|
||||
|
||||
@@ -22,26 +25,34 @@ namespace StoneRed.LogicSimulator.UserInterface.Screens;
|
||||
internal class WorldScreen : SrlsScreen<Grid>
|
||||
{
|
||||
private readonly LogicGateSimulator simulator;
|
||||
private readonly Vector2 logicGateSize = new Vector2(100, 100);
|
||||
private OrthographicCamera camera = null!;
|
||||
private RichTextLayout richTextLayout = null!;
|
||||
|
||||
private Label fpsLabel = null!;
|
||||
private Label upsLabel = null!;
|
||||
private Label positionLabel = null!;
|
||||
private SpinButton frequency = null!;
|
||||
private CheckBox highPerformance = null!;
|
||||
private ListBox nativeComponentsListBox = null!;
|
||||
private float fps;
|
||||
|
||||
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";
|
||||
|
||||
public WorldScreen(List<LogicGate> logicGates)
|
||||
public WorldScreen(IEnumerable<LogicGate> logicGates)
|
||||
{
|
||||
simulator = new LogicGateSimulator(logicGates);
|
||||
}
|
||||
|
||||
public WorldScreen()
|
||||
{
|
||||
simulator = new LogicGateSimulator(new List<LogicGate>());
|
||||
simulator = new LogicGateSimulator(Enumerable.Empty<LogicGate>());
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
@@ -49,11 +60,26 @@ internal class WorldScreen : SrlsScreen<Grid>
|
||||
VerticalStackPanel infoPanel = Root.FindChildById<VerticalStackPanel>("info");
|
||||
fpsLabel = infoPanel.FindChildById<Label>("fps");
|
||||
upsLabel = infoPanel.FindChildById<Label>("ups");
|
||||
positionLabel = infoPanel.FindChildById<Label>("position");
|
||||
|
||||
VerticalStackPanel settingsPanel = Root.FindChildById<VerticalStackPanel>("settings");
|
||||
frequency = settingsPanel.FindChildById<SpinButton>("frequency");
|
||||
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)
|
||||
{
|
||||
MinimumZoom = 0.1f,
|
||||
@@ -68,11 +94,6 @@ internal class WorldScreen : SrlsScreen<Grid>
|
||||
{
|
||||
fps = 1f / gameTime.GetElapsedSeconds();
|
||||
|
||||
RichTextLayout richTextLayout = new RichTextLayout
|
||||
{
|
||||
Font = srls.FontSystem.GetFont(15)
|
||||
};
|
||||
|
||||
Matrix transformMatrix = camera.GetViewMatrix();
|
||||
srls.SpriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: transformMatrix);
|
||||
|
||||
@@ -87,22 +108,37 @@ internal class WorldScreen : SrlsScreen<Grid>
|
||||
if (logicGate is IInteractable interactable)
|
||||
{
|
||||
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);
|
||||
|
||||
srls.SpriteBatch.FillRectangle(logicGate.Metadata.Position * srls.Scale, logicGate.Metadata.Size * srls.Scale, color, 0.2f);
|
||||
// Draw logic gate
|
||||
srls.SpriteBatch.FillRectangle(logicGate.WorldData.Position * srls.Scale, logicGateSize * srls.Scale, color, 0.2f);
|
||||
|
||||
// Draw logic gate connections
|
||||
foreach (LogicGateConnection connection in logicGate.LogicGateConnections)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -125,12 +161,15 @@ internal class WorldScreen : SrlsScreen<Grid>
|
||||
}
|
||||
|
||||
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.HighPerformanceClock = highPerformance.IsChecked;
|
||||
|
||||
fpsLabel.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();
|
||||
KeyboardStateExtended keyboardState = KeyboardExtended.GetState();
|
||||
@@ -139,14 +178,18 @@ internal class WorldScreen : SrlsScreen<Grid>
|
||||
|
||||
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 (mouseState.IsButtonDown(MouseButton.Right))
|
||||
if (mouseState.IsButtonDown(MouseButton.Right) && selectedLogicGate is null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
interactable.OnInteraction(mouseState, previousMouseState, keyboardState);
|
||||
@@ -161,13 +204,42 @@ internal class WorldScreen : SrlsScreen<Grid>
|
||||
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;
|
||||
|
||||
float movementSpeed = (float)Math.Pow(200, 2 - camera.Zoom);
|
||||
|
||||
movementSpeed = Math.Clamp(movementSpeed, 1000, 20000);
|
||||
|
||||
camera.Move(GetMovementDirection(keyboardState) * movementSpeed * gameTime.GetElapsedSeconds());
|
||||
camera.Move(keyboardState.GetMovementDirection() * movementSpeed * gameTime.GetElapsedSeconds());
|
||||
|
||||
if (!keyboardState.IsShiftDown())
|
||||
{
|
||||
@@ -187,64 +259,131 @@ internal class WorldScreen : SrlsScreen<Grid>
|
||||
simulator.Stop();
|
||||
}
|
||||
|
||||
private static Vector2 GetMovementDirection(KeyboardStateExtended keyboardState)
|
||||
private static float RealMod(float x, float m)
|
||||
{
|
||||
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;
|
||||
float r = x % m;
|
||||
return r < 0 ? r + m : r;
|
||||
}
|
||||
|
||||
private static string CalculateFrequency(double hz)
|
||||
private void NativeComponentsListBox_SelectedIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
double khz = hz / 1000d;
|
||||
double mhz = khz / 1000d;
|
||||
double ghz = mhz / 1000d;
|
||||
|
||||
if (ghz >= 1)
|
||||
if (nativeComponentsListBox.SelectedItem is not null)
|
||||
{
|
||||
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";
|
||||
connectionContext = null;
|
||||
selectedLogicGate = srls.LogicGatesManager.CreateLogicGate(nativeComponentsListBox.SelectedItem.Text);
|
||||
selectedLogicGate.WorldData.Name = nativeComponentsListBox.SelectedItem.Text;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowConnectionContextMenu(LogicGate logicGate, Point position, bool showInputs)
|
||||
{
|
||||
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;
|
||||
|
||||
MenuItem[] menuItems = new MenuItem[count];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
srls.ShowContextMenu(logicGate.Metadata.Name, position, menuItems);
|
||||
connectionStatus = isConnected ? "[Disconnect]" : "[Connect]";
|
||||
}
|
||||
|
||||
menuItems[i] = new MenuItem(i.ToString(), $"{connectionStatus} " + (showInputs ? $"Input {i}" : $"Output {i}"));
|
||||
menuItems[i].Selected += (_, _) => OnConnectionClicked(logicGate, index, showInputs);
|
||||
}
|
||||
|
||||
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 StoneRed.LogicSimulator.UserInterface.Screens;
|
||||
|
||||
namespace StoneRed.LogicSimulator.UserInterface.Windows;
|
||||
@@ -13,6 +14,8 @@ internal class QuickMenu : SrlsWindow
|
||||
|
||||
menu.FindMenuItemById("menuMainMenu").Selected += (s, a) => srls.LoadScreen<StartScreen>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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