Refactor project structure and enhance functionality

Changed the project namespace from `Ghost.Editor` to `Ghost.App` across multiple files.
Changed the `InternalsVisibleTo` attribute in `AssemblyInfo.cs` to include `Ghost.App`.
Changed the `ProjectRepository` class to add new asynchronous methods for retrieving projects by ID, name, and metadata path.
Changed the `ProjectService` class to utilize the new asynchronous project loading methods.
Changed the `SceneGraph` classes to improve node management and serialization.
Changed the `EntityManager` class to enhance entity management with new component handling methods.
Added new test classes, `EntityTest` and `SerializationTest`, to ensure reliability in entity and serialization systems.
Added the `Ghost.App` project file to establish a modular project structure.
Added the `Ghost.Generator` project for automated component serialization code generation.
Updated UI components to reflect the new namespace for proper functionality.
This commit is contained in:
2025-06-07 20:54:07 +09:00
parent bab3be2508
commit 40d333b004
123 changed files with 1441 additions and 740 deletions

View File

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

View File

@@ -23,7 +23,7 @@ internal static class ProjectRepository
await createCommand.ExecuteNonQueryAsync(); await createCommand.ExecuteNonQueryAsync();
} }
public static async IAsyncEnumerable<ProjectInfo> LoadProjectsAsync() public static async IAsyncEnumerable<ProjectInfo> GetAllProjectsAsync()
{ {
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder)); using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
connection.Open(); connection.Open();
@@ -47,6 +47,85 @@ internal static class ProjectRepository
} }
} }
public static async Task<ProjectInfo?> GetProjectByIdAsync(int id)
{
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
connection.Open();
await EnsureTableCreatedAsync(connection);
using var command = connection.CreateCommand();
command.CommandText = Command.SELECT_PROJECT_STRING + " WHERE ID = @ID;";
command.Parameters.AddWithValue("@ID", id);
using var reader = await command.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return new ProjectInfo
{
ID = reader.GetInt32(0),
Name = reader.GetString(1),
MetadataPath = reader.GetString(2),
};
}
return null;
}
public static async Task<ProjectInfo?> GetProjectByNameAsync(string name)
{
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
connection.Open();
await EnsureTableCreatedAsync(connection);
using var command = connection.CreateCommand();
command.CommandText = Command.SELECT_PROJECT_STRING + " WHERE Name = @Name;";
command.Parameters.AddWithValue("@Name", name);
using var reader = await command.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return new ProjectInfo
{
ID = reader.GetInt32(0),
Name = reader.GetString(1),
MetadataPath = reader.GetString(2),
};
}
return null;
}
public static async Task<ProjectInfo?> GetProjectByMetadataPathAsync(string metadataPath)
{
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
connection.Open();
await EnsureTableCreatedAsync(connection);
using var command = connection.CreateCommand();
command.CommandText = Command.SELECT_PROJECT_STRING + " WHERE MetadataPath = @MetadataPath;";
command.Parameters.AddWithValue("@MetadataPath", metadataPath);
using var reader = await command.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return new ProjectInfo
{
ID = reader.GetInt32(0),
Name = reader.GetString(1),
MetadataPath = reader.GetString(2),
};
}
return null;
}
public static async Task AddProjectAsync(ProjectInfo project) public static async Task AddProjectAsync(ProjectInfo project)
{ {
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder)); using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));

View File

@@ -13,6 +13,12 @@ internal partial class ProjectService
public const string ASSETS_FOLDER = "Assets"; public const string ASSETS_FOLDER = "Assets";
public const string CONFIG_FOLDER = "ProjectConfig"; public const string CONFIG_FOLDER = "ProjectConfig";
public static ProjectMetadataInfo CurrentProject
{
get;
set;
}
public static void EnsureDefaultTemplate() public static void EnsureDefaultTemplate()
{ {
var templates = Directory.GetFiles(DataPath.s_projectTemplateFolder, "template.json", SearchOption.AllDirectories); var templates = Directory.GetFiles(DataPath.s_projectTemplateFolder, "template.json", SearchOption.AllDirectories);
@@ -114,12 +120,6 @@ internal partial class ProjectService
internal partial class ProjectService internal partial class ProjectService
{ {
public static ProjectMetadataInfo CurrentProject
{
get;
set;
}
public Task AddProjectAsync(ProjectInfo project) public Task AddProjectAsync(ProjectInfo project)
{ {
return ProjectRepository.AddProjectAsync(project); return ProjectRepository.AddProjectAsync(project);
@@ -147,9 +147,14 @@ internal partial class ProjectService
return ProjectRepository.UpdateProjectAsync(project); return ProjectRepository.UpdateProjectAsync(project);
} }
public IAsyncEnumerable<ProjectInfo> LoadAllProjectAsync() public async Task<bool> HasProjectAsync(string path)
{ {
return ProjectRepository.LoadProjectsAsync(); return await ProjectRepository.GetProjectByMetadataPathAsync(path) != null;
}
public IAsyncEnumerable<ProjectInfo> GetAllProjectAsync()
{
return ProjectRepository.GetAllProjectsAsync();
} }
public async Task<Result<ProjectMetadataInfo>> CreateProjectAsync(string projectName, string projectDirectory, Version engineVersion, string templatePath) public async Task<Result<ProjectMetadataInfo>> CreateProjectAsync(string projectName, string projectDirectory, Version engineVersion, string templatePath)
@@ -187,11 +192,18 @@ internal partial class ProjectService
public async Task<Result<ProjectMetadataInfo>> AddProjectFromDirectoryAsync(string projectDirectory) public async Task<Result<ProjectMetadataInfo>> AddProjectFromDirectoryAsync(string projectDirectory)
{ {
var result = await ValidateProjectDirectoryAsync(projectDirectory); var result = await ValidateProjectDirectoryAsync(projectDirectory);
if (result.success) if (!result.success)
{ {
await AddProjectAsync(result.data.Metadata.Name, result.data.Path); return result;
} }
if (await HasProjectAsync(result.data.Path))
{
return Result<ProjectMetadataInfo>.Error("Project already exists.");
}
await AddProjectAsync(result.data.Metadata.Name, result.data.Path);
return result; return result;
} }
} }

View File

@@ -1,102 +0,0 @@
using Ghost.Editor.Helpers;
using Ghost.Editor.Infrastructures.AppState;
using Ghost.Editor.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
using System;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Ghost.Editor
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
private Window? _window;
internal static Window? Window
{
get => (Current as App)!._window;
set
{
if (Current is App app)
{
app._window = value;
}
}
}
internal IHost Host
{
get;
}
/// <summary>
/// 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 App()
{
InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
UseContentRoot(AppContext.BaseDirectory).
ConfigureServices((context, services) =>
{
HostHelper.AddLandingScope(context, services);
HostHelper.AddEngineScope(context, services);
services.AddSingleton<AppStateMachine>();
services.AddSingleton<StackedNotificationService>();
})
.Build();
UnhandledException += App_UnhandledException;
}
internal static IServiceScope CreateScope()
{
return (Current as App)!.Host.Services.CreateScope();
}
public static T GetService<T>() where T : class
{
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.");
}
return service;
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
ActivationHandler.Handle(args);
Host.Start();
var stateMachine = GetService<AppStateMachine>();
stateMachine.RegisterState(StateKey.Landing, () => new LandingState());
stateMachine.RegisterState(StateKey.EngineEditor, () => new EditorState());
await stateMachine.TransitionToAsync(StateKey.Landing);
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
// TODO: Log and handle exceptions as appropriate.
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
}
}
}

