diff --git a/src/MrGameEng.UI/EcsInspector.cs b/src/MrGameEng.UI/EcsInspector.cs
index 68931d0..9b13ebc 100644
--- a/src/MrGameEng.UI/EcsInspector.cs
+++ b/src/MrGameEng.UI/EcsInspector.cs
@@ -42,7 +42,7 @@ public readonly record struct PerfSnapshot(
/// store's entities grouped by archetype, reflects a selected entity's components and fields,
/// writes simple field edits back, and (when a renderer is supplied) picks entities under the
/// cursor and reports frame metrics. Holds no Myra state, so its logic is unit-testable without a
-/// GPU. UI lazily rebuilds when changes.
+/// GPU. UI lazily rebuilds when or change.
///
public sealed class EcsInspector
{
@@ -70,8 +70,14 @@ public sealed class EcsInspector
/// True while the inspector panel is open.
public bool IsOpen { get; private set; }
- /// Increments on every change the UI should rebuild for (open/select/edit/refresh).
- public int Revision { get; private set; }
+ /// Increments when the entity/archetype lists or tab state need a UI rebuild.
+ public int ListRevision { get; private set; }
+
+ ///
+ /// Increments when the selected entity's field view needs a UI rebuild. Kept separate from
+ /// so the periodic refresh never recreates a field editor mid-edit.
+ ///
+ public int DetailRevision { get; private set; }
/// The active tab.
public InspectorTab ActiveTab { get; private set; }
@@ -93,21 +99,22 @@ public sealed class EcsInspector
{
IsOpen = !IsOpen;
_dirty = true;
- Bump();
+ BumpList();
+ BumpDetail();
}
/// Switches the active tab.
public void SetTab(InspectorTab tab)
{
ActiveTab = tab;
- Bump();
+ BumpList();
}
/// Arms (or disarms) world-pick mode.
public void ArmPick(bool armed = true)
{
PickArmed = armed;
- Bump();
+ BumpList();
}
/// Sets the archetype filter and refreshes the list.
@@ -115,7 +122,7 @@ public sealed class EcsInspector
{
Search = search ?? "";
_dirty = true;
- Bump();
+ BumpList();
}
/// Selects an archetype group by index; clears the entity selection.
@@ -123,14 +130,16 @@ public sealed class EcsInspector
{
SelectedArchetype = index;
SelectedEntityId = -1;
- Bump();
+ BumpList();
+ BumpDetail();
}
/// Selects an entity by runtime id.
public void SelectEntity(int entityId)
{
SelectedEntityId = entityId;
- Bump();
+ BumpList();
+ BumpDetail();
}
///
@@ -145,11 +154,24 @@ public sealed class EcsInspector
}
_refreshTimer += deltaSeconds;
- if (_refreshTimer >= 0.5f)
+ if (_refreshTimer < 0.5f)
+ {
+ return;
+ }
+
+ _refreshTimer = 0f;
+ // Only rebuild the lists when entities were actually added or removed — never on a plain
+ // tick, so a field editor in the details pane is not destroyed while it has focus.
+ var count = 0;
+ foreach (var _ in _store.Entities)
+ {
+ count++;
+ }
+
+ if (count != _entityCount)
{
- _refreshTimer = 0f;
_dirty = true;
- Bump();
+ BumpList();
}
}
@@ -231,7 +253,7 @@ public sealed class EcsInspector
// Adding an existing component type replaces it with the edited value.
AddComponentMethod.MakeGenericMethod(componentType).Invoke(entity, [boxed]);
- Bump();
+ BumpDetail();
return true;
}
@@ -281,7 +303,8 @@ public sealed class EcsInspector
{
SelectedEntityId = best;
PickArmed = false;
- Bump();
+ BumpList();
+ BumpDetail();
}
return best;
@@ -345,7 +368,9 @@ public sealed class EcsInspector
private static object BoxedValue(EntityComponent component) => component.Value;
#pragma warning restore CS0618
- private void Bump() => Revision++;
+ private void BumpList() => ListRevision++;
+
+ private void BumpDetail() => DetailRevision++;
private void RebuildIfNeeded()
{
diff --git a/src/MrGameEng.UI/EcsInspectorUi.cs b/src/MrGameEng.UI/EcsInspectorUi.cs
index 0b39769..df424bb 100644
--- a/src/MrGameEng.UI/EcsInspectorUi.cs
+++ b/src/MrGameEng.UI/EcsInspectorUi.cs
@@ -12,7 +12,7 @@ namespace MrGameEng.Inspector;
/// Entities/Performance tabs and arms world-pick; the Entities tab shows archetypes → entities →
/// the selected entity's components and editable fields; the Performance tab shows renderer
/// timings. A thin border tracks the selected entity in the world each frame. Rebuilds lazily on
-/// changes.
+/// / changes.
///
internal sealed class EcsInspectorUi
{
@@ -33,7 +33,8 @@ internal sealed class EcsInspectorUi
private readonly Panel _highlight;
private readonly VerticalStackPanel _panel;
- private int _lastRevision = -1;
+ private int _lastListRevision = -1;
+ private int _lastDetailRevision = -1;
public EcsInspectorUi(EcsInspector inspector, Renderer2D renderer)
{
@@ -91,10 +92,24 @@ internal sealed class EcsInspectorUi
return;
}
- if (_inspector.Revision != _lastRevision)
+ // Stats and perf are cheap text updates — refresh every frame so they stay live.
+ var perf = _inspector.Performance();
+ UpdateStats(perf);
+ if (_inspector.ActiveTab == InspectorTab.Performance)
{
- _lastRevision = _inspector.Revision;
- Rebuild();
+ UpdatePerf(perf);
+ }
+
+ if (_inspector.ListRevision != _lastListRevision)
+ {
+ _lastListRevision = _inspector.ListRevision;
+ RebuildLists();
+ }
+
+ if (_inspector.DetailRevision != _lastDetailRevision)
+ {
+ _lastDetailRevision = _inspector.DetailRevision;
+ RebuildDetails();
}
UpdateHighlight();
@@ -116,39 +131,41 @@ internal sealed class EcsInspectorUi
return section;
}
- private void Rebuild()
+ private void RebuildLists()
{
var entitiesTab = _inspector.ActiveTab == InspectorTab.Entities;
_entitiesSection.Visible = entitiesTab;
_perf.Visible = !entitiesTab;
_pickLabel.Text = _inspector.PickArmed ? "Pick*" : "Pick";
- var perf = _inspector.Performance();
- _stats.Text =
- $"entities {perf.Entities} | archetypes {perf.Archetypes} | {perf.Fps} FPS"
- + (_inspector.PickArmed ? " | click an entity…" : "");
-
if (entitiesTab)
{
RebuildArchetypes();
RebuildEntities();
- RebuildDetails();
- }
- else
- {
- _perf.Text =
- $"FPS {perf.Fps}\n"
- + $"submit {perf.SubmitMs:0.00} ms\n"
- + $"sort {perf.SortMs:0.00} ms\n"
- + $"build {perf.BuildMs:0.00} ms\n"
- + $"upload {perf.UploadMs:0.00} ms\n"
- + $"draw {perf.DrawMs:0.00} ms\n"
- + $"draw calls {perf.DrawCalls}\n"
- + $"sprites {perf.SubmittedSprites} drawn, {perf.CulledSprites} culled\n"
- + $"entities {perf.Entities}";
}
}
+ private void UpdateStats(in PerfSnapshot perf)
+ {
+ _stats.Text =
+ $"entities {perf.Entities} | archetypes {perf.Archetypes} | {perf.Fps} FPS"
+ + (_inspector.PickArmed ? " | click an entity…" : "");
+ }
+
+ private void UpdatePerf(in PerfSnapshot perf)
+ {
+ _perf.Text =
+ $"FPS {perf.Fps}\n"
+ + $"submit {perf.SubmitMs:0.00} ms\n"
+ + $"sort {perf.SortMs:0.00} ms\n"
+ + $"build {perf.BuildMs:0.00} ms\n"
+ + $"upload {perf.UploadMs:0.00} ms\n"
+ + $"draw {perf.DrawMs:0.00} ms\n"
+ + $"draw calls {perf.DrawCalls}\n"
+ + $"sprites {perf.SubmittedSprites} drawn, {perf.CulledSprites} culled\n"
+ + $"entities {perf.Entities}";
+ }
+
private void RebuildArchetypes()
{
_archetypeList.Widgets.Clear();
@@ -209,16 +226,31 @@ internal sealed class EcsInspectorUi
private Widget BuildFieldRow(Type componentType, FieldRow field)
{
- var row = new HorizontalStackPanel { Spacing = 6 };
- row.Widgets.Add(new Label { Text = field.Name, Width = 150 });
+ var row = new HorizontalStackPanel { Spacing = 8 };
+ row.Widgets.Add(
+ new Label
+ {
+ Text = field.Name,
+ Width = 140,
+ TextColor = new Color(170, 178, 190),
+ }
+ );
if (!field.Editable)
{
- row.Widgets.Add(new Label { Text = field.Value });
+ row.Widgets.Add(new Label { Text = field.Value, TextColor = new Color(150, 156, 168) });
return row;
}
- var editor = new TextBox { Text = field.Value, Width = 220 };
+ var editor = new TextBox
+ {
+ Text = field.Value,
+ Width = 150,
+ Padding = new Thickness(4, 2),
+ Background = new SolidBrush(new Color(28, 33, 42)),
+ Border = new SolidBrush(new Color(70, 82, 100)),
+ BorderThickness = new Thickness(1),
+ };
var entityId = _inspector.SelectedEntityId;
editor.KeyDown += (_, args) =>
{
@@ -230,7 +262,13 @@ internal sealed class EcsInspectorUi
row.Widgets.Add(editor);
if (field.Kind == FieldKind.Enum && field.EnumOptions is { Length: > 0 })
{
- row.Widgets.Add(new Label { Text = "(" + string.Join("/", field.EnumOptions) + ")" });
+ row.Widgets.Add(
+ new Label
+ {
+ Text = string.Join(" / ", field.EnumOptions),
+ TextColor = new Color(120, 128, 140),
+ }
+ );
}
return row;