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.
This commit is contained in:
2025-06-27 20:02:02 +09:00
parent 1724072f7e
commit 4110c166cf
191 changed files with 2286 additions and 1462 deletions

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()
{
}
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -1,9 +0,0 @@
namespace Ghost.Editor.Models;
public enum MessageType
{
Informational,
Success,
Warning,
Error
}

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -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}";
}

View File

@@ -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));
}
}

View File

@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.Editor")]
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]

View File

@@ -26,4 +26,8 @@
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -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;
}

View File

@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.UnitTest")]
[assembly: InternalsVisibleTo("Ghost.Editor")]

View 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);
}
}
}

View File

@@ -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())

View 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();
}
}

View 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;
}
}

View File

@@ -0,0 +1,16 @@
namespace Ghost.Editor.Core.AssetHandle;
internal class AssetMeta
{
public Guid Guid
{
get;
internal set;
}
public ImporterSettings? Settings
{
get;
set;
}
}

View File

@@ -0,0 +1,5 @@
namespace Ghost.Editor.Core.AssetHandle;
public abstract class ImporterSettings
{
}

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Contracts;
namespace Ghost.Editor.Core.Contracts;
public interface INavigationAware
{

View 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;
}
}

View File

@@ -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>

View 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);
}
}

View 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>

View File

@@ -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();
}
}

View File

@@ -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)

View 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;
}
}

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Event;
namespace Ghost.Editor.Core.Event;
public delegate void ValueChangedEventHandler<T>(object? sender, ValueChangedEventArgs<T> args);

View 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>

View 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()
{
}
}

View File

@@ -1,6 +1,4 @@
using Ghost.Editor.Contracts;
namespace Ghost.Editor.Services.Contracts;
namespace Ghost.Editor.Core.Inspector;
internal interface IInspectorService
{

View File

@@ -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
{

View File

@@ -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);
}

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Models;
namespace Ghost.Editor.Core.Notifications;
public enum MessageType
{

View File

@@ -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
{

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Services.Contracts;
namespace Ghost.Editor.Core.Progress;
public interface IProgressService
{

View File

@@ -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
{

View File

@@ -1,6 +1,6 @@
using Microsoft.UI.Xaml.Controls;
namespace Ghost.Editor.Resources;
namespace Ghost.Editor.Core.Resources;
public static class EditorIconSource
{

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Resources;
namespace Ghost.Editor.Core.Resources;
internal static class FileExtensions
{

View File

@@ -1,6 +1,6 @@
using System.Reflection;
namespace Ghost.Editor.Resources;
namespace Ghost.Editor.Core.Resources;
internal static class StaticResource
{

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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 =>

View 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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.");
}

View File

@@ -1,3 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.App")]
[assembly: InternalsVisibleTo("Ghost.UnitTest")]

View File

@@ -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()
{
}
}

View File

@@ -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
});
}
}
}

View File

@@ -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();
}
}

View File

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 599 B

View File

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 831 B

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 580 B

After

Width:  |  Height:  |  Size: 580 B

View File

Before

Width:  |  Height:  |  Size: 825 B

After

Width:  |  Height:  |  Size: 825 B

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 433 B

View File

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 599 B

View File

Before

Width:  |  Height:  |  Size: 580 B

After

Width:  |  Height:  |  Size: 580 B

View File

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 583 B

View File

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 831 B

View File

Before

Width:  |  Height:  |  Size: 825 B

After

Width:  |  Height:  |  Size: 825 B

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 852 B

After

Width:  |  Height:  |  Size: 852 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Some files were not shown because too many files have changed in this diff Show More