View File

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

View File

@@ -1,182 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <TargetFramework>net9.0</TargetFramework>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <Nullable>enable</Nullable>
<RootNamespace>Ghost.Editor</RootNamespace>
<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> <LangVersion>preview</LangVersion>
</PropertyGroup> </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\EditorControls.xaml" />
<None Remove="Controls\Internal\InspectorView.xaml" />
<None Remove="Controls\Internal\InternalControls.xaml" />
<None Remove="View\Pages\EngineEditor\ConsolePage.xaml" />
<None Remove="View\Pages\EngineEditor\HierarchyPage.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>
<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> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ghost.Data\Ghost.Data.csproj" />
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" /> <ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
</ItemGroup> </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>
<Folder Include="AppStates\" />
<Folder Include="Resources\" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Misaki.HighPerformance.Unsafe"> <Reference Include="Misaki.HighPerformance.Unsafe">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath> <HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </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="Controls\Internal\InspectorView.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>
<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>True</PublishAot>
<PublishTrimmed>True</PublishTrimmed>
</PropertyGroup>
</Project> </Project>

View File

@@ -1,32 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace Ghost.Editor.Infrastructures.SceneGraph;
public abstract partial class SceneGraphNode : ObservableObject
{
public enum NodeType
{
Scene,
Entity,
}
public abstract NodeType Type
{
get;
}
[ObservableProperty]
public partial string Name
{
get;
set;
}
// Will the new collection allocated if ui bind to this property?
private ObservableCollection<EntityNode>? _children;
public ObservableCollection<EntityNode> Children
{
get => _children ??= new();
}
}

View File

@@ -1,58 +0,0 @@
using Ghost.Engine.Components;
using Ghost.Entities;
using System.Collections.Generic;
namespace Ghost.Editor.Infrastructures.SceneGraph;
public partial class SceneNode : SceneGraphNode
{
private readonly World _world;
private Dictionary<Entity, EntityNode> _entityNodeLookup = new();
public World World => _world;
public Dictionary<Entity, EntityNode> EntityNodeLookup => _entityNodeLookup;
public override NodeType Type => NodeType.Scene;
public SceneNode(World world, string name)
{
_world = world;
Name = name;
}
private EntityNode BuildNodeRecursive(Entity entity, World world)
{
// TODO: Node serialization.
var node = new EntityNode(entity, "New Entity");
_entityNodeLookup[entity] = node;
var hc = world.EntityManager.GetComponent<Hierarchy>(entity);
var child = hc.ValueRO.firstChild;
while (child != Entity.Invalid)
{
node.Children.Add(BuildNodeRecursive(child, world));
var childHC = world.EntityManager.GetComponent<Hierarchy>(child);
child = childHC.ValueRO.nextSibling;
}
return node;
}
private void BuildGraph()
{
foreach (var (entity, hierarchy) in _world.Query<Hierarchy>())
{
if (hierarchy.ValueRO.parent == Entity.Invalid)
{
var node = BuildNodeRecursive(entity, _world);
Children.Add(node);
}
}
}
public void Load()
{
BuildGraph();
}
}

View File

@@ -1,17 +1,21 @@
using Ghost.Entities; using Ghost.Entities;
namespace Ghost.Editor.Infrastructures.SceneGraph; namespace Ghost.Editor.SceneGraph;
public partial class EntityNode : SceneGraphNode public partial class EntityNode : SceneGraphNode
{ {
private readonly Entity _entity; private readonly Entity _entity;
public Entity Entity => _entity; public Entity Entity => _entity;
public override NodeType Type => NodeType.Entity; public override SceneGraphNodeType NodeType => SceneGraphNodeType.Entity;
public EntityNode(Entity entity, string name) public EntityNode(Entity entity, string name)
{ {
_entity = entity; _entity = entity;
Name = name; Name = name;
} }
internal EntityNode()
{
}
} }

View File

@@ -1,9 +1,9 @@
using Ghost.Engine.Components; using Ghost.Engine.Components;
using Ghost.Entities; using Ghost.Entities;
namespace Ghost.Editor.Infrastructures.SceneGraph; namespace Ghost.Editor.SceneGraph;
internal class SceneGraphHelpers public class SceneGraphHelpers
{ {
/// <summary> /// <summary>
/// Creates a new <see cref="EntityNode"/> entity with default components. /// Creates a new <see cref="EntityNode"/> entity with default components.
@@ -33,7 +33,7 @@ internal class SceneGraphHelpers
/// <param name="world">The world context where the entities exist.</param> /// <param name="world">The world context where the entities exist.</param>
/// <param name="parentNode">The parent entity to which the child will be attached.</param> /// <param name="parentNode">The parent entity to which the child will be attached.</param>
/// <param name="childNode">The child entity to be attached.</param> /// <param name="childNode">The child entity to be attached.</param>
public static void AttachChild(SceneNode scene, EntityNode parentNode, EntityNode childNode) public static void AttachChild(WorldNode scene, EntityNode parentNode, EntityNode childNode)
{ {
// 1) If the child already has a parent, detach it first // 1) If the child already has a parent, detach it first
var childHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity); var childHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity);
@@ -56,7 +56,7 @@ internal class SceneGraphHelpers
scene.World.EntityManager.SetComponent(childNode.Entity, in childHierarchy.ValueRO); scene.World.EntityManager.SetComponent(childNode.Entity, in childHierarchy.ValueRO);
// 5) Update children list in parent node // 5) Update children list in parent node
parentNode.Children.Add(childNode); parentNode.AddChild(childNode);
} }
/// <summary> /// <summary>
@@ -64,7 +64,7 @@ internal class SceneGraphHelpers
/// </summary> /// </summary>
/// <param name="world">The world context where the entities exist.</param> /// <param name="world">The world context where the entities exist.</param>
/// <param name="node">The entity to detach from its parent.</param> /// <param name="node">The entity to detach from its parent.</param>
public static void DetachFromParent(SceneNode scene, EntityNode node) public static void DetachFromParent(WorldNode scene, EntityNode node)
{ {
var hierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity); var hierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity);
var parent = hierarchy.ValueRO.parent; var parent = hierarchy.ValueRO.parent;
@@ -107,6 +107,6 @@ internal class SceneGraphHelpers
scene.World.EntityManager.SetComponent(node.Entity, in hierarchy.ValueRO); scene.World.EntityManager.SetComponent(node.Entity, in hierarchy.ValueRO);
// Remove from parent's children list // Remove from parent's children list
scene.EntityNodeLookup[parent].Children.Remove(node); scene.EntityNodeLookup[parent].RemoveChild(node);
} }
} }

View File

