Add component editors and UI controls
Added the `HierarchyEditor` and `LocalToWorldEditor` classes to implement custom component editing functionality. Added the `Vector3Field` control for 3D vector manipulation and its corresponding XAML definition. Added the `ComponentDataView` and `ComponentObject` classes to manage component data display and access. Added the `CustomEditorAttribute` to mark classes as custom editors for specific components. Changed the `IInspectable` interface to use properties for `Icon`, `HeaderContent`, and `InspectorContent`. Changed the `PropertyField` class to enhance UI control binding capabilities. Changed the `EditorWorldManager` to improve world data loading and deserialization processes. Changed the `EntityNode` and `WorldNode` classes to update entity construction and component querying. Changed the `StaticResource` class to include new binding flags for component properties. Changed the `InspectorService` to remove old contract references and adopt new interfaces. Changed the `QueryEnumerable` and related files to update generic constraints for improved type safety. Changed the `QueryItem` class to reflect new generic constraints and enhance deconstruction. Changed the `World.Query` methods to utilize the updated generic constraints. Updated the `SerializationTest` to align with new entity creation and management practices.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using Ghost.Editor.Resources;
|
||||
using Ghost.Editor.Services.Contracts;
|
||||
using Ghost.Engine.Resources;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
@@ -40,7 +39,7 @@ public static class EditorWorldManager
|
||||
}
|
||||
|
||||
await using var readStream = new FileStream(worldPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var deserializedScene = await JsonSerializer.DeserializeAsync<WorldNode>(readStream, StaticResource.defaultSerializerOptions) ?? throw new Exception("Deserialization failed.");
|
||||
var deserializedScene = await JsonSerializer.DeserializeAsync<WorldNode>(readStream, Engine.Resources.StaticResource.defaultSerializerOptions) ?? throw new Exception("Deserialization failed.");
|
||||
|
||||
_loadedWorlds.Clear();
|
||||
|
||||
|
||||
@@ -1,70 +1,102 @@
|
||||
using Ghost.Editor.Contracts;
|
||||
using Ghost.Editor.Controls.Internal;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Resources;
|
||||
using Ghost.Engine.Editor;
|
||||
using Ghost.Entities;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
public partial class EntityNode : SceneGraphNode
|
||||
{
|
||||
private WorldNode _owner;
|
||||
private readonly Entity _entity;
|
||||
|
||||
public Entity Entity => _entity;
|
||||
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Entity;
|
||||
|
||||
public EntityNode(Entity entity, string name)
|
||||
public EntityNode(WorldNode owner, Entity entity, string name)
|
||||
{
|
||||
_owner = owner;
|
||||
_entity = entity;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
internal EntityNode()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EntityNode : IInspectable
|
||||
{
|
||||
public IconSource? Icon => EditorIconSource.entity_24;
|
||||
|
||||
public UIElement? HeaderContent()
|
||||
public UIElement? HeaderContent
|
||||
{
|
||||
var root = new StackPanel()
|
||||
get
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var root = new StackPanel()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
var nameText = new TextBox
|
||||
{
|
||||
Text = Name,
|
||||
FontWeight = FontWeights.Bold,
|
||||
};
|
||||
var idText = new TextBlock
|
||||
{
|
||||
Text = $"ID: {_entity.ID}",
|
||||
Margin = new Thickness(0, 5, 0, 0),
|
||||
};
|
||||
var nameText = new TextBox
|
||||
{
|
||||
Text = Name,
|
||||
FontWeight = FontWeights.Bold,
|
||||
};
|
||||
var idText = new TextBlock
|
||||
{
|
||||
Text = $"ID: {_entity.ID} Generation: {_entity.Generation}",
|
||||
Margin = new Thickness(5, 7, 0, 0),
|
||||
Opacity = 0.75,
|
||||
Style = Application.Current.Resources["CaptionTextBlockStyle"] as Style
|
||||
};
|
||||
|
||||
nameText.SetBinding(TextBox.TextProperty, new Binding
|
||||
{
|
||||
Source = this,
|
||||
Path = new PropertyPath(nameof(Name)),
|
||||
Mode = BindingMode.TwoWay,
|
||||
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
|
||||
});
|
||||
nameText.SetBinding(TextBox.TextProperty, new Binding
|
||||
{
|
||||
Source = this,
|
||||
Path = new PropertyPath(nameof(Name)),
|
||||
Mode = BindingMode.TwoWay,
|
||||
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
|
||||
});
|
||||
|
||||
root.Children.Add(nameText);
|
||||
root.Children.Add(idText);
|
||||
root.Children.Add(nameText);
|
||||
root.Children.Add(idText);
|
||||
|
||||
return root;
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public UIElement? InspectorContent()
|
||||
public UIElement? InspectorContent
|
||||
{
|
||||
return null;
|
||||
get
|
||||
{
|
||||
var root = new StackPanel()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
|
||||
foreach (var (typeHandle, componentPtr) in _owner.World.EntityManager.GetComponentsUnsafe(_entity))
|
||||
{
|
||||
if (componentPtr == IntPtr.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(typeHandle));
|
||||
if (type == null || type.GetCustomAttribute<HideEditorAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dataView = new ComponentDataView(type.Name, _owner.World, _entity, type);
|
||||
root.Children.Add(dataView);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,21 +10,21 @@ public class SceneGraphHelpers
|
||||
/// </summary>
|
||||
/// <param name="world">The world context where the entity will be created.</param>
|
||||
/// <param name="entity">The entity to be wrapped in the <see cref="EntityNode"/>.</param>
|
||||
public static EntityNode CreateEntityNode(World world, Entity entity, string name)
|
||||
public static EntityNode CreateEntityNode(WorldNode owner, Entity entity, string name)
|
||||
{
|
||||
world.EntityManager.AddComponent(entity, LocalToWorld.Identity);
|
||||
world.EntityManager.AddComponent(entity, Hierarchy.Root);
|
||||
return new EntityNode(entity, name);
|
||||
owner.World.EntityManager.AddComponent(entity, LocalToWorld.Identity);
|
||||
owner.World.EntityManager.AddComponent(entity, Hierarchy.Root);
|
||||
return new EntityNode(owner, entity, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Entity"/> and <see cref="EntityNode"/> entity with default components.
|
||||
/// </summary>
|
||||
/// <param name="world">The world context where the entity will be created.</param>
|
||||
public static EntityNode CreateEntityNode(World world, string name)
|
||||
/// <param name="owner">The world context where the entity will be created.</param>
|
||||
public static EntityNode CreateEntityNode(WorldNode owner, string name)
|
||||
{
|
||||
var entity = world.EntityManager.CreateEntity();
|
||||
return CreateEntityNode(world, entity, name);
|
||||
var entity = owner.World.EntityManager.CreateEntity();
|
||||
return CreateEntityNode(owner, entity, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Editor.Contracts;
|
||||
using Ghost.Editor.Core.AssetHandle;
|
||||
using Ghost.Editor.Core.AssetHandle;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Serializer;
|
||||
using Ghost.Editor.Resources;
|
||||
using Ghost.Engine.Components;
|
||||
@@ -76,21 +76,21 @@ public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
return result;
|
||||
}
|
||||
|
||||
private EntityNode BuildNodeRecursive(Entity entity, World world)
|
||||
private EntityNode BuildNodeRecursive(Entity entity)
|
||||
{
|
||||
if (!_entityNodeLookup.TryGetValue(entity, out var node))
|
||||
{
|
||||
node = new EntityNode(entity, "New Entity");
|
||||
node = new EntityNode(this, entity, "New Entity");
|
||||
_entityNodeLookup[entity] = node;
|
||||
}
|
||||
|
||||
var hc = world.EntityManager.GetComponent<Hierarchy>(entity);
|
||||
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);
|
||||
node.AddChild(BuildNodeRecursive(child));
|
||||
var childHC = _world.EntityManager.GetComponent<Hierarchy>(child);
|
||||
child = childHC.ValueRO.nextSibling;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
{
|
||||
if (hierarchy.ValueRO.parent == Entity.Invalid)
|
||||
{
|
||||
var node = BuildNodeRecursive(entity, _world);
|
||||
var node = BuildNodeRecursive(entity);
|
||||
AddChild(node);
|
||||
}
|
||||
}
|
||||
@@ -178,18 +178,7 @@ public partial class WorldNode : IInspectable
|
||||
await EditorWorldManager.LoadWorld(path);
|
||||
}
|
||||
|
||||
public UIElement? HeaderContent()
|
||||
{
|
||||
return new TextBlock
|
||||
{
|
||||
Text = Name,
|
||||
Style = Application.Current.Resources["SubtitleTextBlockStyle"] as Style,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
}
|
||||
public UIElement? HeaderContent => null;
|
||||
|
||||
public UIElement? InspectorContent()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public UIElement? InspectorContent => null;
|
||||
}
|
||||
Reference in New Issue
Block a user