diff --git a/.github/workflows/dotnet-manual.yml b/.github/workflows/dotnet-manual.yml new file mode 100644 index 0000000..abe8f29 --- /dev/null +++ b/.github/workflows/dotnet-manual.yml @@ -0,0 +1,52 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET (Manual Build) +permissions: + contents: read + +on: + workflow_dispatch: # Manual trigger only + inputs: + branch: + description: 'Branch to build' + required: false + default: '' + type: string + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch || github.ref_name }} + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Install Mono + run: | + sudo apt-get update + sudo apt-get install -y mono-devel + + - name: Run Configure setup + shell: pwsh + run: | + Write-Host "Running Configure.ps1..." + echo "1" | pwsh ./Configure.ps1 setup -Verbose + + - name: Restore dependencies + working-directory: CatalystUI + run: dotnet restore + + - name: Build + working-directory: CatalystUI + run: dotnet build --no-restore + + - name: Test + working-directory: CatalystUI + run: dotnet test --no-build --verbosity normal diff --git a/.scripts/Setup.ps1 b/.scripts/Setup.ps1 index 4bfa893..ee5587a 100644 --- a/.scripts/Setup.ps1 +++ b/.scripts/Setup.ps1 @@ -45,12 +45,32 @@ $projectsList = @( Projects = @( @{ Folder = "Core"; Name = "CatalystUI.Attributes" }, @{ Folder = "Core"; Name = "CatalystUI.Collections" }, + @{ Folder = "Core"; Name = "CatalystUI.Mathematics" }, @{ Folder = "Core"; Name = "CatalystUI.Threading" }, @{ Folder = "Tooling"; Name = "CatalystUI.Analyzers" }, @{ Folder = "Tooling"; Name = "CatalystUI.CodeFix" }, - @{ Folder = "Core"; Name = "CatalystUI.Core" } + @{ Folder = "Core"; Name = "CatalystUI.Core" }, + @{ Folder = "Core"; Name = "CatalystUI.Debug" }, + @{ Folder = "Core"; Name = "CatalystUI.Supplementary" } ) - PromptIgnore = $true + PromptIgnore = $false + Depends = @() + }, + @{ + Module = "Arcane" + Projects = @( + @{ Folder = "Modules/Arcane"; Name = "CatalystUI.Modules.Arcane.Core" } + ) + PromptIgnore = $false + Depends = @("Core") + }, + @{ + Module = "Arcane.Ini" + Project = @( + @{ Folder = "Modules/Arcane"; Name = "CatalystUI.Modules.Arcane.Ini" } + ) + PromptIgnore = $false + Depends = @("Arcane") } ) @@ -58,8 +78,7 @@ $projectsList = @( $promptOptions = @("All") + ( $projectsList | Where-Object { -not $_.PromptIgnore -and $_.Module -ne "All" } | - Select-Object -ExpandProperty Module -Unique | - Sort-Object + Select-Object -ExpandProperty Module -Unique ) # Prompt user for module selection diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index 956b7d8..0b50ed1 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -18,6 +18,24 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Attributes", "Co EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.CodeFix", "Tooling\CatalystUI.CodeFix\CatalystUI.CodeFix.csproj", "{E5319DB6-E93C-4A7D-9B3B-F219206BBC54}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Debug", "Core\CatalystUI.Debug\CatalystUI.Debug.csproj", "{39CD2850-CB5B-4F3C-81AB-9430506F3BD7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Supplementary", "Core\CatalystUI.Supplementary\CatalystUI.Supplementary.csproj", "{33B1D211-9C3A-4AA8-95DE-75D9122CC968}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Mathematics", "Core\CatalystUI.Mathematics\CatalystUI.Mathematics.csproj", "{E2B3A13C-9AE6-44D8-8456-58723ABBC343}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{9C3F6A00-82F5-4900-9D6C-07ACBBAAE823}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Crystal", "Crystal", "{41BEF490-7005-4D10-9958-22D636F9DE38}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Arcane", "Arcane", "{C8B02B42-826B-4EDE-B72F-F4F97C1A088D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Veilstone", "Veilstone", "{0E1F2B64-37D9-4C24-9CED-9A44D7CDBB8C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Modules.Arcane.Ini", "Modules\Arcane\CatalystUI.Modules.Arcane.Ini\CatalystUI.Modules.Arcane.Ini.csproj", "{C02600D7-087B-4190-9B47-F15184C19B2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Modules.Arcane.Core", "Modules\Arcane\CatalystUI.Modules.Arcane.Core\CatalystUI.Modules.Arcane.Core.csproj", "{047744B0-87DC-4808-99C4-5AC1F8A1EB4D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,6 +70,26 @@ Global {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Release|Any CPU.Build.0 = Release|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Release|Any CPU.Build.0 = Release|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Release|Any CPU.Build.0 = Release|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Release|Any CPU.Build.0 = Release|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Release|Any CPU.Build.0 = Release|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} @@ -61,5 +99,13 @@ Global {A3936CB7-DC31-414B-9E40-CB9436391068} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} {44E8E3D2-FE47-49EA-A397-EB680E33AA2E} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {E5319DB6-E93C-4A7D-9B3B-F219206BBC54} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {33B1D211-9C3A-4AA8-95DE-75D9122CC968} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {E2B3A13C-9AE6-44D8-8456-58723ABBC343} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {41BEF490-7005-4D10-9958-22D636F9DE38} = {9C3F6A00-82F5-4900-9D6C-07ACBBAAE823} + {C8B02B42-826B-4EDE-B72F-F4F97C1A088D} = {9C3F6A00-82F5-4900-9D6C-07ACBBAAE823} + {0E1F2B64-37D9-4C24-9CED-9A44D7CDBB8C} = {9C3F6A00-82F5-4900-9D6C-07ACBBAAE823} + {C02600D7-087B-4190-9B47-F15184C19B2D} = {C8B02B42-826B-4EDE-B72F-F4F97C1A088D} + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D} = {C8B02B42-826B-4EDE-B72F-F4F97C1A088D} EndGlobalSection EndGlobal diff --git a/CatalystUI/Core/CatalystUI.Core/Builders/CatalystAppBuilder.cs b/CatalystUI/Core/CatalystUI.Core/Builders/CatalystAppBuilder.cs new file mode 100644 index 0000000..5216e92 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Builders/CatalystAppBuilder.cs @@ -0,0 +1,146 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Connectors; +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace Catalyst.Builders { + + /// + /// Builder class for creating a Catalyst application. + /// + public sealed class CatalystAppBuilder { + + /// + /// Constructs a new . + /// + public CatalystAppBuilder() { + // ... + } + + /// + /// Makes a layer available for use in the CatalystUI application. + /// + /// + /// + /// The added layer is registered with the , + /// which allows it to be used globally across the application. + /// + /// + /// Layers are the building blocks of the CatalystUI application, + /// providing a way to organize and manage different parts of the application. + /// Inserting a layer into the application allows it to add additional functionality + /// such as data handling, UI components, or other features. To connect the functionality + /// of multiple layers, you can use connectors. + /// + /// + /// + /// The layer to add. + /// The type of the layer to add. + /// The current instance of the . + public CatalystAppBuilder AddLayer(TLayer layer) where TLayer : ILayer { + ModelRegistry.RegisterLayer(layer); + return this; + } + + /// + /// Makes a connector available for use in the CatalystUI application. + /// + /// + /// + /// The added connector is registered with the , + /// which allows it to be used globally across the application. + /// + /// + /// Connectors allow different layers to communicate with each other, + /// and for their underlying domains to interact seamlessly. To add + /// new functionality to the application, you can insert a layer + /// and connect it to existing layers using connectors. + /// + /// + /// + /// The connector to add. + /// The type of the connector to add. + /// The current instance of the . + public CatalystAppBuilder AddConnector(TConnector connector) where TConnector : IConnector, ILayer> { + ModelRegistry.RegisterConnector(connector); + return this; + } + + /// + /// Builds the CatalystUI Application. + /// + /// + /// + /// Building a Catalyst application will capture the thread on which it is called. + /// This thread should be the main thread of the application to ensure proper operation. + /// Failure to do so may result in unexpected behavior and occasional crashes. + /// + /// + /// The resulting run method will be executed on a separate thread. If the caller needs + /// to perform work on the main thread, it should use the + /// to schedule work on the main thread. + /// + /// + /// The application will not exit until the run method completes or the method is called. + /// To keep an application running indefinitely, you would do as you would in a typical + /// application, such as using a loop or waiting for user input. + /// + /// + /// The method to run when the application starts. + /// A new instance of a CatalystUI application. + public void Build(Func runMethod) { + _ = new CatalystApp(runMethod); + } + + /// + public void Build(Action runMethod) { + _ = new CatalystApp(app => { + runMethod(app); + return 0; // Default exit code + }); + } + + /// + public void Build(Func runMethod) { + _ = new CatalystApp(app => { + runMethod(app).GetAwaiter().GetResult(); + return 0; // Default exit code + }); + } + + /// + public void Build(Func> runMethod) { + _ = new CatalystApp(app => runMethod(app).GetAwaiter().GetResult()); + } + + /// + /// When building a Catalyst application, it is recommended to use the overloads that accept a or as the run method, + /// which allows the application to capture the main thread and prevent issues during asynchronous operations. + /// This method is provided for advanced use cases only and should be used with caution, + /// as some functionality may cause unexpected behavior if the main thread is not captured correctly. + /// MacOS and Linux seem to be particularly sensitive to this, as they require the main thread to be + /// captured for proper system-level operations and UI interactions. + /// + /// + [Obsolete("Use Build(Action) or Build(Func) whenever possible. This method is provided for advanced use cases only.")] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public void Build() { + _ = new CatalystApp(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Builders/Extensions/CatalystAppBuilderExtensions.cs b/CatalystUI/Core/CatalystUI.Core/Builders/Extensions/CatalystAppBuilderExtensions.cs new file mode 100644 index 0000000..784d544 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Builders/Extensions/CatalystAppBuilderExtensions.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Builders.Extensions { + + /// + /// The following class doesn't provide any functionality itself, + /// but it provides the namespace for extension methods, + /// so conditional compilation doesn't yell at end-users + /// if they need it during a debug build but not a release build. + /// + public static class CatalystAppBuilderExtensions { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystApp.cs b/CatalystUI/Core/CatalystUI.Core/CatalystApp.cs new file mode 100644 index 0000000..c58b100 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/CatalystApp.cs @@ -0,0 +1,109 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Debugging; +using Catalyst.Threading; +using System; +using System.Threading; + +namespace Catalyst { + + /// + /// An application utilizing the CatalystUI framework. + /// + public class CatalystApp { + + /// + /// The dispatcher used to handle threading operations in the CatalystUI framework. + /// + /// + /// In most situations, the dispatcher will be the one used to capture the main thread of the application. + /// A and associated should + /// always be created on the main thread of the application. Failure to do so may result in + /// unexpected behavior and occasional crashes. + /// + public ThreadDelegateDispatcher Dispatcher { get; private set; } = null!; // set in the captured main thread + + /// + /// The debug context for the application. + /// + protected readonly DebugContext _debug; + + /// + /// The exit code of the application. + /// + private volatile int _exitCode; + + /// + /// A flag indicating whether the application is exiting. + /// + private volatile bool _isExiting; + + /// + /// A lock used to ensure thread safety when performing Catalyst + /// synchronizational operations, such as exiting the application. + /// + private readonly Lock _lock; + + /// + /// Constructs a new . + /// This method will not return. + /// + internal CatalystApp(Func? runMethod = null) { + // Fields + _debug = CatalystDebug.ForContext("Application"); + _exitCode = 0; + _isExiting = false; + _lock = new(); + + // Properties + _debug.Log(LogLevel.Verbose, "Capturing main thread dispatcher and running application..."); + ThreadDelegateDispatcher.Capture(dispatcher => { + // Assign the main-thread dispatcher to the app. + Dispatcher = dispatcher; + Thread.CurrentThread.Name = "MainThread"; + _debug.Log(LogLevel.Verbose, "Main thread dispatcher captured successfully."); + + // Run the caller's method if provided. + if (runMethod != null) { + _debug.Log(LogLevel.Info, "CatalystApp initialized, initializing worker thread. Application will run until the provided run method exits."); + ThreadDelegateDispatcher workerThread = ThreadDelegateDispatcher.New("CallerThread"); + workerThread.Execute(() => { + Exit(runMethod(this)); + }); + } else { + _debug.Log(LogLevel.Info, "CatalystApp initialized. No run method provided, application will run until exit."); + } + }, isMainThread: true); + + // Set the exit code and let the application return gracefully. + Environment.ExitCode = _exitCode; + } + + /// + /// Kills the main-thread dispatcher and exits the application with the specified exit code. + /// + /// The exit code to use when exiting the application. + public void Exit(int code = 0) { + if (_isExiting) return; + _lock.Enter(); + try { + _exitCode = code; + Dispatcher.Dispose(); + _isExiting = true; + } finally { + _lock.Exit(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj index 339ec99..f4acb4e 100644 --- a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj +++ b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj @@ -19,8 +19,9 @@ - + + diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs index e4d4f8b..d3dec4b 100644 --- a/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs @@ -317,12 +317,12 @@ public static LogLevel DetermineScopeLevel(string scope) { level = parsedResult.Value; _enabledScopes.TryAdd(scope, level); } else { - Trace.WriteLine($"⚠️ Scope '{scope}' is not defined in the configuration file. Using default level: {DebugOptions.MinimumLogLevel}"); + Trace.WriteLine($"⚠️ [Catalyst.Debugging] Scope '{scope}' is not defined in the configuration file. Using default level: {DebugOptions.MinimumLogLevel}"); } // Scope output if (DebugOptions.MinimumLogLevel >= LogLevel.Debug) { - Trace.WriteLine($"ℹ️ Determined scope '{scope}' level: {level}"); + Trace.WriteLine($"ℹ️ [Catalyst.Debugging] Determined scope '{scope}' level: {level}"); } } return level; @@ -362,7 +362,7 @@ private static void PreloadScopes() { /// A function that constructs a new . public static void InjectDebugContext(Func constructor) { if (_debugContextConstructor != null) { - Trace.WriteLine("⚠️ A debug constructor has already been set. The previous constructor will be replaced with the new one."); + Trace.WriteLine("⚠️ [Catalyst.Debugging] A debug constructor has already been set. The previous constructor will be replaced with the new one."); Trace.WriteLine(constructor.GetType().GetGenericArguments()[1].FullName); } _debugContextConstructor = constructor; diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/IInputData.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/IInputData.cs new file mode 100644 index 0000000..eb1b390 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/IInputData.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Interactions { + + /// + /// Represents the data passed from the input device to the interaction. + /// + public interface IInputData { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs index b5a1d22..2fb777f 100644 --- a/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs @@ -22,6 +22,12 @@ public interface IInteraction { /// The input device, or if the origin is unknown. IInputDevice? InputDevice { get; } + /// + /// Gets the input data associated with the interaction. + /// + /// The input data, or if no data is associated. + IInputData? InputData { get; } + } } \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/ModelRegistry.cs b/CatalystUI/Core/CatalystUI.Core/ModelRegistry.cs new file mode 100644 index 0000000..e1f987b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/ModelRegistry.cs @@ -0,0 +1,272 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Connectors; +using Catalyst.Debugging; +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Linq; + +using LayerSet = System.Collections.Generic.HashSet>; +using LayerMap = System.Collections.Generic.Dictionary>>; +using DomainMap = System.Collections.Generic.Dictionary>>>; + +using ConnectorSet = System.Collections.Generic.HashSet, Catalyst.Layers.ILayer>>; +using LowLayerConnectorMap = System.Collections.Generic.Dictionary, Catalyst.Layers.ILayer>>>; +using HighLayerLowLayerMap = System.Collections.Generic.Dictionary, Catalyst.Layers.ILayer>>>>; + +namespace Catalyst { + + /// + /// Provides registration services for implementations of the CatalystUI model in .NET Core applications. + /// + public static class ModelRegistry { + + /// + /// A dictionary that maps domain types to their corresponding layers. + /// + private static readonly DomainMap _layers; + + /// + /// A dictionary that maps two layer types to their corresponding connector. + /// + private static readonly HighLayerLowLayerMap _connectors; + + /// + /// The debug context for the model registry. + /// + private static readonly DebugContext _debug; + + /// + /// A lock used for thread-safety. + /// + private static readonly Lock _lock; + + /// + /// Static constructor for . + /// + static ModelRegistry() { + _layers = []; + _connectors = []; + _debug = CatalystDebug.ForContext("ModelRegistry"); + _lock = new(); + } + + /// + /// Registers a layer instance with the model registry. + /// + /// The layer instance to register. + /// The type of the layer instance being registered. + /// Thrown if is null. + /// Thrown if a layer of the same type is already registered for the same domain. + public static void RegisterLayer(TLayer layer) where TLayer : ILayer { + if (layer == null) throw new ArgumentNullException(nameof(layer), "Layer cannot be null."); + _lock.Enter(); + try { + Type domainType = TLayer.Domain; + Type layerType = typeof(TLayer); + + // Ensure the layer map for the domain exists + if (!_layers.TryGetValue(domainType, out LayerMap? layerMap)) { + layerMap = []; + _layers[domainType] = layerMap; + } + + // Ensure the layer instances set for the layer type exists + if (!layerMap.TryGetValue(layerType, out LayerSet? layerSet)) { + layerSet = []; + layerMap[layerType] = layerSet; + } + + // Register the layer instance + if (!layerSet.Add(layer)) { + throw new InvalidOperationException($"The specified layer of type {layerType.FullName} is already registered for domain {domainType.FullName}."); + } + _debug.Log(LogLevel.Info, $"Registered layer of type {layerType.FullName} for domain {domainType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Registers a connector instance with the model registry. + /// + /// The connector instance to register. + /// The type of the connector instance being registered. + /// Thrown if is null. + /// Thrown if a connector of the same type is already registered between the same layers. + public static void RegisterConnector(TConnector connector) where TConnector : IConnector, ILayer> { + if (connector == null) throw new ArgumentNullException(nameof(connector), "Connector cannot be null."); + _lock.Enter(); + try { + Type highLayerType = TConnector.HighLayer; + Type lowLayerType = TConnector.LowLayer; + + // Ensure the low layer map for the high layer exists + if (!_connectors.TryGetValue(highLayerType, out LowLayerConnectorMap? lowLayerMap)) { + lowLayerMap = []; + _connectors[highLayerType] = lowLayerMap; + } + + // Ensure the connector set for the low layer type exists + if (!lowLayerMap.TryGetValue(lowLayerType, out ConnectorSet? connectorSet)) { + connectorSet = []; + lowLayerMap[lowLayerType] = connectorSet; + } + + // Register the connector instance + if (!connectorSet.Add(connector)) { + throw new InvalidOperationException($"The specified connector of type {typeof(TConnector).FullName} is already registered between layers {highLayerType.FullName} and {lowLayerType.FullName}."); + } + _debug.Log(LogLevel.Info, $"Registered connector of type {typeof(TConnector).FullName} between layers {highLayerType.FullName} and {lowLayerType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Requests for the first registered layer instance of the specified type. + /// + /// + /// When multiple instances of the same layer type are registered, returns the first instance found. + /// + /// The type of layer being requested. + /// The first registered instance of the specified layer type. + /// Thrown if no layer of the specified type is registered. + public static TLayer RequestLayer() where TLayer : ILayer { + _lock.Enter(); + try { + Type domainType = TLayer.Domain; + Type layerType = typeof(TLayer); + + // First, check for an exact type match and return the first instance found + if (_layers.TryGetValue(domainType, out LayerMap? layerMap)) { + if (layerMap.TryGetValue(layerType, out LayerSet? layerSet) && layerSet.Count > 0) { + return (TLayer) layerSet.First(); + } + } + + // Otherwise, search for assignable types and return the first instance found + foreach (KeyValuePair kvp in _layers) { + if (domainType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (layerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + return (TLayer) innerKvp.Value.First(); + } + } + } + } + + // If no layer is found, throw an exception + throw new InvalidOperationException($"No registered layer of type {layerType.FullName} found for domain {domainType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Requests all registered layer instances of the specified type. + /// + /// The type of layer being requested. + /// An enumerable for all registered instances of the specified layer type. + public static IEnumerable RequestLayers() where TLayer : ILayer { + _lock.Enter(); + try { + Type domainType = TLayer.Domain; + Type layerType = typeof(TLayer); + foreach (KeyValuePair kvp in _layers) { + if (domainType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (layerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + foreach (ILayer layer in innerKvp.Value) { + yield return (TLayer) layer; + } + } + } + } + } + } finally { + _lock.Exit(); + } + } + + /// + /// Requests for the first registered connector instance of the specified type. + /// + /// + /// When multiple instances of the same connector type are registered, returns the first instance found. + /// + /// The type of connector being requested. + /// The first registered instance of the specified connector type. + /// Thrown if no connector of the specified type is registered. + public static TConnector RequestConnector() where TConnector : IConnector, ILayer> { + _lock.Enter(); + try { + Type highLayerType = TConnector.HighLayer; + Type lowLayerType = TConnector.LowLayer; + + // First, check for an exact type match and return the first instance found + if (_connectors.TryGetValue(highLayerType, out LowLayerConnectorMap? lowLayerMap)) { + if (lowLayerMap.TryGetValue(lowLayerType, out ConnectorSet? connectorSet) && connectorSet.Count > 0) { + return (TConnector) connectorSet.First(); + } + } + + // Otherwise, search for assignable types and return the first instance found + foreach (KeyValuePair kvp in _connectors) { + if (highLayerType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (lowLayerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + return (TConnector) innerKvp.Value.First(); + } + } + } + } + + // If no connector is found, throw an exception + throw new InvalidOperationException($"No registered connector of type {typeof(TConnector).FullName} found between layers {highLayerType.FullName} and {lowLayerType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Requests all registered connector instances of the specified type. + /// + /// The type of connector being requested. + /// An enumerable for all registered instances of the specified connector type. + public static IEnumerable RequestConnectors() where TConnector : IConnector, ILayer> { + _lock.Enter(); + try { + Type highLayerType = TConnector.HighLayer; + Type lowLayerType = TConnector.LowLayer; + foreach (KeyValuePair kvp in _connectors) { + if (highLayerType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (lowLayerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + foreach (IConnector, ILayer> connector in innerKvp.Value) { + yield return (TConnector) connector; + } + } + } + } + } + } finally { + _lock.Exit(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Native/INativeApi.cs b/CatalystUI/Core/CatalystUI.Core/Native/INativeApi.cs new file mode 100644 index 0000000..6ad2be3 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Native/INativeApi.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Catalyst.Native { + + /// + /// Represents a wrapper for an API which provides native access using the Singleton pattern. + /// + /// A reference to the type of the API instance which is generated. + /// A reference to the type of the wrapped API instance, if applicable. + public interface INativeApi : IDisposable where TSelf : INativeApi { + + /// + /// Gets the API instance which is being wrapped. + /// + /// The wrapped API instance. + TApi Api { get; } + + /// + /// Requests an instance of the API. + /// + /// The instance of the API. + static abstract TSelf GetInstance(); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Native/NativeException.cs b/CatalystUI/Core/CatalystUI.Core/Native/NativeException.cs new file mode 100644 index 0000000..25b52f6 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Native/NativeException.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Catalyst.Native { + + /// + /// Represents an error that occurs during native operations. + /// + /// + public class NativeException : Exception { + + /// + /// Initializes a new instance of the class. + /// + public NativeException() : base() { + // ... + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + public NativeException(string message) : base(message) { + // ... + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception + /// that is the cause of this exception. + /// + public NativeException(string message, Exception innerException) : base(message, innerException) { + // ... + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Debug/CatalystSerilogDebug.cs b/CatalystUI/Core/CatalystUI.Debug/CatalystSerilogDebug.cs new file mode 100644 index 0000000..3dc2b16 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/CatalystSerilogDebug.cs @@ -0,0 +1,165 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Serilog; +using Serilog.Events; +using Serilog.Templates; +using System; +using System.Diagnostics; +using System.IO; + +namespace Catalyst.Debugging { + + /// + /// Serilog utilities for debugging within the CatalystUI.Debug framework. + /// + public static class CatalystSerilogDebug { + + /// + /// Gets the current logger instance for debugging purposes. + /// + private static ILogger? _logger; + + /// + /// Static constructor for . + /// + static CatalystSerilogDebug() { + // Configure the logger + ConfigureLogger(); + } + + /// + /// Configures the global logger for debugging. + /// + private static void ConfigureLogger() { + LoggerConfiguration config = new(); + + // Apply requested minimum level to the logger configuration + switch (CatalystDebug.DebugOptions.MinimumLogLevel) { + case LogLevel.Critical: + config.MinimumLevel.Fatal(); + break; + case LogLevel.Error: + config.MinimumLevel.Error(); + break; + case LogLevel.Warning: + config.MinimumLevel.Warning(); + break; + case LogLevel.Info: + config.MinimumLevel.Information(); + break; + case LogLevel.Debug: + config.MinimumLevel.Debug(); + break; + case LogLevel.Verbose: + config.MinimumLevel.Verbose(); + break; + case LogLevel.None: + default: + config.MinimumLevel.Debug(); + break; + } + + // Add an async sink + config.WriteTo.Async(writeTo => { + // Create the logging template + string levelmap = @" + if @l = 'Verbose' then 'VERBOSE' + else if @l = 'Debug' then 'DEBUG' + else if @l = 'Information' then 'INFO' + else if @l = 'Warning' then 'WARN' + else if @l = 'Error' then 'ERROR' + else if @l = 'Fatal' then 'CRITICAL' + else 'UNKNOWN' + "; + string thread = $@" + if ThreadName is not null then ThreadName + else if ThreadId = {Environment.CurrentManagedThreadId} then 'MainThread' + else Concat('Thread ', ToString(ThreadId)) + "; + string threadTemplate = CatalystDebug.DebugOptions.ShowsThread ? "<{" + thread + "}> " : string.Empty; + string template = threadTemplate + "[{SourceContext}] [{@t:HH:mm:ss:fff}] [{" + levelmap + "}] {@m}{if @x is not null then '" + Environment.NewLine + "' + @x else ''}"; + ExpressionTemplate formatter = new(template + Environment.NewLine); + + // Add a debug sink + writeTo.Debug(formatter); + + // Add a file sink + try { + if (File.Exists(CatalystDebug.OUTPUT_FILE_NAME)) + File.Delete(CatalystDebug.OUTPUT_FILE_NAME); + } catch { + // eh + } + writeTo.File( + path: CatalystDebug.OUTPUT_FILE_NAME, + retainedFileCountLimit: 1, + formatter: formatter + ); + }); + + // Construct the logger + _logger = config + .Enrich.WithThreadId() + .Enrich.WithThreadName() + .CreateLogger() + .ForContext("SourceContext", ""); + Log.Logger = _logger; // Set the global logger to the configured logger + + // Log initialization + Trace.WriteLine($"ℹ️ [Catalyst.Debugging] Catalyst debugging initialized with minimum log level: {CatalystDebug.DebugOptions.MinimumLogLevel}"); + } + + /// + /// Converts a Serilog to a . + /// + /// The Serilog log event level to convert. + /// A corresponding to the provided Serilog log event level. + public static LogLevel FromLogEventLevel(LogEventLevel level) { + return level switch { + LogEventLevel.Verbose => LogLevel.Verbose, + LogEventLevel.Debug => LogLevel.Debug, + LogEventLevel.Information => LogLevel.Info, + LogEventLevel.Warning => LogLevel.Warning, + LogEventLevel.Error => LogLevel.Error, + LogEventLevel.Fatal => LogLevel.Critical, + _ => LogLevel.None + }; + } + + /// + /// Converts a to a Serilog . + /// + /// The log level to convert. + /// A Serilog corresponding to the provided log level. + public static LogEventLevel ToLogEventLevel(LogLevel logLevel) { + return logLevel switch { + LogLevel.Verbose => LogEventLevel.Verbose, + LogLevel.Debug => LogEventLevel.Debug, + LogLevel.Info => LogEventLevel.Information, + LogLevel.Warning => LogEventLevel.Warning, + LogLevel.Error => LogEventLevel.Error, + LogLevel.Critical => LogEventLevel.Fatal, + _ => LogEventLevel.Debug // Default to Debug if None + }; + } + + /// + /// Requests a logger for the specified scope. + /// + internal static (ILogger, LogLevel)? RequestLogger(string scope) { + if (_logger == null) return null; // how? + return (_logger.ForContext("SourceContext", scope), CatalystDebug.DetermineScopeLevel(scope)); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj b/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj new file mode 100644 index 0000000..e4d4cbc --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj @@ -0,0 +1,29 @@ + + + + + + Catalyst.Debugging + Catalyst.Debug + + + CatalystUI Debugging + 1.0.0 + beta.2 + CatalystUI LLC + Debugging API provided by the CatalystUI library. + CatalystUI,debug,debugging + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Debug/Extensions/CatalystAppBuilderExtensions.cs b/CatalystUI/Core/CatalystUI.Debug/Extensions/CatalystAppBuilderExtensions.cs new file mode 100644 index 0000000..012662d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/Extensions/CatalystAppBuilderExtensions.cs @@ -0,0 +1,64 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Debugging; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Builders.Extensions { + + /// + /// Builder extensions for the . + /// + public static class CatalystAppBuilderExtensions { + + /// + /// Instructs the instance to use the Catalyst.Debug + /// library for debugging operations. + /// + /// + /// + /// must be the first method called on the + /// instance, as it sets up the debug context for the entire application + /// and any subsequent calls to the methods, + /// including in various static classes and methods. + /// + /// + /// 99.99% of the time, you will want to wrap the method in #if DEBUG + /// preprocessor directives to ensure that it is only included in debug builds. + /// The same is true for the inclusion of the Catalyst.Debug library itself. + /// Failure to do so will result in a large amount of bloat added to the + /// executable built for release, as well as a significant performance + /// impact due to the overhead of the debug logging system. + /// + /// + /// + /// + /// public static void Main(string[] args) { + /// new CatalystAppBuilder() + /// #if DEBUG + /// .UseCatalystDebug() + /// #endif + /// .AddArcaneIniModule() + /// .AddCrystalGlfw3Module() + /// .Build(Run); + /// } + /// + /// + /// The instance. + /// The modified instance. + public static CatalystAppBuilder UseCatalystDebug(this CatalystAppBuilder builder) { + CatalystDebug.InjectDebugContext(scope => new SerilogDebugContext(scope)); + return builder; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Debug/SerilogDebugContext.cs b/CatalystUI/Core/CatalystUI.Debug/SerilogDebugContext.cs new file mode 100644 index 0000000..875c2ef --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/SerilogDebugContext.cs @@ -0,0 +1,155 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Serilog; +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace Catalyst.Debugging { + + /// + /// A debug context for Serilog integration within the CatalystUI framework. + /// + public sealed class SerilogDebugContext : DebugContext { + + /// + /// Gets the Serilog logger instance for the debug context. + /// + /// A Serilog instance, or if not provided. + public ILogger? Logger { get; } + + /// + public SerilogDebugContext(string scope) : base(scope) { + (ILogger Logger, LogLevel LogLevel)? result = CatalystSerilogDebug.RequestLogger(scope); + if (result == null) { + Logger = null; + } else { + Logger = result.Value.Logger; + Level = result.Value.LogLevel; + } + } + + /// + public override void Log(LogLevel level, string message, string? prefix = null, params object[] args) { + if (Level < level) return; + if (Logger == null) return; + StringBuilder sb = new(); + string? stackTrace = null; + try { + bool needsStackTrace = CatalystDebug.DebugOptions.ShowsFileName || CatalystDebug.DebugOptions.ShowsMethodName || CatalystDebug.DebugOptions.ShowsLineNumber || CatalystDebug.DebugOptions.ShowsStackTrace; + if (needsStackTrace) { + StackTrace trace = new(fNeedFileInfo: true, skipFrames: 2); + if (CatalystDebug.DebugOptions.ShowsStackTrace) stackTrace = trace.ToString(); + StackFrame? frame = trace.GetFrame(0); + DiagnosticMethodInfo? method = frame != null ? DiagnosticMethodInfo.Create(frame) : null; + if (frame != null && method != null) { + bool needsContinuation = false; + bool needsComma = false; + if (CatalystDebug.DebugOptions.ShowsFileName) { + string? file = frame.GetFileName(); + string? typeName = method.Name; + string? assemblyName = method.DeclaringAssemblyName; + sb.Append('<'); + if (!string.IsNullOrEmpty(file)) { + sb.Append(Path.GetFileName(file)); + } else if (!string.IsNullOrEmpty(typeName)) { + sb.Append(typeName); + } else if (!string.IsNullOrEmpty(assemblyName)) { + sb.Append(assemblyName); + } else { + sb.Append("Object"); + } + needsContinuation = true; + needsComma = true; + } + if (CatalystDebug.DebugOptions.ShowsMethodName) { + if (needsComma) { + sb.Append('#'); + } else { + sb.Append('<'); + } + sb.Append(method.Name); + needsContinuation = true; + needsComma = true; + } + if (CatalystDebug.DebugOptions.ShowsLineNumber) { + if (needsComma) { + sb.Append('('); + } else { + sb.Append('<'); + } + sb.Append(frame.GetFileLineNumber()); + if (needsComma) sb.Append(')'); + needsContinuation = true; + needsComma = true; + } + if (needsContinuation) { + sb.Append('>').Append(' '); + } + } + } + } catch { + // not supported probably + } + sb.Append(message).Append(' '); + if (args.Length > 0) { + if (args[0] is Exception e) { + stackTrace = e.StackTrace; + if (e.StackTrace == null) { + stackTrace = e.ToString(); + // Remove the first two lines which are the exception message and type + stackTrace = stackTrace[(stackTrace.IndexOf('\n') + 1)..]; + stackTrace = stackTrace[(stackTrace.IndexOf('\n') + 1)..]; + } + } else { + for (int i = 0; i < args.Length; i++) { + sb.Append(args[i]?.ToString()); + if (i < args.Length - 1) { + sb.Append(' '); + } + } + } + } + if (stackTrace != null) { + sb.AppendLine().Append(stackTrace); + } + switch (level) { + case LogLevel.Critical: + Logger.Fatal(sb.ToString()); + break; + case LogLevel.Error: + Logger.Error(sb.ToString()); + break; + case LogLevel.Warning: + Logger.Warning(sb.ToString()); + break; + case LogLevel.Info: + Logger.Information(sb.ToString()); + break; + case LogLevel.Debug: + Logger.Debug(sb.ToString()); + break; + case LogLevel.Verbose: + Logger.Verbose(sb.ToString()); + break; + case LogLevel.None: + break; // No logging for None level + default: + Logger.Debug(sb.ToString()); // Fall back to Debug for unknown levels + break; + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/CatalystUI.Mathematics.csproj b/CatalystUI/Core/CatalystUI.Mathematics/CatalystUI.Mathematics.csproj new file mode 100644 index 0000000..75a29c1 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/CatalystUI.Mathematics.csproj @@ -0,0 +1,18 @@ + + + + + + Catalyst.Mathematics + Catalyst.Mathematics + + + CatalystUI Mathematics + 1.0.0 + beta.2 + CatalystUI LLC + Mathematics API provided by the CatalystUI library. + CatalystUI,math,mathematics + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Angle.cs b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Angle.cs new file mode 100644 index 0000000..0f7d5c1 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Angle.cs @@ -0,0 +1,253 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics.Geometry { + + /// + /// A programmatic representation of an angle. + /// + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Angle { + + /// + /// The underlying angle in radians. + /// + private readonly double _radians; + + /// + /// Gets the angle in degrees. + /// + /// The angle in degrees. + public double Degrees => RadiansToDegrees(_radians); + + /// + /// Gets the angle in radians. + /// + /// The angle in radians. + public double Radians => _radians; + + /// + /// Gets the angle in gradians (also known as gon). + /// + /// The angle is gradians (gon). + public double Gradians => RadiansToGradians(_radians); + + /// + /// Constructs a new . + /// + /// The angle in radians. + private Angle(double radians) { + _radians = radians; + } + + /// + /// Converts an angle from radians to an instance. + /// + /// The angle in radians. + /// An instance representing the angle in radians. + public static Angle FromRadians(double radians) { + return new(radians); + } + + /// + /// Converts an angle from degrees to an instance. + /// + /// The angle in degrees. + /// An instance representing the angle in degrees. + public static Angle FromDegrees(double degrees) { + return new(DegreesToRadians(degrees)); + } + + /// + /// Converts an angle from gradians (gon) to an instance. + /// + /// The angle in gradians (gon). + /// An instance representing the angle in gradians. + public static Angle FromGradians(double gradians) { + return new(GradiansToRadians(gradians)); + } + + /// + /// Normalizes the angle to a value between 0 and 2π radians (0 and 360 degrees). + /// + /// The normalized angle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Angle Normalize() { + return new((_radians % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI)); + } + + /// + /// Gets the quadrant of the angle. + /// + /// The quadrant of the angle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Quadrant ToQuadrant() { + return (Quadrant) ((int) (Normalize()._radians / (Math.PI / 2)) + 1); + } + + /// + /// Converts an angle from radians to degrees. + /// + /// The angle in radians. + /// The angle in degrees. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double RadiansToDegrees(double radians) { + return radians * (180.0 / Math.PI); + } + + /// + /// Converts an angle from radians to gradians (gon). + /// + /// The angle in radians. + /// The angle in gradians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double RadiansToGradians(double radians) { + return radians * (200.0 / Math.PI); + } + + /// + /// Converts an angle from degrees to radians. + /// + /// The angle in degrees. + /// The angle in radians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double DegreesToRadians(double degrees) { + return degrees * (Math.PI / 180.0); + } + + /// + /// Converts an angle from degrees to gradians (gon). + /// + /// The angle in degrees. + /// The angle in gradians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double DegreesToGradians(double degrees) { + return degrees * (10.0 / 9.0); + } + + /// + /// Converts an angle from gradians (gon) to radians. + /// + /// The angle in gradians. + /// The angle in radians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double GradiansToRadians(double gradians) { + return gradians * (Math.PI / 200.0); + } + + /// + /// Converts an angle from gradians (gon) to degrees. + /// + /// The angle in gradians. + /// The angle in degrees. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double GradiansToDegrees(double gradians) { + return gradians * (9.0 / 1.0); + } + + /// + /// Returns the value of the operand angle without modification. + /// + /// The angle to return. + /// The same angle instance. + public static Angle operator +(Angle angle) { + return angle; + } + + /// + /// Returns the negated value of the operand angle. + /// + /// The angle to negate. + /// The negated angle instance. + public static Angle operator -(Angle angle) { + return new(-angle._radians); + } + + /// + /// Increments the angle by 1 degree (approximately 0.0174533 radians). + /// + /// The angle to increment. + /// A new angle instance incremented by 1 degree. + public static Angle operator ++(Angle angle) { + return new(angle._radians + DegreesToRadians(1)); + } + + /// + /// Decrements the angle by 1 degree (approximately 0.0174533 radians). + /// + /// The angle to decrement. + /// A new angle instance decremented by 1 degree. + public static Angle operator --(Angle angle) { + return new(angle._radians - DegreesToRadians(1)); + } + + /// + /// Adds two angles together, resulting in a new angle. + /// + /// The first angle to add. + /// The second angle to add. + /// A new angle that is the sum of the two angles. + public static Angle operator +(Angle left, Angle right) { + return new(left._radians + right._radians); + } + + /// + /// Subtracts one angle from another, resulting in a new angle. + /// + /// The angle to subtract from. + /// The angle to subtract. + /// A new angle that is the difference of the two angles. + public static Angle operator -(Angle left, Angle right) { + return new(left._radians - right._radians); + } + + /// + /// Multiplies an angle by a scalar value, resulting in a new angle. + /// + /// The angle to multiply. + /// The scalar value to multiply the angle by. + /// A new angle that is the product of the angle and the scalar. + public static Angle operator *(Angle angle, double scalar) { + return new(angle._radians * scalar); + } + + /// + /// Divides an angle by a scalar value, resulting in a new angle. + /// + /// The angle to divide. + /// The scalar value to divide the angle by. + /// A new angle that is the quotient of the angle and the scalar. + public static Angle operator /(Angle angle, double scalar) { + return new(angle._radians / scalar); + } + + /// + /// Modulo operation on an angle with a scalar value, resulting in a new angle. + /// + /// The angle to apply the modulo operation on. + /// The scalar value to apply the modulo operation with. + /// A new angle that is the result of the modulo operation. + public static Angle operator %(Angle angle, double scalar) { + return new(angle._radians % scalar); + } + + /// + public override string ToString() { + return $"{Degrees:F1}°"; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Quadrant.cs b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Quadrant.cs new file mode 100644 index 0000000..0d4c7b8 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Quadrant.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Mathematics.Geometry { + + /// + /// A list of quadrants in a Cartesian coordinate system. + /// + public enum Quadrant { + + /// + /// The first quadrant (Q1) or top-right quadrant, where both x and y coordinates are positive. + /// + /// 1 + First = 1, + + /// + /// The second quadrant (Q2) or top-left quadrant, where x is negative and y is positive. + /// + /// 2 + Second = 2, + + /// + /// The third quadrant (Q3) or bottom-left quadrant, where both x and y coordinates are negative. + /// + /// 3 + Third = 3, + + /// + /// The fourth quadrant (Q4) or bottom-right quadrant, where x is positive and y is negative. + /// + /// 4 + Fourth = 4 + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Range.cs b/CatalystUI/Core/CatalystUI.Mathematics/Range.cs new file mode 100644 index 0000000..46a60db --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Range.cs @@ -0,0 +1,154 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Text; + +namespace Catalyst.Mathematics { + + /// + /// Represents a range of numeric values. + /// + /// The numeric type of the range values. + public readonly record struct Range where TNumber : struct, INumber { + + /// + /// The zero range, where both minimum and maximum are zero and inclusive. + /// + public static readonly Range ZERO = new(RangeValue.ZERO_INCLUSIVE, RangeValue.ZERO_INCLUSIVE); + + // Backing Fields + private readonly RangeValue _minimum; + private readonly RangeValue _maximum; + + /// + /// Gets the minimum value of the range. + /// + /// The range's minimum value. + public required RangeValue Minimum { + get => _minimum; + init { + _minimum = value; + if (_maximum != default) Validate(); + } + } + + /// + /// Gets the maximum value of the range. + /// + /// The range's maximum value. + public required RangeValue Maximum { + get => _maximum; + init { + _maximum = value; + if (_minimum != default) Validate(); + } + } + + /// + /// Constructs a new + /// with the specified minimum and maximum values. + /// + /// The minimum value of the range. + /// The maximum value of the range. + [SetsRequiredMembers] + public Range(RangeValue minimum, RangeValue maximum) { + Minimum = minimum; + Maximum = maximum; + Validate(); + } + + /// + /// Determines if the specified number is within the range. + /// + /// The number to check. + /// if the number is within the range; otherwise, . + public bool Within(TNumber number) { + bool aboveMinimum = _minimum.Exclusive ? number > _minimum.Value : number >= _minimum.Value; + bool belowMaximum = _maximum.Exclusive ? number < _maximum.Value : number <= _maximum.Value; + return aboveMinimum && belowMaximum; + } + + /// + /// Validates the range values. + /// + /// Thrown if the maximum value is less than the minimum value. + private void Validate() { + if (_minimum.Value > _maximum.Value) throw new ArgumentOutOfRangeException(nameof(_maximum), "The maximum value must be greater than or equal to the minimum value."); + if (_minimum.Value == _maximum.Value && (_minimum.Exclusive || _maximum.Exclusive)) throw new ArgumentOutOfRangeException(nameof(_maximum), "The maximum value must be greater than the minimum value when either bound is exclusive."); + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(Minimum.Exclusive ? '(' : '['); + sb.Append(Minimum.Value); + sb.Append(',').Append(' '); + sb.Append(Maximum.Value); + sb.Append(Maximum.Exclusive ? ')' : ']'); + return sb.ToString(); + } + + } + + /// + /// Represents a number value within a specified range. + /// + /// The numeric type of the range value. + public readonly record struct RangeValue where TNumber : struct, INumber { + + /// + /// A zero value which is inclusive within the range. + /// + public static readonly RangeValue ZERO_INCLUSIVE = new(TNumber.Zero, false); + + /// + /// A zero value which is exclusive outside the range. + /// + public static readonly RangeValue ZERO_EXCLUSIVE = new(TNumber.Zero, true); + + /// + /// Gets the underlying value. + /// + /// The underlying value. + public required TNumber Value { get; init; } + + /// + /// Gets a flag indicating whether the value is exclusive outside the range. + /// + /// if the value is exclusive; otherwise, . + public required bool Exclusive { get; init; } + + /// + /// Constructs a new . + /// + /// The underlying value. + /// A flag indicating whether the value is exclusive outside the range. + [SetsRequiredMembers] + public RangeValue(TNumber value, bool exclusive) { + Value = value; + Exclusive = exclusive; + } + + /// + /// Implicitly converts a range value to its underlying numeric type. + /// + /// The range value to convert. + /// The underlying numeric value. + public static implicit operator TNumber(RangeValue rv) { + return rv.Value; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Vector2.cs b/CatalystUI/Core/CatalystUI.Mathematics/Vector2.cs new file mode 100644 index 0000000..429fbb4 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Vector2.cs @@ -0,0 +1,304 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics { + + /// + /// A vector containing two numeric values. + /// + /// The numeric type of the vector values. + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Vector2 where TNumber : struct, INumber { + + /// + /// The common zero vector (0, 0). + /// + public static readonly Vector2 ZERO = new(TNumber.Zero); + + /// + /// The common unit vector (1, 1). + /// + public static readonly Vector2 UNIT = new(TNumber.One); + + /// + /// Gets the X value of the vector. + /// + /// The vector's X value. + public required TNumber X { get; init; } + + /// + /// Gets the Y value of the vector. + /// + /// The vector's Y value. + public required TNumber Y { get; init; } + + /// + /// Gets the R value of the vector (alias for X). + /// + /// The vector's R value. + public TNumber R => X; + + /// + /// Gets the G value of the vector (alias for Y). + /// + /// The vector's G value. + public TNumber G => Y; + + /// + /// Gets the S value of the vector (alias for X). + /// + /// The vector's S value. + public TNumber S => X; + + /// + /// Gets the T value of the vector (alias for Y). + /// + /// The vector's T value. + public TNumber T => Y; + + /// + /// Constructs a new + /// using the specified X and Y values. + /// + /// The X value of the vector. + /// The Y value of the vector. + [SetsRequiredMembers] + public Vector2(TNumber x, TNumber y) { + X = x; + Y = y; + } + + /// + /// Constructs a new + /// using the specified value for both X and Y. + /// + /// The value for both X and Y of the vector. + [SetsRequiredMembers] + public Vector2(TNumber xy) : this(xy, xy) { + // ... + } + + /// + /// Normalizes the vector by preserving direction + /// and setting its length to 1. + /// + /// + /// When calculating a normalized vector, + /// the value is first converted to a + /// double-precision floating point number, + /// which allows the necessary mathematical + /// operations to be performed. It is then + /// converted back to the original numeric + /// type. + /// + /// A new vector with the same direction and a length of 1. + public Vector2 Normalize() { + TNumber lengthSquared = X * X + Y * Y; + if (lengthSquared == TNumber.Zero) return ZERO; + double lengthInverted = 1.0 / Math.Sqrt(double.CreateChecked(lengthSquared)); + TNumber lengthConverted = TNumber.CreateChecked(lengthInverted); + return new(X * lengthConverted, Y * lengthConverted); + } + + /// + /// Converts a from + /// into a from . + /// + /// The newly created . + public Vector2 ToVector2() { + return new( + float.CreateChecked(X), + float.CreateChecked(Y) + ); + } + + /// + /// Converts a from + /// into a from . + /// + /// The to convert. + /// The newly created . + public static Vector2 FromVector2(Vector2 vector2) { + return new( + TNumber.CreateChecked(vector2.X), + TNumber.CreateChecked(vector2.Y) + ); + } + + /// + /// Converts the vector to a different numeric type. + /// + /// The vector to convert. + /// The numeric type to convert to. + /// The newly created vector with the specified numeric type. + public static Vector2 ConvertTo(Vector2 vector) where TToNumber : struct, INumber { + return new( + TToNumber.CreateChecked(vector.X), + TToNumber.CreateChecked(vector.Y) + ); + } + + /// + /// Linearly interpolates between two vectors. + /// + /// The starting vector. + /// The ending vector. + /// The interpolation position, typically between 0 and 1. + /// The interpolated vector. + public static Vector2 Lerp(Vector2 v1, Vector2 v2, TNumber position) { + return new( + v1.X + (v2.X - v1.X) * position, + v1.Y + (v2.Y - v1.Y) * position + ); + } + + /// + /// Calculates the dot product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The dot product of the two vectors. + public static TNumber Dot(Vector2 v1, Vector2 v2) { + return v1.X * v2.X + v1.Y * v2.Y; + } + + /// + /// Calculates the distance between two vectors. + /// + /// The first vector. + /// The second vector. + /// The distance between the two vectors. + public static TNumber Distance(Vector2 v1, Vector2 v2) { + TNumber deltaX = v2.X - v1.X; + TNumber deltaY = v2.Y - v1.Y; + double distance = Math.Sqrt(double.CreateChecked(deltaX * deltaX + deltaY * deltaY)); + return TNumber.CreateChecked(distance); + } + + /// + /// Compares two vectors to determine if the left vector is less than the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is less than the right vector; otherwise, . + public static bool operator <(Vector2 left, Vector2 right) => left.X < right.X && left.Y < right.Y; + + /// + /// Compares two vectors to determine if the left vector is less than or equal to the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is less than or equal to the right vector; otherwise, . + public static bool operator <=(Vector2 left, Vector2 right) => left.X <= right.X && left.Y <= right.Y; + + /// + /// Compares two vectors to determine if the left vector is greater than the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is greater than the right vector; otherwise, . + public static bool operator >(Vector2 left, Vector2 right) => left.X > right.X && left.Y > right.Y; + + /// + /// Compares two vectors to determine if the left vector is greater than or equal to the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is greater than or equal to the right vector; otherwise, . + public static bool operator >=(Vector2 left, Vector2 right) => left.X >= right.X && left.Y >= right.Y; + + /// + /// Unary plus operator. + /// + /// The vector to return. + /// The vector unchanged. + public static Vector2 operator +(Vector2 vector) => vector; + + /// + /// Unary negation operator. + /// + /// The vector to negate. + /// The vector with both X and Y negated. + public static Vector2 operator -(Vector2 vector) => new(-vector.X, -vector.Y); + + /// + /// Finds the sum of two vectors. + /// + /// The left vector. + /// The right vector. + /// The sum of the two vectors. + public static Vector2 operator +(Vector2 left, Vector2 right) => new(left.X + right.X, left.Y + right.Y); + + /// + /// Finds the difference between two vectors. + /// + /// The left vector. + /// The right vector. + /// The difference of the two vectors. + public static Vector2 operator -(Vector2 left, Vector2 right) => new(left.X - right.X, left.Y - right.Y); + + /// + /// Increments the vector by one. + /// + /// The vector to increment. + /// The vector with both X and Y incremented by one. + public static Vector2 operator ++(Vector2 vector) => new(vector.X + TNumber.One, vector.Y + TNumber.One); + + /// + /// Decrements the vector by one. + /// + /// The vector to decrement. + /// The vector with both X and Y decremented by one. + public static Vector2 operator --(Vector2 vector) => new(vector.X - TNumber.One, vector.Y - TNumber.One); + + /// + /// Multiplies the vector by a scalar value. + /// + /// The vector to multiply. + /// The scalar value to multiply by. + /// The vector scaled by the scalar value. + public static Vector2 operator *(Vector2 vector, TNumber scalar) => new(vector.X * scalar, vector.Y * scalar); + + /// + public static Vector2 operator *(TNumber scalar, Vector2 vector) => new(vector.X * scalar, vector.Y * scalar); + + /// + /// Multiplies two vectors together. + /// + /// The left vector. + /// The right vector. + /// The vector with each value multiplied together. + public static Vector2 operator *(Vector2 left, Vector2 right) => new(left.X * right.X, left.Y * right.Y); + + /// + /// Divides the vector by a scalar value. + /// + /// The vector to divide. + /// The scalar value to divide by. + /// The vector divided by the scalar value. + public static Vector2 operator /(Vector2 vector, TNumber scalar) => new(vector.X / scalar, vector.Y / scalar); + + /// + /// Finds the quotient of two vectors. + /// + /// The left vector. + /// The right vector. + /// The vector with each value divided. + public static Vector2 operator /(Vector2 left, Vector2 right) => new(left.X / right.X, left.Y / right.Y); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Vector3.cs b/CatalystUI/Core/CatalystUI.Mathematics/Vector3.cs new file mode 100644 index 0000000..147132b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Vector3.cs @@ -0,0 +1,291 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics { + + /// + /// A vector containing three numeric values. + /// + /// The numeric type of the vector values. + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Vector3 where TNumber : struct, INumber { + + /// + /// The common zero vector (0, 0, 0). + /// + public static readonly Vector3 ZERO = new(TNumber.Zero); + + /// + /// The common unit vector (1, 1, 1). + /// + public static readonly Vector3 UNIT = new(TNumber.One); + + /// + /// Gets the X value of the vector. + /// + /// The vector's X value. + public required TNumber X { get; init; } + + /// + /// Gets the Y value of the vector. + /// + /// The vector's Y value. + public required TNumber Y { get; init; } + + /// + /// Gets the Z value of the vector. + /// + /// The vector's Z value. + public required TNumber Z { get; init; } + + /// + /// Gets the R value of the vector (alias for X). + /// + /// The vector's R value. + public TNumber R => X; + + /// + /// Gets the G value of the vector (alias for Y). + /// + /// The vector's G value. + public TNumber G => Y; + + /// + /// Gets the B value of the vector (alias for Z). + /// + /// The vector's B value. + public TNumber B => Z; + + /// + /// Gets the S value of the vector (alias for X). + /// + /// The vector's S value. + public TNumber S => X; + + /// + /// Gets the T value of the vector (alias for Y). + /// + /// The vector's T value. + public TNumber T => Y; + + /// + /// Gets the P value of the vector (alias for Z). + /// + /// The vector's P value. + public TNumber P => Z; + + /// + /// Constructs a new + /// using the specified X, Y, and Z values. + /// + /// The X value of the vector. + /// The Y value of the vector. + /// The Z value of the vector. + [SetsRequiredMembers] + public Vector3(TNumber x, TNumber y, TNumber z) { + X = x; + Y = y; + Z = z; + } + + /// + /// Constructs a new + /// using the specified value for X, Y, and Z. + /// + /// The value for all components of the vector. + [SetsRequiredMembers] + public Vector3(TNumber xyz) : this(xyz, xyz, xyz) { + // ... + } + + /// + /// Normalizes the vector by preserving direction + /// and setting its length to 1. + /// + /// + /// When calculating a normalized vector, + /// the value is first converted to a + /// double-precision floating point number, + /// which allows the necessary mathematical + /// operations to be performed. It is then + /// converted back to the original numeric + /// type. + /// + /// A new vector with the same direction and a length of 1. + public Vector3 Normalize() { + TNumber lengthSquared = X * X + Y * Y + Z * Z; + if (lengthSquared == TNumber.Zero) return ZERO; + double lengthInverted = 1.0 / Math.Sqrt(double.CreateChecked(lengthSquared)); + TNumber lengthConverted = TNumber.CreateChecked(lengthInverted); + return new(X * lengthConverted, Y * lengthConverted, Z * lengthConverted); + } + + /// + /// Converts a from + /// into a from . + /// + /// The newly created . + public Vector3 ToVector3() { + return new( + float.CreateChecked(X), + float.CreateChecked(Y), + float.CreateChecked(Z) + ); + } + + /// + /// Converts a from + /// into a from . + /// + /// The to convert. + /// The newly created . + public static Vector3 FromVector3(Vector3 vector3) { + return new( + TNumber.CreateChecked(vector3.X), + TNumber.CreateChecked(vector3.Y), + TNumber.CreateChecked(vector3.Z) + ); + } + + /// + /// Converts the vector to a different numeric type. + /// + /// The vector to convert. + /// The numeric type to convert to. + /// The newly created vector with the specified numeric type. + public static Vector3 ConvertTo(Vector3 vector) where TToNumber : struct, INumber { + return new( + TToNumber.CreateChecked(vector.X), + TToNumber.CreateChecked(vector.Y), + TToNumber.CreateChecked(vector.Z) + ); + } + + /// + /// Linearly interpolates between two vectors. + /// + /// The starting vector. + /// The ending vector. + /// The interpolation position, typically between 0 and 1. + /// The interpolated vector. + public static Vector3 Lerp(Vector3 v1, Vector3 v2, TNumber position) { + return new( + v1.X + (v2.X - v1.X) * position, + v1.Y + (v2.Y - v1.Y) * position, + v1.Z + (v2.Z - v1.Z) * position + ); + } + + /// + /// Calculates the dot product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The dot product of the two vectors. + public static TNumber Dot(Vector3 v1, Vector3 v2) { + return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; + } + + /// + /// Calculates the distance between two vectors. + /// + /// The first vector. + /// The second vector. + /// The distance between the two vectors. + public static TNumber Distance(Vector3 v1, Vector3 v2) { + TNumber deltaX = v2.X - v1.X; + TNumber deltaY = v2.Y - v1.Y; + TNumber deltaZ = v2.Z - v1.Z; + double distance = Math.Sqrt(double.CreateChecked(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ)); + return TNumber.CreateChecked(distance); + } + + /// + /// Compares two vectors to determine if the left vector is less than the right vector. + /// + public static bool operator <(Vector3 left, Vector3 right) => left.X < right.X && left.Y < right.Y && left.Z < right.Z; + + /// + /// Compares two vectors to determine if the left vector is less than or equal to the right vector. + /// + public static bool operator <=(Vector3 left, Vector3 right) => left.X <= right.X && left.Y <= right.Y && left.Z <= right.Z; + + /// + /// Compares two vectors to determine if the left vector is greater than the right vector. + /// + public static bool operator >(Vector3 left, Vector3 right) => left.X > right.X && left.Y > right.Y && left.Z > right.Z; + + /// + /// Compares two vectors to determine if the left vector is greater than or equal to the right vector. + /// + public static bool operator >=(Vector3 left, Vector3 right) => left.X >= right.X && left.Y >= right.Y && left.Z >= right.Z; + + /// + /// Unary plus operator. + /// + public static Vector3 operator +(Vector3 vector) => vector; + + /// + /// Unary negation operator. + /// + public static Vector3 operator -(Vector3 vector) => new(-vector.X, -vector.Y, -vector.Z); + + /// + /// Finds the sum of two vectors. + /// + public static Vector3 operator +(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z); + + /// + /// Finds the difference between two vectors. + /// + public static Vector3 operator -(Vector3 left, Vector3 right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z); + + /// + /// Increments the vector by one. + /// + public static Vector3 operator ++(Vector3 vector) => new(vector.X + TNumber.One, vector.Y + TNumber.One, vector.Z + TNumber.One); + + /// + /// Decrements the vector by one. + /// + public static Vector3 operator --(Vector3 vector) => new(vector.X - TNumber.One, vector.Y - TNumber.One, vector.Z - TNumber.One); + + /// + /// Multiplies the vector by a scalar value. + /// + public static Vector3 operator *(Vector3 vector, TNumber scalar) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar); + + /// + public static Vector3 operator *(TNumber scalar, Vector3 vector) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar); + + /// + /// Multiplies two vectors together. + /// + public static Vector3 operator *(Vector3 left, Vector3 right) => new(left.X * right.X, left.Y * right.Y, left.Z * right.Z); + + /// + /// Divides the vector by a scalar value. + /// + public static Vector3 operator /(Vector3 vector, TNumber scalar) => new(vector.X / scalar, vector.Y / scalar, vector.Z / scalar); + + /// + /// Finds the quotient of two vectors. + /// + public static Vector3 operator /(Vector3 left, Vector3 right) => new(left.X / right.X, left.Y / right.Y, left.Z / right.Z); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Vector4.cs b/CatalystUI/Core/CatalystUI.Mathematics/Vector4.cs new file mode 100644 index 0000000..002c16e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Vector4.cs @@ -0,0 +1,272 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics { + + /// + /// A vector containing four numeric values. + /// + /// The numeric type of the vector values. + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Vector4 where TNumber : struct, INumber { + + /// + /// The common zero vector (0, 0, 0, 0). + /// + public static readonly Vector4 ZERO = new(TNumber.Zero); + + /// + /// The common unit vector (1, 1, 1, 1). + /// + public static readonly Vector4 UNIT = new(TNumber.One); + + /// + /// Gets the X value of the vector. + /// + /// The X component of the vector. + public required TNumber X { get; init; } + + /// + /// Gets the Y value of the vector. + /// + /// The Y component of the vector. + public required TNumber Y { get; init; } + + /// + /// Gets the Z value of the vector. + /// + /// The Z component of the vector. + public required TNumber Z { get; init; } + + /// + /// Gets the W value of the vector. + /// + /// The W component of the vector. + public required TNumber W { get; init; } + + /// + /// Gets the R value of the vector (alias for X). + /// + public TNumber R => X; + + /// + /// Gets the G value of the vector (alias for Y). + /// + public TNumber G => Y; + + /// + /// Gets the B value of the vector (alias for Z). + /// + public TNumber B => Z; + + /// + /// Gets the A value of the vector (alias for W). + /// + public TNumber A => W; + + /// + /// Gets the S value of the vector (alias for X). + /// + public TNumber S => X; + + /// + /// Gets the T value of the vector (alias for Y). + /// + public TNumber T => Y; + + /// + /// Gets the P value of the vector (alias for Z). + /// + public TNumber P => Z; + + /// + /// Gets the Q value of the vector (alias for W). + /// + public TNumber Q => W; + + /// + /// Constructs a new using the specified X, Y, Z, and W values. + /// + [SetsRequiredMembers] + public Vector4(TNumber x, TNumber y, TNumber z, TNumber w) { + X = x; + Y = y; + Z = z; + W = w; + } + + /// + /// Constructs a new using the specified value for all components. + /// + [SetsRequiredMembers] + public Vector4(TNumber xyzw) : this(xyzw, xyzw, xyzw, xyzw) { + // ... + } + + /// + /// Normalizes the vector by preserving direction and setting its length to 1. + /// + public Vector4 Normalize() { + TNumber lengthSquared = X * X + Y * Y + Z * Z + W * W; + if (lengthSquared == TNumber.Zero) return ZERO; + double lengthInverted = 1.0 / Math.Sqrt(double.CreateChecked(lengthSquared)); + TNumber lengthConverted = TNumber.CreateChecked(lengthInverted); + return new(X * lengthConverted, Y * lengthConverted, Z * lengthConverted, W * lengthConverted); + } + + /// + /// Converts a from into a from . + /// + public Vector4 ToVector4() { + return new( + float.CreateChecked(X), + float.CreateChecked(Y), + float.CreateChecked(Z), + float.CreateChecked(W) + ); + } + + /// + /// Converts a from into a from . + /// + public static Vector4 FromVector4(Vector4 vector4) { + return new( + TNumber.CreateChecked(vector4.X), + TNumber.CreateChecked(vector4.Y), + TNumber.CreateChecked(vector4.Z), + TNumber.CreateChecked(vector4.W) + ); + } + + /// + /// Converts the vector to a different numeric type. + /// + public static Vector4 ConvertTo(Vector4 vector) where TToNumber : struct, INumber { + return new( + TToNumber.CreateChecked(vector.X), + TToNumber.CreateChecked(vector.Y), + TToNumber.CreateChecked(vector.Z), + TToNumber.CreateChecked(vector.W) + ); + } + + /// + /// Linearly interpolates between two vectors. + /// + public static Vector4 Lerp(Vector4 v1, Vector4 v2, TNumber position) { + return new( + v1.X + (v2.X - v1.X) * position, + v1.Y + (v2.Y - v1.Y) * position, + v1.Z + (v2.Z - v1.Z) * position, + v1.W + (v2.W - v1.W) * position + ); + } + + /// + /// Calculates the dot product of two vectors. + /// + public static TNumber Dot(Vector4 v1, Vector4 v2) { + return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z + v1.W * v2.W; + } + + /// + /// Calculates the distance between two vectors. + /// + public static TNumber Distance(Vector4 v1, Vector4 v2) { + TNumber dx = v2.X - v1.X; + TNumber dy = v2.Y - v1.Y; + TNumber dz = v2.Z - v1.Z; + TNumber dw = v2.W - v1.W; + double distance = Math.Sqrt(double.CreateChecked(dx * dx + dy * dy + dz * dz + dw * dw)); + return TNumber.CreateChecked(distance); + } + + /// + /// Compares two vectors to determine if the left vector is less than the right vector. + /// + public static bool operator <(Vector4 left, Vector4 right) => left.X < right.X && left.Y < right.Y && left.Z < right.Z && left.W < right.W; + + /// + /// Compares two vectors to determine if the left vector is less than or equal to the right vector. + /// + public static bool operator <=(Vector4 left, Vector4 right) => left.X <= right.X && left.Y <= right.Y && left.Z <= right.Z && left.W <= right.W; + + /// + /// Compares two vectors to determine if the left vector is greater than the right vector. + /// + public static bool operator >(Vector4 left, Vector4 right) => left.X > right.X && left.Y > right.Y && left.Z > right.Z && left.W > right.W; + + /// + /// Compares two vectors to determine if the left vector is greater than or equal to the right vector. + /// + public static bool operator >=(Vector4 left, Vector4 right) => left.X >= right.X && left.Y >= right.Y && left.Z >= right.Z && left.W >= right.W; + + /// + /// Unary plus operator. + /// + public static Vector4 operator +(Vector4 vector) => vector; + + /// + /// Unary negation operator. + /// + public static Vector4 operator -(Vector4 vector) => new(-vector.X, -vector.Y, -vector.Z, -vector.W); + + /// + /// Finds the sum of two vectors. + /// + public static Vector4 operator +(Vector4 left, Vector4 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W); + + /// + /// Finds the difference between two vectors. + /// + public static Vector4 operator -(Vector4 left, Vector4 right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W); + + /// + /// Increments the vector by one. + /// + public static Vector4 operator ++(Vector4 vector) => new(vector.X + TNumber.One, vector.Y + TNumber.One, vector.Z + TNumber.One, vector.W + TNumber.One); + + /// + /// Decrements the vector by one. + /// + public static Vector4 operator --(Vector4 vector) => new(vector.X - TNumber.One, vector.Y - TNumber.One, vector.Z - TNumber.One, vector.W - TNumber.One); + + /// + /// Multiplies the vector by a scalar value. + /// + public static Vector4 operator *(Vector4 vector, TNumber scalar) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar, vector.W * scalar); + + /// + public static Vector4 operator *(TNumber scalar, Vector4 vector) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar, vector.W * scalar); + + /// + /// Multiplies two vectors together. + /// + public static Vector4 operator *(Vector4 left, Vector4 right) => new(left.X * right.X, left.Y * right.Y, left.Z * right.Z, left.W * right.W); + + /// + /// Divides the vector by a scalar value. + /// + public static Vector4 operator /(Vector4 vector, TNumber scalar) => new(vector.X / scalar, vector.Y / scalar, vector.Z / scalar, vector.W / scalar); + + /// + /// Finds the quotient of two vectors. + /// + public static Vector4 operator /(Vector4 left, Vector4 right) => new(left.X / right.X, left.Y / right.Y, left.Z / right.Z, left.W / right.W); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/CatalystUI.Supplementary.csproj b/CatalystUI/Core/CatalystUI.Supplementary/CatalystUI.Supplementary.csproj new file mode 100644 index 0000000..061d8fa --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/CatalystUI.Supplementary.csproj @@ -0,0 +1,23 @@ + + + + + + Catalyst.Supplementary + Catalyst.Supplementary + + + CatalystUI Supplementary + 1.0.0 + beta.2 + FireController#1847 + Supplementary API provided by the CatalystUI library. + CatalystUI,supplementary,addons,additional,extra + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/BindingContract.cs b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/BindingContract.cs new file mode 100644 index 0000000..00672da --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/BindingContract.cs @@ -0,0 +1,107 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + + +using System.Threading; +// ReSharper disable once CheckNamespace +using System; + +namespace Catalyst.Supplementary { + + /// + /// Represents a contract for binding data to a specific instance or context. + /// + /// + /// Should be disposed of when no longer needed to allow proper resource cleanup. + /// + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class BindingContract : IDisposable { + + /// + /// Fired when managed resources should be disposed of. + /// + public event Action? DisposeManaged; + + /// + /// Fired when unmanaged resources should be disposed of. + /// + public event Action? DisposeUnmanaged; + + /// + /// The unique identifier for the instance to which the data is bound. + /// + public Guid InstanceId { get; } + + /// + /// A flag indicating whether the object has been disposed of. + /// + private bool _disposed; + + /// + /// A lock used to ensure thread-safe access to the object. + /// + private readonly Lock _lock; + + /// + /// Constructs a new . + /// + public BindingContract() { + // Fields + _disposed = false; + _lock = new(); + + // Properties + InstanceId = Guid.NewGuid(); + } + + /// + /// Disposes of the . + /// + ~BindingContract() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + /// + /// Disposes of the . + /// + public void Dispose() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// if disposal is being performed by the garbage collector, otherwise + /// + private void Dispose(bool disposing) { + _lock.Enter(); + try { + if (_disposed) return; + + // Dispose managed state (managed objects) + if (disposing) { + DisposeManaged?.Invoke(this); + } + + // Dispose unmanaged state (unmanaged objects) + DisposeUnmanaged?.Invoke(this); + + // Indicate disposal completion + _disposed = true; + } finally { + _lock.Exit(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/FileDataConnectorBase.cs b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/FileDataConnectorBase.cs new file mode 100644 index 0000000..f12fe37 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/FileDataConnectorBase.cs @@ -0,0 +1,157 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// A base implementation of . + /// + /// + public abstract class FileDataConnectorBase : IFileDataConnector where TLayerLow : ISemanticsLayer { + + /// + /// A concurrent dictionary mapping binding contracts to their associated file information. + /// + protected readonly ConcurrentDictionary _boundContracts; + + /// + /// A concurrent dictionary mapping file information to their associated file streams. + /// + protected readonly ConcurrentDictionary _boundStreams; + + /// + /// The file mode to use when opening files. + /// + protected readonly FileMode _fileMode; + + /// + /// The file access level to use when opening files. + /// + protected readonly FileAccess _fileAccess; + + /// + /// The file sharing mode to use when opening files. + /// + protected readonly FileShare _fileShare; + + /// + /// A flag indicating whether to enforce exclusive access to files. + /// + protected readonly bool _enforceExclusivity; + + /// + /// Constructs a new . + /// + /// The file mode to use when opening files. + /// The file access level to use when opening files. + /// The file sharing mode to use when opening files. + /// Whether to enforce exclusive access to files. + protected FileDataConnectorBase(FileMode fileMode = FileMode.OpenOrCreate, FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.Read, bool enforceExclusivity = false) { + // Fields + _boundContracts = []; + _boundStreams = []; + _fileMode = fileMode; + _fileAccess = fileAccess; + _fileShare = fileShare; + _enforceExclusivity = enforceExclusivity; + } + + /// + public abstract bool TryRead(FileInfo fileInfo, [NotNullWhen(true)] out TSemantic? semantic); + + /// + public bool TryBind(FileInfo fileInfo, [NotNullWhen(true)] out BindingContract? contract) { + // If exclusivity is enforced, check if the file is already bound + if (_enforceExclusivity && _boundStreams.ContainsKey(fileInfo)) { + throw new InvalidOperationException($"File '{fileInfo.FullName}' is already bound to this handler."); + } + + // Create the file stream and the bound file info + FileStream fileStream = fileInfo.Open(_fileMode, _fileAccess, _fileShare); + if (!_boundStreams.TryAdd(fileInfo, fileStream)) { + contract = null; + return false; + } + + // Create the binding contract + contract = new(); + contract.DisposeUnmanaged += OnBindingDisposed; + if (!_boundContracts.TryAdd(contract, fileInfo)) { + // If the binding contract could not be added, clean up the file stream + _boundStreams.TryRemove(fileInfo, out _); + fileStream.Dispose(); + contract = null; // Clear the binding + return false; + } + + // Fire the file bound event + OnFileBound(fileInfo, fileStream); + return true; // success + } + + /// + public abstract bool TryWrite(FileInfo fileInfo, TSemantic semantic); + + /// + /// Fired immediately after a file is bound to the handler. + /// + /// + /// Can be overridden to handle additional logic when a file is bound. + /// + /// The file information that was bound. + /// The file stream that was opened for the file. + protected virtual void OnFileBound(FileInfo fileInfo, FileStream fileStream) { + // ... + } + + /// + /// Fired immediately before a file stream is closed from the handler. + /// + /// + /// Can be overridden to handle additional logic when a file is unbound. + /// + /// The file information that was unbound. + /// The file stream that was closed. + protected virtual void OnFileUnbound(FileInfo fileInfo, FileStream fileStream) { + // ... + } + + /// + /// Fired when a binding is disposed. + /// + /// The binding contract that was disposed. + protected void OnBindingDisposed(BindingContract binding) { + binding.DisposeUnmanaged -= OnBindingDisposed; + + // Find the bound file info associated with the binding + if (_boundContracts.TryRemove(binding, out FileInfo? fileInfo)) { + if (_boundStreams.TryGetValue(fileInfo, out FileStream? fileStream)) { + OnFileUnbound(fileInfo, fileStream); + _boundStreams.Remove(fileInfo, out _); + fileStream.Dispose(); + } + } else { + throw new InvalidOperationException("Failed to remove binding contract from the handler."); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/IFileDataConnector.cs b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/IFileDataConnector.cs new file mode 100644 index 0000000..b2e2c42 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/IFileDataConnector.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using Catalyst.Connectors; +using Catalyst.Domains; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + +#pragma warning disable CS1712 // Type parameter has no matching typeparam tag in the XML comment (but other type parameters do) + /// + /// Represents a subset of the data connector specifically designed for handling file data. + /// + /// The semantic type will represent the file data. + /// + public interface IFileDataConnector : IDataConnector> where TLayerLow : ISemanticsLayer { +#pragma warning restore CS1712 // Type parameter has no matching typeparam tag in the XML comment (but other type parameters do) + + /// + /// Attempts to read and parse the file information into the specified semantic type. + /// + /// The file information to be read. + /// A new instance of if the read operation is successful; otherwise, . + /// if the read operation is successful; otherwise, . + /// Thrown if is . + bool TryRead(FileInfo fileInfo, [NotNullWhen(true)] out TSemantic? semantic); + + /// + /// Attempts to bind the file information to the connector. + /// + /// + /// + /// Binding to a file allows the data connector to have active control over the file's data, + /// rather than performing stateless reads and writes. Typically, a data connector will then + /// use an existing to perform reads and writes, which can be + /// significantly faster than stateless operations, which would otherwise require re-reading + /// or completely overwriting the file for every operation. By binding to a file, the connector + /// can potentially determine a difference between the file's current semantic and a provided + /// semantic, significantly improving performance for large files or frequent updates. + /// + /// + /// The outputted is used to manage the lifecycle of the binding, + /// and should be disposed of when no longer needed, allowing the data connector to safely + /// clean up resources and release the file for other operations. + /// + /// + /// The file information to be bound. + /// A new if the bind operation is successful; otherwise, . + /// if the bind operation is successful; otherwise, . + bool TryBind(FileInfo fileInfo, [NotNullWhen(true)] out BindingContract? contract); + + /// + /// Attempts to write the specified semantic data back to the file. + /// + /// The file information where the data will be written. + /// The semantic data to be written. + /// if the write operation is successful; otherwise, . + /// Thrown if or are . + bool TryWrite(FileInfo fileInfo, TSemantic semantic); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IDigitalInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IDigitalInputData.cs new file mode 100644 index 0000000..88f910e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IDigitalInputData.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Interactions; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data for digital input devices (e.g., buttons, keys). + /// + public interface IDigitalInputData : IInputData { + + /// + /// Gets a value indicating whether the digital input is currently activated (pressed) or not. + /// + /// if activated; otherwise, . + bool Activated { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IPositionedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IPositionedInputData.cs new file mode 100644 index 0000000..4aa7aca --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IPositionedInputData.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data that includes positional information. + /// + public interface IPositionedInputData : IInputData { + + /// + /// Gets the position associated with the input data. + /// + /// A representing the position. + Vector2 Position { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IScrollInputData.cs new file mode 100644 index 0000000..855057e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IScrollInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data for scroll input devices (e.g., mouse wheels, touchpad scrolls). + /// + public interface IScrollInputData : IInputData { + + /// + /// Gets the offset of the scroll input in both horizontal and vertical directions. + /// + /// + /// Positive values typically indicate scrolling down/right, + /// while negative values indicate scrolling up/left. + /// + /// A representing the scroll offset. + Vector2 Offset { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/ITextInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/ITextInputData.cs new file mode 100644 index 0000000..0e98b3a --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/ITextInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data for text input devices (e.g., keyboards, on-screen keyboards). + /// + /// + /// Text input is handled independently of a device since + /// it can originate from various sources. It's preferred + /// for input detection over a device-specific approach + /// because of Dead Keys. + /// + public interface ITextInputData : IInputData { + + /// + /// Gets the text input received from the input device. + /// + /// The text input as a . + string Text { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/DigitalInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/DigitalInputData.cs new file mode 100644 index 0000000..ccecac2 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/DigitalInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying digital input data. + /// + public readonly record struct DigitalInputData : IDigitalInputData { + + /// + public required bool Activated { get; init; } + + /// + /// Constructs a new with + /// the specified activation state. + /// + /// The activation state of the digital input. + public DigitalInputData(bool activated) { + Activated = activated; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/PositionedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/PositionedInputData.cs new file mode 100644 index 0000000..9e488c1 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/PositionedInputData.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying positioned input data. + /// + public readonly record struct PositionedInputData : IPositionedInputData { + + /// + public required Vector2 Position { get; init; } + + /// + /// Constructs a new with + /// the specified position. + /// + /// The position associated with the input data. + public PositionedInputData(Vector2 position) { + Position = position; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/ScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/ScrollInputData.cs new file mode 100644 index 0000000..1857640 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/ScrollInputData.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying scroll input data. + /// + public readonly record struct ScrollInputData : IScrollInputData { + + /// + public required Vector2 Offset { get; init; } + + /// + /// Constructs a new with + /// the specified offset. + /// + /// The scroll offset. + [SetsRequiredMembers] + public ScrollInputData(Vector2 offset) { + Offset = offset; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/TextInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/TextInputData.cs new file mode 100644 index 0000000..ae6b655 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/TextInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying text input data. + /// + public readonly record struct TextInputData : ITextInputData { + + /// + public required string Text { get; init; } + + /// + /// Constructs a new with + /// the specified text. + /// + /// The text associated with the input data. + public TextInputData(string text) { + Text = text; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputData.cs new file mode 100644 index 0000000..90a9aa3 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputData.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents keyboard input data. + /// + public interface IKeyboardInputData : IDigitalInputData { + + /// + /// Gets the scan code of the key. + /// + /// The scan code of the key. + int ScanCode { get; } + + /// + /// Gets the interpreted key from the keyboard input. + /// + /// The interpreted key. + KeyboardInputKey Key { get; } + + /// + /// Gets the modifier keys active during the keyboard input. + /// + /// The modifier keys. + KeyboardInputModifiers Modifiers { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputDevice.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputDevice.cs new file mode 100644 index 0000000..7e8c8db --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputDevice.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents a keyboard input device. + /// + public interface IKeyboardInputDevice : IInputDevice { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputData.cs new file mode 100644 index 0000000..37f5f5b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputData.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying keyboard input data. + /// + public readonly record struct KeyboardInputData : IKeyboardInputData { + + /// + public required bool Activated { get; init; } + + /// + public required int ScanCode { get; init; } + + /// + public required KeyboardInputKey Key { get; init; } + + /// + public required KeyboardInputModifiers Modifiers { get; init; } + + /// + /// Constructs a new with + /// the specified activation state, scan code, key, and modifiers. + /// + /// Whether the key is activated (pressed) or not. + /// The scan code of the key. + /// The interpreted key. + /// The modifier keys. + [SetsRequiredMembers] + public KeyboardInputData(bool activated, int scanCode, KeyboardInputKey key, KeyboardInputModifiers modifiers) { + Activated = activated; + ScanCode = scanCode; + Key = key; + Modifiers = modifiers; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputKey.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputKey.cs new file mode 100644 index 0000000..6bec0b0 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputKey.cs @@ -0,0 +1,206 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A list of keyboard keys. + /// + /// + /// + /// The design of the enum is to support + /// as many keys as possible, but there will + /// be limitations due to the nature of different + /// languages and keyboard formats. + /// + /// + /// The focus is to map keys to their associated + /// Unicode value under an English, QWERTY U.S. Layout. + /// However, implementing the actual translation + /// to characters will depend on the user's implementation. + /// + /// + /// Some keys, such as the F1-F24 keys, are mapped + /// to the private use Unicode space. If an unsupported + /// key is encountered, it should be mapped to the + /// 0xF900-0xF9FF range. CatalystUI reserves the + /// space of 0xF700-0xF8FF for future use. + /// + /// + public enum KeyboardInputKey { + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + + // NULL + Null = 0, + + // Function keys (F1-F24) + F1 = 0xF700, + F2 = 0xF701, + F3 = 0xF702, + F4 = 0xF703, + F5 = 0xF704, + F6 = 0xF705, + F7 = 0xF706, + F8 = 0xF707, + F9 = 0xF708, + F10 = 0xF709, + F11 = 0xF70A, + F12 = 0xF70B, + F13 = 0xF70C, + F14 = 0xF70D, + F15 = 0xF70E, + F16 = 0xF70F, + F17 = 0xF710, + F18 = 0xF711, + F19 = 0xF712, + F20 = 0xF713, + F21 = 0xF714, + F22 = 0xF715, + F23 = 0xF716, + F24 = 0xF717, + + // Number keys + Zero = 0x0030, + One = 0x0031, + Two = 0x0032, + Three = 0x0033, + Four = 0x0034, + Five = 0x0035, + Six = 0x0036, + Seven = 0x0037, + Eight = 0x0038, + Nine = 0x0039, + + // Alphanumeric keys + A = 0x0041, + B = 0x0042, + C = 0x0043, + D = 0x0044, + E = 0x0045, + F = 0x0046, + G = 0x0047, + H = 0x0048, + I = 0x0049, + J = 0x004A, + K = 0x004B, + L = 0x004C, + M = 0x004D, + N = 0x004E, + O = 0x004F, + P = 0x0050, + Q = 0x0051, + R = 0x0052, + S = 0x0053, + T = 0x0054, + U = 0x0055, + V = 0x0056, + W = 0x0057, + X = 0x0058, + Y = 0x0059, + Z = 0x005A, + + // Symbols + Space = 0x0020, + Apostrophe = 0x0027, + Comma = 0x002C, + Dash = 0x002D, + Period = 0x002E, + Slash = 0x002F, + Semicolon = 0x003B, + Equals = 0x003D, + LeftBracket = 0x005B, + Backslash = 0x005C, + RightBracket = 0x005D, + GraveAccent = 0x0060, + + // Special characters (with Unicode mappings) + Backspace = 0x0008, + Tab = 0x0009, + Enter = 0x000D, + Escape = 0x001B, + Delete = 0x007F, + + // Special characters (without Unicode mappings) + PrintScreen = 0xF800, + ScrollLock = 0xF801, + PauseBreak = 0xF802, + Insert = 0xF803, + CapsLock = 0xF804, + Home = 0xF805, + End = 0xF806, + PageUp = 0xF807, + PageDown = 0xF808, + NumLock = 0xF809, + + // Arrow keys + ArrowLeft = 0x2190, + ArrowUp = 0x2191, + ArrowRight = 0x2192, + ArrowDown = 0x2193, + + // Modifier keys + LeftShift = 0xF820, + LeftControl = 0xF821, + LeftSuper = 0xF822, + LeftWindows = LeftSuper, + LeftCommand = LeftSuper, + LeftAlt = 0xF823, + RightShift = 0xF830, + RightControl = 0xF831, + RightSuper = 0xF832, + RightWindows = RightSuper, + RightCommand = RightSuper, + RightAlt = 0xF833, + Function = 0xF834, + Menu = 0xF835, + + // Number pad + NumpadDivide = 0xF850, + NumpadMultiply = 0xF851, + NumpadSubtract = 0xF852, + NumpadAdd = 0xF853, + NumpadEnter = 0xF854, + NumpadDecimal = 0xF855, + NumpadZero = 0xF860, + NumpadOne = 0xF861, + NumpadTwo = 0xF862, + NumpadThree = 0xF863, + NumpadFour = 0xF864, + NumpadFive = 0xF865, + NumpadSix = 0xF866, + NumpadSeven = 0xF867, + NumpadEight = 0xF868, + NumpadNine = 0xF869, + + // Media controls + MediaPlay = 0xF880, + MediaPause = 0xF881, + MediaStop = 0xF882, + MediaNext = 0xF883, + MediaPrevious = 0xF884, + VolumeMute = 0xF885, + VolumeUp = 0xF886, + VolumeDown = 0xF887, + + // Computer controls + Help = 0xF890, + Print = 0xF891, + Power = 0xF892, + Sleep = 0xF893, + Wake = 0xF894, + +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputModifiers.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputModifiers.cs new file mode 100644 index 0000000..a7899f5 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputModifiers.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A list of keyboard modifier keys. + /// + [Flags] + public enum KeyboardInputModifiers { + + /// + /// No modifier keys. + /// + None = 0, + + /// + /// Shift modifier key. + /// + Shift = 1 << 1, + + /// + /// Control modifier key. + /// + Control = 1 << 2, + + /// + /// Alt modifier key. + /// + Alt = 1 << 3, + + /// + /// (Linux/Other) Super modifier key. + /// + Super = 1 << 4, + + /// + /// (Windows) Windows modifier key. + /// + Windows = Super, + + /// + /// (MacOS) Command modifier key. + /// + Command = Super + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseInputDevice.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseInputDevice.cs new file mode 100644 index 0000000..a11b863 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseInputDevice.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents a mouse input device. + /// + public interface IMouseInputDevice : IInputDevice { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseMovedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseMovedInputData.cs new file mode 100644 index 0000000..b52cf36 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseMovedInputData.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents mouse-move input data. + /// + public interface IMouseMovedInputData : IPositionedInputData { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMousePressedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMousePressedInputData.cs new file mode 100644 index 0000000..fcb247e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMousePressedInputData.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents mouse-press input data. + /// + public interface IMousePressedInputData : IPositionedInputData, IDigitalInputData { + + /// + /// Gets the mouse button that was pressed. + /// + /// The mouse button. + MouseInputButton Button { get; } + + /// + /// Gets the identifier of the button that was pressed. + /// + /// The button identifier. + int ButtonId { get; } + + /// + /// Gets the modifier keys active during the mouse press. + /// + /// The modifier keys. + KeyboardInputModifiers Modifiers { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseScrollInputData.cs new file mode 100644 index 0000000..867221f --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseScrollInputData.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents mouse-scroll input data. + /// + public interface IMouseScrollInputData : IPositionedInputData, IScrollInputData { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseInputButton.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseInputButton.cs new file mode 100644 index 0000000..7bc3ec6 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseInputButton.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A list of mouse buttons. + /// + public enum MouseInputButton { + + /// + /// The primary mouse button (typically left click). + /// + PrimaryButton, + + /// + /// The secondary mouse button (typically right click). + /// + SecondaryButton, + + /// + /// The tertiary mouse button (typically middle click). + /// + TertiaryButton, + + /// + /// Any other button on the mouse. + /// + OtherButton + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseMovedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseMovedInputData.cs new file mode 100644 index 0000000..36a56ef --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseMovedInputData.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + + +using System.Diagnostics.CodeAnalysis; +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying mouse moved input data. + /// + public readonly record struct MouseMovedInputData : IMouseMovedInputData { + + /// + public required Vector2 Position { get; init; } + + /// + /// Constructs a new with + /// the specified position. + /// + /// The position of the mouse. + [SetsRequiredMembers] + public MouseMovedInputData(Vector2 position) { + Position = position; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MousePressedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MousePressedInputData.cs new file mode 100644 index 0000000..9403a3d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MousePressedInputData.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying mouse pressed input data. + /// + public readonly record struct MousePressedInputData : IMousePressedInputData { + + /// + public required Vector2 Position { get; init; } + + /// + public required bool Activated { get; init; } + + /// + public required MouseInputButton Button { get; init; } + + /// + public required int ButtonId { get; init; } + + /// + public required KeyboardInputModifiers Modifiers { get; init; } + + /// + /// Constructs a new with + /// the specified position, activation state, button, button ID, and modifiers. + /// + /// The position of the mouse. + /// The activation state of the button. + /// The mouse button. + /// The button identifier. + /// The modifier keys. + [SetsRequiredMembers] + public MousePressedInputData(Vector2 position, bool activated, MouseInputButton button, int buttonId, KeyboardInputModifiers modifiers) { + Position = position; + Activated = activated; + Button = button; + ButtonId = buttonId; + Modifiers = modifiers; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseScrollInputData.cs new file mode 100644 index 0000000..955c05e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseScrollInputData.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying mouse scroll input data. + /// + public readonly record struct MouseScrollInputData : IMouseScrollInputData { + + /// + public required Vector2 Position { get; init; } + + /// + public required Vector2 Offset { get; init; } + + /// + /// Constructs a new with + /// the specified position and offset. + /// + /// The position of the mouse. + /// The scroll offset. + [SetsRequiredMembers] + public MouseScrollInputData(Vector2 position, Vector2 offset) { + Position = position; + Offset = offset; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/DataLayer/IFileInfoDataLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/DataLayer/IFileInfoDataLayer.cs new file mode 100644 index 0000000..0cdca4e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/DataLayer/IFileInfoDataLayer.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a subset of the data layer specifically for handling file information. + /// + /// + public interface IFileInfoDataLayer : IDataLayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/ILinuxSystemLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/ILinuxSystemLayer.cs new file mode 100644 index 0000000..dada472 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/ILinuxSystemLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using Catalyst.Domains; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a system layer for Linux-based systems. + /// + public interface ILinuxSystemLayer : ISystemLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IMacSystemLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IMacSystemLayer.cs new file mode 100644 index 0000000..d7e7d49 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IMacSystemLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using Catalyst.Domains; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a system layer for Apple's macOS-based systems. + /// + public interface IMacSystemLayer : ISystemLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IWindowsSystemLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IWindowsSystemLayer.cs new file mode 100644 index 0000000..1fce9b9 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IWindowsSystemLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a system layer for Microsoft's Windows-based systems. + /// + public interface IWindowsSystemLayer : ISystemLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Utilities/SystemDetector.cs b/CatalystUI/Core/CatalystUI.Supplementary/Utilities/SystemDetector.cs new file mode 100644 index 0000000..a1ae07a --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Utilities/SystemDetector.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.Runtime.InteropServices; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// A set of utilities to determine the current operating system and environment. + /// + public static class SystemDetector { + + /// + /// Gets the detected operating system type. + /// + public static Type? DetectedSystemType { get; } + + /// + /// Static constructor for . + /// + static SystemDetector() { + // TODO: Is this sufficient? Or should the detection be more advanced? + // Maybe in the future the system layers could provide system information, + // such as versioning or platform-specific features, and then we could + // discern between them here. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + DetectedSystemType = typeof(IWindowsSystemLayer); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + DetectedSystemType = typeof(IMacSystemLayer); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + DetectedSystemType = typeof(ILinuxSystemLayer); + } else { + DetectedSystemType = null; + } + } + + /// + /// Determines if the current system is of the specified type. + /// + /// The type of the system to check. + /// if the current system is of the specified type; otherwise, . + public static bool IsSystem() where TSystem : ISystemLayer { + return DetectedSystemType != null && typeof(TSystem).IsAssignableFrom(DetectedSystemType); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/CatalystUI.Modules.Arcane.Core.csproj b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/CatalystUI.Modules.Arcane.Core.csproj new file mode 100644 index 0000000..dd54f63 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/CatalystUI.Modules.Arcane.Core.csproj @@ -0,0 +1,24 @@ + + + + + + Catalyst.Modules.Arcane + Catalyst.Modules.Arcane + + + CatalystUI Arcane Core + 1.0.0 + alpha.1 + CatalystUI LLC + Core API for the Arcane subset of modules provided by the CatalystUI library. + CatalystUI,Arcane,core,file,filesystem + + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IArcaneDomain.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IArcaneDomain.cs new file mode 100644 index 0000000..580db53 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IArcaneDomain.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +namespace Catalyst.Modules.Arcane { + + /// + /// Represents the Arcane domain in the CatalystUI model. + /// + /// + /// Arcane is a subset of modules for CatalystUI which + /// are designed to work with files. These modules + /// provide functionality for reading, writing, and + /// manipulating various file formats, as well as + /// integrating file-based data into CatalystUI applications. + /// + public interface IArcaneDomain : ISymbolicDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IIniDataConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IIniDataConnector.cs new file mode 100644 index 0000000..cce5dcc --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IIniDataConnector.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Supplementary; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a data handler for INI configuration files. + /// + public interface IIniDataConnector : IFileDataConnector { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchema.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchema.cs new file mode 100644 index 0000000..5d3d42f --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchema.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file as defined by a file's associated schema. + /// + /// + /// Essentially acts as a collection of fields for any form of file + /// which can be defined by a schema. It simplifies the process of + /// working with files by providing a strongly-typed interface + /// to access fields by their identifiers. + /// + /// An enum type which represents all possible fields in the file schema. + public interface IFileSchema where TField : Enum { + + /// + /// Gets a field from the file by its identifier. + /// + /// The field identifier. + /// The type of the value stored in the field. + /// The field instance. + IFileSchemaField GetField(TField field); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaFieldInfo.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaFieldInfo.cs new file mode 100644 index 0000000..d47f9b4 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaFieldInfo.cs @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file's field descriptor as defined by a file's associated schema. + /// + /// An enum type which represents all possible fields in the file schema. + public interface IFileSchemaFieldInfo where TField : Enum { + + /// + /// The field identifier. + /// + /// An enum value representing the field. + static abstract TField Field { get; } + + /// + /// The offset in bytes from the start of the file where the field begins. + /// + /// The offset in bytes, or to represent a variable offset. + static abstract uint? Offset { get; } + + /// + /// The size in bytes of the field. + /// + /// The size in bytes, or to represent a variable size. + static abstract uint? Size { get; } + + /// + /// An optional human-friendly name for the field. + /// + /// + /// Keep in mind the value provided here will always be embedded in compiled builds + /// for end-users, so it should be concise and meaningful if provided. + /// + /// The human-friendly name, or if not specified. + static abstract string? Name { get; } + + /// + /// An optional human-friendly description of the field. + /// + /// + /// Keep in mind the value provided here will always be embedded in compiled builds + /// for end-users, so it should be concise and meaningful if provided. + /// + /// The human-friendly description, or if not specified. + static abstract string? Description { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaField.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaField.cs new file mode 100644 index 0000000..0d8c54c --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaField.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file's field as defined by a file's associated schema. + /// + /// An enum type which represents all possible fields in the file schema. + /// The type of the value stored in the field. + public interface IFileSchemaField : IFileSchemaFieldInfo where TField : Enum { + + /// + /// Gets the value of the field. + /// + /// The field's value. + TValue? Value { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaFieldConstant.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaFieldConstant.cs new file mode 100644 index 0000000..931c89b --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaFieldConstant.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file's field which contains a constant value as defined by a file's associated schema. + /// + /// An enum type which represents all possible fields in the file schema. + /// The type of the value stored in the field. + public interface IFileSchemaFieldConstant : IFileSchemaFieldInfo where TField : Enum { + + /// + /// Gets the value of the field. + /// + /// The field's value. + static abstract TValue? Value { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniComponent.cs new file mode 100644 index 0000000..4431458 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniComponent.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a component for an INI configuration file. + /// + public interface IIniComponent { + + /// + /// Gets the global key-value pairs in the INI file. + /// + /// A read-only dictionary of the global entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Gets the sections defined in the INI file. + /// + /// A read-only list of the INI sections. + IReadOnlyList Sections { get; } + + /// + /// Sets the value for the specified global key in the INI file. + /// + /// The key of the global entry to set. + /// The value to associate with the key. + /// Thrown if the provided key is or empty. + void SetValue(string key, string? value); + + /// + /// Attempts to get the value associated with the specified global key in the INI file. + /// + /// The key of the global entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + /// + /// Attempts to remove the entry with the specified global key from the INI file. + /// + /// The key of the entry to remove. + /// The previously associated value, if the key was found; otherwise, . + /// if the key existed and was removed; otherwise, . + bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed); + + /// + /// Sets or adds a section in the INI file. If a section with the same name already exists, it will be replaced. + /// + /// The section to set or add. + /// Thrown if the provided section is . + void SetSection(IIniSectionComponent section); + + /// + /// Attempts to get a section with the specified name. + /// + /// The name of the section to retrieve. + /// The section with the specified name, if found; otherwise, . + /// if the section exists; otherwise, . + /// Thrown if the provided name is or empty. + bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionComponent? section); + + /// + /// Attempts to remove the section with the specified name from the INI file. + /// + /// The name of the section to remove. + /// The previously associated section, if the section was found; otherwise, . + /// if the section existed and was removed; otherwise, . + /// Thrown if the provided name is or empty. + bool TryRemoveSection(string name, [NotNullWhen(true)] out IIniSectionComponent? removed); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniSectionComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniSectionComponent.cs new file mode 100644 index 0000000..7cabb37 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniSectionComponent.cs @@ -0,0 +1,64 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a component for a section of an INI configuration file. + /// + public interface IIniSectionComponent { + + /// + /// Gets or sets the name of the INI section. + /// + /// The INI section's name. + string Name { get; set; } + + /// + /// Gets the key-value pairs within the INI section. + /// + /// A read-only dictionary of the section's entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Sets the value for the specified key in the INI section. + /// + /// The key of the entry to set. + /// The value to associate with the key. Use to remove the entry. + /// Thrown if the provided key is or empty. + void SetValue(string key, string? value); + + /// + /// Attempts to get the value associated with the specified key in the INI section. + /// + /// The key of the entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + /// + /// Attempts to remove the entry with the specified key from the INI section. + /// + /// The key of the entry to remove. + /// The previously associated value, if the key was found; otherwise, . + /// if the key existed and was removed; otherwise, . + /// Thrown if the provided key is or empty. + bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniComponentsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniComponentsLayer.cs new file mode 100644 index 0000000..fc0283b --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniComponentsLayer.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents the components layer for INI configuration files. + /// + public interface IIniComponentsLayer : IComponentsLayer { + + /// + /// Creates an INI components representation from the provided entries and sections. + /// + /// The global key-value pairs in the INI file, or if there are none. + /// The sections within the INI file, or if there are none. + /// A new instance of an from the provided data. + IIniComponent CreateComponent(IReadOnlyDictionary? entries = null, IReadOnlyList? sections = null); + + /// + /// Creates a section components representation from the provided name and entries. + /// + /// The name of the section. + /// The key-value pairs within the section, or if there are none. + /// A new instance of an from the provided data. + IIniSectionComponent CreateSectionComponent(string name, IReadOnlyDictionary? entries = null); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDataLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDataLayer.cs new file mode 100644 index 0000000..4f224e8 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDataLayer.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Supplementary; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents the data layer for INI configuration files. + /// + public interface IIniDataLayer : IFileInfoDataLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDomain.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDomain.cs new file mode 100644 index 0000000..72f33e2 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDomain.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a domain for INI configuration files. + /// + public interface IIniDomain : ISymbolicDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniParserConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniParserConnector.cs new file mode 100644 index 0000000..50f55ba --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniParserConnector.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Connectors; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a parser connector for INI configuration files. + /// + /// + /// Provides mutation from INI semantic types to INI component types. + /// + public interface IIniParserConnector : IParserConnector { + + /// + /// Mutates the provided INI semantic representation into an INI components representation. + /// + /// The INI semantic representation to mutate. + /// A new instance of an representing the provided semantic data. + IIniComponent ToComponent(IIniSemantic semantic); + + /// + /// Mutates the provided INI components representation into an INI semantic representation. + /// + /// The INI components representation to mutate. + /// A new instance of an representing the provided components data. + IIniSemantic ToSemantic(IIniComponent component); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniSemanticsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniSemanticsLayer.cs new file mode 100644 index 0000000..12cca60 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniSemanticsLayer.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents the semantics layer for INI configuration files. + /// + public interface IIniSemanticsLayer : ISemanticsLayer { + + /// + /// Creates an INI semantics representation from the provided entries and sections. + /// + /// The global key-value pairs in the INI file. + /// The sections within the INI file. + /// A new instance of an from the provided data. + IIniSemantic CreateSemantic(IReadOnlyDictionary entries, IReadOnlyList sections); + + /// + /// Creates a section semantics representation from the provided name and entries. + /// + /// The name of the section. + /// The key-value pairs within the section. + /// A new instance of an from the provided data. + IIniSectionSemantic CreateSectionSemantic(string name, IReadOnlyDictionary entries); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSectionSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSectionSemantic.cs new file mode 100644 index 0000000..7509719 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSectionSemantic.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a semantic for a section of an INI configuration file. + /// + public interface IIniSectionSemantic { + + /// + /// Gets the name of the INI section. + /// + /// The INI section's name. + string Name { get; } + + /// + /// Gets the key-value pairs within the INI section. + /// + /// A read-only dictionary of the section's entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Attempts to get the value associated with the specified key in the INI section. + /// + /// The key of the entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSemantic.cs new file mode 100644 index 0000000..1dd825e --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSemantic.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a semantic for an INI configuration file. + /// + public interface IIniSemantic { + + /// + /// Gets the global key-value pairs in the INI file. + /// + /// + /// A global entry is one defined outside any section. + /// + /// A read-only dictionary of the global entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Gets the sections defined in the INI file. + /// + /// A read-only list of the INI sections. + IReadOnlyList Sections { get; } + + /// + /// Attempts to get the value associated with the specified global key in the INI file. + /// + /// The key of the global entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + /// + /// Attempts to get a section with the specified name. + /// + /// The name of the section to retrieve. + /// The section with the specified name, if found; otherwise, . + /// if the section exists; otherwise, . + /// Thrown if the provided name is or empty. + bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionSemantic? section); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/CatalystUI.Modules.Arcane.Ini.csproj b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/CatalystUI.Modules.Arcane.Ini.csproj new file mode 100644 index 0000000..b104188 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/CatalystUI.Modules.Arcane.Ini.csproj @@ -0,0 +1,23 @@ + + + + + + Catalyst.Modules.Arcane.Ini + Catalyst.Modules.Arcane.Ini + + + CatalystUI Arcane – .INI Configuration File Format + 1.0.0 + beta.2 + CatalystUI LLC + .INI API for the Arcane subset of modules provided by the CatalystUI library. + CatalystUI,Arcane,ini,configuration,file,format + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniComponent.cs new file mode 100644 index 0000000..f9bf257 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniComponent.cs @@ -0,0 +1,195 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// A mutable component for an INI configuration file. + /// + public class IniComponent : IIniComponent { + + /// + /// Internal reference for . + /// + protected readonly Dictionary _entries; + + /// + public virtual IReadOnlyDictionary Entries { + get { + _lock.Enter(); + try { + return _entries; // assume cast-based immutability + } finally { + _lock.Exit(); + } + } + } + + /// + /// Internal reference for . + /// + protected readonly List _sections; + + /// + public virtual IReadOnlyList Sections { + get { + _lock.Enter(); + try { + return _sections; // assume cast-based immutability + } finally { + _lock.Exit(); + } + } + } + + /// + /// A lock to provide thread-safe access to the component. + /// + protected readonly Lock _lock; + + /// + /// Constructs a new + /// with the specified entries, sections, and lock. + /// + /// The global entries in the INI file as a dictionary of key-value pairs, or to start with an empty dictionary. + /// The sections in the INI file as a list of instances, or to start with an empty list. + /// An existing lock to use for thread-safety, or to create a new lock. + public IniComponent(IReadOnlyDictionary? entries = null, IReadOnlyList? sections = null, Lock? @lock = null) { + _entries = entries is null ? new() : new(entries); + _sections = sections is null ? new() : new(sections); + _lock = @lock ?? new(); + } + + /// + public void SetValue(string key, string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + _entries[key] = value; + } finally { + _lock.Exit(); + } + } + + /// + public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.TryGetValue(key, out value); + } finally { + _lock.Exit(); + } + } + + /// + public bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.Remove(key, out removed); + } finally { + _lock.Exit(); + } + } + + /// + public void SetSection(IIniSectionComponent section) { + if (section is null) throw new ArgumentNullException(nameof(section), "The section cannot be null."); + _lock.Enter(); + try { + int index = _sections.FindIndex(s => s.Name.Equals(section.Name, StringComparison.OrdinalIgnoreCase)); + if (index >= 0) { + _sections[index] = section; + } else { + _sections.Add(section); + } + } finally { + _lock.Exit(); + } + } + + /// + public bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionComponent? section) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace."); + _lock.Enter(); + try { + section = _sections.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + return section != null; + } finally { + _lock.Exit(); + } + } + + /// + public bool TryRemoveSection(string name, [NotNullWhen(true)] out IIniSectionComponent? removed) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace."); + _lock.Enter(); + try { + int index = _sections.FindIndex(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + if (index >= 0) { + removed = _sections[index]; + _sections.RemoveAt(index); + return true; + } else { + removed = null; + return false; + } + } finally { + _lock.Exit(); + } + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniComponent)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(',').Append(' '); + sb.Append(nameof(Sections)); + sb.Append(' ').Append('=').Append(' '); + sb.Append('[').Append(' '); + first = true; + foreach (IIniSectionComponent section in Sections) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(section); + } + sb.Append(' ').Append(']'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniSectionComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniSectionComponent.cs new file mode 100644 index 0000000..7e6953c --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniSectionComponent.cs @@ -0,0 +1,147 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Threading; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// A mutable component for a section in an INI configuration file. + /// + public class IniSectionComponent : IIniSectionComponent { + + /// + /// Internal reference for . + /// + protected string _name; + + /// + public virtual string Name { + get { + _lock.Enter(); + try { + return _name; + } finally { + _lock.Exit(); + } + } + set { + _lock.Enter(); + try { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value), "The section name cannot be null or whitespace."); + _name = value; + } finally { + _lock.Exit(); + } + } + } + + /// + /// Internal reference for . + /// + protected readonly Dictionary _entries; + + /// + public virtual IReadOnlyDictionary Entries { + get { + _lock.Enter(); + try { + return _entries; // assume cast-based immutability + } finally { + _lock.Exit(); + } + } + } + + /// + /// A lock to provide thread-safe access to the component. + /// + protected readonly Lock _lock; + + /// + /// Constructs a new + /// with the specified name, entries, and lock. + /// + /// The name of the section. + /// The entries in the section as a dictionary of key-value pairs, or to start with an empty dictionary. + /// An existing lock to use for thread-safety, or to create a new one. + public IniSectionComponent(string name, IReadOnlyDictionary? entries = null, Lock? @lock = null) { + _name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace.") : name; + _entries = entries is null ? new() : new Dictionary(entries); + _lock = @lock ?? new Lock(); + } + + /// + public virtual void SetValue(string key, string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + _entries[key] = value; + } finally { + _lock.Exit(); + } + } + + /// + public virtual bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.TryGetValue(key, out value); + } finally { + _lock.Exit(); + } + } + + /// + public virtual bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.Remove(key, out removed); + } finally { + _lock.Exit(); + } + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniSectionComponent)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Name)).Append(' ').Append('=').Append(' ').Append(Name); + sb.Append(',').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Extensions/CatalystAppBuilderExtensions.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Extensions/CatalystAppBuilderExtensions.cs new file mode 100644 index 0000000..8a7fe26 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Extensions/CatalystAppBuilderExtensions.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Modules.Arcane.Ini; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Builders.Extensions { + + /// + /// Builder extensions for the . + /// + public static class CatalystAppBuilderExtensions { + + /// + /// Adds the Arcane INI model to the . + /// + /// + /// + /// The Arcane INI model adds the following to CatalystUI application: + /// + /// + /// + /// + /// + /// + /// Click on any of the above links to learn more about each component. + /// + /// + /// The to add the model to. + /// The with the Arcane INI model added. + public static CatalystAppBuilder AddArcaneIniModule(this CatalystAppBuilder builder) { + IniDataConnector iniDataConnector = new(); + IniComponentsLayer iniComponentsLayer = new(); + IniParserConnector iniParserConnector = new(); + IniSemanticsLayer iniSemanticsLayer = new(); + ModelRegistry.RegisterConnector(iniDataConnector); + ModelRegistry.RegisterLayer(iniComponentsLayer); + ModelRegistry.RegisterConnector(iniParserConnector); + ModelRegistry.RegisterLayer(iniSemanticsLayer); + return builder; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniComponentsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniComponentsLayer.cs new file mode 100644 index 0000000..f6ea756 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniComponentsLayer.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public sealed class IniComponentsLayer : IIniComponentsLayer { + + /// + public IIniComponent CreateComponent(IReadOnlyDictionary? entries = null, IReadOnlyList? sections = null) { + return new IniComponent(entries, sections); + } + + /// + public IIniSectionComponent CreateSectionComponent(string name, IReadOnlyDictionary? entries = null) { + return new IniSectionComponent(name, entries); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniDataConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniDataConnector.cs new file mode 100644 index 0000000..55e38ff --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniDataConnector.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Supplementary; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public class IniDataConnector : FileDataConnectorBase, IIniDataConnector { + + /// + public override bool TryRead(FileInfo fileInfo, [NotNullWhen(true)] out IIniSemantic? semantic) { + BindingContract? contract = null; + if (!_boundStreams.TryGetValue(fileInfo, out FileStream? stream)) { + if (!TryBind(fileInfo, out contract)) throw new NotSupportedException($"The data connector failed to bind to the file '{fileInfo.FullName}'."); + if (!_boundStreams.TryGetValue(fileInfo, out stream)) throw new FileNotFoundException($"The file '{fileInfo.FullName}' could not be found or opened."); + } + try { + throw new NotImplementedException(); + } finally { + contract?.Dispose(); + } + } + + /// + public override bool TryWrite(FileInfo fileInfo, IIniSemantic semantic) { + BindingContract? contract = null; + if (!_boundStreams.TryGetValue(fileInfo, out FileStream? stream)) { + if (!TryBind(fileInfo, out contract)) throw new NotSupportedException($"The data connector failed to bind to the file '{fileInfo.FullName}'."); + if (!_boundStreams.TryGetValue(fileInfo, out stream)) throw new FileNotFoundException($"The file '{fileInfo.FullName}' could not be found or opened."); + } + try { + throw new NotImplementedException(); + } finally { + contract?.Dispose(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniParserConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniParserConnector.cs new file mode 100644 index 0000000..1de7cb6 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniParserConnector.cs @@ -0,0 +1,55 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public sealed class IniParserConnector : IIniParserConnector { + + /// + public IIniComponent ToComponent(IIniSemantic semantic) { + // Create a lock for the converted component. + Lock @lock = new(); + + // Convert the semantic sections to component sections. + IReadOnlyList sectionsFrom = semantic.Sections; + IIniSectionComponent[] sectionsTo = new IIniSectionComponent[sectionsFrom.Count]; + for (int i = 0; i < sectionsFrom.Count; i++) { + IIniSectionSemantic semanticSection = sectionsFrom[i]; + sectionsTo[i] = new IniSectionComponent(semanticSection.Name, semanticSection.Entries, @lock); + } + + // Convert the semantic to a component. + return new IniComponent(semantic.Entries, sectionsTo, @lock); + } + + /// + public IIniSemantic ToSemantic(IIniComponent component) { + // Convert the component sections to semantic sections. + IReadOnlyList sectionsFrom = component.Sections; + IIniSectionSemantic[] sectionsTo = new IIniSectionSemantic[sectionsFrom.Count]; + for (int i = 0; i < sectionsFrom.Count; i++) { + IIniSectionComponent componentSection = sectionsFrom[i]; + sectionsTo[i] = new IniSectionSemantic(componentSection.Name, componentSection.Entries); + } + + // Convert the component to a semantic. + return new IniSemantic(component.Entries, sectionsTo); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniSemanticsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniSemanticsLayer.cs new file mode 100644 index 0000000..7326210 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniSemanticsLayer.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public sealed class IniSemanticsLayer : IIniSemanticsLayer { + + /// + public IIniSemantic CreateSemantic(IReadOnlyDictionary entries, IReadOnlyList sections) { + return new IniSemantic(entries, sections); + } + + /// + public IIniSectionSemantic CreateSectionSemantic(string name, IReadOnlyDictionary entries) { + return new IniSectionSemantic(name, entries); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSectionSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSectionSemantic.cs new file mode 100644 index 0000000..8a93f4e --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSectionSemantic.cs @@ -0,0 +1,77 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// An immutable semantic for a section in an INI configuration file. + /// + public record IniSectionSemantic : IIniSectionSemantic { + + /// + public required string Name { get; init; } + + /// + public required IReadOnlyDictionary Entries { get; init; } + + /// + /// Constructs a new + /// with the specified name and entries. + /// + /// The name of the section. + /// The entries in the section as a dictionary of key-value pairs. + /// Thrown if is null or whitespace, or if is null. + [SetsRequiredMembers] + public IniSectionSemantic(string name, IReadOnlyDictionary entries) { + Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace.") : name; + Entries = entries ?? throw new ArgumentNullException(nameof(entries), "The entries dictionary cannot be null."); + } + + /// + public virtual bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + return Entries.TryGetValue(key, out value); + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniSectionSemantic)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Name)).Append(' ').Append('=').Append(' ').Append(Name); + sb.Append(',').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSemantic.cs new file mode 100644 index 0000000..2953edb --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSemantic.cs @@ -0,0 +1,94 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// An immutable semantic for an INI configuration file. + /// + public record IniSemantic : IIniSemantic { + + /// + public required IReadOnlyDictionary Entries { get; init; } + + /// + public required IReadOnlyList Sections { get; init; } + + /// + /// Constructs a new + /// with the specified entries and sections. + /// + /// The global entries in the INI file as a dictionary of key-value pairs. + /// The sections in the INI file as a list of instances. + /// Thrown if or is null. + [SetsRequiredMembers] + public IniSemantic(IReadOnlyDictionary entries, IReadOnlyList sections) { + Entries = entries ?? throw new ArgumentNullException(nameof(entries), "The entries dictionary cannot be null."); + Sections = sections ?? throw new ArgumentNullException(nameof(sections), "The sections list cannot be null."); + } + + /// + public virtual bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + return Entries.TryGetValue(key, out value); + } + + /// + public virtual bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionSemantic? section) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace."); + section = Sections.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + return section != null; + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniSemantic)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(',').Append(' '); + sb.Append(nameof(Sections)); + sb.Append(' ').Append('=').Append(' '); + sb.Append('[').Append(' '); + first = true; + foreach (IIniSectionSemantic section in Sections) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(section); + } + sb.Append(' ').Append(']'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj index 5da9d12..73c9f9b 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj +++ b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj @@ -10,7 +10,7 @@ - + @@ -33,6 +33,7 @@ + diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini index 075aa9c..8642783 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini @@ -9,4 +9,5 @@ ShowStackTrace=false [EnabledScopes] Debugging=info Application=info +ModelRegistry=info Profiling=info \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props index 1d3d73e..eeed8e7 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props @@ -3,5 +3,6 @@ + \ No newline at end of file