@@ -0,0 +1,54 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace Ghost.Editor.SceneGraph;
public enum SceneGraphNodeType
{
Scene,
Entity,
}
public abstract partial class SceneGraphNode : ObservableObject
{
public ObservableCollection<SceneGraphNode>? Children
{
get;
private set;
}
[ObservableProperty]
public partial string Name
{
get;
set;
}
public abstract SceneGraphNodeType NodeType
{
get;
}
public int ChildCount => Children?.Count ?? 0;
public virtual void AddChild(SceneGraphNode child)
{
Children ??= new();
Children.Add(child);
}
public virtual bool RemoveChild(SceneGraphNode child)
{
return Children?.Remove(child) ?? false;
}
public SceneGraphNode GetChild(int index)
{
if (Children == null || index < 0 || index >= Children.Count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
return Children[index];
}
}

View File

@@ -0,0 +1,112 @@
using Ghost.Editor.Serializer;
using Ghost.Engine.Components;
using Ghost.Entities;
using System.Text.Json.Serialization;
namespace Ghost.Editor.SceneGraph;
[JsonConverter(typeof(WorldNodeSerializer))]
public partial class WorldNode : SceneGraphNode
{
private readonly World _world;
private Dictionary<Entity, EntityNode> _entityNodeLookup = new();
public World World => _world;
public Dictionary<Entity, EntityNode> EntityNodeLookup => _entityNodeLookup;
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Scene;
public WorldNode(World world, string name)
{
_world = world;
Name = name;
}
internal WorldNode()
{
_world = World.Create();
}
private void UpdateLookup(Entity key, EntityNode value)
{
_entityNodeLookup[key] = value;
if (value.Children == null)
{
return;
}
foreach (var child in value.Children)
{
if (child is EntityNode entityChild)
{
UpdateLookup(entityChild.Entity, entityChild);
}
}
}
public override void AddChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
base.AddChild(entityNode);
UpdateLookup(entityNode.Entity, entityNode);
}
public override bool RemoveChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
var result = base.RemoveChild(child);
if (result)
{
_entityNodeLookup.Remove(entityNode.Entity);
}
return result;
}
private EntityNode BuildNodeRecursive(Entity entity, World world)
{
// TODO: Node serialization.
if (!_entityNodeLookup.TryGetValue(entity, out var node))
{
node = new EntityNode(entity, "New Entity");
_entityNodeLookup[entity] = node;
}
var hc = world.EntityManager.GetComponent<Hierarchy>(entity);
var child = hc.ValueRO.firstChild;
while (child != Entity.Invalid)
{
node.AddChild(BuildNodeRecursive(child, world));
var childHC = world.EntityManager.GetComponent<Hierarchy>(child);
child = childHC.ValueRO.nextSibling;
}
return node;
}
private void BuildGraph()
{
foreach (var (entity, hierarchy) in _world.Query<Hierarchy>())
{
if (hierarchy.ValueRO.parent == Entity.Invalid)
{
var node = BuildNodeRecursive(entity, _world);
AddChild(node);
}
}
}
public void Load()
{
BuildGraph();
}
}

View File

@@ -0,0 +1,131 @@
using Ghost.Editor.SceneGraph;
using Ghost.Engine.Utilities;
using Ghost.Entities;
using Ghost.Entities.Components;
using Ghost.Entities.Utilities;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Ghost.Editor.Serializer;
internal class WorldNodeSerializer : JsonConverter<WorldNode>
{
private static class Property
{
public const string NAME = "Name";
public const string ENTITIES = "Entities";
public const string ID = "ID";
public const string ENTITY_ID = "EntityID";
public const string COMPONENTS = "Components";
public const string DATA = "Data";
public const string SYSTEMS = "Systems";
}
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(WorldNode) || typeToConvert.IsSubclassOf(typeof(WorldNode));
}
public override WorldNode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var element = JsonDocument.ParseValue(ref reader).RootElement;
var name = element.GetProperty(Property.NAME).GetString() ?? "New World";
var world = World.Create();
var result = new WorldNode(world, name);
foreach (var entityElement in element.GetProperty(Property.ENTITIES).EnumerateArray())
{
var entityName = entityElement.GetProperty(Property.NAME).GetString() ?? "New Entity";
var entityID = entityElement.GetProperty(Property.ID).GetInt32();
var entity = new Entity(entityID, 0);
var node = new EntityNode(entity, entityName);
world.EntityManager.AddEntityInternal(entity);
result.EntityNodeLookup[entity] = node;
}
foreach (var componentElement in element.GetProperty(Property.COMPONENTS).EnumerateObject())
{
var typeName = componentElement.Name;
var type = Type.GetType(typeName) ?? throw new Exception($"Type {typeName} not found.");
foreach (var dataElement in componentElement.Value.EnumerateArray())
{
var entityID = dataElement.GetProperty(Property.ENTITY_ID).GetInt32();
var entity = new Entity(entityID, 0);
var dataProperty = dataElement.GetProperty(Property.DATA);
var component = JsonSerializer.Deserialize(dataProperty.GetRawText(), type, options);
if (component is IComponentData data)
{
world.EntityManager.AddComponent(entity, data, type);
}
}
}
foreach (var systemElement in element.GetProperty(Property.SYSTEMS).EnumerateArray())
{
var typeString = systemElement.GetString();
if (string.IsNullOrEmpty(typeString))
{
continue;
}
var systemType = Type.GetType(typeString);
if (systemType == null)
{
continue;
}
world.SystemStorage.AddSystem(systemType);
}
return result;
}
public override void Write(Utf8JsonWriter writer, WorldNode value, JsonSerializerOptions options)
{
writer.WriteObject(() =>
{
writer.WriteString(Property.NAME, value.Name);
writer.WriteArray(Property.ENTITIES, value.World.EntityManager.Entities, entity =>
{
if (!entity.IsValid)
{
return;
}
writer.WriteObject(() =>
{
writer.WriteString(Property.NAME, value.EntityNodeLookup[entity].Name);
writer.WriteNumber(Property.ID, entity.ID);
});
});
writer.WriteObject(Property.COMPONENTS, () =>
{
foreach (var kvp in value.World.ComponentStorage.ComponentPools)
{
var type = TypeHandle.ToType(kvp.Key) ?? throw new Exception($"Type {kvp.Key} not found.");
var typeName = type.AssemblyQualifiedName ?? type.Name;
writer.WriteArray(typeName, kvp.Value.Enumerate(), data =>
{
writer.WriteObject(() =>
{
writer.WriteNumber(Property.ENTITY_ID, data.entity.ID);
writer.WritePropertyName(Property.DATA);
JsonSerializer.Serialize(writer, data.component, type, options);
});
});
}
});
writer.WriteArray(Property.SYSTEMS, value.World.SystemStorage.Systems, systemType =>
{
writer.WriteStringValue(systemType.AssemblyQualifiedName ?? systemType.Name);
});
});
}
}

View File

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

View File

@@ -1,4 +1,4 @@
using Ghost.Engine.Helpers; using Ghost.Engine.Utilities;
using Ghost.Entities.Components; using Ghost.Entities.Components;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -15,7 +15,7 @@ public struct LocalToWorld : IComponentData
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new() get => new()
{ {
matrix = MatrixHelpers.CreateTRS(Vector3.Zero, Quaternion.Identity, Vector3.One) matrix = MatrixUtilities.CreateTRS(Vector3.Zero, Quaternion.Identity, Vector3.One)
}; };
} }
} }

View File

