diff --git a/FinalEngine.ECS/Components/Core/TagComponent.cs b/FinalEngine.ECS/Components/Core/TagComponent.cs index 49a8104d..db84af1f 100644 --- a/FinalEngine.ECS/Components/Core/TagComponent.cs +++ b/FinalEngine.ECS/Components/Core/TagComponent.cs @@ -9,26 +9,26 @@ namespace FinalEngine.ECS.Components.Core; [Category("Core")] public sealed class TagComponent : IEntityComponent, INotifyPropertyChanged { - private string? tag; + private string? name; public event PropertyChangedEventHandler? PropertyChanged; - public string? Tag + public string? Name { get { - return this.tag; + return this.name; } set { - if (this.tag == value) + if (this.name == value) { return; } - this.tag = value; - this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Tag))); + this.name = value; + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Name))); } } } diff --git a/FinalEngine.ECS/FinalEngine.ECS.csproj b/FinalEngine.ECS/FinalEngine.ECS.csproj index e9c3a119..3ff4cad7 100644 --- a/FinalEngine.ECS/FinalEngine.ECS.csproj +++ b/FinalEngine.ECS/FinalEngine.ECS.csproj @@ -29,8 +29,4 @@ - - - - diff --git a/FinalEngine.Editor.Common/Extensions/ServiceCollectionExtensions.cs b/FinalEngine.Editor.Common/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..e2063cc8 --- /dev/null +++ b/FinalEngine.Editor.Common/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +// +// 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 Microsoft.Extensions.DependencyInjection; + +[ExcludeFromCodeCoverage(Justification = "Extensions")] +public static class ServiceCollectionExtensions +{ + public static void AddFactory(this IServiceCollection services) + where TService : class + where TImplementation : class, TService + { + ArgumentNullException.ThrowIfNull(services, 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..caad47c4 --- /dev/null +++ b/FinalEngine.Editor.Common/FinalEngine.Editor.Common.csproj @@ -0,0 +1,40 @@ + + + + net8.0 + enable + All + SA0001 + false + x64 + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/FinalEngine.Editor.Common/Models/Scenes/IScene.cs b/FinalEngine.Editor.Common/Models/Scenes/IScene.cs new file mode 100644 index 00000000..cc98c682 --- /dev/null +++ b/FinalEngine.Editor.Common/Models/Scenes/IScene.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Models.Scenes; + +using System; +using System.Collections.Generic; +using FinalEngine.ECS; + +public interface IScene +{ + IReadOnlyCollection Entities { get; } + + void AddEntity(string tag, Guid uniqueID); + + void RemoveEntity(Guid uniqueIdentifier); + + void Render(); +} diff --git a/FinalEngine.Editor.Common/Models/Scenes/Scene.cs b/FinalEngine.Editor.Common/Models/Scenes/Scene.cs new file mode 100644 index 00000000..0d589830 --- /dev/null +++ b/FinalEngine.Editor.Common/Models/Scenes/Scene.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Models.Scenes; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using FinalEngine.ECS; +using FinalEngine.ECS.Components.Core; +using FinalEngine.ECS.Exceptions; +using FinalEngine.Rendering.Components.Core; +using Microsoft.Extensions.Logging; + +public sealed class Scene : IScene +{ + private readonly ObservableCollection entities; + + private readonly ILogger logger; + + private readonly IEntityWorld world; + + public Scene(ILogger logger, IEntityWorld world) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.world = world ?? throw new ArgumentNullException(nameof(world)); + this.entities = []; + } + + public IReadOnlyCollection Entities + { + get { return this.entities; } + } + + public void AddEntity(string tag, Guid uniqueID) + { + this.logger.LogInformation($"Adding new {nameof(Entity)} to {nameof(Scene)} with ID: '{uniqueID}'."); + + ArgumentException.ThrowIfNullOrWhiteSpace(tag, nameof(tag)); + + var entity = new Entity(uniqueID); + + entity.AddComponent(new TagComponent() + { + Name = tag, + }); + + entity.AddComponent(new TransformComponent()); + + this.world.AddEntity(entity); + this.entities.Add(entity); + + this.logger.LogInformation($"Added {nameof(Entity)} to {nameof(Scene)} with ID: '{uniqueID}'."); + } + + public void RemoveEntity(Guid uniqueIdentifier) + { + this.logger.LogInformation($"Removing {nameof(Entity)} from {nameof(Scene)} with ID: '{uniqueIdentifier}'."); + + var entity = this.entities.FirstOrDefault(x => + { + return x.UniqueIdentifier == uniqueIdentifier; + }); + + if (entity == null) + { + this.logger.LogError($"Failed to locate entity with ID: '{uniqueIdentifier}'."); + throw new EntityNotFoundException(uniqueIdentifier); + } + + this.world.RemoveEntity(entity); + this.entities.Remove(entity); + + this.logger.LogInformation($"Removed {nameof(Entity)} from {nameof(Scene)} with ID: '{uniqueIdentifier}'."); + } + + public void Render() + { + this.world.ProcessAll(GameLoopType.Render); + } +} diff --git a/FinalEngine.Editor.Common/Properties/AssemblyInfo.cs b/FinalEngine.Editor.Common/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..18cf0906 --- /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 library containing services to connect view models to view for the Final Engine editor.")] +[assembly: Guid("3D606010-8CAF-4781-98D1-EB67BCDD8CC1")] diff --git a/FinalEngine.Editor.Common/Services/Application/ApplicationContext.cs b/FinalEngine.Editor.Common/Services/Application/ApplicationContext.cs new file mode 100644 index 00000000..6f8b633f --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Application/ApplicationContext.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Application; + +using System; +using System.IO.Abstractions; +using System.Reflection; +using FinalEngine.Editor.Common.Services.Environment; + +public sealed class ApplicationContext : IApplicationContext +{ + private readonly IEnvironmentContext environment; + + private readonly IFileSystem fileSystem; + + public ApplicationContext(IFileSystem fileSystem, IEnvironmentContext environment) + { + this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + this.environment = environment ?? throw new ArgumentNullException(nameof(environment)); + } + + public string DataDirectory + { + get + { + string directory = this.fileSystem.Path.Combine(this.environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Final Engine"); + + if (!this.fileSystem.Directory.Exists(directory)) + { + this.fileSystem.Directory.CreateDirectory(directory); + } + + return directory; + } + } + + public string Title + { + get { return $"Final Engine - {this.Version}"; } + } + + public Version Version + { + get { return Assembly.GetExecutingAssembly().GetName().Version!; } + } +} diff --git a/FinalEngine.Editor.Common/Services/Application/IApplicationContext.cs b/FinalEngine.Editor.Common/Services/Application/IApplicationContext.cs new file mode 100644 index 00000000..70af45ec --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Application/IApplicationContext.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Application; + +using System; + +public interface IApplicationContext +{ + string DataDirectory { get; } + + string Title { get; } + + Version Version { get; } +} diff --git a/FinalEngine.Editor.Common/Services/Environment/EnvironmentContext.cs b/FinalEngine.Editor.Common/Services/Environment/EnvironmentContext.cs new file mode 100644 index 00000000..3a059819 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Environment/EnvironmentContext.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Environment; + +using System; +using System.Diagnostics.CodeAnalysis; + +[ExcludeFromCodeCoverage(Justification = "Invocation")] +public sealed class EnvironmentContext : IEnvironmentContext +{ + public string GetFolderPath(Environment.SpecialFolder folder) + { + return Environment.GetFolderPath(folder); + } +} diff --git a/FinalEngine.Editor.Common/Services/Environment/IEnvironmentContext.cs b/FinalEngine.Editor.Common/Services/Environment/IEnvironmentContext.cs new file mode 100644 index 00000000..2cb9822d --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Environment/IEnvironmentContext.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Environment; + +using System; + +public interface IEnvironmentContext +{ + string GetFolderPath(Environment.SpecialFolder folder); +} diff --git a/FinalEngine.Editor.Common/Services/Factories/Factory.cs b/FinalEngine.Editor.Common/Services/Factories/Factory.cs new file mode 100644 index 00000000..03eb6138 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Factories/Factory.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Factories; + +using System; + +public sealed class Factory : IFactory +{ + private readonly Func factory; + + 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..0905d49b --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Factories/IFactory.cs @@ -0,0 +1,10 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Factories; + +public interface IFactory +{ + T Create(); +} diff --git a/FinalEngine.Editor.Common/Services/Scenes/ISceneManager.cs b/FinalEngine.Editor.Common/Services/Scenes/ISceneManager.cs new file mode 100644 index 00000000..2df06403 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Scenes/ISceneManager.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Scenes; + +using FinalEngine.Editor.Common.Models.Scenes; + +public interface ISceneManager +{ + IScene ActiveScene { get; } +} diff --git a/FinalEngine.Editor.Common/Services/Scenes/ISceneRenderer.cs b/FinalEngine.Editor.Common/Services/Scenes/ISceneRenderer.cs new file mode 100644 index 00000000..23dd2071 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Scenes/ISceneRenderer.cs @@ -0,0 +1,10 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Scenes; + +public interface ISceneRenderer +{ + void Render(); +} diff --git a/FinalEngine.Editor.Common/Services/Scenes/SceneManager.cs b/FinalEngine.Editor.Common/Services/Scenes/SceneManager.cs new file mode 100644 index 00000000..9dec3152 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Scenes/SceneManager.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Scenes; + +using System; +using FinalEngine.Editor.Common.Models.Scenes; + +public sealed class SceneManager : ISceneManager +{ + public SceneManager(IScene scene) + { + this.ActiveScene = scene ?? throw new ArgumentNullException(nameof(scene)); + } + + public IScene ActiveScene { get; } +} diff --git a/FinalEngine.Editor.Common/Services/Scenes/SceneRenderer.cs b/FinalEngine.Editor.Common/Services/Scenes/SceneRenderer.cs new file mode 100644 index 00000000..fac58e40 --- /dev/null +++ b/FinalEngine.Editor.Common/Services/Scenes/SceneRenderer.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Common.Services.Scenes; + +using System; +using System.Drawing; +using FinalEngine.Rendering; + +public sealed class SceneRenderer : ISceneRenderer +{ + private readonly IRenderDevice renderDevice; + + private readonly IRenderPipeline renderPipeline; + + private readonly ISceneManager sceneManager; + + private bool isInitialized; + + public SceneRenderer(IRenderPipeline renderPipeline, IRenderDevice renderDevice, ISceneManager sceneManager) + { + this.renderPipeline = renderPipeline ?? throw new ArgumentNullException(nameof(renderPipeline)); + this.renderDevice = renderDevice ?? throw new ArgumentNullException(nameof(renderDevice)); + this.sceneManager = sceneManager ?? throw new ArgumentNullException(nameof(sceneManager)); + } + + public void Render() + { + this.Initialize(); + + this.renderDevice.Clear(Color.FromArgb(255, 30, 30, 30)); + this.sceneManager.ActiveScene.Render(); + } + + private void Initialize() + { + if (this.isInitialized) + { + return; + } + + this.renderPipeline.Initialize(); + + this.isInitialized = true; + } +} diff --git a/FinalEngine.Editor.Desktop/App.xaml b/FinalEngine.Editor.Desktop/App.xaml new file mode 100644 index 00000000..3c353475 --- /dev/null +++ b/FinalEngine.Editor.Desktop/App.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FinalEngine.Editor.Desktop/App.xaml.cs b/FinalEngine.Editor.Desktop/App.xaml.cs new file mode 100644 index 00000000..0cacd5dd --- /dev/null +++ b/FinalEngine.Editor.Desktop/App.xaml.cs @@ -0,0 +1,122 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop; + +using System.Diagnostics; +using System.IO.Abstractions; +using System.Windows; +using CommunityToolkit.Mvvm.Messaging; +using FinalEngine.ECS; +using FinalEngine.Editor.Common.Extensions; +using FinalEngine.Editor.Common.Models.Scenes; +using FinalEngine.Editor.Common.Services.Application; +using FinalEngine.Editor.Common.Services.Environment; +using FinalEngine.Editor.Common.Services.Scenes; +using FinalEngine.Editor.Desktop.Services.Actions; +using FinalEngine.Editor.Desktop.Services.Layout; +using FinalEngine.Editor.Desktop.Views; +using FinalEngine.Editor.Desktop.Views.Dialogs.Entities; +using FinalEngine.Editor.Desktop.Views.Dialogs.Layout; +using FinalEngine.Editor.ViewModels; +using FinalEngine.Editor.ViewModels.Dialogs.Entities; +using FinalEngine.Editor.ViewModels.Dialogs.Layout; +using FinalEngine.Editor.ViewModels.Docking; +using FinalEngine.Editor.ViewModels.Inspectors; +using FinalEngine.Editor.ViewModels.Projects; +using FinalEngine.Editor.ViewModels.Scenes; +using FinalEngine.Editor.ViewModels.Services; +using FinalEngine.Editor.ViewModels.Services.Actions; +using FinalEngine.Editor.ViewModels.Services.Entities; +using FinalEngine.Editor.ViewModels.Services.Interactions; +using FinalEngine.Editor.ViewModels.Services.Layout; +using FinalEngine.Rendering; +using FinalEngine.Rendering.OpenGL; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +public partial class App : Application +{ + public App() + { + AppHost = Host.CreateDefaultBuilder() + .ConfigureServices(ConfigureServices) + .Build(); + } + + private static IHost? AppHost { get; set; } + + protected override async void OnExit(ExitEventArgs e) + { + await AppHost!.StopAsync().ConfigureAwait(false); + base.OnExit(e); + } + + protected override async void OnStartup(StartupEventArgs e) + { + await AppHost!.StartAsync().ConfigureAwait(false); + + var viewModel = AppHost.Services.GetRequiredService(); + + var view = new MainView() + { + DataContext = viewModel, + }; + + view.ShowDialog(); + + base.OnStartup(e); + } + + private static void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + services.AddLogging(builder => + { + builder.AddConsole().SetMinimumLevel(Debugger.IsAttached ? LogLevel.Debug : LogLevel.Information); + }); + + services.AddSingleton(WeakReferenceMessenger.Default); + + services.AddTransient(); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + + services.AddTransient(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddFactory(); + services.AddSingleton(); + + services.AddTransient, SaveWindowLayoutView>(); + services.AddTransient, ManageWindowLayoutsView>(); + services.AddTransient, CreateEntityView>(); + + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(x => + { + return new ViewPresenter(x.GetRequiredService>(), x); + }); + } +} diff --git a/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml new file mode 100644 index 00000000..f850d18c --- /dev/null +++ b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml @@ -0,0 +1,7 @@ + diff --git a/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs new file mode 100644 index 00000000..70008906 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Controls.Common; + +using System.Windows.Controls; +using System.Windows.Media; + +/// +/// Interaction logic for IconButton.xaml. +/// +public partial class IconButton : Button +{ + /// + /// Initializes a new instance of the class. + /// + public IconButton() + { + this.InitializeComponent(); + } + + /// + /// Gets or sets the URI source for the icon. + /// + /// + /// The URI source for the icon. + /// + public ImageSource UriSource + { + get { return this.image.Source; } + set { this.image.Source = value; } + } +} diff --git a/FinalEngine.Editor.Desktop/Exceptions/Layout/ToolPaneNotFoundException.cs b/FinalEngine.Editor.Desktop/Exceptions/Layout/ToolPaneNotFoundException.cs new file mode 100644 index 00000000..bb86d3df --- /dev/null +++ b/FinalEngine.Editor.Desktop/Exceptions/Layout/ToolPaneNotFoundException.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Exceptions.Layout; + +using System; + +[Serializable] +public class ToolPaneNotFoundException : Exception +{ + public ToolPaneNotFoundException() + : base("The content identifier could not be matched to a tool pane.") + { + } + + public ToolPaneNotFoundException(string? contentID) + : base($"The content identifier '{contentID}' could not be matched to a tool pane.") + { + this.ContentID = contentID; + } + + public ToolPaneNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + + public string? ContentID { get; } +} diff --git a/FinalEngine.Editor.Desktop/Exceptions/Layout/WindowLayoutNotFoundException.cs b/FinalEngine.Editor.Desktop/Exceptions/Layout/WindowLayoutNotFoundException.cs new file mode 100644 index 00000000..9d63face --- /dev/null +++ b/FinalEngine.Editor.Desktop/Exceptions/Layout/WindowLayoutNotFoundException.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Exceptions.Layout; + +using System; + +[Serializable] +public class WindowLayoutNotFoundException : Exception +{ + public WindowLayoutNotFoundException() + : base("Failed to locate a window layout.") + { + } + + public WindowLayoutNotFoundException(string layoutName) + : base($"Failed to locate a window layout that matches: '{layoutName}'.") + { + this.LayoutName = layoutName; + } + + public WindowLayoutNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + + public string? LayoutName { get; } +} diff --git a/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj b/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj new file mode 100644 index 00000000..1ada7915 --- /dev/null +++ b/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj @@ -0,0 +1,66 @@ + + + + Exe + net8.0-windows + enable + SA0001 + true + All + false + x64 + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + Never + + + + + + + + + + Always + + + diff --git a/FinalEngine.Editor.Desktop/Properties/AssemblyInfo.cs b/FinalEngine.Editor.Desktop/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..29e1d2e2 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Windows; + +[assembly: SupportedOSPlatform("windows")] +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("FinalEngine.Editor.Desktop")] +[assembly: AssemblyDescription("The main editor application for Final Engine")] +[assembly: Guid("18F55601-F1DE-4260-88A8-2F54CA08CA93")] +[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] diff --git a/FinalEngine.Editor.Desktop/Resources/Images/Icons/Settings.png b/FinalEngine.Editor.Desktop/Resources/Images/Icons/Settings.png new file mode 100644 index 00000000..2f263a29 Binary files /dev/null and b/FinalEngine.Editor.Desktop/Resources/Images/Icons/Settings.png differ diff --git a/FinalEngine.Editor.Desktop/Resources/Images/Splashes/splash.png b/FinalEngine.Editor.Desktop/Resources/Images/Splashes/splash.png new file mode 100644 index 00000000..bbd0a3f3 Binary files /dev/null and b/FinalEngine.Editor.Desktop/Resources/Images/Splashes/splash.png differ diff --git a/FinalEngine.Editor.Desktop/Resources/Layouts/default.config b/FinalEngine.Editor.Desktop/Resources/Layouts/default.config new file mode 100644 index 00000000..85106f19 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Resources/Layouts/default.config @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FinalEngine.Editor.Desktop/Selectors/Data/Docking/Panes/PaneTemplateSelector.cs b/FinalEngine.Editor.Desktop/Selectors/Data/Docking/Panes/PaneTemplateSelector.cs new file mode 100644 index 00000000..85a9a739 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Selectors/Data/Docking/Panes/PaneTemplateSelector.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Selectors.Data.Docking.Panes; + +using System.Windows; +using System.Windows.Controls; +using FinalEngine.Editor.ViewModels.Inspectors; +using FinalEngine.Editor.ViewModels.Projects; +using FinalEngine.Editor.ViewModels.Scenes; + +public sealed class PaneTemplateSelector : DataTemplateSelector +{ + public DataTemplate? ConsoleTemplate { get; set; } + + public DataTemplate? EntitySystemsTemplate { get; set; } + + public DataTemplate? ProjectExplorerTemplate { get; set; } + + public DataTemplate? PropertiesTemplate { get; set; } + + public DataTemplate? SceneHierarchyTemplate { get; set; } + + public DataTemplate? SceneViewTemplate { get; set; } + + public override DataTemplate? SelectTemplate(object item, DependencyObject container) + { + return item switch + { + IConsoleToolViewModel => this.ConsoleTemplate, + IEntitySystemsToolViewModel => this.EntitySystemsTemplate, + IProjectExplorerToolViewModel => this.ProjectExplorerTemplate, + IPropertiesToolViewModel => this.PropertiesTemplate, + ISceneHierarchyToolViewModel => this.SceneHierarchyTemplate, + ISceneViewPaneViewModel => this.SceneViewTemplate, + _ => base.SelectTemplate(item, container) + }; + } +} diff --git a/FinalEngine.Editor.Desktop/Selectors/Data/Editing/PropertyTemplateSelector.cs b/FinalEngine.Editor.Desktop/Selectors/Data/Editing/PropertyTemplateSelector.cs new file mode 100644 index 00000000..233394c5 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Selectors/Data/Editing/PropertyTemplateSelector.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Selectors.Data.Editing; + +using System.Windows; +using System.Windows.Controls; +using FinalEngine.Editor.ViewModels.Editing.DataTypes; + +public sealed class PropertyTemplateSelector : DataTemplateSelector +{ + public DataTemplate? BoolPropertyTemplate { get; set; } + + public DataTemplate? DoublePropertyTemplate { get; set; } + + public DataTemplate? FloatPropertyTemplate { get; set; } + + public DataTemplate? IntPropertyTemplate { get; set; } + + public DataTemplate? QuaternionPropertyTemplate { get; set; } + + public DataTemplate? StringPropertyTemplate { get; set; } + + public DataTemplate? Vector2PropertyTemplate { get; set; } + + public DataTemplate? Vector3PropertyTemplate { get; set; } + + public DataTemplate? Vector4PropertyTemplate { get; set; } + + public override DataTemplate? SelectTemplate(object item, DependencyObject container) + { + return item switch + { + StringPropertyViewModel => this.StringPropertyTemplate, + BoolPropertyViewModel => this.BoolPropertyTemplate, + IntPropertyViewModel => this.IntPropertyTemplate, + DoublePropertyViewModel => this.DoublePropertyTemplate, + FloatPropertyViewModel => this.FloatPropertyTemplate, + Vector2PropertyViewModel => this.Vector2PropertyTemplate, + Vector3PropertyViewModel => this.Vector3PropertyTemplate, + Vector4PropertyViewModel => this.Vector4PropertyTemplate, + QuaternionPropertyViewModel => this.QuaternionPropertyTemplate, + _ => base.SelectTemplate(item, container), + }; + } +} diff --git a/FinalEngine.Editor.Desktop/Selectors/Styles/Docking/Panes/PaneStyleSelector.cs b/FinalEngine.Editor.Desktop/Selectors/Styles/Docking/Panes/PaneStyleSelector.cs new file mode 100644 index 00000000..3f7e9642 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Selectors/Styles/Docking/Panes/PaneStyleSelector.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Selectors.Styles.Docking.Panes; + +using System.Windows; +using System.Windows.Controls; +using FinalEngine.Editor.ViewModels.Docking.Panes; +using FinalEngine.Editor.ViewModels.Docking.Tools; + +public sealed class PaneStyleSelector : StyleSelector +{ + public Style? PaneStyle { get; set; } + + public Style? ToolStyle { get; set; } + + public override Style? SelectStyle(object item, DependencyObject container) + { + return item switch + { + IToolViewModel => this.ToolStyle, + IPaneViewModel => this.PaneStyle, + _ => base.SelectStyle(item, container) + }; + } +} diff --git a/FinalEngine.Editor.Desktop/Services/Actions/UserActionRequester.cs b/FinalEngine.Editor.Desktop/Services/Actions/UserActionRequester.cs new file mode 100644 index 00000000..0df9203f --- /dev/null +++ b/FinalEngine.Editor.Desktop/Services/Actions/UserActionRequester.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Services.Actions; + +using System; +using System.Windows; +using FinalEngine.Editor.ViewModels.Services.Actions; +using Microsoft.Extensions.Logging; + +public sealed class UserActionRequester : IUserActionRequester +{ + private readonly ILogger logger; + + public UserActionRequester(ILogger logger) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void RequestOk(string caption, string message) + { + this.logger.LogInformation($"Requesting OK response from user for request: '{message}'."); + MessageBox.Show(message, caption); + } + + public bool RequestYesNo(string caption, string message) + { + this.logger.LogInformation($"Requesting YES/NO response from user for request: '{message}'."); + return MessageBox.Show(message, caption, MessageBoxButton.YesNo) == MessageBoxResult.Yes; + } +} diff --git a/FinalEngine.Editor.Desktop/Services/Layout/LayoutManager.cs b/FinalEngine.Editor.Desktop/Services/Layout/LayoutManager.cs new file mode 100644 index 00000000..07e0daed --- /dev/null +++ b/FinalEngine.Editor.Desktop/Services/Layout/LayoutManager.cs @@ -0,0 +1,150 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Services.Layout; + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using System.Linq; +using System.Windows; +using AvalonDock; +using AvalonDock.Layout.Serialization; +using FinalEngine.Editor.Common.Services.Application; +using FinalEngine.Editor.Desktop.Exceptions.Layout; +using FinalEngine.Editor.Desktop.Views.Docking; +using FinalEngine.Editor.ViewModels.Docking.Tools; +using FinalEngine.Editor.ViewModels.Services.Layout; +using MahApps.Metro.Controls; +using Microsoft.Extensions.Logging; + +public sealed class LayoutManager : ILayoutManager +{ + private static readonly DockingManager Instance = Application.Current.MainWindow.FindChild().DockManager; + + private readonly IApplicationContext application; + + private readonly IFileSystem fileSystem; + + private readonly ILogger logger; + + public LayoutManager( + ILogger logger, + IApplicationContext application, + IFileSystem fileSystem) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + } + + private string LayoutDirectory + { + get + { + string directory = this.fileSystem.Path.Combine(this.application.DataDirectory, "Layouts"); + + if (!this.fileSystem.Directory.Exists(directory)) + { + this.fileSystem.Directory.CreateDirectory(directory); + } + + return directory; + } + } + + public bool ContainsLayout(string layoutName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(layoutName, nameof(layoutName)); + return this.LoadLayoutNames().Contains(layoutName); + } + + public void DeleteLayout(string layoutName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(layoutName, nameof(layoutName)); + + if (!this.ContainsLayout(layoutName)) + { + throw new WindowLayoutNotFoundException(layoutName); + } + + this.logger.LogInformation($"Deleting window layout: '{layoutName}'."); + + this.fileSystem.File.Delete(this.GetLayoutPath(layoutName)); + + this.logger.LogInformation($"Layout deleted."); + } + + public void LoadLayout(string layoutName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(layoutName, nameof(layoutName)); + + if (!this.ContainsLayout(layoutName)) + { + throw new WindowLayoutNotFoundException(layoutName); + } + + this.logger.LogInformation($"Loading window layout: '{layoutName}'."); + + var serializer = new XmlLayoutSerializer(Instance); + serializer.Deserialize(this.GetLayoutPath(layoutName)); + + this.logger.LogInformation("Layout loaded."); + } + + public IEnumerable LoadLayoutNames() + { + var directoryInfo = this.fileSystem.DirectoryInfo.New(this.LayoutDirectory); + var files = directoryInfo.GetFiles("*.config", SearchOption.TopDirectoryOnly); + + return files.Select(x => + { + return this.fileSystem.Path.GetFileNameWithoutExtension(x.Name); + }).ToArray(); + } + + public void ResetLayout() + { + const string defaultLayoutPath = "Resources\\Layouts\\default.config"; + + this.logger.LogInformation("Resting window layout to default layout..."); + + var serializer = new XmlLayoutSerializer(Instance); + serializer.Deserialize(defaultLayoutPath); + + this.logger.LogInformation("Layout reset."); + } + + public void SaveLayout(string layoutName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(layoutName, nameof(layoutName)); + + this.logger.LogInformation($"Saving window layout: '{layoutName}'..."); + + var serializer = new XmlLayoutSerializer(Instance); + serializer.Serialize(this.GetLayoutPath(layoutName)); + + this.logger.LogInformation("Layout saved."); + } + + public void ToggleToolWindow(string contentID) + { + ArgumentException.ThrowIfNullOrWhiteSpace(contentID, nameof(contentID)); + + var tool = Instance.AnchorablesSource.Cast().FirstOrDefault(x => + { + return x.ContentID == contentID; + }) ?? throw new ToolPaneNotFoundException(contentID); + + this.logger.LogInformation($"Toggling tool view visibility for view with ID: '{contentID}'"); + + tool.IsVisible = !tool.IsVisible; + } + + private string GetLayoutPath(string layoutName) + { + return this.fileSystem.Path.Combine(this.LayoutDirectory, $"{layoutName}.config"); + } +} diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/ButtonStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/ButtonStyle.xaml new file mode 100644 index 00000000..779250f0 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/ButtonStyle.xaml @@ -0,0 +1,14 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml new file mode 100644 index 00000000..a4439bda --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml @@ -0,0 +1,27 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/LabelStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/LabelStyle.xaml new file mode 100644 index 00000000..98d1c706 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/LabelStyle.xaml @@ -0,0 +1,9 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/ListBoxItemStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/ListBoxItemStyle.xaml new file mode 100644 index 00000000..74d4d1c9 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/ListBoxItemStyle.xaml @@ -0,0 +1,7 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/TextBoxStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/TextBoxStyle.xaml new file mode 100644 index 00000000..d1e79d9d --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/TextBoxStyle.xaml @@ -0,0 +1,20 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/ToggleButtonStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/ToggleButtonStyle.xaml new file mode 100644 index 00000000..0e6bc3f6 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/ToggleButtonStyle.xaml @@ -0,0 +1,10 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/TransparentListBoxItemStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/TransparentListBoxItemStyle.xaml new file mode 100644 index 00000000..538cb6d0 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/TransparentListBoxItemStyle.xaml @@ -0,0 +1,12 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/TransparentListBoxStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/TransparentListBoxStyle.xaml new file mode 100644 index 00000000..c3c9c49b --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/TransparentListBoxStyle.xaml @@ -0,0 +1,7 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Docking/Panes/PaneStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Docking/Panes/PaneStyle.xaml new file mode 100644 index 00000000..3a064acc --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Docking/Panes/PaneStyle.xaml @@ -0,0 +1,14 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Styles/Docking/Tools/ToolStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Docking/Tools/ToolStyle.xaml new file mode 100644 index 00000000..cb3f0a7d --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Docking/Tools/ToolStyle.xaml @@ -0,0 +1,18 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Views/Dialogs/Entities/CreateEntityView.xaml b/FinalEngine.Editor.Desktop/Views/Dialogs/Entities/CreateEntityView.xaml new file mode 100644 index 00000000..cfae2d6f --- /dev/null +++ b/FinalEngine.Editor.Desktop/Views/Dialogs/Entities/CreateEntityView.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + +