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