@@ -17,6 +17,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" /> <ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
<!--<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />-->
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,9 +1,9 @@
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ghost.Engine.Helpers; namespace Ghost.Engine.Utilities;
public static class MatrixHelpers public static class MatrixUtilities
{ {
/// <summary> /// <summary>
/// Generates a transformation matrix from position, rotation, and scale vectors. /// Generates a transformation matrix from position, rotation, and scale vectors.

View File

@@ -0,0 +1,40 @@
using System.Text.Json;
namespace Ghost.Engine.Utilities;
public static class Utf8JsonWriterExtensions
{
public static void WriteArray<T>(this Utf8JsonWriter writer, ReadOnlySpan<char> name, IEnumerable<T> source, Action<T> writeAction)
{
writer.WriteStartArray(name);
foreach (var item in source)
{
writeAction(item);
}
writer.WriteEndArray();
}
public static void WriteArray<T>(this Utf8JsonWriter writer, ReadOnlySpan<char> name, ReadOnlySpan<T> source, Action<T> writeAction)
{
writer.WriteStartArray(name);
foreach (var item in source)
{
writeAction(item);
}
writer.WriteEndArray();
}
public static void WriteObject(this Utf8JsonWriter writer, Action writeAction)
{
writer.WriteStartObject();
writeAction();
writer.WriteEndObject();
}
public static void WriteObject(this Utf8JsonWriter writer, ReadOnlySpan<char> name, Action writeAction)
{
writer.WriteStartObject(name);
writeAction();
writer.WriteEndObject();
}
}

View File

@@ -1,9 +1,10 @@
global using EntityID = System.Int32; global using EntityID = System.Int32;
global using GenerationID = System.Byte; global using GenerationID = System.UInt16;
global using WorldID = System.UInt16; global using WorldID = System.UInt16;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.App")]
[assembly: InternalsVisibleTo("Ghost.Engine")] [assembly: InternalsVisibleTo("Ghost.Engine")]
[assembly: InternalsVisibleTo("Ghost.Editor")] [assembly: InternalsVisibleTo("Ghost.Editor")]
[assembly: InternalsVisibleTo("Ghost.Test")] [assembly: InternalsVisibleTo("Ghost.Test")]

View File

@@ -18,8 +18,12 @@ internal interface IComponentPool : IDisposable
get; get;
} }
public void Add(Entity entity, IComponentData component);
public bool Remove(Entity entity); public bool Remove(Entity entity);
public bool Has(Entity entity); public bool Has(Entity entity);
public IComponentData Get(Entity entity);
public IEnumerable<(Entity entity, IComponentData component)> Enumerate();
} }
internal interface IComponentPool<T> : IComponentPool internal interface IComponentPool<T> : IComponentPool
@@ -56,6 +60,10 @@ internal class ComponentPool<T> : IComponentPool<T>
_lookup.AsSpan().Fill(Entity.INVALID_ID); _lookup.AsSpan().Fill(Entity.INVALID_ID);
} }
public ComponentPool() : this(16)
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static EntityID GetLookupIndex(Entity entity) private static EntityID GetLookupIndex(Entity entity)
{ {
@@ -68,6 +76,16 @@ internal class ComponentPool<T> : IComponentPool<T>
return _lookup[GetLookupIndex(entity)]; return _lookup[GetLookupIndex(entity)];
} }
public void Add(Entity entity, IComponentData component)
{
if (component is not T typedComponent)
{
throw new ArgumentException($"Component type mismatch. Expected {typeof(T)}, but got {component.GetType()}.");
}
Add(entity, typedComponent);
}
public void Add(Entity entity, T component) public void Add(Entity entity, T component)
{ {
if (!entity.IsValid) if (!entity.IsValid)
@@ -117,6 +135,11 @@ internal class ComponentPool<T> : IComponentPool<T>
return true; return true;
} }
public IComponentData Get(Entity entity)
{
return GetRef(entity);
}
public ref T GetRef(Entity entity) public ref T GetRef(Entity entity)
{ {
if (!entity.IsValid) if (!entity.IsValid)
@@ -128,6 +151,17 @@ internal class ComponentPool<T> : IComponentPool<T>
return ref _components[index].data; return ref _components[index].data;
} }
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
{
for (var i = 0; i < _nextId; i++)
{
if (_components[i].owner.IsValid)
{
yield return (_components[i].owner, _components[i].data);
}
}
}
public bool Has(Entity entity) public bool Has(Entity entity)
{ {
if (entity.ID >= _lookup.Length) if (entity.ID >= _lookup.Length)
@@ -194,6 +228,16 @@ internal class ScriptComponentPool : IComponentPool<ScriptComponent>
_executionList.Sort((a, b) => a.ExecutionOrder.CompareTo(b.ExecutionOrder)); _executionList.Sort((a, b) => a.ExecutionOrder.CompareTo(b.ExecutionOrder));
} }
public void Add(Entity entity, IComponentData component)
{
if (component is not ScriptComponent scriptComponent)
{
throw new ArgumentException($"Component type mismatch. Expected {typeof(ScriptComponent)}, but got {component.GetType()}.");
}
Add(entity, scriptComponent);
}
public void Add(Entity entity, ScriptComponent component) public void Add(Entity entity, ScriptComponent component)
{ {
if (!IsInitialized) if (!IsInitialized)
@@ -283,12 +327,33 @@ internal class ScriptComponentPool : IComponentPool<ScriptComponent>
return _scriptComponents?.ContainsKey(entity) ?? false; return _scriptComponents?.ContainsKey(entity) ?? false;
} }
public List<ScriptComponent>? Get(Entity entity) public IComponentData Get(Entity entity)
{
throw new NotSupportedException("Use GetAll instead of Get for ScriptComponentPool.");
}
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
{
if (_scriptComponents == null)
{
yield break;
}
foreach (var kvp in _scriptComponents)
{
foreach (var script in kvp.Value)
{
yield return (kvp.Key, script);
}
}
}
public IEnumerable<IComponentData> GetAll(Entity entity)
{ {
if (_scriptComponents == null if (_scriptComponents == null
|| !_scriptComponents.TryGetValue(entity, out var scriptList)) || !_scriptComponents.TryGetValue(entity, out var scriptList))
{ {
return null; return Enumerable.Empty<ScriptComponent>();
} }
return scriptList; return scriptList;

View File

@@ -1,20 +1,28 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace Ghost.Entities; namespace Ghost.Entities;
[SkipLocalsInit] [SkipLocalsInit]
public struct Entity : IEquatable<Entity>, IComparable<Entity> public struct Entity : IEquatable<Entity>, IComparable<Entity>
{ {
// Is 256 generations enough? If not, increase the size of GenerationID or make generation as a separate int field.
public const int GENERATION_BITS = sizeof(GenerationID) * 8;
public const int INDEX_BITS = sizeof(EntityID) * 8 - GENERATION_BITS;
private const int _GENERATION_MASK = (1 << GENERATION_BITS) - 1;
private const int _INDEX_MASK = (1 << INDEX_BITS) - 1;
public const EntityID INVALID_ID = -1; public const EntityID INVALID_ID = -1;
[JsonInclude]
private EntityID _id; private EntityID _id;
private GenerationID _generation;
public readonly EntityID ID
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _id;
}
public readonly GenerationID Generation
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _generation;
}
public readonly bool IsValid public readonly bool IsValid
{ {
@@ -22,39 +30,20 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
get => ID != INVALID_ID; get => ID != INVALID_ID;
} }
public readonly EntityID ID
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _id & _INDEX_MASK;
}
public readonly GenerationID Generation
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (GenerationID)(_id >> INDEX_BITS & _GENERATION_MASK);
}
public static Entity Invalid public static Entity Invalid
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(INVALID_ID, 0); get => new(INVALID_ID, GenerationID.MaxValue);
} }
internal Entity(EntityID id, GenerationID generation) internal Entity(EntityID id, GenerationID generation)
{ {
_id = generation << INDEX_BITS | id; _id = id;
_generation = generation;
} }
internal void IncrementGeneration() [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ internal void IncrementGeneration() => _generation++;
var generation = Generation + 1;
if (generation >= _GENERATION_MASK)
{
throw new InvalidOperationException("Generation overflow");
}
_id = _id & ~(_GENERATION_MASK << INDEX_BITS) | generation << INDEX_BITS;
}
public readonly bool Equals(Entity other) public readonly bool Equals(Entity other)
{ {

View File

@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ghost.Entities; namespace Ghost.Entities;
public struct EntityManager : IDisposable public readonly struct EntityManager : IDisposable
{ {
private readonly List<Entity> _entities; private readonly List<Entity> _entities;
private readonly Queue<EntityID> _freeEntitySlots; private readonly Queue<EntityID> _freeEntitySlots;
@@ -15,11 +15,6 @@ public struct EntityManager : IDisposable
public readonly int EntityCount => _entities.Count; public readonly int EntityCount => _entities.Count;
public readonly ReadOnlySpan<Entity> Entities => CollectionsMarshal.AsSpan(_entities); public readonly ReadOnlySpan<Entity> Entities => CollectionsMarshal.AsSpan(_entities);
public event Action<Entity>? OnEntityCreated;
public event Action<EntityID>? OnEntityRemoved;
public event Action<Entity, Type>? OnComponentAdded;
public event Action<Entity, Type>? OnComponentRemoved;
internal EntityManager(World world, int initialCapacity) internal EntityManager(World world, int initialCapacity)
{ {
_entities = new(initialCapacity); _entities = new(initialCapacity);
@@ -45,10 +40,14 @@ public struct EntityManager : IDisposable
_entities.Add(entity); _entities.Add(entity);
} }
OnEntityCreated?.Invoke(entity);
return entity; return entity;
} }
internal readonly void AddEntityInternal(Entity entity)
{
_entities.Add(entity);
}
/// <summary> /// <summary>
/// Removes the specified <see cref="Entity"/> from the world. /// Removes the specified <see cref="Entity"/> from the world.
/// </summary> /// </summary>
@@ -67,7 +66,6 @@ public struct EntityManager : IDisposable
_entities[entity.ID] = slot; _entities[entity.ID] = slot;
_freeEntitySlots.Enqueue(entity.ID); _freeEntitySlots.Enqueue(entity.ID);
OnEntityRemoved?.Invoke(entity.ID);
entity = Entity.Invalid; entity = Entity.Invalid;
} }
@@ -88,6 +86,20 @@ public struct EntityManager : IDisposable
return _entities[entity.ID].Generation == entity.Generation; return _entities[entity.ID].Generation == entity.Generation;
} }
public readonly void AddComponent(Entity entity, IComponentData component, Type type)
{
var typeHandle = TypeHandle.Get(type);
ref var pool = ref CollectionsMarshal.GetValueRefOrAddDefault(_world.ComponentStorage.ComponentPools, typeHandle, out var exists);
if (!exists)
{
var poolType = typeof(ComponentPool<>).MakeGenericType(type);
pool = (IComponentPool)(Activator.CreateInstance(poolType) ?? throw new InvalidOperationException($"Failed to create component pool for type {type}."));
}
pool!.Add(entity, component);
_world.ComponentStorage.GetOrCreateMask(typeHandle).SetBit(entity.ID);
}
/// <summary> /// <summary>
/// Adds a component of type <typeparamref name="T"/> to the given <see cref="Entity"/>. /// Adds a component of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
/// </summary> /// </summary>
@@ -100,7 +112,6 @@ public struct EntityManager : IDisposable
{ {
_world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component); _world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component);
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).SetBit(entity.ID); _world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).SetBit(entity.ID);
OnComponentAdded?.Invoke(entity, typeof(T));
} }
/// <summary> /// <summary>
@@ -123,7 +134,6 @@ public struct EntityManager : IDisposable
} }
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).ClearBit(entity.ID); _world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).ClearBit(entity.ID);
OnComponentRemoved?.Invoke(entity, typeof(T));
return true; return true;
} }
@@ -183,7 +193,6 @@ public struct EntityManager : IDisposable
where T : ScriptComponent, new() where T : ScriptComponent, new()
{ {
_world.ComponentStorage.ScriptComponentPool.Add(entity, new T()); _world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
OnComponentAdded?.Invoke(entity, typeof(ScriptComponent));
} }
/// <summary> /// <summary>
@@ -203,9 +212,14 @@ public struct EntityManager : IDisposable
var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}."); var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}.");
_world.ComponentStorage.ScriptComponentPool.Add(entity, instance); _world.ComponentStorage.ScriptComponentPool.Add(entity, instance);
OnComponentAdded?.Invoke(entity, typeof(ScriptComponent));
} }
/// <summary>
/// Removes a script of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The type of the script to remove.</typeparam>
/// <param name="entity">The entity from which the script is to be removed.</param>
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
public readonly bool RemoveScript<T>(Entity entity) public readonly bool RemoveScript<T>(Entity entity)
where T : ScriptComponent where T : ScriptComponent
{ {
@@ -214,10 +228,15 @@ public struct EntityManager : IDisposable
return false; return false;
} }
OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent));
return true; return true;
} }
/// <summary>
/// Removes a script at the specified index from the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The entity from which the script is to be removed.</param>
/// <param name="index">The index of the script to remove.</param>
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
public readonly bool RemoveScriptAt(Entity entity, int index) public readonly bool RemoveScriptAt(Entity entity, int index)
{ {
if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index)) if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
@@ -225,7 +244,6 @@ public struct EntityManager : IDisposable
return false; return false;
} }
OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent));
return true; return true;
} }
@@ -238,7 +256,7 @@ public struct EntityManager : IDisposable
public readonly T? GetScript<T>(Entity entity) public readonly T? GetScript<T>(Entity entity)
where T : ScriptComponent where T : ScriptComponent
{ {
return (T?)_world.ComponentStorage.ScriptComponentPool.Get(entity)? return (T?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?
.FirstOrDefault(script => script is T tScript); .FirstOrDefault(script => script is T tScript);
} }
@@ -251,7 +269,7 @@ public struct EntityManager : IDisposable
public readonly IEnumerable<T> GetScripts<T>(Entity entity) public readonly IEnumerable<T> GetScripts<T>(Entity entity)
where T : ScriptComponent where T : ScriptComponent
{ {
return (IEnumerable<T>?)_world.ComponentStorage.ScriptComponentPool.Get(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty<T>(); return (IEnumerable<T>?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty<T>();
} }
public readonly void Dispose() public readonly void Dispose()

View File

@@ -24,11 +24,11 @@ internal struct QueryFilter()
internal List<nint> _absent = new(6); internal List<nint> _absent = new(6);
internal List<nint> _disabled = new(6); internal List<nint> _disabled = new(6);
public readonly void ComputeFilterBitMask(World world, ref BitSet result) public readonly void ComputeFilterBitMask(World world, BitSet result)
{ {
BitSet allMask = default; BitSet allMask = new();
BitSet anyMask = default; BitSet anyMask = new();
BitSet absentMask = default; BitSet absentMask = new();
var hasAll = false; var hasAll = false;
var hasAny = false; var hasAny = false;
@@ -77,7 +77,6 @@ internal struct QueryFilter()
absentMask |= mask; absentMask |= mask;
} }
result = new BitSet(world.EntityManager.EntityCount);
result.SetAll(); result.SetAll();
if (hasAll) if (hasAll)

View File

@@ -1,17 +1,17 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Entities.Systems; namespace Ghost.Entities.Systems;
[SkipLocalsInit] [SkipLocalsInit]
public struct SystemStorage public readonly struct SystemStorage
{ {
private readonly List<Type> _systems = new(); private readonly List<Type> _systems = new();
private readonly List<ISystem> _executionList = new(); private readonly List<ISystem> _executionList = new();
private readonly World _world; private readonly World _world;
public event Action<Type>? SystemAdded; internal ReadOnlySpan<Type> Systems => CollectionsMarshal.AsSpan(_systems);
public event Action<Type>? SystemRemoved;
internal SystemStorage(World world) internal SystemStorage(World world)
{ {
@@ -21,7 +21,6 @@ public struct SystemStorage
public readonly void AddSystem(Type systemType) public readonly void AddSystem(Type systemType)
{ {
_systems.Add(systemType); _systems.Add(systemType);
SystemAdded?.Invoke(systemType);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -34,7 +33,6 @@ public struct SystemStorage
public readonly void RemoveSystem(Type systemType) public readonly void RemoveSystem(Type systemType)
{ {
_systems.Remove(systemType); _systems.Remove(systemType);
SystemRemoved?.Invoke(systemType);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -59,7 +59,8 @@ public struct QueryEnumerable<T0>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }
@@ -231,7 +232,8 @@ public struct QueryEnumerable<T0, T1>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }
@@ -408,7 +410,8 @@ public struct QueryEnumerable<T0, T1, T2>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }
@@ -590,7 +593,8 @@ public struct QueryEnumerable<T0, T1, T2, T3>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }
@@ -777,7 +781,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }
@@ -969,7 +974,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }
@@ -1166,7 +1172,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }
@@ -1368,7 +1375,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }

View File

@@ -85,7 +85,8 @@ public struct QueryEnumerable<<#= generics #>>
_count = count; _count = count;
_index = -1; _index = -1;
filters.ComputeFilterBitMask(_world, ref _filterMask); _filterMask = new BitSet(_world.EntityManager.EntityCount);
filters.ComputeFilterBitMask(_world, _filterMask);
Current = default; Current = default;
} }

View File

@@ -25,4 +25,9 @@ internal static class TypeHandle
{ {
return type.TypeHandle.Value; return type.TypeHandle.Value;
} }
public static Type? ToType(nint handle)
{
return Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(handle));
}
} }

