Update editor
This commit is contained in:
@@ -1,97 +0,0 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal partial class AppStateMachine : IDisposable, IAsyncDisposable
|
||||
{
|
||||
private Dictionary<StateKey, Lazy<IAppState>> _states = new();
|
||||
private IAppState? _current;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public void RegisterState(StateKey key, Func<IAppState> stateFactory)
|
||||
{
|
||||
_states[key] = new(stateFactory);
|
||||
}
|
||||
|
||||
public async Task<Result> 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;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal interface IAppState
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when exiting the state.
|
||||
/// </summary>
|
||||
public ValueTask<Result> OnExitingAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Called when entering the state, right after OnEnteringAsync.
|
||||
/// <paramref name="parameter">can be used to pass data into the state, such as a project to load.</summary>
|
||||
/// </summary>
|
||||
public ValueTask<Result> OnEnteringAsync(object? parameter);
|
||||
|
||||
/// <summary>
|
||||
/// Called when exiting the state, specifically for pose transitions.
|
||||
/// </summary>
|
||||
public ValueTask<Result> OnExitedAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Called when entered the state, specifically after the state has been fully initialized and is ready for interaction.
|
||||
/// </summary>
|
||||
/// <param name="parameter">can be used to pass data into the state, such as a project to load.</param>
|
||||
public ValueTask<Result> OnEnteredAsync(object? parameter);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal enum StateKey
|
||||
{
|
||||
None,
|
||||
Landing,
|
||||
EngineEditor,
|
||||
}
|
||||
@@ -208,7 +208,7 @@ public static partial class AssetDatabase
|
||||
/// <summary>
|
||||
/// Move an asset to a new location by path.
|
||||
/// </summary>
|
||||
/// <param name="oldPath">Current path of the asset.</param>
|
||||
/// <param name="oldPath">CurrentApplication path of the asset.</param>
|
||||
/// <param name="newPath">New path for the asset (relative or absolute).</param>
|
||||
/// <returns>Result indicating success or failure.</returns>
|
||||
public static ValueTask<Result> MoveAssetAsync(string oldPath, string newPath, CancellationToken token = default)
|
||||
|
||||
@@ -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<string>.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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<AssetCommand>(new UnboundedChannelOptions
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
32
Ghost.Editor.Core/Contracts/IInspectorService.cs
Normal file
32
Ghost.Editor.Core/Contracts/IInspectorService.cs
Normal file
@@ -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<InspectorSelectionChangedEventArgs> OnSelectionChanged;
|
||||
|
||||
void SetSelected(IInspectable? inspectable, object? source);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
12
Ghost.Editor.Core/Contracts/IPreviewService.cs
Normal file
12
Ghost.Editor.Core/Contracts/IPreviewService.cs
Normal file
@@ -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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.Editor.Core.Progress;
|
||||
namespace Ghost.Editor.Core.Contracts;
|
||||
|
||||
public interface IProgressService
|
||||
{
|
||||
39
Ghost.Editor.Core/EditorApplication.cs
Normal file
39
Ghost.Editor.Core/EditorApplication.cs
Normal file
@@ -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<T>()
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
28
Ghost.Editor.Core/EditorInjectionAttribute.cs
Normal file
28
Ghost.Editor.Core/EditorInjectionAttribute.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Data\Ghost.Data.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
internal interface IInspectorService
|
||||
{
|
||||
public IInspectable? SelectedInspectable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public event Action? OnSelectionChanged;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
21
Ghost.Editor.Core/Services/InspectorService.cs
Normal file
21
Ghost.Editor.Core/Services/InspectorService.cs
Normal file
@@ -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<InspectorSelectionChangedEventArgs>? OnSelectionChanged;
|
||||
|
||||
public void SetSelected(IInspectable? inspectable, object? source)
|
||||
{
|
||||
if (_selected != inspectable)
|
||||
{
|
||||
_selected = inspectable;
|
||||
OnSelectionChanged?.Invoke(this, new InspectorSelectionChangedEventArgs(source, inspectable));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
35
Ghost.Editor.Core/Services/PreviewService.cs
Normal file
35
Ghost.Editor.Core/Services/PreviewService.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -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<T>()
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user