diff --git a/Ghost.Data/Repository/AssetsRepository.cs b/Ghost.Data/Repository/AssetsRepository.cs deleted file mode 100644 index b986fe5..0000000 --- a/Ghost.Data/Repository/AssetsRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ghost.Data.Repository; - -internal class AssetsRepository -{ - -} diff --git a/Ghost.Editor.Core/AppState/AppStateMachine.cs b/Ghost.Editor.Core/AppState/AppStateMachine.cs deleted file mode 100644 index 3dea32d..0000000 --- a/Ghost.Editor.Core/AppState/AppStateMachine.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Ghost.Core; - -namespace Ghost.Editor.Core.AppState; - -internal partial class AppStateMachine : IDisposable, IAsyncDisposable -{ - private Dictionary> _states = new(); - private IAppState? _current; - - private bool _disposed; - - public void RegisterState(StateKey key, Func stateFactory) - { - _states[key] = new(stateFactory); - } - - public async Task TransitionToAsync(StateKey stateKey, object? parameter = null) - { - var previous = _current; - if (!_states.TryGetValue(stateKey, out var next)) - { - return Result.Failure($"State '{stateKey}' not found."); - } - - Result result; - if (previous != null) - { - result = await previous.OnExitingAsync(); - if (result.IsFailure) - { - return result; - } - } - - result = await next.Value.OnEnteringAsync(parameter); - if (result.IsFailure) - { - if (previous != null) - { - await previous.OnEnteredAsync(parameter); - } - - return result; - } - - if (previous != null) - { - result = await previous.OnExitedAsync(); - if (result.IsFailure) - { - await next.Value.OnExitedAsync(); - await previous.OnEnteredAsync(parameter); - return result; - } - } - - result = await next.Value.OnEnteredAsync(parameter); - if (result.IsFailure) - { - await next.Value.OnExitedAsync(); - - if (previous != null) - { - await previous.OnEnteredAsync(parameter); - } - - return result; - } - - _current = next.Value; - - return Result.Success(); - } - - public void Dispose() - { - DisposeAsync().AsTask().Wait(); - } - - public async ValueTask DisposeAsync() - { - if (_disposed) - { - return; - } - - _states.Clear(); - if (_current != null) - { - await _current.OnExitingAsync(); - await _current.OnExitedAsync(); - } - - _current = null; - _disposed = true; - } -} \ No newline at end of file diff --git a/Ghost.Editor.Core/AppState/IAppState.cs b/Ghost.Editor.Core/AppState/IAppState.cs deleted file mode 100644 index 572fa0c..0000000 --- a/Ghost.Editor.Core/AppState/IAppState.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Ghost.Core; - -namespace Ghost.Editor.Core.AppState; - -internal interface IAppState -{ - /// - /// Called when exiting the state. - /// - public ValueTask OnExitingAsync(); - - /// - /// Called when entering the state, right after OnEnteringAsync. - /// can be used to pass data into the state, such as a project to load. - /// - public ValueTask OnEnteringAsync(object? parameter); - - /// - /// Called when exiting the state, specifically for pose transitions. - /// - public ValueTask OnExitedAsync(); - - /// - /// Called when entered the state, specifically after the state has been fully initialized and is ready for interaction. - /// - /// can be used to pass data into the state, such as a project to load. - public ValueTask OnEnteredAsync(object? parameter); -} \ No newline at end of file diff --git a/Ghost.Editor.Core/AppState/StateKey.cs b/Ghost.Editor.Core/AppState/StateKey.cs deleted file mode 100644 index c1126fb..0000000 --- a/Ghost.Editor.Core/AppState/StateKey.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ghost.Editor.Core.AppState; - -internal enum StateKey -{ - None, - Landing, - EngineEditor, -} \ No newline at end of file diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs index 258e079..7c3d0f0 100644 --- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs +++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs @@ -208,7 +208,7 @@ public static partial class AssetDatabase /// /// Move an asset to a new location by path. /// - /// Current path of the asset. + /// CurrentApplication path of the asset. /// New path for the asset (relative or absolute). /// Result indicating success or failure. public static ValueTask MoveAssetAsync(string oldPath, string newPath, CancellationToken token = default) diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs index 3ff3ecb..006ca74 100644 --- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs +++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs @@ -1,5 +1,4 @@ using Ghost.Core; -using Ghost.Data.Services; using System.Collections.Concurrent; using System.Text.Json; @@ -26,7 +25,7 @@ public static partial class AssetDatabase return Result.Failure("AssetsDirectory not initialized"); } - var cacheDir = Path.Combine(AssetsDirectory.Parent!.FullName, ProjectService.CACHE_FOLDER, "ImportedAssets"); + var cacheDir = Path.Combine(AssetsDirectory.Parent!.FullName, EditorApplication.CACHES_FOLDER_NAME, "ImportedAssets"); if (!Directory.Exists(cacheDir)) { Directory.CreateDirectory(cacheDir); diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs index b802e53..e781fcb 100644 --- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs +++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs @@ -1,5 +1,4 @@ using Ghost.Core; -using Ghost.Data.Services; using Microsoft.Data.Sqlite; using System.Text.Json; @@ -19,7 +18,7 @@ public static partial class AssetDatabase throw new InvalidOperationException("AssetsDirectory is not set. Initialize() must be called first."); } - var dbPath = Path.Combine(AssetsDirectory.Parent!.FullName, ProjectService.CACHE_FOLDER, "AssetDatabase.db"); + var dbPath = Path.Combine(AssetsDirectory.Parent!.FullName, EditorApplication.CACHES_FOLDER_NAME, "AssetDatabase.db"); var cacheDir = Path.GetDirectoryName(dbPath); if (!Directory.Exists(cacheDir)) { @@ -301,7 +300,7 @@ public static partial class AssetDatabase var sqlPattern = namePattern.Replace('*', '%').Replace('?', '_'); await using var cmd = s_dbConnection.CreateCommand(); - + // Extract just the filename from the path for matching // SQLite doesn't have a built-in path manipulation, so we search in the full path // and filter by checking if the pattern matches the filename part @@ -319,12 +318,12 @@ public static partial class AssetDatabase // Extract filename and check if it matches the pattern var fileName = Path.GetFileName(path); - + // Convert pattern to regex for proper matching var regexPattern = "^" + System.Text.RegularExpressions.Regex.Escape(namePattern) .Replace("\\*", ".*") .Replace("\\?", ".") + "$"; - + if (System.Text.RegularExpressions.Regex.IsMatch(fileName, regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase)) { if (Guid.TryParse(guidStr, out var guid)) @@ -377,10 +376,10 @@ public static partial class AssetDatabase } // Remove orphaned entries - foreach (var guid in orphanedGuids) - { - await RemoveAssetFromDatabaseAsync(guid, token); - } + foreach (var guid in orphanedGuids) + { + await RemoveAssetFromDatabaseAsync(guid, token); + } } catch { diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs index 87f9e25..883ccf1 100644 --- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs +++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs @@ -1,5 +1,4 @@ using Ghost.Core; -using Ghost.Data.Services; using System.Collections.Concurrent; using System.Text.Json; using System.Text.Json.Serialization; @@ -92,12 +91,7 @@ public static partial class AssetDatabase s_initialized = true; } - if (ProjectService.CurrentProject.Metadata == null) - { - throw new InvalidOperationException("Project metadata is not initialized. Ensure that the project is loaded before accessing the AssetDatabase."); - } - - AssetsDirectory = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(ProjectService.CurrentProject.Path)!, ProjectService.ASSETS_FOLDER)); + AssetsDirectory = new DirectoryInfo(Path.Combine(EditorApplication.CurrentProjectPath, EditorApplication.ASSETS_FOLDER_NAME)); s_commandChannel = Channel.CreateUnbounded(new UnboundedChannelOptions { diff --git a/Ghost.Editor.Core/Inspector/IInspectable.cs b/Ghost.Editor.Core/Contracts/IInspectable.cs similarity index 84% rename from Ghost.Editor.Core/Inspector/IInspectable.cs rename to Ghost.Editor.Core/Contracts/IInspectable.cs index ea90fc9..16cf03b 100644 --- a/Ghost.Editor.Core/Inspector/IInspectable.cs +++ b/Ghost.Editor.Core/Contracts/IInspectable.cs @@ -1,7 +1,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -namespace Ghost.Editor.Core.Inspector; +namespace Ghost.Editor.Core.Contracts; public interface IInspectable { diff --git a/Ghost.Editor.Core/Contracts/IInspectorService.cs b/Ghost.Editor.Core/Contracts/IInspectorService.cs new file mode 100644 index 0000000..9a38c05 --- /dev/null +++ b/Ghost.Editor.Core/Contracts/IInspectorService.cs @@ -0,0 +1,32 @@ +namespace Ghost.Editor.Core.Contracts; + +public class InspectorSelectionChangedEventArgs : EventArgs +{ + public object? Source + { + get; + } + + public IInspectable? Selected + { + get; + } + + public InspectorSelectionChangedEventArgs(object? source, IInspectable? selected) + { + Source = source; + Selected = selected; + } +} + +public interface IInspectorService +{ + IInspectable? Selected + { + get; + } + + event EventHandler OnSelectionChanged; + + void SetSelected(IInspectable? inspectable, object? source); +} \ No newline at end of file diff --git a/Ghost.Editor.Core/Notifications/INotificationService.cs b/Ghost.Editor.Core/Contracts/INotificationService.cs similarity index 76% rename from Ghost.Editor.Core/Notifications/INotificationService.cs rename to Ghost.Editor.Core/Contracts/INotificationService.cs index 6dee627..f96a633 100644 --- a/Ghost.Editor.Core/Notifications/INotificationService.cs +++ b/Ghost.Editor.Core/Contracts/INotificationService.cs @@ -1,6 +1,7 @@ using CommunityToolkit.WinUI.Behaviors; +using Ghost.Editor.Core.Notifications; -namespace Ghost.Editor.Core.Notifications; +namespace Ghost.Editor.Core.Contracts; public interface INotificationService { diff --git a/Ghost.Editor.Core/Contracts/IPreviewService.cs b/Ghost.Editor.Core/Contracts/IPreviewService.cs new file mode 100644 index 0000000..c15510a --- /dev/null +++ b/Ghost.Editor.Core/Contracts/IPreviewService.cs @@ -0,0 +1,12 @@ +namespace Ghost.Editor.Core.Contracts; + +public enum IconSize +{ + Small, + Large +} + +public interface IPreviewService +{ + string GetIconPath(string path, bool isDirectory, IconSize size); +} \ No newline at end of file diff --git a/Ghost.Editor.Core/Progress/IProgressService.cs b/Ghost.Editor.Core/Contracts/IProgressService.cs similarity index 85% rename from Ghost.Editor.Core/Progress/IProgressService.cs rename to Ghost.Editor.Core/Contracts/IProgressService.cs index 6db1c1c..3cb8a3a 100644 --- a/Ghost.Editor.Core/Progress/IProgressService.cs +++ b/Ghost.Editor.Core/Contracts/IProgressService.cs @@ -1,4 +1,4 @@ -namespace Ghost.Editor.Core.Progress; +namespace Ghost.Editor.Core.Contracts; public interface IProgressService { diff --git a/Ghost.Editor.Core/EditorApplication.cs b/Ghost.Editor.Core/EditorApplication.cs new file mode 100644 index 0000000..00226a4 --- /dev/null +++ b/Ghost.Editor.Core/EditorApplication.cs @@ -0,0 +1,39 @@ +using Microsoft.UI.Xaml; + +namespace Ghost.Editor.Core; + +public static class EditorApplication +{ + public const string ASSETS_FOLDER_NAME = "Assets"; + public const string CACHES_FOLDER_NAME = "Caches"; + + private static IServiceProvider? s_serviceProvider; + private static string s_currentProjectPath = string.Empty; + private static string s_currentProjectName = string.Empty; + + internal static Application CurrentApplication => Application.Current; + internal static string CurrentProjectPath => s_currentProjectPath; + internal static string CurrentProjectName => s_currentProjectName; + + internal static void Initialize(IServiceProvider serviceProvider, string projectPath, string projectName) + { + s_serviceProvider = serviceProvider; + s_currentProjectPath = projectPath; + s_currentProjectName = projectName; + } + + public static T GetService() + where T : class + { + if (s_serviceProvider?.GetService(typeof(T)) is not T service) + { + throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices."); + } + + return service; + } + + internal static void Shutdown() + { + } +} \ No newline at end of file diff --git a/Ghost.Editor.Core/EditorInjectionAttribute.cs b/Ghost.Editor.Core/EditorInjectionAttribute.cs new file mode 100644 index 0000000..47bda5f --- /dev/null +++ b/Ghost.Editor.Core/EditorInjectionAttribute.cs @@ -0,0 +1,28 @@ +namespace Ghost.Editor.Core; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] +public class EditorInjectionAttribute : Attribute +{ + public enum ServiceLifetime + { + Singleton, + Transient, + Scoped + } + + public ServiceLifetime Lifetime + { + get; + } + + public Type? ImplementationType + { + get; + } + + public EditorInjectionAttribute(ServiceLifetime lifetime, Type? implementationType = null) + { + Lifetime = lifetime; + ImplementationType = implementationType; + } +} \ No newline at end of file diff --git a/Ghost.Editor.Core/Ghost.Editor.Core.csproj b/Ghost.Editor.Core/Ghost.Editor.Core.csproj index bddef02..ea6f534 100644 --- a/Ghost.Editor.Core/Ghost.Editor.Core.csproj +++ b/Ghost.Editor.Core/Ghost.Editor.Core.csproj @@ -21,7 +21,6 @@ - diff --git a/Ghost.Editor.Core/Inspector/IInspectorService.cs b/Ghost.Editor.Core/Inspector/IInspectorService.cs deleted file mode 100644 index 2b7119c..0000000 --- a/Ghost.Editor.Core/Inspector/IInspectorService.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ghost.Editor.Core.Inspector; - -internal interface IInspectorService -{ - public IInspectable? SelectedInspectable - { - get; - set; - } - - public event Action? OnSelectionChanged; -} \ No newline at end of file diff --git a/Ghost.Editor.Core/Inspector/InspectorService.cs b/Ghost.Editor.Core/Inspector/InspectorService.cs deleted file mode 100644 index 32d2f3f..0000000 --- a/Ghost.Editor.Core/Inspector/InspectorService.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ghost.Editor.Core.Inspector; - -public class InspectorService : IInspectorService -{ - public IInspectable? SelectedInspectable - { - get => field; - set - { - if (field != value) - { - field = value; - OnSelectionChanged?.Invoke(); - } - } - } - - public event Action? OnSelectionChanged; -} \ No newline at end of file diff --git a/Ghost.Editor.Core/SceneGraph/SceneGraphNode.cs b/Ghost.Editor.Core/SceneGraph/SceneGraphNode.cs index 120bc9b..cda5cca 100644 --- a/Ghost.Editor.Core/SceneGraph/SceneGraphNode.cs +++ b/Ghost.Editor.Core/SceneGraph/SceneGraphNode.cs @@ -1,5 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; -using Ghost.Editor.Core.Inspector; +using Ghost.Editor.Core.Contracts; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System.Collections.ObjectModel; diff --git a/Ghost.Editor.Core/Services/InspectorService.cs b/Ghost.Editor.Core/Services/InspectorService.cs new file mode 100644 index 0000000..5512986 --- /dev/null +++ b/Ghost.Editor.Core/Services/InspectorService.cs @@ -0,0 +1,21 @@ +using Ghost.Editor.Core.Contracts; + +namespace Ghost.Editor.Core.Services; + +public class InspectorService : IInspectorService +{ + private IInspectable? _selected; + + public IInspectable? Selected => _selected; + + public event EventHandler? OnSelectionChanged; + + public void SetSelected(IInspectable? inspectable, object? source) + { + if (_selected != inspectable) + { + _selected = inspectable; + OnSelectionChanged?.Invoke(this, new InspectorSelectionChangedEventArgs(source, inspectable)); + } + } +} \ No newline at end of file diff --git a/Ghost.Editor.Core/Notifications/NotificationService.cs b/Ghost.Editor.Core/Services/NotificationService.cs similarity index 91% rename from Ghost.Editor.Core/Notifications/NotificationService.cs rename to Ghost.Editor.Core/Services/NotificationService.cs index ff9abdb..aa0374c 100644 --- a/Ghost.Editor.Core/Notifications/NotificationService.cs +++ b/Ghost.Editor.Core/Services/NotificationService.cs @@ -1,7 +1,9 @@ using CommunityToolkit.WinUI.Behaviors; +using Ghost.Editor.Core.Contracts; +using Ghost.Editor.Core.Notifications; using Microsoft.UI.Xaml.Controls; -namespace Ghost.Editor.Core.Notifications; +namespace Ghost.Editor.Core.Services; public class NotificationService : INotificationService { diff --git a/Ghost.Editor.Core/Services/PreviewService.cs b/Ghost.Editor.Core/Services/PreviewService.cs new file mode 100644 index 0000000..95cc826 --- /dev/null +++ b/Ghost.Editor.Core/Services/PreviewService.cs @@ -0,0 +1,35 @@ +using Ghost.Editor.Core.Contracts; + +namespace Ghost.Editor.Core.Services; + +internal class PreviewService : IPreviewService +{ + public string GetIconPath(string path, bool isDirectory, IconSize size) + { + string iconPath; + if (isDirectory) + { + iconPath = "ms-appx:///Assets/EditorIcons/folder-{0}.png"; + } + else + { + // TODO: Generate preview icons dynamically for known file types like images, meshes, materials, etc. + var ext = Path.GetExtension(path); + iconPath = ext switch + { + ".png" or ".jpg" or ".jpeg" or ".gif" or ".bmp" or ".tiff" or ".svg" => "ms-appx:///Assets/EditorIcons/image-{0}.png", + _ => "ms-appx:///Assets/EditorIcons/document-{0}.png", + }; + } + + var sizeIndex = size switch + { + IconSize.Small => "0", + IconSize.Large => "1", + _ => "0" + }; + + iconPath = string.Format(iconPath, sizeIndex); + return iconPath; + } +} \ No newline at end of file diff --git a/Ghost.Editor.Core/Progress/ProgressService.cs b/Ghost.Editor.Core/Services/ProgressService.cs similarity index 96% rename from Ghost.Editor.Core/Progress/ProgressService.cs rename to Ghost.Editor.Core/Services/ProgressService.cs index 3b31575..78f319e 100644 --- a/Ghost.Editor.Core/Progress/ProgressService.cs +++ b/Ghost.Editor.Core/Services/ProgressService.cs @@ -1,9 +1,10 @@ using CommunityToolkit.WinUI; +using Ghost.Editor.Core.Contracts; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System.Runtime.CompilerServices; -namespace Ghost.Editor.Core.Progress; +namespace Ghost.Editor.Core.Services; public class ProgressService : IProgressService { diff --git a/Ghost.Editor.Core/Utilities/EditorApplication.cs b/Ghost.Editor.Core/Utilities/EditorApplication.cs deleted file mode 100644 index 1110d8d..0000000 --- a/Ghost.Editor.Core/Utilities/EditorApplication.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.UI.Xaml; - -namespace Ghost.Editor.Core.Utilities; - -public static class EditorApplication -{ - private static IServiceProvider? _serviceProvider; - - public static Application Current => Application.Current; - - internal static void Initialize(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public static T GetService() - where T : class - { - if (_serviceProvider?.GetService(typeof(T)) is not T service) - { - throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs."); - } - - return service; - } -} \ No newline at end of file diff --git a/Ghost.Editor.Core/Utilities/FileExtensions.cs b/Ghost.Editor.Core/Utilities/FileExtensions.cs index cc25701..8bfa52b 100644 --- a/Ghost.Editor.Core/Utilities/FileExtensions.cs +++ b/Ghost.Editor.Core/Utilities/FileExtensions.cs @@ -1,12 +1,10 @@ -using Ghost.Data.Models; - namespace Ghost.Editor.Core.Utilities; internal static class FileExtensions { public const string META_FILE_EXTENSION = ".gmeta"; - public const string PROJECT_FILE_EXTENSION = "." + ProjectMetadata.PROJECT_FILE_EXTENSION_NAME; + public const string PROJECT_FILE_EXTENSION = ".gproj"; public const string TEMPLATE_FILE_EXTENSION = ".gtmpl"; public const string SCENE_FILE_EXTENSION = ".gscene"; public const string ASSET_FILE_EXTENSION = ".gasset"; diff --git a/Ghost.Editor.Core/Utilities/TypeCache.cs b/Ghost.Editor.Core/Utilities/TypeCache.cs index c3e8890..c2c076b 100644 --- a/Ghost.Editor.Core/Utilities/TypeCache.cs +++ b/Ghost.Editor.Core/Utilities/TypeCache.cs @@ -29,6 +29,12 @@ public static class TypeCache s_types = loadableTypes.Select(t => t.GetTypeInfo()).ToArray(); } + internal static void Init() + { + // Intentionally left blank. + // This method exists to force the static constructor to run. + } + public static Type[] GetTypes() { return s_types; diff --git a/Ghost.Editor/ActivationHandler.cs b/Ghost.Editor/ActivationHandler.cs index b52f7be..a7296c6 100644 --- a/Ghost.Editor/ActivationHandler.cs +++ b/Ghost.Editor/ActivationHandler.cs @@ -1,30 +1,67 @@ -using Ghost.Data.Resources; -using Ghost.Data.Services; +using Ghost.Editor.Core; using Ghost.Editor.Core.Utilities; +using Ghost.Editor.Models; +using Ghost.Engine; using Microsoft.UI.Xaml; +using System.Reflection; namespace Ghost.Editor; internal static class ActivationHandler { - private static void FolderInitialization() + public static LaunchArguments ParseArguments(ReadOnlySpan args) { - if (!Directory.Exists(DataPath.s_applicationDataFolder)) + var arguments = new LaunchArguments(); + var properties = typeof(LaunchArguments).GetProperties(); + var split = args.Split(' '); + + while (split.MoveNext()) { - Directory.CreateDirectory(DataPath.s_applicationDataFolder); + var range = split.Current; + var arg = args[range.Start..range.End]; + if (arg.Length > 2) + { + if (arg[0] == '-' && arg[1] == '-') + { + var argName = arg[2..]; + foreach (var property in properties) + { + var propName = property.Name; + var attr = property.GetCustomAttributes(false).FirstOrDefault(); + if (attr != null) + { + propName = attr.Name; + } + + if (argName.Equals(propName, StringComparison.OrdinalIgnoreCase)) + { + if (split.MoveNext()) + { + var valueRange = split.Current; + var value = args[valueRange.Start..valueRange.End]; + var convertedValue = Convert.ChangeType(value.ToString(), property.PropertyType); + + property.SetValue(arguments, convertedValue); + break; + } + } + } + } + } } - if (!Directory.Exists(DataPath.s_projectTemplateFolder)) - { - Directory.CreateDirectory(DataPath.s_projectTemplateFolder); - } + return arguments; } - public static void Handle(LaunchActivatedEventArgs args) + public static async Task HandleAsync(LaunchArguments args) { - FolderInitialization(); - ProjectService.EnsureDefaultTemplate(); + await Task.Run(() => + { + TypeCache.Init(); + ((EngineCore)App.GetService()).Init(); + }); - EditorApplication.Initialize(((App)(Application.Current)).Host.Services); + // TODO: Initialize other subsystems here. + // await Task.Delay(10000); // Wait 10 seconds to simulate work. } } \ No newline at end of file diff --git a/Ghost.Editor/App.xaml b/Ghost.Editor/App.xaml index 961b1c6..a329b04 100644 --- a/Ghost.Editor/App.xaml +++ b/Ghost.Editor/App.xaml @@ -9,9 +9,8 @@ - + - diff --git a/Ghost.Editor/App.xaml.cs b/Ghost.Editor/App.xaml.cs index e1ad521..dea3bb0 100644 --- a/Ghost.Editor/App.xaml.cs +++ b/Ghost.Editor/App.xaml.cs @@ -1,15 +1,18 @@ using Ghost.Core; -using Ghost.Editor.Core.AppState; -using Ghost.Editor.Core.Inspector; -using Ghost.Editor.Core.Notifications; -using Ghost.Editor.Core.Progress; -using Ghost.Editor.Utilities; +using Ghost.Editor.Core; +using Ghost.Editor.Core.Contracts; +using Ghost.Editor.Core.Services; +using Ghost.Editor.View.Pages.EngineEditor; +using Ghost.Editor.View.Windows; +using Ghost.Editor.ViewModels.Controls; +using Ghost.Editor.ViewModels.Pages.EngineEditor; +using Ghost.Editor.ViewModels.Windows; +using Ghost.Engine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.UI.Xaml; - -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. +using System.Diagnostics; +using WinUIEx; namespace Ghost.Editor; @@ -53,13 +56,32 @@ public partial class App : Application UseContentRoot(AppContext.BaseDirectory). ConfigureServices((context, services) => { - HostHelper.AddLandingScope(context, services); - HostHelper.AddEngineScope(context, services); + services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + + services.AddTransient(); + + #region Should be deleted + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + #endregion }) .Build(); @@ -81,32 +103,55 @@ public partial class App : Application return service; } - /// - /// Invoked when the application is launched. - /// - /// Details about the launch request and process. protected override async void OnLaunched(LaunchActivatedEventArgs args) { base.OnLaunched(args); + var arguments = ActivationHandler.ParseArguments("--project-path F:/GhostProject/Test2 --project-name Test2"); // args.Arguments + if (!arguments.IsValid()) + { + Exit(); + return; + } + + EditorApplication.Initialize(Host.Services, arguments.ProjectPath, arguments.ProjectName); + + var splashWindow = new SplashWindow(); + splashWindow.Activate(); + Window = splashWindow; + await Host.StartAsync(); - ActivationHandler.Handle(args); + await ActivationHandler.HandleAsync(arguments); - var stateMachine = GetService(); - stateMachine.RegisterState(StateKey.Landing, () => new LandingState()); - stateMachine.RegisterState(StateKey.EngineEditor, () => new EditorState()); + splashWindow.Hide(); - await stateMachine.TransitionToAsync(StateKey.Landing); + var editorWindow = new EngineEditorWindow(); + editorWindow.Activate(); + Window = editorWindow; + + splashWindow.Close(); } private void OnClosed(object? sender, WindowEventArgs args) { - Host.StopAsync().GetAwaiter().GetResult(); - Host.Dispose(); + try + { + Host.StopAsync().GetAwaiter().GetResult(); + Host.Dispose(); + + EditorApplication.Shutdown(); + } + catch (Exception ex) + { + Debugger.BreakForUserUnhandledException(ex); + } } private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { Logger.LogError(e.Exception); +#if DEBUG + Debugger.BreakForUserUnhandledException(e.Exception); +#endif } } \ No newline at end of file diff --git a/Ghost.Editor/Assets/EditorIcons/document-0.png b/Ghost.Editor/Assets/EditorIcons/document-0.png new file mode 100644 index 0000000..f361c50 Binary files /dev/null and b/Ghost.Editor/Assets/EditorIcons/document-0.png differ diff --git a/Ghost.Editor/Assets/EditorIcons/document-1.png b/Ghost.Editor/Assets/EditorIcons/document-1.png new file mode 100644 index 0000000..b094a1e Binary files /dev/null and b/Ghost.Editor/Assets/EditorIcons/document-1.png differ diff --git a/Ghost.Editor/Assets/EditorIcons/folder-0.png b/Ghost.Editor/Assets/EditorIcons/folder-0.png new file mode 100644 index 0000000..e90bb59 Binary files /dev/null and b/Ghost.Editor/Assets/EditorIcons/folder-0.png differ diff --git a/Ghost.Editor/Assets/EditorIcons/folder-1.png b/Ghost.Editor/Assets/EditorIcons/folder-1.png new file mode 100644 index 0000000..8b2ba79 Binary files /dev/null and b/Ghost.Editor/Assets/EditorIcons/folder-1.png differ diff --git a/Ghost.Editor/Assets/EditorIcons/image-0.png b/Ghost.Editor/Assets/EditorIcons/image-0.png new file mode 100644 index 0000000..a177532 Binary files /dev/null and b/Ghost.Editor/Assets/EditorIcons/image-0.png differ diff --git a/Ghost.Editor/Assets/EditorIcons/image-1.png b/Ghost.Editor/Assets/EditorIcons/image-1.png new file mode 100644 index 0000000..a02fea0 Binary files /dev/null and b/Ghost.Editor/Assets/EditorIcons/image-1.png differ diff --git a/Ghost.Editor/Assets/Icon.scale-100.png b/Ghost.Editor/Assets/Icon.scale-100.png index 2f5b942..a9e79bc 100644 Binary files a/Ghost.Editor/Assets/Icon.scale-100.png and b/Ghost.Editor/Assets/Icon.scale-100.png differ diff --git a/Ghost.Editor/Assets/Icon.scale-125.png b/Ghost.Editor/Assets/Icon.scale-125.png index 4bbb04d..c4f1eba 100644 Binary files a/Ghost.Editor/Assets/Icon.scale-125.png and b/Ghost.Editor/Assets/Icon.scale-125.png differ diff --git a/Ghost.Editor/Assets/Icon.scale-150.png b/Ghost.Editor/Assets/Icon.scale-150.png index d70b84b..9b29626 100644 Binary files a/Ghost.Editor/Assets/Icon.scale-150.png and b/Ghost.Editor/Assets/Icon.scale-150.png differ diff --git a/Ghost.Editor/Assets/Icon.scale-200.png b/Ghost.Editor/Assets/Icon.scale-200.png index 462c308..a05a6b9 100644 Binary files a/Ghost.Editor/Assets/Icon.scale-200.png and b/Ghost.Editor/Assets/Icon.scale-200.png differ diff --git a/Ghost.Editor/Assets/Icon.scale-400.png b/Ghost.Editor/Assets/Icon.scale-400.png index 42e4317..2e54086 100644 Binary files a/Ghost.Editor/Assets/Icon.scale-400.png and b/Ghost.Editor/Assets/Icon.scale-400.png differ diff --git a/Ghost.Editor/Assets/Icon.targetsize-16.png b/Ghost.Editor/Assets/Icon.targetsize-16.png index c2be39b..784bda7 100644 Binary files a/Ghost.Editor/Assets/Icon.targetsize-16.png and b/Ghost.Editor/Assets/Icon.targetsize-16.png differ diff --git a/Ghost.Editor/Assets/Icon.targetsize-24.png b/Ghost.Editor/Assets/Icon.targetsize-24.png index f74f603..5a020e4 100644 Binary files a/Ghost.Editor/Assets/Icon.targetsize-24.png and b/Ghost.Editor/Assets/Icon.targetsize-24.png differ diff --git a/Ghost.Editor/Assets/Icon.targetsize-256.png b/Ghost.Editor/Assets/Icon.targetsize-256.png index cfb06dd..1f36225 100644 Binary files a/Ghost.Editor/Assets/Icon.targetsize-256.png and b/Ghost.Editor/Assets/Icon.targetsize-256.png differ diff --git a/Ghost.Editor/Assets/Icon.targetsize-32.png b/Ghost.Editor/Assets/Icon.targetsize-32.png index 33a089f..22268c0 100644 Binary files a/Ghost.Editor/Assets/Icon.targetsize-32.png and b/Ghost.Editor/Assets/Icon.targetsize-32.png differ diff --git a/Ghost.Editor/Assets/Icon.targetsize-48.png b/Ghost.Editor/Assets/Icon.targetsize-48.png index d7dd154..cd3eecc 100644 Binary files a/Ghost.Editor/Assets/Icon.targetsize-48.png and b/Ghost.Editor/Assets/Icon.targetsize-48.png differ diff --git a/Ghost.Editor/Assets/icon.ico b/Ghost.Editor/Assets/icon.ico new file mode 100644 index 0000000..26c99b9 Binary files /dev/null and b/Ghost.Editor/Assets/icon.ico differ diff --git a/Ghost.Editor/Assets/icon.svg b/Ghost.Editor/Assets/icon.svg new file mode 100644 index 0000000..0689f0c --- /dev/null +++ b/Ghost.Editor/Assets/icon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-16.png b/Ghost.Editor/Assets/icon.targetsize-16_altform-lightunplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-16.png rename to Ghost.Editor/Assets/icon.targetsize-16_altform-lightunplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-unplated_targetsize-16.png b/Ghost.Editor/Assets/icon.targetsize-16_altform-unplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-unplated_targetsize-16.png rename to Ghost.Editor/Assets/icon.targetsize-16_altform-unplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-24.png b/Ghost.Editor/Assets/icon.targetsize-24_altform-lightunplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-24.png rename to Ghost.Editor/Assets/icon.targetsize-24_altform-lightunplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-unplated_targetsize-24.png b/Ghost.Editor/Assets/icon.targetsize-24_altform-unplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-unplated_targetsize-24.png rename to Ghost.Editor/Assets/icon.targetsize-24_altform-unplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-256.png b/Ghost.Editor/Assets/icon.targetsize-256_altform-lightunplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-256.png rename to Ghost.Editor/Assets/icon.targetsize-256_altform-lightunplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-unplated_targetsize-256.png b/Ghost.Editor/Assets/icon.targetsize-256_altform-unplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-unplated_targetsize-256.png rename to Ghost.Editor/Assets/icon.targetsize-256_altform-unplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-32.png b/Ghost.Editor/Assets/icon.targetsize-32_altform-lightunplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-32.png rename to Ghost.Editor/Assets/icon.targetsize-32_altform-lightunplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-unplated_targetsize-32.png b/Ghost.Editor/Assets/icon.targetsize-32_altform-unplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-unplated_targetsize-32.png rename to Ghost.Editor/Assets/icon.targetsize-32_altform-unplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-48.png b/Ghost.Editor/Assets/icon.targetsize-48_altform-lightunplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-lightunplated_targetsize-48.png rename to Ghost.Editor/Assets/icon.targetsize-48_altform-lightunplated.png diff --git a/Ghost.Editor/Assets/Icon.altform-unplated_targetsize-48.png b/Ghost.Editor/Assets/icon.targetsize-48_altform-unplated.png similarity index 100% rename from Ghost.Editor/Assets/Icon.altform-unplated_targetsize-48.png rename to Ghost.Editor/Assets/icon.targetsize-48_altform-unplated.png diff --git a/Ghost.Editor/Controls/ViewModelPage.cs b/Ghost.Editor/Controls/ViewModelPage.cs deleted file mode 100644 index 36ba697..0000000 --- a/Ghost.Editor/Controls/ViewModelPage.cs +++ /dev/null @@ -1,38 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using Ghost.Editor.Core.Contracts; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Navigation; - -namespace Ghost.Editor.Controls; - -public abstract partial class ViewModelPage : Page - where VM : ObservableObject -{ - public VM ViewModel - { - get; - } - - protected ViewModelPage(VM viewModel) - { - ViewModel = viewModel; - } - - protected override void OnNavigatedTo(NavigationEventArgs e) - { - base.OnNavigatedTo(e); - if (ViewModel is INavigationAware navigationAware) - { - navigationAware.OnNavigatedTo(e.Parameter); - } - } - - protected override void OnNavigatedFrom(NavigationEventArgs e) - { - base.OnNavigatedFrom(e); - if (ViewModel is INavigationAware navigationAware) - { - navigationAware.OnNavigatedFrom(); - } - } -} \ No newline at end of file diff --git a/Ghost.Editor/Core/AppState/EditorState.cs b/Ghost.Editor/Core/AppState/EditorState.cs deleted file mode 100644 index d620ea1..0000000 --- a/Ghost.Editor/Core/AppState/EditorState.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Ghost.Core; -using Ghost.Data.Models; -using Ghost.Data.Services; -using Ghost.Editor.Core.AssetHandle; -using Ghost.Editor.View.Windows; -using Ghost.Engine; - -namespace Ghost.Editor.Core.AppState; - -internal class EditorState : IAppState -{ - private EngineEditorWindow? _window; - private EngineCore? _engineCore; - - public ValueTask OnExitingAsync() - { - if (App.Window == _window) - { - App.Window = null; - } - - _engineCore?.Dispose(); - - return ValueTask.FromResult(Result.Success()); - } - - public ValueTask OnEnteringAsync(object? parameter) - { - if (parameter is not ProjectMetadataInfo metadataInfo) - { - return ValueTask.FromResult(Result.Failure("Invalid parameter for entering EditorState.")); - } - - ProjectService.CurrentProject = metadataInfo; - - _engineCore = App.GetService(); - _engineCore.Init(); - - _window = App.GetService(); - _window.Activate(); - - App.Window = _window; - - return ValueTask.FromResult(Result.Success()); - } - - public ValueTask OnExitedAsync() - { - _window?.Close(); - _window = null; - - return ValueTask.FromResult(Result.Success()); - } - - public async ValueTask OnEnteredAsync(object? parameter) - { - await AssetDatabase.Initialize(); - return Result.Success(); - } -} \ No newline at end of file diff --git a/Ghost.Editor/Core/AppState/LandingState.cs b/Ghost.Editor/Core/AppState/LandingState.cs deleted file mode 100644 index b4d54db..0000000 --- a/Ghost.Editor/Core/AppState/LandingState.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Ghost.Core; -using Ghost.Editor.View.Windows; - -namespace Ghost.Editor.Core.AppState; - -internal class LandingState : IAppState -{ - private LandingWindow? _window; - - public ValueTask OnExitingAsync() - { - if (App.Window == _window) - { - App.Window = null; - } - - return ValueTask.FromResult(Result.Success()); - } - - public ValueTask OnEnteringAsync(object? parameter) - { - _window = App.GetService(); - _window.Activate(); - - App.Window = _window; - - return ValueTask.FromResult(Result.Success()); - } - - public ValueTask OnExitedAsync() - { - _window?.Close(); - _window = null; - - return ValueTask.FromResult(Result.Success()); - } - - public ValueTask OnEnteredAsync(object? parameter) - { - return ValueTask.FromResult(Result.Success()); - } -} diff --git a/Ghost.Editor/Ghost.Editor.csproj b/Ghost.Editor/Ghost.Editor.csproj index 8cf67fb..1288031 100644 --- a/Ghost.Editor/Ghost.Editor.csproj +++ b/Ghost.Editor/Ghost.Editor.csproj @@ -45,7 +45,106 @@ - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + Designer + + + + MSBuild:Compile @@ -54,11 +153,6 @@ MSBuild:Compile - - - MSBuild:Compile - - MSBuild:Compile @@ -79,11 +173,6 @@ MSBuild:Compile - - - MSBuild:Compile - - MSBuild:Compile diff --git a/Ghost.Editor/Models/LaunchArguments.cs b/Ghost.Editor/Models/LaunchArguments.cs new file mode 100644 index 0000000..630dbeb --- /dev/null +++ b/Ghost.Editor/Models/LaunchArguments.cs @@ -0,0 +1,35 @@ +namespace Ghost.Editor.Models; + +[AttributeUsage(AttributeTargets.Property)] +internal sealed class ArgumentNameAttribute : Attribute +{ + public string Name + { + get; + } + + public ArgumentNameAttribute(string name) + { + Name = name; + } +} + +internal class LaunchArguments +{ + [ArgumentName("project-path")] + public string ProjectPath + { + get; set; + } = string.Empty; + + [ArgumentName("project-name")] + public string ProjectName + { + get; set; + } = string.Empty; + + public bool IsValid() + { + return Directory.Exists(ProjectPath) && !string.IsNullOrWhiteSpace(ProjectName); + } +} \ No newline at end of file diff --git a/Ghost.Editor/Package.appxmanifest b/Ghost.Editor/Package.appxmanifest index 8cb61da..3025c27 100644 --- a/Ghost.Editor/Package.appxmanifest +++ b/Ghost.Editor/Package.appxmanifest @@ -36,7 +36,7 @@ + Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\icon.png" BackgroundColor="transparent"> diff --git a/Ghost.Editor/Themes/Generic.xaml b/Ghost.Editor/Themes/Generic.xaml new file mode 100644 index 0000000..70f6ee1 --- /dev/null +++ b/Ghost.Editor/Themes/Generic.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + 12 + 12 + 24 + 2,2,6,1 + 32 + 24 + 24 + 0 + 0 + 0,1,0,2 + 0,1,0,2 + 9,0,0,1 + 10,0,30,0 + 24 + 12,1,0,3 + 32 + + + + -