|
|
@ -5,9 +5,9 @@ using System.Numerics; |
|
|
|
using gaemstone.Client.Utility; |
|
|
|
using gaemstone.Client.Utility; |
|
|
|
using gaemstone.ECS; |
|
|
|
using gaemstone.ECS; |
|
|
|
using gaemstone.Flecs; |
|
|
|
using gaemstone.Flecs; |
|
|
|
using gaemstone.Utility; |
|
|
|
|
|
|
|
using ImGuiNET; |
|
|
|
using ImGuiNET; |
|
|
|
using static gaemstone.Client.Systems.ImGuiManager; |
|
|
|
using static gaemstone.Client.Systems.ImGuiManager; |
|
|
|
|
|
|
|
using static gaemstone.Flecs.Core; |
|
|
|
using Icon = gaemstone.Client.Utility.ForkAwesome; |
|
|
|
using Icon = gaemstone.Client.Utility.ForkAwesome; |
|
|
|
using ImGuiInternal = ImGuiNET.Internal.ImGui; |
|
|
|
using ImGuiInternal = ImGuiNET.Internal.ImGui; |
|
|
|
|
|
|
|
|
|
|
@ -75,8 +75,8 @@ public class EntityInspector |
|
|
|
SetDocInfo("/flecs/system/System" , 1 , Icon.Cog , 1.0f, 0.7f, 0.7f); |
|
|
|
SetDocInfo("/flecs/system/System" , 1 , Icon.Cog , 1.0f, 0.7f, 0.7f); |
|
|
|
SetDocInfo("/flecs/core/Observer" , 2 , Icon.Eye , 1.0f, 0.8f, 0.8f); |
|
|
|
SetDocInfo("/flecs/core/Observer" , 2 , Icon.Eye , 1.0f, 0.8f, 0.8f); |
|
|
|
SetDocInfo("/gaemstone/Doc/Relation" , 3 , Icon.ShareAlt , 0.7f, 1.0f, 0.8f); |
|
|
|
SetDocInfo("/gaemstone/Doc/Relation" , 3 , Icon.ShareAlt , 0.7f, 1.0f, 0.8f); |
|
|
|
SetDocInfo("/flecs/core/Tag" , 4 , Icon.Tag , 0.7f, 0.8f, 1.0f); |
|
|
|
SetDocInfo("/flecs/core/Component" , 4 , Icon.PencilSquare , 0.6f, 0.6f, 1.0f); |
|
|
|
SetDocInfo("/flecs/core/Component" , 5 , Icon.PencilSquare , 0.6f, 0.6f, 1.0f); |
|
|
|
SetDocInfo("/flecs/core/Tag" , 5 , Icon.Tag , 0.7f, 0.8f, 1.0f); |
|
|
|
SetDocInfo("/flecs/core/Prefab" , 6 , Icon.Cube , 0.9f, 0.8f, 1.0f); |
|
|
|
SetDocInfo("/flecs/core/Prefab" , 6 , Icon.Cube , 0.9f, 0.8f, 1.0f); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -94,7 +94,7 @@ public class EntityInspector |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[System] |
|
|
|
[System] |
|
|
|
public void ShowExplorerWindow(EntityRef window, InspectorWindow _, History? history) |
|
|
|
public void ShowInspectorWindow(EntityRef window, InspectorWindow _, History? history) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var isOpen = true; |
|
|
|
var isOpen = true; |
|
|
|
var fontSize = ImGui.GetFontSize(); |
|
|
|
var fontSize = ImGui.GetFontSize(); |
|
|
@ -173,9 +173,9 @@ public class EntityInspector |
|
|
|
ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthFixed); |
|
|
|
ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthFixed); |
|
|
|
|
|
|
|
|
|
|
|
ImGui.TableNextColumn(); |
|
|
|
ImGui.TableNextColumn(); |
|
|
|
var hasExpanded = window.Has<Expanded, Core.Wildcard>(); |
|
|
|
var hasExpanded = window.Has<Expanded, Wildcard>(); |
|
|
|
if (IconButtonWithToolTip(Icon.Outdent, "Collapse all items in the Explorer View", hasExpanded)) |
|
|
|
if (IconButtonWithToolTip(Icon.Outdent, "Collapse all items in the Explorer View", hasExpanded)) |
|
|
|
window.Remove<Expanded, Core.Wildcard>(); |
|
|
|
window.Remove<Expanded, Wildcard>(); |
|
|
|
|
|
|
|
|
|
|
|
if (history != null) { |
|
|
|
if (history != null) { |
|
|
|
var hasPrev = ((selected != null) ? history.Current?.Prev : history.Current) != null; |
|
|
|
var hasPrev = ((selected != null) ? history.Current?.Prev : history.Current) != null; |
|
|
@ -215,6 +215,7 @@ public class EntityInspector |
|
|
|
|
|
|
|
|
|
|
|
ImGui.SameLine(); |
|
|
|
ImGui.SameLine(); |
|
|
|
if (IconButtonWithToolTip(Icon.Trash, "Delete the current entity", (selected != null))) { |
|
|
|
if (IconButtonWithToolTip(Icon.Trash, "Delete the current entity", (selected != null))) { |
|
|
|
|
|
|
|
// TODO: Delete history for deleted entity? |
|
|
|
SetSelected(window, history, selected!.Parent); |
|
|
|
SetSelected(window, history, selected!.Parent); |
|
|
|
selected.Delete(); // TODO: Confirmation dialog? |
|
|
|
selected.Delete(); // TODO: Confirmation dialog? |
|
|
|
} |
|
|
|
} |
|
|
@ -272,208 +273,174 @@ public class EntityInspector |
|
|
|
ImGui.PopStyleVar(); |
|
|
|
ImGui.PopStyleVar(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private struct EntitySummary |
|
|
|
private class ExplorerEntry |
|
|
|
: IComparable<EntitySummary> |
|
|
|
: IComparable<ExplorerEntry> |
|
|
|
{ |
|
|
|
{ |
|
|
|
public Entity Entity { get; init; } |
|
|
|
private readonly EntityInspector _module; |
|
|
|
public SpecialType? Type { get; init; } |
|
|
|
|
|
|
|
public string? Name { get; init; } |
|
|
|
public EntityRef Entity { get; } |
|
|
|
public string? DocName { get; init; } |
|
|
|
public int NumChildren { get; } |
|
|
|
public Color? DocColor { get; init; } |
|
|
|
public bool HasChildren => (NumChildren > 0); |
|
|
|
public bool HasChildren { get; init; } |
|
|
|
public bool IsExpanded { get; } |
|
|
|
public bool IsExpanded { get; init; } |
|
|
|
public bool IsDisabled { get; } |
|
|
|
public bool IsDisabled { get; init; } |
|
|
|
|
|
|
|
|
|
|
|
private int _nameCached; private string? _name; |
|
|
|
public int CompareTo(EntitySummary other) |
|
|
|
private int _docNameCached; private string? _docName; |
|
|
|
|
|
|
|
public string? Name => (_nameCached++ > 0) ? _name : _name = Entity.Name; |
|
|
|
|
|
|
|
public string? DocName => (_docNameCached++ > 0) ? _docName : _docName = Entity.GetDocName(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private float? _priority; |
|
|
|
|
|
|
|
private EntityRef? _type; |
|
|
|
|
|
|
|
public float Priority => EnsureTypeCached()._priority!.Value; |
|
|
|
|
|
|
|
public EntityRef? Type => EnsureTypeCached()._type; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public ExplorerEntry(EntityInspector module, EntityRef entity, |
|
|
|
|
|
|
|
int numChildren, bool isExpanded, bool isDisabled) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
_module = module; |
|
|
|
|
|
|
|
Entity = entity; |
|
|
|
|
|
|
|
NumChildren = numChildren; |
|
|
|
|
|
|
|
IsExpanded = (isExpanded && HasChildren); |
|
|
|
|
|
|
|
IsDisabled = isDisabled; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private ExplorerEntry EnsureTypeCached() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (_priority != null) return this; |
|
|
|
|
|
|
|
(_type, _priority) = _module.FindDisplayType(Entity); |
|
|
|
|
|
|
|
return this; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public int CompareTo(ExplorerEntry? other) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (other == null) return -1; |
|
|
|
|
|
|
|
return Compare(Priority, other.Priority) |
|
|
|
|
|
|
|
?? Compare(Name, other.Name) |
|
|
|
|
|
|
|
?? Compare(DocName, other.DocName) |
|
|
|
|
|
|
|
?? Compare(Entity.Id, other.Entity.Id) |
|
|
|
|
|
|
|
?? 0; |
|
|
|
static int? Compare<T>(T x, T y) { |
|
|
|
static int? Compare<T>(T x, T y) { |
|
|
|
if (x is null) { if (y is null) return null; else return 1; } |
|
|
|
if (x is null) { if (y is null) return null; else return 1; } |
|
|
|
else if (y is null) return -1; |
|
|
|
else if (y is null) return -1; |
|
|
|
var result = Comparer<T>.Default.Compare(x, y); |
|
|
|
var result = Comparer<T>.Default.Compare(x, y); |
|
|
|
return (result != 0) ? result : null; |
|
|
|
return (result != 0) ? result : null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Compare(Type, other.Type) |
|
|
|
|
|
|
|
?? Compare(Name, other.Name) |
|
|
|
|
|
|
|
?? Compare(DocName, other.DocName) |
|
|
|
|
|
|
|
?? Compare(Entity.Id, other.Entity.Id) |
|
|
|
|
|
|
|
?? 0; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public string DisplayName { get { |
|
|
|
|
|
|
|
var name = (DocName != null) ? $"\"{DocName}\"" : Name ?? Entity.Id.ToString(); |
|
|
|
|
|
|
|
if (Type != null) name = $"{DisplayIcon} {name}"; |
|
|
|
|
|
|
|
return name; |
|
|
|
|
|
|
|
} } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public string? DisplayIcon => Type switch { |
|
|
|
|
|
|
|
SpecialType.Module => Icon.Archive, |
|
|
|
|
|
|
|
SpecialType.System => Icon.Cog, |
|
|
|
|
|
|
|
SpecialType.Relation => Icon.ShareAlt, |
|
|
|
|
|
|
|
SpecialType.Component => Icon.PencilSquare, |
|
|
|
|
|
|
|
SpecialType.Tag => Icon.Tag, |
|
|
|
|
|
|
|
SpecialType.Prefab => Icon.Cube, |
|
|
|
|
|
|
|
_ => null, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Color? DisplayColor => DocColor ?? Type switch { |
|
|
|
|
|
|
|
SpecialType.Module => Color.FromRGB(1.0f, 0.9f, 0.7f), |
|
|
|
|
|
|
|
SpecialType.System => Color.FromRGB(1.0f, 0.7f, 0.7f), |
|
|
|
|
|
|
|
SpecialType.Relation => Color.FromRGB(0.7f, 1.0f, 0.8f), |
|
|
|
|
|
|
|
SpecialType.Component => Color.FromRGB(0.6f, 0.6f, 1.0f), |
|
|
|
|
|
|
|
SpecialType.Tag => Color.FromRGB(0.7f, 0.8f, 1.0f), |
|
|
|
|
|
|
|
SpecialType.Prefab => Color.FromRGB(0.9f, 0.8f, 1.0f), |
|
|
|
|
|
|
|
_ => null, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public enum SpecialType |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
Module, |
|
|
|
|
|
|
|
System, |
|
|
|
|
|
|
|
Relation, |
|
|
|
|
|
|
|
Component, |
|
|
|
|
|
|
|
Tag, |
|
|
|
|
|
|
|
Prefab, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private const int MAX_CHILDREN = 64; |
|
|
|
|
|
|
|
private void ExplorerView(EntityRef window, History? history, EntityRef? selected) |
|
|
|
private void ExplorerView(EntityRef window, History? history, EntityRef? selected) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var Expanded = window.World.LookupByTypeOrThrow<Expanded>(); |
|
|
|
// For some reason, the analyzer thinks world can be |
|
|
|
|
|
|
|
// nullable, so let's be explicit about the type here. |
|
|
|
List<EntitySummary> GetSummaries(Entity? parent) { |
|
|
|
World world = window.World; |
|
|
|
var result = new List<EntitySummary>(); |
|
|
|
|
|
|
|
var expression = $"(ChildOf, {parent?.Id ?? 0})" // Must be child of parent, or root entity. |
|
|
|
var Wildcard = world.LookupByTypeOrThrow<Wildcard>().Entity; |
|
|
|
+ ",?(Identifier, Name)" // Name (in hierarchy) |
|
|
|
var Any = world.LookupByTypeOrThrow<Any>().Entity; |
|
|
|
+ ",?(flecs.doc.Description, Name)" // DocName (human-readable) |
|
|
|
var This = world.LookupByTypeOrThrow<This>().Entity; |
|
|
|
+ ",?(flecs.doc.Description, flecs.doc.Color)" // DocColor |
|
|
|
var Var = world.LookupByTypeOrThrow<Variable>().Entity; |
|
|
|
+ ",[none] ?ChildOf(_, $This)" // HasChildren |
|
|
|
bool IsSpecialEntity(Entity entity) |
|
|
|
+ $",?{Expanded.Id}({window.Id}, $This)" // IsExpanded |
|
|
|
=> (entity == Wildcard) || (entity == Any) |
|
|
|
+ ",?Disabled" // IsDisabled |
|
|
|
|| (entity == This) || (entity == Var); |
|
|
|
+ ",?Module,?flecs.system.System,?gaemstone.Doc.Relation,?Component,?Tag,?Prefab"; // Type |
|
|
|
|
|
|
|
|
|
|
|
var expId = world.LookupByTypeOrThrow<Expanded>().Id; |
|
|
|
using (var rule = new Rule(window.World, new(expression))) { |
|
|
|
List<ExplorerEntry> GetEntries(Entity? parent) { |
|
|
|
foreach (var iter in rule.Iter()) { |
|
|
|
var result = new List<ExplorerEntry>(); |
|
|
|
var names = iter.FieldOrEmpty<Core.Identifier>(2); |
|
|
|
using var rule = new Rule(world, new( |
|
|
|
var docNames = iter.FieldOrEmpty<Flecs.Doc.Description>(3); |
|
|
|
$"(ChildOf, {parent?.Id ?? 0})" // Must be child of parent, or root entity. |
|
|
|
var docColors = iter.FieldOrEmpty<Flecs.Doc.Description>(4); |
|
|
|
+ $",?{expId}({window.Id}, $This)" // Whether entity is expanded in explorer view. |
|
|
|
|
|
|
|
+ $",?Disabled" // Don't filter out disabled entities. |
|
|
|
var hasChildren = iter.FieldIsSet(5); |
|
|
|
)); |
|
|
|
var isExpanded = iter.FieldIsSet(6); |
|
|
|
foreach (var iter in rule.Iter()) { |
|
|
|
var isDisabled = iter.FieldIsSet(7); |
|
|
|
var isExpanded = iter.FieldIsSet(2); |
|
|
|
|
|
|
|
var isDisabled = iter.FieldIsSet(3); |
|
|
|
var isModule = iter.FieldIsSet(8); |
|
|
|
for (var i = 0; i < iter.Count; i++) { |
|
|
|
var isSystem = iter.FieldIsSet(9); |
|
|
|
var entity = iter.Entity(i); |
|
|
|
var isRelation = iter.FieldIsSet(10); |
|
|
|
var count = IsSpecialEntity(entity) ? 0 |
|
|
|
var components = iter.FieldOrEmpty<Core.Component>(11); |
|
|
|
: IdRef.Pair<ChildOf>(entity).Count; |
|
|
|
var isTag = iter.FieldIsSet(12); |
|
|
|
result.Add(new(this, entity, count, isExpanded, isDisabled)); |
|
|
|
var isPrefab = iter.FieldIsSet(13); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < iter.Count; i++) { |
|
|
|
|
|
|
|
// Certain built-in components in Flecs actually have a size of 0, |
|
|
|
|
|
|
|
// thus don't actually hold any value and behave more like tags. |
|
|
|
|
|
|
|
// We pretend they are just tags and mark them as such. |
|
|
|
|
|
|
|
var component = components.GetOrNull(i); |
|
|
|
|
|
|
|
var isComponent = (component?.Size > 0); |
|
|
|
|
|
|
|
var isTagEquiv = (component?.Size == 0) || isTag; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var type = isModule ? SpecialType.Module |
|
|
|
|
|
|
|
: isSystem ? SpecialType.System |
|
|
|
|
|
|
|
: isRelation ? SpecialType.Relation |
|
|
|
|
|
|
|
: isComponent ? SpecialType.Component |
|
|
|
|
|
|
|
: isTagEquiv ? SpecialType.Tag |
|
|
|
|
|
|
|
: isPrefab ? SpecialType.Prefab |
|
|
|
|
|
|
|
: (SpecialType?)null; |
|
|
|
|
|
|
|
result.Add(new() { |
|
|
|
|
|
|
|
Entity = iter.Entity(i), |
|
|
|
|
|
|
|
Type = type, |
|
|
|
|
|
|
|
Name = names.GetOrNull(i)?.ToString(), |
|
|
|
|
|
|
|
DocName = docNames.GetOrNull(i)?.ToString(), |
|
|
|
|
|
|
|
DocColor = Color.TryParseHex(docColors.GetOrNull(i)?.ToString()), |
|
|
|
|
|
|
|
HasChildren = hasChildren, |
|
|
|
|
|
|
|
IsExpanded = isExpanded, |
|
|
|
|
|
|
|
IsDisabled = isDisabled, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
if (result.Count > MAX_CHILDREN) |
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
result.Sort(); |
|
|
|
|
|
|
|
return result; |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void EntryNode(EntitySummary summary) { |
|
|
|
var spacingX = ImGui.GetStyle().ItemInnerSpacing.X; |
|
|
|
var entity = new EntityRef(window.World, summary.Entity); |
|
|
|
var spacingY = ImGui.GetStyle().ItemSpacing.Y; |
|
|
|
var isExpanded = summary.IsExpanded; |
|
|
|
var nodeHeight = ImGui.GetTextLineHeight(); |
|
|
|
var isSelected = (selected == entity); |
|
|
|
|
|
|
|
|
|
|
|
var scrollToSelected = window.Has<ScrollToSelected>(); |
|
|
|
var flags = ImGuiTreeNodeFlags.OpenOnArrow |
|
|
|
|
|
|
|
| ImGuiTreeNodeFlags.OpenOnDoubleClick |
|
|
|
float GetIndent(int depth) => depth * (nodeHeight + spacingX); |
|
|
|
| ImGuiTreeNodeFlags.SpanAvailWidth; |
|
|
|
|
|
|
|
if (!summary.HasChildren) flags |= ImGuiTreeNodeFlags.Leaf |
|
|
|
bool RenderNode(ExplorerEntry entry, int? depth) { |
|
|
|
| ImGuiTreeNodeFlags.Bullet |
|
|
|
var entity = entry.Entity; |
|
|
|
| ImGuiTreeNodeFlags.NoTreePushOnOpen; |
|
|
|
var startY = ImGui.GetCursorPosY(); |
|
|
|
if (isSelected) flags |= ImGuiTreeNodeFlags.Selected; |
|
|
|
var isVisible = ImGui.IsRectVisible(new(nodeHeight, nodeHeight)); |
|
|
|
|
|
|
|
var isSelected = (entity == selected); |
|
|
|
var hasColor = false; |
|
|
|
var isRenderPass = (depth != null); |
|
|
|
if (summary.DisplayColor is Color color) { ImGui.PushStyleColor(ImGuiCol.Text, color.RGBA); hasColor = true; } |
|
|
|
|
|
|
|
if (summary.DocName != null) ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[2]); |
|
|
|
if (isRenderPass) { |
|
|
|
if (summary.IsDisabled) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().DisabledAlpha); |
|
|
|
// Button for expanding the child entries. |
|
|
|
ImGui.SetNextItemOpen(isExpanded); |
|
|
|
if (entry.HasChildren && !entry.IsExpanded) { |
|
|
|
ImGui.TreeNodeEx(summary.DisplayName, flags); |
|
|
|
ImGui.SetCursorPosX(GetIndent(depth!.Value)); |
|
|
|
if (summary.IsDisabled) ImGui.PopStyleVar(); |
|
|
|
if (ImGui.Button($"##Expand{entity.Id}", new(nodeHeight))) |
|
|
|
if (summary.DocName != null) ImGui.PopFont(); |
|
|
|
window.Add<Expanded>(entity); |
|
|
|
if (hasColor) ImGui.PopStyleColor(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// When hovering over the node, display brief description (if available). |
|
|
|
|
|
|
|
if (ImGui.IsItemHovered() && entity.GetDocBrief() is string brief) |
|
|
|
|
|
|
|
ImGui.SetTooltip(brief); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// When node is clicked (but not on the arrow), select this entity. |
|
|
|
|
|
|
|
if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen()) |
|
|
|
|
|
|
|
SetSelected(window, history, entity, scrollTo: false); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// When node is toggled, toggle (Expanded, entity) |
|
|
|
|
|
|
|
// relation on the inspector window entity. |
|
|
|
|
|
|
|
if (ImGui.IsItemToggledOpen()) { |
|
|
|
|
|
|
|
if (isExpanded) { |
|
|
|
|
|
|
|
isExpanded = false; |
|
|
|
|
|
|
|
window.Remove(Expanded, entity); |
|
|
|
|
|
|
|
} else if (summary.HasChildren) { |
|
|
|
|
|
|
|
isExpanded = true; |
|
|
|
|
|
|
|
window.Add(Expanded, entity); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (window.Has<ScrollToSelected>() && isSelected) { |
|
|
|
// Render the entity text, or a dummy if not necessary. |
|
|
|
ImGui.SetScrollHereY(); |
|
|
|
ImGui.SetCursorPos(new(GetIndent(depth!.Value + 1), startY)); |
|
|
|
window.Remove<ScrollToSelected>(); |
|
|
|
var flags = RenderEntityFlags.IsHeaderLike |
|
|
|
} |
|
|
|
| RenderEntityFlags.SpanAvailWidth; |
|
|
|
|
|
|
|
if (isSelected) flags |= RenderEntityFlags.IsSelected; |
|
|
|
|
|
|
|
if (isVisible) RenderEntity(window, history, entity, flags); |
|
|
|
|
|
|
|
else ImGui.Dummy(new(nodeHeight, nodeHeight)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Scroll to this entry if selected and scrolling was requested. |
|
|
|
|
|
|
|
if (scrollToSelected && isSelected) { |
|
|
|
|
|
|
|
window.Remove<ScrollToSelected>(); |
|
|
|
|
|
|
|
ImGui.SetScrollHereY(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else ImGui.Dummy(new(nodeHeight, nodeHeight)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If node is not expanded, don't run any of code involving child entries. |
|
|
|
|
|
|
|
if (!entry.IsExpanded) return isVisible; |
|
|
|
|
|
|
|
|
|
|
|
if (isExpanded && summary.HasChildren) { |
|
|
|
// Get the child entries of this entity. |
|
|
|
var children = GetSummaries(entity); |
|
|
|
var entries = GetEntries(entity); |
|
|
|
if (children.Count > MAX_CHILDREN) { |
|
|
|
|
|
|
|
ImGui.TreePush(); |
|
|
|
// If scrolling to selected, make sure every node is sorted. |
|
|
|
ImGui.TextWrapped($"{Icon.ExclamationTriangle} Too many children. " + |
|
|
|
// Otherwise the target node's position might not be right. |
|
|
|
"If an entity's full path is known, it can be entered in the path input."); |
|
|
|
if (!scrollToSelected) { |
|
|
|
ImGui.TreePop(); |
|
|
|
foreach (var child in entries) |
|
|
|
} else foreach (var child in children) |
|
|
|
if (RenderNode(child, null)) |
|
|
|
EntryNode(child); |
|
|
|
isVisible = true; |
|
|
|
ImGui.TreePop(); |
|
|
|
if (!isVisible) return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!isRenderPass) return isVisible; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entries.Sort(); |
|
|
|
|
|
|
|
ImGui.SetCursorPosY(startY + nodeHeight); |
|
|
|
|
|
|
|
foreach (var child in entries) |
|
|
|
|
|
|
|
RenderNode(child, depth + 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var fullHeight = ImGui.GetCursorPosY() - startY - spacingY; |
|
|
|
|
|
|
|
ImGui.SetCursorPos(new(GetIndent(depth!.Value), startY)); |
|
|
|
|
|
|
|
if (ImGui.Button($"##Expand{entity.Id}", new(nodeHeight, fullHeight))) |
|
|
|
|
|
|
|
window.Remove<Expanded>(entity); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
foreach (var summary in GetSummaries(Entity.None)) |
|
|
|
var entries = GetEntries(null); |
|
|
|
EntryNode(summary); |
|
|
|
entries.Sort(); |
|
|
|
|
|
|
|
foreach (var child in entries) |
|
|
|
|
|
|
|
RenderNode(child, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void ComponentsTab(EntityRef window, History? history, EntityRef? selected) |
|
|
|
private void ComponentsTab(EntityRef window, History? history, EntityRef? selected) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (selected == null) return; |
|
|
|
if (selected == null) return; |
|
|
|
var ChildOf = window.World.LookupByTypeOrThrow<Core.ChildOf>(); |
|
|
|
var ChildOf = window.World.LookupByTypeOrThrow<ChildOf>(); |
|
|
|
foreach (var id in selected.Type) { |
|
|
|
foreach (var id in selected.Type) { |
|
|
|
// Hide ChildOf relations, as they are visible in the explorer. |
|
|
|
// Hide ChildOf relations, as they are visible in the explorer. |
|
|
|
if (id.IsPair && (id.Id.RelationUnsafe == ChildOf)) continue; |
|
|
|
if (id.IsPair && (id.Id.RelationUnsafe == ChildOf)) continue; |
|
|
@ -485,8 +452,8 @@ public class EntityInspector |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (selected == null) return; |
|
|
|
if (selected == null) return; |
|
|
|
var world = window.World; |
|
|
|
var world = window.World; |
|
|
|
var ChildOf = world.LookupByTypeOrThrow<Core.ChildOf>(); |
|
|
|
var ChildOf = world.LookupByTypeOrThrow<ChildOf>(); |
|
|
|
var Wildcard = world.LookupByTypeOrThrow<Core.Wildcard>(); |
|
|
|
var Wildcard = world.LookupByTypeOrThrow<Wildcard>(); |
|
|
|
|
|
|
|
|
|
|
|
if (ImGui.CollapsingHeader($"As {Icon.Tag} Entity", ImGuiTreeNodeFlags.DefaultOpen)) |
|
|
|
if (ImGui.CollapsingHeader($"As {Icon.Tag} Entity", ImGuiTreeNodeFlags.DefaultOpen)) |
|
|
|
foreach (var iter in Iterator.FromTerm(world, new(selected))) |
|
|
|
foreach (var iter in Iterator.FromTerm(world, new(selected))) |
|
|
@ -624,7 +591,7 @@ public class EntityInspector |
|
|
|
bool scrollTo = true) // Should entity be scrolled to in the explorer view? |
|
|
|
bool scrollTo = true) // Should entity be scrolled to in the explorer view? |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (entity != null) window.Add<Selected>(entity); |
|
|
|
if (entity != null) window.Add<Selected>(entity); |
|
|
|
else window.Remove<Selected, Core.Wildcard>(); |
|
|
|
else window.Remove<Selected, Wildcard>(); |
|
|
|
|
|
|
|
|
|
|
|
for (var parent = entity?.Parent; parent != null; parent = parent.Parent) |
|
|
|
for (var parent = entity?.Parent; parent != null; parent = parent.Parent) |
|
|
|
window.Add<Expanded>(parent); |
|
|
|
window.Add<Expanded>(parent); |
|
|
@ -661,10 +628,10 @@ public class EntityInspector |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Rule? _findDisplayTypeRule; |
|
|
|
private Rule? _findDisplayTypeRule; |
|
|
|
private EntityRef? FindDisplayType(EntityRef entity) |
|
|
|
private (EntityRef? DisplayType, float Priority) FindDisplayType(EntityRef entity) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var world = entity.World; |
|
|
|
var world = entity.World; |
|
|
|
var component = world.LookupByTypeOrThrow<Core.Component>(); |
|
|
|
var component = world.LookupByTypeOrThrow<Component>(); |
|
|
|
|
|
|
|
|
|
|
|
var rule = _findDisplayTypeRule ??= new Rule(world, new( |
|
|
|
var rule = _findDisplayTypeRule ??= new Rule(world, new( |
|
|
|
$"$Type, gaemstone.Doc.DisplayType($Type)")); |
|
|
|
$"$Type, gaemstone.Doc.DisplayType($Type)")); |
|
|
@ -675,13 +642,13 @@ public class EntityInspector |
|
|
|
foreach (var iter in _findDisplayTypeRule.Iter().SetVar(rule.ThisVar!, entity)) |
|
|
|
foreach (var iter in _findDisplayTypeRule.Iter().SetVar(rule.ThisVar!, entity)) |
|
|
|
for (var i = 0; i < iter.Count; i++) { |
|
|
|
for (var i = 0; i < iter.Count; i++) { |
|
|
|
var type = iter.GetVar(typeVar); |
|
|
|
var type = iter.GetVar(typeVar); |
|
|
|
if ((type == component) && (entity.GetOrNull<Core.Component>(component)?.Size == 0)) |
|
|
|
if ((type == component) && (entity.GetOrNull<Component>(component)?.Size == 0)) |
|
|
|
type = world.LookupByTypeOrThrow<Core.Tag>(); |
|
|
|
type = world.LookupByTypeOrThrow<Tag>(); |
|
|
|
var priority = type.GetOrNull<DocPriority>()?.Value ?? float.MaxValue; |
|
|
|
var priority = type.GetOrNull<DocPriority>()?.Value ?? float.MaxValue; |
|
|
|
if (priority <= curPriority) { curType = type; curPriority = priority; } |
|
|
|
if (priority <= curPriority) { curType = type; curPriority = priority; } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return curType; |
|
|
|
return (curType, curPriority); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -689,7 +656,7 @@ public class EntityInspector |
|
|
|
// == Utility ImGui Functions == |
|
|
|
// == Utility ImGui Functions == |
|
|
|
// ============================= |
|
|
|
// ============================= |
|
|
|
|
|
|
|
|
|
|
|
private void RenderIdentifier(EntityRef window, History? history, IdentifierRef id) |
|
|
|
private void RenderIdentifier(EntityRef window, History? history, IdRef id) |
|
|
|
{ |
|
|
|
{ |
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, ImGui.GetStyle().ItemSpacing.Y)); |
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, ImGui.GetStyle().ItemSpacing.Y)); |
|
|
|
if (id.AsPair() is (EntityRef relation, EntityRef target)) { |
|
|
|
if (id.AsPair() is (EntityRef relation, EntityRef target)) { |
|
|
@ -705,8 +672,14 @@ public class EntityInspector |
|
|
|
ImGui.PopStyleVar(); |
|
|
|
ImGui.PopStyleVar(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void RenderEntity(EntityRef window, History? history, EntityRef entity) |
|
|
|
private void RenderEntity( |
|
|
|
|
|
|
|
EntityRef window, History? history, EntityRef entity, |
|
|
|
|
|
|
|
RenderEntityFlags flags = default) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
var spanAvailWidth = (flags & RenderEntityFlags.SpanAvailWidth) != 0; |
|
|
|
|
|
|
|
var isHeaderLike = (flags & RenderEntityFlags.IsHeaderLike) != 0; |
|
|
|
|
|
|
|
var isSelected = (flags & RenderEntityFlags.IsSelected) != 0; |
|
|
|
|
|
|
|
|
|
|
|
var pos = ImGui.GetCursorScreenPos(); |
|
|
|
var pos = ImGui.GetCursorScreenPos(); |
|
|
|
// Adjust based on AlignTextToFramePadding() or similar. |
|
|
|
// Adjust based on AlignTextToFramePadding() or similar. |
|
|
|
pos.Y += ImGuiInternal.GetCurrentWindow().DC.CurrLineTextBaseOffset; |
|
|
|
pos.Y += ImGuiInternal.GetCurrentWindow().DC.CurrLineTextBaseOffset; |
|
|
@ -715,7 +688,7 @@ public class EntityInspector |
|
|
|
var dummySize = new Vector2(20, ImGui.GetTextLineHeight()); |
|
|
|
var dummySize = new Vector2(20, ImGui.GetTextLineHeight()); |
|
|
|
if (!ImGui.IsRectVisible(pos, pos + dummySize)) { ImGui.Dummy(dummySize); return; } |
|
|
|
if (!ImGui.IsRectVisible(pos, pos + dummySize)) { ImGui.Dummy(dummySize); return; } |
|
|
|
|
|
|
|
|
|
|
|
var displayType = FindDisplayType(entity); |
|
|
|
var (displayType, _) = FindDisplayType(entity); |
|
|
|
var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor()); |
|
|
|
var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor()); |
|
|
|
var docIcon = entity.GetOrNull<DocIcon>()?.Value.ToString() ?? displayType?.GetOrNull<DocIcon>()?.Value.ToString(); |
|
|
|
var docIcon = entity.GetOrNull<DocIcon>()?.Value.ToString() ?? displayType?.GetOrNull<DocIcon>()?.Value.ToString(); |
|
|
|
var docName = entity.GetDocName(false); |
|
|
|
var docName = entity.GetDocName(false); |
|
|
@ -724,22 +697,42 @@ public class EntityInspector |
|
|
|
var displayName = (docName != null) ? $"\"{docName}\"" : entity.Name ?? entity.Id.ToString(); |
|
|
|
var displayName = (docName != null) ? $"\"{docName}\"" : entity.Name ?? entity.Id.ToString(); |
|
|
|
if (docIcon is string icon) displayName = $"{icon} {displayName}"; |
|
|
|
if (docIcon is string icon) displayName = $"{icon} {displayName}"; |
|
|
|
|
|
|
|
|
|
|
|
var font = ImGui.GetIO().Fonts.Fonts[(docName != null) ? 2 : 0]; |
|
|
|
var font = ImGui.GetIO().Fonts.Fonts[(docName != null) ? 2 : 0]; |
|
|
|
ImGui.PushFont(font); var size = ImGui.CalcTextSize(displayName); ImGui.PopFont(); |
|
|
|
|
|
|
|
var color = docColor ?? Color.FromRGBA(ImGui.GetColorU32(ImGuiCol.Text)); |
|
|
|
var color = docColor ?? Color.FromRGBA(ImGui.GetColorU32(ImGuiCol.Text)); |
|
|
|
if (isDisabled) color = color.WithAlpha(ImGui.GetStyle().DisabledAlpha); |
|
|
|
if (isDisabled) color = color.WithAlpha(ImGui.GetStyle().DisabledAlpha); |
|
|
|
|
|
|
|
|
|
|
|
var ctrl = ImGui.IsKeyDown(ImGuiKey.ModCtrl); |
|
|
|
// Gotta push the font to calculate size properly. |
|
|
|
|
|
|
|
// Can't just pass it to CalcTextSize for some reason. |
|
|
|
|
|
|
|
ImGui.PushFont(font); var size = ImGui.CalcTextSize(displayName); ImGui.PopFont(); |
|
|
|
|
|
|
|
if (isHeaderLike) size.X += ImGui.GetStyle().FramePadding.X * 2; |
|
|
|
|
|
|
|
if (spanAvailWidth) size.X = Math.Max(size.X, ImGui.GetContentRegionAvail().X); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isHeaderLike) { |
|
|
|
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); |
|
|
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Button, isSelected ? ImGui.GetColorU32(ImGuiCol.Header) |
|
|
|
|
|
|
|
: Color.Transparent.RGBA); |
|
|
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive , ImGui.GetColorU32(ImGuiCol.HeaderActive)); |
|
|
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, ImGui.GetColorU32(ImGuiCol.HeaderHovered)); |
|
|
|
|
|
|
|
ImGui.Button($"##{entity.Id}", size); |
|
|
|
|
|
|
|
ImGui.PopStyleColor(3); |
|
|
|
|
|
|
|
ImGui.PopStyleVar(); |
|
|
|
|
|
|
|
} else ImGui.InvisibleButton($"##{entity.Id}", size); |
|
|
|
|
|
|
|
|
|
|
|
var shift = ImGui.IsKeyDown(ImGuiKey.ModShift); |
|
|
|
var shift = ImGui.IsKeyDown(ImGuiKey.ModShift); |
|
|
|
if (ImGui.InvisibleButton(entity.Id.ToString(), size) && (ctrl || shift)) { |
|
|
|
var canClick = isHeaderLike || shift; |
|
|
|
|
|
|
|
if (canClick && ImGui.IsItemClicked()) { |
|
|
|
if (shift) window = NewEntityInspectorWindow(window.World); |
|
|
|
if (shift) window = NewEntityInspectorWindow(window.World); |
|
|
|
SetSelected(window, history, entity); |
|
|
|
// Don't re-select this entity if it's already selected. |
|
|
|
|
|
|
|
// Unless we're opening it in a new inspector window. |
|
|
|
|
|
|
|
// This is to make sure the history doesn't get polluted. |
|
|
|
|
|
|
|
if (!isSelected || shift) SetSelected(window, history, entity, false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var drawList = ImGui.GetWindowDrawList(); |
|
|
|
var drawList = ImGui.GetWindowDrawList(); |
|
|
|
|
|
|
|
if (isHeaderLike) pos.X += ImGui.GetStyle().FramePadding.X; |
|
|
|
drawList.AddText(font, ImGui.GetFontSize(), pos, color.RGBA, displayName); |
|
|
|
drawList.AddText(font, ImGui.GetFontSize(), pos, color.RGBA, displayName); |
|
|
|
// Draw an underscore (like a hyperlink) if hovered and Ctrl key is held. |
|
|
|
if (!isHeaderLike && canClick && ImGui.IsItemHovered()) { |
|
|
|
if (ImGui.IsItemHovered() && (ctrl || shift)) { |
|
|
|
// Draw a hyperlink-link underscore. |
|
|
|
pos.Y -= 1.75f; |
|
|
|
pos.Y -= 1.75f; |
|
|
|
drawList.AddLine(pos + new Vector2(0, size.Y), pos + size, color.RGBA); |
|
|
|
drawList.AddLine(pos + new Vector2(0, size.Y), pos + size, color.RGBA); |
|
|
|
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); |
|
|
|
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); |
|
|
@ -760,4 +753,12 @@ public class EntityInspector |
|
|
|
ImGui.EndTooltip(); |
|
|
|
ImGui.EndTooltip(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Flags] |
|
|
|
|
|
|
|
private enum RenderEntityFlags |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
SpanAvailWidth = 0b001, // Extend the bounding box to span available width. |
|
|
|
|
|
|
|
IsHeaderLike = 0b010, // Render the element like a header instead of a link. |
|
|
|
|
|
|
|
IsSelected = 0b100, // Render a background showing this as selected. |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|