View File

@@ -0,0 +1,114 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Ghost.Generator
{
[Generator]
public class ComponentSerializationGenerator : IIncrementalGenerator
{
private void GenerateJsonContext(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> symbols)
{
if (symbols.IsDefaultOrEmpty)
{
return;
}
var sb = new StringBuilder();
sb.Append(@"
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Ghost.Engine.Components.Serialization
{
[JsonSourceGenerationOptions(WriteIndented = true, IncludeFields = true)]");
foreach (var symbol in symbols.Distinct(SymbolEqualityComparer.Default))
{
if (symbol is null)
{
continue;
}
var fqtn = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
sb.Append($@"
[JsonSerializable(typeof({fqtn}))]");
}
sb.Append(@"
public partial class ComponentJsonContext : JsonSerializerContext
{
private static readonly Dictionary<Type, JsonTypeInfo> _typeLookup = new()
{");
foreach (var symbol in symbols.Distinct(SymbolEqualityComparer.Default))
{
if (symbol is null)
{
continue;
}
var fqtn = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
sb.Append($@"
{{ typeof({fqtn}), ComponentJsonContext.{symbol.Name} }},");
}
sb.Append(@"
};
/// <summary>
/// Tries to retrieve the generated JsonTypeInfo for a given component type.
/// </summary>
public static bool TryGetTypeInfo(Type componentType, out JsonTypeInfo jsonTypeInfo) =>
_typeLookup.TryGetValue(componentType, out jsonTypeInfo);
}
}");
context.AddSource("ComponentJsonContext.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var componentCandidates = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (syntaxNode, _) => syntaxNode is StructDeclarationSyntax,
transform: (ctx, _) =>
{
var structSyntax = (StructDeclarationSyntax)ctx.Node;
if (!(ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is INamedTypeSymbol symbol))
{
return null;
}
var compilation = ctx.SemanticModel.Compilation;
var iComponentDataSymbol = compilation.GetTypeByMetadataName("Ghost.Entities.Components.IComponentData");
if (iComponentDataSymbol == null)
{
return null;
}
foreach (var iface in symbol.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(iface, iComponentDataSymbol))
{
return symbol;
}
}
return null;
})
.Where(symbol => symbol != null)
.Collect();
context.RegisterSourceOutput(componentCandidates, GenerateJsonContext);
}
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
</ItemGroup>
</Project>

View File

@@ -3,7 +3,7 @@ using Ghost.Data.Services;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using System.IO; using System.IO;
namespace Ghost.Editor; namespace Ghost.App;
internal static class ActivationHandler internal static class ActivationHandler
{ {

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<Application <Application
x:Class="Ghost.Editor.App" x:Class="Ghost.App.GhostApplication"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Ghost.Editor"> xmlns:local="using:Ghost.App">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View File

@@ -0,0 +1,101 @@
using Ghost.App.Infrastructures.AppState;
using Ghost.App.Services;
using Ghost.App.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
using System;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Ghost.App;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class GhostApplication : Application
{
private Window? _window;
internal static Window? Window
{
get => (Current as GhostApplication)!._window;
set
{
if (Current is GhostApplication app)
{
app._window = value;
}
}
}
internal IHost Host
{
get;
}
/// <summary>
/// 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 GhostApplication()
{
InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
UseContentRoot(AppContext.BaseDirectory).
ConfigureServices((context, services) =>
{
HostHelper.AddLandingScope(context, services);
HostHelper.AddEngineScope(context, services);
services.AddSingleton<AppStateMachine>();
services.AddSingleton<StackedNotificationService>();
})
.Build();
UnhandledException += App_UnhandledException;
}
internal static IServiceScope CreateScope()
{
return (Current as GhostApplication)!.Host.Services.CreateScope();
}
public static T GetService<T>() where T : class
{
if ((Current as GhostApplication)!.Host.Services.GetService(typeof(T)) is not T service)
{
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
}
return service;
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
ActivationHandler.Handle(args);
Host.Start();
var stateMachine = GetService<AppStateMachine>();
stateMachine.RegisterState(StateKey.Landing, () => new LandingState());
stateMachine.RegisterState(StateKey.EngineEditor, () => new EditorState());
await stateMachine.TransitionToAsync(StateKey.Landing);
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
// TODO: Log and handle exceptions as appropriate.
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
}
}

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

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 432 B

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 456 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Contracts; namespace Ghost.App.Contracts;
internal interface INavigationAware internal interface INavigationAware
{ {

View File

@@ -1,6 +1,6 @@
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
namespace Ghost.Editor.Contracts; namespace Ghost.App.Contracts;
internal interface INotificationService internal interface INotificationService
{ {

View File

@@ -1,9 +1,9 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Ghost.Editor.Contracts; using Ghost.App.Contracts;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation; using Microsoft.UI.Xaml.Navigation;
namespace Ghost.Editor.Controls; namespace Ghost.App.Controls;
public abstract partial class ViewModelPage<VM> : Page public abstract partial class ViewModelPage<VM> : Page
where VM : ObservableObject where VM : ObservableObject

View File

@@ -0,0 +1,185 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>Ghost.App</RootNamespace>
<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\EditorControls.xaml" />
<None Remove="Controls\Internal\InspectorView.xaml" />
<None Remove="Controls\Internal\InternalControls.xaml" />
<None Remove="View\Pages\EngineEditor\ConsolePage.xaml" />
<None Remove="View\Pages\EngineEditor\HierarchyPage.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.Editor\Ghost.Editor.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>
<Folder Include="Resources\" />
</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\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="Controls\Internal\InspectorView.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>
<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>
</PropertyGroup>
</Project>

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ghost.Editor.Infrastructures.AppState; namespace Ghost.App.Infrastructures.AppState;
internal class AppStateMachine internal class AppStateMachine
{ {

View File

@@ -1,10 +1,11 @@
using Ghost.Data.Models; using Ghost.App.View.Windows;
using Ghost.Data.Models;
using Ghost.Data.Services; using Ghost.Data.Services;
using Ghost.Editor.View.Windows;
using Ghost.Engine; using Ghost.Engine;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ghost.Editor.Infrastructures.AppState; namespace Ghost.App.Infrastructures.AppState;
internal class EditorState : IAppState internal class EditorState : IAppState
{ {
@@ -13,9 +14,9 @@ internal class EditorState : IAppState
public Task OnExitingAsync() public Task OnExitingAsync()
{ {
if (App.Window == _window) if (GhostApplication.Window == _window)
{ {
App.Window = null; GhostApplication.Window = null;
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -24,18 +25,18 @@ internal class EditorState : IAppState
{ {
if (parameter is not ProjectMetadataInfo metadataInfo) if (parameter is not ProjectMetadataInfo metadataInfo)
{ {
throw new System.ArgumentException("Parameter must be of type ProjectMetadata.", nameof(parameter)); throw new ArgumentException("Parameter must be of type ProjectMetadata.", nameof(parameter));
} }
ProjectService.CurrentProject = metadataInfo; ProjectService.CurrentProject = metadataInfo;
_engineCore = App.GetService<EngineCore>(); _engineCore = GhostApplication.GetService<EngineCore>();
await _engineCore.StartAsync(new Engine.Models.LaunchArgument()); await _engineCore.StartAsync(new Engine.Models.LaunchArgument());
_window = App.GetService<EngineEditorWindow>(); _window = GhostApplication.GetService<EngineEditorWindow>();
_window.Activate(); _window.Activate();
App.Window = _window; GhostApplication.Window = _window;
} }
public async Task OnExitedAsync() public async Task OnExitedAsync()
@@ -45,9 +46,9 @@ internal class EditorState : IAppState
await _engineCore.ShutDownAsync(); await _engineCore.ShutDownAsync();
} }
if (App.Window == _window) if (GhostApplication.Window == _window)
{ {
App.Window = null; GhostApplication.Window = null;
} }
_window?.Close(); _window?.Close();

View File

@@ -1,6 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ghost.Editor.Infrastructures.AppState; namespace Ghost.App.Infrastructures.AppState;
internal interface IAppState internal interface IAppState
{ {

View File

@@ -1,7 +1,7 @@
using Ghost.Editor.View.Windows; using Ghost.App.View.Windows;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ghost.Editor.Infrastructures.AppState; namespace Ghost.App.Infrastructures.AppState;
internal class LandingState : IAppState internal class LandingState : IAppState
{ {
@@ -9,17 +9,17 @@ internal class LandingState : IAppState
public Task OnExitingAsync() public Task OnExitingAsync()
{ {
if (App.Window == _window) if (GhostApplication.Window == _window)
{ {
App.Window = null; GhostApplication.Window = null;
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task OnEnteringAsync(object? parameter) public Task OnEnteringAsync(object? parameter)
{ {
_window = App.GetService<LandingWindow>(); _window = GhostApplication.GetService<LandingWindow>();
App.Window = _window; GhostApplication.Window = _window;
_window.Activate(); _window.Activate();
return Task.CompletedTask; return Task.CompletedTask;
@@ -27,9 +27,9 @@ internal class LandingState : IAppState
public Task OnExitedAsync() public Task OnExitedAsync()
{ {
if (App.Window == _window) if (GhostApplication.Window == _window)
{ {
App.Window = null; GhostApplication.Window = null;
} }
_window?.Close(); _window?.Close();

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Infrastructures.AppState; namespace Ghost.App.Infrastructures.AppState;
internal enum StateKey internal enum StateKey
{ {

View File

@@ -1,4 +1,4 @@
namespace Ghost.Editor.Models; namespace Ghost.App.Models;
internal struct AssetItem() internal struct AssetItem()
{ {

View File

@@ -1,6 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
namespace Ghost.Editor.Models; namespace Ghost.App.Models;
internal class ExplorerItem(string name, string path, bool isDirectory) internal class ExplorerItem(string name, string path, bool isDirectory)
{ {

View File

@@ -2,7 +2,7 @@
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System; using System;
namespace Ghost.Editor.Services; namespace Ghost.App.Services;
public class StackedNotificationService public class StackedNotificationService
{ {

View File

@@ -11,4 +11,7 @@
<StaticResource x:Key="TabViewItemHeaderBackgroundSelected" ResourceKey="ControlFillColorSecondaryBrush" /> <StaticResource x:Key="TabViewItemHeaderBackgroundSelected" ResourceKey="ControlFillColorSecondaryBrush" />
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries> </ResourceDictionary.ThemeDictionaries>
<Style TargetType="TabView">
<Setter Property="TabWidthMode" Value="Compact" />
</Style>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -2,7 +2,7 @@
using System; using System;
using System.Linq; using System.Linq;
namespace Ghost.Editor.Helpers; namespace Ghost.App.Utilities;
public static class ComponentTypeCache public static class ComponentTypeCache
{ {

View File

@@ -2,7 +2,7 @@
using System; using System;
using System.IO; using System.IO;
namespace Ghost.Editor.Helpers.Converters; namespace Ghost.Editor.Utilities.Converters;
public partial class AssetPathToGlyphConverter : IValueConverter public partial class AssetPathToGlyphConverter : IValueConverter
{ {

View File

@@ -1,7 +1,7 @@
using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Data;
using System; using System;
namespace Ghost.Editor.Helpers.Converters; namespace Ghost.Editor.Utilities.Converters;
public partial class GetDirectoryNameConverter : IValueConverter public partial class GetDirectoryNameConverter : IValueConverter
{ {

View File

@@ -1,7 +1,7 @@
using Ghost.Data.Services; using Ghost.App.View.Pages.EngineEditor;
using Ghost.Editor.View.Pages.EngineEditor; using Ghost.App.View.Pages.Landing;
using Ghost.Editor.View.Pages.Landing; using Ghost.App.View.Windows;
using Ghost.Editor.View.Windows; using Ghost.Data.Services;
using Ghost.Editor.ViewModels.Pages.EngineEditor; using Ghost.Editor.ViewModels.Pages.EngineEditor;
using Ghost.Editor.ViewModels.Pages.Landing; using Ghost.Editor.ViewModels.Pages.Landing;
using Ghost.Editor.ViewModels.Windows; using Ghost.Editor.ViewModels.Windows;
@@ -9,7 +9,7 @@ using Ghost.Engine;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace Ghost.Editor.Helpers; namespace Ghost.App.Utilities;
internal static partial class HostHelper internal static partial class HostHelper
{ {

View File

@@ -5,14 +5,14 @@ using Windows.Storage;
using Windows.Storage.Pickers; using Windows.Storage.Pickers;
using WinRT.Interop; using WinRT.Interop;
namespace Ghost.Editor.Helpers; namespace Ghost.App.Utilities;
public static class SystemUtilities public static class SystemUtilities
{ {
public static async Task<StorageFolder?> OpenFolderPickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "") public static async Task<StorageFolder?> OpenFolderPickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "")
{ {
var openPicker = new FolderPicker(); var openPicker = new FolderPicker();
var hWnd = WindowNative.GetWindowHandle(App.Window); var hWnd = WindowNative.GetWindowHandle(GhostApplication.Window);
InitializeWithWindow.Initialize(openPicker, hWnd); InitializeWithWindow.Initialize(openPicker, hWnd);
openPicker.SuggestedStartLocation = startLocation; openPicker.SuggestedStartLocation = startLocation;
@@ -26,7 +26,7 @@ public static class SystemUtilities
public static async Task<StorageFile?> OpenFilePickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "", params IEnumerable<string> filter) public static async Task<StorageFile?> OpenFilePickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "", params IEnumerable<string> filter)
{ {
var openPicker = new FileOpenPicker(); var openPicker = new FileOpenPicker();
var hWnd = WindowNative.GetWindowHandle(App.Window); var hWnd = WindowNative.GetWindowHandle(GhostApplication.Window);
InitializeWithWindow.Initialize(openPicker, hWnd); InitializeWithWindow.Initialize(openPicker, hWnd);
openPicker.SuggestedStartLocation = startLocation; openPicker.SuggestedStartLocation = startLocation;

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<Page <Page
x:Class="Ghost.Editor.View.Pages.EngineEditor.ConsolePage" x:Class="Ghost.App.View.Pages.EngineEditor.ConsolePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Ghost.Editor.View.Pages.EngineEditor" xmlns:local="using:Ghost.App.View.Pages.EngineEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> mc:Ignorable="d">

View File

@@ -1,7 +1,7 @@
using Ghost.Editor.ViewModels.Pages.EngineEditor; using Ghost.Editor.ViewModels.Pages.EngineEditor;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
namespace Ghost.Editor.View.Pages.EngineEditor; namespace Ghost.App.View.Pages.EngineEditor;
internal sealed partial class ConsolePage : Page internal sealed partial class ConsolePage : Page
{ {
@@ -12,7 +12,7 @@ internal sealed partial class ConsolePage : Page
public ConsolePage() public ConsolePage()
{ {
ViewModel = App.GetService<ConsoleViewModel>(); ViewModel = GhostApplication.GetService<ConsoleViewModel>();
InitializeComponent(); InitializeComponent();
} }

View File

@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<Page <Page
x:Class="Ghost.Editor.View.Pages.EngineEditor.HierarchyPage" x:Class="Ghost.App.View.Pages.EngineEditor.HierarchyPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Ghost.Editor.View.Pages.EngineEditor" xmlns:local="using:Ghost.App.View.Pages.EngineEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sg="using:Ghost.Editor.Infrastructures.SceneGraph" xmlns:sg="using:Ghost.Editor.SceneGraph"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources> <Page.Resources>
<DataTemplate x:Key="SceneTemplate" x:DataType="sg:SceneGraphNode"> <DataTemplate x:Key="SceneTemplate" x:DataType="sg:SceneGraphNode">
<TreeViewItem <TreeViewItem
AutomationProperties.Name="{x:Bind Name}" AutomationProperties.Name="{x:Bind Name}"
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}" Background="{ThemeResource ControlSolidFillColorDefaultBrush}"
IsExpanded="True" IsExpanded="True"
ItemsSource="{x:Bind Children}"> ItemsSource="{x:Bind Children}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
@@ -33,10 +33,10 @@
</DataTemplate> </DataTemplate>
</Page.Resources> </Page.Resources>
<Grid Background="{ThemeResource LayerFillColorDefaultBrush}"> <Grid Padding="4,6" Background="{ThemeResource LayerFillColorDefaultBrush}">
<TreeView ItemsSource="{x:Bind ViewModel.SceneList}"> <TreeView ItemsSource="{x:Bind ViewModel.SceneList}">
<TreeView.ItemTemplateSelector> <TreeView.ItemTemplateSelector>
<local:HierarchyTemplateSector EntityTemplate="{StaticResource EntityTemplate}" SceneTemplate="{StaticResource SceneTemplate}" /> <local:HierarchyTemplateSector EntityTemplate="{StaticResource EntityTemplate}" WorldTemplate="{StaticResource SceneTemplate}" />
</TreeView.ItemTemplateSelector> </TreeView.ItemTemplateSelector>
</TreeView> </TreeView>
</Grid> </Grid>

View File

@@ -1,4 +1,4 @@
using Ghost.Editor.Infrastructures.SceneGraph; using Ghost.Editor.SceneGraph;
using Ghost.Editor.ViewModels.Pages.EngineEditor; using Ghost.Editor.ViewModels.Pages.EngineEditor;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
@@ -6,7 +6,7 @@ using Microsoft.UI.Xaml.Controls;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info. // and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Ghost.Editor.View.Pages.EngineEditor; namespace Ghost.App.View.Pages.EngineEditor;
/// <summary> /// <summary>
/// An empty page that can be used on its own or navigated to within a Frame. /// An empty page that can be used on its own or navigated to within a Frame.
/// </summary> /// </summary>
@@ -19,7 +19,7 @@ internal sealed partial class HierarchyPage : Page
public HierarchyPage() public HierarchyPage()
{ {
ViewModel = App.GetService<HierarchyViewModel>(); ViewModel = GhostApplication.GetService<HierarchyViewModel>();
InitializeComponent(); InitializeComponent();
} }
@@ -27,7 +27,7 @@ internal sealed partial class HierarchyPage : Page
internal partial class HierarchyTemplateSector : DataTemplateSelector internal partial class HierarchyTemplateSector : DataTemplateSelector
{ {
public DataTemplate? SceneTemplate public DataTemplate? WorldTemplate
{ {
get; get;
set; set;
@@ -41,16 +41,16 @@ internal partial class HierarchyTemplateSector : DataTemplateSelector
protected override DataTemplate SelectTemplateCore(object item) protected override DataTemplate SelectTemplateCore(object item)
{ {
if (SceneTemplate == null || EntityTemplate == null) if (WorldTemplate == null || EntityTemplate == null)
{ {
return base.SelectTemplateCore(item); return base.SelectTemplateCore(item);
} }
var node = (SceneGraphNode)item; var node = (SceneGraphNode)item;
return node.Type switch return node.NodeType switch
{ {
SceneGraphNode.NodeType.Scene => SceneTemplate, SceneGraphNodeType.Scene => WorldTemplate,
SceneGraphNode.NodeType.Entity => EntityTemplate, SceneGraphNodeType.Entity => EntityTemplate,
_ => base.SelectTemplateCore(item) _ => base.SelectTemplateCore(item)
}; };
} }

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<Page <Page
x:Class="Ghost.Editor.View.Pages.EngineEditor.ProjectPage" x:Class="Ghost.App.View.Pages.EngineEditor.ProjectPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="using:Ghost.Editor.Helpers.Converters" xmlns:converter="using:Ghost.Editor.Utilities.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Ghost.Editor.View.Pages.EngineEditor" xmlns:local="using:Ghost.App.View.Pages.EngineEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Ghost.Editor.Models" xmlns:model="using:Ghost.App.Models"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources> <Page.Resources>
@@ -28,6 +28,7 @@
BorderBrush="{ThemeResource CardStrokeColorDefaultSolid}" BorderBrush="{ThemeResource CardStrokeColorDefaultSolid}"
BorderThickness="0,0,1,0"> BorderThickness="0,0,1,0">
<TreeView <TreeView
x:Name="DirectoryTreeView"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.SubDirectories}" ItemsSource="{x:Bind ViewModel.SubDirectories}"
@@ -81,6 +82,7 @@
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<GridView <GridView
x:Name="AssetsGridView"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.DirectoryAssets, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.DirectoryAssets, Mode=OneWay}"

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