Refactor Vector3Field and update project structure
Changed Vector3Field.cs to derive from ValueControl<Vector3> and use NumberBox controls for better UI handling. Changed EditorControls.xaml to update resource paths for new controls. Changed InternalControls.xaml to simplify the resource dictionary by removing unnecessary references. Changed IComponentEditor.cs to reflect updates in the component editor's lifecycle methods. Changed project files for Ghost.Editor and Ghost.Core to include new dependencies and project references. Changed FileExtensions.cs and IInspectorService.cs to align with the new namespace structure. Changed Result.cs to enhance error handling and success checking methods. Changed TypeHandle.cs to improve type handling compatibility. Changed AssemblyInfo.cs files to include new assembly visibility attributes for better encapsulation. Added new graphics-related classes and interfaces in the Ghost.Engine project, including IGraphicsDevice and DX12GraphicsDevice. Added a new Mesh class to handle 3D mesh data and provide methods for creating geometric shapes. Added GraphicsPipeline.cs to manage the graphics rendering loop and device initialization. Added ScenePage.xaml and ScenePage.xaml.cs to create a new page for rendering scenes. Updated HierarchyPage.xaml.cs and InspectorPage.xaml.cs to use the new service locator pattern for service retrieval. Updated LandingWindow.xaml.cs and EngineEditorWindow.xaml.cs to utilize the new service locator pattern for better service access. Updated Logger.cs to enhance logging capabilities with optional stack traces and assertion logging. Updated QueryFilter.cs and QueryEnumerable.cs to use the new TypeHandle structure for improved efficiency. Updated WorldNode.cs and WorldNodeSerializer.cs to enhance serialization and management of world nodes. Updated AssetDatabase and related classes to improve asset management and metadata generation. Updated Ghost.UnitTest.csproj to include new project references and package dependencies for unit tests.
@@ -1,96 +0,0 @@
|
||||
using Ghost.Editor.Event;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ghost.Editor.Controls;
|
||||
|
||||
// TODO: value update event
|
||||
public sealed partial class Vector3Field : Control
|
||||
{
|
||||
private bool _suppressCallback;
|
||||
|
||||
public Vector3 Value
|
||||
{
|
||||
get => new((float)X, (float)Y, (float)Z);
|
||||
set
|
||||
{
|
||||
if (value == Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_suppressCallback = true;
|
||||
|
||||
X = value.X;
|
||||
Y = value.Y;
|
||||
Z = value.Z;
|
||||
|
||||
_suppressCallback = false;
|
||||
}
|
||||
}
|
||||
|
||||
public double X
|
||||
{
|
||||
get => (double)GetValue(XProperty);
|
||||
set => SetValue(XProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty XProperty =
|
||||
DependencyProperty.Register(nameof(X), typeof(double), typeof(Vector3Field), new PropertyMetadata(0.0, ValueChanged));
|
||||
|
||||
public double Y
|
||||
{
|
||||
get => (double)GetValue(YProperty);
|
||||
set => SetValue(YProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty YProperty =
|
||||
DependencyProperty.Register(nameof(Y), typeof(double), typeof(Vector3Field), new PropertyMetadata(0.0, ValueChanged));
|
||||
|
||||
public double Z
|
||||
{
|
||||
get => (double)GetValue(ZProperty);
|
||||
set => SetValue(ZProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ZProperty =
|
||||
DependencyProperty.Register(nameof(Z), typeof(double), typeof(Vector3Field), new PropertyMetadata(0.0, ValueChanged));
|
||||
|
||||
public event ValueChangedEventHandler<Vector3>? OnValueChanged;
|
||||
|
||||
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is Vector3Field vector3Field)
|
||||
{
|
||||
if (vector3Field._suppressCallback)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var oldValue = vector3Field.Value;
|
||||
if (e.Property == XProperty)
|
||||
{
|
||||
var f = (float)(double)e.OldValue;
|
||||
oldValue.X = f;
|
||||
}
|
||||
else if (e.Property == YProperty)
|
||||
{
|
||||
var f = (float)(double)e.OldValue;
|
||||
oldValue.Y = f;
|
||||
}
|
||||
else if (e.Property == ZProperty)
|
||||
{
|
||||
var f = (float)(double)e.OldValue;
|
||||
oldValue.Z = f;
|
||||
}
|
||||
|
||||
vector3Field.OnValueChanged?.Invoke(vector3Field, new ValueChangedEventArgs<Vector3>(oldValue, vector3Field.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3Field()
|
||||
{
|
||||
DefaultStyleKey = typeof(Vector3Field);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Controls/BasicInput/PropertyField.xaml" />
|
||||
<ResourceDictionary Source="/Controls/BasicInput/Vector3Field.xaml" />
|
||||
|
||||
<ResourceDictionary Source="/Controls/Internal/InternalControls.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Controls/Internal/ComponentDataView.xaml" />
|
||||
<ResourceDictionary Source="/Controls/Internal/NavigationTabView.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
@@ -1,24 +0,0 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public abstract class Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the Guid of the asset.
|
||||
/// </summary>
|
||||
public Guid GUID
|
||||
{
|
||||
get;
|
||||
} = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// True if the asset is a folder, false if it is a file.
|
||||
/// </summary>
|
||||
public bool IsFolder
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal void GenerateMetadata()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
interface IComponentEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the component editor is created.
|
||||
/// </summary>
|
||||
/// <param name="componentObject">The component data to edit.</param>
|
||||
/// <param name="container">The container to add the editor controls to.</param>
|
||||
public void Create(ComponentObject componentObject, StackPanel container);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component editor needs to update its UI based on the current state of the component data.
|
||||
/// </summary>
|
||||
/// <param name="componentObject">The component data to edit.</param>
|
||||
public void Update(ComponentObject componentObject);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component editor is destroyed.
|
||||
/// </summary>
|
||||
/// <param name="componentObject">The component data to edit.</param>
|
||||
public void Destroy(ComponentObject componentObject);
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Remove="Assets\icon-256.png" />
|
||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-16.png" />
|
||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-24.png" />
|
||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-256.png" />
|
||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-32.png" />
|
||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-48.png" />
|
||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-16.png" />
|
||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-24.png" />
|
||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-256.png" />
|
||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-32.png" />
|
||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-48.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Icon.scale-100.png" />
|
||||
<None Remove="Assets\Icon.scale-125.png" />
|
||||
<None Remove="Assets\Icon.scale-150.png" />
|
||||
<None Remove="Assets\Icon.scale-200.png" />
|
||||
<None Remove="Assets\Icon.scale-400.png" />
|
||||
<None Remove="Assets\Icon.targetsize-16.png" />
|
||||
<None Remove="Assets\Icon.targetsize-16_altform-unplated.png" />
|
||||
<None Remove="Assets\Icon.targetsize-24.png" />
|
||||
<None Remove="Assets\Icon.targetsize-24_altform-lightunplated.png" />
|
||||
<None Remove="Assets\Icon.targetsize-256.png" />
|
||||
<None Remove="Assets\Icon.targetsize-256_altform-unplated.png" />
|
||||
<None Remove="Assets\Icon.targetsize-32.png" />
|
||||
<None Remove="Assets\Icon.targetsize-32_altform-lightunplated.png" />
|
||||
<None Remove="Assets\Icon.targetsize-48.png" />
|
||||
<None Remove="Assets\Icon.targetsize-48_altform-unplated.png" />
|
||||
<None Remove="Controls\BasicInput\PropertyField.xaml" />
|
||||
<None Remove="Controls\BasicInput\Vector3Field.xaml" />
|
||||
<None Remove="Controls\EditorControls.xaml" />
|
||||
<None Remove="Controls\Internal\ComponentDataView.xaml" />
|
||||
<None Remove="Controls\Internal\InternalControls.xaml" />
|
||||
<None Remove="Controls\Internal\NavigationTabView.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\ConsolePage.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\HierarchyPage.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\InspectorPage.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\ProjectPage.xaml" />
|
||||
<None Remove="View\Pages\Landing\CreateProjectPage.xaml" />
|
||||
<None Remove="View\Pages\Landing\OpenProjectPage.xaml" />
|
||||
<None Remove="View\Windows\EngineEditorWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Remove="App.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.TabbedCommandBar" Version="8.2.250402" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageReference Include="WinUIEx" Version="2.5.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Data\Ghost.Data.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Pages\Landing\CreateProjectPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Window\Landing.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Pages\Landing\OpenProjectPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Misaki.HighPerformance.Unsafe">
|
||||
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Pages\EngineEditor\InspectorPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Pages\EngineEditor\HierarchyPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Pages\EngineEditor\ProjectPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Pages\EngineEditor\ConsolePage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Themes\Override.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\Internal\InternalControls.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Windows\EngineEditorWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\BasicInput\PropertyField.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\EditorControls.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\Layout\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\BasicInput\Vector3Field.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\Internal\ComponentDataView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\Internal\NavigationTabView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals" />
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<Nullable>enable</Nullable>
|
||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<PublishAot>False</PublishAot>
|
||||
<PublishTrimmed>False</PublishTrimmed>
|
||||
<RootNamespace>Ghost.Editor</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Ghost.Editor.Models;
|
||||
|
||||
public enum MessageType
|
||||
{
|
||||
Informational,
|
||||
Success,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Ghost.Editor.Resources;
|
||||
|
||||
internal static class FileExtensions
|
||||
{
|
||||
public const string PROJECT_FILE_EXTENSION = ".ghostproj";
|
||||
public const string TEMPLATE_FILE_EXTENSION = ".ghosttemplate";
|
||||
public const string SCENE_FILE_EXTENSION = ".ghostscene";
|
||||
public const string ASSET_FILE_EXTENSION = ".ghostasset";
|
||||
public const string SHADER_FILE_EXTENSION = ".ghostshader";
|
||||
public const string MATERIAL_FILE_EXTENSION = ".ghostmaterial";
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
|
||||
namespace Ghost.Editor.Services.Contracts;
|
||||
|
||||
internal interface IInspectorService
|
||||
{
|
||||
public IInspectable? SelectedInspectable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public event Action? OnSelectionChanged;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Ghost.Editor.Models;
|
||||
|
||||
namespace Ghost.Editor.Services.Contracts;
|
||||
|
||||
public interface INotificationService
|
||||
{
|
||||
public void ShowNotification(string? message, MessageType type, int duration = 5, string? title = null);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Ghost.Editor.Services.Contracts;
|
||||
|
||||
public interface IProgressService
|
||||
{
|
||||
public void ShowProgress(string message, double progress = 0.0);
|
||||
public void ShowIndeterminateProgress(string message);
|
||||
public void SetProgress(double progress);
|
||||
public void HideProgress();
|
||||
}
|
||||
9
Ghost.Core/Ghost.Core.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.Data.Models;
|
||||
namespace Ghost.Core;
|
||||
|
||||
public readonly struct Result
|
||||
{
|
||||
@@ -22,20 +22,28 @@ public readonly struct Result
|
||||
return new Result(false, message);
|
||||
}
|
||||
|
||||
public void CheckSuccess()
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
throw new InvalidOperationException($"Operation failed: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => success ? "OK" : $"Error: {message}";
|
||||
}
|
||||
|
||||
public readonly struct Result<T>
|
||||
{
|
||||
public readonly bool success;
|
||||
public readonly T? data;
|
||||
public readonly T value;
|
||||
|
||||
public readonly string? message;
|
||||
|
||||
public Result(bool success, T? data, string? message = null)
|
||||
public Result(bool success, T data, string? message = null)
|
||||
{
|
||||
this.success = success;
|
||||
this.data = data;
|
||||
this.value = data;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@@ -46,8 +54,16 @@ public readonly struct Result<T>
|
||||
|
||||
public static Result<T> Error(string? message)
|
||||
{
|
||||
return new Result<T>(false, default, message);
|
||||
return new Result<T>(false, default!, message);
|
||||
}
|
||||
|
||||
public override string ToString() => success ? $"OK: {data}" : $"Error: {message}";
|
||||
public void CheckSuccess()
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
throw new InvalidOperationException($"Operation failed: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => success ? $"OK: {value}" : $"Error: {message}";
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities.Utilities;
|
||||
namespace Ghost.Core;
|
||||
|
||||
internal static class TypeHandle
|
||||
public readonly struct TypeHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the type handle for the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to get the handle for.</typeparam>
|
||||
/// <returns>The type handle as a nint.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nint Get<T>()
|
||||
public readonly IntPtr Value
|
||||
{
|
||||
return typeof(T).TypeHandle.Value;
|
||||
get;
|
||||
}
|
||||
|
||||
private TypeHandle(IntPtr value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -21,13 +20,23 @@ internal static class TypeHandle
|
||||
/// <param name="type">The type to get the handle for.</param>
|
||||
/// <returns>The type handle as a nint.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nint Get(Type type)
|
||||
{
|
||||
return type.TypeHandle.Value;
|
||||
}
|
||||
public static TypeHandle Get(Type type) => new TypeHandle(type.TypeHandle.Value);
|
||||
|
||||
public static Type? ToType(nint handle)
|
||||
/// <summary>
|
||||
/// Gets the type handle for the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to get the handle for.</typeparam>
|
||||
/// <returns>The type handle as a nint.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TypeHandle Get<T>() => Get(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Converts a TypeHandle to a Type.
|
||||
/// </summary>
|
||||
/// <param name="handle">The TypeHandle to convert.</param>
|
||||
/// <returns>The corresponding Type.</returns>
|
||||
public Type? ToType()
|
||||
{
|
||||
return Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(handle));
|
||||
return Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(Value));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
|
||||
@@ -26,4 +26,8 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Data.Models;
|
||||
using Ghost.Core;
|
||||
using Ghost.Data.Models;
|
||||
using Ghost.Data.Repository;
|
||||
using Ghost.Data.Resources;
|
||||
using System.IO.Compression;
|
||||
@@ -197,12 +198,12 @@ internal partial class ProjectService
|
||||
return result;
|
||||
}
|
||||
|
||||
if (await HasProjectAsync(result.data.Path))
|
||||
if (await HasProjectAsync(result.value.Path))
|
||||
{
|
||||
return Result<ProjectMetadataInfo>.Error("Project already exists.");
|
||||
}
|
||||
|
||||
await AddProjectAsync(result.data.Metadata.Name, result.data.Path);
|
||||
await AddProjectAsync(result.value.Metadata.Name, result.value.Path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.UnitTest")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||
158
Ghost.Editor.Core/AssetHandle/AssetDatabase.Meta.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using Ghost.Engine.Services;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
public static partial class AssetDatabase
|
||||
{
|
||||
private static readonly Dictionary<string, Type> _importerTypeLookup = new();
|
||||
|
||||
private static void InitializeMetaData()
|
||||
{
|
||||
if (_watcher == null)
|
||||
{
|
||||
throw new InvalidOperationException("AssetDatabase is not initialized. Ensure that Initialize() is called before registering asset importers.");
|
||||
}
|
||||
|
||||
var importerTypes = TypeCache.GetTypes().Where(t => t.GetCustomAttribute<AssetImporterAttribute>() != null);
|
||||
foreach (var type in importerTypes)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<AssetImporterAttribute>()!;
|
||||
foreach (var extension in attribute.SupportedExtensions)
|
||||
{
|
||||
_importerTypeLookup[extension] = type;
|
||||
}
|
||||
}
|
||||
|
||||
_watcher.Created += OnAssetCreated;
|
||||
_watcher.Deleted += OnAssetDeleted;
|
||||
_watcher.Renamed += OnAssetRenamed;
|
||||
}
|
||||
|
||||
private static Result<string> GetMetaFilePath(string assetPath)
|
||||
{
|
||||
if (Directory.Exists(assetPath))
|
||||
{
|
||||
return Result<string>.Error("Folder does not have meta data");
|
||||
}
|
||||
|
||||
if (Path.GetExtension(assetPath).Equals(".meta", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Result<string>.Error("Asset path cannot be a meta file");
|
||||
}
|
||||
|
||||
return Result<string>.OK(assetPath + ".meta");
|
||||
}
|
||||
|
||||
private static ImporterSettings? GetDefaultSettingsForAsset(string assetPath)
|
||||
{
|
||||
var extension = Path.GetExtension(assetPath);
|
||||
|
||||
if (_importerTypeLookup.TryGetValue(extension, out var importerType))
|
||||
{
|
||||
var settingsType = importerType.BaseType?.GetGenericArguments()[0];
|
||||
if (settingsType == null || !typeof(ImporterSettings).IsAssignableFrom(settingsType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (ImporterSettings?)Activator.CreateInstance(settingsType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void WriteMetaFile(string metaFilePath, AssetMeta metaData)
|
||||
{
|
||||
using var fileStream = File.Create(metaFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
JsonSerializer.Serialize(fileStream, metaData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void GenerateMetaFile(string assetPath)
|
||||
{
|
||||
var metaFileResult = GetMetaFilePath(assetPath);
|
||||
if (!metaFileResult.success)
|
||||
{
|
||||
Logger.LogError(metaFileResult.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(metaFileResult.value))
|
||||
{
|
||||
var existingMeta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.value));
|
||||
if (existingMeta != null && _assetPathLookup.TryGetValue(existingMeta.Guid, out var path))
|
||||
{
|
||||
if (assetPath != path)
|
||||
{
|
||||
existingMeta.Guid = Guid.NewGuid();
|
||||
WriteMetaFile(metaFileResult.value, existingMeta);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultSettings = GetDefaultSettingsForAsset(assetPath);
|
||||
var metaData = new AssetMeta
|
||||
{
|
||||
Guid = Guid.NewGuid(),
|
||||
Settings = defaultSettings
|
||||
};
|
||||
|
||||
WriteMetaFile(metaFileResult.value, metaData);
|
||||
}
|
||||
|
||||
private static void OnAssetCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
GenerateMetaFile(e.FullPath);
|
||||
}
|
||||
|
||||
private static void OnAssetDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var metaFileResult = GetMetaFilePath(e.FullPath);
|
||||
if (metaFileResult.success && File.Exists(metaFileResult.value))
|
||||
{
|
||||
try
|
||||
{
|
||||
var meta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.value));
|
||||
if (meta != null
|
||||
&& _assetPathLookup.TryGetValue(meta.Guid, out var path)
|
||||
&& path == e.FullPath)
|
||||
{
|
||||
_assetPathLookup.Remove(meta.Guid);
|
||||
}
|
||||
|
||||
File.Delete(metaFileResult.value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnAssetRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
var oldMetaPath = e.OldFullPath + ".meta";
|
||||
var newMetaPath = e.FullPath + ".meta";
|
||||
|
||||
if (File.Exists(oldMetaPath))
|
||||
{
|
||||
File.Move(oldMetaPath, newMetaPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
GenerateMetaFile(e.FullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,11 @@ using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public static class AssetDatabase
|
||||
public static partial class AssetDatabase
|
||||
{
|
||||
private static readonly Dictionary<string, Action<string>> _assetOpenHandlers = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
static AssetDatabase()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
RegisterAssetHandles();
|
||||
}
|
||||
|
||||
private static void RegisterAssetHandles()
|
||||
private static void InitializeAssetHandle()
|
||||
{
|
||||
var methods = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes())
|
||||
35
Ghost.Editor.Core/AssetHandle/AssetDatabase.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Ghost.Data.Services;
|
||||
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public static partial class AssetDatabase
|
||||
{
|
||||
private static FileSystemWatcher? _watcher;
|
||||
|
||||
private static readonly Dictionary<Guid, string> _assetPathLookup = new();
|
||||
|
||||
public static DirectoryInfo? AssetsDirectory
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
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));
|
||||
_watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = AssetsDirectory.FullName,
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
InitializeAssetHandle();
|
||||
InitializeMetaData();
|
||||
}
|
||||
}
|
||||
15
Ghost.Editor.Core/AssetHandle/AssetImporterAttribute.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class AssetImporterAttribute : Attribute
|
||||
{
|
||||
public string[] SupportedExtensions
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public AssetImporterAttribute(params string[] supportedExtensions)
|
||||
{
|
||||
SupportedExtensions = supportedExtensions;
|
||||
}
|
||||
}
|
||||
16
Ghost.Editor.Core/AssetHandle/AssetMeta.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
internal class AssetMeta
|
||||
{
|
||||
public Guid Guid
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
public ImporterSettings? Settings
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
5
Ghost.Editor.Core/AssetHandle/ImporterSettings.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public abstract class ImporterSettings
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.Editor.Contracts;
|
||||
namespace Ghost.Editor.Core.Contracts;
|
||||
|
||||
public interface INavigationAware
|
||||
{
|
||||
70
Ghost.Editor.Core/Controls/BasicInput/Vector3Field.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Ghost.Editor.Core.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ghost.Editor.Controls;
|
||||
|
||||
[TemplatePart(Name = "XComponent", Type = typeof(NumberBox))]
|
||||
[TemplatePart(Name = "YComponent", Type = typeof(NumberBox))]
|
||||
[TemplatePart(Name = "ZComponent", Type = typeof(NumberBox))]
|
||||
public sealed partial class Vector3Field : ValueControl<Vector3>
|
||||
{
|
||||
private NumberBox? _xComponent;
|
||||
private NumberBox? _yComponent;
|
||||
private NumberBox? _zComponent;
|
||||
|
||||
public Vector3Field()
|
||||
{
|
||||
DefaultStyleKey = typeof(Vector3Field);
|
||||
}
|
||||
|
||||
protected override void ValueChanged(Vector3 oldValue, Vector3 newValue)
|
||||
{
|
||||
SyncFromValue();
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_xComponent?.ValueChanged -= OnComponentChanged;
|
||||
_yComponent?.ValueChanged -= OnComponentChanged;
|
||||
_zComponent?.ValueChanged -= OnComponentChanged;
|
||||
|
||||
_xComponent = GetTemplateChild("XComponent") as NumberBox;
|
||||
_yComponent = GetTemplateChild("YComponent") as NumberBox;
|
||||
_zComponent = GetTemplateChild("ZComponent") as NumberBox;
|
||||
|
||||
SyncFromValue();
|
||||
|
||||
_xComponent?.ValueChanged += OnComponentChanged;
|
||||
_yComponent?.ValueChanged += OnComponentChanged;
|
||||
_zComponent?.ValueChanged += OnComponentChanged;
|
||||
}
|
||||
|
||||
private void SyncFromValue()
|
||||
{
|
||||
SuppressChangedEvent = true;
|
||||
_xComponent?.Value = Value.X;
|
||||
_yComponent?.Value = Value.Y;
|
||||
_zComponent?.Value = Value.Z;
|
||||
SuppressChangedEvent = false;
|
||||
}
|
||||
|
||||
private void OnComponentChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
|
||||
{
|
||||
if (SuppressChangedEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newValue = new Vector3(
|
||||
(float)(_xComponent?.Value ?? 0),
|
||||
(float)(_yComponent?.Value ?? 0),
|
||||
(float)(_zComponent?.Value ?? 0));
|
||||
|
||||
RiseChangedEvent(Value, newValue);
|
||||
Value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -22,17 +22,17 @@
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="X" />
|
||||
<NumberBox Grid.Column="1" Value="{Binding X, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
|
||||
<NumberBox x:Name="XComponent" Grid.Column="1" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Text="Y" />
|
||||
<NumberBox Grid.Column="3" Value="{TemplateBinding Y}" />
|
||||
<NumberBox x:Name="YComponent" Grid.Column="3" />
|
||||
<TextBlock
|
||||
Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Text="Z" />
|
||||
<NumberBox Grid.Column="5" Value="{TemplateBinding Z}" />
|
||||
<NumberBox x:Name="ZComponent" Grid.Column="5" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
13
Ghost.Editor.Core/Controls/ControlsDictionary.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Ghost.Editor.Core.Controls;
|
||||
|
||||
public partial class ControlsDictionary : ResourceDictionary
|
||||
{
|
||||
private const string _DICTIONARY_PATH = "ms-appx:///Ghost.Editor.Core/Controls/ControlsDictionary.xaml";
|
||||
|
||||
public ControlsDictionary()
|
||||
{
|
||||
Source = new Uri(_DICTIONARY_PATH, UriKind.Absolute);
|
||||
}
|
||||
}
|
||||
10
Ghost.Editor.Core/Controls/ControlsDictionary.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///Ghost.Editor.Core/Controls/BasicInput/PropertyField.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Ghost.Editor.Core/Controls/BasicInput/Vector3Field.xaml" />
|
||||
|
||||
<ResourceDictionary Source="ms-appx:///Ghost.Editor.Core/Controls/Internal/ComponentDataView.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Ghost.Editor.Core/Controls/Internal/NavigationTabView.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
@@ -1,25 +1,27 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Resources;
|
||||
using Ghost.Editor.Utilities;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using Ghost.Entities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Controls.Internal;
|
||||
|
||||
internal unsafe sealed partial class ComponentDataView : Control
|
||||
{
|
||||
private delegate void EditorUpdate();
|
||||
|
||||
private StackPanel? _contentContainer;
|
||||
|
||||
private readonly World? _world;
|
||||
private readonly Entity _entity = Entity.Invalid;
|
||||
private readonly Type? _componentType;
|
||||
|
||||
private EventHandler<object>? _updateHandler;
|
||||
private IComponentEditor? _customEditor;
|
||||
private ComponentEditor? _customEditor;
|
||||
private PropertyField[]? _propertyFields;
|
||||
private EditorUpdate? _editorUpdate;
|
||||
|
||||
public string HeaderText
|
||||
{
|
||||
@@ -36,7 +38,8 @@ internal unsafe sealed partial class ComponentDataView : Control
|
||||
|
||||
Unloaded += (s, e) =>
|
||||
{
|
||||
CompositionTarget.Rendering -= _updateHandler;
|
||||
_customEditor?.Destroy();
|
||||
|
||||
_contentContainer = null;
|
||||
_customEditor = null;
|
||||
_propertyFields = null;
|
||||
@@ -59,7 +62,7 @@ internal unsafe sealed partial class ComponentDataView : Control
|
||||
ReBuild();
|
||||
}
|
||||
|
||||
private void ReflectionUpdate(object? sender, object e)
|
||||
private void ReflectionUpdate()
|
||||
{
|
||||
if (_propertyFields == null)
|
||||
{
|
||||
@@ -72,9 +75,9 @@ internal unsafe sealed partial class ComponentDataView : Control
|
||||
}
|
||||
}
|
||||
|
||||
private void CustomEditorUpdate(object? sender, object e)
|
||||
private void CustomEditorUpdate()
|
||||
{
|
||||
_customEditor!.Update(new ComponentObject(_world!, _entity));
|
||||
_customEditor!.Update();
|
||||
}
|
||||
|
||||
public void ReBuild()
|
||||
@@ -92,13 +95,14 @@ internal unsafe sealed partial class ComponentDataView : Control
|
||||
|
||||
var componentObject = new ComponentObject(_world, _entity);
|
||||
var editorType = TypeCache.GetTypes().FirstOrDefault(t =>
|
||||
typeof(IComponentEditor).IsAssignableFrom(t) &&
|
||||
typeof(ComponentEditor).IsAssignableFrom(t) &&
|
||||
t.GetCustomAttribute<CustomEditorAttribute>()?.TargetType.IsAssignableFrom(_componentType) == true);
|
||||
|
||||
if (editorType != null)
|
||||
{
|
||||
_customEditor = (IComponentEditor)Activator.CreateInstance(editorType)!;
|
||||
_customEditor.Create(componentObject, _contentContainer);
|
||||
_customEditor = (ComponentEditor)Activator.CreateInstance(editorType)!;
|
||||
_customEditor.Initialize(componentObject);
|
||||
_customEditor.Create(_contentContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -108,7 +112,7 @@ internal unsafe sealed partial class ComponentDataView : Control
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
var component = _world.ComponentStorage.ComponentPools[_componentType.TypeHandle.Value].Get(_entity);
|
||||
var component = _world.ComponentStorage.ComponentPools[TypeHandle.Get(_componentType)].Get(_entity);
|
||||
var propertyField = PropertyField.Create(field.Name, field, component);
|
||||
|
||||
_propertyFields[i] = propertyField;
|
||||
@@ -116,7 +120,21 @@ internal unsafe sealed partial class ComponentDataView : Control
|
||||
}
|
||||
}
|
||||
|
||||
_updateHandler = _customEditor == null ? ReflectionUpdate : CustomEditorUpdate;
|
||||
CompositionTarget.Rendering += _updateHandler;
|
||||
_editorUpdate = _customEditor == null ? ReflectionUpdate : CustomEditorUpdate;
|
||||
_editorUpdate();
|
||||
|
||||
_world.ComponentChanged += OnComponentChanged;
|
||||
}
|
||||
|
||||
private void OnComponentChanged(World world, Entity entity, Type type)
|
||||
{
|
||||
if (world != _world
|
||||
|| entity != _entity
|
||||
|| type != _componentType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_editorUpdate?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Editor.Contracts;
|
||||
using Ghost.Editor.Core.Contracts;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Controls.Internal;
|
||||
@@ -18,7 +19,9 @@ public sealed partial class NavigationTabView : TabView
|
||||
{
|
||||
public NavigationTabView()
|
||||
{
|
||||
this.SelectionChanged += NavigationTabView_SelectionChanged;
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
VerticalAlignment = VerticalAlignment.Stretch;
|
||||
SelectionChanged += NavigationTabView_SelectionChanged;
|
||||
}
|
||||
|
||||
private void NavigationTabView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
70
Ghost.Editor.Core/Controls/ValueControl.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Ghost.Editor.Core.Event;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Core.Controls;
|
||||
|
||||
public partial class ValueControl<T> : Control
|
||||
{
|
||||
private bool _suppressChangedEvent;
|
||||
|
||||
protected bool SuppressChangedEvent
|
||||
{
|
||||
get => _suppressChangedEvent;
|
||||
set => _suppressChangedEvent = value;
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get => (T)GetValue(ValueProperty);
|
||||
set
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(Value, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetValue(ValueProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(T), typeof(ValueControl<T>), new PropertyMetadata(default(T), ChangedCallback));
|
||||
|
||||
public event ValueChangedEventHandler<T>? OnValueChanged;
|
||||
|
||||
private static void ChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ValueControl<T> valueControl)
|
||||
{
|
||||
valueControl.ValueChanged((T)e.OldValue, (T)e.NewValue);
|
||||
|
||||
if (!valueControl._suppressChangedEvent)
|
||||
{
|
||||
valueControl.OnValueChanged?.Invoke(valueControl, new((T)e.OldValue, (T)e.NewValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ValueChanged(T oldValue, T newValue)
|
||||
{
|
||||
}
|
||||
|
||||
protected void RiseChangedEvent(T oldValue, T newValue)
|
||||
{
|
||||
OnValueChanged?.Invoke(this, new(oldValue, newValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value without notifying the change event.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value to set.</param>
|
||||
/// <remarks>This method only suppresses the change event notification, not the <see cref="ValueChanged(T, T)"/> method.
|
||||
/// Useful when you need to change the value programmatically without triggering the change event.</remarks>
|
||||
public void SetValueWithoutNotifying(T value)
|
||||
{
|
||||
_suppressChangedEvent = true;
|
||||
SetValue(ValueProperty, value);
|
||||
_suppressChangedEvent = false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.Editor.Event;
|
||||
namespace Ghost.Editor.Core.Event;
|
||||
|
||||
public delegate void ValueChangedEventHandler<T>(object? sender, ValueChangedEventArgs<T> args);
|
||||
|
||||
48
Ghost.Editor.Core/Ghost.Editor.Core.csproj
Normal file
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>Ghost.Editor.Core</RootNamespace>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Data\Ghost.Data.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\BasicInput\PropertyField.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Controls\BasicInput\Vector3Field.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Controls\ControlsDictionary.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Controls\Internal\ComponentDataView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Controls\Internal\NavigationTabView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Data\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
40
Ghost.Editor.Core/Inspector/ComponentEditor.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
public abstract class ComponentEditor
|
||||
{
|
||||
private ComponentObject _componentObject;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the underlying component object used by this class to manage its functionality.
|
||||
/// </summary>
|
||||
protected ComponentObject ComponentObject => _componentObject;
|
||||
|
||||
internal void Initialize(ComponentObject componentObject)
|
||||
{
|
||||
_componentObject = componentObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component editor is created.
|
||||
/// </summary>
|
||||
/// <param name="container">The container to add the editor controls to.</param>
|
||||
public virtual void Create(StackPanel container)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component editor needs to update its UI based on the current state of the component data.
|
||||
/// </summary>
|
||||
public virtual void Update()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component editor is destroyed.
|
||||
/// </summary>
|
||||
public virtual void Destroy()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using Ghost.Editor.Contracts;
|
||||
|
||||
namespace Ghost.Editor.Services.Contracts;
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
internal interface IInspectorService
|
||||
{
|
||||
@@ -1,7 +1,4 @@
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Services.Contracts;
|
||||
|
||||
namespace Ghost.Editor.Services;
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
public class InspectorService : IInspectorService
|
||||
{
|
||||
@@ -1,8 +1,9 @@
|
||||
using Ghost.Editor.Models;
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
|
||||
namespace Ghost.Editor.Services.Contracts;
|
||||
namespace Ghost.Editor.Core.Notifications;
|
||||
|
||||
public interface INotificationService
|
||||
{
|
||||
public void ShowNotification(string? message, MessageType type, int duration = 5, string? title = null);
|
||||
public void ShowNotification(Notification notification);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.Editor.Models;
|
||||
namespace Ghost.Editor.Core.Notifications;
|
||||
|
||||
public enum MessageType
|
||||
{
|
||||
@@ -1,10 +1,7 @@
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Ghost.Editor.Models;
|
||||
using Ghost.Editor.Services.Contracts;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
|
||||
namespace Ghost.Editor.Services;
|
||||
namespace Ghost.Editor.Core.Notifications;
|
||||
|
||||
public class NotificationService : INotificationService
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.Editor.Services.Contracts;
|
||||
namespace Ghost.Editor.Core.Progress;
|
||||
|
||||
public interface IProgressService
|
||||
{
|
||||
@@ -1,10 +1,9 @@
|
||||
using CommunityToolkit.WinUI;
|
||||
using Ghost.Editor.Services.Contracts;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Editor.Services;
|
||||
namespace Ghost.Editor.Core.Progress;
|
||||
|
||||
public class ProgressService : IProgressService
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Resources;
|
||||
namespace Ghost.Editor.Core.Resources;
|
||||
|
||||
public static class EditorIconSource
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ghost.Editor.Resources;
|
||||
namespace Ghost.Editor.Core.Resources;
|
||||
|
||||
internal static class FileExtensions
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Resources;
|
||||
namespace Ghost.Editor.Core.Resources;
|
||||
|
||||
internal static class StaticResource
|
||||
{
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Editor.Resources;
|
||||
using Ghost.Editor.Services.Contracts;
|
||||
using Ghost.Editor.Core.Progress;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
@@ -1,6 +1,6 @@
|
||||
using Ghost.Editor.Controls.Internal;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Resources;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Engine.Editor;
|
||||
using Ghost.Entities;
|
||||
using Microsoft.UI.Text;
|
||||
@@ -13,16 +13,23 @@ namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
public partial class EntityNode : SceneGraphNode
|
||||
{
|
||||
private WorldNode _owner;
|
||||
private readonly Entity _entity;
|
||||
public WorldNode Owner
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Entity Entity
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Entity;
|
||||
|
||||
public EntityNode(WorldNode owner, Entity entity, string name)
|
||||
{
|
||||
_owner = owner;
|
||||
_entity = entity;
|
||||
Owner = owner;
|
||||
Entity = entity;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +55,7 @@ public partial class EntityNode : IInspectable
|
||||
};
|
||||
var idText = new TextBlock
|
||||
{
|
||||
Text = $"ID: {_entity.ID} Generation: {_entity.Generation}",
|
||||
Text = $"ID: {Entity.ID} Generation: {Entity.Generation}",
|
||||
Margin = new Thickness(5, 7, 0, 0),
|
||||
Opacity = 0.75,
|
||||
Style = Application.Current.Resources["CaptionTextBlockStyle"] as Style
|
||||
@@ -79,20 +86,20 @@ public partial class EntityNode : IInspectable
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
|
||||
foreach (var (typeHandle, componentPtr) in _owner.World.EntityManager.GetComponentsUnsafe(_entity))
|
||||
foreach (var (typeHandle, componentPtr) in Owner.World.EntityManager.GetComponentsUnsafe(Entity))
|
||||
{
|
||||
if (componentPtr == IntPtr.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(typeHandle));
|
||||
var type = typeHandle.ToType();
|
||||
if (type == null || type.GetCustomAttribute<HideEditorAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dataView = new ComponentDataView(type.Name, _owner.World, _entity, type);
|
||||
var dataView = new ComponentDataView(type.Name, Owner.World, Entity, type);
|
||||
root.Children.Add(dataView);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ghost.Editor.Core.AssetHandle;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Serializer;
|
||||
using Ghost.Editor.Resources;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Entities;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -2,7 +2,6 @@
|
||||
using Ghost.Engine.Utilities;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Utilities;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -107,7 +106,7 @@ internal class WorldNodeSerializer : JsonConverter<WorldNode>
|
||||
{
|
||||
foreach (var kvp in value.World.ComponentStorage.ComponentPools)
|
||||
{
|
||||
var type = TypeHandle.ToType(kvp.Key) ?? throw new Exception($"Type {kvp.Key} not found.");
|
||||
var type = kvp.Key.ToType() ?? throw new Exception($"Type {kvp.Key} not found.");
|
||||
var typeName = type.AssemblyQualifiedName ?? type.Name;
|
||||
|
||||
writer.WriteArray(typeName, kvp.Value.Enumerate(), data =>
|
||||
26
Ghost.Editor.Core/Utilities/EditorApplication.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
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,7 +1,7 @@
|
||||
using Ghost.Entities;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Utilities;
|
||||
namespace Ghost.Editor.Core.Utilities;
|
||||
|
||||
public static class TypeCache
|
||||
{
|
||||
@@ -31,7 +31,7 @@ public static class ComponentTypeCache
|
||||
{
|
||||
var world = World.GetWorld(i);
|
||||
var typeHandles = world.ComponentStorage.ComponentPools.Keys;
|
||||
_componentTypes[i] = typeHandles.Select(handle => Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(handle))).ToArray();
|
||||
_componentTypes[i] = typeHandles.Select(handle => handle.ToType()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ghost.Data.Resources;
|
||||
using Ghost.Data.Services;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.IO;
|
||||
|
||||
namespace Ghost.Editor;
|
||||
|
||||
@@ -24,5 +24,7 @@ internal static class ActivationHandler
|
||||
{
|
||||
FolderInitialization();
|
||||
ProjectService.EnsureDefaultTemplate();
|
||||
|
||||
EditorApplication.Initialize(((App)(Application.Current)).Host.Services);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application
|
||||
x:Class="Ghost.Editor.EditorApplication"
|
||||
x:Class="Ghost.Editor.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:core="using:Ghost.Editor.Core.Controls"
|
||||
xmlns:local="using:Ghost.Editor">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<XamlControlsResources Source="/Controls/EditorControls.xaml" />
|
||||
<core:ControlsDictionary />
|
||||
<ResourceDictionary Source="/Themes/Override.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ghost.Editor.Core.AppState;
|
||||
using Ghost.Editor.Services;
|
||||
using Ghost.Editor.Services.Contracts;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Notifications;
|
||||
using Ghost.Editor.Core.Progress;
|
||||
using Ghost.Editor.Utilities;
|
||||
using Ghost.Engine.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -15,16 +16,16 @@ namespace Ghost.Editor;
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class EditorApplication : Application
|
||||
public partial class App : Application
|
||||
{
|
||||
private Window? _window;
|
||||
|
||||
internal static Window? Window
|
||||
{
|
||||
get => (Current as EditorApplication)!._window;
|
||||
get => (Current as App)!._window;
|
||||
set
|
||||
{
|
||||
if (Current is EditorApplication app)
|
||||
if (Current is App app)
|
||||
{
|
||||
app._window = value;
|
||||
}
|
||||
@@ -40,7 +41,7 @@ public partial class EditorApplication : Application
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
internal EditorApplication()
|
||||
internal App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
@@ -64,12 +65,12 @@ public partial class EditorApplication : Application
|
||||
|
||||
internal static IServiceScope CreateScope()
|
||||
{
|
||||
return (Current as EditorApplication)!.Host.Services.CreateScope();
|
||||
return (Current as App)!.Host.Services.CreateScope();
|
||||
}
|
||||
|
||||
public static T GetService<T>() where T : class
|
||||
{
|
||||
if ((Current as EditorApplication)!.Host.Services.GetService(typeof(T)) is not T service)
|
||||
if ((Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
|
||||
{
|
||||
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.App")]
|
||||
[assembly: InternalsVisibleTo("Ghost.UnitTest")]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
namespace Ghost.Editor.AssetHandle;
|
||||
|
||||
public abstract class Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the Guid of the asset.
|
||||
/// </summary>
|
||||
public Guid GUID
|
||||
{
|
||||
get;
|
||||
} = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// True if the asset is a folder, false if it is a file.
|
||||
/// </summary>
|
||||
public bool IsFolder
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal void GenerateMetadata()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.AssetHandle;
|
||||
|
||||
public static class AssetDatabase
|
||||
{
|
||||
private static readonly Dictionary<string, Action<string>> _assetOpenHandlers = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly Dictionary<string, Func<string, Task>> _asyncAssetOpenHandler = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
static AssetDatabase()
|
||||
{
|
||||
RegisterAssetHandles();
|
||||
}
|
||||
|
||||
private static void RegisterAssetHandles()
|
||||
{
|
||||
var methods = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
.Where(m => m.GetCustomAttribute<AssetOpenHandlerAttribute>() != null &&
|
||||
m.GetParameters().Length == 1 &&
|
||||
m.GetParameters()[0].ParameterType == typeof(string));
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var attr = method.GetCustomAttribute<AssetOpenHandlerAttribute>()!;
|
||||
var del = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), method);
|
||||
foreach (var ext in attr.Extensions)
|
||||
{
|
||||
if (_assetOpenHandlers.ContainsKey(ext))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate handler for extension '{ext}'");
|
||||
}
|
||||
|
||||
_assetOpenHandlers[ext] = del;
|
||||
}
|
||||
}
|
||||
|
||||
var asyncMethods = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
.Where(m => m.GetCustomAttribute<AsyncAssetOpenHandlerAttribute>() != null &&
|
||||
m.GetParameters().Length == 1 &&
|
||||
m.GetParameters()[0].ParameterType == typeof(string) &&
|
||||
m.ReturnType == typeof(Task));
|
||||
|
||||
foreach (var method in asyncMethods)
|
||||
{
|
||||
var attr = method.GetCustomAttribute<AsyncAssetOpenHandlerAttribute>()!;
|
||||
var del = (Func<string, Task>)Delegate.CreateDelegate(typeof(Func<string, Task>), method);
|
||||
foreach (var ext in attr.Extensions)
|
||||
{
|
||||
if (_asyncAssetOpenHandler.ContainsKey(ext))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate async handler for extension '{ext}'");
|
||||
}
|
||||
_asyncAssetOpenHandler[ext] = del;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask OpenAsset(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
if (_assetOpenHandlers.TryGetValue(extension, out var handler))
|
||||
{
|
||||
handler(path);
|
||||
}
|
||||
else if (_asyncAssetOpenHandler.TryGetValue(extension, out var asyncHandler))
|
||||
{
|
||||
await asyncHandler(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
namespace Ghost.Editor.AssetHandle;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class AssetOpenHandlerAttribute : Attribute
|
||||
{
|
||||
public string[] Extensions
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public AssetOpenHandlerAttribute(params string[] extensions)
|
||||
{
|
||||
Extensions = extensions.Select(e => e.StartsWith(".") ? e.ToLowerInvariant() : "." + e.ToLowerInvariant()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class AsyncAssetOpenHandlerAttribute : Attribute
|
||||
{
|
||||
public string[] Extensions
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public AsyncAssetOpenHandlerAttribute(params string[] extensions)
|
||||
{
|
||||
Extensions = extensions.Select(e => e.StartsWith(".") ? e.ToLowerInvariant() : "." + e.ToLowerInvariant()).ToArray();
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 831 B |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 580 B |
|
Before Width: | Height: | Size: 825 B After Width: | Height: | Size: 825 B |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 580 B |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 583 B |
|
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 831 B |
|
Before Width: | Height: | Size: 825 B After Width: | Height: | Size: 825 B |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 852 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |