diff --git a/FinalEngine.Editor.Common/Extensions/ServiceCollectionExtensions.cs b/FinalEngine.Editor.Common/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..0896a6c3 --- /dev/null +++ b/FinalEngine.Editor.Common/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Extensions; + +using System; +using System.Diagnostics.CodeAnalysis; +using FinalEngine.Editor.Common.Services.Factories; +using FinalEngine.Editor.Common.Services.Rendering; +using Microsoft.Extensions.DependencyInjection; + +/// +/// Provides extension methods for an . +/// +[ExcludeFromCodeCoverage(Justification = "Extension Methods")] +public static class ServiceCollectionExtensions +{ + /// + /// Adds all common services to specified collection. + /// + /// + /// The services collection. + /// + /// + /// The collection. + /// + /// + /// The specified parameter cannot be null. + /// + public static IServiceCollection AddCommon(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddSingleton(); + + return services; + } + + /// + /// Adds a factory that creates an instance of type to the specified collection. + /// + /// + /// The type of instance to create. + /// + /// + /// The services collection. + /// + /// + /// The specified parameter cannot be null. + /// + public static void AddFactory(this IServiceCollection services) + where TViewModel : class + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddTransient(); + services.AddSingleton>(x => + { + return () => + { + return x.GetRequiredService(); + }; + }); + + services.AddSingleton, Factory>(); + } +} diff --git a/FinalEngine.Editor.Common/FinalEngine.Editor.Common.csproj b/FinalEngine.Editor.Common/FinalEngine.Editor.Common.csproj new file mode 100644 index 00000000..c108b177 --- /dev/null +++ b/FinalEngine.Editor.Common/FinalEngine.Editor.Common.csproj @@ -0,0 +1,41 @@ + + + + net7.0 + 11.0 + enable + All + false + true + x64 + + + + CA1848;CA2254 + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/FinalEngine.Editor.Common/Properties/AssemblyInfo.cs b/FinalEngine.Editor.Common/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..18db25cb --- /dev/null +++ b/FinalEngine.Editor.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("FinalEngine.Editor.Common")] +[assembly: AssemblyDescription("A common services library for the Final Engine editor.")] +[assembly: Guid("D8E94A1E-3718-429D-A3F7-40ADAADA636D")] diff --git a/FinalEngine.Editor.Common/Services/Factories/Factory.cs b/FinalEngine.Editor.Common/Services/Factories/Factory.cs new file mode 100644 index 00000000..62496659 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Factories/Factory.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Factories; + +using System; + +/// +/// Provides a standard implementation of an . +/// +/// +/// The type of instance to create. +/// +/// +public sealed class Factory : IFactory +{ + /// + /// The function used to create the instance. + /// + private readonly Func factory; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The function used to create the instance. + /// + /// + /// The specified parameter cannot be null. + /// + public Factory(Func factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + public T Create() + { + return this.factory(); + } +} diff --git a/FinalEngine.Editor.Common/Services/Factories/IFactory.cs b/FinalEngine.Editor.Common/Services/Factories/IFactory.cs new file mode 100644 index 00000000..d22f4f29 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Factories/IFactory.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Factories; + +/// +/// Defines an interface that provides a method to create an instance of type . +/// +/// +/// The type of instance to create. +/// +public interface IFactory +{ + /// + /// Creates an instance of type . + /// + /// + /// The newly created instance of type . + /// + T Create(); +} diff --git a/FinalEngine.Editor.Common/Services/Rendering/ISceneRenderer.cs b/FinalEngine.Editor.Common/Services/Rendering/ISceneRenderer.cs new file mode 100644 index 00000000..f7dc7eec --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Rendering/ISceneRenderer.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Rendering; + +/// +/// Defines an interface that represents a scene renderer. +/// +public interface ISceneRenderer +{ + /// + /// Changes the current scene projection the specified and . + /// + /// + /// The width of the projection. + /// + /// + /// The height of the projection. + /// + void ChangeProjection(int projectionWidth, int projectionHeight); + + /// + /// Renders the currently active scene. + /// + void Render(); +} diff --git a/FinalEngine.Editor.Common/Services/Rendering/SceneRenderer.cs b/FinalEngine.Editor.Common/Services/Rendering/SceneRenderer.cs new file mode 100644 index 00000000..5fed2e42 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Rendering/SceneRenderer.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Rendering; + +using System; +using System.Drawing; +using System.Numerics; +using FinalEngine.Rendering; +using Microsoft.Extensions.Logging; + +/// +/// Provides a standard implementation of an . +/// +/// +public sealed class SceneRenderer : ISceneRenderer +{ + /// + /// The logger. + /// + private readonly ILogger logger; + + /// + /// The render device. + /// + private readonly IRenderDevice renderDevice; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The logger. + /// + /// + /// The render device. + /// + /// + /// The specified or parameter cannot be null. + /// + public SceneRenderer(ILogger logger, IRenderDevice renderDevice) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.renderDevice = renderDevice ?? throw new ArgumentNullException(nameof(renderDevice)); + } + + /// + public void ChangeProjection(int projectionWidth, int projectionHeight) + { + this.logger.LogDebug($"Changing projection to: ({projectionWidth}, {projectionHeight})."); + + this.renderDevice.Pipeline.SetUniform("u_projection", Matrix4x4.CreateOrthographicOffCenter(0, projectionWidth, 0, projectionHeight, -1, 1)); + this.renderDevice.Rasterizer.SetViewport(new Rectangle(0, 0, projectionWidth, projectionHeight)); + } + + /// + public void Render() + { + this.renderDevice.Clear(Color.FromArgb(255, 30, 30, 30)); + } +} diff --git a/FinalEngine.Editor.Desktop/App.xaml b/FinalEngine.Editor.Desktop/App.xaml index 412083d9..cc63a725 100644 --- a/FinalEngine.Editor.Desktop/App.xaml +++ b/FinalEngine.Editor.Desktop/App.xaml @@ -8,6 +8,8 @@ + + diff --git a/FinalEngine.Editor.Desktop/App.xaml.cs b/FinalEngine.Editor.Desktop/App.xaml.cs index 41264071..7ea2be1c 100644 --- a/FinalEngine.Editor.Desktop/App.xaml.cs +++ b/FinalEngine.Editor.Desktop/App.xaml.cs @@ -4,14 +4,19 @@ namespace FinalEngine.Editor.Desktop; -using System; using System.Diagnostics; using System.IO; using System.Windows; +using FinalEngine.Editor.Common.Extensions; using FinalEngine.Editor.Desktop.Views; using FinalEngine.Editor.ViewModels; +using FinalEngine.Editor.ViewModels.Docking.Panes; +using FinalEngine.Rendering; +using FinalEngine.Rendering.OpenGL; +using FinalEngine.Rendering.OpenGL.Invocation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; /// @@ -19,15 +24,48 @@ namespace FinalEngine.Editor.Desktop; /// public partial class App : Application { + /// + /// Initializes a new instance of the class. + /// + public App() + { + AppHost = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration(ConfigureAppConfiguration) + .ConfigureServices(ConfigureServices) + .Build(); + } + + /// + /// Gets or sets the application host. + /// + /// + /// The application host. + /// + private static IHost? AppHost { get; set; } + + /// + /// Exits the main application, disposing of any existing resources. + /// + /// + /// The instance containing the event data. + /// + protected override async void OnExit(ExitEventArgs e) + { + await AppHost!.StopAsync(); + base.OnExit(e); + } + /// /// Starts up the main application on the current platform. /// /// /// A that contains the event data. /// - protected override void OnStartup(StartupEventArgs e) + protected override async void OnStartup(StartupEventArgs e) { - var viewModel = ConfigureServices().GetRequiredService(); + await AppHost!.StartAsync(); + + var viewModel = AppHost.Services.GetRequiredService(); var view = new MainView() { @@ -38,31 +76,32 @@ protected override void OnStartup(StartupEventArgs e) } /// - /// Builds the configuration used throughout the lifetime of the application. + /// Configures the applications configuration. /// - /// - /// The newly created to be used throughout the lifetime of the application. - /// - private static IConfiguration BuildConfiguration() + /// + /// The builder. + /// + private static void ConfigureAppConfiguration(IConfigurationBuilder builder) { string environment = Debugger.IsAttached ? "Development" : "Production"; - return new ConfigurationBuilder() + builder .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile($"appsettings.{environment}.json") - .Build(); + .AddJsonFile($"appsettings.{environment}.json"); } /// - /// Configures the services to be consumed by the application. + /// Configures the services to be consumed by the application. /// - /// - /// The newly configured . - /// - private static IServiceProvider ConfigureServices() + /// + /// The context. + /// + /// + /// The services. + /// + private static void ConfigureServices(HostBuilderContext context, IServiceCollection services) { - var services = new ServiceCollection(); - var configuration = BuildConfiguration(); + var configuration = context.Configuration; services.AddLogging(builder => { @@ -71,8 +110,12 @@ private static IServiceProvider ConfigureServices() .AddFile(configuration.GetSection("LoggingOptions")); }); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddCommon(); - return services.BuildServiceProvider(); + services.AddFactory(); + services.AddSingleton(); } } diff --git a/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj b/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj index ee52c386..0b56b54e 100644 --- a/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj +++ b/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj @@ -1,7 +1,7 @@ - + - WinExe + Exe true net7.0-windows 11.0 @@ -27,6 +27,7 @@ + all @@ -36,9 +37,11 @@ + + all @@ -47,7 +50,9 @@ + + diff --git a/FinalEngine.Editor.Desktop/Selectors/Docking/PanesStyleSelector.cs b/FinalEngine.Editor.Desktop/Selectors/Docking/PanesStyleSelector.cs new file mode 100644 index 00000000..b3b176e5 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Selectors/Docking/PanesStyleSelector.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Selectors.Docking; + +using System.Windows; +using System.Windows.Controls; +using FinalEngine.Editor.ViewModels.Docking.Panes; +using FinalEngine.Editor.ViewModels.Docking.Tools; + +/// +/// Provides a style selector for an . +/// +/// +public class PanesStyleSelector : StyleSelector +{ + /// + /// Gets or sets the document style. + /// + /// + /// The document style. + /// + public Style? PaneStyle { get; set; } + + /// + /// Gets or sets the tool style. + /// + /// + /// The tool style. + /// + public Style? ToolStyle { get; set; } + + /// + /// When overridden in a derived class, returns a based on custom logic. + /// + /// + /// The content. + /// + /// + /// The element to which the style will be applied. + /// + /// + /// Returns an application-specific style to apply; otherwise, . + /// + public override Style? SelectStyle(object item, DependencyObject container) + { + if (item is IToolViewModel) + { + return this.ToolStyle; + } + + if (item is IPaneViewModel) + { + return this.PaneStyle; + } + + return base.SelectStyle(item, container); + } +} diff --git a/FinalEngine.Editor.Desktop/Selectors/Docking/PanesTemplateSelector.cs b/FinalEngine.Editor.Desktop/Selectors/Docking/PanesTemplateSelector.cs new file mode 100644 index 00000000..7dd2be8f --- /dev/null +++ b/FinalEngine.Editor.Desktop/Selectors/Docking/PanesTemplateSelector.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Selectors.Docking; + +using System.Windows; +using System.Windows.Controls; +using FinalEngine.Editor.ViewModels.Docking.Panes; + +/// +/// Provides a pane template style selector. +/// +/// +public class PanesTemplateSelector : DataTemplateSelector +{ + /// + /// Gets or sets the scene template. + /// + /// + /// The scene template. + /// + public DataTemplate? SceneTemplate { get; set; } + + /// + /// When overridden in a derived class, returns a based on custom logic. + /// + /// + /// The data object for which to select the template. + /// + /// + /// The data-bound object. + /// + /// + /// Returns a or null. The default value is null. + /// + public override DataTemplate? SelectTemplate(object item, DependencyObject container) + { + if (item is ISceneViewModel) + { + return this.SceneTemplate; + } + + return base.SelectTemplate(item, container); + } +} diff --git a/FinalEngine.Editor.Desktop/Styles/Docking/PaneStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Docking/PaneStyle.xaml new file mode 100644 index 00000000..3a064acc --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Docking/PaneStyle.xaml @@ -0,0 +1,14 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Docking/ToolStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Docking/ToolStyle.xaml new file mode 100644 index 00000000..cb3f0a7d --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Docking/ToolStyle.xaml @@ -0,0 +1,18 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Views/Docking/Panes/SceneView.xaml b/FinalEngine.Editor.Desktop/Views/Docking/Panes/SceneView.xaml new file mode 100644 index 00000000..9549f31d --- /dev/null +++ b/FinalEngine.Editor.Desktop/Views/Docking/Panes/SceneView.xaml @@ -0,0 +1,12 @@ + + diff --git a/FinalEngine.Editor.Desktop/Views/Docking/Panes/SceneView.xaml.cs b/FinalEngine.Editor.Desktop/Views/Docking/Panes/SceneView.xaml.cs new file mode 100644 index 00000000..7b6c3e58 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Views/Docking/Panes/SceneView.xaml.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Views.Docking.Panes; + +using OpenTK.Graphics.OpenGL4; +using OpenTK.Windowing.Common; +using OpenTK.Wpf; + +/// +/// Interaction logic for SceneView.xaml. +/// +public partial class SceneView : GLWpfControl +{ + /// + /// Initializes a new instance of the class. + /// + public SceneView() + { + this.InitializeComponent(); + + this.Start(new GLWpfControlSettings() + { + MajorVersion = 4, + MinorVersion = 5, + GraphicsContextFlags = ContextFlags.ForwardCompatible, + GraphicsProfile = ContextProfile.Core, + RenderContinuously = true, + }); + + this.Render += this.SceneView_Render; + } + + /// + /// Block and wait until the scene is rendered. + /// + /// + /// The delta time. + /// + private void SceneView_Render(System.TimeSpan obj) + { + GL.Finish(); + } +} diff --git a/FinalEngine.Editor.Desktop/Views/MainView.xaml b/FinalEngine.Editor.Desktop/Views/MainView.xaml index 41388116..1e796dd9 100644 --- a/FinalEngine.Editor.Desktop/Views/MainView.xaml +++ b/FinalEngine.Editor.Desktop/Views/MainView.xaml @@ -5,6 +5,10 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:selectors="clr-namespace:FinalEngine.Editor.Desktop.Selectors.Docking" + xmlns:panes="clr-namespace:FinalEngine.Editor.Desktop.Views.Docking.Panes" + xmlns:vm="clr-namespace:FinalEngine.Editor.ViewModels;assembly=FinalEngine.Editor.ViewModels" + d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" mc:Ignorable="d" Width="800" Height="450" @@ -30,4 +34,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FinalEngine.Editor.ViewModels/Docking/Panes/IPaneViewModel.cs b/FinalEngine.Editor.ViewModels/Docking/Panes/IPaneViewModel.cs new file mode 100644 index 00000000..79d8e431 --- /dev/null +++ b/FinalEngine.Editor.ViewModels/Docking/Panes/IPaneViewModel.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.ViewModels.Docking.Panes; + +/// +/// Defines an interface that represents a pane view model. +/// +/// +/// A pane view is a view which is used as part of a dockable layout system. It represents any element that can be docked to a dockable layout. +/// +public interface IPaneViewModel +{ + /// + /// Gets or sets the content identifier. + /// + /// + /// The content identifier. + /// + string ContentID { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// + /// true if this instance is active; otherwise, false. + /// + bool IsActive { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is selected. + /// + /// + /// true if this instance is selected; otherwise, false. + /// + bool IsSelected { get; set; } + + /// + /// Gets or sets the title. + /// + /// + /// The title. + /// + string Title { get; set; } +} diff --git a/FinalEngine.Editor.ViewModels/Docking/Panes/ISceneViewModel.cs b/FinalEngine.Editor.ViewModels/Docking/Panes/ISceneViewModel.cs new file mode 100644 index 00000000..60c9eb58 --- /dev/null +++ b/FinalEngine.Editor.ViewModels/Docking/Panes/ISceneViewModel.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.ViewModels.Docking.Panes; + +using System.Drawing; +using System.Windows.Input; + +/// +/// Defines an interface that represents a scene view, view model. +/// +public interface ISceneViewModel +{ + /// + /// Gets the size of the projection. + /// + /// + /// The size of the projection. + /// + Size ProjectionSize { get; } + + /// + /// Gets the render command. + /// + /// + /// The render command. + /// + ICommand RenderCommand { get; } +} diff --git a/FinalEngine.Editor.ViewModels/Docking/Panes/PaneViewModelBase.cs b/FinalEngine.Editor.ViewModels/Docking/Panes/PaneViewModelBase.cs new file mode 100644 index 00000000..100e4fc5 --- /dev/null +++ b/FinalEngine.Editor.ViewModels/Docking/Panes/PaneViewModelBase.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.ViewModels.Docking.Panes; + +using CommunityToolkit.Mvvm.ComponentModel; + +/// +/// The base class to be implemented for a document view. +/// +/// +/// +public partial class PaneViewModelBase : ObservableObject, IPaneViewModel +{ + /// + /// The content identifier. + /// + private string? contentID; + + /// + /// Indicates whether this instance is active. + /// + private bool isActive; + + /// + /// Indicates whether this instance is selected. + /// + private bool isSelected; + + /// + /// The title. + /// + private string? title; + + /// + /// Gets or sets the content identifier. + /// + /// + /// The content identifier. + /// + public string ContentID + { + get { return this.contentID ?? string.Empty; } + set { this.SetProperty(ref this.contentID, value); } + } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// + /// true if this instance is active; otherwise, false. + /// + public bool IsActive + { + get { return this.isActive; } + set { this.SetProperty(ref this.isActive, value); } + } + + /// + /// Gets or sets a value indicating whether this instance is selected. + /// + /// + /// true if this instance is selected; otherwise, false. + /// + public bool IsSelected + { + get { return this.isSelected; } + set { this.SetProperty(ref this.isSelected, value); } + } + + /// + /// Gets or sets the title. + /// + /// + /// The title. + /// + public string Title + { + get { return this.title ?? string.Empty; } + set { this.SetProperty(ref this.title, value); } + } +} diff --git a/FinalEngine.Editor.ViewModels/Docking/Panes/SceneViewModel.cs b/FinalEngine.Editor.ViewModels/Docking/Panes/SceneViewModel.cs new file mode 100644 index 00000000..6c5aafae --- /dev/null +++ b/FinalEngine.Editor.ViewModels/Docking/Panes/SceneViewModel.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.ViewModels.Docking.Panes; + +using System; +using System.Drawing; +using System.Windows.Input; +using CommunityToolkit.Mvvm.Input; +using FinalEngine.Editor.Common.Services.Rendering; + +/// +/// Represents a standard implementation of an . +/// +/// +/// +public partial class SceneViewModel : PaneViewModelBase, ISceneViewModel +{ + /// + /// The scene renderer. + /// + private readonly ISceneRenderer sceneRenderer; + + /// + /// The projection size. + /// + private Size projectionSize; + + /// + /// The render command. + /// + private ICommand? renderCommand; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The scene renderer. + /// + /// + /// The specified parameter cannot be null. + /// + public SceneViewModel(ISceneRenderer sceneRenderer) + { + this.sceneRenderer = sceneRenderer ?? throw new ArgumentNullException(nameof(sceneRenderer)); + + this.Title = "Scene View"; + } + + /// + /// Gets or sets the size of the projection. + /// + /// + /// The size of the projection. + /// + public Size ProjectionSize + { + get + { + return this.projectionSize; + } + + set + { + this.SetProperty(ref this.projectionSize, value); + this.sceneRenderer.ChangeProjection(this.projectionSize.Width, this.projectionSize.Height); + } + } + + /// + /// Gets the render command. + /// + /// + /// The render command. + /// + public ICommand RenderCommand + { + get { return this.renderCommand ??= new RelayCommand(this.Render); } + } + + /// + /// Renders the scene to the view. + /// + private void Render() + { + this.sceneRenderer.Render(); + } +} diff --git a/FinalEngine.Editor.ViewModels/Docking/Tools/IToolViewModel.cs b/FinalEngine.Editor.ViewModels/Docking/Tools/IToolViewModel.cs new file mode 100644 index 00000000..a45a1c27 --- /dev/null +++ b/FinalEngine.Editor.ViewModels/Docking/Tools/IToolViewModel.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.ViewModels.Docking.Tools; + +using FinalEngine.Editor.ViewModels.Docking.Panes; + +/// +/// Defines an interface that represents a tool window view model. +/// +/// +/// A tool view is a view which is used as part of a dockable layout system. It represents any element that can be docked to the tool section of a dockable layout. +/// +/// +public interface IToolViewModel : IPaneViewModel +{ + /// + /// Gets or sets a value indicating whether this instance is visible. + /// + /// + /// true if this instance is visible; otherwise, false. + /// + public bool IsVisible { get; set; } +} diff --git a/FinalEngine.Editor.ViewModels/Docking/Tools/ToolViewModelBase.cs b/FinalEngine.Editor.ViewModels/Docking/Tools/ToolViewModelBase.cs new file mode 100644 index 00000000..5b34aa4e --- /dev/null +++ b/FinalEngine.Editor.ViewModels/Docking/Tools/ToolViewModelBase.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.ViewModels.Docking.Tools; + +using FinalEngine.Editor.ViewModels.Docking.Panes; + +/// +/// The base class to be implemented for a tool window view. +/// +/// +/// +public partial class ToolViewModelBase : PaneViewModelBase, IToolViewModel +{ + /// + /// Indicates whether this instance is visible. + /// + private bool isVisible; + + /// + /// Initializes a new instance of the class. + /// + protected ToolViewModelBase() + { + this.IsVisible = true; + } + + /// + /// Gets or sets a value indicating whether this instance is visible. + /// + /// + /// true if this instance is visible; otherwise, false. + /// + public bool IsVisible + { + get { return this.isVisible; } + set { this.SetProperty(ref this.isVisible, value); } + } +} diff --git a/FinalEngine.Editor.ViewModels/FinalEngine.Editor.ViewModels.csproj b/FinalEngine.Editor.ViewModels/FinalEngine.Editor.ViewModels.csproj index cee707c8..bd37817c 100644 --- a/FinalEngine.Editor.ViewModels/FinalEngine.Editor.ViewModels.csproj +++ b/FinalEngine.Editor.ViewModels/FinalEngine.Editor.ViewModels.csproj @@ -28,6 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all @@ -36,6 +37,7 @@ + diff --git a/FinalEngine.Editor.ViewModels/IMainViewModel.cs b/FinalEngine.Editor.ViewModels/IMainViewModel.cs index a133bb38..e47f6d8e 100644 --- a/FinalEngine.Editor.ViewModels/IMainViewModel.cs +++ b/FinalEngine.Editor.ViewModels/IMainViewModel.cs @@ -4,8 +4,10 @@ namespace FinalEngine.Editor.ViewModels; -using CommunityToolkit.Mvvm.Input; -using FinalEngine.Editor.ViewModels.Interaction; +using System.Collections.Generic; +using System.Windows.Input; +using FinalEngine.Editor.ViewModels.Docking.Panes; +using FinalEngine.Editor.ViewModels.Docking.Tools; /// /// Defines an interface that represents the main view. @@ -18,7 +20,15 @@ public interface IMainViewModel /// /// The exit command. /// - IRelayCommand ExitCommand { get; } + ICommand ExitCommand { get; } + + /// + /// Gets the documents attached to this . + /// + /// + /// The documents attached to this . + /// + IEnumerable Panes { get; } /// /// Gets the title. @@ -27,4 +37,12 @@ public interface IMainViewModel /// The title. /// string Title { get; } + + /// + /// Gets the tool windows attached to this . + /// + /// + /// The tool windows attached to this . + /// + IEnumerable Tools { get; } } diff --git a/FinalEngine.Editor.ViewModels/MainViewModel.cs b/FinalEngine.Editor.ViewModels/MainViewModel.cs index 5f856f7a..574eea50 100644 --- a/FinalEngine.Editor.ViewModels/MainViewModel.cs +++ b/FinalEngine.Editor.ViewModels/MainViewModel.cs @@ -5,9 +5,14 @@ namespace FinalEngine.Editor.ViewModels; using System; +using System.Collections.Generic; using System.Reflection; +using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FinalEngine.Editor.Common.Services.Factories; +using FinalEngine.Editor.ViewModels.Docking.Panes; +using FinalEngine.Editor.ViewModels.Docking.Tools; using FinalEngine.Editor.ViewModels.Interaction; using FinalEngine.Utilities.Extensions; using Microsoft.Extensions.Logging; @@ -25,9 +30,13 @@ public partial class MainViewModel : ObservableObject, IMainViewModel private readonly ILogger logger; /// - /// The main application title. + /// The exit command. + /// + private ICommand? exitCommand; + + /// + /// The application title. /// - [ObservableProperty] private string? title; /// @@ -36,15 +45,49 @@ public partial class MainViewModel : ObservableObject, IMainViewModel /// /// The logger. /// + /// + /// The scene view model factory. + /// /// /// The specified parameter cannot be null. /// - public MainViewModel(ILogger logger) + public MainViewModel(ILogger logger, IFactory sceneViewModelFactory) { + if (sceneViewModelFactory == null) + { + throw new ArgumentNullException(nameof(sceneViewModelFactory)); + } + + this.Panes = new List() + { + sceneViewModelFactory.Create(), + }; + + this.Tools = new List(); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.Title = $"Final Engine - {Assembly.GetExecutingAssembly().GetVersionString()}"; } + /// + public ICommand ExitCommand + { + get { return this.exitCommand ??= new RelayCommand(this.Exit); } + } + + /// + public IEnumerable Panes { get; } + + /// + public string Title + { + get { return this.title!; } + private set { this.SetProperty(ref this.title, value); } + } + + /// + public IEnumerable Tools { get; } + /// /// Attempts to the exit the main application. /// @@ -54,7 +97,6 @@ public MainViewModel(ILogger logger) /// /// The specified parameter cannot be null. /// - [RelayCommand] private void Exit(ICloseable? closeable) { if (closeable == null) diff --git a/FinalEngine.Tests/Editor/Common/Services/Factories/FactoryTests.cs b/FinalEngine.Tests/Editor/Common/Services/Factories/FactoryTests.cs new file mode 100644 index 00000000..4fd9ec18 --- /dev/null +++ b/FinalEngine.Tests/Editor/Common/Services/Factories/FactoryTests.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Tests.Editor.Common.Services.Factories; + +using System; +using FinalEngine.Editor.Common.Services.Factories; +using NUnit.Framework; + +[TestFixture] +public sealed class FactoryTests +{ + private Factory factory; + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenFactoryIsNull() + { + // Act and assert + Assert.Throws(() => + { + new Factory(null); + }); + } + + [Test] + public void CreateShouldReturnDifferentReferenceWhenInvoked() + { + // Arrange + var expected = this.factory.Create(); + + // Act + var actual = this.factory.Create(); + + // Assert + Assert.That(actual, Is.Not.SameAs(expected)); + } + + [Test] + public void CreateShouldReturnFactoryTestsWhenInvoked() + { + // Act + var actual = this.factory.Create(); + + // Assert + Assert.That(actual, Is.TypeOf()); + } + + [SetUp] + public void Setup() + { + this.factory = new Factory(() => + { + return new FactoryTests(); + }); + } +} diff --git a/FinalEngine.Tests/Editor/Common/Services/Rendering/SceneRendererTests.cs b/FinalEngine.Tests/Editor/Common/Services/Rendering/SceneRendererTests.cs new file mode 100644 index 00000000..f73158ba --- /dev/null +++ b/FinalEngine.Tests/Editor/Common/Services/Rendering/SceneRendererTests.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Tests.Editor.Common.Services.Rendering; + +using System; +using System.Drawing; +using System.Linq.Expressions; +using System.Numerics; +using FinalEngine.Editor.Common.Services.Rendering; +using FinalEngine.Rendering; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; + +[TestFixture] +public sealed class SceneRendererTests +{ + private Mock> logger; + + private Mock pipeline; + + private Mock rasterizer; + + private Mock renderDevice; + + private SceneRenderer sceneRenderer; + + [Test] + public void ChangeProjectionShouldSetUniformProjectionWhenInvoked() + { + // Arrange + const int width = 1280; + const int height = 720; + + var projection = Matrix4x4.CreateOrthographicOffCenter(0, width, 0, height, -1, 1); + + Expression> match = (mat) => mat.Equals(projection); + + // Act + this.sceneRenderer.ChangeProjection(width, height); + + // Assert + this.pipeline.Verify(x => x.SetUniform("u_projection", It.Is(match)), Times.Once); + } + + [Test] + public void ChangeProjectionShouldSetViewportWhenInvoked() + { + // Arrange + const int width = 1280; + const int height = 720; + + Expression> match = (rect) => + rect.X == 0 && + rect.Y == 0 && + rect.Width == width + && rect.Height == height; + + // Act + this.sceneRenderer.ChangeProjection(width, height); + + // Assert + this.rasterizer.Verify(x => x.SetViewport(It.Is(match), 0, 1), Times.Once); + } + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenLoggerIsNull() + { + // Act and assert + Assert.Throws(() => + { + new SceneRenderer(null, this.renderDevice.Object); + }); + } + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenRenderDeviceIsNull() + { + // Act and assert + Assert.Throws(() => + { + new SceneRenderer(this.logger.Object, null); + }); + } + + [Test] + public void RenderShouldInvokeClearWhenInvoked() + { + // Act + this.sceneRenderer.Render(); + + // Assert + this.renderDevice.Verify(x => x.Clear(It.IsAny(), 1, 0), Times.Once); + } + + [SetUp] + public void Setup() + { + this.logger = new Mock>(); + this.renderDevice = new Mock(); + this.pipeline = new Mock(); + this.rasterizer = new Mock(); + + this.renderDevice.SetupGet(x => x.Pipeline).Returns(this.pipeline.Object); + this.renderDevice.SetupGet(x => x.Rasterizer).Returns(this.rasterizer.Object); + + this.sceneRenderer = new SceneRenderer(this.logger.Object, this.renderDevice.Object); + } +} diff --git a/FinalEngine.Tests/Editor/ViewModels/Docking/Panes/PaneViewModelBaseTests.cs b/FinalEngine.Tests/Editor/ViewModels/Docking/Panes/PaneViewModelBaseTests.cs new file mode 100644 index 00000000..4a37e35e --- /dev/null +++ b/FinalEngine.Tests/Editor/ViewModels/Docking/Panes/PaneViewModelBaseTests.cs @@ -0,0 +1,117 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Tests.Editor.ViewModels.Docking.Panes; + +using FinalEngine.Editor.ViewModels.Docking.Panes; +using Moq; +using NUnit.Framework; + +[TestFixture] +public sealed class PaneViewModelBaseTests +{ + private Mock viewModel; + + [Test] + public void ConstructorShouldNotThrowExceptionWhenInvoked() + { + // Act and assert + Assert.DoesNotThrow(() => + { + this.viewModel = new Mock(); + }); + } + + [Test] + public void ContentIDShouldReturnEmptyWhenNotSet() + { + // Act + string actual = this.viewModel.Object.ContentID; + + // Assert + Assert.That(actual, Is.Empty); + } + + [Test] + public void ContentIDShouldReturnHelloWorldWhenSet() + { + // Arrange + string expected = "Hello, World!"; + + // Act + this.viewModel.Object.ContentID = expected; + + // Assert + Assert.That(this.viewModel.Object.ContentID, Is.EqualTo(expected)); + } + + [Test] + public void IsActiveShouldReturnFalseWhenNotSet() + { + // Act + bool actual = this.viewModel.Object.IsActive; + + // Assert + Assert.That(actual, Is.False); + } + + [Test] + public void IsActiveShouldReturnTrueWhenSetToTrue() + { + // Act + this.viewModel.Object.IsActive = true; + + // Assert + Assert.That(this.viewModel.Object.IsActive, Is.True); + } + + [Test] + public void IsSelectedeShouldReturnTrueWhenSetToTrue() + { + // Act + this.viewModel.Object.IsSelected = true; + + // Assert + Assert.That(this.viewModel.Object.IsSelected, Is.True); + } + + [Test] + public void IsSelectedShouldReturnFalseWhenNotSet() + { + // Act + bool actual = this.viewModel.Object.IsSelected; + + // Assert + Assert.That(actual, Is.False); + } + + [SetUp] + public void Setup() + { + this.viewModel = new Mock(); + } + + [Test] + public void TitleShouldReturnEmptyWhenNotSet() + { + // Act + string actual = this.viewModel.Object.Title; + + // Assert + Assert.That(actual, Is.Empty); + } + + [Test] + public void TitleShouldReturnHelloWorldWhenSet() + { + // Arrange + string expected = "Hello, World!"; + + // Act + this.viewModel.Object.Title = expected; + + // Assert + Assert.That(this.viewModel.Object.Title, Is.EqualTo(expected)); + } +} diff --git a/FinalEngine.Tests/Editor/ViewModels/Docking/Panes/SceneViewModelTests.cs b/FinalEngine.Tests/Editor/ViewModels/Docking/Panes/SceneViewModelTests.cs new file mode 100644 index 00000000..37cee96f --- /dev/null +++ b/FinalEngine.Tests/Editor/ViewModels/Docking/Panes/SceneViewModelTests.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Tests.Editor.ViewModels.Docking.Panes; + +using System; +using System.Drawing; +using FinalEngine.Editor.Common.Services.Rendering; +using FinalEngine.Editor.ViewModels.Docking.Panes; +using Moq; +using NUnit.Framework; + +[TestFixture] +public sealed class SceneViewModelTests +{ + private Mock sceneRenderer; + + private SceneViewModel viewModel; + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenSceneRendererIsNull() + { + // Act and assert + Assert.Throws(() => + { + new SceneViewModel(null); + }); + } + + [Test] + public void ProjectionSizeShouldInvokeSceneRendererChangeProjectionWhenSet() + { + // Arrange + var projection = new Size(1280, 720); + + // Act + this.viewModel.ProjectionSize = projection; + + // Assert + this.sceneRenderer.Verify(x => x.ChangeProjection(1280, 720), Times.Once()); + } + + [Test] + public void ProjectionSizeShouldReturnHighDefinitionWhenSetToHighDefinition() + { + // Arrange + var expected = new Size(1280, 720); + + // Act + this.viewModel.ProjectionSize = expected; + + // Assert + Assert.That(this.viewModel.ProjectionSize, Is.EqualTo(expected)); + } + + [Test] + public void ProjectionSizeShouldReturnZeroWhenNotSet() + { + // Act + var actual = this.viewModel.ProjectionSize; + + // Assert + Assert.That(actual, Is.EqualTo(Size.Empty)); + } + + [Test] + public void RenderCommandShouldInvokeSceneRendererRenderWhenInvoked() + { + // Act + this.viewModel.RenderCommand.Execute(null); + + // Assert + this.sceneRenderer.Verify(x => x.Render(), Times.Once()); + } + + [SetUp] + public void Setup() + { + this.sceneRenderer = new Mock(); + this.viewModel = new SceneViewModel(this.sceneRenderer.Object); + } + + [Test] + public void TitleShouldReturnSceneViewWhenSet() + { + // Arrange + string expected = "Scene View"; + + // Act + string actual = this.viewModel.Title; + + // Assert + Assert.That(actual, Is.EqualTo(expected)); + } +} diff --git a/FinalEngine.Tests/Editor/ViewModels/Docking/Tools/ToolViewModelBaseTests.cs b/FinalEngine.Tests/Editor/ViewModels/Docking/Tools/ToolViewModelBaseTests.cs new file mode 100644 index 00000000..187f86aa --- /dev/null +++ b/FinalEngine.Tests/Editor/ViewModels/Docking/Tools/ToolViewModelBaseTests.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Tests.Editor.ViewModels.Docking.Tools; + +using FinalEngine.Editor.ViewModels.Docking.Tools; +using Moq; +using NUnit.Framework; + +[TestFixture] +public sealed class ToolViewModelBaseTests +{ + private Mock viewModel; + + [Test] + public void ConstructorShouldNotThrowExceptionWhenInvoked() + { + // Act and assert + Assert.DoesNotThrow(() => + { + new Mock(); + }); + } + + [Test] + public void IsVisibleShouldReturnFalseWhenSetToFalse() + { + // Act + this.viewModel.Object.IsVisible = false; + + // Assert + Assert.That(this.viewModel.Object.IsVisible, Is.False); + } + + [Test] + public void IsVisibleShouldReturnTrueWhenNotSet() + { + // Act + bool actual = this.viewModel.Object.IsVisible; + + // Assert + Assert.That(actual, Is.True); + } + + [SetUp] + public void Setup() + { + this.viewModel = new Mock(); + } +} diff --git a/FinalEngine.Tests/Editor/ViewModels/MainViewModelTests.cs b/FinalEngine.Tests/Editor/ViewModels/MainViewModelTests.cs index d0744435..13293023 100644 --- a/FinalEngine.Tests/Editor/ViewModels/MainViewModelTests.cs +++ b/FinalEngine.Tests/Editor/ViewModels/MainViewModelTests.cs @@ -5,8 +5,12 @@ namespace FinalEngine.Tests.Editor.ViewModels; using System; +using System.Linq; using System.Reflection; +using FinalEngine.Editor.Common.Services.Factories; +using FinalEngine.Editor.Common.Services.Rendering; using FinalEngine.Editor.ViewModels; +using FinalEngine.Editor.ViewModels.Docking.Panes; using FinalEngine.Editor.ViewModels.Interaction; using FinalEngine.Utilities.Extensions; using Microsoft.Extensions.Logging; @@ -18,15 +22,36 @@ public sealed class MainViewModelTests { private Mock> logger; + private Mock sceneRenderer; + + private Mock> sceneViewModelFactory; + private MainViewModel viewModel; + [Test] + public void ConstructorShouldInvokeSceneViewModelFactoryCreateWhenInvoked() + { + // Assert + this.sceneViewModelFactory.Verify(x => x.Create(), Times.Once()); + } + [Test] public void ConstructorShouldThrowArgumentNullExceptionWhenLoggerIsNull() { // Act and assert Assert.Throws(() => { - new MainViewModel(null); + new MainViewModel(null, this.sceneViewModelFactory.Object); + }); + } + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenSceneViewModelFactoryIsNull() + { + // Act and assert + Assert.Throws(() => + { + new MainViewModel(this.logger.Object, null); }); } @@ -53,11 +78,37 @@ public void ExitCommandShouldThrowArgumentNullExceptionWhenCloseableIsNull() }); } + [Test] + public void PanesShouldContainSceneViewModelWhenCreated() + { + // Act + var actual = this.viewModel.Panes.FirstOrDefault(x => + { + return x.GetType() == typeof(SceneViewModel); + }); + + // Assert + Assert.That(actual, Is.Not.Null); + } + + [Test] + public void PanesShouldNotBeNullWhenCreated() + { + // Act + var actual = this.viewModel.Panes; + + // Assert + Assert.That(actual, Is.Not.Null); + } + [SetUp] public void Setup() { this.logger = new Mock>(); - this.viewModel = new MainViewModel(this.logger.Object); + this.sceneViewModelFactory = new Mock>(); + this.sceneRenderer = new Mock(); + this.sceneViewModelFactory.Setup(x => x.Create()).Returns(new SceneViewModel(this.sceneRenderer.Object)); + this.viewModel = new MainViewModel(this.logger.Object, this.sceneViewModelFactory.Object); } [Test] @@ -72,4 +123,14 @@ public void TitleShouldReturnCorrectTitleOnStartup() // Assert Assert.That(actual, Is.EqualTo(expected)); } + + [Test] + public void ToolsShouldNotBeNullWhenCreated() + { + // Act + var actual = this.viewModel.Tools; + + // Assert + Assert.That(actual, Is.Not.Null); + } } diff --git a/FinalEngine.sln b/FinalEngine.sln index 4e853e44..26063458 100644 --- a/FinalEngine.sln +++ b/FinalEngine.sln @@ -64,10 +64,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinalEngine.Audio.OpenAL", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Editor", "Editor", "{37EAC441-4D26-49E2-884D-E1979650C873}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinalEngine.Editor.Desktop", "FinalEngine.Editor.Desktop\FinalEngine.Editor.Desktop.csproj", "{8FF533B9-33BB-4E42-AAE1-95E8F2747264}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinalEngine.Editor.Desktop", "FinalEngine.Editor.Desktop\FinalEngine.Editor.Desktop.csproj", "{8FF533B9-33BB-4E42-AAE1-95E8F2747264}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinalEngine.Editor.ViewModels", "FinalEngine.Editor.ViewModels\FinalEngine.Editor.ViewModels.csproj", "{DB6A0D9E-7199-4721-AFCF-2774080012FD}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinalEngine.Editor.Common", "FinalEngine.Editor.Common\FinalEngine.Editor.Common.csproj", "{5CD4CCAD-5645-43E2-A61F-E2882FEE3EF8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -154,6 +156,10 @@ Global {DB6A0D9E-7199-4721-AFCF-2774080012FD}.Debug|x64.Build.0 = Debug|x64 {DB6A0D9E-7199-4721-AFCF-2774080012FD}.Release|x64.ActiveCfg = Release|Any CPU {DB6A0D9E-7199-4721-AFCF-2774080012FD}.Release|x64.Build.0 = Release|Any CPU + {5CD4CCAD-5645-43E2-A61F-E2882FEE3EF8}.Debug|x64.ActiveCfg = Debug|x64 + {5CD4CCAD-5645-43E2-A61F-E2882FEE3EF8}.Debug|x64.Build.0 = Debug|x64 + {5CD4CCAD-5645-43E2-A61F-E2882FEE3EF8}.Release|x64.ActiveCfg = Release|Any CPU + {5CD4CCAD-5645-43E2-A61F-E2882FEE3EF8}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -179,6 +185,7 @@ Global {D67794A8-89AC-46E0-B303-94110BB382F8} = {B4084D99-8DC5-4E1C-B5BF-9C7351CE71B8} {8FF533B9-33BB-4E42-AAE1-95E8F2747264} = {37EAC441-4D26-49E2-884D-E1979650C873} {DB6A0D9E-7199-4721-AFCF-2774080012FD} = {37EAC441-4D26-49E2-884D-E1979650C873} + {5CD4CCAD-5645-43E2-A61F-E2882FEE3EF8} = {37EAC441-4D26-49E2-884D-E1979650C873} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A278B383-0D14-41B3-A71C-4505E1D503DF} diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs index 8782cdcc..3ae3960b 100644 --- a/SharedAssemblyInfo.cs +++ b/SharedAssemblyInfo.cs @@ -9,8 +9,8 @@ [assembly: AssemblyCopyright("© 2023 Software Antics")] [assembly: AssemblyTrademark("Software Antics™")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2023.1.834.1250")] -[assembly: AssemblyFileVersion("2023.1.834.1250")] +[assembly: AssemblyVersion("2023.1.1337.1042")] +[assembly: AssemblyFileVersion("2023.1.1337.1042")] #if DEBUG [assembly: AssemblyConfiguration("Debug")] #else