diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..405934cd Binary files /dev/null and b/.DS_Store differ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..77540501 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,96 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '00 12 * * *' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + strategy: + fail-fast: false + matrix: + include: + - language: csharp + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore index dc00d664..38f083ac 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ # Visual Studio cache directory .vs/ +# Visual Studio Code directory +.vscode/ + # Gradle cache directory .gradle/ @@ -71,3 +74,4 @@ crashlytics-build.properties /[Aa]ssets/[Ss]treamingAssets/aa/* /.idea /.vsconfig +/TestData/* diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..ddb6ff85 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "visualstudiotoolsforunity.vstuc" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..da60e25a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Unity", + "type": "vstuc", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..12dd4d4f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,70 @@ +{ + "files.exclude": { + "**/.DS_Store": true, + "**/.git": true, + "**/.vs": true, + "**/.gitmodules": true, + "**/.vsconfig": true, + "**/*.booproj": true, + "**/*.pidb": true, + "**/*.suo": true, + "**/*.user": true, + "**/*.userprefs": true, + "**/*.unityproj": true, + "**/*.dll": true, + "**/*.exe": true, + "**/*.pdf": true, + "**/*.mid": true, + "**/*.midi": true, + "**/*.wav": true, + "**/*.gif": true, + "**/*.ico": true, + "**/*.jpg": true, + "**/*.jpeg": true, + "**/*.png": true, + "**/*.psd": true, + "**/*.tga": true, + "**/*.tif": true, + "**/*.tiff": true, + "**/*.3ds": true, + "**/*.3DS": true, + "**/*.fbx": true, + "**/*.FBX": true, + "**/*.lxo": true, + "**/*.LXO": true, + "**/*.ma": true, + "**/*.MA": true, + "**/*.obj": true, + "**/*.OBJ": true, + "**/*.asset": true, + "**/*.cubemap": true, + "**/*.flare": true, + "**/*.mat": true, + "**/*.meta": true, + "**/*.prefab": true, + "**/*.unity": true, + "build/": true, + "Build/": true, + "Library/": true, + "library/": true, + "obj/": true, + "Obj/": true, + "Logs/": true, + "logs/": true, + "ProjectSettings/": true, + "UserSettings/": true, + "temp/": true, + "Temp/": true + }, + "files.associations": { + "*.asset": "yaml", + "*.meta": "yaml", + "*.prefab": "yaml", + "*.unity": "yaml", + }, + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.patterns": { + "*.sln": "*.csproj", + }, + "dotnet.defaultSolution": "Digital-Logic-Sim.sln" +} \ No newline at end of file diff --git a/Assets/Build/DLS.unity b/Assets/Build/DLS.unity index 209cbc4f..677f1b78 100644 --- a/Assets/Build/DLS.unity +++ b/Assets/Build/DLS.unity @@ -244,7 +244,7 @@ MonoBehaviour: openInMainMenu: 0 testProjectName: MainTest openA: 1 - chipToOpenA: BuzzTest + chipToOpenA: SPSTest chipToOpenB: TEST MergeSplit testVecA: {x: 100, y: 0.5333334} testVecB: {x: 1.71, y: 0.0025} diff --git a/Assets/Scripts/Description/CustomStopwatch.cs b/Assets/Scripts/Description/CustomStopwatch.cs new file mode 100644 index 00000000..8fec4cf5 --- /dev/null +++ b/Assets/Scripts/Description/CustomStopwatch.cs @@ -0,0 +1,36 @@ +using System; +using System.Diagnostics; +using System.Runtime.Serialization; +using Newtonsoft.Json; + +// Originally on DLS.SaveSystem, now on DLS.Description because of assembly definitions. +namespace DLS.Description { + public class CustomStopwatch { + [JsonIgnore] + public Stopwatch Stopwatch; + public TimeSpan StartFrom; + [JsonIgnore] + public TimeSpan Elapsed {get => StartFrom.Add(Stopwatch.Elapsed);} + [JsonConstructor] + public CustomStopwatch(TimeSpan StartFrom) { + Stopwatch = Stopwatch.StartNew(); + this.StartFrom = StartFrom; + } + [OnSerializing] + public void Save(StreamingContext unused) + { + StartFrom = Elapsed; + Stopwatch = Stopwatch.StartNew(); + } + public void Save() + { + StartFrom = Elapsed; + Stopwatch = Stopwatch.StartNew(); + } + public CustomStopwatch() { + Stopwatch = Stopwatch.StartNew(); + StartFrom = new(); + } + + } +} \ No newline at end of file diff --git a/Assets/Scripts/Description/CustomStopwatch.cs.meta b/Assets/Scripts/Description/CustomStopwatch.cs.meta new file mode 100644 index 00000000..8ec18bfc --- /dev/null +++ b/Assets/Scripts/Description/CustomStopwatch.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 65d145e050f884d4fa29a1eb85460172 \ No newline at end of file diff --git a/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs b/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs index 3d1e60d0..0c723db0 100644 --- a/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs +++ b/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using UnityEngine.TextCore.Text; namespace DLS.Description { @@ -14,16 +15,12 @@ public static class ChipTypeHelper { ChipType.Clock, "CLOCK" }, { ChipType.Pulse, "PULSE" }, { ChipType.TriStateBuffer, "3-STATE BUFFER" }, + { ChipType.Constant_8Bit, "CONST" }, + { ChipType.Detector, "DETECTOR" }, // ---- Memory ---- - { ChipType.dev_Ram_8Bit, "dev.RAM-8" }, + { ChipType.dev_Ram_8Bit, "RAM-8" }, { ChipType.Rom_256x16, $"ROM 256{mulSymbol}16" }, - // ---- Split / Merge ---- - { ChipType.Split_4To1Bit, "4-1BIT" }, - { ChipType.Split_8To1Bit, "8-1BIT" }, - { ChipType.Split_8To4Bit, "8-4BIT" }, - { ChipType.Merge_4To8Bit, "4-8BIT" }, - { ChipType.Merge_1To8Bit, "1-8BIT" }, - { ChipType.Merge_1To4Bit, "1-4BIT" }, + { ChipType.EEPROM_256x16, $"EEPROM 256{mulSymbol}16" }, // ---- Displays ----- { ChipType.DisplayRGB, "RGB DISPLAY" }, @@ -33,80 +30,72 @@ public static class ChipTypeHelper { ChipType.Buzzer, "BUZZER" }, + { ChipType.SPS, "SPS" }, + { ChipType.RTC, "RTC" }, + // ---- Not really chips (but convenient to treat them as such anyway) ---- // ---- Inputs/Outputs ---- - { ChipType.In_1Bit, "IN-1" }, - { ChipType.In_4Bit, "IN-4" }, - { ChipType.In_8Bit, "IN-8" }, - { ChipType.Out_1Bit, "OUT-1" }, - { ChipType.Out_4Bit, "OUT-4" }, - { ChipType.Out_8Bit, "OUT-8" }, { ChipType.Key, "KEY" }, - // ---- Buses ---- - { ChipType.Bus_1Bit, "BUS-1" }, - { ChipType.Bus_4Bit, "BUS-4" }, - { ChipType.Bus_8Bit, "BUS-8" }, - { ChipType.BusTerminus_1Bit, "BUS-TERMINUS-1" }, - { ChipType.BusTerminus_4Bit, "BUS-TERMINUS-4" }, - { ChipType.BusTerminus_8Bit, "BUS-TERMINUS-8" } + { ChipType.Button, "BUTTON" }, + { ChipType.Toggle, "DIPSWITCH" }, + }; + public static string GetName(ChipType type) => Names[type]; public static bool IsBusType(ChipType type) => IsBusOriginType(type) || IsBusTerminusType(type); - public static bool IsBusOriginType(ChipType type) => type is ChipType.Bus_1Bit or ChipType.Bus_4Bit or ChipType.Bus_8Bit; + public static bool IsBusOriginType(ChipType type) => type is ChipType.Bus; - public static bool IsBusTerminusType(ChipType type) => type is ChipType.BusTerminus_1Bit or ChipType.BusTerminus_4Bit or ChipType.BusTerminus_8Bit; + public static bool IsBusTerminusType(ChipType type) => type is ChipType.BusTerminus; - public static bool IsRomType(ChipType type) => type == ChipType.Rom_256x16; + public static bool IsRomType(ChipType type) => type == ChipType.Rom_256x16 || type == ChipType.EEPROM_256x16; - public static ChipType GetCorrespondingBusTerminusType(ChipType type) + public static (bool isInput, bool isOutput, PinBitCount numBits) IsInputOrOutputPin(ChipDescription chip) { - return type switch + return chip.ChipType switch { - ChipType.Bus_1Bit => ChipType.BusTerminus_1Bit, - ChipType.Bus_4Bit => ChipType.BusTerminus_4Bit, - ChipType.Bus_8Bit => ChipType.BusTerminus_8Bit, - _ => throw new Exception("No corresponding bus terminus found for type: " + type) + ChipType.In_Pin => (true, false, chip.OutputPins[0].BitCount), + ChipType.Out_Pin => (false, true, chip.InputPins[0].BitCount), + _ => (false, false, new PinBitCount { BitCount = 1 }) }; } - public static ChipType GetPinType(bool isInput, PinBitCount numBits) + public static string GetDevPinName(bool isInput, PinBitCount numBits) { - if (isInput) - { - return numBits switch - { - PinBitCount.Bit1 => ChipType.In_1Bit, - PinBitCount.Bit4 => ChipType.In_4Bit, - PinBitCount.Bit8 => ChipType.In_8Bit, - _ => throw new Exception("No input pin type found for bitcount: " + numBits) - }; - } - - return numBits switch - { - PinBitCount.Bit1 => ChipType.Out_1Bit, - PinBitCount.Bit4 => ChipType.Out_4Bit, - PinBitCount.Bit8 => ChipType.Out_8Bit, - _ => throw new Exception("No output pin type found for bitcount: " + numBits) - }; + return (isInput ? "IN-" : "OUT-") + numBits.BitCount.ToString(); } - public static (bool isInput, bool isOutput, PinBitCount numBits) IsInputOrOutputPin(ChipType type) + public static string GetBusName(PinBitCount numBits) { - return type switch - { - ChipType.In_1Bit => (true, false, PinBitCount.Bit1), - ChipType.Out_1Bit => (false, true, PinBitCount.Bit1), - ChipType.In_4Bit => (true, false, PinBitCount.Bit4), - ChipType.Out_4Bit => (false, true, PinBitCount.Bit4), - ChipType.In_8Bit => (true, false, PinBitCount.Bit8), - ChipType.Out_8Bit => (false, true, PinBitCount.Bit8), - _ => (false, false, PinBitCount.Bit1) - }; + return "BUS-" + numBits.ToString(); + } + + public static string GetBusTerminusName(PinBitCount numBits) + { + return "BUS-TERMINUS-" + numBits.ToString(); + } + + + public static bool IsDevPin(ChipType chipType) + { + return chipType == ChipType.In_Pin || chipType == ChipType.Out_Pin; + } + public static bool IsClickableDisplayType(ChipType type) { + // Return true for any chiptype that is a clickable display + + return type == ChipType.Button || type == ChipType.Toggle; + } + + public static bool IsInternalDataModifiable(ChipType type) { + return type == ChipType.EEPROM_256x16 || type == ChipType.Toggle; + } + + public static bool IsMergeSplitChip(ChipType chipType) + { + return chipType == ChipType.Split_Pin || chipType == ChipType.Merge_Pin; } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Assets/Scripts/Description/Serialization/Serializer.cs b/Assets/Scripts/Description/Serialization/Serializer.cs index 4ee50c55..259d48d9 100644 --- a/Assets/Scripts/Description/Serialization/Serializer.cs +++ b/Assets/Scripts/Description/Serialization/Serializer.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; @@ -23,6 +26,7 @@ static JsonSerializerSettings CreateSerializationSettings() settings.Converters.Add(new Vector2Converter()); settings.Converters.Add(new ColorConverter()); settings.Converters.Add(new DateTimeConverter()); + settings.Converters.Add(new PinBitCountConverter()); return settings; } @@ -110,8 +114,24 @@ public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer) => (DateTime)reader.Value; } + + public class PinBitCountConverter : JsonConverter + { + + + public override void WriteJson(JsonWriter writer, [AllowNull] PinBitCount value, JsonSerializer serializer) + { + string formattedBitCount = value.BitCount.ToString(); + writer.WriteValue(formattedBitCount); + } - class CustomJsonTextWriter : JsonTextWriter + public override PinBitCount ReadJson(JsonReader reader, Type objectType, [AllowNull] PinBitCount existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return new PinBitCount(ushort.Parse(reader.Value.ToString())); + } + } + + class CustomJsonTextWriter : JsonTextWriter { int arrayDepth; @@ -147,5 +167,6 @@ protected override void WriteIndentSpace() base.WriteIndentSpace(); } } + } } \ No newline at end of file diff --git a/Assets/Scripts/Description/Types/ChipDescription.cs b/Assets/Scripts/Description/Types/ChipDescription.cs index b5b0e67f..0c6ee89a 100644 --- a/Assets/Scripts/Description/Types/ChipDescription.cs +++ b/Assets/Scripts/Description/Types/ChipDescription.cs @@ -21,6 +21,8 @@ public class ChipDescription public SubChipDescription[] SubChips; public WireDescription[] Wires; public DisplayDescription[] Displays; + public NoteDescription[] Notes; + public bool HasCustomLayout = false; // ---- Convenience Functions ---- public bool HasDisplay() => Displays != null && Displays.Length > 0; diff --git a/Assets/Scripts/Description/Types/ProjectDescription.cs b/Assets/Scripts/Description/Types/ProjectDescription.cs index a8dc0f33..3ef6e5d8 100644 --- a/Assets/Scripts/Description/Types/ProjectDescription.cs +++ b/Assets/Scripts/Description/Types/ProjectDescription.cs @@ -4,11 +4,12 @@ namespace DLS.Description { - public struct ProjectDescription + public struct ProjectDescription { public string ProjectName; public string DLSVersion_LastSaved; public string DLSVersion_EarliestCompatible; + public string DLSVersion_LastSavedModdedVersion; public DateTime CreationTime; public DateTime LastSaveTime; @@ -21,6 +22,11 @@ public struct ProjectDescription public bool Prefs_SimPaused; public int Prefs_SimTargetStepsPerSecond; public int Prefs_SimStepsPerClockTick; + public int Perfs_PinIndicators; + + // Stats + public ulong StepsRanSinceCreated; + public CustomStopwatch /* We should ask Stack Overflow why we cannot access this class from outside its namespace */ TimeSpentSinceCreated; // List of all player-created chips (in order of creation -- oldest first) public string[] AllCustomChipNames; @@ -28,8 +34,15 @@ public struct ProjectDescription public List StarredList; public List ChipCollections; - // ---- Helper functions ---- - public bool IsStarred(string chipName, bool isCollection) + // List of all I/O (in order of creation -- oldest first) + public List pinBitCounts; + + // Used both for Merge Chips and Split Chips + // Dictionnary of Big Pin and Small Pin Ex : (4,1) or (8,4) or (8,1) + public List> SplitMergePairs; + + // ---- Helper functions ---- + public bool IsStarred(string chipName, bool isCollection) { foreach (StarredItem item in StarredList) { @@ -38,9 +51,22 @@ public bool IsStarred(string chipName, bool isCollection) return false; } + + public void AddChipToCollection(string collectionName, string chipName) { + if(collectionName == null) throw new ArgumentNullException(collectionName); + foreach(ChipCollection collection in ChipCollections) + { + if(collection.Name.Equals(chipName, StringComparison.OrdinalIgnoreCase)) + { + collection.Chips.Add(chipName); + } + } + + } } - public struct StarredItem + + public struct StarredItem { public string Name; public bool IsCollection; diff --git a/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs b/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs index 610f0298..d1f64e4a 100644 --- a/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs +++ b/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs @@ -9,10 +9,12 @@ public enum ChipType TriStateBuffer, Clock, Pulse, + Detector, // ---- Memory ---- dev_Ram_8Bit, Rom_256x16, + EEPROM_256x16, // ---- Displays ---- SevenSegmentDisplay, @@ -21,33 +23,31 @@ public enum ChipType DisplayLED, // ---- Merge / Split ---- - Merge_1To4Bit, - Merge_1To8Bit, - Merge_4To8Bit, - Split_4To1Bit, - Split_8To4Bit, - Split_8To1Bit, + Merge_Pin, + Split_Pin, // ---- In / Out Pins ---- - In_1Bit, - In_4Bit, - In_8Bit, - Out_1Bit, - Out_4Bit, - Out_8Bit, - - Key, - - // ---- Buses ---- - Bus_1Bit, - BusTerminus_1Bit, - Bus_4Bit, - BusTerminus_4Bit, - Bus_8Bit, - BusTerminus_8Bit, + In_Pin, + Out_Pin, + + Key, + + Button, + Toggle, + + Constant_8Bit, + + // ---- Buses ---- + Bus, + BusTerminus, // ---- Audio ---- - Buzzer + Buzzer, + + // ---- Time ---- + RTC, + // ---- Clock ---- + SPS, } } \ No newline at end of file diff --git a/Assets/Scripts/Description/Types/SubTypes/NoteDescription.cs b/Assets/Scripts/Description/Types/SubTypes/NoteDescription.cs new file mode 100644 index 00000000..3ed628a8 --- /dev/null +++ b/Assets/Scripts/Description/Types/SubTypes/NoteDescription.cs @@ -0,0 +1,32 @@ +using UnityEngine; + +namespace DLS.Description +{ + public class NoteDescription + { + public Vector2 Size; + public NoteColour Colour; + public string Text; // Text content of the note + public Vector2 Position; + public int ID; + + public NoteDescription(int id, NoteColour colour, string text, Vector2 position) + { + ID = id; + Colour = colour; + Text = text; + Position = position; + } + } + + public enum NoteColour + { + Red, + Yellow, + Green, + Blue, + Violet, + Pink, + White + } +} \ No newline at end of file diff --git a/Assets/Scripts/Description/Types/SubTypes/NoteDescription.cs.meta b/Assets/Scripts/Description/Types/SubTypes/NoteDescription.cs.meta new file mode 100644 index 00000000..7d75a264 --- /dev/null +++ b/Assets/Scripts/Description/Types/SubTypes/NoteDescription.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e671a94ba3f6d401286fb933621efb6d \ No newline at end of file diff --git a/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs b/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs index 62745260..31a22c87 100644 --- a/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs +++ b/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections; using UnityEngine; namespace DLS.Description @@ -10,8 +12,11 @@ public struct PinDescription public PinBitCount BitCount; public PinColour Colour; public PinValueDisplayMode ValueDisplayMode; + public int face; // Which edge of the chip the pin is on: 0 = top, 1 = right, 2 = bottom, 3 = left + public float LocalOffset; //offset on chip edge for pin location + - public PinDescription(string name, int id, Vector2 position, PinBitCount bitCount, PinColour colour, PinValueDisplayMode valueDisplayMode) + public PinDescription(string name, int id, Vector2 position, PinBitCount bitCount, PinColour colour, PinValueDisplayMode valueDisplayMode, float localoff = 0) { Name = name; ID = id; @@ -19,17 +24,80 @@ public PinDescription(string name, int id, Vector2 position, PinBitCount bitCoun BitCount = bitCount; Colour = colour; ValueDisplayMode = valueDisplayMode; - } + LocalOffset = localoff; + face = 1; + } } - public enum PinBitCount + public struct PinBitCount { - Bit1 = 1, - Bit4 = 4, - Bit8 = 8 - } + public const int Bit1 = 1; + public const int Bit4 = 4; + public const int Bit8 = 8; + public const int Bit16 = 16; + public const int Bit32 = 32; + + public int BitCount; + + public override string ToString() + { + return BitCount.ToString(); + } + public PinBitCount(ushort BitCount = 1) + { + this.BitCount = BitCount; + } + public readonly BitArray GetEmptyBitArray() + { + return new BitArray(length: BitCount<<1); + } + + public override bool Equals(object @object) + { + if(@object is uint number) + { + return BitCount == number; + } + return base.Equals(@object); + } + + public override int GetHashCode() + { + return HashCode.Combine(BitCount); + } + + public int GetTier() + { + if (BitCount <= 64) return 0; + else if (BitCount <= 512) return 1; + else if (BitCount <= 4096) return 2; + else return 3; + } + + public static bool operator ==(PinBitCount a, PinBitCount b) => a.BitCount == b.BitCount; + public static bool operator !=(PinBitCount a, PinBitCount b) => a.BitCount != b.BitCount; + + public static bool operator ==(uint number, PinBitCount b) => number == b.BitCount; + public static bool operator !=(uint number, PinBitCount b) => number != b.BitCount; + public static bool operator ==(PinBitCount a, uint number) => number == a.BitCount; + public static bool operator !=(PinBitCount a, uint number) => number != a.BitCount; + + public static bool operator ==(PinBitCount a, int number) => number == a.BitCount; + public static bool operator !=(PinBitCount a, int number) => number != a.BitCount; + + + public static implicit operator uint(PinBitCount b) => (uint)b.BitCount; + public static implicit operator int(PinBitCount b) => b.BitCount; + public static implicit operator ushort(PinBitCount b) => (ushort)b.BitCount; + public static implicit operator PinBitCount(long b) => new PinBitCount((ushort)b); + + + public static explicit operator PinBitCount(ushort number) => new PinBitCount(number); + public static explicit operator PinBitCount(int number) => new PinBitCount((ushort)number); + + } - public enum PinColour + public enum PinColour { Red, Orange, diff --git a/Assets/Scripts/Game/Elements/DevPinInstance.cs b/Assets/Scripts/Game/Elements/DevPinInstance.cs index 7827e687..b698a09d 100644 --- a/Assets/Scripts/Game/Elements/DevPinInstance.cs +++ b/Assets/Scripts/Game/Elements/DevPinInstance.cs @@ -10,7 +10,7 @@ namespace DLS.Game { public class DevPinInstance : IMoveable { - public readonly PinBitCount BitCount; + public PinBitCount BitCount; public readonly char[] decimalDisplayCharBuffer = new char[16]; // Size/Layout info @@ -19,8 +19,8 @@ public class DevPinInstance : IMoveable public readonly bool IsInputPin; public readonly string Name; public readonly PinInstance Pin; - public readonly Vector2Int StateGridDimensions; - public readonly Vector2 StateGridSize; + public Vector2Int StateGridDimensions; + public Vector2 StateGridSize; public PinValueDisplayMode pinValueDisplayMode; @@ -37,18 +37,9 @@ public DevPinInstance(PinDescription pinDescription, bool isInput) // Calculate layout info faceDir = new Vector2(IsInputPin ? 1 : -1, 0); - StateGridDimensions = BitCount switch - { - PinBitCount.Bit1 => new Vector2Int(1, 1), - PinBitCount.Bit4 => new Vector2Int(2, 2), - PinBitCount.Bit8 => new Vector2Int(4, 2), - _ => throw new Exception("Bit count not implemented") - }; - StateGridSize = BitCount switch - { - PinBitCount.Bit1 => Vector2.one * (DevPinStateDisplayRadius * 2 + DevPinStateDisplayOutline * 2), - _ => (Vector2)StateGridDimensions * MultiBitPinStateDisplaySquareSize + Vector2.one * DevPinStateDisplayOutline - }; + StateGridDimensions = GridHelper.GetStateGridDimension(BitCount.BitCount); + StateGridSize = BitCount.BitCount == 1 ? Vector2.one * (DevPinStateDisplayRadius * 2 + DevPinStateDisplayOutline * 2) : (Vector2)StateGridDimensions * MultiBitPinStateDisplaySquareSize + Vector2.one * DevPinStateDisplayOutline; + } public Vector2 HandlePosition => Position; @@ -56,10 +47,14 @@ public DevPinInstance(PinDescription pinDescription, bool isInput) public Vector2 PinPosition { - get + get { - int gridDst = BitCount is PinBitCount.Bit1 or PinBitCount.Bit4 ? 6 : 9; - return HandlePosition + faceDir * (GridSize * gridDst); + if(BitCount.BitCount is 1 or 4 or 8) + { + return HandlePosition + faceDir * (GridSize * (BitCount.BitCount is 1 or 4 ? 6 : 9)); + } + + return StateDisplayPosition + faceDir * (StateGridSize.x / 2 + 2 * GridSize); } } @@ -88,12 +83,12 @@ public bool ShouldBeIncludedInSelectionBox(Vector2 selectionCentre, Vector2 sele public int GetStateDecimalDisplayValue() { - uint rawValue = PinState.GetBitStates(Pin.State); + uint rawValue = Pin.State.GetValue(); int displayValue = (int)rawValue; if (pinValueDisplayMode == PinValueDisplayMode.SignedDecimal) { - displayValue = Maths.TwosComplement(rawValue, (int)BitCount); + displayValue = Maths.TwosComplement(rawValue, Math.Min((int)BitCount,32)); } return displayValue; @@ -119,7 +114,7 @@ Bounds2D CreateBoundingBox(float pad) public void ToggleState(int bitIndex) { - PinState.Toggle(ref Pin.PlayerInputState, bitIndex); + Pin.PlayerInputState.ToggleBit(bitIndex); } public bool PointIsInInteractionBounds(Vector2 point) => PointIsInHandleBounds(point) || PointIsInStateIndicatorBounds(point); @@ -127,5 +122,12 @@ public void ToggleState(int bitIndex) public bool PointIsInStateIndicatorBounds(Vector2 point) => Maths.PointInCircle2D(point, StateDisplayPosition, DevPinStateDisplayRadius); public bool PointIsInHandleBounds(Vector2 point) => HandleBounds().PointInBounds(point); - } + + public void ChangeBitCount(ushort bitcount) + { + BitCount.BitCount = bitcount; + StateGridDimensions = GridHelper.GetStateGridDimension(BitCount.BitCount); + StateGridSize = BitCount.BitCount == 1 ? Vector2.one * (DevPinStateDisplayRadius * 2 + DevPinStateDisplayOutline * 2) : (Vector2)StateGridDimensions * MultiBitPinStateDisplaySquareSize + Vector2.one * DevPinStateDisplayOutline; + } + } } \ No newline at end of file diff --git a/Assets/Scripts/Game/Elements/DisplayInstance.cs b/Assets/Scripts/Game/Elements/DisplayInstance.cs index 7ae4338e..f2f54a12 100644 --- a/Assets/Scripts/Game/Elements/DisplayInstance.cs +++ b/Assets/Scripts/Game/Elements/DisplayInstance.cs @@ -4,7 +4,7 @@ namespace DLS.Game { - public class DisplayInstance + public class DisplayInstance : IClickable { public List ChildDisplays; public DisplayDescription Desc; diff --git a/Assets/Scripts/Game/Elements/NoteInstance.cs b/Assets/Scripts/Game/Elements/NoteInstance.cs new file mode 100644 index 00000000..117966e1 --- /dev/null +++ b/Assets/Scripts/Game/Elements/NoteInstance.cs @@ -0,0 +1,92 @@ +using System; +using DLS.Graphics; +using Seb.Helpers; +using Seb.Types; +using Seb.Vis; +using UnityEngine; +using static DLS.Graphics.DrawSettings; +using DLS.Description; + + +namespace DLS.Game +{ + public class NoteInstance : IMoveable + { + public readonly NoteDescription Description; + // Position of the note in the simulation + public Vector2 Position { get; set; } + + // Position when the move operation started + public Vector2 MoveStartPosition { get; set; } + + // Reference point for straight-line movement + public Vector2 StraightLineReferencePoint { get; set; } + + // Indicates if a reference point for straight-line movement exists + public bool HasReferencePointForStraightLineMovement { get; set; } + + // Indicates if the note is currently selected + public bool IsSelected { get; set; } + + // Indicates if the current position is valid for movement + public bool IsValidMovePos { get; set; } + + // Snap point for grid alignment (can be overridden if needed) + public virtual Vector2 SnapPoint => Position; + + // Bounding box for selection + + // Bounding box for the note + public Bounds2D BoundingBox => Bounds2D.CreateFromCentreAndSize(Position + Size / 2, Size); + public virtual Bounds2D SelectionBoundingBox => Bounds2D.CreateFromCentreAndSize(Position + Size / 2, Size + new Vector2(DrawSettings.ChipOutlineWidth + DrawSettings.SelectionBoundsPadding, DrawSettings.ChipOutlineWidth + DrawSettings.SelectionBoundsPadding)); + + // Unique identifier for the note + public int ID { get; private set; } + + // Text content of the note + public string Text { get; set; } + + // Dimensions of the note + public Vector2 Size { get; set; } + public NoteColour Colour; + + // Constructor + public NoteInstance(NoteDescription desc) + { + Description = desc; + ID = desc.ID; + Position = desc.Position; + Text = desc.Text; + Size = desc.Size; + Colour = desc.Colour; + IsSelected = false; + IsValidMovePos = true; + Resize(); + } + + // Determines if the note should be included in a selection box + public bool ShouldBeIncludedInSelectionBox(Vector2 selectionCentre, Vector2 selectionSize) + { + var halfSelectionSize = selectionSize / 2; + var halfNoteSize = Size / 2; + + return Math.Abs(Position.x - selectionCentre.x) <= (halfSelectionSize.x + halfNoteSize.x) && + Math.Abs(Position.y - selectionCentre.y) <= (halfSelectionSize.y + halfNoteSize.y); + } + + public void Resize() + { + Vector2 minSize = new Vector2(2f, 2f); + Size = minSize; + Vector2 textSize = Draw.CalculateTextBoundsSize(Text, FontSizeNoteText, DrawSettings.ActiveUITheme.FontBold); + if (textSize.x > minSize.x) + { + Size = new Vector2(textSize.x + 1f, Size.y); + } + if (textSize.y + 1f > minSize.y) + { + Size = new Vector2(Size.x, textSize.y + 1f); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Game/Elements/NoteInstance.cs.meta b/Assets/Scripts/Game/Elements/NoteInstance.cs.meta new file mode 100644 index 00000000..3c7713f4 --- /dev/null +++ b/Assets/Scripts/Game/Elements/NoteInstance.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 638b89b0327cd46ff903e71b095bc4dd \ No newline at end of file diff --git a/Assets/Scripts/Game/Elements/PinInstance.cs b/Assets/Scripts/Game/Elements/PinInstance.cs index 2d610eb7..e88978ed 100644 --- a/Assets/Scripts/Game/Elements/PinInstance.cs +++ b/Assets/Scripts/Game/Elements/PinInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using DLS.Description; using DLS.Graphics; using DLS.Simulation; @@ -10,21 +11,23 @@ public class PinInstance : IInteractable { public readonly PinAddress Address; - public readonly PinBitCount bitCount; + public PinBitCount bitCount; public readonly bool IsBusPin; public readonly bool IsSourcePin; // Pin may be attached to a chip or a devPin as its parent public readonly IMoveable parent; - public uint State; // sim state - public uint PlayerInputState; // dev input pins only + public PinStateValue State; // sim state + public PinStateValue PlayerInputState; public PinColour Colour; bool faceRight; public float LocalPosY; public string Name; + public int face; + public int ID; + - - public PinInstance(PinDescription desc, PinAddress address, IMoveable parent, bool isSourcePin) + public PinInstance(PinDescription desc, PinAddress address, IMoveable parent, bool isSourcePin) { this.parent = parent; bitCount = desc.BitCount; @@ -33,34 +36,74 @@ public PinInstance(PinDescription desc, PinAddress address, IMoveable parent, bo IsSourcePin = isSourcePin; Colour = desc.Colour; - IsBusPin = parent is SubChipInstance subchip && subchip.IsBus; + IsBusPin = parent is SubChipInstance subchip && subchip.IsBus; faceRight = isSourcePin; - PinState.SetAllDisconnected(ref State); + desc.face = faceRight ? 1 : 3; // 1 for right, 3 for left + face = faceRight ? 1 : 3; + State.SetAllDisconnected(); + ID = desc.ID; + LocalPosY = desc.LocalOffset; + State.MakeFromPinBitCount(bitCount); + PlayerInputState.MakeFromPinBitCount(bitCount); } public Vector2 ForwardDir => faceRight ? Vector2.right : Vector2.left; + public Vector2 FacingDir => face == 1 ? Vector2.right : face == 3 ? Vector2.left : face == 2 ? Vector2.down : Vector2.up; + public Vector2 GetWorldPos() + { + switch (parent) + { + case DevPinInstance devPin: + return devPin.PinPosition; + case SubChipInstance subchip: + { + Vector2 chipSize = subchip.Size; + Vector2 chipPos = subchip.Position; - public Vector2 GetWorldPos() - { - switch (parent) - { - case DevPinInstance devPin: - return devPin.PinPosition; - case SubChipInstance subchip: - { - Vector2 chipSize = subchip.Size; - Vector2 chipPos = subchip.Position; - - float xLocal = (chipSize.x / 2 + DrawSettings.ChipOutlineWidth / 2 - DrawSettings.SubChipPinInset) * (faceRight ? 1 : -1); - return chipPos + new Vector2(xLocal, LocalPosY); - } - default: - throw new Exception("Parent type not supported"); - } - } + float halfWidth = chipSize.x / 2f; + float halfHeight = chipSize.y / 2f; + float inset = DrawSettings.SubChipPinInset; + float outlineOffset = DrawSettings.ChipOutlineWidth / 2f; + + + float x = 0f; + float y = 0f; + + switch (face) + { + case 0: // Top edge (Y fixed) + x = LocalPosY; + y = halfHeight + outlineOffset - inset; + break; + + case 1: // Right edge (X fixed) + x = halfWidth + outlineOffset - inset; + y = LocalPosY; + break; - public void SetBusFlip(bool flipped) + case 2: // Bottom edge (Y fixed) + x = LocalPosY; + y = -halfHeight - outlineOffset + inset; + break; + + case 3: // Left edge (X fixed) + x = -halfWidth - outlineOffset + inset; + y = LocalPosY; + break; + + default: + throw new Exception("Invalid pin face: " + face); + } + + return chipPos + new Vector2(x, y); + } + default: + throw new Exception("Parent type not supported"); + } + } + + public void SetBusFlip(bool flipped) { faceRight = IsSourcePin ^ flipped; } @@ -68,14 +111,18 @@ public void SetBusFlip(bool flipped) public Color GetColLow() => DrawSettings.ActiveTheme.StateLowCol[(int)Colour]; public Color GetColHigh() => DrawSettings.ActiveTheme.StateHighCol[(int)Colour]; - public Color GetStateCol(int bitIndex, bool hover = false, bool canUsePlayerState = true) + public Color GetStateCol(int bitIndex, bool hover = false, bool canUsePlayerState = true, bool forWires = false) { - uint pinState = (IsSourcePin && canUsePlayerState) ? PlayerInputState : State; // dev input pin uses player state (so it updates even when sim is paused) - uint state = PinState.GetBitTristatedValue(pinState, bitIndex); - - if (state == PinState.LogicDisconnected) return DrawSettings.ActiveTheme.StateDisconnectedCol; - return DrawSettings.GetStateColour(state == PinState.LogicHigh, (uint)Colour, hover); + PinStateValue pinState = (IsSourcePin && canUsePlayerState) ? PlayerInputState : State; // dev input pin uses player state (so it updates even when sim is paused) + uint state = pinState.GetTristatedValue(bitIndex); + if (state == PinStateValue.LOGIC_DISCONNECTED) return DrawSettings.ActiveTheme.StateDisconnectedCol; + if(forWires && bitCount >= 64) { return DrawSettings.GetFlatColour(state == PinStateValue.LOGIC_HIGH, (uint)Colour, hover); } + return DrawSettings.GetStateColour(state == PinStateValue.LOGIC_HIGH, (uint)Colour, hover); } + public void ChangeBitCount(int NewBitCount) + { + bitCount.BitCount = (ushort)NewBitCount; + } } } \ No newline at end of file diff --git a/Assets/Scripts/Game/Elements/SubChipInstance.cs b/Assets/Scripts/Game/Elements/SubChipInstance.cs index d85725ee..1f9410ff 100644 --- a/Assets/Scripts/Game/Elements/SubChipInstance.cs +++ b/Assets/Scripts/Game/Elements/SubChipInstance.cs @@ -23,12 +23,13 @@ public class SubChipInstance : IMoveable public readonly uint[] InternalData; public readonly bool IsBus; - public readonly Vector2 MinSize; + public Vector2 MinSize; public readonly string MultiLineName; public readonly PinInstance[] OutputPins; public string activationKeyString; // input char for the 'key chip' type (stored as string to avoid allocating when drawing) public string Label; + public bool HasCustomLayout; public SubChipInstance(ChipDescription description, SubChipDescription subChipDesc) { @@ -42,8 +43,14 @@ public SubChipInstance(ChipDescription description, SubChipDescription subChipDe MultiLineName = CreateMultiLineName(description.Name); MinSize = CalculateMinChipSize(description.InputPins, description.OutputPins, description.Name); + HasCustomLayout = description.HasCustomLayout; + InputPins = CreatePinInstances(description.InputPins, true); OutputPins = CreatePinInstances(description.OutputPins, false); + if (HasCustomLayout) + { + LoadCustomLayout(description); + } AllPins = InputPins.Concat(OutputPins).ToArray(); LoadOutputPinColours(subChipDesc.OutputPinColourInfo); @@ -85,9 +92,12 @@ PinInstance[] CreatePinInstances(PinDescription[] pinDescriptions, bool isInputP PinAddress address = new(subChipDesc.ID, desc.ID); pins[i] = new PinInstance(desc, address, this, !isInputPin); } + if (!HasCustomLayout) + { + // If no custom layout, then calculate the default layout - CalculatePinLayout(pins); - + CalculatePinLayout(pins); + } return pins; } } @@ -136,11 +146,20 @@ public void SetKeyChipActivationChar(char c) public void UpdatePinLayout() { - CalculatePinLayout(InputPins); - CalculatePinLayout(OutputPins); - } + if (!HasCustomLayout) + { + CalculatePinLayout(InputPins); + CalculatePinLayout(OutputPins); + } + else + { + PinInstance[] combinedPins = InputPins.Concat(OutputPins).ToArray(); + CustomLayout(combinedPins); + + } + } - void CalculatePinLayout(PinInstance[] pins) + void CalculatePinLayout(PinInstance[] pins) { // If only one pin, it should be placed in the centre if (pins.Length == 1) @@ -177,6 +196,66 @@ void CalculatePinLayout(PinInstance[] pins) } } + void CustomLayout(PinInstance[] pins) + { + if (pins == null || pins.Length == 0) return; + + foreach (int face in new[] { 0, 1, 2, 3 }) + { + var facePins = pins.Where(p => p.face == face).ToList(); + if (facePins.Count == 0) continue; + + bool isHorizontal = face == 0 || face == 2; + float chipSpan = isHorizontal ? Size.x : Size.y; + + float GetHalfHeight(PinInstance pin) => PinHeightFromBitCount(pin.bitCount) / 2f; + float GetRequiredSpacing(PinInstance a, PinInstance b) + => GetHalfHeight(a) + GetHalfHeight(b) + DrawSettings.GridSize; + float GetMinBound(PinInstance pin) => -chipSpan / 2f + GetHalfHeight(pin); + float GetMaxBound(PinInstance pin) => chipSpan / 2f - GetHalfHeight(pin); + + void Clamp(PinInstance pin) + => pin.LocalPosY = Mathf.Clamp(pin.LocalPosY, GetMinBound(pin), GetMaxBound(pin)); + + foreach (var pin in facePins) + Clamp(pin); + + // Sweeps negative to positive (left to right / bottom to top) + var sweepLowToHigh = facePins.OrderBy(p => p.LocalPosY).ToList(); + for (int i = 1; i < sweepLowToHigh.Count; i++) + { + var prev = sweepLowToHigh[i - 1]; + var curr = sweepLowToHigh[i]; + + float spacing = GetRequiredSpacing(prev, curr); + float delta = curr.LocalPosY - prev.LocalPosY; + + if (delta < spacing) + { + curr.LocalPosY = prev.LocalPosY + spacing; + Clamp(curr); + } + } + // now sweep positive to negative (right to left / top to bottom) to ensure both ends are within chip + var sweepHighToLow = facePins.OrderByDescending(p => p.LocalPosY).ToList(); + for (int i = 1; i < sweepHighToLow.Count; i++) + { + var prev = sweepHighToLow[i - 1]; + var curr = sweepHighToLow[i]; + + float spacing = GetRequiredSpacing(prev, curr); + float delta = prev.LocalPosY - curr.LocalPosY; + + if (delta < spacing) + { + curr.LocalPosY = prev.LocalPosY - spacing; + Clamp(curr); + } + } + } + } + public void SetCustomLayout(bool SetCustom) => this.HasCustomLayout = SetCustom; + // Min chip height based on input and output pins public static float MinChipHeightForPins(PinDescription[] inputs, PinDescription[] outputs) => Mathf.Max(MinChipHeightForPins(inputs), MinChipHeightForPins(outputs)); @@ -186,8 +265,64 @@ public static float MinChipHeightForPins(PinDescription[] pins) return CalculateDefaultPinLayout(pins.Select(p => p.BitCount).ToArray()).chipHeight; } - // Calculate minimal height of chip to fit the given pins, and calculate their y positions (in grid space) - public static (float chipHeight, float[] pinGridY) CalculateDefaultPinLayout(PinBitCount[] pins) + + //updates min size of chip based on which pins are on which faces, needed for custom layouts + public void updateMinSize() + { + PinInstance[] pins = InputPins.Concat(OutputPins).ToArray(); + if (pins == null || pins.Length == 0) return; + float Min0 = 0f; + float Min1 = 0f; + float Min2 = 0f; + float Min3 = 0f; + foreach (PinInstance pin in pins) + { + + int pinGridHeight = pin.bitCount.BitCount switch + { + PinBitCount.Bit1 => 2, + PinBitCount.Bit4 => 3, + PinBitCount.Bit8 => 4, + _ => Mathf.RoundToInt(PinHeightFromBitCount(pin.bitCount) /DrawSettings.GridSize) + }; + + if (pin.face == 0) + { + Min0 += pinGridHeight; + } + else if (pin.face == 1) + { + Min1 += pinGridHeight; + } + else if (pin.face == 2) + { + Min2 += pinGridHeight; + } + else + { + Min3 += pinGridHeight; + } + } + + float MinY = Mathf.Max(Min1, Min3); + float MinX = Mathf.Max(Min0, Min2); + + MinX = Mathf.Abs(MinX) * DrawSettings.GridSize; + MinY = Mathf.Abs(MinY) * DrawSettings.GridSize; + + string multiLineName = CreateMultiLineName(Description.Name); + bool hasMultiLineName = multiLineName != Description.Name; + float minNameHeight = DrawSettings.GridSize * (hasMultiLineName ? 4 : 3); + + Vector2 nameDrawBoundsSize = DevSceneDrawer.CalculateChipNameBounds(multiLineName); + + float sizeX = Mathf.Max(nameDrawBoundsSize.x + DrawSettings.GridSize, MinX); + float sizeY = Mathf.Max(minNameHeight, MinY); + MinSize = new Vector2(sizeX, sizeY); + } + + // Calculate minimal height of chip to fit the given pins, and calculate their y positions (in grid space) + public static (float chipHeight, float[] pinGridY) CalculateDefaultPinLayout(PinBitCount[] pins) { int gridY = 0; // top float[] pinGridYVals = new float[pins.Length]; @@ -195,12 +330,14 @@ public static (float chipHeight, float[] pinGridY) CalculateDefaultPinLayout(Pin for (int i = 0; i < pins.Length; i++) { PinBitCount pinBitCount = pins[i]; - int pinGridHeight = pinBitCount switch + int pinGridHeight = pinBitCount.BitCount switch { PinBitCount.Bit1 => 2, PinBitCount.Bit4 => 3, - _ => 4 - }; + PinBitCount.Bit8 => 4, + _ => Mathf.RoundToInt(PinHeightFromBitCount(pinBitCount) / DrawSettings.GridSize) + + }; pinGridYVals[i] = gridY - pinGridHeight / 2f; gridY -= pinGridHeight; @@ -304,13 +441,18 @@ public static Vector2 CalculateMinChipSize(PinDescription[] inputPins, PinDescri public static float PinHeightFromBitCount(PinBitCount bitCount) { - return bitCount switch + return bitCount.BitCount switch { PinBitCount.Bit1 => DrawSettings.PinRadius * 2, - PinBitCount.Bit4 => DrawSettings.PinHeight4Bit, + PinBitCount.Bit4 => DrawSettings.PinHeight4Bit, PinBitCount.Bit8 => DrawSettings.PinHeight8Bit, - _ => throw new Exception("Bit count not implemented " + bitCount) - }; + _ => (GetPinDepthMultiplier(bitCount) * bitCount.BitCount * DrawSettings.PinHeightPerBit) + 0.03f, + }; + } + + public static float GetPinDepthMultiplier(PinBitCount bitCount) + { + return (float)Math.Pow(8, -bitCount.GetTier()); } // Split chip name into two lines (if contains a space character) @@ -378,6 +520,12 @@ public void FlipBus() } } + public void UpdateInternalData(uint[] data) + { + if (!ChipTypeHelper.IsInternalDataModifiable(ChipType)) throw new Exception("Internal Data is not modifiable for the type of chip : " + ChipTypeHelper.GetName(ChipType)); + Array.Copy(data, InternalData, data.Length); + } + void LoadOutputPinColours(OutputPinColourInfo[] cols) { if (cols == null) return; @@ -393,5 +541,25 @@ void LoadOutputPinColours(OutputPinColourInfo[] cols) } } } - } + void LoadCustomLayout(ChipDescription chipDesc) + { + void ApplyLayout(PinInstance pin, PinDescription[] descriptions) + { + var desc = Array.Find(descriptions, d => d.ID == pin.ID); + if (desc.ID != 0) // found a matching description + { + pin.face = desc.face; + + pin.LocalPosY = desc.LocalOffset; + + } + } + + foreach (var pin in InputPins) + ApplyLayout(pin, chipDesc.InputPins); + + foreach (var pin in OutputPins) + ApplyLayout(pin, chipDesc.OutputPins); + } + } } \ No newline at end of file diff --git a/Assets/Scripts/Game/Elements/WireInstance.cs b/Assets/Scripts/Game/Elements/WireInstance.cs index afe28ec4..6e7371e3 100644 --- a/Assets/Scripts/Game/Elements/WireInstance.cs +++ b/Assets/Scripts/Game/Elements/WireInstance.cs @@ -312,16 +312,15 @@ public void RemoveConnectionDependency() public Color GetColour(int bitIndex) { - Color col = IsFullyConnected ? SourcePin.GetStateCol(bitIndex, false, false) : DrawSettings.ActiveTheme.StateDisconnectedCol; + Color col = IsFullyConnected ? SourcePin.GetStateCol(bitIndex, false, false, true) : DrawSettings.ActiveTheme.StateDisconnectedCol; - if (bitCount != PinBitCount.Bit1 && bitIndex % 2 == 0) + if (bitCount != PinBitCount.Bit1 && bitIndex % 2 == 0 && bitCount <= 64) { Color alternatingWireHighlightDisconnected = Color.white * 0.075f; Color alternatingWireHighlightConnected = Color.white * 0.01f; col += IsFullyConnected ? alternatingWireHighlightConnected : alternatingWireHighlightDisconnected; } - - return col; + return col; } public static Vector2 ClosestPointOnLineSegment(Vector2 p, Vector2 a1, Vector2 a2) diff --git a/Assets/Scripts/Game/Helpers/GridHelper.cs b/Assets/Scripts/Game/Helpers/GridHelper.cs index 63df3ee2..22789851 100644 --- a/Assets/Scripts/Game/Helpers/GridHelper.cs +++ b/Assets/Scripts/Game/Helpers/GridHelper.cs @@ -20,6 +20,11 @@ public static Vector2 SnapMovingElementToGrid(Vector2 centrePos, Vector2 anchorP return centrePos_Snapped; } + public static float ClampToGrid(float number, float min, float max) + { + return Mathf.Clamp(SnapToGrid(number), Mathf.Max(min,SnapToGrid(min)), Mathf.Min(max,SnapToGrid(max))); + } + public static float SnapToGrid(float v) { int intV = Mathf.RoundToInt(v / GridSize); @@ -52,5 +57,30 @@ public static Vector2 ForceStraightLine(Vector2 prev, Vector2 curr) return prev + offset; } + + public static Vector2Int GetStateGridDimension(int bitcount) + { + int h = 1; + int w = bitcount; + + int bestH = h; + int bestW = w; + + while (2 * h <= bitcount) + { + h++; + while (h * w > bitcount) + { + w--; + } + if (w * h == bitcount && w + h < bestH + bestW) + { + bestW = w; + bestH = h; + } + } + + return new Vector2Int(bestW, bestH); + } } } \ No newline at end of file diff --git a/Assets/Scripts/Game/Interaction/ChipInteractionController.cs b/Assets/Scripts/Game/Interaction/ChipInteractionController.cs index da9491d3..aca01f76 100644 --- a/Assets/Scripts/Game/Interaction/ChipInteractionController.cs +++ b/Assets/Scripts/Game/Interaction/ChipInteractionController.cs @@ -50,7 +50,7 @@ public class ChipInteractionController public bool CanInteractWithPin => CanInteract; public bool CanInteractWithPinStateDisplay => CanInteract && !IsCreatingWire && Project.ActiveProject.CanEditViewedChip; public bool CanInteractWithPinHandle => CanInteractWithPinStateDisplay; - + public bool CanInteractWithButton => CanInteract; public ChipInteractionController(Project project) { @@ -63,6 +63,17 @@ public void Update() HandleMouseInput(); } + public void Delete(IMoveable element, bool clearSelection = true, bool recordUndo = true) + { + if (!HasControl) return; + if (recordUndo) ActiveDevChip.UndoController.RecordDeleteElements(new List(new[] { element })); + if (element is SubChipInstance subChip) ActiveDevChip.DeleteSubChip(subChip); + if (element is NoteInstance noteInstance) ActiveDevChip.DeleteNote(noteInstance); + else if (element is DevPinInstance devPin) ActiveDevChip.DeleteDevPin(devPin); + + if (clearSelection) SelectedElements.Clear(); + } + // Don't allow interaction with wire that's currently being placed (this would allow it to try to connect to itself for example...) public bool CanInteractWithWire(WireInstance wire) => CanInteract && wire != WireToPlace; @@ -232,7 +243,8 @@ void HandleKeyboardInput() { CancelEverything(); } - } + + } void HandleMouseInput() { @@ -318,6 +330,40 @@ void DuplicateElements(List elements) // Get description of each element, and start placing a copy of it foreach (IMoveable element in elementsToDuplicate) { + ChipDescription desc; + + if (element is SubChipInstance subchip) + { + desc = subchip.Description; + } + else if (element is NoteInstance note) + { + // Create a new NoteDescription for the duplicated note + NoteDescription noteDesc = new NoteDescription( + IDGenerator.GenerateNewElementID(ActiveDevChip), + note.Colour, + note.Text, + note.Position + ); + + // Create a new NoteInstance and add it to the duplicated elements + IMoveable duplicatedNote = StartPlacingNote(noteDesc, note.Position, true); + duplicatedElements.Add(duplicatedNote); + duplicatedElementIDFromOriginalID.Add(note.ID, duplicatedNote.ID); + continue; + } + else + { + DevPinInstance devpin = (DevPinInstance)element; + ChipType pinType = ChipTypeHelper.GetPinType(devpin.IsInputPin, devpin.BitCount); + desc = BuiltinChipCreator.CreateInputOrOutputPin(pinType); + + // Copy pin description from duplicated pin + PinDescription pinDesc = DescriptionCreator.CreatePinDescription(devpin); + if (devpin.IsInputPin) desc.InputPins[0] = pinDesc; + else desc.OutputPins[0] = pinDesc; + } + IMoveable duplicatedElement = CreateElementFromDuplicationSource(element); StartPlacing(duplicatedElement, element.Position, true); duplicatedElement.StraightLineReferencePoint = element.Position; @@ -600,6 +646,7 @@ void FinishPlacingNewElements() ActiveDevChip.AddNewSubChip(subchip, false); } else if (elementToPlace is DevPinInstance devPin) ActiveDevChip.AddNewDevPin(devPin, false); + else if (elementToPlace is NoteInstance note) ActiveDevChip.AddNote(note, false); } foreach (WireInstance wire in DuplicatedWires) @@ -608,7 +655,9 @@ void FinishPlacingNewElements() wire.ApplyMoveOffset(); } + ActiveDevChip.UndoController.RecordAddElements(SelectedElements, DuplicatedWires.Count > 0); + DuplicatedWires.Clear(); OnFinishedPlacingItems(); @@ -889,7 +938,7 @@ void StartPlacing(IMoveable elementToPlace, Vector2 position, bool isDuplicating } ChipType chipType; - if (elementToPlace is DevPinInstance devPin) chipType = ChipTypeHelper.GetPinType(devPin.IsInputPin, devPin.BitCount); + if (elementToPlace is DevPinInstance devPinInstance) chipType = devPinInstance.IsInputPin ? ChipType.In_Pin : ChipType.Out_Pin; else chipType = ((SubChipInstance)elementToPlace).ChipType; // Place bus terminus to right of bus origin @@ -929,8 +978,7 @@ void StartPlacing(IMoveable elementToPlace, Vector2 position, bool isDuplicating { elementToPlace.MoveStartPosition -= Vector2.right * busPairSpacing / 2; - ChipType terminusType = ChipTypeHelper.GetCorrespondingBusTerminusType(chipType); - ChipDescription terminusDescription = Project.ActiveProject.chipLibrary.GetChipDescription(ChipTypeHelper.GetName(terminusType)); + ChipDescription terminusDescription = Project.ActiveProject.chipLibrary.GetTerminusDescription(((SubChipInstance)elementToPlace).OutputPins[0].bitCount); SubChipInstance terminus = (SubChipInstance)StartPlacing(terminusDescription, position, false); SubChipInstance busOrigin = (SubChipInstance)elementToPlace; @@ -945,13 +993,14 @@ IMoveable CreateElementFromChipDescription(ChipDescription chipDescription) int instanceID = IDGenerator.GenerateNewElementID(ActiveDevChip); // Input/output dev pins are represented as chips for convenience - (bool isInput, bool isOutput, PinBitCount numBits) ioPinInfo = ChipTypeHelper.IsInputOrOutputPin(chipDescription.ChipType); + (bool isInput, bool isOutput, PinBitCount numBits) ioPinInfo = ChipTypeHelper.IsInputOrOutputPin(chipDescription); if (ioPinInfo.isInput || ioPinInfo.isOutput) // Dev pin { - PinDescription pinDesc = ioPinInfo.isInput ? chipDescription.InputPins[0] : chipDescription.OutputPins[0]; + PinDescription pinDesc = ioPinInfo.isInput ? chipDescription.OutputPins[0] : chipDescription.InputPins[0]; pinDesc.ID = instanceID; - elementToPlace = new DevPinInstance(pinDesc, ioPinInfo.isInput); + elementToPlace = new DevPinInstance(pinDesc, ioPinInfo.isInput); + } else // SubChip @@ -962,7 +1011,50 @@ IMoveable CreateElementFromChipDescription(ChipDescription chipDescription) return elementToPlace; } + + public void StartPlacingNote(NoteDescription noteDescription) + { + StartPlacingNote(noteDescription, InputHelper.MousePosWorld, false); + } + + public IMoveable StartPlacingNote(NoteDescription noteDescription, Vector2 position, bool isDuplicating) + { + newElementsAreDuplicatedElements = isDuplicating; + if (!isPlacingNewElements) + { + CancelEverything(); + isPlacingNewElements = true; + hasExittedMultiModeSincePlacementStart = false; + StartMovingSelectedItems(); + } + + IMoveable elementToPlace; + int instanceID = IDGenerator.GenerateNewElementID(ActiveDevChip); + + NoteDescription noteDesc = DescriptionCreator.CreateNoteDescriptionForPlacing(instanceID, noteDescription.Colour, noteDescription.Text, position); + elementToPlace = new NoteInstance(noteDesc); + + // If placing multiple elements simultaneously, place the new element below the previous one + // (unless is duplicating elements, in which case their relative positions should be preserved) + if (SelectedElements.Count > 0 && !isDuplicating) + { + float spacing = (elementToPlace.SelectionBoundingBox.Size.y + SelectedElements[^1].SelectionBoundingBox.Size.y) / 2; + elementToPlace.MoveStartPosition = SelectedElements[^1].MoveStartPosition + Vector2.down * spacing; + elementToPlace.HasReferencePointForStraightLineMovement = false; + } + else + { + moveElementMouseStartPos = InputHelper.MousePosWorld + elementToPlace.SelectionBoundingBox.Size / 2;; + elementToPlace.MoveStartPosition = position; + elementToPlace.StraightLineReferencePoint = position; + elementToPlace.HasReferencePointForStraightLineMovement = isDuplicating; + } + + Select(elementToPlace); + return elementToPlace; + } + IMoveable CreateElementFromDuplicationSource(IMoveable duplicationSource) { IMoveable element; diff --git a/Assets/Scripts/Game/Interaction/Interfaces/IClickable.cs b/Assets/Scripts/Game/Interaction/Interfaces/IClickable.cs new file mode 100644 index 00000000..b2f4e8b6 --- /dev/null +++ b/Assets/Scripts/Game/Interaction/Interfaces/IClickable.cs @@ -0,0 +1,10 @@ +using System.Numerics; +using Seb.Types; + +namespace DLS.Game +{ + public interface IClickable : IInteractable + { + + } +} \ No newline at end of file diff --git a/Assets/Scripts/Game/Interaction/Interfaces/IClickable.cs.meta b/Assets/Scripts/Game/Interaction/Interfaces/IClickable.cs.meta new file mode 100644 index 00000000..c2a4fa2e --- /dev/null +++ b/Assets/Scripts/Game/Interaction/Interfaces/IClickable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a9bbc5b1a9f41364788a809974538188 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs b/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs index 21f9dbd0..8e96f459 100644 --- a/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs +++ b/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs @@ -11,14 +11,16 @@ public static class KeyboardShortcuts public static bool MainMenu_SettingsShortcutTriggered => CtrlShortcutTriggered(KeyCode.S); public static bool MainMenu_QuitShortcutTriggered => CtrlShortcutTriggered(KeyCode.Q); - // ---- Bottom Bar Menu shortcuts ---- + // ---- Bottom Bar Menu shorcuts ---- + public static bool NewNoteShortcutTriggered => CtrlShortcutTriggered(KeyCode.M); public static bool SaveShortcutTriggered => CtrlShortcutTriggered(KeyCode.S); public static bool LibraryShortcutTriggered => CtrlShortcutTriggered(KeyCode.L); public static bool PreferencesShortcutTriggered => CtrlShortcutTriggered(KeyCode.P); + public static bool StatsShortcutTriggered => CtrlShortcutTriggered(KeyCode.T); public static bool CreateNewChipShortcutTriggered => CtrlShortcutTriggered(KeyCode.N); public static bool QuitToMainMenuShortcutTriggered => CtrlShortcutTriggered(KeyCode.Q); public static bool SearchShortcutTriggered => CtrlShortcutTriggered(KeyCode.F); - + public static bool SpecialChipsShortcutTriggered => CtrlShortcutTriggered(KeyCode.B); // ---- Misc shortcuts ---- public static bool DuplicateShortcutTriggered => MultiModeHeld && InputHelper.IsKeyDownThisFrame(KeyCode.D); diff --git a/Assets/Scripts/Game/Interaction/UndoController.cs b/Assets/Scripts/Game/Interaction/UndoController.cs index 96a75f35..5e19ab6a 100644 --- a/Assets/Scripts/Game/Interaction/UndoController.cs +++ b/Assets/Scripts/Game/Interaction/UndoController.cs @@ -89,6 +89,7 @@ public void RecordMoveElements(List movedElements) } // Wire move offsets + int wireCount = devChip.Wires.Count; moveUndoAction.wireMoveOffsets = new Vector2[wireCount]; for (int i = 0; i < wireCount; i++) @@ -113,6 +114,7 @@ public void RecordAddElements(List addedElements, bool hasWires) void RecordAddOrDeleteElements(List elements, bool delete, bool hasWires) { List subchips = elements.OfType().ToList(); + List notes = elements.OfType().ToList(); DevPinInstance[] devPins = elements.OfType().ToArray(); // When deleting elements, store full state of ALL wires, not just those affected by the deletion. @@ -130,11 +132,12 @@ void RecordAddOrDeleteElements(List elements, bool delete, bool hasWi wireState = CreateFullWireState(devChip, wiresThatWillBeDeletedAutomatically); } - ElementExistenceAction deleteAction = new() { chipNames = subchips.Select(s => s.Description.Name).ToArray(), + noteIDs = notes.Select(s => s.Description.ID).ToArray(), subchipDescriptions = subchips.Select(DescriptionCreator.CreateSubChipDescription).ToArray(), + noteDescriptions = notes.Select(DescriptionCreator.CreateNoteDescription).ToArray(), pinDescriptions = devPins.Select(DescriptionCreator.CreatePinDescription).ToArray(), pinInInputFlags = devPins.Select(p => p.IsInputPin).ToArray(), wireStateBeforeDelete = wireState, @@ -284,7 +287,9 @@ public void Restore(DevChipInstance devChip) class ElementExistenceAction : UndoAction { public string[] chipNames; + public int[] noteIDs; public SubChipDescription[] subchipDescriptions; + public NoteDescription[] noteDescriptions; public PinDescription[] pinDescriptions; public bool[] pinInInputFlags; @@ -333,6 +338,22 @@ public void Trigger(bool undo, DevChipInstance devChip) } } + // ---- Handle notes ---- + for (int i = 0; i < noteIDs.Length; i++) + { + NoteDescription description = Project.ActiveProject.ViewedChip.GetNoteByID(noteIDs[i]).Description; + + if (addElement) + { + NoteInstance note = new(description); + devChip.AddNote(note, false); + Project.ActiveProject.controller.Select(note, true); + } + else if (!devChip.TryDeleteNoteByID(noteDescriptions[i].ID)) + { + } + } + if (addElement && wireStateBeforeDelete != null) { wireStateBeforeDelete.Restore(devChip); diff --git a/Assets/Scripts/Game/Main/Main.cs b/Assets/Scripts/Game/Main/Main.cs index 247a085f..226da591 100644 --- a/Assets/Scripts/Game/Main/Main.cs +++ b/Assets/Scripts/Game/Main/Main.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using DLS.Description; @@ -13,6 +14,7 @@ public static class Main { public static readonly Version DLSVersion = new(2, 1, 6); public static readonly Version DLSVersion_EarliestCompatible = new(2, 0, 0); + public static readonly Version DLSVersion_ModdedID = new(1, 1, 1); public const string LastUpdatedString = "5 May 2025"; public static AppSettings ActiveAppSettings; @@ -64,7 +66,7 @@ public static void LoadMainMenu() public static void CreateOrLoadProject(string projectName, string startupChipName = "") { - if (Loader.ProjectExists(projectName)) ActiveProject = LoadProject(projectName); + if (Loader.ProjectExists(projectName)) { ActiveProject = LoadProject(projectName); Saver.SaveProjectDescription(ActiveProject.description); } else ActiveProject = CreateProject(projectName); ActiveProject.LoadDevChipOrCreateNewIfDoesntExist(startupChipName); @@ -79,8 +81,10 @@ static Project CreateProject(string projectName) { ProjectName = projectName, DLSVersion_LastSaved = DLSVersion.ToString(), + DLSVersion_LastSavedModdedVersion = DLSVersion_ModdedID.ToString(), DLSVersion_EarliestCompatible = DLSVersion_EarliestCompatible.ToString(), CreationTime = DateTime.Now, + TimeSpentSinceCreated = new(), Prefs_ChipPinNamesDisplayMode = PreferencesMenu.DisplayMode_OnHover, Prefs_MainPinNamesDisplayMode = PreferencesMenu.DisplayMode_OnHover, Prefs_SimTargetStepsPerSecond = 1000, @@ -88,7 +92,9 @@ static Project CreateProject(string projectName) Prefs_SimPaused = false, AllCustomChipNames = Array.Empty(), StarredList = BuiltinCollectionCreator.GetDefaultStarredList().ToList(), - ChipCollections = new List(BuiltinCollectionCreator.CreateDefaultChipCollections()) + ChipCollections = new List(BuiltinCollectionCreator.CreateDefaultChipCollections()), + pinBitCounts = Project.PinBitCounts, + SplitMergePairs = Project.SplitMergePairs }; Saver.SaveProjectDescription(initialDescription); @@ -109,7 +115,7 @@ public static void OpenSaveDataFolderInFileBrowser() } catch (Exception e) { - Debug.LogError("Error opening folder: " + e.Message); + UnityEngine.Debug.LogError("Error opening folder: " + e.Message); } } diff --git a/Assets/Scripts/Game/Main/UnityMain.cs b/Assets/Scripts/Game/Main/UnityMain.cs index bca26bf9..e0e9e008 100644 --- a/Assets/Scripts/Game/Main/UnityMain.cs +++ b/Assets/Scripts/Game/Main/UnityMain.cs @@ -128,8 +128,8 @@ void EditorDebugUpdate() if (InteractionState.PinUnderMouse != null) { SimPin simPin = Project.ActiveProject.rootSimChip.GetSimPinFromAddress(InteractionState.PinUnderMouse.Address); - ushort bitData = PinState.GetBitStates(simPin.State); - ushort tristateFlags = PinState.GetTristateFlags(simPin.State); + uint bitData = simPin.State.GetValue(); + uint tristateFlags = simPin.State.GetTristatedFlags() ; string bitString = StringHelper.CreateBinaryString(bitData, true); string triStateString = StringHelper.CreateBinaryString(tristateFlags, true); diff --git a/Assets/Scripts/Game/Project/BuiltInPinTypeCreator.cs b/Assets/Scripts/Game/Project/BuiltInPinTypeCreator.cs new file mode 100644 index 00000000..51589dd1 --- /dev/null +++ b/Assets/Scripts/Game/Project/BuiltInPinTypeCreator.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using DLS.Description; +using UnityEngine; +using static DLS.Graphics.DrawSettings; + +namespace DLS.Game +{ + public static class BuiltinPinTypeCreator + { + public static PinBitCount[] CreateBuiltInPinType() + { + return new PinBitCount[] + { + 1, 4, 8 + }; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/BuiltInPinTypeCreator.cs.meta b/Assets/Scripts/Game/Project/BuiltInPinTypeCreator.cs.meta new file mode 100644 index 00000000..02931dd2 --- /dev/null +++ b/Assets/Scripts/Game/Project/BuiltInPinTypeCreator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e9fcd48a66a25a44da90495333d5c3f0 \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/BuiltinChipCreator.cs b/Assets/Scripts/Game/Project/BuiltinChipCreator.cs index c757f1e1..2d6a72b6 100644 --- a/Assets/Scripts/Game/Project/BuiltinChipCreator.cs +++ b/Assets/Scripts/Game/Project/BuiltinChipCreator.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using DLS.Description; +using DLS.Simulation; using UnityEngine; using static DLS.Graphics.DrawSettings; @@ -9,55 +11,164 @@ namespace DLS.Game public static class BuiltinChipCreator { static readonly Color ChipCol_SplitMerge = new(0.1f, 0.1f, 0.1f); //new(0.8f, 0.8f, 0.8f); + static bool AllBlack; + static Color AllBlackColor = Color.black; - public static ChipDescription[] CreateAllBuiltinChipDescriptions() + public static ChipDescription[] CreateAllBuiltinChipDescriptions(ProjectDescription description) { + AllBlack = description.ProjectName.Contains("ahic"); + return new[] { - // ---- I/O Pins ---- - CreateInputOrOutputPin(ChipType.In_1Bit), - CreateInputOrOutputPin(ChipType.Out_1Bit), - CreateInputOrOutputPin(ChipType.In_4Bit), - CreateInputOrOutputPin(ChipType.Out_4Bit), - CreateInputOrOutputPin(ChipType.In_8Bit), - CreateInputOrOutputPin(ChipType.Out_8Bit), CreateInputKeyChip(), + CreateInputButtonChip(), + CreateInputToggleChip(), + // ---- Basic Chips ---- CreateNand(), CreateTristateBuffer(), CreateClock(), CreatePulse(), + CreateConstant_8(), + CreateDetector(), + // ---- Memory ---- dev_CreateRAM_8(), CreateROM_8(), + CreateEEPROM_8(), + // ---- Merge / Split ---- - CreateBitConversionChip(ChipType.Split_4To1Bit, PinBitCount.Bit4, PinBitCount.Bit1, 1, 4), - CreateBitConversionChip(ChipType.Split_8To4Bit, PinBitCount.Bit8, PinBitCount.Bit4, 1, 2), - CreateBitConversionChip(ChipType.Split_8To1Bit, PinBitCount.Bit8, PinBitCount.Bit1, 1, 8), - CreateBitConversionChip(ChipType.Merge_1To8Bit, PinBitCount.Bit1, PinBitCount.Bit8, 8, 1), - CreateBitConversionChip(ChipType.Merge_1To4Bit, PinBitCount.Bit1, PinBitCount.Bit4, 4, 1), - CreateBitConversionChip(ChipType.Merge_4To8Bit, PinBitCount.Bit4, PinBitCount.Bit8, 2, 1), // ---- Displays ---- CreateDisplay7Seg(), CreateDisplayRGB(), CreateDisplayDot(), CreateDisplayLED(), - // ---- Bus ---- - CreateBus(PinBitCount.Bit1), - CreateBusTerminus(PinBitCount.Bit1), - CreateBus(PinBitCount.Bit4), - CreateBusTerminus(PinBitCount.Bit4), - CreateBus(PinBitCount.Bit8), - CreateBusTerminus(PinBitCount.Bit8), // ---- Audio ---- - CreateBuzzer() - }; + CreateBuzzer(), + // ---- Clock ---- + CreateSPSChip(), + // ---- Time ---- + CreateRTC() + } + .Concat(CreateInOutPins(description.pinBitCounts)) + .Concat(CreateSplitMergePins(description.SplitMergePairs)) + .Concat(CreateBusAndBusTerminus(description.pinBitCounts)) + .ToArray(); + + } - static ChipDescription CreateNand() + static ChipDescription[] CreateInOutPins(List pinBitCountsToLoad) + { + ChipDescription[] DevPinDescriptions = new ChipDescription[pinBitCountsToLoad.Count * 2]; + + + for (int i = 0; i < pinBitCountsToLoad.Count; i++) + { + DevPinDescriptions[i * 2] = CreateInPin(pinBitCountsToLoad[i]); + DevPinDescriptions[i * 2 + 1] = CreateOutPin(pinBitCountsToLoad[i]); + } + return DevPinDescriptions; + } + + public static ChipDescription CreateInPin(PinBitCount pinBitCount) + { + PinDescription[] outPin = new[] { CreatePinDescription("IN", 0, pinBitCount) }; + ChipDescription InChip = CreateBuiltinChipDescription(ChipType.In_Pin, Vector2.zero, Color.clear, null, outPin, null, + NameDisplayLocation.Hidden, name: ChipTypeHelper.GetDevPinName(true, pinBitCount)); + return InChip; + } + + public static ChipDescription CreateOutPin(PinBitCount pinBitCount) + { + PinDescription[] inPin = new[] { CreatePinDescription("OUT", 0, pinBitCount) }; + + ChipDescription OutChip = CreateBuiltinChipDescription(ChipType.Out_Pin, Vector2.zero, Color.clear, inPin, null, null, + NameDisplayLocation.Hidden, name: ChipTypeHelper.GetDevPinName(false, pinBitCount)); + + return OutChip; + } + + static ChipDescription[] CreateSplitMergePins(List> pairs) + { + ChipDescription[] SplitMergeDescriptions = new ChipDescription[pairs.Count * 2]; + + for (int i = 0; i < pairs.Count; i++) + { + SplitMergeDescriptions[i * 2] = CreateMergeChip(pairs[i]); + SplitMergeDescriptions[i * 2 + 1] = CreateSplitChip(pairs[i]); + } + + return SplitMergeDescriptions; + } + + public static ChipDescription CreateSplitChip(KeyValuePair pair) + { + (PinBitCount a, PinBitCount b) counts = (pair.Key, pair.Value); + int smallInBig = counts.a / counts.b; + + PinDescription[] splitIN = new[] { CreatePinDescription("IN", 0, counts.a) }; + PinDescription[] splitOUT = new PinDescription[smallInBig]; + + for (int j = 0; j < smallInBig; j++) + { + string letter = " " + (char)('A' + smallInBig -1 - j); + splitOUT[j] = CreatePinDescription("OUT" + letter, j + 1, counts.b); + } + string splitName = counts.a.ToString() + "-" + counts.b.ToString() + "BIT"; + + Vector2 minChipSize = SubChipInstance.CalculateMinChipSize(splitIN, splitOUT, splitName); + float width = Mathf.Max(GridSize * 9, minChipSize.x); + + Vector2 size = new Vector2(width, minChipSize.y); + return CreateBuiltinChipDescription(ChipType.Split_Pin, size, GetColor(ChipCol_SplitMerge), splitIN, splitOUT, name: splitName); + } + + public static ChipDescription CreateMergeChip(KeyValuePair pair) + { + (PinBitCount a, PinBitCount b) counts = (pair.Key, pair.Value); + int smallInBig = counts.a / counts.b; + + PinDescription[] mergeIN = new PinDescription[smallInBig]; + PinDescription[] mergeOUT = new[] { CreatePinDescription("OUT", smallInBig, counts.a) }; + + for (int j = 0; j < smallInBig; j++) + { + string letter = " " + (char)('A' + smallInBig -1 - j); + mergeIN[j] = CreatePinDescription("IN" + letter, j, counts.b); + } + string mergeName = counts.b.ToString() + "-" + counts.a.ToString() + "BIT"; + + Vector2 minChipSize = SubChipInstance.CalculateMinChipSize(mergeIN, mergeOUT, mergeName); + float width = Mathf.Max(GridSize * 9, minChipSize.x); + Vector2 size = new Vector2(width, minChipSize.y); + + return CreateBuiltinChipDescription(ChipType.Merge_Pin, size, GetColor(ChipCol_SplitMerge), mergeIN, mergeOUT, name: mergeName); + } + + + static ChipDescription[] CreateBusAndBusTerminus(List pinBitCountsToLoad) + { + ChipDescription[] descriptions = new ChipDescription[pinBitCountsToLoad.Count*2]; + + + for(int i = 0; i < pinBitCountsToLoad.Count; i++) + { + descriptions[i] = CreateBus(pinBitCountsToLoad[i]); + } + for (int i = 0; i < pinBitCountsToLoad.Count; i++) + { + descriptions[i + pinBitCountsToLoad.Count] = CreateBusTerminus(pinBitCountsToLoad[i]); + } + + + return descriptions; + + } + static ChipDescription CreateNand() { - Color col = new(0.73f, 0.26f, 0.26f); + Color col = GetColor(new(0.73f, 0.26f, 0.26f)); Vector2 size = new(CalculateGridSnappedWidth(GridSize * 8), GridSize * 4); PinDescription[] inputPins = { CreatePinDescription("IN B", 0), CreatePinDescription("IN A", 1) }; @@ -68,7 +179,7 @@ static ChipDescription CreateNand() static ChipDescription CreateBuzzer() { - Color col = new(0, 0, 0); + Color col = GetColor(new(0, 0, 0)); PinDescription[] inputPins = { @@ -81,10 +192,42 @@ static ChipDescription CreateBuzzer() return CreateBuiltinChipDescription(ChipType.Buzzer, size, col, inputPins, null, null); } + static ChipDescription CreateSPSChip() + { + Color col = new(0.4f, 0.3f, 0.3f); + + PinDescription[] outputPins = + { + CreatePinDescription("SPCT", 3, PinBitCount.Bit16), + CreatePinDescription("SPS", 2, PinBitCount.Bit16), + CreatePinDescription("SPCT_OVERFLOW", 1, PinBitCount.Bit1), + CreatePinDescription("SPS_OVERFLOW", 0, PinBitCount.Bit1), + }; + + float height = SubChipInstance.MinChipHeightForPins(outputPins, null); + Vector2 size = new(CalculateGridSnappedWidth(GridSize * 9), height); + + return CreateBuiltinChipDescription(ChipType.SPS, size, col, null, outputPins); + } + + static ChipDescription CreateRTC() + { + Color col = new(0.4f, 0.4f, 0.4f); + + PinDescription[] outputPins = + { + CreatePinDescription("TIME", 0, PinBitCount.Bit32), + }; + + float height = SubChipInstance.MinChipHeightForPins(outputPins, null); + Vector2 size = new(CalculateGridSnappedWidth(GridSize * 9), height); + + return CreateBuiltinChipDescription(ChipType.RTC, size, col, null, outputPins); + } static ChipDescription dev_CreateRAM_8() { - Color col = new(0.85f, 0.45f, 0.3f); + Color col = GetColor(new(0.85f, 0.45f, 0.3f)); PinDescription[] inputPins = { @@ -108,19 +251,76 @@ static ChipDescription CreateROM_8() }; PinDescription[] outputPins = { - CreatePinDescription("OUT B", 1, PinBitCount.Bit8), - CreatePinDescription("OUT A", 2, PinBitCount.Bit8) - }; + CreatePinDescription("OUT B", 1, PinBitCount.Bit8), + CreatePinDescription("OUT A", 2, PinBitCount.Bit8) + }; - Color col = new(0.25f, 0.35f, 0.5f); + Color col = GetColor(new(0.25f, 0.35f, 0.5f)); Vector2 size = new(GridSize * 12, SubChipInstance.MinChipHeightForPins(inputPins, outputPins)); return CreateBuiltinChipDescription(ChipType.Rom_256x16, size, col, inputPins, outputPins); } - static ChipDescription CreateInputKeyChip() + static ChipDescription CreateEEPROM_8() + { + PinDescription[] inputPins = + { + CreatePinDescription("ADDRESS", 0, PinBitCount.Bit8), + CreatePinDescription("DATA B", 1, PinBitCount.Bit8), + CreatePinDescription("DATA A", 2, PinBitCount.Bit8), + CreatePinDescription("WRITE", 3, PinBitCount.Bit1), + CreatePinDescription("CLOCK", 4, PinBitCount.Bit1) + }; + PinDescription[] outputPins = + { + CreatePinDescription("OUT B", 5, PinBitCount.Bit8), + CreatePinDescription("OUT A", 6, PinBitCount.Bit8) + + }; + + Color col = GetColor(new(0.25f, 0.35f, 0.5f)); + Vector2 size = new(GridSize * 12, SubChipInstance.MinChipHeightForPins(inputPins, outputPins)); + + return CreateBuiltinChipDescription(ChipType.EEPROM_256x16, size, col, inputPins, outputPins); + } + + static ChipDescription CreateConstant_8() { + PinDescription[] outputPins = + { + CreatePinDescription("VALUE OUT", 0, PinBitCount.Bit8), + }; + Color col = new(0.1f, 0.1f, 0.1f); + Vector2 size = Vector2.one * GridSize * 6; + + return CreateBuiltinChipDescription(ChipType.Constant_8Bit, size, col, null, outputPins); + } + + static ChipDescription CreateDetector() + { + PinDescription[] inputPins = + { + CreatePinDescription("IN", 0, PinBitCount.Bit1), + }; + + PinDescription[] outputPins = + { + CreatePinDescription("0", 1, PinBitCount.Bit1), + CreatePinDescription("1", 2, PinBitCount.Bit1), + CreatePinDescription("Z", 3, PinBitCount.Bit1), + }; + + Color col = new(0.1f, 0.1f, 0.3f); + Vector2 size = new(GridSize * 12, SubChipInstance.MinChipHeightForPins(inputPins, outputPins)); + + return CreateBuiltinChipDescription(ChipType.Detector, size, col, inputPins, outputPins); + } + + + static ChipDescription CreateInputKeyChip() + { + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); Vector2 size = new Vector2(GridSize, GridSize) * 3; PinDescription[] outputPins = { CreatePinDescription("OUT", 0) }; @@ -128,10 +328,50 @@ static ChipDescription CreateInputKeyChip() return CreateBuiltinChipDescription(ChipType.Key, size, col, null, outputPins, null, NameDisplayLocation.Hidden); } + static ChipDescription CreateInputButtonChip() + { + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); + Vector2 size = new Vector2(GridSize, GridSize) * 3; + float displayWidth = size.x - GridSize *0.5f; - static ChipDescription CreateTristateBuffer() + PinDescription[] outputPins = { CreatePinDescription("OUT", 0) }; + DisplayDescription[] displays = + { + new() + { + Position = Vector2.zero, + Scale = displayWidth, + SubChipID = -1 + } + }; + + return CreateBuiltinChipDescription(ChipType.Button, size, col, null, outputPins, displays, NameDisplayLocation.Hidden); + } + + static ChipDescription CreateInputToggleChip() + { + Color col = GetColor(new(70, 130, 180)); + Vector2 size = new Vector2(1f, 2f) * GridSize; + float displayWidth = size.x; + + PinDescription[] outputPins = { CreatePinDescription("OUT", 0) }; + DisplayDescription[] displays = + { + new() + { + Position = Vector2.zero, + Scale = displayWidth, + SubChipID = -1 + } + }; + + return CreateBuiltinChipDescription(ChipType.Toggle, size, col, null, outputPins, displays, NameDisplayLocation.Hidden); + } + + + static ChipDescription CreateTristateBuffer() { - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); Vector2 size = new(CalculateGridSnappedWidth(1.5f), GridSize * 5); PinDescription[] inputPins = { CreatePinDescription("IN", 0), CreatePinDescription("ENABLE", 1) }; @@ -143,7 +383,7 @@ static ChipDescription CreateTristateBuffer() static ChipDescription CreateClock() { Vector2 size = new(GridHelper.SnapToGrid(1), GridSize * 3); - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); PinDescription[] outputPins = { CreatePinDescription("CLK", 0) }; return CreateBuiltinChipDescription(ChipType.Clock, size, col, null, outputPins); @@ -152,43 +392,13 @@ static ChipDescription CreateClock() static ChipDescription CreatePulse() { Vector2 size = new(GridHelper.SnapToGrid(1), GridSize * 3); - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); PinDescription[] inputPins = { CreatePinDescription("IN", 0) }; PinDescription[] outputPins = { CreatePinDescription("PULSE", 1) }; return CreateBuiltinChipDescription(ChipType.Pulse, size, col, inputPins, outputPins); } - static ChipDescription CreateBitConversionChip(ChipType chipType, PinBitCount bitCountIn, PinBitCount bitCountOut, int numIn, int numOut) - { - PinDescription[] inputPins = new PinDescription[numIn]; - PinDescription[] outputPins = new PinDescription[numOut]; - - for (int i = 0; i < numIn; i++) - { - string pinName = GetPinName(i, numIn, true); - inputPins[i] = CreatePinDescription(pinName, i, bitCountIn); - } - - for (int i = 0; i < numOut; i++) - { - string pinName = GetPinName(i, numOut, false); - outputPins[i] = CreatePinDescription(pinName, numIn + i, bitCountOut); - } - - float height = SubChipInstance.MinChipHeightForPins(inputPins, outputPins); - Vector2 size = new(GridSize * 9, height); - - return CreateBuiltinChipDescription(chipType, size, ChipCol_SplitMerge, inputPins, outputPins); - } - - static string GetPinName(int pinIndex, int pinCount, bool isInput) - { - string letter = " " + (char)('A' + pinCount - pinIndex - 1); - if (pinCount == 1) letter = ""; - return (isInput ? "IN" : "OUT") + letter; - } - static ChipDescription CreateDisplay7Seg() { PinDescription[] inputPins = @@ -203,7 +413,7 @@ static ChipDescription CreateDisplay7Seg() CreatePinDescription("COL", 7) }; - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); float height = SubChipInstance.MinChipHeightForPins(inputPins, null); Vector2 size = new(GridSize * 10, height); float displayWidth = size.x - GridSize * 2; @@ -226,7 +436,7 @@ static ChipDescription CreateDisplayRGB() float width = height; float displayWidth = height - GridSize * 2; - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); Vector2 size = new(width, height); PinDescription[] inputPins = @@ -282,7 +492,7 @@ static ChipDescription CreateDisplayDot() float width = height; float displayWidth = height - GridSize * 2; - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); Vector2 size = new(width, height); @@ -299,48 +509,28 @@ static ChipDescription CreateDisplayDot() return CreateBuiltinChipDescription(ChipType.DisplayDot, size, col, inputPins, outputPins, displays, NameDisplayLocation.Hidden); } - // (Not a chip, but convenient to treat it as one) - public static ChipDescription CreateInputOrOutputPin(ChipType type) - { - (bool isInput, bool isOutput, PinBitCount numBits) = ChipTypeHelper.IsInputOrOutputPin(type); - string name = isInput ? "IN" : "OUT"; - PinDescription[] pin = { CreatePinDescription(name, 0, numBits) }; - - PinDescription[] inputs = isInput ? pin : null; - PinDescription[] outputs = isOutput ? pin : null; - - return CreateBuiltinChipDescription(type, Vector2.zero, Color.clear, inputs, outputs); - } - static Vector2 BusChipSize(PinBitCount bitCount) { - return bitCount switch + return bitCount.BitCount switch { PinBitCount.Bit1 => new Vector2(GridSize * 2, GridSize * 2), PinBitCount.Bit4 => new Vector2(GridSize * 2, GridSize * 3), PinBitCount.Bit8 => new Vector2(GridSize * 2, GridSize * 4), - _ => throw new Exception("Bus bit count not implemented") + _ => new Vector2(GridSize * 2, 0.5f * bitCount.BitCount * GridSize) }; } - static ChipDescription CreateBus(PinBitCount bitCount) + public static ChipDescription CreateBus(PinBitCount bitCount) { - ChipType type = bitCount switch - { - PinBitCount.Bit1 => ChipType.Bus_1Bit, - PinBitCount.Bit4 => ChipType.Bus_4Bit, - PinBitCount.Bit8 => ChipType.Bus_8Bit, - _ => throw new Exception("Bus bit count not implemented") - }; - string name = ChipTypeHelper.GetName(type); + string name = ChipTypeHelper.GetBusName(bitCount); PinDescription[] inputs = { CreatePinDescription(name + " (Hidden)", 0, bitCount) }; PinDescription[] outputs = { CreatePinDescription(name, 1, bitCount) }; - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); - return CreateBuiltinChipDescription(type, BusChipSize(bitCount), col, inputs, outputs, null, NameDisplayLocation.Hidden); + return CreateBuiltinChipDescription(ChipType.Bus, BusChipSize(bitCount), col, inputs, outputs, null, NameDisplayLocation.Hidden, name:name); } static ChipDescription CreateDisplayLED() @@ -354,7 +544,7 @@ static ChipDescription CreateDisplayLED() float width = height; float displayWidth = height - GridSize * 0.5f; - Color col = new(0.1f, 0.1f, 0.1f); + Color col = GetColor(new(0.1f, 0.1f, 0.1f)); Vector2 size = new(width, height); @@ -372,26 +562,20 @@ static ChipDescription CreateDisplayLED() } - static ChipDescription CreateBusTerminus(PinBitCount bitCount) + public static ChipDescription CreateBusTerminus(PinBitCount bitCount) { - ChipType type = bitCount switch - { - PinBitCount.Bit1 => ChipType.BusTerminus_1Bit, - PinBitCount.Bit4 => ChipType.BusTerminus_4Bit, - PinBitCount.Bit8 => ChipType.BusTerminus_8Bit, - _ => throw new Exception("Bus bit count not implemented") - }; - + string name = ChipTypeHelper.GetBusTerminusName(bitCount); ChipDescription busOrigin = CreateBus(bitCount); PinDescription[] inputs = { CreatePinDescription(busOrigin.Name, 0, bitCount) }; - return CreateBuiltinChipDescription(type, BusChipSize(bitCount), busOrigin.Colour, inputs, null, null, NameDisplayLocation.Hidden); + return CreateBuiltinChipDescription(ChipType.BusTerminus, BusChipSize(bitCount), busOrigin.Colour, inputs, null, null, NameDisplayLocation.Hidden, name); } - static ChipDescription CreateBuiltinChipDescription(ChipType type, Vector2 size, Color col, PinDescription[] inputs, PinDescription[] outputs, DisplayDescription[] displays = null, NameDisplayLocation nameLoc = NameDisplayLocation.Centre) + static ChipDescription CreateBuiltinChipDescription(ChipType type, Vector2 size, Color col, PinDescription[] inputs, PinDescription[] outputs, DisplayDescription[] displays = null, NameDisplayLocation nameLoc = NameDisplayLocation.Centre, string name = "") { - string name = ChipTypeHelper.GetName(type); + if (!ChipTypeHelper.IsDevPin(type) && !ChipTypeHelper.IsMergeSplitChip(type) && !ChipTypeHelper.IsBusType(type)){name = ChipTypeHelper.GetName(type); } + ValidatePinIDs(inputs, outputs, name); return new ChipDescription @@ -409,12 +593,12 @@ static ChipDescription CreateBuiltinChipDescription(ChipType type, Vector2 size, }; } - static PinDescription CreatePinDescription(string name, int id, PinBitCount bitCount = PinBitCount.Bit1) => + static PinDescription CreatePinDescription(string name, int id, ushort bitcount = 1) => new( name, id, Vector2.zero, - bitCount, + new(bitcount), PinColour.Red, PinValueDisplayMode.Off ); @@ -443,5 +627,10 @@ void AddPins(PinDescription[] pins) } } } + + static Color GetColor(Color color) + { + return AllBlack ? AllBlackColor : color; + } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Game/Project/BuiltinCollectionCreator.cs b/Assets/Scripts/Game/Project/BuiltinCollectionCreator.cs index f7e6508a..abc2290e 100644 --- a/Assets/Scripts/Game/Project/BuiltinCollectionCreator.cs +++ b/Assets/Scripts/Game/Project/BuiltinCollectionCreator.cs @@ -23,28 +23,35 @@ public static ChipCollection[] CreateDefaultChipCollections() ChipType.Clock, ChipType.Pulse, ChipType.Key, - ChipType.TriStateBuffer + ChipType.TriStateBuffer, + ChipType.Constant_8Bit ), CreateChipCollection("IN/OUT", - ChipType.In_1Bit, - ChipType.In_4Bit, - ChipType.In_8Bit, - ChipType.Out_1Bit, - ChipType.Out_4Bit, - ChipType.Out_8Bit - ), - CreateChipCollection("MERGE/SPLIT", - ChipType.Merge_1To4Bit, - ChipType.Merge_1To8Bit, - ChipType.Merge_4To8Bit, - ChipType.Split_4To1Bit, - ChipType.Split_8To4Bit, - ChipType.Split_8To1Bit + ChipType.Button, + ChipType.Toggle + ).AddNames + ( + "IN-1", + "IN-4", + "IN-8", + "OUT-1", + "OUT-4", + "OUT-8" + ) + + , + CreateByNames("MERGE/SPLIT", + "1-4BIT", + "1-8BIT", + "4-8BIT", + "4-1BIT", + "8-4BIT", + "8-1BIT" ), - CreateChipCollection("BUS", - ChipType.Bus_1Bit, - ChipType.Bus_4Bit, - ChipType.Bus_8Bit + CreateByNames("BUS", + "BUS-1", + "BUS-4", + "BUS-8" ), CreateChipCollection("DISPLAY", ChipType.SevenSegmentDisplay, @@ -53,7 +60,9 @@ public static ChipCollection[] CreateDefaultChipCollections() ChipType.DisplayLED ), CreateChipCollection("MEMORY", - ChipType.Rom_256x16 + ChipType.Rom_256x16, + ChipType.EEPROM_256x16, + ChipType.dev_Ram_8Bit ) }; } @@ -62,5 +71,16 @@ static ChipCollection CreateChipCollection(string name, params ChipType[] chipTy { return new ChipCollection(name, chipTypes.Select(t => ChipTypeHelper.GetName(t)).ToArray()); } + + static ChipCollection CreateByNames(string name, params string[] chipNames) + { + return new ChipCollection(name, chipNames); + } + + static ChipCollection AddNames(this ChipCollection chipCollection, params string[] chipNames) + { + chipCollection.Chips.AddRange(chipNames); + return chipCollection; + } } } \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/ChipLibrary.cs b/Assets/Scripts/Game/Project/ChipLibrary.cs index 602a6427..82f81552 100644 --- a/Assets/Scripts/Game/Project/ChipLibrary.cs +++ b/Assets/Scripts/Game/Project/ChipLibrary.cs @@ -19,7 +19,7 @@ public ChipLibrary(ChipDescription[] customChips, ChipDescription[] builtinChips foreach (ChipDescription chip in builtinChips) { // Bus terminus chip should not be shown to the user (it is created automatically upon placement of a bus start point) - bool hidden = ChipTypeHelper.IsBusTerminusType(chip.ChipType) || chip.ChipType == ChipType.dev_Ram_8Bit; + bool hidden = ChipTypeHelper.IsBusTerminusType(chip.ChipType); AddChipToLibrary(chip, hidden); builtinChipNames.Add(chip.Name); @@ -55,6 +55,19 @@ void RebuildChipDescriptionLookup() public ChipDescription GetChipDescription(string name) => descriptionFromNameLookup[name]; + public ChipDescription GetTerminusDescription(PinBitCount bitCount) + { + foreach(ChipDescription desc in hiddenChips) + { + if(desc.ChipType == ChipType.BusTerminus && desc.InputPins[0].BitCount == bitCount) + { + return desc; + } + } + + throw new System.Exception("Bus terminus not found"); + } + public bool TryGetChipDescription(string name, out ChipDescription description) => descriptionFromNameLookup.TryGetValue(name, out description); public void RemoveChip(string chipName) @@ -63,7 +76,7 @@ public void RemoveChip(string chipName) RebuildChipDescriptionLookup(); } - public void NotifyChipSaved(ChipDescription description) + public void NotifyChipSaved(ChipDescription description, bool hidden = false) { // Replace chip description if already exists bool foundChip = false; @@ -79,11 +92,12 @@ public void NotifyChipSaved(ChipDescription description) } // Otherwise add as new description - if (!foundChip) AddChipToLibrary(description); + if (!foundChip) AddChipToLibrary(description, hidden); RebuildChipDescriptionLookup(); } + public void NotifyChipRenamed(ChipDescription description, string nameOld) { // Replace chip description @@ -133,6 +147,7 @@ public ChipDescription[] GetDirectParentChips(string chipName) void AddChipToLibrary(ChipDescription description, bool hidden = false) { + if(description.ChipType != ChipType.Custom) builtinChipNames.Add(description.Name); if (hidden) hiddenChips.Add(description); else allChips.Add(description); } diff --git a/Assets/Scripts/Game/Project/DevChipInstance.cs b/Assets/Scripts/Game/Project/DevChipInstance.cs index c25d9584..9a7a6cd3 100644 --- a/Assets/Scripts/Game/Project/DevChipInstance.cs +++ b/Assets/Scripts/Game/Project/DevChipInstance.cs @@ -24,6 +24,7 @@ public class DevChipInstance public SimChip SimChip; bool hasSimChip; + public bool HasCustomLayout; public string ChipName => LastSavedDescription == null ? string.Empty : LastSavedDescription.Name; @@ -67,6 +68,8 @@ public static (DevChipInstance devChip, bool anyElementFailedToLoad) LoadFromDes description.InputPins ??= Array.Empty(); description.OutputPins ??= Array.Empty(); description.Wires ??= Array.Empty(); + description.Notes ??= Array.Empty(); + instance.HasCustomLayout = description.HasCustomLayout; bool anyElementFailedToLoad = false; @@ -94,6 +97,14 @@ public static (DevChipInstance devChip, bool anyElementFailedToLoad) LoadFromDes instance.AddNewDevPin(new DevPinInstance(pinDescription, false), true); } + // Load notes + for (int i = 0; i < description.Notes.Length; i++) + { + NoteDescription noteDescription = description.Notes[i]; + NoteInstance note = new(noteDescription); + instance.AddNote(note, true); + } + // ---- Load wires ---- // Wires can fail to load if associated pin was deleted from subchip. This means that wire indices stored in the save data might not line up with our loaded wires list. // So, keep track here of the wires with correct indices (with failed entries just being left as null) @@ -228,7 +239,7 @@ public void AddNewDevPin(DevPinInstance pin, bool isLoadingFromFile) AddElement(pin); if (!isLoadingFromFile) { - Simulator.AddPin(SimChip, pin.ID, pin.IsInputPin); + Simulator.AddPin(SimChip, pin.ID, pin.IsInputPin, pin.BitCount); } } @@ -244,6 +255,11 @@ public void AddWire(WireInstance wire, bool isLoading, int insertIndex = -1) } } + public void AddNote(NoteInstance note, bool isLoading) + { + AddElement(note); + } + void AddElement(IMoveable element) { Elements.Add(element); @@ -257,7 +273,6 @@ void RemoveElement(IMoveable element) Debug.Assert(success, "Trying to delete element that was already deleted?"); } - public void DeleteDevPin(DevPinInstance devPin) { DeleteWiresAttachedToPin(devPin.Pin); @@ -369,6 +384,15 @@ public void DeleteSubChip(SubChipInstance subChip) if (hasSimChip) Simulator.RemoveSubChip(SimChip, subChip.ID); } + public void DeleteNote(NoteInstance note) + { + // Ensure subchip exists before deleting + // (required for buses, where one end of bus is deleted automatically when other end is deleted; but user may select both ends for deletion) + if (!Elements.Contains(note)) return; + + RemoveElement(note); + } + // Delete subchip with given id (if it exists) public bool TryDeleteSubChipByID(int id) { @@ -399,6 +423,20 @@ public bool TryDeleteDevPinByID(int id) return false; } + public bool TryDeleteNoteByID(int id) + { + for (int i = 0; i < Elements.Count; i++) + { + if (Elements[i] is NoteInstance note && note.ID == id) + { + DeleteNote(note); + return true; + } + } + + return false; + } + public bool TryGetSubChipByID(int id, out SubChipInstance subchip) { foreach (IMoveable element in Elements) @@ -525,6 +563,11 @@ public void NotifyConnectedWiresPointsInserted(WireInstance wire, int insertInde } public IEnumerable GetSubchips() => Elements.OfType(); + public IEnumerable GetNotes() => Elements.OfType(); + public NoteInstance GetNoteByID(int id) + { + return Elements.OfType().FirstOrDefault(n => n.ID == id); + } public IEnumerable GetOutputPins() { diff --git a/Assets/Scripts/Game/Project/Project.cs b/Assets/Scripts/Game/Project/Project.cs index e2a5be6e..e7e15fc3 100644 --- a/Assets/Scripts/Game/Project/Project.cs +++ b/Assets/Scripts/Game/Project/Project.cs @@ -9,6 +9,7 @@ using DLS.Simulation; using Seb.Helpers; using UnityEngine; +using UnityEngine.Windows; using Debug = UnityEngine.Debug; namespace DLS.Game @@ -21,7 +22,11 @@ public enum SaveMode Rename, SaveAs } - + public static readonly List> SplitMergePairs = new() { + new(8,4), new(8,1), + new(4,1) + }; + public static readonly List PinBitCounts = new List { 1, 4, 8}; public static Project ActiveProject; public readonly ChipLibrary chipLibrary; @@ -181,7 +186,7 @@ public void SaveFromDescription(ChipDescription saveChipDescription, SaveMode sa // (same thing if saving a new version of it) if (ViewedChip.LastSavedDescription != null && saveMode != SaveMode.SaveAs) { - UpdateAndSaveAffectedChips(ViewedChip.LastSavedDescription, saveChipDescription, false); + UpdateAndSaveAffectedChips(ViewedChip.LastSavedDescription, saveChipDescription, false); } if (saveMode is SaveMode.Rename) @@ -234,6 +239,7 @@ public void CreateBlankDevChip() SetNewActiveDevChip(devChip); } + public void LoadDevChipOrCreateNewIfDoesntExist(string chipName) { if (chipLibrary.TryGetChipDescription(chipName, out ChipDescription description)) @@ -296,6 +302,14 @@ public void NotifyRomContentsEdited(SubChipInstance romChip) simChip.UpdateInternalState(romChip.InternalData); } + public void NotifyRomContentsEditedRuntime(SimChip simChip) + { + bool foundChip = ViewedChip.TryGetSubChipByID(simChip.ID, out SubChipInstance instance); + if (foundChip) { + instance.UpdateInternalData(simChip.InternalState); + } + } + public void NotifyLEDColourChanged(SubChipInstance ledChip, uint colIndex) { SimChip simChip = rootSimChip.GetSubChipFromID(ledChip.ID); @@ -303,7 +317,33 @@ public void NotifyLEDColourChanged(SubChipInstance ledChip, uint colIndex) ledChip.InternalData[0] = colIndex; } - public void DeleteChip(string chipToDeleteName) + public void NotifyButtonColourChanged(SubChipInstance buttonChip, uint colIndex) + { + SimChip simChip = rootSimChip.GetSubChipFromID(buttonChip.ID); + simChip.InternalState[0] = colIndex; + buttonChip.InternalData[0] = colIndex; + } + + public void NotifyToggleStateChanged(SimChip simChip) + { + bool foundChip = ViewedChip.TryGetSubChipByID(simChip.ID, out SubChipInstance instance); + if (foundChip) + { + instance.UpdateInternalData(simChip.InternalState); + } + } + + public void NotifyConstantEdited(SubChipInstance constantChip, ushort value) + { + SimChip simChip = rootSimChip. + GetSubChipFromID + (constantChip.ID); + constantChip.InternalData[0] = value; + simChip.UpdateInternalState(constantChip.InternalData); + + } + + public void DeleteChip(string chipToDeleteName) { // If the current chip only contains the deleted chip directly as a subchip, it will be removed from the sim and everything is fine. // However, if it is contained indirectly somewhere within one of the chip's subchips (or their subchips, etc), then it's a bit tricky (and @@ -327,7 +367,7 @@ public void DeleteChip(string chipToDeleteName) SetStarred(chipToDeleteName, false, false, false); // ensure removed from starred list EnsureChipRemovedFromCollections(chipToDeleteName); UpdateAndSaveProjectDescription(); - + // If has deleted the chip that's currently being edited, then open a blank chip if (ChipDescription.NameMatch(ViewedChip.ChipName, chipToDeleteName)) @@ -391,6 +431,25 @@ void SearchRecursive(ChipDescription desc) } } + public void CreateBlankNote(Vector2 position, string text) + { + // Get all possible values of the NoteColour enum + Array colours = Enum.GetValues(typeof(NoteColour)); + + // Select a random color + NoteColour randomColour = (NoteColour)colours.GetValue(UnityEngine.Random.Range(0, colours.Length)); + + // Create the note with the random color + NoteDescription noteDesc = new NoteDescription( + IDGenerator.GenerateNewElementID(editModeChip), + randomColour, + text, + position + ); + + controller.StartPlacingNote(noteDesc); + } + bool ChipContainsSubChipDirectly(DevChipInstance chip, string targetName) { foreach (IMoveable element in chip.Elements) @@ -484,6 +543,7 @@ public void ToggleGridDisplay() public void NotifyExit() { simThreadActive = false; + ActiveProject.UpdateAndSaveProjectDescription(ActiveProject.description); } void SimThread() @@ -690,5 +750,47 @@ void EnsureChipRemovedFromCollections(string chipNameToRemove) } } } - } + + public void AddNewPinSize(int pinSize) + { + PinBitCount pinBitCount = pinSize; + description.pinBitCounts.Add(pinBitCount); + ChipDescription inPin = BuiltinChipCreator.CreateInPin(pinBitCount); + ChipDescription outPin = BuiltinChipCreator.CreateOutPin(pinBitCount); + ChipDescription bus = BuiltinChipCreator.CreateBus(pinBitCount); + ChipDescription busTerminus = BuiltinChipCreator.CreateBusTerminus(pinBitCount); + chipLibrary.NotifyChipSaved(inPin); + chipLibrary.NotifyChipSaved(outPin); + chipLibrary.NotifyChipSaved(bus); + chipLibrary.NotifyChipSaved(busTerminus, true); + + + if (description.ChipCollections.Any(c => c.Name == "IN/OUT")) + { + description.ChipCollections.First(c => c.Name == "IN/OUT").Chips.Add(inPin.Name); + description.ChipCollections.First(c => c.Name == "IN/OUT").Chips.Add(outPin.Name); + } + + if(description.ChipCollections.Any(c => c.Name == "BUS")) + { + description.ChipCollections.First(c => c.Name == "BUS").Chips.Add(bus.Name); + } + } + + public void AddNewMergeSplit(int a, int b) + { + KeyValuePair pair = new(Math.Max(a,b), Math.Min(b,a)); + description.SplitMergePairs.Add(pair); + ChipDescription mergeChip = BuiltinChipCreator.CreateMergeChip(pair); + ChipDescription splitChip = BuiltinChipCreator.CreateSplitChip(pair); + chipLibrary.NotifyChipSaved(mergeChip); + chipLibrary.NotifyChipSaved(splitChip); + if (description.ChipCollections.Any(c => c.Name == "MERGE/SPLIT")) + { + description.ChipCollections.First(c => c.Name == "MERGE/SPLIT").Chips.Add(mergeChip.Name); + description.ChipCollections.First(c => c.Name == "MERGE/SPLIT").Chips.Add(splitChip.Name); + } + + } + } } \ No newline at end of file diff --git a/Assets/Scripts/Graphics/DrawSettings.cs b/Assets/Scripts/Graphics/DrawSettings.cs index 3f3ff864..e2be6ffb 100644 --- a/Assets/Scripts/Graphics/DrawSettings.cs +++ b/Assets/Scripts/Graphics/DrawSettings.cs @@ -1,4 +1,5 @@ using System.Linq; +using DLS.SaveSystem; using Seb.Vis; using Seb.Vis.UI; using UnityEngine; @@ -13,6 +14,7 @@ public static class DrawSettings public const float PinHeight1Bit = 0.185f; public const float PinHeight4Bit = 0.3f; public const float PinHeight8Bit = 0.43f; + public const float PinHeightPerBit = 0.05f; public const float PinRadius = PinHeight1Bit / 2; public const FontType FontBold = FontType.JetbrainsMonoBold; @@ -20,6 +22,7 @@ public static class DrawSettings public const float FontSizeChipName = 0.25f; public const float FontSizePinLabel = 0.2f; + public const float FontSizeNoteText = 0.2f; public const float SubChipPinInset = 0.015f; public const float SelectionBoundsPadding = 0.08f; @@ -49,14 +52,20 @@ public static class DrawSettings public static readonly ThemeDLS ActiveTheme = CreateTheme(); public static readonly UIThemeDLS ActiveUITheme = CreateUITheme(); - // ---- Helper functions ---- - public static Color GetStateColour(bool isHigh, uint index, bool hover = false) + // ---- Helper functions ---- + public static Color GetStateColour(bool isHigh, uint index,bool hover = false) { index = (uint)Mathf.Min(index, ActiveTheme.StateHighCol.Length - 1); // clamp just to be safe... if (!isHigh && hover) return ActiveTheme.StateHoverCol[index]; return isHigh ? ActiveTheme.StateHighCol[index] : ActiveTheme.StateLowCol[index]; } + public static Color GetFlatColour(bool isHigh, uint index, bool hover = false) { + index = (uint)Mathf.Min(index, ActiveTheme.FlatColors.Length - 1); // clamp just to be safe... + if (!isHigh && hover) return ActiveTheme.FlatColorsHover[index]; + return ActiveTheme.FlatColors[index]; + } + static ThemeDLS CreateTheme() { const float whiteLow = 0.35f; @@ -85,31 +94,66 @@ static ThemeDLS CreateTheme() new(whiteHigh, whiteHigh, whiteHigh) }; - Color[] stateHover = stateLow.Select(c => Brighten(c, 0.075f)).ToArray(); - return new ThemeDLS + Color[] note = { - SelectionBoxCol = new Color(1, 1, 1, 0.1f), - SelectionBoxMovingCol = new Color(1, 1, 1, 0.125f), - SelectionBoxInvalidCol = MakeCol255(243, 81, 75, 120), - SelectionBoxOtherIsInvaldCol = MakeCol255(243, 150, 75, 80), - StateLowCol = stateLow, - StateHighCol = stateHigh, - StateHoverCol = stateHover, - StateDisconnectedCol = Color.black, - DevPinHandle = MakeCol(0.31f), - DevPinHandleHighlighted = MakeCol(0.7f), - PinCol = Color.black, - PinLabelCol = new Color(0, 0, 0, 0.7f), - PinHighlightCol = Color.white, - PinInvalidCol = MakeCol(0.15f), - SevenSegCols = new Color[] - { - new(0.1f, 0.09f, 0.09f), new(1, 0.32f, 0.28f), new(0.19f, 0.15f, 0.15f), // Col A: OFF, ON, HIGHLIGHT + new Color(0.4f, 0.3f, 0.3f), + new Color(0.45f, 0.34f, 0.14f), + new Color(0.3f, 0.4f, 0.3f), + new Color(0.3f, 0.34f, 0.5f), + new Color(0.39f, 0.28f, 0.38f), + new Color(0.45f, 0.2f, 0.45f), + new Color(0.6f, 0.6f, 0.6f) + }; + + Color[] flatColors = + { + MakeCol255(155, 34, 38), + MakeCol255(202, 103, 2), + MakeCol255(238, 155, 0), + MakeCol255(233, 216, 166), + MakeCol255(0, 205, 226), + MakeCol255(238, 130, 238), + MakeCol255(212, 4, 116), + new(whiteHigh, whiteHigh, whiteHigh) + }; + + Color[] stateHover = stateLow.Select(c => Brighten(c, 0.075f)).ToArray(); + Color[] flatHover = flatColors.Select(c => Brighten(c, 0.075f)).ToArray(); + Color[] stateHover = stateLow.Select(c => Brighten(c, 0.1f)).ToArray(); + + return new ThemeDLS + { + SelectionBoxCol = new Color(1, 1, 1, 0.1f), + SelectionBoxMovingCol = new Color(1, 1, 1, 0.125f), + SelectionBoxInvalidCol = MakeCol255(243, 81, 75, 120), + SelectionBoxOtherIsInvaldCol = MakeCol255(243, 150, 75, 80), + StateLowCol = stateLow, + StateHighCol = stateHigh, + NoteCol = note, + StateHoverCol = stateHover, + StateDisconnectedCol = Color.black, + DevPinHandle = MakeCol(0.31f), + DevPinHandleHighlighted = MakeCol(0.7f), + PinCol = Color.black, + PinLabelCol = new Color(0, 0, 0, 0.7f), + PinHighlightCol = Color.white, + PinInvalidCol = MakeCol(0.15f), + SevenSegCols = new Color[] + { + new(0.1f, 0.09f, 0.09f), new(1, 0.32f, 0.28f), new(0.19f, 0.15f, 0.15f), // Col A: OFF, ON, HIGHLIGHT new(0.09f, 0.09f, 0.1f), new(0, 0.61f, 1f), new(0.15f, 0.15f, 0.19f) // Col B: OFF, ON, HIGHLIGHT }, BackgroundCol = MakeCol255(66, 66, 69), GridCol = MakeCol255(49, 49, 51), + FlatColors = flatColors, + FlatColorsHover = flatHover, + PinSizeIndicatorColors = new Color[] { + new(0,0,0,0), // Depth 0 -- UNUSED + MakeCol255(153, 102, 51), // Depth 1 + MakeCol255(255, 0, 0), // Depth 2 + MakeCol255(255, 153, 0) // Depth 3 + } }; } @@ -237,6 +281,11 @@ public class ThemeDLS public Color[] StateHighCol; public Color[] StateHoverCol; public Color[] StateLowCol; + public Color[] NoteCol; + public Color[] DisplayLEDCols; // Disconnected, Off, On + public Color[] FlatColors; + public Color[] FlatColorsHover; + public Color[] PinSizeIndicatorColors; } public class UIThemeDLS diff --git a/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs b/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs index 932230d8..826cacda 100644 --- a/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs +++ b/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs @@ -23,7 +23,10 @@ public static class BottomBarUI $"NEW CHIP {shortcutTextCol}Ctrl+N", $"SAVE CHIP {shortcutTextCol}Ctrl+S", $"FIND CHIP {shortcutTextCol}Ctrl+F", + //$"NEW NOTE {shortcutTextCol}Ctrl+M", + $"ADD SPECIAL {shortcutTextCol}Ctrl+B", $"LIBRARY {shortcutTextCol}Ctrl+L", + $"STATS {shortcutTextCol}Ctrl+T", // Ctrl+'T' from the T in Stats $"PREFS {shortcutTextCol}Ctrl+P", $"QUIT {shortcutTextCol}Ctrl+Q" }; @@ -31,9 +34,13 @@ public static class BottomBarUI const int NewChipButtonIndex = 0; const int SaveChipButtonIndex = 1; const int FindChipButtonIndex = 2; - const int LibraryButtonIndex = 3; - const int OptionsButtonIndex = 4; - const int QuitButtonIndex = 5; + const int AddSpecialButtonIndex = 3; + const int LibraryButtonIndex = 4; + const int StatsButtonIndex = 5; + const int OptionsButtonIndex = 6; + const int QuitButtonIndex = 7; + + const string c = ""; // ---- State ---- static float scrollX; @@ -110,7 +117,10 @@ void ButtonPressed(int i) if (i == NewChipButtonIndex) CreateNewChip(); else if (i == SaveChipButtonIndex) OpenSaveMenu(); else if (i == FindChipButtonIndex) OpenSearchMenu(); + //else if (i == NewNoteButtonIndex) CreateNewNote(); + else if (i == AddSpecialButtonIndex) OpenAddSpecialMenu(); else if (i == LibraryButtonIndex) OpenLibraryMenu(); + else if (i == StatsButtonIndex) OpenStatsMenu(); else if (i == OptionsButtonIndex) OpenPreferencesMenu(); else if (i == QuitButtonIndex) ExitToMainMenu(); } @@ -388,7 +398,9 @@ static void ExitIfTrue(bool exit) static void OpenSaveMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.ChipSave); static void OpenSearchMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.Search); static void OpenLibraryMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.ChipLibrary); + static void OpenStatsMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.ProjectStats); static void OpenPreferencesMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.Preferences); + static void OpenAddSpecialMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.SpecialChipMaker); static void CreateNewChip() { @@ -404,17 +416,25 @@ static void ConfirmNewChip(bool confirm) } } + static void CreateNewNote() + { + Project.ActiveProject.CreateBlankNote(Vector2.zero, "Text"); + } + static void HandleKeyboardShortcuts() { if (MenuButtonsAndShortcutsEnabled) { if (KeyboardShortcuts.CreateNewChipShortcutTriggered) CreateNewChip(); if (KeyboardShortcuts.SaveShortcutTriggered) OpenSaveMenu(); + if (KeyboardShortcuts.NewNoteShortcutTriggered) CreateNewNote(); if (KeyboardShortcuts.LibraryShortcutTriggered) OpenLibraryMenu(); } + if (KeyboardShortcuts.StatsShortcutTriggered) OpenStatsMenu(); if (KeyboardShortcuts.PreferencesShortcutTriggered) OpenPreferencesMenu(); if (KeyboardShortcuts.QuitToMainMenuShortcutTriggered) ExitToMainMenu(); + if (KeyboardShortcuts.SpecialChipsShortcutTriggered) OpenAddSpecialMenu(); } public static void Reset() diff --git a/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs index 959e0c26..a2e22025 100644 --- a/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs @@ -18,10 +18,15 @@ public static class ChipCustomizationMenu "Name: Top", "Name: Hidden" }; + static readonly string[] layoutOptions = + { + "Layout: Default", + "Layout: Custom" + }; - // ---- State ---- - static SubChipInstance[] subChipsWithDisplays; + // ---- State ---- + static SubChipInstance[] subChipsWithDisplays; static string displayLabelString; static string colHexCodeString; @@ -29,17 +34,29 @@ public static class ChipCustomizationMenu static readonly UIHandle ID_ColourPicker = new("CustomizeMenu_ChipCol"); static readonly UIHandle ID_ColourHexInput = new("CustomizeMenu_ChipColHexInput"); static readonly UIHandle ID_NameDisplayOptions = new("CustomizeMenu_NameDisplayOptions"); - static readonly UI.ScrollViewDrawElementFunc drawDisplayScrollEntry = DrawDisplayScroll; + static readonly UIHandle ID_LayoutOptions = new("CustomizeMenu_LayoutOptions"); + static readonly UI.ScrollViewDrawElementFunc drawDisplayScrollEntry = DrawDisplayScroll; static readonly Func hexStringInputValidator = ValidateHexStringInput; - - public static void OnMenuOpened() + public static bool isCustomLayout; + public static bool isDraggingPin; + static float pinDragStartY; + static float pinDragMouseStartY; + public static bool isPinPositionValid; + static float lastValidOffset; + static int lastValidFace; + static readonly float minPinSpacing = 0.025f; + public static PinInstance selectedPin; + public static void OnMenuOpened() { DevChipInstance chip = Project.ActiveProject.ViewedChip; subChipsWithDisplays = chip.GetSubchips().Where(c => c.Description.HasDisplay()).OrderBy(c => c.Position.x).ThenBy(c => c.Position.y).ToArray(); CustomizationSceneDrawer.OnCustomizationMenuOpened(); displayLabelString = $"DISPLAYS ({subChipsWithDisplays.Length}):"; + isCustomLayout = false; + isDraggingPin = false; + selectedPin = null; - InitUIFromChipDescription(); + InitUIFromChipDescription(); } public static void DrawMenu() @@ -53,16 +70,53 @@ public static void DrawMenu() DrawSettings.UIThemeDLS theme = DrawSettings.ActiveUITheme; UI.DrawPanel(UI.TopLeft, new Vector2(width, UI.Height), theme.MenuPanelCol, Anchor.TopLeft); - - // ---- Cancel/confirm buttons ---- - int cancelConfirmButtonIndex = MenuHelper.DrawButtonPair("CANCEL", "CONFIRM", UI.TopLeft + Vector2.down * pad, pw, false); + HandlePinDragging(); + // ---- Cancel/confirm buttons ---- + int cancelConfirmButtonIndex = MenuHelper.DrawButtonPair("CANCEL", "CONFIRM", UI.TopLeft + Vector2.down * pad, pw, false); // ---- Chip name UI ---- int nameDisplayMode = UI.WheelSelector(ID_NameDisplayOptions, nameDisplayOptions, NextPos(), new Vector2(pw, DrawSettings.ButtonHeight), theme.OptionsWheel, Anchor.TopLeft); ChipSaveMenu.ActiveCustomizeDescription.NameLocation = (NameDisplayLocation)nameDisplayMode; - - // ---- Chip colour UI ---- - Color newCol = UI.DrawColourPicker(ID_ColourPicker, NextPos(), pw, Anchor.TopLeft); + // ---- Chip layout UI ---- + int layoutMode = UI.WheelSelector(ID_LayoutOptions, layoutOptions, NextPos(), new Vector2(pw, DrawSettings.ButtonHeight), theme.OptionsWheel, Anchor.TopLeft); + if (layoutMode == 0 && isCustomLayout) + { + // Switch to default layout + isCustomLayout = false; + ChipSaveMenu.ActiveCustomizeChip.SetCustomLayout(false); + // Reset pins on the preview instance + foreach (PinInstance pin in ChipSaveMenu.ActiveCustomizeChip.InputPins) + { + pin.face = 3; + pin.LocalPosY = 0; + } + foreach (PinInstance pin in ChipSaveMenu.ActiveCustomizeChip.OutputPins) + { + pin.face = 1; + pin.LocalPosY = 0; + //Reset layout + + } + ChipSaveMenu.ActiveCustomizeChip.updateMinSize(); + if (ChipSaveMenu.ActiveCustomizeChip.MinSize.x > ChipSaveMenu.ActiveCustomizeChip.Description.Size.x) + { + ChipSaveMenu.ActiveCustomizeChip.Description.Size.x = ChipSaveMenu.ActiveCustomizeChip.MinSize.x; + } + if (ChipSaveMenu.ActiveCustomizeChip.MinSize.y > ChipSaveMenu.ActiveCustomizeChip.Description.Size.y) + { + ChipSaveMenu.ActiveCustomizeChip.Description.Size.y = ChipSaveMenu.ActiveCustomizeChip.MinSize.y; + } + ChipSaveMenu.ActiveCustomizeChip.UpdatePinLayout(); + } + else if (layoutMode == 1 && !isCustomLayout) + { + // Switch to custom layout + isCustomLayout = true; + ChipSaveMenu.ActiveCustomizeChip.SetCustomLayout(true); + } + + // ---- Chip colour UI ---- + Color newCol = UI.DrawColourPicker(ID_ColourPicker, NextPos(), pw, Anchor.TopLeft); InputFieldTheme inputTheme = MenuHelper.Theme.ChipNameInputField; inputTheme.fontSize = MenuHelper.Theme.FontSizeRegular; @@ -151,13 +205,33 @@ static void InitUIFromChipDescription() // Init name display mode WheelSelectorState nameDisplayWheelState = UI.GetWheelSelectorState(ID_NameDisplayOptions); nameDisplayWheelState.index = (int)ChipSaveMenu.ActiveCustomizeDescription.NameLocation; - } + + // Init layout mode by checking if any pins have custom positions + isCustomLayout = Project.ActiveProject.ViewedChip.HasCustomLayout; + + WheelSelectorState layoutWheelState = UI.GetWheelSelectorState(ID_LayoutOptions); + layoutWheelState.index = isCustomLayout ? 1 : 0; + } static void UpdateCustomizeDescription() { List displays = ChipSaveMenu.ActiveCustomizeChip.Displays; ChipSaveMenu.ActiveCustomizeDescription.Displays = displays.Select(s => s.Desc).ToArray(); - } + ChipSaveMenu.ActiveCustomizeDescription.HasCustomLayout = isCustomLayout; + + //Saves pin offset and faces + for (int i = 0; i < ChipSaveMenu.ActiveCustomizeChip.Description.InputPins.Length; i++) + { + ChipSaveMenu.ActiveCustomizeChip.Description.InputPins[i].LocalOffset = ChipSaveMenu.ActiveCustomizeChip.InputPins[i].LocalPosY; + ChipSaveMenu.ActiveCustomizeChip.Description.InputPins[i].face = ChipSaveMenu.ActiveCustomizeChip.InputPins[i].face; + } + for (int i = 0; i < ChipSaveMenu.ActiveCustomizeChip.Description.OutputPins.Length; i++) + { + ChipSaveMenu.ActiveCustomizeChip.Description.OutputPins[i].LocalOffset = ChipSaveMenu.ActiveCustomizeChip.OutputPins[i].LocalPosY; + ChipSaveMenu.ActiveCustomizeChip.Description.OutputPins[i].face = ChipSaveMenu.ActiveCustomizeChip.OutputPins[i].face; + + } + } static void UpdateChipColHexStringFromColour(Color col) { @@ -198,5 +272,173 @@ static bool ValidateHexStringInput(string text) return numHexDigits <= 6; } + + static void HandlePinDragging() + { + if (!InteractionState.MouseIsOverUI) + { + // Start dragging a pin + if (InputHelper.IsMouseDownThisFrame(MouseButton.Left)) + { + if (InteractionState.ElementUnderMouse is PinInstance pin) + { + selectedPin = pin; + isDraggingPin = true; + lastValidOffset = pin.LocalPosY; + lastValidFace = pin.face; + } + } + + if (isDraggingPin && selectedPin?.parent is SubChipInstance chip) + { + Vector2 mouseWorld = InputHelper.MousePosWorld; + Vector2 chipCenter = chip.Position; + Vector2 localMouse = mouseWorld - chipCenter; + Vector2 chipHalfSize = chip.Size / 2f; + + // Determine closest edge + float distTop = Mathf.Abs(localMouse.y - chipHalfSize.y); + float distBottom = Mathf.Abs(localMouse.y + chipHalfSize.y); + float distRight = Mathf.Abs(localMouse.x - chipHalfSize.x); + float distLeft = Mathf.Abs(localMouse.x + chipHalfSize.x); + + int closestFace = 0; + float minDist = distTop; + + if (distRight < minDist) { closestFace = 1; minDist = distRight; } + if (distBottom < minDist) { closestFace = 2; minDist = distBottom; } + if (distLeft < minDist) { closestFace = 3; } + + selectedPin.face = closestFace; + + float pinHeight = SubChipInstance.PinHeightFromBitCount(selectedPin.bitCount); + + float maxOffset; + float offsetAlongFace; + + bool shouldSnapToGrid = Project.ActiveProject.ShouldSnapToGrid; + + if (closestFace == 0 || closestFace == 2) + { + // Horizontal face - move along X axis + maxOffset = chipHalfSize.x - pinHeight / 2f; + offsetAlongFace = shouldSnapToGrid ? GridHelper.ClampToGrid(localMouse.x, -maxOffset, maxOffset) : + Mathf.Clamp(localMouse.x, -maxOffset, maxOffset); + } + else + { + // Vertical face - move along Y axis + maxOffset = chipHalfSize.y - pinHeight / 2f; + offsetAlongFace = shouldSnapToGrid ? GridHelper.ClampToGrid(localMouse.y, -maxOffset, maxOffset) : + Mathf.Clamp(localMouse.y, -maxOffset, maxOffset); + } + + selectedPin.LocalPosY = offsetAlongFace; + + PinInstance overlappedPin; + isPinPositionValid = !DoesPinOverlap(selectedPin, out overlappedPin); + + // End drag on mouse release + if (InputHelper.IsMouseUpThisFrame(MouseButton.Left)) + { + if (isPinPositionValid) + { + lastValidOffset = offsetAlongFace; + lastValidFace = selectedPin.face; + + if (!isCustomLayout) + { + isCustomLayout = true; + UI.GetWheelSelectorState(ID_LayoutOptions).index = 1; + ChipSaveMenu.ActiveCustomizeChip.SetCustomLayout(true); + } + } + else + { + selectedPin.LocalPosY = lastValidOffset; + selectedPin.face = lastValidFace; + } + + isDraggingPin = false; + selectedPin = null; + isPinPositionValid = true; + } + } + } + } + + public static bool DoesPinOverlap(PinInstance pin, out PinInstance overlappedPin) + { + overlappedPin = null; + if (!(pin.parent is SubChipInstance chip)) return false; + + // Get all pins on the same chip to check pins on the same face as selectedpin + List pinsToCheck = new List(); + pinsToCheck.AddRange(chip.InputPins); + pinsToCheck.AddRange(chip.OutputPins); + + foreach (PinInstance otherPin in pinsToCheck) + { + if (otherPin == pin) continue; + + // Only check pins on the same face + if (otherPin.face != pin.face) continue; + + float distanceAlongFace = Mathf.Abs(pin.LocalPosY - otherPin.LocalPosY); + + // Calculate minimum required spacing based on pin sizes + float pinHeight = SubChipInstance.PinHeightFromBitCount(pin.bitCount); + + float otherPinHeight = SubChipInstance.PinHeightFromBitCount(otherPin.bitCount); + + // Required space is half each pin's height plus some buffer + float requiredSpacing = (pinHeight + otherPinHeight) / 2f + minPinSpacing; + + if (distanceAlongFace < requiredSpacing) + { + overlappedPin = otherPin; + return true; + } + } + + return false; + } + + static void FaceSnapping(PinInstance pin, float mouseY) + { + if (pin.parent is SubChipInstance chip) + { + Vector2 chipSize = chip.Size; + Vector2 chipPos = chip.Position; + + //Calculate distances to top and bottom edges + float distanceToTop = Mathf.Abs(mouseY - (chipPos.y + chipSize.y / 2)); + float distanceToBottom = Mathf.Abs(mouseY - (chipPos.y - chipSize.y / 2)); + + //Calculate distances to left and right edges + float distanceToLeft = Mathf.Abs(chipPos.x - chipSize.x / 2); + float distanceToRight = Mathf.Abs(chipPos.x + chipSize.x / 2); + + //Determine the closest vertical edge (top or bottom) + float closestVerticalDistance = Mathf.Min(distanceToTop, distanceToBottom); + bool isTopCloser = closestVerticalDistance == distanceToTop; + + //Determine the closest horizontal edge (left or right) + float closestHorizontalDistance = Mathf.Min(distanceToLeft, distanceToRight); + bool isLeftCloser = closestHorizontalDistance == distanceToLeft; + //Compare the closest of the 2 previously closests + if (closestVerticalDistance < closestHorizontalDistance) + { + if (isTopCloser) {pin.face = 0;} + else {pin.face = 2;} + } + else + { + if (isLeftCloser) {pin.face = 3;} + else{ pin.face = 1; } + } + } + } + } } \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs index 4a333008..882d7ba3 100644 --- a/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs @@ -250,6 +250,7 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) int buttonIndex_moveStep = DrawHorizontalButtonGroup(buttonNames_moveSingleStep, interactableStates_move, ref topLeft, panelContentBounds.Width); int buttonIndex_moveJump = DrawHorizontalButtonGroup(buttonNames_jump, interactableStates_move, ref topLeft, panelContentBounds.Width); ChipActionButtons(selectedChipName, ref topLeft, panelContentBounds.Width); + bool stats = DrawHorizontalButtonGroup(new[]{"STATS"}, null, ref topLeft, panelContentBounds.Width) == 0; bool moveSingleStepDown = buttonIndex_moveStep == 1; bool moveJumpDown = buttonIndex_moveJump == 1; @@ -262,6 +263,11 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) project.SetStarred(selectedChipName, !isStarred, false); } + if (stats) { + ChipStatsMenu.SetChip(selectedChipName); + UIDrawer.SetActiveMenu(UIDrawer.MenuType.ChipStats); + } + if (moveSingleStepDown || moveJumpDown) // Move chip down { bool moveWithinCurrentCollection = (moveSingleStepDown && canStepDownInCollection) || (moveJumpDown && !canJumpDownACollection); @@ -298,6 +304,7 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) { // ---- Draw ---- ChipCollection collection = collections[selectedCollectionIndex]; + string selectedCollectionName = collection.Name; ButtonTheme colSource = GetButtonTheme(true, true); DrawHeader(collection.Name, colSource.buttonCols.normal, colSource.textCols.normal, ref topLeft, panelContentBounds.Width); @@ -313,6 +320,12 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) interactableStates_renameDelete[1] = canRenameOrDelete; int buttonIndexEditCollection = DrawHorizontalButtonGroup(buttonNames_collectionRenameOrDelete, interactableStates_renameDelete, ref topLeft, panelContentBounds.Width); + bool stats = DrawHorizontalButtonGroup(new[]{"STATS"}, null, ref topLeft, panelContentBounds.Width) == 0; + + if (stats) { + CollectionStatsMenu.SetCollection(selectedCollectionName); + UIDrawer.SetActiveMenu(UIDrawer.MenuType.CollectionStats); + } // ---- Handle button inputs ---- if (toggleStarred) { @@ -450,10 +463,11 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) topLeft = UI.GetCurrentBoundsScope().BottomLeft + Vector2.down * SectionSpacing; MenuHelper.DrawReservedMenuPanel(panelID, UI.GetCurrentBoundsScope()); } - } - // Delete confirmation - if (isConfirmingChipDeletion || isConfirmingCollectionDeletion) + } + + // Delete confirmation + if (isConfirmingChipDeletion || isConfirmingCollectionDeletion) { using (UI.BeginBoundsScope(true)) { @@ -736,5 +750,6 @@ string FormatChipName(int index) } } } - } + + } } \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ChipSaveMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ChipSaveMenu.cs index 53b191ea..6a7e078a 100644 --- a/Assets/Scripts/Graphics/UI/Menus/ChipSaveMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/ChipSaveMenu.cs @@ -113,15 +113,38 @@ public static void DrawMenu() } } - // Create a subchip instance based on the current dev chip (we need a subchip instance to be able to draw a preview of the chip in the customization menu) - // The description on this subchip holds potential customizations, such as name changes, resizing, colour etc. - static SubChipInstance CreateCustomizationState() - { - ChipDescription desc = DescriptionCreator.CreateChipDescription(Project.ActiveProject.ViewedChip); - return CreatePreviewSubChipInstance(desc); - } - - static void OpenCustomizationMenu() + // Create a subchip instance based on the current dev chip (we need a subchip instance to be able to draw a preview of the chip in the customization menu) + // The description on this subchip holds potential customizations, such as name changes, resizing, colour etc. + // This will load custom pin layouts if available to support editting existing chips. if no custom layout will use default behaviours. + static SubChipInstance CreateCustomizationState() + { + DevChipInstance viewedChip = Project.ActiveProject.ViewedChip; + ChipDescription desc = DescriptionCreator.CreateChipDescription(viewedChip); + + desc.HasCustomLayout = viewedChip.HasCustomLayout; + + // Copy layout if it exists + if (desc.HasCustomLayout && viewedChip.LastSavedDescription != null) + { + var savedDesc = viewedChip.LastSavedDescription; + + for (int i = 0; i < desc.InputPins.Length && i < savedDesc.InputPins.Length; i++) + { + desc.InputPins[i].face = savedDesc.InputPins[i].face; + desc.InputPins[i].LocalOffset = savedDesc.InputPins[i].LocalOffset; + } + + for (int i = 0; i < desc.OutputPins.Length && i < savedDesc.OutputPins.Length; i++) + { + desc.OutputPins[i].face = savedDesc.OutputPins[i].face; + desc.OutputPins[i].LocalOffset = savedDesc.OutputPins[i].LocalOffset; + } + } + + return CreatePreviewSubChipInstance(desc); + } + + static void OpenCustomizationMenu() { ActiveCustomizeChip = CreatePreviewSubChipInstance(ActiveCustomizeDescription); CustomizeStateBeforeEnteringCustomizeMenu = CreatePreviewSubChipInstance(Saver.CloneChipDescription(ActiveCustomizeDescription)); diff --git a/Assets/Scripts/Graphics/UI/Menus/ChipStatsMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ChipStatsMenu.cs new file mode 100644 index 00000000..2666dc96 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ChipStatsMenu.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; +using DLS.Description; +using DLS.Game; +using Seb.Types; +using Seb.Vis; +using Seb.Vis.UI; +using UnityEngine; + +namespace DLS.Graphics +{ + public static class ChipStatsMenu + { + const float entrySpacing = 0.5f; + const float menuWidth = 55; + const float verticalOffset = 22; + + static readonly Vector2 entrySize = new(menuWidth, DrawSettings.SelectorWheelHeight); + public static readonly Vector2 settingFieldSize = new(entrySize.x / 3, entrySize.y); + + static string chip; + + // ---- Stats ---- + static readonly string usesLabel = "Uses"; + + static readonly string totalUsesLabel = "Total uses"; + + static readonly string usedByLabel = "Used by"; + + static readonly string numOfChipsInChipLabel = "Number of chips in this chip"; + + + public static void SetChip(string chip) { + ChipStatsMenu.chip = chip; + isChipBuiltIn = Project.ActiveProject.chipLibrary.IsBuiltinChip(chip); + } + static bool isChipBuiltIn; + public static void DrawMenu() + { + DrawSettings.UIThemeDLS theme = DrawSettings.ActiveUITheme; + MenuHelper.DrawBackgroundOverlay(); + Draw.ID panelID = UI.ReservePanel(); + + const int inputTextPad = 1; + Color labelCol = Color.white; + Color headerCol = new(0.46f, 1, 0.54f); + Vector2 topLeft = UI.Centre + new Vector2(-menuWidth / 2, verticalOffset); + Vector2 labelPosCurr = topLeft; + + using (UI.BeginBoundsScope(true)) + { + // Draw stats + Vector2 usesLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, usesLabel, labelCol * 0.75f, true); + UI.DrawPanel(usesLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(GetChipUses().ToString(), theme.FontBold, theme.FontSizeRegular, usesLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + AddSpacing(); + + Vector2 usedByLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, usedByLabel, labelCol * 0.75f, true); + UI.DrawPanel(usedByLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(GetChipUsedBy().ToString(), theme.FontBold, theme.FontSizeRegular, usedByLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + AddSpacing(); + + Vector2 totalUsesLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, totalUsesLabel, labelCol * 0.75f, true); + UI.DrawPanel(totalUsesLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(GetChipUsesTotal().ToString(), theme.FontBold, theme.FontSizeRegular, totalUsesLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + + if (!isChipBuiltIn) { + AddSpacing(); + Vector2 numOfChipsInChipLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, numOfChipsInChipLabel, labelCol * 0.75f, true); + UI.DrawPanel(numOfChipsInChipLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(GetNumOfChipsInChip().ToString(), theme.FontBold, theme.FontSizeRegular, numOfChipsInChipLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + } + + // Draw close + Vector2 buttonTopLeft = new(50, UI.PrevBounds.Bottom - 1 * (DrawSettings.DefaultButtonSpacing * 6)); + bool result = UI.Button("CLOSE", MenuHelper.Theme.ButtonTheme, buttonTopLeft, new Vector2(menuWidth / 1.118f, 0)); + + // Draw menu background + Bounds2D menuBounds = UI.GetCurrentBoundsScope(); + MenuHelper.DrawReservedMenuPanel(panelID, menuBounds); + + // Close + if (result) + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + + return; + + void AddSpacing() + { + labelPosCurr.y -= entrySize.y + entrySpacing; + } + } + + private static uint GetChipUses() { + uint uses = 0; + foreach (ChipDescription chip in Project.ActiveProject.chipLibrary.allChips) + if (chip.Name != ChipStatsMenu.chip) + foreach (SubChipDescription subChip in chip.SubChips) + if (subChip.Name == ChipStatsMenu.chip) uses++; + + return uses; + } + private static int GetChipUsesTotal() { + Dictionary usesByChip = new(); + foreach (ChipDescription chip in Project.ActiveProject.chipLibrary.allChips) + { + usesByChip.Add(chip, 0); + foreach (SubChipDescription subChip in chip.SubChips) + if (subChip.Name == ChipStatsMenu.chip) usesByChip[chip]++; + else if (usesByChip.Any(e => e.Key.Name == subChip.Name)) usesByChip[chip] += usesByChip.First(e => e.Key.Name == subChip.Name).Value; + } + + return usesByChip.Values.ToArray().Sum(); + } + private static int GetNumOfChipsInChip() => + Project.ActiveProject.chipLibrary.GetChipDescription(chip).SubChips.Length; + + private static int GetChipUsedBy() => + Project.ActiveProject.chipLibrary.GetDirectParentChips(chip).Length; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ChipStatsMenu.cs.meta b/Assets/Scripts/Graphics/UI/Menus/ChipStatsMenu.cs.meta new file mode 100644 index 00000000..c7ad2449 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ChipStatsMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4d891c77cf9d5fb4984ca10a6b12e5fb \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/CollectionStatsMenu.cs b/Assets/Scripts/Graphics/UI/Menus/CollectionStatsMenu.cs new file mode 100644 index 00000000..a667e895 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/CollectionStatsMenu.cs @@ -0,0 +1,64 @@ +using System.Linq; +using DLS.Game; +using Seb.Types; +using Seb.Vis; +using Seb.Vis.UI; +using UnityEngine; + +namespace DLS.Graphics +{ + public static class CollectionStatsMenu + { + const float entrySpacing = 0.5f; + const float menuWidth = 55; + const float verticalOffset = 22; + + static readonly Vector2 entrySize = new(menuWidth, DrawSettings.SelectorWheelHeight); + public static readonly Vector2 settingFieldSize = new(entrySize.x / 3, entrySize.y); + + static string collection; + + // ---- Stats ---- + static readonly string numOfChipsLabel = "Number of chips"; + + public static void SetCollection(string collection) => CollectionStatsMenu.collection = collection; + public static void DrawMenu() + { + DrawSettings.UIThemeDLS theme = DrawSettings.ActiveUITheme; + MenuHelper.DrawBackgroundOverlay(); + Draw.ID panelID = UI.ReservePanel(); + + const int inputTextPad = 1; + Color labelCol = Color.white; + Color headerCol = new(0.46f, 1, 0.54f); + Vector2 topLeft = UI.Centre + new Vector2(-menuWidth / 2, verticalOffset); + Vector2 labelPosCurr = topLeft; + + using (UI.BeginBoundsScope(true)) + { + // Draw stats + Vector2 numOfChipsLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, numOfChipsLabel, labelCol * 0.75f, true); + UI.DrawPanel(numOfChipsLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(GetCollectionChipsLength().ToString(), theme.FontBold, theme.FontSizeRegular, numOfChipsLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + + // Draw close + Vector2 buttonTopLeft = new(50, UI.PrevBounds.Bottom - 1 * (DrawSettings.DefaultButtonSpacing * 6)); + bool result = UI.Button("CLOSE", MenuHelper.Theme.ButtonTheme, buttonTopLeft, new Vector2(menuWidth / 1.118f, 0)); + + // Draw menu background + Bounds2D menuBounds = UI.GetCurrentBoundsScope(); + MenuHelper.DrawReservedMenuPanel(panelID, menuBounds); + + // Close + if (result) + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + + return; + + } + + private static int GetCollectionChipsLength() => + Project.ActiveProject.description.ChipCollections.First(e => e.Name == collection).Chips.Count; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/CollectionStatsMenu.cs.meta b/Assets/Scripts/Graphics/UI/Menus/CollectionStatsMenu.cs.meta new file mode 100644 index 00000000..ea0bd31b --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/CollectionStatsMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5216bbbe9ac67bb43a8fa1acc4dd9812 \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ConstantEditMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ConstantEditMenu.cs new file mode 100644 index 00000000..9ac1b3c2 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ConstantEditMenu.cs @@ -0,0 +1,73 @@ +using System; +using DLS.Game; +using Seb.Helpers; +using Seb.Vis; +using Seb.Vis.UI; +using UnityEngine; + +namespace DLS.Graphics +{ + public static class ConstantEditMenu + { + static SubChipInstance constantChip; + static byte value; + + static readonly UIHandle ID_ValueInput = new("ConstantChipEdit_Value"); + static readonly Func integerInputValidator = ValidateValueInput; + + public static void DrawMenu() + { + MenuHelper.DrawBackgroundOverlay(); + Draw.ID panelID = UI.ReservePanel(); + DrawSettings.UIThemeDLS theme = DrawSettings.ActiveUITheme; + + Vector2 pos = UI.Centre + Vector2.up * (UI.HalfHeight * 0.25f); + + using (UI.BeginBoundsScope(true)) + { + UI.DrawText("Value of Constant", theme.FontBold, theme.FontSizeRegular, pos, Anchor.TextCentre, Color.white * 0.8f); + + InputFieldTheme inputFieldTheme = DrawSettings.ActiveUITheme.ChipNameInputField; + inputFieldTheme.fontSize = DrawSettings.ActiveUITheme.FontSizeRegular; + + Vector2 size = new(5.6f, DrawSettings.SelectorWheelHeight); + Vector2 inputPos = UI.PrevBounds.CentreBottom + Vector2.down * DrawSettings.VerticalButtonSpacing; + InputFieldState state = UI.InputField(ID_ValueInput, inputFieldTheme, inputPos, size, "0", Anchor.CentreTop, 1, integerInputValidator, forceFocus: true); + short tempValue; + if (state.text.Equals("-")) tempValue = 0; + else short.TryParse(state.text, out tempValue); + value = (byte)tempValue; + + MenuHelper.CancelConfirmResult result = MenuHelper.DrawCancelConfirmButtons(UI.GetCurrentBoundsScope().BottomLeft, UI.GetCurrentBoundsScope().Width, true); + MenuHelper.DrawReservedMenuPanel(panelID, UI.GetCurrentBoundsScope()); + + if (result == MenuHelper.CancelConfirmResult.Cancel) + { + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + else if (result == MenuHelper.CancelConfirmResult.Confirm) + { + Project.ActiveProject.NotifyConstantEdited(constantChip, value); + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + } + } + + public static void OnMenuOpened() + { + constantChip = (SubChipInstance)ContextMenu.interactionContext; + value = (byte)constantChip.InternalData[0]; + UI.GetInputFieldState(ID_ValueInput).SetText(value.ToString()); + } + + public static bool ValidateValueInput(string s) + { + Debug.Log(s); + if (s.Length > 4) return false; + if (string.IsNullOrEmpty(s)) return true; + if (s.Contains(" ")) return false; + if (s.Equals("-")) return true; + return short.TryParse(s, out _); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ConstantEditMenu.cs.meta b/Assets/Scripts/Graphics/UI/Menus/ConstantEditMenu.cs.meta new file mode 100644 index 00000000..48bfae92 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ConstantEditMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cd93cbc5664f856498b66b2f285fc8d7 \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ContextMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ContextMenu.cs index 086fd726..b3921545 100644 --- a/Assets/Scripts/Graphics/UI/Menus/ContextMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/ContextMenu.cs @@ -28,6 +28,10 @@ public static class ContextMenu new MenuEntry(Format(Enum.GetName(typeof(PinColour), col)), () => SetCol(col), CanSetCol) ).ToArray(); + static readonly MenuEntry[] noteColEntries = ((NoteColour[])Enum.GetValues(typeof(NoteColour))).Select(col => + new MenuEntry(Format(Enum.GetName(typeof(NoteColour), col)), () => SetCol(col), CanSetNoteCol) + ).ToArray(); + static readonly MenuEntry deleteEntry = new(Format("DELETE"), Delete, CanDelete); static readonly MenuEntry openChipEntry = new(Format("OPEN"), OpenChip, CanOpenChip); @@ -49,6 +53,8 @@ public static class ContextMenu static readonly MenuEntry[] entries_builtinLED = entries_builtinSubchip.Concat(new[] { dividerMenuEntry }).Concat(pinColEntries).ToArray(); + static readonly MenuEntry[] entries_builtinButton = entries_builtinLED; + static readonly MenuEntry[] entries_builtinBus = { new(Format("FLIP"), FlipBus, CanFlipBus), @@ -77,8 +83,16 @@ public static class ContextMenu deleteEntry }; + static readonly MenuEntry[] entries_builtinConstantChip = +{ + new(Format("EDIT"), OpenConstantEditMenu, CanEditCurrentChip), + labelChipEntry, + deleteEntry + }; + + - static readonly MenuEntry[] entries_subChipOutput = pinColEntries; + static readonly MenuEntry[] entries_subChipOutput = pinColEntries; static readonly MenuEntry[] entries_inputDevPin = new[] { @@ -99,6 +113,13 @@ public static class ContextMenu new(Format("DELETE"), Delete, CanDelete) }; + static readonly MenuEntry[] entries_note = new[] + { + new(Format("EDIT"), OpenNoteTextPopup, CanEditCurrentChip), + new(Format("DELETE"), Delete, CanDelete), + dividerMenuEntry + }.Concat(noteColEntries).ToArray(); + static readonly MenuEntry[] entries_bottomBarChip = { openChipEntry, @@ -159,8 +180,9 @@ static void HandleOpenMenuInput() bool openDevPinContextMenu = (hoverElement is PinInstance pin && pin.parent is DevPinInstance) || hoverElement is DevPinInstance; bool openWireContextMenu = hoverElement is WireInstance; bool openSubchipOutputPinContextMenu = hoverElement is PinInstance pin2 && pin2.parent is SubChipInstance && pin2.IsSourcePin && !pin2.IsBusPin; + bool openNoteContextMenu = hoverElement is NoteInstance; - if (openSubChipContextMenu || openDevPinContextMenu || openWireContextMenu || openSubchipOutputPinContextMenu) + if (openSubChipContextMenu || openDevPinContextMenu || openWireContextMenu || openSubchipOutputPinContextMenu || openNoteContextMenu) { interactionContextName = string.Empty; interactionContext = hoverElement; @@ -184,6 +206,9 @@ static void HandleOpenMenuInput() else if (subChip.ChipType is ChipType.Pulse) activeContextMenuEntries = entries_builtinPulseChip; else if (ChipTypeHelper.IsBusType(subChip.ChipType)) activeContextMenuEntries = entries_builtinBus; else if (subChip.ChipType == ChipType.DisplayLED) activeContextMenuEntries = entries_builtinLED; + else if (subChip.ChipType == ChipType.Button) activeContextMenuEntries = entries_builtinButton; + else if (subChip.ChipType == ChipType.Constant_8Bit) activeContextMenuEntries = entries_builtinConstantChip; + else activeContextMenuEntries = entries_builtinSubchip; } @@ -213,6 +238,12 @@ static void HandleOpenMenuInput() headerName = CreatePinHeaderName(pinContext.Name); activeContextMenuEntries = entries_subChipOutput; } + else if (openNoteContextMenu) + { + NoteInstance note = (NoteInstance)interactionContext; + headerName = "NOTE"; + activeContextMenuEntries = entries_note; + } SetContextMenuOpen(headerName); } @@ -341,11 +372,12 @@ void DrawHeader() static bool CanDelete() => Project.ActiveProject.CanEditViewedChip; static bool CanFlipBus() => Project.ActiveProject.CanEditViewedChip; + static bool CanSetNoteCol() => Project.ActiveProject.CanEditViewedChip && UIDrawer.ActiveMenu != UIDrawer.MenuType.ChipCustomization; static bool CanSetCol() { if (!Project.ActiveProject.CanEditViewedChip || UIDrawer.ActiveMenu == UIDrawer.MenuType.ChipCustomization) return false; if (interactionContext is PinInstance pin) return pin.IsSourcePin; - if (interactionContext is SubChipInstance subchip) return subchip.ChipType == ChipType.DisplayLED; + if (interactionContext is SubChipInstance subchip) return subchip.ChipType == ChipType.DisplayLED || subchip.ChipType == ChipType.Button; return false; } @@ -361,11 +393,25 @@ static void SetCol(PinColour col) { pin.Colour = col; } - else if (interactionContext is SubChipInstance subchip) + + if(!(interactionContext is SubChipInstance subchip)) { return; } + + else if (subchip.ChipType == ChipType.DisplayLED) { Project.ActiveProject.NotifyLEDColourChanged(subchip, (uint)col); } - + else if (subchip.ChipType == ChipType.Button) + { + Project.ActiveProject.NotifyLEDColourChanged(subchip, (uint)col); + subchip.OutputPins[0].Colour = col; + } + + } + + static void SetCol(NoteColour col) + { + NoteInstance note = (NoteInstance)interactionContext; + note.Colour = col; } static void OpenChipLabelPopup() @@ -373,6 +419,11 @@ static void OpenChipLabelPopup() UIDrawer.SetActiveMenu(UIDrawer.MenuType.ChipLabelPopup); } + static void OpenNoteTextPopup() + { + UIDrawer.SetActiveMenu(UIDrawer.MenuType.NoteTextPopup); + } + public static void EditWire() { Project.ActiveProject.controller.EnterWireEditMode((WireInstance)interactionContext); @@ -403,6 +454,8 @@ static void OpenKeyBindMenu() static void OpenPulseEditMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.PulseEdit); + static void OpenConstantEditMenu() => UIDrawer.SetActiveMenu(UIDrawer.MenuType.ConstantEdit); + static bool CanEditCurrentChip() => Project.ActiveProject.CanEditViewedChip; static bool CanEditWire() => CanEditCurrentChip(); diff --git a/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs b/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs index 97f8205d..eb783828 100644 --- a/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs @@ -486,7 +486,12 @@ static void DrawVersionInfo() static void Quit() { - Application.Quit(); + #if UNITY_EDITOR + // There should be a NullReferenceException when quitting, but it does not affect the application. + UnityEditor.EditorApplication.isPlaying = false; + #else + Application.Quit(); + #endif } enum MenuScreen diff --git a/Assets/Scripts/Graphics/UI/Menus/NoteTextMenu.cs b/Assets/Scripts/Graphics/UI/Menus/NoteTextMenu.cs new file mode 100644 index 00000000..85ab5ae9 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/NoteTextMenu.cs @@ -0,0 +1,76 @@ +using DLS.Game; +using Seb.Types; +using Seb.Vis; +using Seb.Vis.UI; +using UnityEngine; + +namespace DLS.Graphics +{ + public static class NoteTextMenu + { + const string MaxLabelLength = "MY REAL LONG LABEL TEXT"; + static NoteInstance note; + static readonly UIHandle ID_NameField = new("NoteTextMenu_NameField"); + + static readonly string[] CancelConfirmButtonNames = + { + "CANCEL", "CONFIRM" + }; + + static readonly bool[] ButtonGroupInteractStates = { true, true }; + + public static void OnMenuOpened() + { + note = (NoteInstance)ContextMenu.interactionContext; + + TextAreaState textAreaState = UI.GetTextAreaState(ID_NameField); + textAreaState.SetText(note.Text); + textAreaState.SelectAll(); + } + + public static void DrawMenu() + { + UI.DrawFullscreenPanel(DrawSettings.ActiveUITheme.MenuBackgroundOverlayCol); + float spacing = 0.8f; + + DrawSettings.UIThemeDLS theme = DrawSettings.ActiveUITheme; + InputFieldTheme inputTheme = DrawSettings.ActiveUITheme.ChipNameInputField; + Draw.ID panelID = UI.ReservePanel(); + + using (UI.BeginBoundsScope(true)) + { + Vector2 unpaddedSize = Draw.CalculateTextBoundsSize(MaxLabelLength, inputTheme.fontSize, inputTheme.font); + const float padX = 2.25f; + Vector2 inputFieldSize = unpaddedSize + new Vector2(padX, 28f); + Vector2 pos = UI.Centre + Vector2.up * 5; + + // Draw input field + TextAreaState inputFieldState = UI.TextArea(ID_NameField, inputTheme, pos, inputFieldSize, note.Text, Anchor.Centre, padX / 2, MaxLabelLength, 8, null, true); + Bounds2D inputFieldBounds = UI.PrevBounds; + string newName = string.Join("", inputFieldState.lines); + + // Draw cancel/confirm buttons + Vector2 buttonsTopLeft = UI.PrevBounds.BottomLeft + Vector2.down * spacing; + int buttonIndex = UI.HorizontalButtonGroup(CancelConfirmButtonNames, ButtonGroupInteractStates, theme.ButtonTheme, buttonsTopLeft, inputFieldBounds.Width, DrawSettings.DefaultButtonSpacing, 0, Anchor.TopLeft); + + MenuHelper.DrawReservedMenuPanel(panelID, UI.GetCurrentBoundsScope()); + + // Keyboard shortcuts and UI input + if (KeyboardShortcuts.CancelShortcutTriggered || buttonIndex == 0) Cancel(); + else if (buttonIndex == 1) Confirm(newName); + } + } + + static void Confirm(string newName) + { + note.Text = newName; + note.Resize(); + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + + static void Cancel() + { + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/NoteTextMenu.cs.meta b/Assets/Scripts/Graphics/UI/Menus/NoteTextMenu.cs.meta new file mode 100644 index 00000000..56cdef6a --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/NoteTextMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2b32928726c1a4f50aee89e2e60ac14a \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/PreferencesMenu.cs b/Assets/Scripts/Graphics/UI/Menus/PreferencesMenu.cs index 13909b2b..dccf87b4 100644 --- a/Assets/Scripts/Graphics/UI/Menus/PreferencesMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/PreferencesMenu.cs @@ -50,6 +50,14 @@ public static class PreferencesMenu "Active", "Paused" }; + static readonly string[] PinIndicators = + { + "Off", + "On Hover", + "Tab To Toggle", + "On Disconnected", + "Always" + }; static readonly Vector2 entrySize = new(menuWidth, DrawSettings.SelectorWheelHeight); public static readonly Vector2 settingFieldSize = new(entrySize.x / 3, entrySize.y); @@ -63,8 +71,9 @@ public static class PreferencesMenu static readonly UIHandle ID_SimStatus = new("PREFS_SimStatus"); static readonly UIHandle ID_SimFrequencyField = new("PREFS_SimTickTarget"); static readonly UIHandle ID_ClockSpeedInput = new("PREFS_ClockSpeed"); + static readonly UIHandle ID_PinIndicators = new("PREFS_PinIndicators"); - static readonly string showGridLabel = "Show grid" + CreateShortcutString("Ctrl+G"); + static readonly string showGridLabel = "Show grid" + CreateShortcutString("Ctrl+G"); static readonly string simStatusLabel = "Sim Status" + CreateShortcutString("Ctrl+Space"); static readonly Func integerInputValidator = ValidateIntegerInput; @@ -99,7 +108,8 @@ public static void DrawMenu(Project project) int mainPinNamesMode = DrawNextWheel("Show I/O pin names", PinDisplayOptions, ID_MainPinNames); int chipPinNamesMode = DrawNextWheel("Show chip pin names", PinDisplayOptions, ID_ChipPinNames); int gridDisplayMode = DrawNextWheel(showGridLabel, GridDisplayOptions, ID_GridDisplay); - DrawHeader("EDITING:"); + int pinIndicatorsMode = DrawNextWheel("Show Pin indicators",PinIndicators, ID_PinIndicators); + DrawHeader("EDITING:"); int snappingMode = DrawNextWheel("Snap to grid", SnappingOptions, ID_Snapping); int straightWireMode = DrawNextWheel("Straight wires", StraightWireOptions, ID_StraightWires); @@ -140,9 +150,10 @@ public static void DrawMenu(Project project) project.description.Prefs_SimTargetStepsPerSecond = targetSimTicksPerSecond; project.description.Prefs_SimStepsPerClockTick = clockSpeed; project.description.Prefs_SimPaused = pauseSim; + project.description.Perfs_PinIndicators = pinIndicatorsMode; - // Cancel / Confirm - if (result == MenuHelper.CancelConfirmResult.Cancel) + // Cancel / Confirm + if (result == MenuHelper.CancelConfirmResult.Cancel) { // Restore original description project.description = originalProjectDesc; @@ -207,8 +218,9 @@ static void UpdateUIFromDescription() UI.GetWheelSelectorState(ID_Snapping).index = projDesc.Prefs_Snapping; UI.GetWheelSelectorState(ID_StraightWires).index = projDesc.Prefs_StraightWires; UI.GetWheelSelectorState(ID_SimStatus).index = projDesc.Prefs_SimPaused ? 1 : 0; - // -- Input fields - UI.GetInputFieldState(ID_SimFrequencyField).SetText(projDesc.Prefs_SimTargetStepsPerSecond + "", false); + UI.GetWheelSelectorState(ID_PinIndicators).index = projDesc.Perfs_PinIndicators; + // -- Input fields + UI.GetInputFieldState(ID_SimFrequencyField).SetText(projDesc.Prefs_SimTargetStepsPerSecond + "", false); UI.GetInputFieldState(ID_ClockSpeedInput).SetText(projDesc.Prefs_SimStepsPerClockTick + "", false); } diff --git a/Assets/Scripts/Graphics/UI/Menus/ProjectStatsMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ProjectStatsMenu.cs new file mode 100644 index 00000000..5309006f --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ProjectStatsMenu.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using DLS.Description; +using DLS.Game; +using Seb.Types; +using Seb.Vis; +using Seb.Vis.UI; +using UnityEngine; + +namespace DLS.Graphics +{ + public static class ProjectStatsMenu + { + const float entrySpacing = 0.5f; + const float menuWidth = 55; + const float verticalOffset = 22; + + static readonly Vector2 entrySize = new(menuWidth, DrawSettings.SelectorWheelHeight); + public static readonly Vector2 settingFieldSize = new(entrySize.x / 3, entrySize.y); + + + // ---- Stats ---- + static readonly string srscLabel /* Source engine label */ = "Steps ran since created"; + static readonly string tsscLabel = "Time spent since created"; + static readonly string createdOnLabel = "Created on"; + static readonly string chipsLabel = "Chips"; + static readonly string chipsUsedLabel = "Chips used"; + static readonly string chipsUsedTotalLabel = "Total chips used"; + + public static void DrawMenu() + { + DrawSettings.UIThemeDLS theme = DrawSettings.ActiveUITheme; + MenuHelper.DrawBackgroundOverlay(); + Draw.ID panelID = UI.ReservePanel(); + + const int inputTextPad = 1; + Color labelCol = Color.white; + Color headerCol = new(0.46f, 1, 0.54f); + Vector2 topLeft = UI.Centre + new Vector2(-menuWidth / 2, verticalOffset); + Vector2 labelPosCurr = topLeft; + + using (UI.BeginBoundsScope(true)) + { + // Draw stats + Vector2 srscLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, srscLabel, labelCol * 0.75f, true); + UI.DrawPanel(srscLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(Project.ActiveProject.description.StepsRanSinceCreated.ToString(), theme.FontBold, theme.FontSizeRegular, srscLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + AddSpacing(); + + Vector2 tsscLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, tsscLabel, labelCol * 0.75f, true); + UI.DrawPanel(tsscLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(FormatTime(Project.ActiveProject.description.TimeSpentSinceCreated.Elapsed), theme.FontBold, theme.FontSizeRegular, tsscLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + AddSpacing(); + + Vector2 createdOnLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, createdOnLabel, labelCol * 0.75f, true); + UI.DrawPanel(createdOnLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(FormatTime(Project.ActiveProject.description.CreationTime), theme.FontBold, theme.FontSizeRegular, createdOnLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + AddSpacing(); + + Vector2 chipsLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, chipsLabel, labelCol * 0.75f, true); + UI.DrawPanel(chipsLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(Project.ActiveProject.chipLibrary.allChips.Count.ToString(), theme.FontBold, theme.FontSizeRegular, chipsLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + AddSpacing(); + + Vector2 chipsUsedLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, chipsUsedLabel, labelCol * 0.75f, true); + UI.DrawPanel(chipsUsedLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(GetChipsUsed().ToString(), theme.FontBold, theme.FontSizeRegular, chipsUsedLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + AddSpacing(); + + Vector2 chipsUsedTotalLabelRight = MenuHelper.DrawLabelSectionOfLabelInputPair(labelPosCurr, entrySize, chipsUsedTotalLabel, labelCol * 0.75f, true); + UI.DrawPanel(chipsUsedTotalLabelRight, settingFieldSize, new Color(0.18f, 0.18f, 0.18f), Anchor.CentreRight); + UI.DrawText(GetTotalChipsUsed().ToString(), theme.FontBold, theme.FontSizeRegular, chipsUsedTotalLabelRight + new Vector2(inputTextPad - settingFieldSize.x, 0), Anchor.TextCentreLeft, Color.white); + + // Draw close + Vector2 buttonTopLeft = new(50, UI.PrevBounds.Bottom - 1 * (DrawSettings.DefaultButtonSpacing * 6)); + bool result = UI.Button("CLOSE", MenuHelper.Theme.ButtonTheme, buttonTopLeft, new Vector2(menuWidth / 1.118f, 0)); + + // Draw menu background + Bounds2D menuBounds = UI.GetCurrentBoundsScope(); + MenuHelper.DrawReservedMenuPanel(panelID, menuBounds); + + // Close + if (result) + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + + return; + + + void AddSpacing() + { + labelPosCurr.y -= entrySize.y + entrySpacing; + } + + } + static int GetTotalChipsUsed() { + int uses = 0; + foreach (ChipDescription chip in Project.ActiveProject.chipLibrary.allChips) { + Dictionary usesByChip = new(); + foreach (ChipDescription chipchip in Project.ActiveProject.chipLibrary.allChips) + { + usesByChip.Add(chipchip, 0); + foreach (SubChipDescription subChip in chipchip.SubChips) + if (subChip.Name == chip.Name) usesByChip[chipchip]++; + else if (usesByChip.Any(e => e.Key.Name == subChip.Name)) usesByChip[chipchip] += usesByChip.First(e => e.Key.Name == subChip.Name).Value; + } + + uses += usesByChip.Values.ToArray().Sum(); + } + return uses; + } + static uint GetChipsUsed() { + uint uses = 0; + foreach (ChipDescription chip in Project.ActiveProject.chipLibrary.allChips) + foreach (SubChipDescription subChip in chip.SubChips) + uses++; + + return uses; + } + static string FormatTime(TimeSpan time) { + if (time.Days == 0 && time.Hours == 0 && time.Minutes == 0) + return $"{time.Seconds}s"; + else if (time.Days == 0 && time.Hours == 0) + return $"{time.Minutes}m {time.Seconds}s"; + else if (time.Days == 0) + return $"{time.Hours}h {time.Minutes}m {time.Seconds}s"; + else + return $"{time.Days}d {time.Hours}h {time.Minutes}m {time.Seconds}s"; + } + static string FormatTime(DateTime time) { + return time.ToString(@"MMM dd\, yyyy", CultureInfo.InvariantCulture); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ProjectStatsMenu.cs.meta b/Assets/Scripts/Graphics/UI/Menus/ProjectStatsMenu.cs.meta new file mode 100644 index 00000000..e2ebe0ea --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ProjectStatsMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d440a07d08a53c041aaaff570d965801 \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/SpecialChipMakerMenu.cs b/Assets/Scripts/Graphics/UI/Menus/SpecialChipMakerMenu.cs new file mode 100644 index 00000000..607b0c9a --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/SpecialChipMakerMenu.cs @@ -0,0 +1,353 @@ +using Seb.Vis.UI; +using Seb.Vis; +using UnityEngine; +using static DLS.Graphics.DrawSettings; +using System; +using System.Collections.Generic; +using DLS.Game; +using System.Linq; + + + +namespace DLS.Graphics +{ + public static class SpecialChipMakerMenu + { + public static List PinBitCountsMade = new(); + public static List> MergeSplitsMade = new(); + + public static List PinBitCountsAwaitingSave = new(); + public static List> MergeSplitsAwaitingSave = new(); + + public static bool saved; + + const float textSpacing = 0.25f; + const float entrySpacing = 0.5f; + const float menuWidth = 55; + const float verticalOffset = 22; + + static readonly Vector2 entrySize = new(menuWidth, DrawSettings.SelectorWheelHeight); + public static readonly Vector2 settingFieldSize = new(entrySize.x / 3, entrySize.y); + + static int previousValue; + public static bool changeToBeAdded; + public static bool displayDone; + + static readonly string[] SpecialChipTypes = + { + "Pins", + "Merge/Split" + }; + const int OPTION_PIN = 0; + const int OPTION_MERGE_SPLIT = 1; + + + static readonly UIHandle ID_SpecialChipTypes = new("SPEC_SpecialChipTypes"); + static readonly UIHandle ID_PinSize = new("SPEC_PinSize"); + + static readonly UIHandle ID_FirstMergeSplit = new("SPEC_MergeSplitA"); + static readonly UIHandle ID_SecondMergeSplit = new("SPEC_MergeSplitB"); + + + + static readonly Func pinSizeInputValidator = ValidatePinSizeInput; + + static bool canAddChip; + static int currentlyAddingPinBitOfSize; + + static KeyValuePair currentlyAddingMergeSplit; + + public static void DrawMenu() + { + UI.DrawFullscreenPanel(ActiveUITheme.MenuBackgroundOverlayCol); + + UIThemeDLS theme = ActiveUITheme; + InputFieldTheme inputTheme = ActiveUITheme.ChipNameInputField; + Draw.ID panelID = UI.ReservePanel(); + + const float headerSpacing = 1.5f; + Vector2 topLeft = UI.Centre + new Vector2(-menuWidth / 2, verticalOffset); + Vector2 labelPosCurr = topLeft; + Color labelCol = Color.white; + Color headerCol = new(0.46f, 1, 0.54f); + Color errorCol = new(1, 0.4f, 0.45f); + Color doneCol = new(128, 128, 0); + + + using (UI.BeginBoundsScope(true)) + { + DrawHeader("SPECIAL CHIPS:"); + int mainPinNamesMode = DrawNextWheel("Special chip type:", SpecialChipTypes, ID_SpecialChipTypes); + + if (mainPinNamesMode == OPTION_PIN) + { + DrawSpecialPinMenu(); + } + else if (mainPinNamesMode == OPTION_MERGE_SPLIT) + { + DrawSpecialMergeSplitMenu(); + } + + AddSpacing(); + DrawDoneSection(displayDone); + + Vector2 buttonTopLeft = new(labelPosCurr.x, UI.PrevBounds.Bottom - 2f); + int addOrClose = UI.VerticalButtonGroup(new[] { "Add special chip", "Save", "Close" }, new[] {canAddChip && !displayDone, !saved, true }, + ActiveUITheme.ButtonTheme, buttonTopLeft + (menuWidth / 2) * Vector2.right, entrySize, false, false, entrySpacing); + + if(mainPinNamesMode == OPTION_PIN && canAddChip && addOrClose == 0) + { + AddNewBitSize(currentlyAddingPinBitOfSize); + changeToBeAdded = false; + } + if(mainPinNamesMode == OPTION_MERGE_SPLIT && canAddChip && addOrClose == 0) + { + AddNewMergeSplit(currentlyAddingMergeSplit.Key, currentlyAddingMergeSplit.Value); + changeToBeAdded = false; + } + + + if (addOrClose == 1) + { + SaveChanges(); + Main.ActiveProject.SaveCurrentProjectDescription() ; + saved = true; + } + + if (addOrClose == 2) + { + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + + MenuHelper.DrawReservedMenuPanel(panelID, UI.GetCurrentBoundsScope()); + } + + if(KeyboardShortcuts.CancelShortcutTriggered) + { + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None) ; + } + + void DrawSpecialMergeSplitMenu() + { + DrawHeader("NEW MERGE/SPLIT CHIP:"); + InputFieldState firstPinSize = MenuHelper.LabeledInputField("First pin size:", labelCol, labelPosCurr, entrySize, ID_FirstMergeSplit, pinSizeInputValidator, settingFieldSize.x); + AddSpacing(); + InputFieldState secondPinSize = MenuHelper.LabeledInputField("Second pin size:", labelCol, labelPosCurr, entrySize, ID_SecondMergeSplit, pinSizeInputValidator, settingFieldSize.x); + int firstPinSizeAttempt = int.TryParse(firstPinSize.text, out int a) ? a : -1; + int secondPinSizeAttempt = int.TryParse(secondPinSize.text, out int b) ? b : -1; + (bool valid, string reason) confirmation = RealMergeSplitConfirmation(firstPinSizeAttempt, secondPinSizeAttempt); + + + + if (firstPinSizeAttempt != -1 && secondPinSizeAttempt != -1 && !confirmation.valid && !displayDone) + { + AddSpacing(); + DrawErrorSection(confirmation.reason); + canAddChip = false; + } + + else if (firstPinSizeAttempt != -1 && secondPinSizeAttempt != -1 && confirmation.valid) + { + canAddChip = true; + currentlyAddingMergeSplit = new (Math.Max(firstPinSizeAttempt, secondPinSizeAttempt), Math.Min(firstPinSizeAttempt, secondPinSizeAttempt)); + displayDone = DisplayDone(false); + return; + } + displayDone = DisplayDone(firstPinSizeAttempt == -1 && secondPinSizeAttempt == -1); + canAddChip = false; + } + + void DrawSpecialPinMenu() + { + DrawHeader("NEW PIN:"); + InputFieldState pinSizeInput = MenuHelper.LabeledInputField("Size of new pin:", labelCol, labelPosCurr,entrySize,ID_PinSize, pinSizeInputValidator, settingFieldSize.x); + int pinSizeAttempt = int.TryParse(pinSizeInput.text, out int a) ? a : -1; + (bool valid, string reason) confirmation = RealSizeConfirmation(pinSizeAttempt); + if (!confirmation.valid && pinSizeAttempt != -1 && !displayDone) + { + AddSpacing(); + DrawErrorSection(confirmation.reason); + canAddChip = false; + } + else if (confirmation.valid && pinSizeAttempt != -1) { + canAddChip = true; + currentlyAddingPinBitOfSize = pinSizeAttempt; + displayDone = DisplayDone(false); + return; + } + displayDone = DisplayDone(pinSizeAttempt == -1); + canAddChip = false; + } + + void DrawDoneSection(bool done) { + if(!done) { return; } + AddHeaderSpacing(); + UI.DrawText("DONE !", theme.FontBold, theme.FontSizeRegular, labelPosCurr, Anchor.TextCentreLeft, doneCol); + AddHeaderSpacing(); + + } + + void DrawErrorSection(string reason) + { + AddHeaderSpacing(); + UI.DrawText("ERROR", theme.FontBold, theme.FontSizeRegular, labelPosCurr, Anchor.TextCentreLeft, errorCol); + AddHeaderSpacing(); + + AddTextSpacing(); + UI.DrawText(" "+reason, theme.FontRegular, theme.FontSizeRegular, labelPosCurr, Anchor.TextCentreLeft, errorCol); + AddTextSpacing(); + + + } + + int DrawNextWheel(string label, string[] options, UIHandle id) + { + int index = MenuHelper.LabeledOptionsWheel(label, labelCol, labelPosCurr, entrySize, id, options, settingFieldSize.x, true); + AddSpacing(); + return index; + } + + void DrawHeader(string text) + { + AddHeaderSpacing(); + UI.DrawText(text, theme.FontBold, theme.FontSizeRegular, labelPosCurr, Anchor.TextCentreLeft, headerCol); + AddHeaderSpacing(); + } + + void AddSpacing() + { + labelPosCurr.y -= entrySize.y + entrySpacing; + } + + void AddHeaderSpacing() + { + labelPosCurr.y -= headerSpacing; + } + + void AddTextSpacing() + { + labelPosCurr.y -= textSpacing; + } + + } + + public static void OnMenuOpened() + { + PinBitCountsAwaitingSave = new(); + MergeSplitsAwaitingSave = new(); + + RefreshPinBitCounts(); + RefreshMergeSplits(); + saved = true; + changeToBeAdded = true; + displayDone = false; + previousValue = -1; + } + + public static void RefreshPinBitCounts() + { + PinBitCountsMade = new(); + foreach(var pinBit in Main.ActiveProject.description.pinBitCounts) + { + PinBitCountsMade.Add(pinBit.BitCount); + } + foreach(var pinBit in PinBitCountsAwaitingSave) + { + PinBitCountsMade.Add(pinBit); + } + } + + public static void RefreshMergeSplits() + { + MergeSplitsMade = new(); + foreach (var PinBitPair in Main.ActiveProject.description.SplitMergePairs) + { + MergeSplitsMade.Add(new(PinBitPair.Key, PinBitPair.Value)); + } + + foreach(var pair in MergeSplitsAwaitingSave) + { + MergeSplitsMade.Add(pair); + } + } + + public static bool ValidatePinSizeInput(string s) + { + if (string.IsNullOrEmpty(s)){ changeToBeAdded = false; return true; } + if (s.Contains(" ")) return false; + if (int.TryParse(s, out int a)) + { + if(a < 0) return false; + if(a > 65536) return false; + changeToBeAdded = previousValue != a; + previousValue = a; + return true; + } + return false; + } + + public static (bool valid, string reason) RealSizeConfirmation(int a) + { + if (a < 1) { return (false, "Pin size must be at least 1 bit."); } + if (a > 64 && a % 8 != 0 && a<= 512) { return (false, "Pin size > 64 and not a multiple of 8."); } + if(a > 512 && a % 64 != 0 && a <= 4096) { return (false, "Pin size > 512 and not a multiple of 64."); } + if (a > 4096 && a % 512 != 0) { return (false, "Pin size > 4096 and not a multiple of 512."); } + if (PinBitCountsMade.Contains(a)) { return (false, "Pins with this count already exist."); } + + + return (true, ""); + } + + public static (bool valid, string reason) RealMergeSplitConfirmation(int a, int b) + { + if (MergeSplitsMade.Any(k => (k.Key == a && k.Value == b) || (k.Value == a && k.Key == b))) { return (false, "These Merge/Split chips already exist."); } + if (!PinBitCountsMade.Contains(a) && !PinBitCountsMade.Contains(b) ) { return (false, $"No pins with pinsize {a} and {b} exist. Create them first."); } + if (!PinBitCountsMade.Contains(a) ) { return (false, $"No pin with pinsize {a} exist. Create it first, if valid."); } + if (!PinBitCountsMade.Contains(b) ) { return (false, $"No pin with pinsize {b} exist. Create it first, if valid."); } + int bigger = Math.Max(a, b); + int smaller = Math.Min(a, b); + if(bigger%smaller != 0) { return (false, $"{bigger} / {smaller} isn't an integer."); } + + + return (true, ""); + } + + public static void AddNewBitSize(int a) + { + PinBitCountsAwaitingSave.Add(a); + RefreshPinBitCounts(); + saved = false; + + } + + public static void AddNewMergeSplit(int a, int b) + { + MergeSplitsAwaitingSave.Add(new(a,b)); + RefreshMergeSplits(); + saved = false; + } + + public static bool DisplayDone(bool empty) + { + return !changeToBeAdded && !empty; + } + + public static void SaveChanges() + { + if (!saved) + { + foreach (int a in PinBitCountsAwaitingSave) + { + Main.ActiveProject.AddNewPinSize(a); + } + foreach (var pair in MergeSplitsAwaitingSave) + { + Main.ActiveProject.AddNewMergeSplit(pair.Key, pair.Value); + } + saved = true; + PinBitCountsAwaitingSave = new(); + MergeSplitsAwaitingSave = new(); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/SpecialChipMakerMenu.cs.meta b/Assets/Scripts/Graphics/UI/Menus/SpecialChipMakerMenu.cs.meta new file mode 100644 index 00000000..f3e113d2 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/SpecialChipMakerMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 343a789684b62a340a501dc405e29ef8 \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/UIDrawer.cs b/Assets/Scripts/Graphics/UI/UIDrawer.cs index 091248e4..d70f7b1c 100644 --- a/Assets/Scripts/Graphics/UI/UIDrawer.cs +++ b/Assets/Scripts/Graphics/UI/UIDrawer.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using DLS.Game; using Seb.Vis.UI; @@ -17,10 +18,16 @@ public enum MenuType MainMenu, RebindKeyChip, RomEdit, + ChipStats, + CollectionStats, + ProjectStats, PulseEdit, - UnsavedChanges, + ConstantEdit, + UnsavedChanges, Search, - ChipLabelPopup + ChipLabelPopup, + NoteTextPopup + SpecialChipMaker } static MenuType activeMenuOld; @@ -64,10 +71,16 @@ static void DrawProjectMenus(Project project) else if (menuToDraw == MenuType.PinRename) PinEditMenu.DrawMenu(); else if (menuToDraw == MenuType.RebindKeyChip) RebindKeyChipMenu.DrawMenu(); else if (menuToDraw == MenuType.RomEdit) RomEditMenu.DrawMenu(); + else if (menuToDraw == MenuType.ChipStats) ChipStatsMenu.DrawMenu(); + else if (menuToDraw == MenuType.CollectionStats) CollectionStatsMenu.DrawMenu(); + else if (menuToDraw == MenuType.ProjectStats) ProjectStatsMenu.DrawMenu(); else if (menuToDraw == MenuType.UnsavedChanges) UnsavedChangesPopup.DrawMenu(); else if (menuToDraw == MenuType.Search) SearchPopup.DrawMenu(); else if (menuToDraw == MenuType.ChipLabelPopup) ChipLabelMenu.DrawMenu(); + else if (menuToDraw == MenuType.NoteTextPopup) NoteTextMenu.DrawMenu(); else if (menuToDraw == MenuType.PulseEdit) PulseEditMenu.DrawMenu(); + else if (menuToDraw == MenuType.ConstantEdit) ConstantEditMenu.DrawMenu(); + else if (menuToDraw == MenuType.SpecialChipMaker) SpecialChipMakerMenu.DrawMenu(); else { bool showSimPausedBanner = project.simPaused; @@ -97,7 +110,11 @@ static void NotifyIfActiveMenuChanged() else if (ActiveMenu == MenuType.RomEdit) RomEditMenu.OnMenuOpened(); else if (ActiveMenu == MenuType.Search) SearchPopup.OnMenuOpened(); else if (ActiveMenu == MenuType.ChipLabelPopup) ChipLabelMenu.OnMenuOpened(); + else if (ActiveMenu == MenuType.NoteTextPopup) NoteTextMenu.OnMenuOpened(); else if (ActiveMenu == MenuType.PulseEdit) PulseEditMenu.OnMenuOpened(); + else if (ActiveMenu == MenuType.ConstantEdit) ConstantEditMenu.OnMenuOpened(); + else if (ActiveMenu == MenuType.SpecialChipMaker) SpecialChipMakerMenu.OnMenuOpened(); + if (InInputBlockingMenu() && Project.ActiveProject != null && Project.ActiveProject.controller != null) { diff --git a/Assets/Scripts/Graphics/World/CustomizationSceneDrawer.cs b/Assets/Scripts/Graphics/World/CustomizationSceneDrawer.cs index 0657fd59..e3a5a241 100644 --- a/Assets/Scripts/Graphics/World/CustomizationSceneDrawer.cs +++ b/Assets/Scripts/Graphics/World/CustomizationSceneDrawer.cs @@ -5,6 +5,7 @@ using Seb.Types; using Seb.Vis; using UnityEngine; +using System.Linq; namespace DLS.Graphics { @@ -331,17 +332,39 @@ bool DrawScaleHandle(Vector2 edge, Vector2Int dir) Vector2 mouseDelta = InputHelper.MousePosWorld - chipResizeMouseStartPos; Vector2 desiredSize = chipResizeStartSize + Vector2.Scale(dir, mouseDelta) * 2; - // Always snap chip height so that pins align with grid lines/centers - float deltaY = GridHelper.SnapToGrid(desiredSize.y - chip.MinSize.y); - desiredSize.y = chip.MinSize.y + deltaY; - // Snap chip width to grid lines if in snap mode - if (Project.ActiveProject.ShouldSnapToGrid && dir.x != 0) desiredSize.x = GridHelper.SnapToGridForceEven(desiredSize.x) - DrawSettings.ChipOutlineWidth; - + // snaps if snapping is on or if chip has pins on top or bottom + bool snapX = Project.ActiveProject.ShouldSnapToGrid; + if (!snapX && chip.HasCustomLayout) + { + bool hasXFacePins = chip.InputPins.Concat(chip.OutputPins).Any(p => p.face == 0 || p.face == 2); + snapX = hasXFacePins; + } + + // snaps if snapping is on or if chip has pins on left or right. if default layout then forces snapping on Y + bool snapY = true; + if (chip.HasCustomLayout) + { + bool hasYFacePins = chip.InputPins.Concat(chip.OutputPins).Any(p => p.face == 1 || p.face == 3); + snapY = hasYFacePins || Project.ActiveProject.ShouldSnapToGrid; + } + + if (snapY && dir.y != 0) + { + float deltaY = GridHelper.SnapToGrid(desiredSize.y - chip.MinSize.y); + desiredSize.y = chip.MinSize.y + deltaY; + } + + if (snapX && dir.x != 0) + { + desiredSize.x = GridHelper.SnapToGridForceEven(desiredSize.x) - DrawSettings.ChipOutlineWidth; + } + + chip.updateMinSize(); Vector2 sizeNew = Vector2.Max(desiredSize, chip.MinSize); if (sizeNew != chip.Size) { - chip.Description.Size = Vector2.Max(desiredSize, chip.MinSize); + chip.Description.Size = Vector2.Max(desiredSize, chip.MinSize); ChipSaveMenu.ActiveCustomizeChip.UpdatePinLayout(); } } diff --git a/Assets/Scripts/Graphics/World/DevSceneDrawer.cs b/Assets/Scripts/Graphics/World/DevSceneDrawer.cs index eefb8307..f470d550 100644 --- a/Assets/Scripts/Graphics/World/DevSceneDrawer.cs +++ b/Assets/Scripts/Graphics/World/DevSceneDrawer.cs @@ -153,6 +153,9 @@ static void DrawMoveableElements(bool drawSelectedOnly) DrawSubChip(subchip); break; } + case NoteInstance note: + DrawNote(note); + break; } } @@ -199,12 +202,36 @@ public static void DrawPinLabel(PinInstance pin) FontType font = FontBold; Vector2 size = Draw.CalculateTextBoundsSize(text, FontSizePinLabel, font) + LabelBackgroundPadding; - Vector2 centre = pin.GetWorldPos() + pin.ForwardDir * (size.x / 2 + offsetX); + Vector2 centre = pin.GetWorldPos() + pin.FacingDir * (size.x / 2 + offsetX); Draw.Quad(centre, size, ActiveTheme.PinLabelCol); Draw.Text(font, text, FontSizePinLabel, centre, Anchor.TextFirstLineCentre, Color.white); } + public static void DrawNote(NoteInstance note) + { + Vector2 centre = note.Position + note.Size / 2; + int colIndex = (int)note.Colour; + Color col = ActiveTheme.NoteCol[colIndex]; + Color textCol = note.Colour == NoteColour.White ? Color.black : Color.white; + // Color backgroundColor = note.IsSelected ? ActiveTheme.NoteSelectedBackgroundCol : ActiveTheme.NoteBackgroundCol; + + Draw.Quad(centre, note.Size + Vector2.one * ChipOutlineWidth, GetChipOutlineCol(col)); + Draw.Quad(centre, note.Size, col); + Draw.Quad(centre + new Vector2(0, note.Size.y / 2 - 0.1f), new Vector2(note.Size.x, 0.2f), GetChipOutlineCol(col)); + Draw.Text(FontBold, "NOTE", 0.15f, centre + new Vector2(-note.Size.x / 2 + 0.2f, note.Size.y / 2 - 0.1f), Anchor.TextCentre, col); + + + // Draw.Quad(centre, size, backgroundColor); + Draw.Text(FontBold, note.Text, FontSizeNoteText, centre, Anchor.TextCentre, textCol); + + if (InputHelper.MouseInsideBounds_World(centre, note.Size)) + { + InteractionState.NotifyElementUnderMouse(note); + } + + } + public static void DrawSubChipLabel(SubChipInstance chip) { string text = chip.Label; @@ -220,13 +247,31 @@ public static void DrawSubChipLabel(SubChipInstance chip) Draw.Text(font, text, FontSizePinLabel, centre, Anchor.TextFirstLineCentre, Color.white); } + static void CopyToCharBuffer(DevPinInstance pin, string text) + { + char[] chars = text.ToCharArray(); + for (int i = 0; i < chars.Length; i++) { + pin.decimalDisplayCharBuffer[i] = chars[i]; + } + } public static void DrawPinDecValue(DevPinInstance pin) { if (pin.pinValueDisplayMode == PinValueDisplayMode.Off) return; + int charCount; + + if (pin.Pin.State.IsValueBiggerThanInt() || (((pin.GetStateDecimalDisplayValue()&(1<<31)) == (1<<31)) && pin.pinValueDisplayMode!=PinValueDisplayMode.SignedDecimal) ) + { + charCount = 7; + CopyToCharBuffer(pin, "TOO BIG"); + } - int charCount; + else if (pin.GetStateDecimalDisplayValue() == int.MinValue) + { + charCount = 11; + CopyToCharBuffer(pin, "-2147483648"); + } - if (pin.pinValueDisplayMode != PinValueDisplayMode.HEX) + else if (pin.pinValueDisplayMode != PinValueDisplayMode.HEX) { charCount = StringHelper.CreateIntegerStringNonAlloc(pin.decimalDisplayCharBuffer, pin.GetStateDecimalDisplayValue()); } @@ -236,6 +281,8 @@ public static void DrawPinDecValue(DevPinInstance pin) charCount = StringHelper.CreateHexStringNonAlloc(pin.decimalDisplayCharBuffer, pin.GetStateDecimalDisplayValue()); } + + FontType font = FontBold; Bounds2D parentBounds = pin.BoundingBox; const float offsetY = 0.225f; @@ -267,7 +314,7 @@ public static void DrawSubChip(SubChipInstance subchip) if (isKeyChip) { // Key changes colour when pressed down - if (PinState.FirstBitHigh(subchip.OutputPins[0].State)) chipCol = Color.white; + if (subchip.OutputPins[0].State.SmallHigh()) chipCol = Color.white; } Color outlineCol = GetChipOutlineCol(chipCol); @@ -441,6 +488,11 @@ public static Bounds2D DrawDisplay(DisplayInstance display, Vector2 posParent, f bounds = DrawDisplay_LED(posWorld, scaleWorld, col); } + else if (ChipTypeHelper.IsClickableDisplayType(display.DisplayType)) + { + bounds = DrawClickableDisplay(display, posParent, parentScale, rootChip, sim); + } + display.LastDrawBounds = bounds; return bounds; } @@ -582,9 +634,120 @@ public static Bounds2D DrawDisplay_LED(Vector2 centre, float scale, Color col) return Bounds2D.CreateFromCentreAndSize(centre, Vector2.one * scale); } - public static void DrawDevPin(DevPinInstance devPin) + public static Bounds2D DrawClickableDisplay(DisplayInstance display, Vector2 posParent, float parentScale, SubChipInstance rootChip, SimChip sim = null) + { + Bounds2D bounds = Bounds2D.CreateEmpty(); + Vector2 posLocal = display.Desc.Position; + Vector2 posWorld = posParent + posLocal * parentScale; + float scaleWorld = display.Desc.Scale * parentScale; + + bool inBounds = false; + bool clicked = false; + + // Calls to draw functions for each clickable display -- must include isSelected == false in the mouse detection to work. + if (display.DisplayType == ChipType.Button) + { + (bounds, inBounds, clicked) = DrawInteractable_Button(posWorld, scaleWorld, sim); + } + + else if (display.DisplayType == ChipType.Toggle) + { + (bounds, inBounds, clicked) = DrawInteractable_Toggle(posWorld, scaleWorld, sim); + } + + if (inBounds) + { + InteractionState.NotifyElementUnderMouse(display); + } + + rootChip.IsSelected = clicked ? false : rootChip.IsSelected; + + display.LastDrawBounds = bounds; + return bounds; + } + + public static (Bounds2D bounds, bool inBounds, bool clicked) DrawInteractable_Button(Vector2 centre, float scale, SimChip chipSource) + { + Bounds2D bounds = Bounds2D.CreateFromCentreAndSize(centre, Vector2.one * scale); + bool inBounds = false; + bool pressed = false; + + const float buttonSize = 0.875f; + Color col = ActiveTheme.StateDisconnectedCol; + + if (chipSource != null) + { + inBounds = bounds.PointInBounds(InputHelper.MousePosWorld); + pressed = inBounds && InputHelper.IsMouseHeld(MouseButton.Left) && controller.CanInteractWithButton; + uint displayColIndex = chipSource.InternalState[0]; + col = GetStateColour(pressed, displayColIndex); + chipSource.OutputPins[0].State.SmallSet(pressed ? Constants.LOGIC_HIGH : Constants.LOGIC_LOW); + } + + Vector2 buttonDrawSize = Vector2.one * (scale * buttonSize); + Draw.Squircle(centre, Vector2.one * scale, 0.15f * scale, ActiveTheme.DevPinHandle); + Draw.Squircle(centre, buttonDrawSize, 0.15f * scale * buttonSize, col); + + return (bounds, inBounds, pressed); + } + + public static (Bounds2D bounds, bool inBounds, bool clicked) DrawInteractable_Toggle(Vector2 centre, float scale, SimChip chipSource) + { + + Vector2 ratio = new Vector2(1f, 2f); + Bounds2D wholeBounds = Bounds2D.CreateFromCentreAndSize(centre, ratio * scale); + + const float switchHorizontalDrawRatio = 0.875f; + + bool inBounds = false; + bool gettingClicked = false; + + int currentSwitchHeadPos = 1; + int nextSwitchHeadPos = 1; + + + const float toggleSize = 1f; + Color col = ActiveTheme.StateDisconnectedCol; + + Vector2 toggleDrawSize = ratio * (scale * toggleSize); + Vector2 switchDrawSize = switchHorizontalDrawRatio * scale * toggleSize * Vector2.one; + Vector2 innerSwitchDrawSize = switchDrawSize * 0.775f; + + float verticalOffset = (toggleDrawSize.y / 2 - (switchDrawSize.y / switchHorizontalDrawRatio) / 2); + + + if (chipSource != null) + { + bool currentState = (chipSource.InternalState[0] & 1) == 1 ? true : false; + currentSwitchHeadPos = currentState ? -1 : 1; + Bounds2D bounds = Bounds2D.CreateFromCentreAndSize(centre + Vector2.up * verticalOffset * currentSwitchHeadPos, switchDrawSize); + inBounds = bounds.PointInBounds(InputHelper.MousePosWorld); + gettingClicked = inBounds && InputHelper.IsMouseDownThisFrame(MouseButton.Left) && controller.CanInteractWithButton; + bool nextState = gettingClicked ? !currentState : currentState; + chipSource.OutputPins[0].State.SmallSet(nextState ? Constants.LOGIC_HIGH : Constants.LOGIC_LOW); + + + nextSwitchHeadPos = nextState ? -1 : 1; + + if (currentState != nextState) { + chipSource.InternalState[0] = (uint)(nextState ? 1 : 0); + Project.ActiveProject.NotifyToggleStateChanged(chipSource); + } + } + verticalOffset *= nextSwitchHeadPos; + + Draw.Quad(centre, toggleDrawSize, col); + Draw.Quad(centre + Vector2.up * verticalOffset, switchDrawSize, ActiveTheme.BackgroundCol); + Draw.Quad(centre + Vector2.up * verticalOffset, innerSwitchDrawSize, ActiveTheme.DevPinHandleHighlighted); + + + return (wholeBounds, inBounds, gettingClicked); + } + + + public static void DrawDevPin(DevPinInstance devPin) { - if (devPin.BitCount == PinBitCount.Bit1) + if (devPin.BitCount == (uint)1) { Draw1BitDevPin(devPin); } @@ -777,8 +940,9 @@ static void DrawMultiBitWire(WireInstance wire) WireLayoutHelper.CreateMultiBitWireLayout(wire.BitWires, wire, WireThickness); + int length = wire.BitWires.Length / (wire.bitCount <= 64 ? 1 : wire.bitCount <=512 ? 8 : 64); // Draw - for (int bitIndex = 0; bitIndex < wire.BitWires.Length; bitIndex++) + for (int bitIndex = 0; bitIndex < length; bitIndex++) { WireInstance.BitWire bitWire = wire.BitWires[bitIndex]; Color col = wire.GetColour(bitIndex); @@ -847,9 +1011,30 @@ static void DrawPin(PinInstance pin) { DrawMultiBitPin(pin); } - } - static void DrawSingleBitPin(PinInstance pin) + //makes pins red if too close + if (ChipCustomizationMenu.isDraggingPin && ChipCustomizationMenu.selectedPin == pin && !ChipCustomizationMenu.isPinPositionValid) + { + Vector2 pinPos = pin.GetWorldPos(); + if (pin.bitCount == PinBitCount.Bit1) + { + Draw.Quad(pinPos, Vector2.one * PinRadius * 2.4f, Color.red); + } + else + { + float pinWidth = PinRadius * 2 * 0.95f; + float pinHeight = SubChipInstance.PinHeightFromBitCount(pin.bitCount); + + Vector2 pinSize = (pin.face == 0 || pin.face == 2) + ? new Vector2(pinHeight, pinWidth) // horizontal pin + : new Vector2(pinWidth, pinHeight); // vertical pin + + Draw.Quad(pinPos, pinSize * 1.2f, Color.red); + } + } + } + + static void DrawSingleBitPin(PinInstance pin) { Vector2 pinPos = pin.GetWorldPos(); Vector2 pinSelectionBoundsPos = pinPos + pin.ForwardDir * 0.02f; @@ -868,32 +1053,207 @@ static void DrawSingleBitPin(PinInstance pin) } Draw.Point(pinPos, PinRadius, pinCol); - } + // ---- input/output arrow ---- + + Vector2 dir = pin.face switch + { + 0 => Vector2.down, + 1 => Vector2.left, + 2 => Vector2.up, + 3 => Vector2.right, + _ => Vector2.zero, + }; + if (pin.IsSourcePin) + { + dir = -dir; + + } + float pinThickness = PinRadius * 2f; + float arrowLength = pinThickness * 0.35f; + float arrowWidth = arrowLength * 1.2f; + Vector2 perp = new Vector2(-dir.y, dir.x); + float edgeOffset = pinThickness / 4f; + Vector2 centerOffset = dir * edgeOffset * (pin.IsSourcePin ? 1 : -1); + Vector2 arrowCenter = pinPos + centerOffset; + Vector2 tip = arrowCenter + dir * (arrowLength / 2f); + Vector2 baseCenter = arrowCenter - dir * (arrowLength / 2f); + Vector2 baseLeft = baseCenter + perp * (arrowWidth / 2f); + Vector2 baseRight = baseCenter - perp * (arrowWidth / 2f); + + // Draws input/output indicators on subchip pins only + bool isInputToCustomChip = pin.parent is SubChipInstance; + if (isInputToCustomChip) + { + // Check if pin is connect to any wire for the Is Disconnected setting + + List wireList = Project.ActiveProject.controller.ActiveDevChip.Wires; + bool isConnected = false; + for (int i = wireList.Count - 1; i >= 0; i--) + { + WireInstance wire = wireList[i]; + if (PinAddress.Equals(wire.SourcePin.Address, pin.Address) || PinAddress.Equals(wire.TargetPin.Address, pin.Address)) + { + isConnected = true; + break; + } + + } + //set up display mode based on settings + int pinIndicatorMode = Project.ActiveProject.description.Perfs_PinIndicators; + bool drawIndicator = false; + switch (pinIndicatorMode) + { + case 1: // "On Hover" + drawIndicator = mouseOverPin; + break; + case 2: // "Tab To Toggle" + drawIndicator = Project.ActiveProject.PinNameDisplayIsTabToggledOn; + break; + case 3: // "If Pin is not connected" + drawIndicator = !isConnected; + break; + case 4: // "Always" + drawIndicator = true; + break; + + } + if (drawIndicator) + + { + Draw.Point(pinPos, PinRadius, new Color(34f / 255f, 34f / 255f, 34f / 255f, 1f)); + float angle = 0; + float wedgeSpan = 0f; + + if (!pin.IsSourcePin) + { + wedgeSpan = 100f; //edit angle of input + angle = Mathf.Atan2(-dir.y, -dir.x) * Mathf.Rad2Deg; + } + else + { + wedgeSpan = 150f; //edits angle of output + angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; + } + float angleStart = angle - wedgeSpan / 2f; + float angleEnd = angle + wedgeSpan / 2f; + Draw.WedgePolygon(pinPos, PinRadius, angleStart, angleEnd, ActiveTheme.PinCol, pin.face, pin.IsSourcePin); + } + } + } static void DrawMultiBitPin(PinInstance pin) - { - Vector2 pinPos = pin.GetWorldPos(); - Vector2 pinSelectionBoundsPos = pinPos + Vector2.right * ((pin.IsSourcePin ? 1 : -1) * 0.02f); - const float pinWidth = PinRadius * 2 * 0.95f; - float pinHeight = SubChipInstance.PinHeightFromBitCount(pin.bitCount); - Vector2 pinSize = new(pinWidth, pinHeight); + { + Vector2 pinPos = pin.GetWorldPos(); - bool mouseOverPin = !InteractionState.MouseIsOverUI && InputHelper.MouseInsideBounds_World(pinSelectionBoundsPos, pinSize); + bool isHorizontal = pin.face == 0 || pin.face == 2; + float pinWidth = PinRadius * 2 * 0.95f; + float pinHeight = SubChipInstance.PinHeightFromBitCount(pin.bitCount); + Vector2 pinSize = isHorizontal ? new Vector2(pinHeight, pinWidth) : new Vector2(pinWidth, pinHeight); - if (mouseOverPin) InteractionState.NotifyElementUnderMouse(pin); - bool canInteract = controller.CanInteractWithPin; + // Determine direction for selection offset (used for mouse interaction) + Vector2 offsetDir = Vector2.zero; + switch (pin.face) + { + case 1: offsetDir = Vector2.left; break; // right face + case 3: offsetDir = Vector2.right; break; // left face + } - Color pinCol = mouseOverPin && canInteract ? ActiveTheme.PinHighlightCol : ActiveTheme.PinCol; - // If hovering over pin while creating a wire, colour should indicate whether it is a valid connection - if (mouseOverPin && canInteract && controller.IsCreatingWire && !controller.CanCompleteWireConnection(pin)) + + Vector2 pinSelectionBoundsPos = pinPos + offsetDir * 0.02f; + bool mouseOverPin = !InteractionState.MouseIsOverUI && + InputHelper.MouseInsideBounds_World(pinSelectionBoundsPos, pinSize); + if (mouseOverPin) + InteractionState.NotifyElementUnderMouse(pin); + + bool canInteract = controller.CanInteractWithPin; + + Color pinCol = mouseOverPin && canInteract ? ActiveTheme.PinHighlightCol : ActiveTheme.PinCol; + // If hovering over pin while creating a wire, colour should indicate whether it is a valid connection + if (mouseOverPin && canInteract && controller.IsCreatingWire && !controller.CanCompleteWireConnection(pin)) + { + pinCol = ActiveTheme.PinInvalidCol; + } + + Draw.Quad(pinPos, pinSize, pinCol); + + + // Draw pin indicator + if (pin.bitCount >= 64 && !mouseOverPin) { - pinCol = ActiveTheme.PinInvalidCol; + Vector2 depthIndicatorSize = isHorizontal ? new(pinHeight, pinWidth / 8f) : new(pinWidth / 8f, pinHeight); + Draw.Quad(pinPos + pin.FacingDir * 0.25f * pinWidth, depthIndicatorSize, ActiveTheme.PinSizeIndicatorColors[pin.bitCount.GetTier()]); + } + + // Draws input/output indicators on subchip pins only + bool isOnCustomChip = pin.parent is SubChipInstance; + if (isOnCustomChip) + { + // Check if pin is connect to any wire + List wireList = Project.ActiveProject.controller.ActiveDevChip.Wires; + bool isConnected = false; + for (int i = wireList.Count - 1; i >= 0; i--) + { + WireInstance wire = wireList[i]; + if (PinAddress.Equals(wire.SourcePin.Address, pin.Address) || PinAddress.Equals(wire.TargetPin.Address, pin.Address)) + { + isConnected = true; + break; + } + + } + //set up display mode based on settings + int pinIndicatorMode = Project.ActiveProject.description.Perfs_PinIndicators; + bool drawIndicator = false; + switch (pinIndicatorMode) + { + case 1: // "On Hover" + drawIndicator = mouseOverPin; + break; + case 2: // "Tab To Toggle" + drawIndicator = Project.ActiveProject.PinNameDisplayIsTabToggledOn; + break; + case 3: // "If Pin is not connected" + drawIndicator = !isConnected; + break; + case 4: // "Always" + drawIndicator = true; + break; + } + if (drawIndicator) + { + Vector2 dir; + switch (pin.face) + { + case 0: dir = Vector2.down; break; + case 1: dir = Vector2.left; break; + case 2: dir = Vector2.up; break; + case 3: dir = Vector2.right; break; + default: dir = Vector2.zero; break; + } + if (pin.IsSourcePin) { dir = -dir; } + float pinThickness = isHorizontal ? pinSize.y : pinSize.x; + float arrowLength = pinThickness / 2f; + float arrowWidth = arrowLength * 2f; + Vector2 perp = new Vector2(-dir.y, dir.x); + + + float edgeOffset = pinThickness / 4f; + + //Shifts arrow based on if input/output to ensure its not hidden behind subchip + Vector2 centerOffset = dir * edgeOffset * (pin.IsSourcePin ? 1 : -1); + Vector2 arrowCenter = pinPos + centerOffset; + + Vector2 tip = arrowCenter + dir * (arrowLength / 2f); + Vector2 baseCenter = arrowCenter - dir * (arrowLength / 2f); + Vector2 baseLeft = baseCenter + perp * (arrowWidth / 2f); + Vector2 baseRight = baseCenter - perp * (arrowWidth / 2f); + Draw.Triangle(tip, baseLeft, baseRight, new Color(34f / 255f, 34f / 255f, 34f / 255f, 1f)); + } } - Draw.Quad(pinPos, pinSize, pinCol); - } - public static void DrawGrid(Color gridCol) + } + public static void DrawGrid(Color gridCol) { float thickness = GridThickness; @@ -949,7 +1309,7 @@ static int WireDrawOrder(WireInstance wire) if (wire.IsBusWire) return int.MaxValue - 2; // Draw wires carrying high signal above those carrying low signal (for single bit wires) - bool wireIsHigh = wire.bitCount == PinBitCount.Bit1 && PinState.FirstBitHigh(wire.SourcePin.State); + bool wireIsHigh = wire.bitCount == PinBitCount.Bit1 && wire.SourcePin.State.SmallHigh(); int drawPriority_signalHigh = wireIsHigh ? 1000 : 0; // Draw multi-bit wires above single bit wires diff --git a/Assets/Scripts/Graphics/World/WireLayoutHelper.cs b/Assets/Scripts/Graphics/World/WireLayoutHelper.cs index 5aa63d37..125c72e8 100644 --- a/Assets/Scripts/Graphics/World/WireLayoutHelper.cs +++ b/Assets/Scripts/Graphics/World/WireLayoutHelper.cs @@ -22,6 +22,7 @@ public static void CreateMultiBitWireLayout(WireInstance.BitWire[] bitWires, Wir Vector2 dirPrev = Vector2.zero; int numBits = bitWires.Length; + int wiresToDraw = (int)(numBits * (SubChipInstance.GetPinDepthMultiplier(wire.bitCount))); float offsetSign = 1; // Create layout @@ -36,10 +37,10 @@ public static void CreateMultiBitWireLayout(WireInstance.BitWire[] bitWires, Wir // This gives appearance of wires flipping over, rather than bending at an uncomfortable angle, which I think looks better... if (i > 0) offsetSign *= Flip(wireDir, dirPrev); - for (int bitIndex = 0; bitIndex < numBits; bitIndex++) + for (int bitIndex = 0; bitIndex < wiresToDraw; bitIndex++) { WireInstance.BitWire bitWire = bitWires[bitIndex]; - float bitOffsetDst = (bitIndex - (numBits - 1) / 2f) * thickness * 2 * thicknessOffsetT; + float bitOffsetDst = (bitIndex - (wiresToDraw - 1) / 2f) * thickness * 2 * thicknessOffsetT; Vector2 bitWireOffset = wirePerpDir * bitOffsetDst; Vector2 posA = i == 0 ? wireCentreA + bitWireOffset : bitWire.Points[i]; diff --git a/Assets/Scripts/SaveSystem/DescriptionCreator.cs b/Assets/Scripts/SaveSystem/DescriptionCreator.cs index 196e0ea1..a27dd4b3 100644 --- a/Assets/Scripts/SaveSystem/DescriptionCreator.cs +++ b/Assets/Scripts/SaveSystem/DescriptionCreator.cs @@ -20,12 +20,13 @@ public static ChipDescription CreateChipDescription(DevChipInstance chip) string name = hasSavedDesc ? descOld.Name : string.Empty; DisplayDescription[] displays = hasSavedDesc ? descOld.Displays : null; - // Create pin and subchip descriptions + // Create pin, subchip and notes descriptions PinDescription[] inputPins = OrderPins(chip.GetInputPins()).Select(CreatePinDescription).ToArray(); PinDescription[] outputPins = OrderPins(chip.GetOutputPins()).Select(CreatePinDescription).ToArray(); SubChipDescription[] subchips = chip.GetSubchips().Select(CreateSubChipDescription).ToArray(); Vector2 minChipsSize = SubChipInstance.CalculateMinChipSize(inputPins, outputPins, name); size = Vector2.Max(minChipsSize, size); + NoteDescription[] notes = chip.GetNotes().Select(CreateNoteDescription).ToArray(); UpdateWireIndicesForDescriptionCreation(chip); @@ -42,6 +43,7 @@ public static ChipDescription CreateChipDescription(DevChipInstance chip) InputPins = inputPins, OutputPins = outputPins, Wires = chip.Wires.Select(CreateWireDescription).ToArray(), + Notes = notes, Displays = displays, ChipType = ChipType.Custom }; @@ -85,9 +87,13 @@ public static uint[] CreateDefaultInstanceData(ChipType type) return type switch { ChipType.Rom_256x16 => new uint[256], // ROM contents + ChipType.EEPROM_256x16 => new uint[257], // EEPROM contents + Rising-Edge detection ChipType.Key => new uint[] { 'K' }, // Key binding ChipType.Pulse => new uint[] { 50, 0, 0 }, // Pulse width, ticks remaining, input state old ChipType.DisplayLED => new uint[] { 0 }, // LED colour + ChipType.Button => new uint[] { 0 }, // Button colour + ChipType.Toggle => new uint[] { 0 }, // Toggle State + ChipType.Constant_8Bit => new uint[] { 0 }, // Content _ => ChipTypeHelper.IsBusType(type) ? new uint[2] : null }; } @@ -161,6 +167,22 @@ public static PinDescription CreatePinDescription(DevPinInstance devPin) => devPin.pinValueDisplayMode ); + public static NoteDescription CreateNoteDescription(NoteInstance note) => + new( + note.ID, + note.Colour, + note.Text, + note.Position + ); + + public static NoteDescription CreateNoteDescriptionForPlacing(int id, NoteColour colour, string text, Vector2 pos) => + new( + id, + colour, + text, + pos + ); + static Color RandomInitialChipColour() { Random rng = new(); diff --git a/Assets/Scripts/SaveSystem/Loader.cs b/Assets/Scripts/SaveSystem/Loader.cs index 590a3af1..1962a0bc 100644 --- a/Assets/Scripts/SaveSystem/Loader.cs +++ b/Assets/Scripts/SaveSystem/Loader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; using DLS.Description; @@ -23,6 +24,7 @@ public static AppSettings LoadAppSettings() public static Project LoadProject(string projectName) { ProjectDescription projectDescription = LoadProjectDescription(projectName); + if (projectDescription.TimeSpentSinceCreated == null) projectDescription.TimeSpentSinceCreated = new(); ChipLibrary chipLibrary = LoadChipLibrary(projectDescription); return new Project(projectDescription, chipLibrary); } @@ -53,6 +55,7 @@ public static ProjectDescription LoadProjectDescription(string projectName) collection.UpdateDisplayStrings(); } + UpgradeHelper.ApplyVersionChangesToProject(ref desc); // Apply changes necessary to ProjectDesc file for modded game. return desc; } @@ -81,14 +84,14 @@ public static ProjectDescription[] LoadAllProjectDescriptions() static ChipLibrary LoadChipLibrary(ProjectDescription projectDescription) { string chipDirectoryPath = SavePaths.GetChipsPath(projectDescription.ProjectName); - ChipDescription[] loadedChips = new ChipDescription[projectDescription.AllCustomChipNames.Length]; + ChipDescription[] loadedChips = new ChipDescription[projectDescription.AllCustomChipNames.Length]; if (!Directory.Exists(chipDirectoryPath) && loadedChips.Length > 0) throw new DirectoryNotFoundException(chipDirectoryPath); - ChipDescription[] builtinChips = BuiltinChipCreator.CreateAllBuiltinChipDescriptions(); + ChipDescription[] builtinChips = BuiltinChipCreator.CreateAllBuiltinChipDescriptions(projectDescription); HashSet customChipNameHashset = new(ChipDescription.NameComparer); - for (int i = 0; i < loadedChips.Length; i++) + for (int i = 0; i < projectDescription.AllCustomChipNames.Length; i++) { string chipPath = Path.Combine(chipDirectoryPath, projectDescription.AllCustomChipNames[i] + ".json"); string chipSaveString = File.ReadAllText(chipPath); @@ -98,13 +101,12 @@ static ChipLibrary LoadChipLibrary(ProjectDescription projectDescription) customChipNameHashset.Add(chipDesc.Name); } - - // If built-in chip name conflicts with a custom chip, the built-in chip must have been added in a newer version. - // In that case, simply exclude the built-in chip. TODO: warn player that they should rename their chip if they want access to new builtin version - builtinChips = builtinChips.Where(b => !customChipNameHashset.Contains(b.Name)).ToArray(); + // If built-in chip name conflicts with a custom chip, the built-in chip must have been added in a newer version. + // In that case, simply exclude the built-in chip. TODO: warn player that they should rename their chip if they want access to new builtin version + builtinChips = builtinChips.Where(b => !customChipNameHashset.Contains(b.Name)).ToArray(); UpgradeHelper.ApplyVersionChanges(loadedChips, builtinChips); - return new ChipLibrary(loadedChips, builtinChips); + return new ChipLibrary(loadedChips, builtinChips); } } } \ No newline at end of file diff --git a/Assets/Scripts/SaveSystem/Saver.cs b/Assets/Scripts/SaveSystem/Saver.cs index feea08af..788f69ca 100644 --- a/Assets/Scripts/SaveSystem/Saver.cs +++ b/Assets/Scripts/SaveSystem/Saver.cs @@ -18,6 +18,7 @@ public static void SaveProjectDescription(ProjectDescription projectDescription) projectDescription.LastSaveTime = DateTime.Now; projectDescription.DLSVersion_LastSaved = Main.DLSVersion.ToString(); projectDescription.DLSVersion_EarliestCompatible = Main.DLSVersion_EarliestCompatible.ToString(); + projectDescription.DLSVersion_LastSavedModdedVersion = Main.DLSVersion_ModdedID.ToString(); string data = Serializer.SerializeProjectDescription(projectDescription); WriteToFile(data, SavePaths.GetProjectDescriptionPath(projectDescription.ProjectName)); diff --git a/Assets/Scripts/SaveSystem/UpgradeHelper.cs b/Assets/Scripts/SaveSystem/UpgradeHelper.cs index e6bf1703..e852a322 100644 --- a/Assets/Scripts/SaveSystem/UpgradeHelper.cs +++ b/Assets/Scripts/SaveSystem/UpgradeHelper.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using DLS.Description; using DLS.Game; using UnityEngine; @@ -6,6 +8,7 @@ namespace DLS.SaveSystem { public static class UpgradeHelper { + public static void ApplyVersionChanges(ChipDescription[] customChips, ChipDescription[] builtinChips) { Main.Version defaultVersion = new(2, 0, 0); @@ -26,8 +29,43 @@ public static void ApplyVersionChanges(ChipDescription[] customChips, ChipDescri } } + public static void ApplyVersionChangesToProject(ref ProjectDescription projectDescription) + { + Main.Version defaultModdedVersion = new(1, 0, 0); + Main.Version moddedVersion_1_1_0 = new(1, 1, 0); // Custom IN and OUTS version + Main.Version moddedVersion_1_1_1 = new(1, 1, 1); // New 16 and 32 bit pins + + + bool canParseModdedVersion = Main.Version.TryParse(projectDescription.DLSVersion_LastSavedModdedVersion, out Main.Version projectVersion); + + bool isVersionEarlierThan_1_1_0 = (!canParseModdedVersion) || projectVersion.ToInt() < moddedVersion_1_1_0.ToInt(); + bool isVersionEarlierThan_1_1_1 = (!canParseModdedVersion) || projectVersion.ToInt() < moddedVersion_1_1_1.ToInt(); + + bool isSplitMergeInvalid = projectDescription.SplitMergePairs == null || projectDescription.SplitMergePairs.Count == 0; + bool isPinBitCountInvalid = projectDescription.pinBitCounts == null || projectDescription.pinBitCounts.Count == 0; + + if (isVersionEarlierThan_1_1_0 | isPinBitCountInvalid) + { + projectDescription.DLSVersion_LastSavedModdedVersion = Main.DLSVersion_ModdedID.ToString(); + projectDescription.pinBitCounts = Project.PinBitCounts; + projectDescription.SplitMergePairs = Project.SplitMergePairs; + } + + if (isVersionEarlierThan_1_1_0 | isSplitMergeInvalid) + { + projectDescription.DLSVersion_LastSavedModdedVersion = Main.DLSVersion_ModdedID.ToString(); + projectDescription.SplitMergePairs = Project.SplitMergePairs; + } + + if (isVersionEarlierThan_1_1_1) + { + projectDescription.DLSVersion_LastSavedModdedVersion = Main.DLSVersion_ModdedID.ToString(); + projectDescription.pinBitCounts.Union(Project.PinBitCounts); + projectDescription.SplitMergePairs.Union(Project.SplitMergePairs); + } + } - static void UpdateChipPre_2_1_5(ChipDescription chipDesc) + static void UpdateChipPre_2_1_5(ChipDescription chipDesc) { string ledName = ChipTypeHelper.GetName(ChipType.DisplayLED); diff --git a/Assets/Scripts/Seb/Helpers/StringHelper.cs b/Assets/Scripts/Seb/Helpers/StringHelper.cs index 979209e3..625e854b 100644 --- a/Assets/Scripts/Seb/Helpers/StringHelper.cs +++ b/Assets/Scripts/Seb/Helpers/StringHelper.cs @@ -36,7 +36,7 @@ public static string CreateBinaryString(uint value, bool removeLeadingZeroes = f public static int CreateIntegerStringNonAlloc(char[] charArray, int value) { bool isNegative = value < 0; - value = Math.Abs(value); + value = Math.Abs(value); int digitCount = value == 0 ? 1 : (int)Math.Log10(value) + 1; int charCount = digitCount; diff --git a/Assets/Scripts/Seb/Helpers/TypeHelper.cs b/Assets/Scripts/Seb/Helpers/TypeHelper.cs new file mode 100644 index 00000000..61ad748b --- /dev/null +++ b/Assets/Scripts/Seb/Helpers/TypeHelper.cs @@ -0,0 +1,21 @@ +using System.Numerics; +using UnityEngine; + +namespace Seb.Helpers +{ + + public static class TypeHelper + { + public static System.Numerics.Vector2 ToNumerics(this UnityEngine.Vector2 value) + { + return new System.Numerics.Vector2(value.x, value.y); + } + + public static System.Numerics.Vector2 ToUnity(this System.Numerics.Vector2 value) + { + return new System.Numerics.Vector2(value.X, value.Y); + } + + } + +} \ No newline at end of file diff --git a/Assets/Scripts/Seb/Helpers/TypeHelper.cs.meta b/Assets/Scripts/Seb/Helpers/TypeHelper.cs.meta new file mode 100644 index 00000000..1bcbd484 --- /dev/null +++ b/Assets/Scripts/Seb/Helpers/TypeHelper.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 50080f7945539d740adc7d8ee6b47324 \ No newline at end of file diff --git a/Assets/Scripts/Seb/SebVis/Draw.cs b/Assets/Scripts/Seb/SebVis/Draw.cs index 905fe682..c7863d33 100644 --- a/Assets/Scripts/Seb/SebVis/Draw.cs +++ b/Assets/Scripts/Seb/SebVis/Draw.cs @@ -3,6 +3,8 @@ using Seb.Vis.Text.FontLoading; using Seb.Vis.Text.Rendering; using UnityEngine; +using System.Collections.Generic; + namespace Seb.Vis { @@ -92,7 +94,21 @@ public static void Diamond(Vector2 pos, Vector2 size, Color col) shapeDrawer.AddToLayer(data); } - public static void SatValQuad(Vector2 centre, Vector2 size, float hue) + public static void Squircle(Vector2 centre, Vector2 size, float cornerRadius, Color col) + { + if (size.x == 0 || size.y == 0 || col.a == 0) return; + if (cornerRadius <= 0) + { + Quad(centre, size, col); + return; + } + + ShapeData data = ShapeData.CreateSquircle(centre, size, cornerRadius, col, activeMaskMin, activeMaskMax); + shapeDrawer.AddToLayer(data); + } + + + public static void SatValQuad(Vector2 centre, Vector2 size, float hue) { if (size.x == 0 || size.y == 0) return; @@ -154,9 +170,39 @@ public static void Line(Vector2 a, Vector2 b, float thickness, Color col, float shapeDrawer.AddToLayer(data); } - // ------ Composite Draw Functions ------ - - public static void LinePath(Vector2[] points, float thickness, Color col, float animT = 1) + public static void WedgePolygon(Vector2 center, float radius, float angleStartDeg, float angleEndDeg, Color color, int PinFace, bool isSource) + { + List points = new List(); + // plot arc + for (int i = 0; i <= 48; i++) + { + float t = (float)i / 48; + float angleDeg = Mathf.Lerp(angleStartDeg, angleEndDeg, t); + float angleRad = angleDeg * Mathf.Deg2Rad; + Vector2 point = center + new Vector2(Mathf.Cos(angleRad), Mathf.Sin(angleRad)) * (radius*1.002f); + points.Add(point); + } + Vector2 dir = PinFace switch + { + 0 => Vector2.up, + 1 => Vector2.right, + 2 => Vector2.down, + 3 => Vector2.left, + _ => Vector2.left + }; + //shortens radius based on pin type to move point away from chip edge + float radshift = isSource ? 0.80f : 0.25f; + //adds the shifted "center" point + points.Add(center + dir * (radius * radshift)); + + for (int i = 0; i < points.Count - 2; i++) + { + Triangle(points[i], points[i + 1], points[points.Count - 1], color); + } + } + // ------ Composite Draw Functions ------ + + public static void LinePath(Vector2[] points, float thickness, Color col, float animT = 1) { if (col.a == 0 || thickness == 0 || animT <= 0) return; diff --git a/Assets/Scripts/Seb/SebVis/Internal/Resources/Draw.shader b/Assets/Scripts/Seb/SebVis/Internal/Resources/Draw.shader index bd23ff92..16d536b6 100644 --- a/Assets/Scripts/Seb/SebVis/Internal/Resources/Draw.shader +++ b/Assets/Scripts/Seb/SebVis/Internal/Resources/Draw.shader @@ -12,7 +12,7 @@ Shader "Vis/Draw" Blend SrcAlpha OneMinusSrcAlpha Pass - { + { CGPROGRAM #pragma vertex vert #pragma fragment frag @@ -64,6 +64,7 @@ Shader "Vis/Draw" static const int HUE_TYPE = 5; static const int DIAMOND_TYPE = 6; static const int POINT_OUTLINE_TYPE = 7; + static const int SQUIRCLE_TYPE = 8; static const bool AA_Enabled = true; @@ -145,7 +146,7 @@ Shader "Vis/Draw" } } // Quad (2 = regular, 4 = saturation/value display, 5 = hue display) - else if (instance.type == QUAD_TYPE || instance.type == SATVAL_TYPE || instance.type == HUE_TYPE) + else if (instance.type == QUAD_TYPE || instance.type == SATVAL_TYPE || instance.type == HUE_TYPE || instance.type == SQUIRCLE_TYPE) { float3 worldCentre = float3(instance.a * LayerScale + LayerOffset, 0); float4 size = float4(instance.b.xy * LayerScale, 1, 1); @@ -154,7 +155,9 @@ Shader "Vis/Draw" o.posLocal = vertexLocal; o.sizeData = instance.b.xy; - if (instance.type == 4) o.col = instance.param; // Override col to store hue value + if (instance.type == SQUIRCLE_TYPE) o.lineEndPoints.x = instance.param; // Store corner radius in lineEndPoints if SQUIRCLE (to not make an even bigger v2f) + else if (instance.type == 4) o.col = instance.param; // Override col to store hue value + } // Triangle else if (instance.type == TRIANGLE_TYPE) @@ -215,6 +218,12 @@ Shader "Vis/Draw" return length(p - pointOnLineSeg); } + float squircleSdf(float2 position, float2 halfSize, float cornerRadius) + { + position = abs(position) - halfSize + cornerRadius; + return length(max(position, 0.0)) + min(max(position.x, position.y), 0.0) - cornerRadius; + } + // Alternate AA formulation which seems to work a bit better for very thin lines? float4 lineDraw_aa2(v2f i) { @@ -313,6 +322,19 @@ Shader "Vis/Draw" return i.col; } + float4 squircleDraw(v2f i) + { + float sdf = squircleSdf(i.posLocal, i.sizeData / 2, i.lineEndPoints.x); // LineEndPoints.x has been overwritten with the corner radius + float alpha = CalculateAlphaFromSDF(sdf, i); + float3 col = i.col.rgb; + + + return float4(col, alpha * i.col.a); + + } + + + float4 frag(v2f i) : SV_Target { // Mask @@ -326,6 +348,8 @@ Shader "Vis/Draw" if (i.shapeType == SATVAL_TYPE) return satValQuadDraw(i); if (i.shapeType == HUE_TYPE) return hueQuadDraw(i); if (i.shapeType == DIAMOND_TYPE) return diamondDraw(i); + if (i.shapeType == SQUIRCLE_TYPE) return squircleDraw(i); + return float4(0, 0, 0, 1); } ENDCG diff --git a/Assets/Scripts/Seb/SebVis/Internal/ShapeTypes/ShapeData.cs b/Assets/Scripts/Seb/SebVis/Internal/ShapeTypes/ShapeData.cs index 2dc01fbf..909eac6c 100644 --- a/Assets/Scripts/Seb/SebVis/Internal/ShapeTypes/ShapeData.cs +++ b/Assets/Scripts/Seb/SebVis/Internal/ShapeTypes/ShapeData.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json.Linq; using UnityEngine; namespace Seb.Vis.Internal @@ -12,7 +13,8 @@ public enum ShapeType SatVal = 4, HueQuad = 5, Diamond = 6, - PointOutline = 7 + PointOutline = 7, + Squircle = 8 } public struct ShapeData @@ -64,7 +66,9 @@ public static ShapeData CreatePointOutline(Vector2 centre, float radius, float t public static ShapeData CreateSatVal(Vector2 centre, Vector2 size, float value, Vector2 maskMin, Vector2 maskMax) => new(ShapeType.SatVal, centre, size, value, Color.clear, maskMin, maskMax); - public static ShapeData CreateHueQuad(Vector2 centre, Vector2 size, Vector2 maskMin, Vector2 maskMax) => new(ShapeType.HueQuad, centre, size, 0, Color.clear, maskMin, maskMax); + public static ShapeData CreateSquircle(Vector2 centre, Vector2 size, float cornerRadius, Color col, Vector2 maskMin, Vector2 maskMax) => new(ShapeType.Squircle, centre, size, cornerRadius, col, maskMin, maskMax); + + public static ShapeData CreateHueQuad(Vector2 centre, Vector2 size, Vector2 maskMin, Vector2 maskMax) => new(ShapeType.HueQuad, centre, size, 0, Color.clear, maskMin, maskMax); public static ShapeData CreateDiamond(Vector2 centre, Vector2 size, Color col, Vector2 maskMin, Vector2 maskMax) => new(ShapeType.Diamond, centre, size, 0, col, maskMin, maskMax); diff --git a/Assets/Scripts/Seb/SebVis/UI/UI.cs b/Assets/Scripts/Seb/SebVis/UI/UI.cs index ca18b3f2..b5197056 100644 --- a/Assets/Scripts/Seb/SebVis/UI/UI.cs +++ b/Assets/Scripts/Seb/SebVis/UI/UI.cs @@ -26,6 +26,7 @@ public static class UI // -- State lookups -- static readonly Dictionary inputFieldStates = new(); + static readonly Dictionary textAreaStates = new(); static readonly Dictionary buttonStates = new(); static readonly Dictionary colPickerStates = new(); static readonly Dictionary scrollbarStates = new(); @@ -536,6 +537,347 @@ int CharIndexBeforeMouse(float textLeft) } } + public static TextAreaState TextArea(UIHandle id, InputFieldTheme theme, Vector2 pos, Vector2 size, string defaultText, Anchor anchor, float textPad, string lineLength, int maxLines, Func validation = null, bool forceFocus = false) + { + TextAreaState state = GetTextAreaState(id); + state.maxCharsPerLine = lineLength.Length; + state.maxLines = maxLines; + + Vector2 centre = CalculateCentre(pos, size, anchor); + (Vector2 centre, Vector2 size) ss = UIToScreenSpace(centre, size); + + if (IsRendering) + { + Vector2 textTopLeft_ss = ss.centre - new Vector2(ss.size.x / 2 - textPad * scale, -ss.size.y / 2 + textPad * 2 * scale); + Draw.Quad(ss.centre, ss.size, theme.bgCol); + + // Focus input + bool mouseInBounds = InputHelper.MouseInBounds_ScreenSpace(ss.centre, ss.size); + + if (InputHelper.IsMouseDownThisFrame(MouseButton.Left)) + { + state.SetFocus(mouseInBounds); + state.isMouseDownInBounds = mouseInBounds; + + // Set caret pos based on mouse position + if (mouseInBounds) + { + Vector2Int newCaretPos = CharIndexBeforeMouse(textTopLeft_ss.x, textTopLeft_ss.y); + state.SetCursorIndex(newCaretPos.x, newCaretPos.y, InputHelper.ShiftIsHeld); + } + } + + // Hold-drag left mouse to select + if (state.focused && InputHelper.IsMouseHeld(MouseButton.Left) && state.isMouseDownInBounds) + { + Vector2Int newCaretPos = CharIndexBeforeMouse(textTopLeft_ss.x, textTopLeft_ss.y); + state.SetCursorIndex(newCaretPos.x, newCaretPos.y, true); + } + + if (forceFocus && !state.focused) + { + state.SetFocus(true); + } + + // Draw focus outline and update text + if (state.focused) + { + // state.EnforceTextLimit(lineLength.Length, maxLines); + const float outlineWidth = 0.05f; + Draw.QuadOutline(ss.centre, ss.size, outlineWidth * scale, theme.focusBorderCol); + foreach (char c in InputHelper.InputStringThisFrame) + { + // if (maxLines <= state.CountLines()) continue; + bool invalidChar = char.IsControl(c) || char.IsSurrogate(c) || char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.Format || char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.PrivateUse; + if (invalidChar) continue; + state.TryInsertText(c + "", validation); + state.UpdateLastInputTime(); // Reset caret blink timer + + } + + // Paste from clipboard + if (InputHelper.CtrlIsHeld && InputHelper.IsKeyDownThisFrame(KeyCode.V)) + { + string sanitizedText = InputHelper.GetClipboardContents().Replace("\n", ""); + state.TryInsertText(sanitizedText, validation); + } + + if (state.lines.Count > 0) + { + // Backspace / delete + if (CanTrigger(ref state.backspaceTrigger, KeyCode.Backspace)) + { + int charDeleteCount = InputHelper.CtrlIsHeld ? state.lines[state.cursorLineIndex].Length : 1; // delete all if ctrl is held + for (int i = 0; i < charDeleteCount; i++) + { + state.Delete(true, validation); + } + } + else if (CanTrigger(ref state.deleteTrigger, KeyCode.Delete)) + { + state.Delete(false, validation); + } + if (InputHelper.IsKeyDownThisFrame(KeyCode.Return) && state.lines.Count < maxLines) + { + state.NewLine(); + } + + // Arrow keys + bool select = InputHelper.ShiftIsHeld; + bool leftArrow = CanTrigger(ref state.arrowKeyTrigger, KeyCode.LeftArrow); + bool rightArrow = CanTrigger(ref state.arrowKeyTrigger, KeyCode.RightArrow); + bool upArrow = CanTrigger(ref state.arrowKeyTrigger, KeyCode.UpArrow); + bool downArrow = CanTrigger(ref state.arrowKeyTrigger, KeyCode.DownArrow); + bool jumpToPrevWordStart = InputHelper.CtrlIsHeld && leftArrow; + bool jumpToNextWordEnd = InputHelper.CtrlIsHeld && rightArrow; + bool jumpToStart = (state.isSelecting && leftArrow) || InputHelper.IsKeyDownThisFrame(KeyCode.UpArrow) || InputHelper.IsKeyDownThisFrame(KeyCode.PageUp) || InputHelper.IsKeyDownThisFrame(KeyCode.Home) || (jumpToPrevWordStart && InputHelper.AltIsHeld); + bool jumpToEnd = (state.isSelecting && rightArrow) || InputHelper.IsKeyDownThisFrame(KeyCode.DownArrow) || InputHelper.IsKeyDownThisFrame(KeyCode.PageDown) || InputHelper.IsKeyDownThisFrame(KeyCode.End) || (jumpToNextWordEnd && InputHelper.AltIsHeld); + + if (jumpToStart) state.SetCursorIndex(0, 0, select); + else if (jumpToEnd) state.SetCursorIndex(state.lines[state.lines.Count - 1].Length, state.lines.Count, select); + else if (jumpToNextWordEnd) state.SetCursorIndex(state.NextWordEndIndex(), state.cursorLineIndex, select); + else if (jumpToPrevWordStart) state.SetCursorIndex(state.PrevWordIndex(), state.cursorLineIndex, select); + else if (leftArrow) state.DecrementCursor(select); + else if (rightArrow) state.IncrementCursor(select); + else if (upArrow) state.DecrementLine(select); + else if (downArrow) state.IncrementLine(select); + + bool copyTriggered = InputHelper.CtrlIsHeld && InputHelper.IsKeyDownThisFrame(KeyCode.C); + bool cutTriggered = InputHelper.CtrlIsHeld && InputHelper.IsKeyDownThisFrame(KeyCode.X); + + // Copy selected text (or all text if nothing selected) + if (copyTriggered || cutTriggered) + { + string copyText; + + if (state.isSelecting) + { + int startLine = Mathf.Min(state.cursorLineIndex, state.selectionStartIndex / state.maxCharsPerLine); + int endLine = Mathf.Max(state.cursorLineIndex, state.selectionStartIndex / state.maxCharsPerLine); + + int startChar = Mathf.Min(state.cursorBeforeCharIndex, state.selectionStartIndex % state.maxCharsPerLine); + int endChar = Mathf.Max(state.cursorBeforeCharIndex, state.selectionStartIndex % state.maxCharsPerLine); + + if (startLine == endLine) + { + // Single-line selection + copyText = state.lines[startLine].Substring(startChar, endChar - startChar); + } + else + { + // Multi-line selection + StringBuilder sb = new(); + sb.AppendLine(state.lines[startLine].Substring(startChar)); + for (int i = startLine + 1; i < endLine; i++) + { + sb.AppendLine(state.lines[i]); + } + sb.Append(state.lines[endLine].Substring(0, endChar)); + copyText = sb.ToString(); + } + } + else + { + // No selection, copy the entire text + copyText = string.Join("\n", state.lines); + } + + InputHelper.CopyToClipboard(copyText); + + if (cutTriggered) + { + if (state.isSelecting) state.Delete(true, validation); + else state.ClearText(); + + } + } + + // Select all + if (InputHelper.CtrlIsHeld && InputHelper.IsKeyDownThisFrame(KeyCode.A)) state.SelectAll(); + } + } + + // Draw text + using (CreateMaskScope(centre, size)) + { + float fontSize_ss = theme.fontSize * scale; + bool showDefaultText = (state.lines.Count == 0 || (state.lines.Count == 1 && string.IsNullOrEmpty(state.lines[0]))) || !Application.isPlaying; + string[] lines = showDefaultText ? new[] { "Enter something..." } : state.lines.ToArray(); + Debug.Log($"{String.Join(", ", lines).Replace("\n", "\\n")}"); + Color textCol = showDefaultText ? theme.defaultTextCol : theme.textCol; + for (int i = 0; i < lines.Length; i++) + { + Vector2 textPos_ss = textTopLeft_ss + new Vector2(0, -i * theme.fontSize * 1.2f * scale); + Draw.Text(theme.font, lines[i], fontSize_ss, textPos_ss, Anchor.TextCentreLeft, textCol); + } + + if (Application.isPlaying) + { + Vector2 boundsSizeUpToCaret = Vector2.zero; + for (int i = 0; i <= state.cursorLineIndex; i++) + { + if (state.lines.Count == 0) + { + state.lines.Add(string.Empty); // Ensure there is at least one line + } + + i = Mathf.Clamp(i, 0, state.lines.Count - 1); // Clamp lineIndex to valid range + string line = state.lines[i]; // Safely access the line + if (i < state.cursorLineIndex) + { + // Add the size of the entire line for lines before the caret's line + boundsSizeUpToCaret.y -= theme.fontSize * 1.2f * scale; // Move down by line height + } + else + { + // Calculate the size up to the caret's position in the current line + boundsSizeUpToCaret.x = Draw.CalculateTextBoundsSize(line.AsSpan(0, state.cursorBeforeCharIndex), theme.fontSize, theme.font).x; + } + } + + if (state.isSelecting) + { + // Calculate global start and end indices dynamically + int startGlobalIndex = 0; + int endGlobalIndex = 0; + + // Calculate the global index for the cursor and selection start + for (int i = 0; i < state.lines.Count; i++) + { + if (i < state.cursorLineIndex) + { + startGlobalIndex += state.lines[i].Length; + } + if (i < state.selectionStartIndex / state.maxCharsPerLine) + { + endGlobalIndex += state.lines[i].Length; + } + } + + startGlobalIndex += state.cursorBeforeCharIndex; + endGlobalIndex += state.selectionStartIndex % state.maxCharsPerLine; + + // Ensure startGlobalIndex is less than or equal to endGlobalIndex + if (startGlobalIndex > endGlobalIndex) + { + (startGlobalIndex, endGlobalIndex) = (endGlobalIndex, startGlobalIndex); + } + + int currentGlobalIndex = 0; + + for (int lineIndex = 0; lineIndex < state.lines.Count; lineIndex++) + { + string line = state.lines[lineIndex]; + int lineStartIndex = currentGlobalIndex; + int lineEndIndex = lineStartIndex + line.Length; + + // Determine the selection range for this line using global indices + int selectionStartInLine = Mathf.Clamp(startGlobalIndex - lineStartIndex, 0, line.Length); + int selectionEndInLine = Mathf.Clamp(endGlobalIndex - lineStartIndex, 0, line.Length); + + // Draw the selection box for the current line + Vector2 boundsSizeUpToStart = Draw.CalculateTextBoundsSize(line.AsSpan(0, selectionStartInLine), theme.fontSize, theme.font); + Vector2 boundsSizeUpToEnd = Draw.CalculateTextBoundsSize(line.AsSpan(0, selectionEndInLine), theme.fontSize, theme.font); + + float startX = textTopLeft_ss.x + boundsSizeUpToStart.x * scale; + float endX = textTopLeft_ss.x + boundsSizeUpToEnd.x * scale; + + if (startX > endX) + { + (startX, endX) = (endX, startX); + } + + Vector2 c = new((endX + startX) / 2, textTopLeft_ss.y - lineIndex * theme.fontSize * 1.2f * scale); + Vector2 s = new(endX - startX, theme.fontSize * 1.2f * scale); + + Draw.Quad(c, s, new Color(0.2f, 0.6f, 1, 0.5f)); + + currentGlobalIndex += line.Length; + } + } + + // Draw caret + const float blinkDuration = 0.5f; + if (state.focused && (int)((Time.time - state.lastInputTime) / blinkDuration) % 2 == 0) + { + Vector2 caretTextBoundsTest = Draw.CalculateTextBoundsSize("Mj", theme.fontSize, theme.font); + Vector2 caretOffset = GetCaretOffset(theme.fontSize, theme.font); + Vector2 caretPos_ss = textTopLeft_ss + caretOffset * scale; + Vector2 caretSize = new(0.125f * theme.fontSize, caretTextBoundsTest.y * 1.2f); + Draw.Quad(caretPos_ss, caretSize * scale, theme.textCol); + } + } + } + } + + OnFinishedDrawingUIElement(centre, size); + return state; + + static bool CanTrigger(ref TextAreaState.TriggerState triggerState, KeyCode key) + { + if (InputHelper.IsKeyDownThisFrame(key)) triggerState.lastManualTime = Time.time; + + if (InputHelper.IsKeyDownThisFrame(key) || (InputHelper.IsKeyHeld(key) && CanAutoTrigger(triggerState))) + { + triggerState.lastAutoTiggerTime = Time.time; + return true; + } + + return false; + } + + static bool CanAutoTrigger(TextAreaState.TriggerState triggerState) + { + const float autoTriggerStartDelay = 0.5f; + const float autoTriggerRepeatDelay = 0.04f; + bool initialDelayOver = Time.time - triggerState.lastManualTime > autoTriggerStartDelay; + bool canRepeat = Time.time - triggerState.lastAutoTiggerTime > autoTriggerRepeatDelay; + return initialDelayOver && canRepeat; + } + + Vector2Int CharIndexBeforeMouse(float textLeft, float textTop) + { + Vector2 mousePos = InputHelper.MousePos; + + // Calculate the line index based on the vertical mouse position + float lineHeight = theme.fontSize * 1.2f * scale; // Line height with scaling + float adjustedTextTop = textTop + lineHeight / 3; // Adjust for the center of the first line + int lineIndex = Mathf.FloorToInt((adjustedTextTop - mousePos.y) / lineHeight); + lineIndex = Mathf.Clamp(lineIndex, 0, state.lines.Count - 1); // Clamp to valid line range + + // Get the line of text at the calculated line index + string line = state.lines[lineIndex]; + float lineWidth = Draw.CalculateTextBoundsSize(line.AsSpan(), theme.fontSize, theme.font).x; + float lineLeft = textLeft; + float lineRight = lineLeft + lineWidth * scale; + + // Calculate the character index within the line based on the clamped mouse position + float t = Mathf.InverseLerp(lineLeft, lineRight, mousePos.x); + int charIndexInLine = Mathf.RoundToInt(t * line.Length); + charIndexInLine = Mathf.Clamp(charIndexInLine, 0, line.Length); + + return new Vector2Int(charIndexInLine, lineIndex); + } + + Vector2 GetCaretOffset(float fontSize, FontType font) + { + // Ensure cursor index is within bounds for the current line + int cursorIndex = Mathf.Clamp(state.cursorBeforeCharIndex, 0, state.lines[state.cursorLineIndex].Length); + + // Get the current line of text + string line = state.lines[state.cursorLineIndex]; + + // Calculate the width of the text up to the caret position + ReadOnlySpan span = line.AsSpan(0, cursorIndex); + float x = Draw.CalculateTextBoundsSize(span, fontSize, font).x; + + // Calculate the vertical offset based on the line index + float y = state.cursorLineIndex * 1.2f * fontSize; + + return new Vector2(x, -y); + } + } + // Reserve spot in the drawing order for a panel. Returns an ID which can be used // to modify its properties (position, size, colour etc) at a later point. public static Draw.ID ReservePanel() => Draw.ReserveQuad(); @@ -1118,6 +1460,7 @@ static ReadOnlySpan GetNextLine(ref ReadOnlySpan text, int maxLineLe public static ColourPickerState GetColourPickerState(UIHandle id) => GetOrCreateState(id, colPickerStates); public static InputFieldState GetInputFieldState(UIHandle id) => GetOrCreateState(id, inputFieldStates); + public static TextAreaState GetTextAreaState(UIHandle id) => GetOrCreateState(id, textAreaStates); public static ButtonState GetButtonState(UIHandle id) => GetOrCreateState(id, buttonStates); @@ -1128,6 +1471,7 @@ static ReadOnlySpan GetNextLine(ref ReadOnlySpan text, int maxLineLe public static void ResetAllStates() { inputFieldStates.Clear(); + textAreaStates.Clear(); colPickerStates.Clear(); buttonStates.Clear(); scrollbarStates.Clear(); diff --git a/Assets/Scripts/Seb/SebVis/UI/UIStates.cs b/Assets/Scripts/Seb/SebVis/UI/UIStates.cs index a34ae211..28d4244f 100644 --- a/Assets/Scripts/Seb/SebVis/UI/UIStates.cs +++ b/Assets/Scripts/Seb/SebVis/UI/UIStates.cs @@ -1,6 +1,8 @@ using System; using Seb.Helpers; using UnityEngine; +using System.Linq; +using System.Collections.Generic; namespace Seb.Vis.UI { @@ -245,4 +247,453 @@ public struct TriggerState public float lastAutoTiggerTime; } } + + public class TextAreaState + { + public TriggerState arrowKeyTrigger; + public TriggerState backspaceTrigger; + public int cursorBeforeCharIndex; + public int cursorLineIndex; + public TriggerState deleteTrigger; + public bool isMouseDownInBounds; + public bool isSelecting; + public float lastInputTime; + public int selectionStartIndex; + public List lines { get; private set; } = new(); + public bool focused { get; private set; } + public int maxCharsPerLine = 0; + public int maxLines = 0; + + public int SelectionMinIndex => Mathf.Min(selectionStartIndex, cursorBeforeCharIndex); + public int SelectionMaxIndex => Mathf.Max(selectionStartIndex, cursorBeforeCharIndex); + + public void ClearText() + { + lines.Clear(); + lines.Add(string.Empty); + cursorBeforeCharIndex = 0; + cursorLineIndex = 0; + isSelecting = false; + } + + public void SetFocus(bool newFocusState) + { + if (newFocusState != focused) + { + focused = newFocusState; + lastInputTime = Time.time; + + if (!newFocusState) + { + // Reset selection and caret position when focus is lost + isSelecting = false; + // selectionStartIndex = cursorBeforeCharIndex; // Reset selection start + } + } + } + + public void Delete(bool deleteLeft, Func validation = null) + { + if (lines.Count == 0) return; + + string currentLine = lines[cursorLineIndex]; + + if (isSelecting) + { + isSelecting = false; + DeleteSelection(validation); + } + else + { + if (deleteLeft && cursorBeforeCharIndex > 0) + { + // Delete a character to the left of the cursor + string newLine = currentLine.Remove(cursorBeforeCharIndex - 1, 1); + if (validation == null || validation(newLine)) + { + lines[cursorLineIndex] = newLine; + DecrementCursor(); + } + } + else if (!deleteLeft && cursorBeforeCharIndex < currentLine.Length) + { + // Delete a character to the right of the cursor + string newLine = currentLine.Remove(cursorBeforeCharIndex, 1); + if (validation == null || validation(newLine)) + { + lines[cursorLineIndex] = newLine; + } + } + else if (deleteLeft && cursorBeforeCharIndex == 0 && cursorLineIndex > 0) + { + // Merge with the previous line + string previousLine = lines[cursorLineIndex - 1]; + + // Remove the trailing \n from the previous line if it exists + if (previousLine.EndsWith("\n")) + { + previousLine = previousLine.Substring(0, previousLine.Length - 1); + } + + lines[cursorLineIndex - 1] = previousLine + currentLine; + lines.RemoveAt(cursorLineIndex); + cursorLineIndex--; + cursorBeforeCharIndex = previousLine.Length; + } + else if (!deleteLeft && cursorBeforeCharIndex == currentLine.Length && cursorLineIndex < lines.Count - 1) + { + // Merge with the next line + string nextLine = lines[cursorLineIndex + 1]; + lines[cursorLineIndex] += nextLine; + lines.RemoveAt(cursorLineIndex + 1); + } + } + + // Ensure the current line is valid and handle edge cases + if (lines.Count == 0) + { + lines.Add(string.Empty); + cursorLineIndex = 0; + cursorBeforeCharIndex = 0; + } + else if (cursorLineIndex >= lines.Count) + { + cursorLineIndex = lines.Count - 1; + cursorBeforeCharIndex = lines[cursorLineIndex].Length; + } + else if (cursorBeforeCharIndex > lines[cursorLineIndex].Length) + { + cursorBeforeCharIndex = lines[cursorLineIndex].Length; + } + + UpdateLastInputTime(); + } + + private void DeleteSelection(Func validation = null) + { + int startLine = Mathf.Min(cursorLineIndex, selectionStartIndex / maxCharsPerLine); + int endLine = Mathf.Max(cursorLineIndex, selectionStartIndex / maxCharsPerLine); + + int startChar = Mathf.Min(cursorBeforeCharIndex, selectionStartIndex % maxCharsPerLine); + int endChar = Mathf.Max(cursorBeforeCharIndex, selectionStartIndex % maxCharsPerLine); + + // Clamp startChar and endChar to the valid range of the string + startChar = Mathf.Clamp(startChar, 0, lines[startLine].Length); + endChar = Mathf.Clamp(endChar, 0, lines[endLine].Length); + + if (startLine == endLine) + { + string line = lines[startLine]; + string newLine = line.Remove(startChar, endChar - startChar); + if (validation == null || validation(newLine)) + { + lines[startLine] = newLine; + cursorBeforeCharIndex = startChar; + } + } + else + { + string startLineText = lines[startLine].Substring(0, startChar); + string endLineText = lines[endLine].Substring(endChar); + lines[startLine] = startLineText + endLineText; + + for (int i = endLine; i > startLine; i--) + { + lines.RemoveAt(i); + } + + cursorLineIndex = startLine; + cursorBeforeCharIndex = startChar; + } + } + + public void NewLine() + { + // Clear selection before creating a new line + if (isSelecting) + { + DeleteSelection(); + } + + string currentLine = lines[cursorLineIndex]; + + // Check if the current line already ends with a newline character + bool endsWithNewline = currentLine.Contains("\n"); + + // Text after the caret + string newLine = currentLine.Substring(cursorBeforeCharIndex); + + // Text before the caret + if (endsWithNewline) + { + lines[cursorLineIndex] = currentLine.Substring(0, cursorBeforeCharIndex); // Keep the existing \n + newLine = newLine + "\n"; // Remove the \n from the new line + } + else + { + lines[cursorLineIndex] = currentLine.Substring(0, cursorBeforeCharIndex) + "\n"; // Add a new \n + } + + // Insert the new line into the list + lines.Insert(cursorLineIndex + 1, newLine); + + // Move the caret to the start of the new line + cursorLineIndex++; + cursorBeforeCharIndex = 0; + + UpdateLastInputTime(); + } + + public void SelectAll() + { + cursorLineIndex = 0; + cursorBeforeCharIndex = 0; + selectionStartIndex = 0; + cursorLineIndex = lines.Count - 1; + cursorBeforeCharIndex = lines[cursorLineIndex].Length; + isSelecting = true; + } + + public void SetCursorIndex(int charIndex, int lineIndex, bool select = false) + { + if (select && !isSelecting) + { + isSelecting = true; + selectionStartIndex = cursorBeforeCharIndex + cursorLineIndex * maxCharsPerLine; + } + + cursorLineIndex = Mathf.Clamp(lineIndex, 0, lines.Count - 1); + cursorBeforeCharIndex = Mathf.Clamp(charIndex, 0, lines[cursorLineIndex].Length); + + UpdateLastInputTime(); + + int globalIndex = maxCharsPerLine * cursorLineIndex + cursorBeforeCharIndex; + + if (globalIndex == selectionStartIndex || !select) + { + isSelecting = false; + } + } + + public void UpdateLastInputTime() + { + lastInputTime = Time.time; + } + + public void SetText(string text, bool focus = true) + { + // Clear the current lines + lines.Clear(); + + // Split the input text into lines based on newline characters + string[] splitLines = text.Split('\n'); + + // Reset the cursor position + cursorLineIndex = 0; + cursorBeforeCharIndex = 0; + + lines.Add(string.Empty); // Add an empty line to start + + // Add each line to the lines list + foreach (string line in splitLines) + { + TryInsertText(line); + // NewLine(); + } + + // Remove the extra new line added after the last line + if (lines.Count > 0 && string.IsNullOrEmpty(lines[^1])) + { + lines.RemoveAt(lines.Count - 1); + } + + // Set focus if required + SetFocus(focus); + } + + public void TryInsertText(string textToAdd, Func validation = null) + { + if (isSelecting) + { + Delete(true); + } + + string currentLine = lines[cursorLineIndex]; + + if (currentLine.Contains("\n")) + { + // Find the index of the newline character + int newlineIndex = currentLine.IndexOf('\n'); + + // If the cursor is positioned after the newline, move it before the newline + if (cursorBeforeCharIndex > newlineIndex) + { + cursorBeforeCharIndex = newlineIndex; + } + } + + // Prevent adding text if maxLines is reached and the cursor is on the last line + if (lines.Count == maxLines && cursorLineIndex == lines.Count - 1) + { + // Limit the number of characters in the last line + if (currentLine.Length >= maxCharsPerLine) + { + return; // Do nothing if the last line is already full + } + + // Truncate the text to fit within the maxCharsPerLine limit + int remainingChars = maxCharsPerLine - currentLine.Length; + textToAdd = textToAdd.Substring(0, Mathf.Min(textToAdd.Length, remainingChars)); + } + + string newLine = currentLine.Insert(cursorBeforeCharIndex, textToAdd); + + if (validation == null || validation(newLine)) + { + lines[cursorLineIndex] = newLine; + cursorBeforeCharIndex += textToAdd.Length; + + // Handle wrapping if maxCharsPerLine is set + if (maxCharsPerLine > 0) + { + while (lines[cursorLineIndex].Length > maxCharsPerLine) + { + string currentText = lines[cursorLineIndex]; + int wrapIndex = FindWrapIndex(currentText); + + // Split the line at the wrap index + string overflowText = currentText.Substring(wrapIndex); + lines[cursorLineIndex] = currentText.Substring(0, wrapIndex); + + // Add overflow text to the next line + if (cursorLineIndex + 1 < lines.Count) + { + lines[cursorLineIndex + 1] = overflowText + lines[cursorLineIndex + 1]; + } + else if (lines.Count < maxLines) + { + lines.Add(overflowText); + } + else + { + // Truncate overflow text if maxLines is reached + lines[cursorLineIndex] += overflowText.Substring(0, Mathf.Min(overflowText.Length, maxCharsPerLine - lines[cursorLineIndex].Length)); + break; + } + + // Adjust cursor position + cursorLineIndex++; + cursorBeforeCharIndex = overflowText.Length; + + // Stop wrapping if maxLines is reached + if (lines.Count >= maxLines) + { + break; + } + } + } + } + } + + private int FindWrapIndex(string text) + { + if (text.Length <= maxCharsPerLine) return text.Length; + + // Look for the last whitespace within the maxCharsPerLine limit + for (int i = maxCharsPerLine; i > 0; i--) + { + if (char.IsWhiteSpace(text[i])) + { + return i + 1; // Include the space in the wrap + } + } + + // If no whitespace is found, split at maxCharsPerLine + return maxCharsPerLine; + } + + public void IncrementCursor(bool select = false) + { + if (cursorBeforeCharIndex < lines[cursorLineIndex].Length) + { + SetCursorIndex(cursorBeforeCharIndex + 1, cursorLineIndex, select); + } + else if (cursorLineIndex < lines.Count - 1) + { + SetCursorIndex(0, cursorLineIndex + 1, select); + } + } + + public void DecrementCursor(bool select = false) + { + if (cursorBeforeCharIndex > 0) + { + SetCursorIndex(cursorBeforeCharIndex - 1, cursorLineIndex, select); + } + else if (cursorLineIndex > 0) + { + SetCursorIndex(lines[cursorLineIndex - 1].Length, cursorLineIndex - 1, select); + } + } + + public void IncrementLine(bool select = false) + { + if (cursorLineIndex == lines.Count) + { + SetCursorIndex(lines[cursorLineIndex].Length, cursorLineIndex, select); + } + else if (cursorLineIndex < lines.Count - 1) + { + if (lines[cursorLineIndex + 1].Length < cursorBeforeCharIndex) + { + cursorBeforeCharIndex = lines[cursorLineIndex + 1].Length; + } + SetCursorIndex(cursorBeforeCharIndex, cursorLineIndex + 1, select); + } + } + + public void DecrementLine(bool select = false) + { + if (cursorLineIndex == 0) + { + SetCursorIndex(0, cursorLineIndex, select); + } + else if (cursorLineIndex > 0) + { + if (lines[cursorLineIndex - 1].Length < cursorBeforeCharIndex) + { + cursorBeforeCharIndex = lines[cursorLineIndex - 1].Length; + } + SetCursorIndex(cursorBeforeCharIndex, cursorLineIndex - 1, select); + } + } + + public int NextWordEndIndex() + { + string currentLine = lines[cursorLineIndex]; + for (int i = cursorBeforeCharIndex; i < currentLine.Length; i++) + { + if (char.IsWhiteSpace(currentLine[i])) return i; + } + + return currentLine.Length; + } + + public int PrevWordIndex() + { + string currentLine = lines[cursorLineIndex]; + for (int i = cursorBeforeCharIndex - 1; i >= 0; i--) + { + if (char.IsWhiteSpace(currentLine[i])) return i + 1; + } + + return 0; + } + + public struct TriggerState + { + public float lastManualTime; + public float lastAutoTiggerTime; + } + } } \ No newline at end of file diff --git a/Assets/Scripts/Seb/SebVis/_Dev/Mess/MyVisTest.cs b/Assets/Scripts/Seb/SebVis/_Dev/Mess/MyVisTest.cs index ca07d5d7..5761a7d2 100644 --- a/Assets/Scripts/Seb/SebVis/_Dev/Mess/MyVisTest.cs +++ b/Assets/Scripts/Seb/SebVis/_Dev/Mess/MyVisTest.cs @@ -169,8 +169,13 @@ void DrawSceneObjects() Vector2 c = tri.transform.position; Draw.Triangle(c + tri.offsetA, c + tri.offsetB, c + tri.offsetC, tri.col); } - } - // Seb.Vis.Draw.EndTransformState(); + else if (t.TryGetComponent(out SquircleVisTest s)) + { + Draw.Squircle(s.transform.position, s.size, s.radius, s.col); + } + + // Seb.Vis.Draw.EndTransformState(); + } } } \ No newline at end of file diff --git a/Assets/Scripts/Seb/SebVis/_Dev/Mess/TestTypes/SquircleVisTest.cs b/Assets/Scripts/Seb/SebVis/_Dev/Mess/TestTypes/SquircleVisTest.cs new file mode 100644 index 00000000..71423b95 --- /dev/null +++ b/Assets/Scripts/Seb/SebVis/_Dev/Mess/TestTypes/SquircleVisTest.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +public class SquircleVisTest : MonoBehaviour +{ + public Vector2 size; + public float radius; + public Color col = Color.white; +} \ No newline at end of file diff --git a/Assets/Scripts/Seb/SebVis/_Dev/Mess/TestTypes/SquircleVisTest.cs.meta b/Assets/Scripts/Seb/SebVis/_Dev/Mess/TestTypes/SquircleVisTest.cs.meta new file mode 100644 index 00000000..8c8a01b7 --- /dev/null +++ b/Assets/Scripts/Seb/SebVis/_Dev/Mess/TestTypes/SquircleVisTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e0a905fb2283fa4ca4bbb9f600e2138 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation/BitArrayHelper.cs b/Assets/Scripts/Simulation/BitArrayHelper.cs new file mode 100644 index 00000000..7abdf2a8 --- /dev/null +++ b/Assets/Scripts/Simulation/BitArrayHelper.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections; +using System.Diagnostics; +using UnityEngine; + +namespace DLS.Simulation +{ + // Helper class for dealing with pin state. + // Pin state is stored as a uint32, with format: + // Tristate flags (most significant 16 bits) | Bit states (least significant 16 bits) + public static class BitArrayHelper + { + public static uint[] PreComputedUINTMasks = { + 0b0000_0000_0000_0000__0000_0000_0000_0000, + + 0b0000_0000_0000_0001__0000_0000_0000_0001, + 0b0000_0000_0000_0011__0000_0000_0000_0011, + 0b0000_0000_0000_0111__0000_0000_0000_0111, + 0b0000_0000_0000_1111__0000_0000_0000_1111, + + 0b0000_0000_0001_1111__0000_0000_0001_1111, + 0b0000_0000_0011_1111__0000_0000_0011_1111, + 0b0000_0000_0111_1111__0000_0000_0111_1111, + 0b0000_0000_1111_1111__0000_0000_1111_1111, + + 0b0000_0001_1111_1111__0000_0001_1111_1111, + 0b0000_0011_1111_1111__0000_0011_1111_1111, + 0b0000_0111_1111_1111__0000_0111_1111_1111, + 0b0000_1111_1111_1111__0000_1111_1111_1111, + + 0b0001_1111_1111_1111__0001_1111_1111_1111, + 0b0011_1111_1111_1111__0011_1111_1111_1111, + 0b0111_1111_1111_1111__0111_1111_1111_1111, + 0b1111_1111_1111_1111__1111_1111_1111_1111, + }; + + public static ushort[] PreComputedSHORTMasks = { + 0000_0000_0000_0000, + + 0b0000_0000_0000_0001, + 0b0000_0000_0000_0011, + 0b0000_0000_0000_0111, + 0b0000_0000_0000_1111, + + 0b0000_0000_0001_1111, + 0b0000_0000_0011_1111, + 0b0000_0000_0111_1111, + 0b0000_0000_1111_1111, + + 0b0000_0001_1111_1111, + 0b0000_0011_1111_1111, + 0b0000_0111_1111_1111, + 0b0000_1111_1111_1111, + + 0b0001_1111_1111_1111, + 0b0011_1111_1111_1111, + 0b0111_1111_1111_1111, + 0b1111_1111_1111_1111, + }; + + public static int[] PreComputedINTMasks = { + 0000_0000_0000_0000, + + 0b0000_0000_0000_0001, + 0b0000_0000_0000_0011, + 0b0000_0000_0000_0111, + 0b0000_0000_0000_1111, + + 0b0000_0000_0001_1111, + 0b0000_0000_0011_1111, + 0b0000_0000_0111_1111, + 0b0000_0000_1111_1111, + + 0b0000_0001_1111_1111, + 0b0000_0011_1111_1111, + 0b0000_0111_1111_1111, + 0b0000_1111_1111_1111, + + 0b0001_1111_1111_1111, + 0b0011_1111_1111_1111, + 0b0111_1111_1111_1111, + 0b1111_1111_1111_1111, + }; + + + public static BitArray NonMutativeOR(BitArray A, BitArray B) + { + BitArray temp = new BitArray(A); + temp.Or(B); + return temp; + } + + public static BitArray NonMutativeAND(BitArray A, BitArray B) + { + BitArray temp = new BitArray(A); + temp.And(B); + return temp; + } + + public static BitArray NonMutativeNOT(BitArray A) + { + BitArray temp = new BitArray(A); + temp.Not(); + return temp; + } + + public static BitArray TrueBitArray(int length) + { + BitArray array = new BitArray(length); + array.SetAll(true); + return array; + } + + public static ushort GetUShortAtIndexOfMaxLength(BitArray state, int index, int maxLength) + { + int len = Mathf.Min(maxLength + index, state.Count, 16 + index); + ushort n = 0; + for (int i = index; i < len ; i++) + { + if (state.Get(i)) + n |= (ushort)(1 << (i-index)); + } + return n; + } + + public static uint GetUIntAtIndexOfMaxLength(BitArray state, int index, int maxLength) + { + int len = Mathf.Min(maxLength + index, state.Count, 32 + index); + uint n = 0; + for (int i = index; i < len; i++) + { + if (state.Get(i)) + n |= (uint)(1 << (i - index)); + } + return n; + } + + + public static uint GetFirstUIntFromByteArray(BitArray state) + { + int len = Math.Min(32, state.Count); + uint n = 0; + for (byte i = 0; i < len; i++) + { + if (state.Get(i)) + n |= (uint)(1 << i); + } + return n; + } + + public static BitArray GetBitArrayOfMaxLengthStartingAtIndex(BitArray state, int index, int length) + { + BitArray bitArray = new BitArray(length); + int len = Mathf.Min(length + index, state.Length); + for(int i = index;i < len;i++) + { + bitArray[i - index] = state[i]; + } + return bitArray; + } + + public static void SetNBitsAtIndex(ref BitArray state, BitArray source, int index, int length) + { + int len = Mathf.Min(index + length, state.Length, index + source.Length); + for (int i = index; i < len; i++) + { + state.Set(i, source[i-index]); + } + } + + public static void SetUShortOfMaxLengthAtIndex(ref BitArray state, ushort value, int index, int length) + { + int len = Mathf.Min(index + length, state.Length, index + 16); + for(int i = index; i < len; i++) + { + state.Set(i, ((value >> (i-index)) & 1) == 1); + } + } + + public static void SetUIntOfMaxLengthAtIndex(ref BitArray state, uint value, int index, int length) + { + int len = Mathf.Min(index + length, state.Length, index + 32); + for (int i = index; i < len; i++) + { + state.Set(i, ((value >> (i - index)) & 1) == 1); + } + } + + } +} \ No newline at end of file diff --git a/Assets/Scripts/Simulation/PinState.cs.meta b/Assets/Scripts/Simulation/BitArrayHelper.cs.meta similarity index 100% rename from Assets/Scripts/Simulation/PinState.cs.meta rename to Assets/Scripts/Simulation/BitArrayHelper.cs.meta diff --git a/Assets/Scripts/Simulation/PinState.cs b/Assets/Scripts/Simulation/PinState.cs deleted file mode 100644 index 3d121e9f..00000000 --- a/Assets/Scripts/Simulation/PinState.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace DLS.Simulation -{ - // Helper class for dealing with pin state. - // Pin state is stored as a uint32, with format: - // Tristate flags (most significant 16 bits) | Bit states (least significant 16 bits) - public static class PinState - { - // Each bit has three possible states (tri-state logic): - public const ushort LogicLow = 0; - public const ushort LogicHigh = 1; - public const ushort LogicDisconnected = 2; - - // Mask for single bit value (bit state, and tristate flag) - public const uint SingleBitMask = 1 | (1 << 16); - - public static ushort GetBitStates(uint state) => (ushort)state; - public static ushort GetTristateFlags(uint state) => (ushort)(state >> 16); - - public static void Set(ref uint state, ushort bitStates, ushort tristateFlags) - { - state = (uint)(bitStates | (tristateFlags << 16)); - } - - public static void Set(ref uint state, uint other) => state = other; - - public static ushort GetBitTristatedValue(uint state, int bitIndex) - { - ushort bitState = (ushort)((GetBitStates(state) >> bitIndex) & 1); - ushort tri = (ushort)((GetTristateFlags(state) >> bitIndex) & 1); - return (ushort)(bitState | (tri << 1)); // Combine to form tri-stated value: 0 = LOW, 1 = HIGH, 2 = DISCONNECTED - } - - public static bool FirstBitHigh(uint state) => (state & 1) == LogicHigh; - - public static void Set4BitFrom8BitSource(ref uint state, uint source8bit, bool firstNibble) - { - ushort sourceBitStates = GetBitStates(source8bit); - ushort sourceTristateFlags = GetTristateFlags(source8bit); - - if (firstNibble) - { - const ushort mask = 0b1111; - Set(ref state, (ushort)(sourceBitStates & mask), (ushort)(sourceTristateFlags & mask)); - } - else - { - const uint mask = 0b11110000; - Set(ref state, (ushort)((sourceBitStates & mask) >> 4), (ushort)((sourceTristateFlags & mask) >> 4)); - } - } - - public static void Set8BitFrom4BitSources(ref uint state, uint a, uint b) - { - ushort bitStates = (ushort)(GetBitStates(a) | (GetBitStates(b) << 4)); - ushort tristateFlags = (ushort)((GetTristateFlags(a) & 0b1111) | ((GetTristateFlags(b) & 0b1111) << 4)); - Set(ref state, bitStates, tristateFlags); - } - - - public static void Toggle(ref uint state, int bitIndex) - { - ushort bitStates = GetBitStates(state); - bitStates ^= (ushort)(1u << bitIndex); - - // Clear tristate flags (can't be disconnected if toggling as only input dev pins are allowed) - Set(ref state, bitStates, 0); - } - - public static void SetAllDisconnected(ref uint state) => Set(ref state, 0, ushort.MaxValue); - } -} \ No newline at end of file diff --git a/Assets/Scripts/Simulation/PinStateValue.cs b/Assets/Scripts/Simulation/PinStateValue.cs new file mode 100644 index 00000000..ddf39ff5 --- /dev/null +++ b/Assets/Scripts/Simulation/PinStateValue.cs @@ -0,0 +1,502 @@ +using System; +using System.Collections; +using System.Collections.Specialized; +using DLS.Description; +using static UnityEngine.Random; + +namespace DLS.Simulation +{ + public struct PinStateValue + { + public const uint LOGIC_LOW = 0; + public const uint LOGIC_HIGH = 1; + public const uint LOGIC_DISCONNECTED = 2; + + public uint a; // If bitcount <= 16 : use only this -- FASTEST + + public BitVector32 b; // If 16 < bitcount <=32 : use this too -- medium + public BitArray BigValues; // If bitcount >= 32 use this INSTEAD -- SLOWEST + public BitArray BigTristates; // Goes with it + public int size; + + const short maxSigned16bitValue = 0x7FFF; + public void MakeFromPinBitCount(PinBitCount pinBitCount) + { + size = pinBitCount.BitCount; + if (size <= 16) + { + a = 0; + } + else if (size <= 32) + { + a = 0; + b = new BitVector32(0); + } + else + { + BigValues = new BitArray(size); + BigTristates = new BitArray(size); + } + } + + + public void MakeFromAnother(PinStateValue source) + { + CopyFrom(source); + } + + public void MakeFromValueAndFlags(PinBitCount pinBitCount, uint values, uint flags) + { + size = pinBitCount.BitCount; + + if (size <= 16) + { + SetShortTristateAndValue((ushort)value, (ushort)flags); + } + else if (size <= 32) { + SetMedium(values, flags); + } + else { throw new Exception("ArgumentsException : PinBitCount should be smaller or equal to 32 bits for this operation."); } + } + + public bool SmallHigh() + { + return (a & 1) == 1; + } + + public void SetAllDisconnected() + { + if(size <= 16) + { + a = 0xFFFF0000; + return; + } + + else if(size <= 32) + { + a = 0; + b = new BitVector32(-1); + return; + } + + BigValues = new BitArray(size); + BigTristates = BitArrayHelper.TrueBitArray(size); + } + + public void SmallSet(uint value) + { + a = value; + } + + public void SmallToggle() + { + a ^= 1; + } + + public bool FirstBitHigh() + { + if (size <= 32) { return (a & 1) == 1; } + else return BigValues.Get(0); + } + public void ToggleBit(int index) + { + if (size == 1) { SmallToggle(); } + else if (size <= 32) { a ^= (uint)(1 << index); } + else if (size > 32) BigValues.Set(index, !BigValues.Get(index)); + } + + public void SetFirstBit(bool firstBit) + { + if (size == 1) { SmallSet((uint)(firstBit ? 1 : 0)); } + else if (size <= 32) { + a = firstBit ? a | 1 : a ^ 1; + } + else BigValues.Set(0, firstBit); + } + + public void SetShortValue(ushort value) + { + a = value| (a & 0xFFFF0000); + } + + public void SetShortTristateAndValue(ushort tristate, ushort value) + { + a = (uint)(value | tristate << 16); + } + + public void SetMediumValue(uint value) + { + a = value; + } + + public void SetShort(uint valueAndFlags) + { + a = valueAndFlags; + } + + public void SetMedium(uint value, uint tristateFlags) + { + a = value; + b = new BitVector32((int)tristateFlags); + } + public uint GetShortValues() + { + return a & 0xFFFF; + } + + public uint GetMediumValues() + { + return a; + } + + public uint GetShortTristate() + { + return (a & 0xFFFF0000)>>16; + } + + public uint GetMediumTristate() + { + return (uint)b.Data; + } + + public uint GetValue() + { + if(size == 1) { return (uint)(SmallHigh() ? 1 : 0); } + if(size <=16) { return GetShortValues(); } + if(size <=32) { return GetMediumValues(); } + return BitArrayHelper.GetFirstUIntFromByteArray(BigValues); + } + + public ushort GetSmallTristatedValue() + { + return (ushort)((a & 1) | ((a >> 16) & 1) << 1); + } + + public ushort GetTristatedValue(int index) + { + if (size == 1) { + GetSmallTristatedValue(); + } + if (size <= 16) + { + return GetShortBitTristatedValue(index); + } + + else if (size <= 32) + { + return GetMediumBitTristatedValue(index); + } + else + { + return GetBigBitTristatedValue(index); + } + } + + ushort GetShortBitTristatedValue(int index) + { + return (ushort)(((GetShortValues() >> index) & 1) | (((GetShortTristate() >> index) & 1) << 1)); + } + + + ushort GetMediumBitTristatedValue(int index) + { + return (ushort)((a >> index) & 1 | (GetMediumTristate() >> index & 1) << 1); + } + + ushort GetBigBitTristatedValue(int index) + { + return (ushort)(BigValues.Get(index)?1:BigTristates.Get(index)?2:0); + } + + public uint GetTristatedFlags() + { + if (size == 1) { return (uint)(a >> 1); } + if (size <=16) { return a & 0xFFFF0000; } + if (size <= 32) { return (uint)b.Data; } + return BitArrayHelper.GetFirstUIntFromByteArray(BigTristates); + } + + public void CopyFrom(PinStateValue pinStateValue) + { + size = pinStateValue.size; + if(size == 1) {SmallCopyFrom(pinStateValue); } + else if (size <= 16) { ShortCopyFrom(pinStateValue); } + else if (size <= 32) {MediumCopyFrom(pinStateValue); } + else {BigCopyFrom(pinStateValue); } + } + + void SmallCopyFrom(PinStateValue pinStateValue) + { + a = pinStateValue.a; + } + + void ShortCopyFrom(PinStateValue pinStateValue) + { + a = pinStateValue.a; + } + + void MediumCopyFrom(PinStateValue pinStateValue) + { + a = pinStateValue.a; + b = pinStateValue.b; + } + + void BigCopyFrom(PinStateValue pinStateValue) + { + BigValues = new BitArray(pinStateValue.BigValues); + BigTristates = new BitArray(pinStateValue.BigTristates); + } + + public uint OR(PinStateValue pinStateValue) + { + if (size == 1) { return (pinStateValue.a | a); } + else if (size <= 16) { return pinStateValue.GetShortValues() | GetShortValues(); } + else if (size <= 32) { return pinStateValue.GetMediumValues() | GetMediumValues(); } + return 0; + } + + public void SetAsOr(PinStateValue pinStateValue) + { + if (size == 1) { a |= pinStateValue.a; } + else if (size <= 16) { SetShortValue((ushort)pinStateValue.GetShortValues()); } + else if (size <= 32) { SetMediumValue(a | pinStateValue.GetMediumValues()); } + else { BigValues.Or(pinStateValue.BigValues); } + + } + + public void SetAsAnd(PinStateValue pinStateValue) + { + if (size == 1) { a &= pinStateValue.a; } + else if (size <= 16) { SetShortValue((ushort)(a & pinStateValue.GetShortValues())); } + else if (size <= 32) { SetMediumValue(a & pinStateValue.GetMediumValues()); } + else { BigValues.And(pinStateValue.BigValues); } + } + + public bool HandleConflictShort(PinStateValue other) + { + bool set; + uint OR = a | other.a; + uint AND = a & other.a; + ushort bitsNew = (ushort)(Simulator.RandomBool() ? OR : AND); + + ushort mask = (ushort)(OR >> 16); // tristate flags + bitsNew = (ushort)((bitsNew & ~mask) | ((ushort)OR & mask)); // can always accept input for tristated bits + + ushort tristateNew = (ushort)(AND >> 16); + uint stateNew = (uint)(bitsNew | (tristateNew << 16)); + set = stateNew != a; + + SetShortTristateAndValue(tristateNew, bitsNew); + + return set; + } + + public bool HandleConflictMedium(PinStateValue other) + { + bool set; + (uint a, uint b) OR = (a | other.a, (uint)(b.Data | other.b.Data)) ; + (uint a, uint b) AND = (a & other.a, (uint)(b.Data & other.b.Data)); + uint bitsNew = Simulator.RandomBool() ? OR.a : AND.a; + + bitsNew = (bitsNew & ~OR.b) | (OR.b); + + uint tristateNew = AND.b; + + set = bitsNew != a && (tristateNew != b.Data); + + a = bitsNew; + b = new BitVector32((int)tristateNew); + + return set; + } + + public bool HandleConflictBig(PinStateValue other) { + bool set; + + (BitArray a, BitArray b) OR = (BitArrayHelper.NonMutativeOR(BigValues, other.BigValues), BitArrayHelper.NonMutativeOR(BigTristates, other.BigTristates)) ; + (BitArray a, BitArray b) AND = (BitArrayHelper.NonMutativeAND(BigValues, other.BigValues), BitArrayHelper.NonMutativeAND(BigTristates, other.BigTristates)); + BitArray bitsNew = new BitArray(Simulator.RandomBool() ? OR.a : AND.a); + + bitsNew = BitArrayHelper.NonMutativeOR( + BitArrayHelper.NonMutativeAND(bitsNew, BitArrayHelper.NonMutativeNOT(OR.b)), + OR.b); + + BitArray tristatesNew = AND.b; + set = !bitsNew.Equals(BigValues) && !tristatesNew.Equals(BigTristates); + + BigValues = bitsNew; + BigTristates = tristatesNew; + + return set; + } + + public bool HandleConflicts(PinStateValue other) + { + if (size <= 16) + { + return HandleConflictShort(other); + } + else if (size <= 32) + { + return HandleConflictMedium(other); + } + else + { + return HandleConflictBig(other); + } + } + + public void HandleShortSplit(ref SimPin[] targets) + { + int targetSize = targets[0].State.size; + uint fullMask = BitArrayHelper.PreComputedUINTMasks[targetSize]; + int offset = targets.Length - 1; + + for (int i = 0; i < targets.Length; i++) { + targets[offset - i].State.a = (a>>(i*targetSize)) & fullMask; + } + } + + public void HandleMediumSplit(ref SimPin[] targets) { + int targetSize = targets[0].State.size; + uint mask = BitArrayHelper.PreComputedSHORTMasks[targetSize]; + int offset = targets.Length - 1; + + for (int i = 0; i < targets.Length; i++) + { + int off = (i * targetSize); + targets[offset - i].State.a = (uint)(((a>>off) & mask) | (((b.Data>>off) & mask) <<16)); + } + } + + public void HandleBigSplit(ref SimPin[] targets) + { + int targetSize = targets[0].State.size; + if (targetSize <= 16) + { + for (int i = 0; i < targets.Length; i++) + { + targets[^(i+1)].State.a = (uint)(BitArrayHelper.GetUShortAtIndexOfMaxLength(BigValues, i * targetSize, targetSize) + | (BitArrayHelper.GetUShortAtIndexOfMaxLength(BigTristates, i * targetSize, targetSize) << 16)); + } + } + + else if (targetSize <= 32) + { + for (int i = 0; i < targets.Length; i++) + { + targets[^(i + 1)].State.a = BitArrayHelper.GetUIntAtIndexOfMaxLength(BigValues, i * targetSize, targetSize); + targets[^(i + 1)].State.b = new BitVector32((int)BitArrayHelper.GetUIntAtIndexOfMaxLength(BigTristates, i * targetSize, targetSize)); + } + } + + else + { + for (int i = 0; i < targets.Length; i++) + { + targets[^(i + 1)].State.BigValues = new BitArray(BitArrayHelper.GetBitArrayOfMaxLengthStartingAtIndex(BigValues, i * targetSize, targetSize)); + targets[^(i + 1)].State.BigTristates = new BitArray(BitArrayHelper.GetBitArrayOfMaxLengthStartingAtIndex(BigTristates, i * targetSize, targetSize)); + } + } + + } + + public void HandleSplit(ref SimPin[] targets) + { + if (size <= 16) { HandleShortSplit(ref targets); } + else if (size <= 32) { HandleMediumSplit(ref targets); } + else { HandleBigSplit(ref targets); } + + } + + + public void HandleShortMerge(SimPin[] sources) + { + int sourceSize = sources[0].State.size; + uint fullMask = BitArrayHelper.PreComputedUINTMasks[sourceSize]; + int offset = sources.Length - 1; + + a = sources[offset].State.a & fullMask; + + for (int i = 1; i < sources.Length; i++) + { + a = a | ( sources[offset - i].State.a & fullMask) << (i * sourceSize); + } + + } + + public void HandleMediumMerge(SimPin[] sources) + { + int sourceSize = sources[0].State.size; + uint mask = BitArrayHelper.PreComputedSHORTMasks[sourceSize]; + int offset = sources.Length - 1; + + + a = sources[offset].State.a & mask; + int tri = (int)(sources[offset].State.a >> 16 & mask); + + for (int i = 1; i < sources.Length; i++) + { + int shift = (i * sourceSize); + uint sourceState = sources[offset-i].State.a; + a |= (sourceState & mask) << shift; + tri |= (int)((sourceState >> 16)& mask) << shift; + } + + b = new BitVector32(tri); + } + + public void HandleBigMerge(SimPin[] sources) + { + int sourceSize = sources[0].State.size; // Always the same + BigValues = new BitArray(size); + BigTristates = BitArrayHelper.TrueBitArray(size); + + if (sourceSize <= 16) + { + uint mask = BitArrayHelper.PreComputedSHORTMasks[sourceSize]; + uint triMask = mask << 16; + for (int i = 0; i < sources.Length; i++) + { + uint sourceState = sources[^(i + 1)].State.a; + BitArrayHelper.SetUShortOfMaxLengthAtIndex(ref BigValues, (ushort)(sourceState & mask), (i * sourceSize), sourceSize); + BitArrayHelper.SetUShortOfMaxLengthAtIndex(ref BigTristates, (ushort)((sourceState & triMask)>>16), (i * sourceSize), sourceSize); + } + } + else if (sourceSize <= 32) + { + for (int i = 0; i < sources.Length; i++) + { + BitArrayHelper.SetUIntOfMaxLengthAtIndex(ref BigValues, sources[^(i + 1)].State.a, i * sourceSize, sourceSize); + BitArrayHelper.SetUIntOfMaxLengthAtIndex(ref BigTristates, (uint)sources[^(i + 1)].State.b.Data, i * sourceSize, sourceSize); + } + } + else + { + for (int i = 0; i < sources.Length; i++) + { + BitArrayHelper.SetNBitsAtIndex(ref BigValues, sources[^(i + 1)].State.BigValues, sourceSize * i, sourceSize); + BitArrayHelper.SetNBitsAtIndex(ref BigTristates, sources[^(i+1)].State.BigTristates, sourceSize * i, sourceSize); + } + } + } + + public void HandleMerge(SimPin[] sources) + { + if(size <= 16) { HandleShortMerge(sources); } + else if(size <= 32) { HandleMediumMerge(sources); } + else + { + HandleBigMerge(sources); + } + } + + public bool IsValueBiggerThanInt() { + if (size <= 32) { return false; } + for (int i = 33; i < BigValues.Length; i++) { + if (BigValues[i]) return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Simulation/PinStateValue.cs.meta b/Assets/Scripts/Simulation/PinStateValue.cs.meta new file mode 100644 index 00000000..bf6e35ef --- /dev/null +++ b/Assets/Scripts/Simulation/PinStateValue.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d5a1af65fa02f294d960270c8bb6a22d \ No newline at end of file diff --git a/Assets/Scripts/Simulation/SimChip.cs b/Assets/Scripts/Simulation/SimChip.cs index 5efec7c5..2b5f27e4 100644 --- a/Assets/Scripts/Simulation/SimChip.cs +++ b/Assets/Scripts/Simulation/SimChip.cs @@ -77,6 +77,7 @@ public SimChip(ChipDescription desc, int id, uint[] internalState, SimChip[] sub InternalState[i] = BitConverter.ToUInt32(randomBytes); } } + // Load in serialized persistent state (rom data, etc.) else if (internalState is { Length: > 0 }) { @@ -218,7 +219,7 @@ public void AddPin(SimPin pin, bool isInput) } } - static SimPin CreateSimPinFromDescription(PinDescription desc, bool isInput, SimChip parent) => new(desc.ID, isInput, parent); + static SimPin CreateSimPinFromDescription(PinDescription desc, bool isInput, SimChip parent) => new(desc.ID, isInput, parent, desc.BitCount); public void RemovePin(int removePinID) { @@ -270,7 +271,7 @@ public void RemoveConnection(PinAddress sourcePinAddress, PinAddress targetPinAd removeTargetPin.numInputConnections -= 1; if (removeTargetPin.numInputConnections == 0) { - PinState.SetAllDisconnected(ref removeTargetPin.State); + removeTargetPin.State.SetAllDisconnected(); removeTargetPin.latestSourceID = -1; removeTargetPin.latestSourceParentChipID = -1; if (targetChip != null) removeTargetPin.parentChip.numConnectedInputs--; diff --git a/Assets/Scripts/Simulation/SimPin.cs b/Assets/Scripts/Simulation/SimPin.cs index 9528fe5e..6fcec59f 100644 --- a/Assets/Scripts/Simulation/SimPin.cs +++ b/Assets/Scripts/Simulation/SimPin.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using DLS.Description; namespace DLS.Simulation { @@ -7,7 +9,7 @@ public class SimPin public readonly int ID; public readonly SimChip parentChip; public readonly bool isInput; - public uint State; + public PinStateValue State; public SimPin[] ConnectedTargetPins = Array.Empty(); @@ -23,7 +25,7 @@ public class SimPin public int numInputConnections; public int numInputsReceivedThisFrame; - public SimPin(int id, bool isInput, SimChip parentChip) + public SimPin(int id, bool isInput, SimChip parentChip, PinBitCount pinBitCount) { this.parentChip = parentChip; this.isInput = isInput; @@ -31,10 +33,11 @@ public SimPin(int id, bool isInput, SimChip parentChip) latestSourceID = -1; latestSourceParentChipID = -1; - PinState.SetAllDisconnected(ref State); + State.MakeFromPinBitCount(pinBitCount); + State.SetAllDisconnected(); } - public bool FirstBitHigh => PinState.FirstBitHigh(State); + public bool FirstBitHigh => State.FirstBitHigh(); public void PropagateSignal() { @@ -59,21 +62,7 @@ void ReceiveInput(SimPin source) if (numInputsReceivedThisFrame > 0) { - // Has already received input this frame, so choose at random whether to accept conflicting input. - // Note: for multi-bit pins, this choice is made identically for all bits, rather than individually. - // Todo: maybe consider changing to per-bit in the future...) - - uint OR = source.State | State; - uint AND = source.State & State; - ushort bitsNew = (ushort)(Simulator.RandomBool() ? OR : AND); // randomly accept or reject conflicting state - - ushort mask = (ushort)(OR >> 16); // tristate flags - bitsNew = (ushort)((bitsNew & ~mask) | ((ushort)OR & mask)); // can always accept input for tristated bits - - ushort tristateNew = (ushort)(AND >> 16); - uint stateNew = (uint)(bitsNew | (tristateNew << 16)); - set = stateNew != State; - State = stateNew; + set = State.HandleConflicts(source.State); } else { diff --git a/Assets/Scripts/Simulation/Simulator.cs b/Assets/Scripts/Simulation/Simulator.cs index 1d777628..be8cc2eb 100644 --- a/Assets/Scripts/Simulation/Simulator.cs +++ b/Assets/Scripts/Simulation/Simulator.cs @@ -1,13 +1,23 @@ using System; +using System.Collections; using System.Collections.Concurrent; using System.Diagnostics; +using UnityEngine; using DLS.Description; using DLS.Game; +using NUnit.Framework.Interfaces; using Random = System.Random; +using System.Security.Cryptography; namespace DLS.Simulation { - public static class Simulator + public static class Constants + { + public const uint LOGIC_LOW = 0; + public const uint LOGIC_HIGH = 1; + public const uint LOGIC_DISCONNECTED = 2; + } + public static class Simulator { public static readonly Random rng = new(); static readonly Stopwatch stopwatch = Stopwatch.StartNew(); @@ -66,9 +76,8 @@ public static void RunSimulationStep(SimChip rootSimChip, DevPinInstance[] input try { SimPin simPin = rootSimChip.GetSimPinFromAddress(input.Pin.Address); - PinState.Set(ref simPin.State, input.Pin.PlayerInputState); - input.Pin.State = input.Pin.PlayerInputState; + simPin.State.CopyFrom(input.Pin.PlayerInputState); } catch (Exception) { @@ -88,6 +97,7 @@ public static void RunSimulationStep(SimChip rootSimChip, DevPinInstance[] input } UpdateAudioState(); + Project.ActiveProject.description.StepsRanSinceCreated++; } public static void UpdateInPausedState() @@ -230,151 +240,105 @@ static void ProcessBuiltinChip(SimChip chip) // ---- Process Built-in chips ---- case ChipType.Nand: { - uint nandOp = 1 ^ (chip.InputPins[0].State & chip.InputPins[1].State); - chip.OutputPins[0].State = (ushort)(nandOp & 1); - break; - } - case ChipType.Clock: - { - bool high = stepsPerClockTransition != 0 && ((simulationFrame / stepsPerClockTransition) & 1) == 0; - PinState.Set(ref chip.OutputPins[0].State, high ? PinState.LogicHigh : PinState.LogicLow); - break; - } - case ChipType.Pulse: - { - const int pulseDurationIndex = 0; - const int pulseTicksRemainingIndex = 1; - const int pulseInputOldIndex = 2; - - uint inputState = chip.InputPins[0].State; - bool pulseInputHigh = PinState.FirstBitHigh(inputState); - uint pulseTicksRemaining = chip.InternalState[pulseTicksRemainingIndex]; - - if (pulseTicksRemaining == 0) - { - bool isRisingEdge = pulseInputHigh && chip.InternalState[pulseInputOldIndex] == 0; - if (isRisingEdge) - { - pulseTicksRemaining = chip.InternalState[pulseDurationIndex]; - chip.InternalState[pulseTicksRemainingIndex] = pulseTicksRemaining; - } - } - - uint outputState = PinState.LogicLow; - if (pulseTicksRemaining > 0) - { - chip.InternalState[1]--; - outputState = PinState.LogicHigh; - } - else if (PinState.GetTristateFlags(inputState) != 0) - { - PinState.SetAllDisconnected(ref outputState); - } + uint nandOp = 1 ^ (chip.InputPins[0].State.a & chip.InputPins[1].State.a); - chip.OutputPins[0].State = outputState; - chip.InternalState[pulseInputOldIndex] = pulseInputHigh ? 1u : 0; + chip.OutputPins[0].State.a = (nandOp & 1); - break; - } - case ChipType.Split_4To1Bit: - { - uint inState4Bit = chip.InputPins[0].State; - chip.OutputPins[0].State = (inState4Bit >> 3) & PinState.SingleBitMask; - chip.OutputPins[1].State = (inState4Bit >> 2) & PinState.SingleBitMask; - chip.OutputPins[2].State = (inState4Bit >> 1) & PinState.SingleBitMask; - chip.OutputPins[3].State = (inState4Bit >> 0) & PinState.SingleBitMask; - break; - } - case ChipType.Merge_1To4Bit: - { - uint stateA = chip.InputPins[3].State & PinState.SingleBitMask; // lsb - uint stateB = chip.InputPins[2].State & PinState.SingleBitMask; - uint stateC = chip.InputPins[1].State & PinState.SingleBitMask; - uint stateD = chip.InputPins[0].State & PinState.SingleBitMask; - chip.OutputPins[0].State = stateA | stateB << 1 | stateC << 2 | stateD << 3; - break; - } - case ChipType.Merge_1To8Bit: - { - uint stateA = chip.InputPins[7].State & PinState.SingleBitMask; // lsb - uint stateB = chip.InputPins[6].State & PinState.SingleBitMask; - uint stateC = chip.InputPins[5].State & PinState.SingleBitMask; - uint stateD = chip.InputPins[4].State & PinState.SingleBitMask; - uint stateE = chip.InputPins[3].State & PinState.SingleBitMask; - uint stateF = chip.InputPins[2].State & PinState.SingleBitMask; - uint stateG = chip.InputPins[1].State & PinState.SingleBitMask; - uint stateH = chip.InputPins[0].State & PinState.SingleBitMask; - chip.OutputPins[0].State = stateA | stateB << 1 | stateC << 2 | stateD << 3 | stateE << 4 | stateF << 5 | stateG << 6 | stateH << 7; - break; - } - case ChipType.Merge_4To8Bit: - { - SimPin in4A = chip.InputPins[0]; - SimPin in4B = chip.InputPins[1]; - SimPin out8 = chip.OutputPins[0]; - PinState.Set8BitFrom4BitSources(ref out8.State, in4B.State, in4A.State); - break; - } - case ChipType.Split_8To4Bit: + break; + } + case ChipType.Clock: { - SimPin in8 = chip.InputPins[0]; - SimPin out4A = chip.OutputPins[0]; - SimPin out4B = chip.OutputPins[1]; - PinState.Set4BitFrom8BitSource(ref out4A.State, in8.State, false); - PinState.Set4BitFrom8BitSource(ref out4B.State, in8.State, true); - break; - } - case ChipType.Split_8To1Bit: + bool high = stepsPerClockTransition != 0 && ((simulationFrame / stepsPerClockTransition) & 1) == 0; + chip.OutputPins[0].State.SmallSet((high ? Constants.LOGIC_HIGH : Constants.LOGIC_LOW)); + break; + } + case ChipType.Pulse: { - uint in8 = chip.InputPins[0].State; - chip.OutputPins[0].State = (in8 >> 7) & PinState.SingleBitMask; - chip.OutputPins[1].State = (in8 >> 6) & PinState.SingleBitMask; - chip.OutputPins[2].State = (in8 >> 5) & PinState.SingleBitMask; - chip.OutputPins[3].State = (in8 >> 4) & PinState.SingleBitMask; - chip.OutputPins[4].State = (in8 >> 3) & PinState.SingleBitMask; - chip.OutputPins[5].State = (in8 >> 2) & PinState.SingleBitMask; - chip.OutputPins[6].State = (in8 >> 1) & PinState.SingleBitMask; - chip.OutputPins[7].State = (in8 >> 0) & PinState.SingleBitMask; - break; - } - case ChipType.TriStateBuffer: + const int pulseDurationIndex = 0; + const int pulseTicksRemainingIndex = 1; + const int pulseInputOldIndex = 2; + + uint inputState = chip.InputPins[0].State.a; + bool pulseInputHigh = (inputState & 1) == 1; + uint pulseTicksRemaining = chip.InternalState[pulseTicksRemainingIndex]; + + if (pulseTicksRemaining == 0) + { + bool isRisingEdge = pulseInputHigh && chip.InternalState[pulseInputOldIndex] == 0; + if (isRisingEdge) + { + pulseTicksRemaining = chip.InternalState[pulseDurationIndex]; + chip.InternalState[pulseTicksRemainingIndex] = pulseTicksRemaining; + } + } + + uint outputState = 0; + if (pulseTicksRemaining > 0) + { + chip.InternalState[1]--; + outputState = 1; + } + else if ((inputState >> 1) != 0) + { + outputState = 2; + } + + chip.OutputPins[0].State.a = outputState; + chip.InternalState[pulseInputOldIndex] = pulseInputHigh ? 1u : 0; + + break; + } + + /*case ChipType.Split_Pin: + { + BitArray[] bitArrays = new BitArray[chip.OutputPins.Length]; + for (int i = 0; i < chip.OutputPins.Length; i++) + { + bitArrays[i] = chip.OutputPins[i].State; + } + PinState.SplitOneToMany(ref bitArrays, chip.InputPins[0].State); + break; + } + case ChipType.Merge_Pin: + { + BitArray[] bitArrays = new BitArray[chip.InputPins.Length]; + for (int i = 0; i < chip.InputPins.Length; i++) + { + bitArrays[i] = chip.InputPins[i].State; + } + PinState.MergeManyToOne(ref chip.OutputPins[0].State, bitArrays); + break; + }*/ + + case ChipType.TriStateBuffer: { - SimPin dataPin = chip.InputPins[0]; - SimPin enablePin = chip.InputPins[1]; - SimPin outputPin = chip.OutputPins[0]; + SimPin outputPin = chip.OutputPins[0]; - if (PinState.FirstBitHigh(enablePin.State)) outputPin.State = dataPin.State; - else PinState.SetAllDisconnected(ref outputPin.State); + if (chip.InputPins[1].State.SmallHigh()) outputPin.State = chip.InputPins[0].State; + else outputPin.State.a = 0b_0000_0000_0000_0001___0000_0000_0000_0000; break; } case ChipType.Key: { bool isHeld = SimKeyboardHelper.KeyIsHeld((char)chip.InternalState[0]); - chip.OutputPins[0].State = isHeld ? PinState.LogicHigh : PinState.LogicLow; + chip.OutputPins[0].State.SmallSet(isHeld ? Constants.LOGIC_HIGH : Constants.LOGIC_LOW); break; } case ChipType.DisplayRGB: { const uint addressSpace = 256; - uint addressPin = chip.InputPins[0].State; - uint redPin = chip.InputPins[1].State; - uint greenPin = chip.InputPins[2].State; - uint bluePin = chip.InputPins[3].State; - uint resetPin = chip.InputPins[4].State; - uint writePin = chip.InputPins[5].State; - uint refreshPin = chip.InputPins[6].State; - uint clockPin = chip.InputPins[7].State; + uint addressPin = chip.InputPins[0].State.GetShortValues(); + bool writePin = chip.InputPins[5].State.SmallHigh(); + bool clockPin = chip.InputPins[7].State.SmallHigh(); - // Detect clock rising edge - bool clockHigh = PinState.FirstBitHigh(clockPin); - bool isRisingEdge = clockHigh && chip.InternalState[^1] == 0; - chip.InternalState[^1] = clockHigh ? 1u : 0; + bool isRisingEdge = clockPin && chip.InternalState[^1] == 0; + chip.InternalState[^1] = clockPin ? 1u : 0; if (isRisingEdge) { // Clear back buffer - if (PinState.FirstBitHigh(resetPin)) + if (chip.InputPins[4].State.SmallHigh()) { for (int i = 0; i < addressSpace; i++) { @@ -382,15 +346,14 @@ static void ProcessBuiltinChip(SimChip chip) } } // Write to back-buffer - else if (PinState.FirstBitHigh(writePin)) + else if (writePin) { - uint addressIndex = PinState.GetBitStates(addressPin) + addressSpace; - uint data = (uint)(PinState.GetBitStates(redPin) | (PinState.GetBitStates(greenPin) << 4) | (PinState.GetBitStates(bluePin) << 8)); - chip.InternalState[addressIndex] = data; + uint data = (chip.InputPins[1].State.GetShortValues() | (chip.InputPins[2].State.GetShortValues() << 4) | (chip.InputPins[3].State.GetShortValues() << 8)); + chip.InternalState[addressPin + addressSpace] = data; } // Copy back-buffer to display buffer - if (PinState.FirstBitHigh(refreshPin)) + if (chip.InputPins[6].State.SmallHigh()) { for (int i = 0; i < addressSpace; i++) { @@ -400,32 +363,28 @@ static void ProcessBuiltinChip(SimChip chip) } // Output current pixel colour - uint colData = chip.InternalState[PinState.GetBitStates(addressPin)]; - chip.OutputPins[0].State = (ushort)((colData >> 0) & 0b1111); // red - chip.OutputPins[1].State = (ushort)((colData >> 4) & 0b1111); // green - chip.OutputPins[2].State = (ushort)((colData >> 8) & 0b1111); // blue + uint colData = chip.InternalState[addressPin]; + chip.OutputPins[0].State.SetShort((ushort)(colData & 0b1111));//red + chip.OutputPins[1].State.SetShort((ushort)((colData >> 4) & 0b1111));//green + chip.OutputPins[2].State.SetShort((ushort)((colData >> 8) & 0b1111) );//blue - break; + + break; } case ChipType.DisplayDot: { const uint addressSpace = 256; - uint addressPin = chip.InputPins[0].State; - uint pixelInputPin = chip.InputPins[1].State; - uint resetPin = chip.InputPins[2].State; - uint writePin = chip.InputPins[3].State; - uint refreshPin = chip.InputPins[4].State; - uint clockPin = chip.InputPins[5].State; + uint addressPin = chip.InputPins[0].State.GetShortValues(); + bool clockPin = chip.InputPins[5].State.SmallHigh(); // Detect clock rising edge - bool clockHigh = PinState.FirstBitHigh(clockPin); - bool isRisingEdge = clockHigh && chip.InternalState[^1] == 0; - chip.InternalState[^1] = clockHigh ? 1u : 0; + bool isRisingEdge = clockPin && chip.InternalState[^1] == 0; + chip.InternalState[^1] = clockPin ? 1u : 0; if (isRisingEdge) { // Clear back buffer - if (PinState.FirstBitHigh(resetPin)) + if (chip.InputPins[2].State.SmallHigh()) { for (int i = 0; i < addressSpace; i++) { @@ -433,15 +392,14 @@ static void ProcessBuiltinChip(SimChip chip) } } // Write to back-buffer - else if (PinState.FirstBitHigh(writePin)) + else if (chip.InputPins[3].State.SmallHigh()) { - uint addressIndex = PinState.GetBitStates(addressPin) + addressSpace; - uint data = PinState.GetBitStates(pixelInputPin); - chip.InternalState[addressIndex] = data; + uint data = (uint)(chip.InputPins[1].State.SmallHigh() ? 1 : 0); + chip.InternalState[addressPin + addressSpace] = data; } // Copy back-buffer to display buffer - if (PinState.FirstBitHigh(refreshPin)) + if (chip.InputPins[4].State.SmallHigh()) { for (int i = 0; i < addressSpace; i++) { @@ -451,68 +409,133 @@ static void ProcessBuiltinChip(SimChip chip) } // Output current pixel colour - ushort pixelState = (ushort)chip.InternalState[PinState.GetBitStates(addressPin)]; - chip.OutputPins[0].State = pixelState; - + chip.OutputPins[0].State.SmallSet(chip.InternalState[addressPin]); break; } case ChipType.dev_Ram_8Bit: { - uint addressPin = chip.InputPins[0].State; - uint dataPin = chip.InputPins[1].State; - uint writeEnablePin = chip.InputPins[2].State; - uint resetPin = chip.InputPins[3].State; - uint clockPin = chip.InputPins[4].State; + uint addressPin = chip.InputPins[0].State.GetShortValues(); // Detect clock rising edge - bool clockHigh = PinState.FirstBitHigh(clockPin); + bool clockHigh = chip.InputPins[4].State.SmallHigh(); bool isRisingEdge = clockHigh && chip.InternalState[^1] == 0; chip.InternalState[^1] = clockHigh ? 1u : 0; // Write/Reset on rising edge if (isRisingEdge) { - if (PinState.FirstBitHigh(resetPin)) + if (chip.InputPins[3].State.SmallHigh()) { for (int i = 0; i < 256; i++) { chip.InternalState[i] = 0; } } - else if (PinState.FirstBitHigh(writeEnablePin)) + else if (chip.InputPins[2].State.SmallHigh()) { - chip.InternalState[PinState.GetBitStates(addressPin)] = PinState.GetBitStates(dataPin); + chip.InternalState[addressPin] = chip.InputPins[1].State.GetShortValues(); } } // Output data at current address - chip.OutputPins[0].State = (ushort)chip.InternalState[PinState.GetBitStates(addressPin)]; + chip.OutputPins[0].State.SetShort((ushort)chip.InternalState[addressPin]); break; } case ChipType.Rom_256x16: { - const int ByteMask = 0b11111111; - uint address = PinState.GetBitStates(chip.InputPins[0].State); + const uint mask = 0x00ff; + uint address = chip.InputPins[0].State.GetShortValues(); uint data = chip.InternalState[address]; - chip.OutputPins[0].State = (ushort)((data >> 8) & ByteMask); - chip.OutputPins[1].State = (ushort)(data & ByteMask); - break; + + chip.OutputPins[0].State.SetShort((data>>8) & mask); + chip.OutputPins[1].State.SetShort(data & mask); + + break; } + + case ChipType.EEPROM_256x16: + { + const uint mask = 0x00ff; + uint address = chip.InputPins[0].State.GetShortValues(); + bool isWriting = chip.InputPins[3].State.SmallHigh(); + bool clockHigh = chip.InputPins[4].State.SmallHigh(); + bool isRisingEdge = clockHigh && chip.InternalState[^1] == 0; + chip.InternalState[^1] = clockHigh ? 1u : 0; + + + if (isWriting && isRisingEdge) + { + uint writeData = (ushort)(((chip.InputPins[1].State.GetShortValues() & mask) << 8) | (chip.InputPins[2].State.GetShortValues() & mask)); + chip.InternalState[address] = writeData; + Project.ActiveProject.NotifyRomContentsEditedRuntime(chip); + } + + uint data = chip.InternalState[address]; + chip.OutputPins[0].State.SetShort((data >> 8) & mask); + chip.OutputPins[1]. State.SetShort(data & mask); + break; + } + case ChipType.Buzzer: { - int freqIndex = PinState.GetBitStates(chip.InputPins[0].State); - int volumeIndex = PinState.GetBitStates(chip.InputPins[1].State); - audioState.RegisterNote(freqIndex, (uint)volumeIndex); + int freqIndex = (int)chip.InputPins[0].State.GetShortValues(); + uint volumeIndex = chip.InputPins[1].State.GetShortValues(); + audioState.RegisterNote(freqIndex, volumeIndex); + break; + } + case ChipType.SPS: + { + double tps = Project.ActiveProject.simAvgTicksPerSec; + ushort sps = (ushort)tps; + ushort spc = (ushort)stepsPerClockTransition; + + chip.OutputPins[3].State.SmallSet((uint)(tps >= 65536 ? 1 : 0)); + chip.OutputPins[2].State.SmallSet((uint)(stepsPerClockTransition > 65535 ? 1 : 0)); + chip.OutputPins[1].State.SetShort(sps); + chip.OutputPins[0].State.SetShort(spc); + break; + } + case ChipType.RTC: + { + uint unixTime = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + chip.OutputPins[0].State.SetMedium(unixTime, 0); + break; + } + case ChipType.Constant_8Bit: + { + chip.OutputPins[0].State.SetShort((ushort)chip.InternalState[0]); break; } + + case ChipType.Split_Pin: + { + chip.InputPins[0].State.HandleSplit(ref chip.OutputPins); + break; + } + case ChipType.Detector: + { + uint state = chip.InputPins[0].State.GetSmallTristatedValue(); + chip.OutputPins[0].State.SmallSet(1 << 16); + chip.OutputPins[1].State.SmallSet(1 << 16); + chip.OutputPins[2].State.SmallSet(1 << 16); + chip.OutputPins[state].State.SmallSet(1); + break; + } + + case ChipType.Merge_Pin: + { + chip.OutputPins[0].State.HandleMerge(chip.InputPins); + break; + } + // ---- Bus types ---- default: { if (ChipTypeHelper.IsBusOriginType(chip.ChipType)) { SimPin inputPin = chip.InputPins[0]; - PinState.Set(ref chip.OutputPins[0].State, inputPin.State); + chip.OutputPins[0].State.CopyFrom(inputPin.State); } break; @@ -557,13 +580,13 @@ static SimChip BuildSimChipRecursive(ChipDescription chipDesc, ChipLibrary libra return simChip; } - public static void AddPin(SimChip simChip, int pinID, bool isInputPin) + public static void AddPin(SimChip simChip, int pinID, bool isInputPin, PinBitCount pinBitCount) { SimModifyCommand command = new() { type = SimModifyCommand.ModificationType.AddPin, modifyTarget = simChip, - simPinToAdd = new SimPin(pinID, isInputPin, simChip), + simPinToAdd = new SimPin(pinID, isInputPin, simChip, pinBitCount), pinIsInputPin = isInputPin }; modificationQueue.Enqueue(command); @@ -701,4 +724,4 @@ public enum ModificationType public int removeSubChipID; } } -} \ No newline at end of file +} diff --git a/Assets/Settings.meta b/Assets/Settings.meta new file mode 100644 index 00000000..fdd88cac --- /dev/null +++ b/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6d0353d1576e9154f973793f7bc4308b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Build Profiles.meta b/Assets/Settings/Build Profiles.meta new file mode 100644 index 00000000..5b4e1f70 --- /dev/null +++ b/Assets/Settings/Build Profiles.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5f0ee81e026d8b74cad1e5add1f651ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Build Profiles/New Linux Profile.asset b/Assets/Settings/Build Profiles/New Linux Profile.asset new file mode 100644 index 00000000..f5d1a384 --- /dev/null +++ b/Assets/Settings/Build Profiles/New Linux Profile.asset @@ -0,0 +1,42 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0} + m_Name: New Linux Profile + m_EditorClassIdentifier: + m_AssetVersion: 1 + m_BuildTarget: 24 + m_Subtarget: 2 + m_PlatformId: cb423bfea44b4d658edb8bc5d91a3024 + m_PlatformBuildProfile: + rid: 9124197694904729793 + m_OverrideGlobalSceneList: 0 + m_Scenes: [] + m_ScriptingDefines: [] + m_PlayerSettingsYaml: + m_Settings: [] + references: + version: 2 + RefIds: + - rid: 9124197694904729793 + type: {class: LinuxPlatformSettings, ns: UnityEditor.LinuxStandalone, asm: UnityEditor.LinuxStandalone.Extensions} + data: + m_Development: 0 + m_ConnectProfiler: 0 + m_BuildWithDeepProfilingSupport: 0 + m_AllowDebugging: 0 + m_WaitForManagedDebugger: 0 + m_ManagedDebuggerFixedPort: 0 + m_ExplicitNullChecks: 0 + m_ExplicitDivideByZeroChecks: 0 + m_ExplicitArrayBoundsChecks: 0 + m_CompressionType: 0 + m_InstallInBuildFolder: 0 diff --git a/Assets/Settings/Build Profiles/New Linux Profile.asset.meta b/Assets/Settings/Build Profiles/New Linux Profile.asset.meta new file mode 100644 index 00000000..311584b4 --- /dev/null +++ b/Assets/Settings/Build Profiles/New Linux Profile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eafbc780aff4acd48ad4824cd8063138 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json index fe5629cb..7fa98e9f 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -2,6 +2,7 @@ "dependencies": { "com.unity.ide.rider": "3.0.36", "com.unity.ide.visualstudio": "2.0.23", + "com.unity.toolchain.linux-x86_64": "2.0.10", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.assetbundle": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index b62ca8b2..0507288a 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -50,6 +50,16 @@ "com.unity.modules.jsonserialize": "1.0.0" } }, + "com.unity.toolchain.linux-x86_64": { + "version": "2.0.10", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.10", + "com.unity.sysroot.linux-x86_64": "2.0.9" + }, + "url": "https://packages.unity.com" + }, "com.unity.toolchain.win-x86_64-linux-x86_64": { "version": "2.0.10", "depth": 0, diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 8908a3b3..bfcd58fe 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -714,15 +714,16 @@ PlayerSettings: webGLWebAssemblyBigInt: 0 webGLCloseOnQuit: 0 webWasm2023: 0 - scriptingDefineSymbols: {} + scriptingDefineSymbols: + Standalone: additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: - Standalone: 0 - il2cppCompilerConfiguration: Standalone: 1 + il2cppCompilerConfiguration: + Standalone: 2 il2cppCodeGeneration: - Standalone: 1 + Standalone: 0 il2cppStacktraceInformation: {} managedStrippingLevel: EmbeddedLinux: 1 @@ -751,8 +752,8 @@ PlayerSettings: editorAssembliesCompatibilityLevel: 1 m_RenderingPath: 1 m_MobileRenderingPath: 1 - metroPackageName: 2D_BuiltInRenderer - metroPackageVersion: + metroPackageName: 2DBuiltInRenderer + metroPackageVersion: 1.0.0.0 metroCertificatePath: metroCertificatePassword: metroCertificateSubject: @@ -760,7 +761,7 @@ PlayerSettings: metroCertificateNotAfter: 0000000000000000 metroApplicationDescription: 2D_BuiltInRenderer wsaImages: {} - metroTileShortName: + metroTileShortName: Digital-Logic-Sim metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 diff --git a/README.md b/README.md index 0f56dc0b..38c7261a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -# Digital-Logic-Sim -A minimalistic digital logic simulator, which I created as part of my video series: [Exploring How Computers Work](https://www.youtube.com/playlist?list=PLFt_AvWsXl0dPhqVsKt1Ni_46ARyiCGSq). -
You can find the latest builds over [here](https://sebastian.itch.io/digital-logic-sim).
+# Digital-Logic-Sim Community Edit +Our version of Sebastian Lague's Digital Logic Sim, which you can find on [itch.io](https://sebastian.itch.io/digital-logic-sim) and on [GitHub](https://github.com/SebLague/Digital-Logic-Sim). -Note: Pull requests are welcome, but please be aware that I'm far more likely to merge performance/ux improvements and bug fixes than new built-in chips or features. I do hope to provide some form of mod support in the future, but don't have any concrete plans for it right now. If you'd like to ask or discuss anything relating to development with me/others, check out [Discussions/Dev](https://github.com/SebLague/Digital-Logic-Sim/discussions/categories/dev). - -[![IMAGE ALT TEXT HERE](https://raw.githubusercontent.com/SebLague/Images/master/Exploring%20how%20computers%20work.jpg)](http://www.youtube.com/watch?v=QZwneRb-zqA) +If you want to know what we are working on right now, check our [Task Management](https://nimble-pineapple-9b5.notion.site/2048ce5472ef8067a14cf50ecfb276e4?v=2048ce5472ef807e9872000c03ec9fe8). +Feel free to open a pull request and contribute to it, we would love to add your features! +This Community Edit is made so that you can have all the features that you love in one single repository or build. diff --git a/TestData/AppSettings.json b/TestData/AppSettings.json index 75e0b057..8fae20ec 100644 --- a/TestData/AppSettings.json +++ b/TestData/AppSettings.json @@ -1,6 +1,6 @@ { - "ResolutionX": 1280, - "ResolutionY": 720, - "fullscreenMode": 1, + "ResolutionX": 960, + "ResolutionY": 540, + "fullscreenMode": 3, "VSyncEnabled": true } \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/#AA.json b/TestData/Projects/MainTest/Chips/#AA.json new file mode 100644 index 00000000..8df2b430 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/#AA.json @@ -0,0 +1,21 @@ +{ + "DLSVersion": "2.1.6", + "Name": "#AA", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 0.575, + "y": 0.375 + }, + "Colour": { + "r": 1.0, + "g": 1.0, + "b": 1.0, + "a": 1 + }, + "InputPins":[], + "OutputPins":[], + "SubChips":[], + "Wires":[], + "Displays":[] +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/4sw.json b/TestData/Projects/MainTest/Chips/4sw.json new file mode 100644 index 00000000..afd17233 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/4sw.json @@ -0,0 +1,193 @@ +{ + "DLSVersion": "2.1.6", + "Name": "4sw", + "NameLocation": 2, + "ChipType": 0, + "Size": { + "x": 0.95, + "y": 0.375 + }, + "Colour": { + "r": 0.0, + "g": 0.3282586, + "b": 0.389112562, + "a": 1 + }, + "InputPins":[], + "OutputPins":[ + { + "Name":"OUT", + "ID":419497571, + "Position":{ + "x":1.5, + "y":0.375 + }, + "BitCount":4, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "SubChips":[ + { + "Name":"DIPSWITCH", + "ID":637918487, + "Label":"A", + "Position":{ + "x":-1.72875, + "y":1.33714 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":0}], + "InternalData":[0] + }, + { + "Name":"DIPSWITCH", + "ID":2003682645, + "Label":"B", + "Position":{ + "x":-1.72875, + "y":0.83714 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":0}], + "InternalData":[0] + }, + { + "Name":"DIPSWITCH", + "ID":1080690296, + "Label":"C", + "Position":{ + "x":-1.72875, + "y":0.125 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":0}], + "InternalData":[0] + }, + { + "Name":"DIPSWITCH", + "ID":357019424, + "Label":"D", + "Position":{ + "x":-1.72875, + "y":-0.375 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":0}], + "InternalData":[0] + }, + { + "Name":"1-4BIT", + "ID":1676219068, + "Label":"", + "Position":{ + "x":-0.3025, + "y":0.375 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":4}], + "InternalData":null + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":637918487 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1676219068 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":2003682645 + }, + "TargetPinAddress":{ + "PinID":1, + "PinOwnerID":1676219068 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1080690296 + }, + "TargetPinAddress":{ + "PinID":2, + "PinOwnerID":1676219068 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":357019424 + }, + "TargetPinAddress":{ + "PinID":3, + "PinOwnerID":1676219068 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":4, + "PinOwnerID":1676219068 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":419497571 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays":[ + { + "SubChipID":2003682645, + "Position":{ + "x":-0.125, + "y":0.0 + }, + "Scale":1.0 + }, + { + "SubChipID":637918487, + "Position":{ + "x":-0.375, + "y":0.0 + }, + "Scale":0.9999999 + }, + { + "SubChipID":1080690296, + "Position":{ + "x":0.125, + "y":0.0 + }, + "Scale":1.0 + }, + { + "SubChipID":357019424, + "Position":{ + "x":0.375, + "y":0.0 + }, + "Scale":1.0 + } + ] +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/AMFsdf.json b/TestData/Projects/MainTest/Chips/AMFsdf.json new file mode 100644 index 00000000..97484b2f --- /dev/null +++ b/TestData/Projects/MainTest/Chips/AMFsdf.json @@ -0,0 +1,33 @@ +{ + "Name": "AMFsdf", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.025, + "y": 0.375 + }, + "Colour": { + "r": 0.739096463, + "g": 0.53381747, + "b": 0.150521308, + "a": 1 + }, + "InputPins":[], + "OutputPins":[], + "SubChips":[ + { + "Name":"SDFS", + "ID":1021676828, + "Label":"", + "Position":{ + "x":-2.02778, + "y":-1.61806 + }, + "OutputPinColourInfo":[], + "InternalData":null + } + ], + "Wires":[], + "Displays": null, + "Notes": null +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/BUTTON_test.json b/TestData/Projects/MainTest/Chips/BUTTON_test.json new file mode 100644 index 00000000..c13a79a6 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/BUTTON_test.json @@ -0,0 +1,201 @@ +{ + "DLSVersion": "2.1.6", + "Name": "BUTTON_test", + "NameLocation": 2, + "ChipType": 0, + "Size": { + "x": 1.775, + "y": 1.0 + }, + "Colour": { + "r": 0.182485923, + "g": 0.182485923, + "b": 0.182485923, + "a": 1 + }, + "InputPins":[], + "OutputPins":[ + { + "Name":"OUT", + "ID":1550572376, + "Position":{ + "x":-7.75, + "y":3.125 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"OUT", + "ID":1499665694, + "Position":{ + "x":-7.75, + "y":2.375 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"OUT", + "ID":587226579, + "Position":{ + "x":-7.75, + "y":1.5625 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"OUT", + "ID":462089182, + "Position":{ + "x":-7.75, + "y":0.8125 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "SubChips":[ + { + "Name":"BUTTON", + "ID":31096828, + "Label":"A", + "Position":{ + "x":-9.1975, + "y":3.125 + }, + "OutputPinColourInfo":[{"PinColour":1,"PinID":0}], + "InternalData":[1] + }, + { + "Name":"BUTTON", + "ID":1194019505, + "Label":"B", + "Position":{ + "x":-9.1975, + "y":2.375 + }, + "OutputPinColourInfo":[{"PinColour":5,"PinID":0}], + "InternalData":[5] + }, + { + "Name":"BUTTON", + "ID":402429062, + "Label":"C", + "Position":{ + "x":-9.1975, + "y":1.5625 + }, + "OutputPinColourInfo":[{"PinColour":7,"PinID":0}], + "InternalData":[7] + }, + { + "Name":"BUTTON", + "ID":795484620, + "Label":"D", + "Position":{ + "x":-9.1975, + "y":0.8125 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":0}], + "InternalData":[0] + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":31096828 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1550572376 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1194019505 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1499665694 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":402429062 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":587226579 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":795484620 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":462089182 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays":[ + { + "SubChipID":31096828, + "Position":{ + "x":-0.65625, + "y":0.28125 + }, + "Scale":1.0 + }, + { + "SubChipID":1194019505, + "Position":{ + "x":-0.28125, + "y":0.28125 + }, + "Scale":1.0 + }, + { + "SubChipID":402429062, + "Position":{ + "x":0.09375, + "y":0.28125 + }, + "Scale":1.0 + }, + { + "SubChipID":795484620, + "Position":{ + "x":0.46875, + "y":0.28125 + }, + "Scale":1.0 + } + ] +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/BUTTON_test2.json b/TestData/Projects/MainTest/Chips/BUTTON_test2.json new file mode 100644 index 00000000..a5d5f48b --- /dev/null +++ b/TestData/Projects/MainTest/Chips/BUTTON_test2.json @@ -0,0 +1,144 @@ +{ + "DLSVersion": "2.1.6", + "Name": "BUTTON_test2", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 3.45, + "y": 1.5 + }, + "Colour": { + "r": 0.295296669, + "g": 0.143883049, + "b": 0.09285392, + "a": 1 + }, + "InputPins":[], + "OutputPins":[ + { + "Name":"OUT", + "ID":893831017, + "Position":{ + "x":-5.125, + "y":2.6875 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"OUT", + "ID":1274421634, + "Position":{ + "x":-5.125, + "y":2.25 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"OUT", + "ID":970537214, + "Position":{ + "x":-5.125, + "y":1.75 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"OUT", + "ID":2132483942, + "Position":{ + "x":-5.125, + "y":1.25 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "SubChips":[ + { + "Name":"BUTTON_test", + "ID":579194441, + "Label":"", + "Position":{ + "x":-7.5225, + "y":2.30401 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":1550572376},{"PinColour":0,"PinID":1499665694},{"PinColour":0,"PinID":587226579},{"PinColour":0,"PinID":462089182}], + "InternalData":null + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":1550572376, + "PinOwnerID":579194441 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":893831017 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":1499665694, + "PinOwnerID":579194441 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1274421634 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":587226579, + "PinOwnerID":579194441 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":970537214 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":462089182, + "PinOwnerID":579194441 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":2132483942 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays":[ + { + "SubChipID":579194441, + "Position":{ + "x":-0.625, + "y":0.1087 + }, + "Scale":1.30434787 + } + ] +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/BuzzTest.json b/TestData/Projects/MainTest/Chips/BuzzTest.json index 40fc6f42..93244f4d 100644 --- a/TestData/Projects/MainTest/Chips/BuzzTest.json +++ b/TestData/Projects/MainTest/Chips/BuzzTest.json @@ -1,5 +1,5 @@ { - "DLSVersion": "2.1.5", + "DLSVersion": "2.1.6", "Name": "BuzzTest", "NameLocation": 0, "ChipType": 0, @@ -21,7 +21,7 @@ "x":-3.76235, "y":0.65436 }, - "BitCount":8, + "BitCount":"8", "Colour":0, "ValueDisplayMode":0 }, @@ -32,7 +32,7 @@ "x":-3.84573, "y":-0.12514 }, - "BitCount":4, + "BitCount":"4", "Colour":0, "ValueDisplayMode":0 } diff --git a/TestData/Projects/MainTest/Chips/ConstTest.json b/TestData/Projects/MainTest/Chips/ConstTest.json new file mode 100644 index 00000000..3a902037 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/ConstTest.json @@ -0,0 +1,60 @@ +{ + "DLSVersion": "2.1.6", + "Name": "ConstTest", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.475, + "y": 1.0 + }, + "Colour": { + "r": 0.214282215, + "g": 0.17916204, + "b": 0.250567675, + "a": 1 + }, + "InputPins":[], + "OutputPins":[ + { + "Name":"OUT", + "ID":635027160, + "Position":{ + "x":0.625, + "y":1.8125 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "SubChips":[ + { + "Name":"CONST", + "ID":608381212, + "Label":"", + "Position":{ + "x":-1.51, + "y":1.8125 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":0}], + "InternalData":[241] + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":608381212 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":635027160 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays": null +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/EEPROM_test.json b/TestData/Projects/MainTest/Chips/EEPROM_test.json new file mode 100644 index 00000000..a9fc5561 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/EEPROM_test.json @@ -0,0 +1,211 @@ +{ + "DLSVersion": "2.1.6", + "Name": "EEPROM_test", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.775, + "y": 2.0 + }, + "Colour": { + "r": 0.806846559, + "g": 0.617581964, + "b": 0.627907336, + "a": 1 + }, + "InputPins":[ + { + "Name":"IN", + "ID":544654036, + "Position":{ + "x":-4.0, + "y":0.5625 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"IN", + "ID":1542575473, + "Position":{ + "x":-4.0, + "y":0.0625 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"IN", + "ID":551362643, + "Position":{ + "x":-4.0, + "y":-0.4375 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"IN", + "ID":524361546, + "Position":{ + "x":-3.125, + "y":-0.9375 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"IN", + "ID":420837064, + "Position":{ + "x":-3.125, + "y":-1.5 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "OutputPins":[ + { + "Name":"OUT", + "ID":310978193, + "Position":{ + "x":1.34104, + "y":0.58048 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"OUT", + "ID":837524785, + "Position":{ + "x":1.375, + "y":-0.9375 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "SubChips":[ + { + "Name":"EEPROM 256×16", + "ID":129549989, + "Label":"", + "Position":{ + "x":-1.13688, + "y":-0.19005 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":5},{"PinColour":0,"PinID":6}], + "InternalData":[816,890,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1] + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":524361546 + }, + "TargetPinAddress":{ + "PinID":3, + "PinOwnerID":129549989 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":5, + "PinOwnerID":129549989 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":310978193 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":544654036 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":129549989 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1542575473 + }, + "TargetPinAddress":{ + "PinID":1, + "PinOwnerID":129549989 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":551362643 + }, + "TargetPinAddress":{ + "PinID":2, + "PinOwnerID":129549989 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":420837064 + }, + "TargetPinAddress":{ + "PinID":4, + "PinOwnerID":129549989 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":6, + "PinOwnerID":129549989 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":837524785 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays": null +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/FLIP-FLOP.json b/TestData/Projects/MainTest/Chips/FLIP-FLOP.json index 5f43b6dd..2990888a 100644 --- a/TestData/Projects/MainTest/Chips/FLIP-FLOP.json +++ b/TestData/Projects/MainTest/Chips/FLIP-FLOP.json @@ -1,6 +1,8 @@ { + "DLSVersion": "2.1.6", "Name": "FLIP-FLOP", "NameLocation": 0, + "ChipType": 0, "Size": { "x": 1.59, "y": 0.56 @@ -31,7 +33,7 @@ "y":-0.1875 }, "BitCount":1, - "Colour":5, + "Colour":6, "ValueDisplayMode":0 } ], @@ -49,28 +51,6 @@ } ], "SubChips":[ - { - "Name":"D-LATCH", - "ID":1435657029, - "Label":null, - "Position":{ - "x":-3.22, - "y":0.25 - }, - "OutputPinColourInfo":[{"PinColour":0,"PinID":1677203907}], - "InternalData":null - }, - { - "Name":"D-LATCH", - "ID":1262867679, - "Label":null, - "Position":{ - "x":-0.97, - "y":-0.4375 - }, - "OutputPinColourInfo":[{"PinColour":0,"PinID":1677203907}], - "InternalData":null - }, { "Name":"NOT", "ID":687615599, @@ -84,20 +64,6 @@ } ], "Wires":[ - { - "SourcePinAddress":{ - "PinID":1677203907, - "PinOwnerID":1262867679 - }, - "TargetPinAddress":{ - "PinID":0, - "PinOwnerID":1920302974 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":0, @@ -111,64 +77,7 @@ "ConnectedWireIndex":-1, "ConnectedWireSegmentIndex":-1, "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":0, - "PinOwnerID":1732460994 - }, - "TargetPinAddress":{ - "PinID":1709633590, - "PinOwnerID":1435657029 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":2001814538, - "PinOwnerID":687615599 - }, - "TargetPinAddress":{ - "PinID":1820713789, - "PinOwnerID":1435657029 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":-4.25,"y":-0.1875},{"x":-4.25,"y":0.125},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":0, - "PinOwnerID":2143575788 - }, - "TargetPinAddress":{ - "PinID":1820713789, - "PinOwnerID":1262867679 - }, - "ConnectionType":1, - "ConnectedWireIndex":1, - "ConnectedWireSegmentIndex":0, - "Points":[{"x":-5.96018,"y":-0.1875},{"x":-5.96018,"y":-0.5625},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":1677203907, - "PinOwnerID":1435657029 - }, - "TargetPinAddress":{ - "PinID":1709633590, - "PinOwnerID":1262867679 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":-2.0,"y":0.25},{"x":-2.0,"y":-0.3125},{"x":0.0,"y":0.0}] } ], - "Displays":[], - "ChipType": 0 + "Displays":[] } \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/MEM-1.json b/TestData/Projects/MainTest/Chips/MEM-1.json index a09046ed..2411a68d 100644 --- a/TestData/Projects/MainTest/Chips/MEM-1.json +++ b/TestData/Projects/MainTest/Chips/MEM-1.json @@ -1,6 +1,8 @@ { + "DLSVersion": "2.1.6", "Name": "MEM-1", "NameLocation": 0, + "ChipType": 0, "Size": { "x": 1.015, "y": 1.0 @@ -31,7 +33,7 @@ "y":-0.5 }, "BitCount":1, - "Colour":1, + "Colour":2, "ValueDisplayMode":0 }, { @@ -42,7 +44,7 @@ "y":-1.375 }, "BitCount":1, - "Colour":2, + "Colour":3, "ValueDisplayMode":0 }, { @@ -53,7 +55,7 @@ "y":-2.0 }, "BitCount":1, - "Colour":3, + "Colour":4, "ValueDisplayMode":0 } ], @@ -71,17 +73,6 @@ } ], "SubChips":[ - { - "Name":"D-LATCH", - "ID":782972032, - "Label":null, - "Position":{ - "x":-0.72, - "y":0.0 - }, - "OutputPinColourInfo":[{"PinColour":0,"PinID":1677203907}], - "InternalData":null - }, { "Name":"AND", "ID":936394086, @@ -90,7 +81,7 @@ "x":-4.015, "y":-1.5 }, - "OutputPinColourInfo":[{"PinColour":4,"PinID":1580367471}], + "OutputPinColourInfo":[{"PinColour":5,"PinID":1580367471}], "InternalData":null }, { @@ -112,7 +103,7 @@ "x":-2.64, "y":-0.625 }, - "OutputPinColourInfo":[{"PinColour":1,"PinID":1580367471}], + "OutputPinColourInfo":[{"PinColour":2,"PinID":1580367471}], "InternalData":null } ], @@ -145,34 +136,6 @@ "ConnectedWireSegmentIndex":-1, "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":1677203907, - "PinOwnerID":782972032 - }, - "TargetPinAddress":{ - "PinID":0, - "PinOwnerID":364195663 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":0, - "PinOwnerID":1453644181 - }, - "TargetPinAddress":{ - "PinID":1709633590, - "PinOwnerID":782972032 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":0, @@ -201,20 +164,6 @@ "ConnectedWireSegmentIndex":-1, "Points":[{"x":0.0,"y":0.0},{"x":0.25,"y":-1.5},{"x":0.25,"y":-0.375},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":1580367471, - "PinOwnerID":957057383 - }, - "TargetPinAddress":{ - "PinID":1820713789, - "PinOwnerID":782972032 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":-1.875,"y":-0.625},{"x":-1.875,"y":-0.125},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":1580367471, @@ -225,7 +174,7 @@ "PinOwnerID":957057383 }, "ConnectionType":1, - "ConnectedWireIndex":5, + "ConnectedWireIndex":3, "ConnectedWireSegmentIndex":0, "Points":[{"x":-3.25,"y":-1.5},{"x":-3.25,"y":-0.75},{"x":0.0,"y":0.0}] }, @@ -244,6 +193,5 @@ "Points":[{"x":0.0,"y":0.0},{"x":-4.75,"y":-2.0},{"x":-4.75,"y":-1.625},{"x":0.0,"y":0.0}] } ], - "Displays":[], - "ChipType": 0 + "Displays":[] } \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/RTCTest.json b/TestData/Projects/MainTest/Chips/RTCTest.json new file mode 100644 index 00000000..93127ad3 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/RTCTest.json @@ -0,0 +1,135 @@ +{ + "DLSVersion": "2.1.6", + "Name": "RTCTest", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.175, + "y": 2.0 + }, + "Colour": { + "r": 0.8187522, + "g": 0.385453254, + "b": 0.334759057, + "a": 1 + }, + "InputPins":[], + "OutputPins":[ + { + "Name":"OUT", + "ID":1492030497, + "Position":{ + "x":1.25, + "y":1.0 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":1 + }, + { + "Name":"OUT", + "ID":900692905, + "Position":{ + "x":1.25, + "y":0.5 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":1 + }, + { + "Name":"OUT", + "ID":1767071873, + "Position":{ + "x":1.25, + "y":0.0 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":1 + }, + { + "Name":"OUT", + "ID":1820587991, + "Position":{ + "x":1.25, + "y":-0.5 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":1 + } + ], + "SubChips":[ + { + "Name":"RTC", + "ID":2097162199, + "Label":"", + "Position":{ + "x":-1.0, + "y":0.25 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":3},{"PinColour":0,"PinID":2},{"PinColour":0,"PinID":1},{"PinColour":0,"PinID":0}], + "InternalData":null + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":3, + "PinOwnerID":2097162199 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1492030497 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":2097162199 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":900692905 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":1, + "PinOwnerID":2097162199 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1767071873 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":2097162199 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1820587991 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays": null +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/SPSTest.json b/TestData/Projects/MainTest/Chips/SPSTest.json new file mode 100644 index 00000000..e38b63a1 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/SPSTest.json @@ -0,0 +1,144 @@ +{ + "DLSVersion": "2.1.6", + "Name": "SPSTest", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.175, + "y": 3.5 + }, + "Colour": { + "r": 0.376221627, + "g": 0.949174643, + "b": 0.4645198, + "a": 1 + }, + "InputPins":[], + "OutputPins":[ + { + "Name":"OUT", + "ID":1947288272, + "Position":{ + "x":1.1525, + "y":0.4375 + }, + "BitCount":"16", + "Colour":0, + "ValueDisplayMode":1, + "face":1, + "LocalOffset":0.0 + }, + { + "Name":"OUT", + "ID":307217692, + "Position":{ + "x":1.1525, + "y":-0.5 + }, + "BitCount":"16", + "Colour":0, + "ValueDisplayMode":1, + "face":1, + "LocalOffset":0.0 + }, + { + "Name":"OUT", + "ID":917260170, + "Position":{ + "x":1.125, + "y":-1.3125 + }, + "BitCount":"1", + "Colour":0, + "ValueDisplayMode":0, + "face":1, + "LocalOffset":0.0 + }, + { + "Name":"OUT", + "ID":2003410447, + "Position":{ + "x":1.125, + "y":-1.8125 + }, + "BitCount":"1", + "Colour":0, + "ValueDisplayMode":0, + "face":1, + "LocalOffset":0.0 + } + ], + "SubChips":[ + { + "Name":"SPS", + "ID":1380022051, + "Label":"", + "Position":{ + "x":-1.25, + "y":-0.3125 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":3},{"PinColour":0,"PinID":2},{"PinColour":0,"PinID":1},{"PinColour":0,"PinID":0}], + "InternalData":null + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":1, + "PinOwnerID":1380022051 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":917260170 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1380022051 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":2003410447 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":1380022051 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":307217692 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":3, + "PinOwnerID":1380022051 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1947288272 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays": null, + "HasCustomLayout": false +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/StatsTest.json b/TestData/Projects/MainTest/Chips/StatsTest.json new file mode 100644 index 00000000..48be5c87 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/StatsTest.json @@ -0,0 +1,44 @@ +{ + "DLSVersion": "2.1.6", + "Name": "StatsTest", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.475, + "y": 0.375 + }, + "Colour": { + "r": 0.122551017, + "g": 0.9483913, + "b": 0.0456536263, + "a": 1 + }, + "InputPins":[], + "OutputPins":[], + "SubChips":[ + { + "Name":"#AA", + "ID":1839347277, + "Label":"", + "Position":{ + "x":-1.125, + "y":-0.625 + }, + "OutputPinColourInfo":[], + "InternalData":null + }, + { + "Name":"#AA", + "ID":1590622850, + "Label":"", + "Position":{ + "x":1.32377, + "y":-1.9697 + }, + "OutputPinColourInfo":[], + "InternalData":null + } + ], + "Wires":[], + "Displays": null +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/StatsTest1.json b/TestData/Projects/MainTest/Chips/StatsTest1.json new file mode 100644 index 00000000..f117d373 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/StatsTest1.json @@ -0,0 +1,44 @@ +{ + "DLSVersion": "2.1.6", + "Name": "StatsTest1", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.625, + "y": 0.375 + }, + "Colour": { + "r": 0.249502838, + "g": 0.0543793626, + "b": 0.182806984, + "a": 1 + }, + "InputPins":[], + "OutputPins":[], + "SubChips":[ + { + "Name":"StatsTest", + "ID":1008107898, + "Label":"", + "Position":{ + "x":-0.5, + "y":1.25 + }, + "OutputPinColourInfo":[], + "InternalData":null + }, + { + "Name":"StatsTest", + "ID":930092738, + "Label":"", + "Position":{ + "x":-0.45249, + "y":-0.02033 + }, + "OutputPinColourInfo":[], + "InternalData":null + } + ], + "Wires":[], + "Displays": null +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/StatsTest2.json b/TestData/Projects/MainTest/Chips/StatsTest2.json new file mode 100644 index 00000000..3cabca04 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/StatsTest2.json @@ -0,0 +1,66 @@ +{ + "DLSVersion": "2.1.6", + "Name": "StatsTest2", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.625, + "y": 0.375 + }, + "Colour": { + "r": 0.7356529, + "g": 0.03489643, + "b": 0.3001187, + "a": 1 + }, + "InputPins":[], + "OutputPins":[], + "SubChips":[ + { + "Name":"StatsTest1", + "ID":56785596, + "Label":"", + "Position":{ + "x":-2.5, + "y":0.75 + }, + "OutputPinColourInfo":[], + "InternalData":null + }, + { + "Name":"StatsTest1", + "ID":92195362, + "Label":"", + "Position":{ + "x":-0.5, + "y":-0.625 + }, + "OutputPinColourInfo":[], + "InternalData":null + }, + { + "Name":"StatsTest1", + "ID":1416875230, + "Label":"", + "Position":{ + "x":1.5, + "y":2.3125 + }, + "OutputPinColourInfo":[], + "InternalData":null + }, + { + "Name":"StatsTest1", + "ID":19961094, + "Label":"", + "Position":{ + "x":-2.375, + "y":-1.5 + }, + "OutputPinColourInfo":[], + "InternalData":null + } + ], + "Wires":[], + "Displays": null +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/Chips/WIP2.json b/TestData/Projects/MainTest/Chips/WIP2.json index f6d2cb43..333d73ef 100644 --- a/TestData/Projects/MainTest/Chips/WIP2.json +++ b/TestData/Projects/MainTest/Chips/WIP2.json @@ -1,4 +1,5 @@ { + "DLSVersion": "2.1.6", "Name": "WIP2", "NameLocation": 0, "ChipType": 0, @@ -20,7 +21,7 @@ "x":-6.07331, "y":-1.66224 }, - "BitCount":1, + "BitCount":"1", "Colour":0, "ValueDisplayMode":0 } @@ -33,7 +34,7 @@ "x":-1.50295, "y":7.39811 }, - "BitCount":8, + "BitCount":"8", "Colour":0, "ValueDisplayMode":1 }, @@ -44,7 +45,7 @@ "x":1.6467, "y":2.99376 }, - "BitCount":8, + "BitCount":"8", "Colour":0, "ValueDisplayMode":1 }, @@ -55,7 +56,7 @@ "x":4.11785, "y":-0.46966 }, - "BitCount":8, + "BitCount":"8", "Colour":0, "ValueDisplayMode":1 } @@ -105,17 +106,6 @@ "OutputPinColourInfo":[{"PinColour":0,"PinID":128796038},{"PinColour":0,"PinID":632432616}], "InternalData":null }, - { - "Name":"dev.RAM-8", - "ID":247733947, - "Label":null, - "Position":{ - "x":7.47288, - "y":5.30698 - }, - "OutputPinColourInfo":[{"PinColour":0,"PinID":5}], - "InternalData":null - }, { "Name":"ALU-8", "ID":301021643, @@ -190,7 +180,7 @@ "x":-9.20893, "y":5.13451 }, - "OutputPinColourInfo":[{"PinColour":2,"PinID":1151713402},{"PinColour":2,"PinID":349361804},{"PinColour":2,"PinID":883528113},{"PinColour":2,"PinID":1590314579},{"PinColour":2,"PinID":1175090400},{"PinColour":2,"PinID":805153383},{"PinColour":2,"PinID":1401872117},{"PinColour":2,"PinID":1118858130},{"PinColour":2,"PinID":742048505},{"PinColour":2,"PinID":1476868706},{"PinColour":2,"PinID":87848670},{"PinColour":0,"PinID":288562873},{"PinColour":0,"PinID":1148046748},{"PinColour":0,"PinID":1249996143},{"PinColour":0,"PinID":1157119205},{"PinColour":0,"PinID":845055315},{"PinColour":0,"PinID":895153575}], + "OutputPinColourInfo":[{"PinColour":3,"PinID":1151713402},{"PinColour":3,"PinID":349361804},{"PinColour":3,"PinID":883528113},{"PinColour":3,"PinID":1590314579},{"PinColour":3,"PinID":1175090400},{"PinColour":3,"PinID":805153383},{"PinColour":3,"PinID":1401872117},{"PinColour":3,"PinID":1118858130},{"PinColour":3,"PinID":742048505},{"PinColour":3,"PinID":1476868706},{"PinColour":3,"PinID":87848670},{"PinColour":0,"PinID":288562873},{"PinColour":0,"PinID":1148046748},{"PinColour":0,"PinID":1249996143},{"PinColour":0,"PinID":1157119205},{"PinColour":0,"PinID":845055315},{"PinColour":0,"PinID":895153575}], "InternalData":null }, { @@ -314,17 +304,6 @@ "OutputPinColourInfo":[{"PinColour":0,"PinID":1222614200}], "InternalData":null }, - { - "Name":"8-4BIT", - "ID":657752229, - "Label":null, - "Position":{ - "x":7.0552, - "y":-0.62319 - }, - "OutputPinColourInfo":[{"PinColour":0,"PinID":1},{"PinColour":0,"PinID":2}], - "InternalData":null - }, { "Name":"REGISTER-1", "ID":602363710, @@ -336,17 +315,6 @@ "OutputPinColourInfo":[{"PinColour":0,"PinID":1938418986}], "InternalData":null }, - { - "Name":"8-4BIT", - "ID":1049240007, - "Label":null, - "Position":{ - "x":7.0925, - "y":-1.44374 - }, - "OutputPinColourInfo":[{"PinColour":0,"PinID":1},{"PinColour":0,"PinID":2}], - "InternalData":null - }, { "Name":"CLOCK", "ID":719592481, @@ -423,17 +391,6 @@ }, "OutputPinColourInfo":[{"PinColour":0,"PinID":1454394007}], "InternalData":null - }, - { - "Name":"4-8BIT", - "ID":769198515, - "Label":"", - "Position":{ - "x":9.07547, - "y":-0.88058 - }, - "OutputPinColourInfo":[{"PinColour":0,"PinID":2}], - "InternalData":null } ], "Wires":[ @@ -563,20 +520,6 @@ "ConnectedWireSegmentIndex":-1, "Points":[{"x":0.0,"y":0.0},{"x":1.23784,"y":5.88339},{"x":1.2602,"y":5.01362},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":5, - "PinOwnerID":247733947 - }, - "TargetPinAddress":{ - "PinID":581914641, - "PinOwnerID":153832190 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":158720235, @@ -657,38 +600,10 @@ "PinOwnerID":1255067638 }, "ConnectionType":1, - "ConnectedWireIndex":12, + "ConnectedWireIndex":11, "ConnectedWireSegmentIndex":0, "Points":[{"x":-7.14197,"y":-0.34843},{"x":-7.32057,"y":2.72294},{"x":-5.72798,"y":2.72294},{"x":-5.72798,"y":6.37857},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":1580367471, - "PinOwnerID":2092373440 - }, - "TargetPinAddress":{ - "PinID":4, - "PinOwnerID":247733947 - }, - "ConnectionType":1, - "ConnectedWireIndex":15, - "ConnectedWireSegmentIndex":2, - "Points":[{"x":-5.72798,"y":5.10441},{"x":-0.72235,"y":5.10441},{"x":-0.72235,"y":4.33081},{"x":5.52711,"y":4.33081},{"x":5.52711,"y":4.58868},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":1580367471, - "PinOwnerID":2092373440 - }, - "TargetPinAddress":{ - "PinID":1027672421, - "PinOwnerID":609414526 - }, - "ConnectionType":1, - "ConnectedWireIndex":16, - "ConnectedWireSegmentIndex":0, - "Points":[{"x":-3.13415,"y":5.10441},{"x":-3.13415,"y":5.33194},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":605802866, @@ -703,20 +618,6 @@ "ConnectedWireSegmentIndex":-1, "Points":[{"x":0.0,"y":0.0},{"x":8.0906,"y":0.63864},{"x":8.0906,"y":-2.26752},{"x":-10.70327,"y":-2.26752},{"x":-10.70327,"y":4.54317},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":1, - "PinOwnerID":76195595 - }, - "TargetPinAddress":{ - "PinID":1, - "PinOwnerID":247733947 - }, - "ConnectionType":1, - "ConnectedWireIndex":4, - "ConnectedWireSegmentIndex":0, - "Points":[{"x":5.05626,"y":3.98817},{"x":5.05627,"y":5.40778},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":158720235, @@ -755,7 +656,7 @@ "PinOwnerID":1237918361 }, "ConnectionType":1, - "ConnectedWireIndex":21, + "ConnectedWireIndex":17, "ConnectedWireSegmentIndex":0, "Points":[{"x":-1.89033,"y":0.5235},{"x":0.0,"y":0.0}] }, @@ -811,7 +712,7 @@ "PinOwnerID":1805959142 }, "ConnectionType":1, - "ConnectedWireIndex":25, + "ConnectedWireIndex":21, "ConnectedWireSegmentIndex":0, "Points":[{"x":-9.97518,"y":1.88111},{"x":-9.97518,"y":0.08361},{"x":0.0,"y":0.0}] }, @@ -839,7 +740,7 @@ "PinOwnerID":1867709155 }, "ConnectionType":1, - "ConnectedWireIndex":27, + "ConnectedWireIndex":23, "ConnectedWireSegmentIndex":2, "Points":[{"x":-5.10695,"y":1.25578},{"x":-5.10695,"y":4.79626},{"x":0.0,"y":0.0}] }, @@ -853,7 +754,7 @@ "PinOwnerID":687931053 }, "ConnectionType":1, - "ConnectedWireIndex":12, + "ConnectedWireIndex":11, "ConnectedWireSegmentIndex":0, "Points":[{"x":-7.44021,"y":-0.3515},{"x":-7.4402,"y":-1.17057},{"x":4.71352,"y":-1.17057},{"x":4.71352,"y":0.27946},{"x":0.0,"y":0.0}] }, @@ -867,7 +768,7 @@ "PinOwnerID":2007842624 }, "ConnectionType":1, - "ConnectedWireIndex":29, + "ConnectedWireIndex":25, "ConnectedWireSegmentIndex":1, "Points":[{"x":-1.54796,"y":-1.17057},{"x":-1.54796,"y":1.16265},{"x":0.0,"y":0.0}] }, @@ -881,7 +782,7 @@ "PinOwnerID":1237918361 }, "ConnectionType":1, - "ConnectedWireIndex":30, + "ConnectedWireIndex":26, "ConnectedWireSegmentIndex":0, "Points":[{"x":-1.54796,"y":-0.55101},{"x":0.0,"y":0.0}] }, @@ -895,7 +796,7 @@ "PinOwnerID":1255067638 }, "ConnectionType":1, - "ConnectedWireIndex":25, + "ConnectedWireIndex":21, "ConnectedWireSegmentIndex":2, "Points":[{"x":-6.06223,"y":0.86},{"x":-6.00002,"y":2.45656},{"x":-5.28537,"y":2.45656},{"x":-5.28537,"y":5.55538},{"x":0.0,"y":0.0}] }, @@ -909,7 +810,7 @@ "PinOwnerID":1255067638 }, "ConnectionType":1, - "ConnectedWireIndex":28, + "ConnectedWireIndex":24, "ConnectedWireSegmentIndex":1, "Points":[{"x":-4.93394,"y":4.78826},{"x":-4.93398,"y":5.94273},{"x":0.0,"y":0.0}] }, @@ -965,38 +866,10 @@ "PinOwnerID":602363710 }, "ConnectionType":1, - "ConnectedWireIndex":29, + "ConnectedWireIndex":25, "ConnectedWireSegmentIndex":1, "Points":[{"x":-6.53977,"y":-1.17057},{"x":-6.53977,"y":-3.23403},{"x":8.24873,"y":-3.23403},{"x":8.24873,"y":0.62628},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":128796038, - "PinOwnerID":2007842624 - }, - "TargetPinAddress":{ - "PinID":0, - "PinOwnerID":657752229 - }, - "ConnectionType":1, - "ConnectedWireIndex":6, - "ConnectedWireSegmentIndex":0, - "Points":[{"x":1.6297,"y":2.29716},{"x":1.62996,"y":2.95738},{"x":5.22762,"y":2.95738},{"x":5.22762,"y":-0.60454},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":128796038, - "PinOwnerID":1237918361 - }, - "TargetPinAddress":{ - "PinID":0, - "PinOwnerID":1049240007 - }, - "ConnectionType":1, - "ConnectedWireIndex":34, - "ConnectedWireSegmentIndex":2, - "Points":[{"x":2.37536,"y":-0.44751},{"x":2.37532,"y":-1.42509},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":0, @@ -1081,34 +954,6 @@ "ConnectedWireSegmentIndex":0, "Points":[{"x":4.36891,"y":3.98071},{"x":4.36892,"y":7.03436},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":128796038, - "PinOwnerID":1137372670 - }, - "TargetPinAddress":{ - "PinID":0, - "PinOwnerID":247733947 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":6.51219,"y":6.99579},{"x":6.51219,"y":5.85893},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":1580367471, - "PinOwnerID":2092373440 - }, - "TargetPinAddress":{ - "PinID":1027672421, - "PinOwnerID":1137372670 - }, - "ConnectionType":1, - "ConnectedWireIndex":16, - "ConnectedWireSegmentIndex":2, - "Points":[{"x":4.69719,"y":4.33081},{"x":4.69719,"y":5.91079},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":1151713402, @@ -1165,20 +1010,6 @@ "ConnectedWireSegmentIndex":-1, "Points":[{"x":0.0,"y":0.0},{"x":-6.88907,"y":6.57451},{"x":-6.88907,"y":8.98738},{"x":11.72848,"y":8.98738},{"x":11.72848,"y":1.91077},{"x":8.02446,"y":1.91077},{"x":8.02446,"y":0.93603},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":805153383, - "PinOwnerID":518660003 - }, - "TargetPinAddress":{ - "PinID":2, - "PinOwnerID":247733947 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":-6.08978,"y":6.25451},{"x":-6.08978,"y":3.45086},{"x":6.48437,"y":3.45086},{"x":6.48437,"y":5.04943},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":1401872117, @@ -1357,7 +1188,7 @@ "PinOwnerID":687931053 }, "ConnectionType":1, - "ConnectedWireIndex":60, + "ConnectedWireIndex":51, "ConnectedWireSegmentIndex":4, "Points":[{"x":3.21705,"y":0.11463},{"x":3.22904,"y":-0.08855},{"x":6.1216,"y":-0.06494},{"x":6.1098,"y":0.57261},{"x":0.0,"y":0.0}] }, @@ -1455,7 +1286,7 @@ "PinOwnerID":2085911674 }, "ConnectionType":1, - "ConnectedWireIndex":25, + "ConnectedWireIndex":21, "ConnectedWireSegmentIndex":2, "Points":[{"x":-6.93894,"y":0.86458},{"x":-6.93908,"y":-3.70234},{"x":9.74005,"y":-3.60917},{"x":9.77077,"y":-2.85651},{"x":0.0,"y":0.0}] }, @@ -1473,20 +1304,6 @@ "ConnectedWireSegmentIndex":-1, "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] }, - { - "SourcePinAddress":{ - "PinID":2, - "PinOwnerID":769198515 - }, - "TargetPinAddress":{ - "PinID":0, - "PinOwnerID":2085911674 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, { "SourcePinAddress":{ "PinID":1938418986, @@ -1511,37 +1328,9 @@ "PinOwnerID":2085911674 }, "ConnectionType":1, - "ConnectedWireIndex":37, + "ConnectedWireIndex":33, "ConnectedWireSegmentIndex":1, "Points":[{"x":8.22771,"y":-3.23403},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":2, - "PinOwnerID":657752229 - }, - "TargetPinAddress":{ - "PinID":1, - "PinOwnerID":769198515 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] - }, - { - "SourcePinAddress":{ - "PinID":2, - "PinOwnerID":1049240007 - }, - "TargetPinAddress":{ - "PinID":0, - "PinOwnerID":769198515 - }, - "ConnectionType":0, - "ConnectedWireIndex":-1, - "ConnectedWireSegmentIndex":-1, - "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] } ], "Displays": null diff --git a/TestData/Projects/MainTest/Deleted Chips/D-LATCH.json b/TestData/Projects/MainTest/Deleted Chips/D-LATCH.json new file mode 100644 index 00000000..a25899ac --- /dev/null +++ b/TestData/Projects/MainTest/Deleted Chips/D-LATCH.json @@ -0,0 +1,266 @@ +{ + "ChipType": 0, + "Colour": { + "r": 0.8017309, + "g": 0.425566971, + "b": 0.11274536, + "a": 1 + }, + "Displays":[], + "InputPins":[ + { + "Name":"DATA", + "ID":1709633590, + "Position":{ + "x":-6.375, + "y":0.5 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + }, + { + "Name":"STORE", + "ID":1820713789, + "Position":{ + "x":-6.375, + "y":-0.25 + }, + "BitCount":1, + "Colour":1, + "ValueDisplayMode":0 + } + ], + "Name": "D-LATCH", + "NameLocation": 0, + "OutputPins":[ + { + "Name":"OUT", + "ID":1677203907, + "Position":{ + "x":2.375, + "y":0.125 + }, + "BitCount":1, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "Size": { + "x": 1.29, + "y": 0.5 + }, + "SubChips":[ + { + "Name":"NAND", + "ID":114480724, + "Label":"", + "Position":{ + "x":-0.125, + "y":0.875 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":2}], + "InternalData":null + }, + { + "Name":"NAND", + "ID":242969089, + "Label":"", + "Position":{ + "x":-0.125, + "y":-0.6875 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":2}], + "InternalData":null + }, + { + "Name":"NAND", + "ID":630167622, + "Label":"", + "Position":{ + "x":-2.125, + "y":1.0 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":2}], + "InternalData":null + }, + { + "Name":"NAND", + "ID":736914670, + "Label":"", + "Position":{ + "x":-2.25, + "y":-0.8125 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":2}], + "InternalData":null + }, + { + "Name":"NAND", + "ID":1748511812, + "Label":"", + "Position":{ + "x":-3.75, + "y":-0.9375 + }, + "OutputPinColourInfo":[{"PinColour":0,"PinID":2}], + "InternalData":null + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":114480724 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1677203907 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.75,"y":0.875},{"x":0.75,"y":0.125},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":242969089 + }, + "TargetPinAddress":{ + "PinID":1, + "PinOwnerID":114480724 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.75,"y":-0.6875},{"x":0.75,"y":-0.375},{"x":-0.9679,"y":0.57151},{"x":-0.9679,"y":0.75},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":114480724 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":242969089 + }, + "ConnectionType":1, + "ConnectedWireIndex":0, + "ConnectedWireSegmentIndex":1, + "Points":[{"x":0.75,"y":0.36127},{"x":-0.94879,"y":-0.25672},{"x":-0.94879,"y":-0.5625},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":630167622 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":114480724 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":736914670 + }, + "TargetPinAddress":{ + "PinID":1, + "PinOwnerID":242969089 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":2, + "PinOwnerID":1748511812 + }, + "TargetPinAddress":{ + "PinID":1, + "PinOwnerID":736914670 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1709633590 + }, + "TargetPinAddress":{ + "PinID":1, + "PinOwnerID":1748511812 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":-4.5,"y":0.5},{"x":-4.5,"y":-1.0625},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1709633590 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1748511812 + }, + "ConnectionType":1, + "ConnectedWireIndex":6, + "ConnectedWireSegmentIndex":1, + "Points":[{"x":-4.5,"y":-0.8125},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1820713789 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":736914670 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":-2.9375,"y":-0.25},{"x":-2.9375,"y":-0.6875},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1820713789 + }, + "TargetPinAddress":{ + "PinID":1, + "PinOwnerID":630167622 + }, + "ConnectionType":1, + "ConnectedWireIndex":8, + "ConnectedWireSegmentIndex":0, + "Points":[{"x":-2.9375,"y":-0.25},{"x":-2.9375,"y":0.875},{"x":0.0,"y":0.0}] + }, + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":1709633590 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":630167622 + }, + "ConnectionType":1, + "ConnectedWireIndex":6, + "ConnectedWireSegmentIndex":1, + "Points":[{"x":-4.5,"y":0.49506},{"x":-4.5,"y":1.125},{"x":0.0,"y":0.0}] + } + ] +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/ProjectDescription.json b/TestData/Projects/MainTest/ProjectDescription.json index 6d1823ff..1da83914 100644 --- a/TestData/Projects/MainTest/ProjectDescription.json +++ b/TestData/Projects/MainTest/ProjectDescription.json @@ -1,20 +1,25 @@ { "ProjectName": "MainTest", - "DLSVersion_LastSaved": "2.1.5", + "DLSVersion_LastSaved": "2.1.6", "DLSVersion_EarliestCompatible": "2.0.0", + "DLSVersion_LastSavedModdedVersion": "1.1.1", "CreationTime": "2025-03-14T18:23:30.404+01:00", - "LastSaveTime": "2025-05-04T09:15:41.061+02:00", + "LastSaveTime": "2025-06-19T16:58:18.880+02:00", "Prefs_MainPinNamesDisplayMode": 2, "Prefs_ChipPinNamesDisplayMode": 1, "Prefs_GridDisplayMode": 1, "Prefs_Snapping": 0, "Prefs_StraightWires": 0, "Prefs_SimPaused": false, - "Prefs_SimTargetStepsPerSecond": 150, + "Prefs_SimTargetStepsPerSecond": 15, "Prefs_SimStepsPerClockTick": 6, + "Perfs_PinIndicators": 4, + "StepsRanSinceCreated": 105863230, + "TimeSpentSinceCreated": { + "StartFrom": "02:50:15.0946759" + }, "AllCustomChipNames":[ "AND", - "D-LATCH", "NOT", "OR", "NOR", @@ -62,7 +67,19 @@ "RAM-sync", "TEST MergeSplit", "#", - "BuzzTest" + "BuzzTest", + "#AA", + "StatsTest", + "StatsTest1", + "StatsTest2", + "SPSTest", + "EEPROM_test", + "BUTTON_test", + "BUTTON_test2", + "4sw", + "ConstTest", + "_", + "IndicatorTest" ], "StarredList":[ { @@ -96,53 +113,124 @@ { "Name":"BuzzTest", "IsCollection":false + }, + { + "Name":"_", + "IsCollection":false + }, + { + "Name":"IndicatorTest", + "IsCollection":false } ], "ChipCollections":[ { "Chips":["NAND","AND","NOT","NOR","XOR","OR","KEY","CLOCK","3-STATE BUFFER"], - "IsToggledOpen":false, + "IsToggledOpen":true, "Name":"BASICS" }, { - "Chips":["IN-1","IN-4","IN-8","OUT-1","OUT-4","OUT-8"], - "IsToggledOpen":false, + "Chips":["IN-1","IN-4","IN-8","OUT-1","OUT-4","OUT-8","IN-16","OUT-16","IN-32","OUT-32","IN-128","OUT-128","IN-2","OUT-2","IN-6","OUT-6","IN-5","OUT-5","IN-7","OUT-7","IN-9","OUT-9","IN-31","OUT-31","IN-37","OUT-37","IN-12","OUT-12","IN-13","OUT-13","IN-21","OUT-21","IN-23","OUT-23","IN-17","OUT-17","IN-56","OUT-56"], + "IsToggledOpen":true, "Name":"IN/OUT" }, { - "Chips":["1-4BIT","1-8BIT","4-8BIT","8-4BIT","8-1BIT","4-1BIT"], - "IsToggledOpen":false, + "Chips":["32-16BIT","32-8BIT","32-4BIT","32-1BIT","16-32BIT","16-8BIT","16-4BIT","16-1BIT","8-32BIT","8-16BIT","8-4BIT","8-1BIT","4-32BIT","4-16BIT","4-8BIT","4-1BIT","1-32BIT","1-16BIT","1-8BIT","1-4BIT"], + "IsToggledOpen":true, "Name":"MERGE/SPLIT" }, { "Chips":["7-SEGMENT","DECIMAL-4","DECIMAL-8","DOT DISPLAY","RGB DISPLAY","LED"], - "IsToggledOpen":false, + "IsToggledOpen":true, "Name":"DISPLAY" }, { - "Chips":["BUS-1","BUS-4","BUS-8"], - "IsToggledOpen":false, + "Chips":["BUS-1","BUS-4","BUS-8","BUS-16","BUS-32","BUS-128","BUS-2","BUS-6","BUS-5","BUS-7","BUS-9","BUS-31","BUS-37","BUS-12","BUS-13","BUS-21","BUS-23","BUS-17","BUS-56"], + "IsToggledOpen":true, "Name":"BUS" }, { "Chips":["REGISTER-4","REG-8"], - "IsToggledOpen":false, + "IsToggledOpen":true, "Name":"MEMORY" }, { "Chips":["D-LATCH","FLIP-FLOP","OR-8","MEM-1","NOT-8","AND(8,1)","MUX-8","PC","BUF-8","ALU-8","DECODE-3","AND-3","CONTROL UNIT","TOGGLE","FLAGS","DISP-7","demo","7-SEGMENT DRIVER","DABBLE","LSB","LSHIFT-8","DOUBLE DABBLE","ALU","BUS BUFFER","MEM-256","REGISTER-8","XNOR","EQUALS-8","ADDER-4","DECODER-2","ADDER-8","ADDER","MEM-16","REGISTER-1","AND-8","RAM-256×8 (async)","ROM 256×16"], - "IsToggledOpen":false, + "IsToggledOpen":true, "Name":"KEEP" }, { - "Chips":["WIP2","RAM-sync"], - "IsToggledOpen":false, + "Chips":["WIP2","RAM-sync","CONST"], + "IsToggledOpen":true, "Name":"TEST" }, { - "Chips":["PULSE","TEST MergeSplit"], + "Chips":["PULSE","TEST MergeSplit","BUZZER","SPS","#","BuzzTest","SPSTest","#AA","StatsTest","StatsTest1","StatsTest2","EEPROM 256×16","EEPROM_test","BUTTON","BUTTON_test","BUTTON_test2","DIPSWITCH","4sw","RAM-8","DETECTOR","RTC","ConstTest"], "IsToggledOpen":true, "Name":"OTHER" } + ], + "pinBitCounts":[ + "1", + "4", + "8", + "16", + "32", + "128", + "2", + "6", + "5", + "7", + "9", + "31", + "37", + "12", + "13", + "21", + "23", + "17", + "56" + ], + "SplitMergePairs":[ + { + "Key":"32", + "Value":"16" + }, + { + "Key":"32", + "Value":"8" + }, + { + "Key":"32", + "Value":"4" + }, + { + "Key":"32", + "Value":"1" + }, + { + "Key":"16", + "Value":"8" + }, + { + "Key":"16", + "Value":"4" + }, + { + "Key":"16", + "Value":"1" + }, + { + "Key":"8", + "Value":"4" + }, + { + "Key":"8", + "Value":"1" + }, + { + "Key":"4", + "Value":"1" + } ] } \ No newline at end of file