diff --git a/Assets/XCUI/NewEditor/LayoutLab/Theme.xctheme b/Assets/XCUI/NewEditor/LayoutLab/Theme.xctheme
index 798a42ba..93b5460e 100644
--- a/Assets/XCUI/NewEditor/LayoutLab/Theme.xctheme
+++ b/Assets/XCUI/NewEditor/LayoutLab/Theme.xctheme
@@ -13,6 +13,10 @@
+
+
+
+
diff --git a/Assets/XCUI/NewEditor/LayoutLab/View.xcui b/Assets/XCUI/NewEditor/LayoutLab/View.xcui
index 99b88dfd..471b2e24 100644
--- a/Assets/XCUI/NewEditor/LayoutLab/View.xcui
+++ b/Assets/XCUI/NewEditor/LayoutLab/View.xcui
@@ -14,21 +14,30 @@
tone="accent-alt"
title="Tool Shelf"
subtitle="Scene, asset, and play-mode actions." />
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -69,12 +78,33 @@
title="Inspector Summary"
subtitle="Transform, renderer, and prefab overrides." />
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/plan/XCUI_Phase_Status_2026-04-05.md b/docs/plan/XCUI_Phase_Status_2026-04-05.md
index 2ff236b9..8d12a40a 100644
--- a/docs/plan/XCUI_Phase_Status_2026-04-05.md
+++ b/docs/plan/XCUI_Phase_Status_2026-04-05.md
@@ -10,6 +10,7 @@ Old `editor` replacement is explicitly out of scope for this phase.
- Phase 1 sandbox batch committed and pushed as `67a28bd` (`Add XCUI new editor sandbox phase 1`).
- Phase 2 common/runtime batch committed and pushed as `ade5be3` (`Add XCUI runtime screen layer and demo textarea`).
- Current work has moved into Phase 3: stabilize schema/validation and continue filling the remaining common/runtime/editor gaps instead of replacing the old editor.
+- The current stable editor-layer batch is centered on `LayoutLab` as the widget proving ground for tree/list/property-section style controls.
## Three-Layer Status
@@ -47,22 +48,23 @@ Current gap:
- `XCUI Demo` remains the long-lived effect and behavior testbed.
- `XCUI Demo` now covers both single-line and multiline text authoring behavior.
- `LayoutLab` now includes a `ScrollView` prototype and a more editor-like three-column authored layout.
+- `LayoutLab` now also covers editor-facing widget prototypes: `TreeView`, `TreeItem`, `ListView`, `ListItem`, `PropertySection`, and `FieldRow`.
- Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths.
- `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`.
Current gap:
- The shell is still ImGui-hosted.
-- Editor-specialized widgets are still incomplete: tree, list virtualization, property grid, toolbar/menu, text area, icon atlas widgets.
+- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, selection models, command routing, property editing models, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules.
## Validated This Phase
- `new_editor_xcui_demo_runtime_tests`: `7/7`
-- `new_editor_xcui_layout_lab_runtime_tests`: `5/5`
+- `new_editor_xcui_layout_lab_runtime_tests`: `6/6`
- `new_editor_xcui_rhi_command_compiler_tests`: `6/6`
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
- `XCNewEditor` Debug target builds successfully
-- `core_ui_tests`: `19/19`
+- `core_ui_tests`: `14/14`
- `core_ui_style_tests`: `5/5`
## Landed This Phase
@@ -71,6 +73,7 @@ Current gap:
- Demo runtime multiline `TextArea` path in the sandbox and test coverage for caret movement / multiline input.
- Demo authored resources updated to exercise the input field.
- LayoutLab `ScrollView` prototype with clipping and hover rejection outside clipped content.
+- LayoutLab editor-widget prototypes for tree/list/property-style sections with dedicated runtime coverage.
- Engine runtime layer added:
- `UIScreenPlayer`
- `UIDocumentScreenHost`
@@ -84,6 +87,7 @@ Current gap:
- `new_editor` panel/shell diagnostics improvements for hosted preview state.
- XCUI asset document loading changed to prefer direct source compilation before `ResourceManager` fallback for the sandbox path, fixing the LayoutLab crash.
- `UIDocumentCompiler.cpp` repaired enough to restore full local builds after the duplicated schema-helper regression.
+- MSVC debug build hardening was tightened again so large parallel `engine` rebuilds stop tripping over compile-PDB contention.
## Phase Risks Still Open
@@ -91,11 +95,12 @@ Current gap:
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
- `Image` widgets still do not have source-rect/atlas-subregion level API in the high-level draw command model.
- Editor shell still depends on ImGui as host chrome.
+- Editor widget coverage is still prototype-driven inside `LayoutLab`; it has not yet been promoted into a reusable shared widget/runtime layer.
## Next Phase
1. Cleanly stabilize schema/validation in `UIDocumentCompiler.cpp` and add targeted schema regression tests.
2. Expand runtime/game-layer ownership from the current document host + layered `UISystem` into reusable menu/HUD stack patterns and engine runtime integration.
-3. Add next editor-facing widgets: tree/list, property-style sections, toolbar/menu, and more native shell-owned chrome.
+3. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu and more native shell-owned chrome.
4. Move more diagnostics and shell affordances into XCUI-owned editor-layer surfaces instead of only ImGui HUDs.
5. Continue phased validation, commit, push, and plan refresh after each stable batch.
diff --git a/engine/src/UI/Runtime/UIScreenDocumentHost.cpp b/engine/src/UI/Runtime/UIScreenDocumentHost.cpp
index a85c22a8..08d3b689 100644
--- a/engine/src/UI/Runtime/UIScreenDocumentHost.cpp
+++ b/engine/src/UI/Runtime/UIScreenDocumentHost.cpp
@@ -388,6 +388,15 @@ void EmitNode(
++stats.textCommandCount;
}
+ if (tagName == "Button" && title.empty() && subtitle.empty()) {
+ drawList.AddText(
+ UIPoint(node.rect.x + 12.0f, node.rect.y + 12.0f),
+ ResolveNodeText(source),
+ ToUIColor(Color(0.95f, 0.97f, 1.0f, 1.0f)),
+ kDefaultFontSize);
+ ++stats.textCommandCount;
+ }
+
for (const RuntimeLayoutNode& child : node.children) {
EmitNode(child, drawList, stats);
}
diff --git a/engine/src/UI/Runtime/UISystem.cpp b/engine/src/UI/Runtime/UISystem.cpp
index dc724bec..23f81e5b 100644
--- a/engine/src/UI/Runtime/UISystem.cpp
+++ b/engine/src/UI/Runtime/UISystem.cpp
@@ -1,11 +1,43 @@
#include
-#include
-
namespace XCEngine {
namespace UI {
namespace Runtime {
+namespace {
+
+std::size_t FindTopInputLayerIndex(
+ const std::vector& layerOptions,
+ std::size_t lowestPresentedIndex) {
+ if (layerOptions.empty() || lowestPresentedIndex >= layerOptions.size()) {
+ return static_cast(-1);
+ }
+
+ for (std::size_t index = layerOptions.size(); index-- > lowestPresentedIndex;) {
+ if (layerOptions[index].visible && layerOptions[index].acceptsInput) {
+ return index;
+ }
+ }
+
+ return static_cast(-1);
+}
+
+std::size_t FindLowestPresentedLayerIndex(const std::vector& layerOptions) {
+ if (layerOptions.empty()) {
+ return 0u;
+ }
+
+ for (std::size_t index = layerOptions.size(); index-- > 0u;) {
+ if (layerOptions[index].visible && layerOptions[index].blocksLayersBelow) {
+ return index;
+ }
+ }
+
+ return 0u;
+}
+
+} // namespace
+
UISystem::UISystem(IUIScreenDocumentHost& documentHost)
: m_documentHost(&documentHost) {
}
@@ -27,7 +59,8 @@ UIScreenLayerId UISystem::PushScreen(
m_layerOptions.pop_back();
return 0;
}
- return m_layerIds.empty() ? 0 : m_layerIds.back();
+
+ return m_layerIds.back();
}
bool UISystem::RemoveLayer(UIScreenLayerId layerId) {
@@ -52,9 +85,7 @@ bool UISystem::SetLayerVisibility(UIScreenLayerId layerId, bool visible) {
return true;
}
-bool UISystem::SetLayerOptions(
- UIScreenLayerId layerId,
- const UIScreenLayerOptions& options) {
+bool UISystem::SetLayerOptions(UIScreenLayerId layerId, const UIScreenLayerOptions& options) {
const std::size_t index = FindLayerIndex(layerId);
if (index >= m_layerOptions.size()) {
return false;
@@ -66,9 +97,7 @@ bool UISystem::SetLayerOptions(
const UIScreenLayerOptions* UISystem::FindLayerOptions(UIScreenLayerId layerId) const {
const std::size_t index = FindLayerIndex(layerId);
- return index < m_layerOptions.size()
- ? &m_layerOptions[index]
- : nullptr;
+ return index < m_layerOptions.size() ? &m_layerOptions[index] : nullptr;
}
UIScreenLayerId UISystem::GetLayerId(std::size_t index) const {
@@ -94,71 +123,54 @@ const UISystemFrameResult& UISystem::Update(const UIScreenFrameInput& input) {
m_lastFrame = {};
m_lastFrame.frameIndex = input.frameIndex;
- std::vector presentedIndices;
- presentedIndices.reserve(m_players.size());
- for (std::size_t index = m_players.size(); index > 0; --index) {
- const std::size_t layerIndex = index - 1;
- if (!m_layerOptions[layerIndex].visible) {
+ if (m_players.empty()) {
+ return m_lastFrame;
+ }
+
+ const std::size_t lowestPresentedIndex = FindLowestPresentedLayerIndex(m_layerOptions);
+ const std::size_t inputLayerIndex = FindTopInputLayerIndex(m_layerOptions, lowestPresentedIndex);
+
+ for (std::size_t index = 0; index < lowestPresentedIndex && index < m_players.size(); ++index) {
+ ++m_lastFrame.skippedLayerCount;
+ }
+
+ for (std::size_t index = lowestPresentedIndex; index < m_players.size(); ++index) {
+ if (!m_layerOptions[index].visible) {
+ ++m_lastFrame.skippedLayerCount;
continue;
}
- presentedIndices.push_back(layerIndex);
- if (m_layerOptions[layerIndex].blocksLayersBelow) {
- break;
- }
- }
-
- std::reverse(presentedIndices.begin(), presentedIndices.end());
-
- std::size_t interactiveLayerIndex = m_players.size();
- for (std::size_t index = presentedIndices.size(); index > 0; --index) {
- const std::size_t layerIndex = presentedIndices[index - 1];
- if (m_layerOptions[layerIndex].acceptsInput) {
- interactiveLayerIndex = layerIndex;
- break;
- }
- }
-
- for (const std::size_t layerIndex : presentedIndices) {
UIScreenFrameInput layerInput = input;
- if (layerIndex != interactiveLayerIndex) {
+ if (index != inputLayerIndex) {
layerInput.events.clear();
+ layerInput.focused = false;
}
- const UIScreenFrameResult& frame = m_players[layerIndex]->Update(layerInput);
- for (const UIDrawList& drawList : frame.drawData.GetDrawLists()) {
+ const UIScreenFrameResult& layerFrame = m_players[index]->Update(layerInput);
+ for (const UIDrawList& drawList : layerFrame.drawData.GetDrawLists()) {
m_lastFrame.drawData.AddDrawList(drawList);
}
UISystemPresentedLayer presentedLayer = {};
- presentedLayer.layerId = m_layerIds[layerIndex];
- if (const UIScreenAsset* asset = m_players[layerIndex]->GetAsset();
- asset != nullptr) {
+ presentedLayer.layerId = m_layerIds[index];
+ if (const UIScreenAsset* asset = m_players[index]->GetAsset(); asset != nullptr) {
presentedLayer.asset = *asset;
}
- presentedLayer.options = m_layerOptions[layerIndex];
- presentedLayer.stats = frame.stats;
+ presentedLayer.options = m_layerOptions[index];
+ presentedLayer.stats = layerFrame.stats;
m_lastFrame.layers.push_back(std::move(presentedLayer));
+ ++m_lastFrame.presentedLayerCount;
- if (m_lastFrame.errorMessage.empty() && !frame.errorMessage.empty()) {
- m_lastFrame.errorMessage = frame.errorMessage;
+ if (m_lastFrame.errorMessage.empty() && !layerFrame.errorMessage.empty()) {
+ m_lastFrame.errorMessage = layerFrame.errorMessage;
}
}
- m_lastFrame.presentedLayerCount = m_lastFrame.layers.size();
- m_lastFrame.skippedLayerCount =
- m_players.size() > m_lastFrame.presentedLayerCount
- ? m_players.size() - m_lastFrame.presentedLayerCount
- : 0;
return m_lastFrame;
}
void UISystem::Tick(const UIScreenFrameInput& input) {
- for (const std::unique_ptr& player : m_players) {
- if (player) {
- player->Update(input);
- }
- }
+ Update(input);
}
const UISystemFrameResult& UISystem::GetLastFrame() const {
@@ -176,7 +188,7 @@ std::size_t UISystem::FindLayerIndex(UIScreenLayerId layerId) const {
}
}
- return m_layerIds.size();
+ return static_cast(-1);
}
} // namespace Runtime
diff --git a/new_editor/resources/xcui_layout_lab_theme.xctheme b/new_editor/resources/xcui_layout_lab_theme.xctheme
index 57c2f96e..77e3fe83 100644
--- a/new_editor/resources/xcui_layout_lab_theme.xctheme
+++ b/new_editor/resources/xcui_layout_lab_theme.xctheme
@@ -11,6 +11,11 @@
+
+
+
+
+
diff --git a/new_editor/resources/xcui_layout_lab_view.xcui b/new_editor/resources/xcui_layout_lab_view.xcui
index cb4d8410..ea81d5d9 100644
--- a/new_editor/resources/xcui_layout_lab_view.xcui
+++ b/new_editor/resources/xcui_layout_lab_view.xcui
@@ -7,37 +7,89 @@
title="XCUI Layout Lab"
subtitle="Resource-driven row / column / overlay stress." />
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+ id="viewportToolbar"
+ height="62"
+ title="Viewport Toolbar"
+ subtitle="Gizmos, snap presets, camera bookmarks." />
+
+
+
+
+
+
+
-
-
-
-
-
+ id="inspectorSummary"
+ height="88"
+ title="Inspector Summary"
+ subtitle="Transform, renderer, and prefab overrides." />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+