Add XCUI runtime screen stack helper
This commit is contained in:
@@ -73,7 +73,7 @@ Current gap:
|
|||||||
- `new_editor_xcui_rhi_command_compiler_tests`: `6/6`
|
- `new_editor_xcui_rhi_command_compiler_tests`: `6/6`
|
||||||
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
|
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
|
||||||
- `XCNewEditor` Debug target builds successfully
|
- `XCNewEditor` Debug target builds successfully
|
||||||
- `core_ui_tests`: `14/14`
|
- `core_ui_tests`: `16/16`
|
||||||
- `core_ui_style_tests`: `5/5`
|
- `core_ui_style_tests`: `5/5`
|
||||||
- `ui_resource_tests`: `11/11`
|
- `ui_resource_tests`: `11/11`
|
||||||
- `editor_tests` targeted bridge smoke: `3/3`
|
- `editor_tests` targeted bridge smoke: `3/3`
|
||||||
@@ -102,8 +102,10 @@ Current gap:
|
|||||||
- Engine runtime layer added:
|
- Engine runtime layer added:
|
||||||
- `UIScreenPlayer`
|
- `UIScreenPlayer`
|
||||||
- `UIDocumentScreenHost`
|
- `UIDocumentScreenHost`
|
||||||
|
- `UIScreenStackController`
|
||||||
- `UISystem`
|
- `UISystem`
|
||||||
- layered screen composition and modal blocking semantics
|
- layered screen composition and modal blocking semantics
|
||||||
|
- Runtime/game integration scaffolding now includes reusable `HUD/menu/modal` stack helpers on top of `UISystem`.
|
||||||
- Runtime document-host draw emission now preserves button labels for shared screen rendering.
|
- Runtime document-host draw emission now preserves button labels for shared screen rendering.
|
||||||
- RHI image path improvements:
|
- RHI image path improvements:
|
||||||
- clipped image UV adjustment
|
- clipped image UV adjustment
|
||||||
|
|||||||
@@ -522,6 +522,7 @@ add_library(XCEngine STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UISystem.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UISystem.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Runtime/UIScreenDocumentHost.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Runtime/UIScreenDocumentHost.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Runtime/UIScreenPlayer.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Runtime/UIScreenPlayer.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Runtime/UIScreenStackController.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Runtime/UISystem.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Runtime/UISystem.cpp
|
||||||
|
|
||||||
# Input
|
# Input
|
||||||
@@ -587,6 +588,9 @@ target_link_libraries(XCEngine PUBLIC
|
|||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
|
target_compile_options(XCEngine PRIVATE
|
||||||
|
/FS
|
||||||
|
$<$<CONFIG:Debug,RelWithDebInfo>:/Z7>)
|
||||||
target_link_libraries(XCEngine PUBLIC delayimp)
|
target_link_libraries(XCEngine PUBLIC delayimp)
|
||||||
target_link_options(XCEngine INTERFACE "/DELAYLOAD:assimp-vc143-mt.dll")
|
target_link_options(XCEngine INTERFACE "/DELAYLOAD:assimp-vc143-mt.dll")
|
||||||
set_target_properties(XCEngine PROPERTIES
|
set_target_properties(XCEngine PROPERTIES
|
||||||
|
|||||||
54
engine/include/XCEngine/UI/Runtime/UIScreenStackController.h
Normal file
54
engine/include/XCEngine/UI/Runtime/UIScreenStackController.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/UI/Runtime/UISystem.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace UI {
|
||||||
|
namespace Runtime {
|
||||||
|
|
||||||
|
struct UIScreenStackEntry {
|
||||||
|
UIScreenLayerId layerId = 0;
|
||||||
|
UIScreenAsset asset = {};
|
||||||
|
UIScreenLayerOptions options = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class UIScreenStackController {
|
||||||
|
public:
|
||||||
|
explicit UIScreenStackController(UISystem& system);
|
||||||
|
|
||||||
|
UIScreenLayerId PushScreen(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const UIScreenLayerOptions& options = UIScreenLayerOptions());
|
||||||
|
UIScreenLayerId PushMenu(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const std::string& debugName = {});
|
||||||
|
UIScreenLayerId PushModal(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const std::string& debugName = {});
|
||||||
|
UIScreenLayerId PushHud(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const std::string& debugName = {});
|
||||||
|
|
||||||
|
bool ReplaceTop(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const UIScreenLayerOptions& options = UIScreenLayerOptions());
|
||||||
|
bool Pop();
|
||||||
|
bool Remove(UIScreenLayerId layerId);
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
std::size_t GetEntryCount() const;
|
||||||
|
const UIScreenStackEntry* GetTop() const;
|
||||||
|
const std::vector<UIScreenStackEntry>& GetEntries() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UISystem* m_system = nullptr;
|
||||||
|
std::vector<UIScreenStackEntry> m_entries = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Runtime
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace XCEngine
|
||||||
148
engine/src/UI/Runtime/UIScreenStackController.cpp
Normal file
148
engine/src/UI/Runtime/UIScreenStackController.cpp
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#include <XCEngine/UI/Runtime/UIScreenStackController.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace UI {
|
||||||
|
namespace Runtime {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string ResolveDebugName(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const std::string& fallbackName,
|
||||||
|
const std::string& explicitName) {
|
||||||
|
if (!explicitName.empty()) {
|
||||||
|
return explicitName;
|
||||||
|
}
|
||||||
|
if (!asset.screenId.empty()) {
|
||||||
|
return asset.screenId;
|
||||||
|
}
|
||||||
|
return fallbackName;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
UIScreenStackController::UIScreenStackController(UISystem& system)
|
||||||
|
: m_system(&system) {
|
||||||
|
}
|
||||||
|
|
||||||
|
UIScreenLayerId UIScreenStackController::PushScreen(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const UIScreenLayerOptions& options) {
|
||||||
|
const UIScreenLayerId layerId = m_system != nullptr
|
||||||
|
? m_system->PushScreen(asset, options)
|
||||||
|
: 0;
|
||||||
|
if (layerId == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIScreenStackEntry entry = {};
|
||||||
|
entry.layerId = layerId;
|
||||||
|
entry.asset = asset;
|
||||||
|
entry.options = options;
|
||||||
|
m_entries.push_back(std::move(entry));
|
||||||
|
return layerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIScreenLayerId UIScreenStackController::PushMenu(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const std::string& debugName) {
|
||||||
|
UIScreenLayerOptions options = {};
|
||||||
|
options.debugName = ResolveDebugName(asset, "Menu", debugName);
|
||||||
|
options.acceptsInput = true;
|
||||||
|
options.blocksLayersBelow = true;
|
||||||
|
return PushScreen(asset, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
UIScreenLayerId UIScreenStackController::PushModal(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const std::string& debugName) {
|
||||||
|
UIScreenLayerOptions options = {};
|
||||||
|
options.debugName = ResolveDebugName(asset, "Modal", debugName);
|
||||||
|
options.acceptsInput = true;
|
||||||
|
options.blocksLayersBelow = true;
|
||||||
|
return PushScreen(asset, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
UIScreenLayerId UIScreenStackController::PushHud(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const std::string& debugName) {
|
||||||
|
UIScreenLayerOptions options = {};
|
||||||
|
options.debugName = ResolveDebugName(asset, "HUD", debugName);
|
||||||
|
options.acceptsInput = false;
|
||||||
|
options.blocksLayersBelow = false;
|
||||||
|
return PushScreen(asset, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIScreenStackController::ReplaceTop(
|
||||||
|
const UIScreenAsset& asset,
|
||||||
|
const UIScreenLayerOptions& options) {
|
||||||
|
if (m_entries.empty()) {
|
||||||
|
return PushScreen(asset, options) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Pop()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PushScreen(asset, options) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIScreenStackController::Pop() {
|
||||||
|
if (m_system == nullptr || m_entries.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UIScreenLayerId layerId = m_entries.back().layerId;
|
||||||
|
if (!m_system->RemoveLayer(layerId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.pop_back();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIScreenStackController::Remove(UIScreenLayerId layerId) {
|
||||||
|
if (m_system == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < m_entries.size(); ++index) {
|
||||||
|
if (m_entries[index].layerId != layerId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_system->RemoveLayer(layerId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.erase(m_entries.begin() + static_cast<std::ptrdiff_t>(index));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIScreenStackController::Clear() {
|
||||||
|
if (m_system != nullptr) {
|
||||||
|
m_system->DestroyAllPlayers();
|
||||||
|
}
|
||||||
|
m_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t UIScreenStackController::GetEntryCount() const {
|
||||||
|
return m_entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const UIScreenStackEntry* UIScreenStackController::GetTop() const {
|
||||||
|
return m_entries.empty() ? nullptr : &m_entries.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<UIScreenStackEntry>& UIScreenStackController::GetEntries() const {
|
||||||
|
return m_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Runtime
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
|
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
|
||||||
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
|
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
|
||||||
|
#include <XCEngine/UI/Runtime/UIScreenStackController.h>
|
||||||
#include <XCEngine/UI/Runtime/UISystem.h>
|
#include <XCEngine/UI/Runtime/UISystem.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -15,6 +16,7 @@ using XCEngine::UI::Runtime::UIScreenAsset;
|
|||||||
using XCEngine::UI::Runtime::UIScreenFrameInput;
|
using XCEngine::UI::Runtime::UIScreenFrameInput;
|
||||||
using XCEngine::UI::Runtime::UIScreenPlayer;
|
using XCEngine::UI::Runtime::UIScreenPlayer;
|
||||||
using XCEngine::UI::Runtime::UIDocumentScreenHost;
|
using XCEngine::UI::Runtime::UIDocumentScreenHost;
|
||||||
|
using XCEngine::UI::Runtime::UIScreenStackController;
|
||||||
using XCEngine::UI::Runtime::UISystem;
|
using XCEngine::UI::Runtime::UISystem;
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@@ -141,3 +143,65 @@ TEST(UIRuntimeTest, UISystemForwardsActiveScreenToPlayer) {
|
|||||||
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Modal Dialog"));
|
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Modal Dialog"));
|
||||||
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Base Screen"));
|
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Base Screen"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(UIRuntimeTest, ScreenStackControllerAppliesHudAndMenuLayerPolicies) {
|
||||||
|
TempFileScope hudView("xcui_runtime_hud", ".xcui", BuildViewMarkup("HUD Screen"));
|
||||||
|
TempFileScope menuView("xcui_runtime_menu", ".xcui", BuildViewMarkup("Pause Menu", "Paused"));
|
||||||
|
|
||||||
|
UIDocumentScreenHost host = {};
|
||||||
|
UISystem system(host);
|
||||||
|
UIScreenStackController stack(system);
|
||||||
|
|
||||||
|
const auto hudLayer = stack.PushHud(BuildScreenAsset(hudView.Path(), "runtime.hud"), "hud");
|
||||||
|
const auto menuLayer = stack.PushMenu(BuildScreenAsset(menuView.Path(), "runtime.menu"), "menu");
|
||||||
|
ASSERT_NE(hudLayer, 0u);
|
||||||
|
ASSERT_NE(menuLayer, 0u);
|
||||||
|
|
||||||
|
ASSERT_EQ(stack.GetEntryCount(), 2u);
|
||||||
|
ASSERT_NE(stack.GetTop(), nullptr);
|
||||||
|
EXPECT_EQ(stack.GetTop()->layerId, menuLayer);
|
||||||
|
|
||||||
|
const auto* hudOptions = system.FindLayerOptions(hudLayer);
|
||||||
|
const auto* menuOptions = system.FindLayerOptions(menuLayer);
|
||||||
|
ASSERT_NE(hudOptions, nullptr);
|
||||||
|
ASSERT_NE(menuOptions, nullptr);
|
||||||
|
EXPECT_FALSE(hudOptions->acceptsInput);
|
||||||
|
EXPECT_FALSE(hudOptions->blocksLayersBelow);
|
||||||
|
EXPECT_TRUE(menuOptions->acceptsInput);
|
||||||
|
EXPECT_TRUE(menuOptions->blocksLayersBelow);
|
||||||
|
|
||||||
|
const auto& frame = system.Update(BuildInputState(4u));
|
||||||
|
EXPECT_EQ(frame.presentedLayerCount, 1u);
|
||||||
|
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Pause Menu"));
|
||||||
|
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "HUD Screen"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIRuntimeTest, ScreenStackControllerReplaceTopSwapsMenuContent) {
|
||||||
|
TempFileScope pauseView("xcui_runtime_pause", ".xcui", BuildViewMarkup("Pause Menu"));
|
||||||
|
TempFileScope settingsView("xcui_runtime_settings", ".xcui", BuildViewMarkup("Settings Menu"));
|
||||||
|
|
||||||
|
UIDocumentScreenHost host = {};
|
||||||
|
UISystem system(host);
|
||||||
|
UIScreenStackController stack(system);
|
||||||
|
|
||||||
|
const auto pauseLayer = stack.PushMenu(BuildScreenAsset(pauseView.Path(), "runtime.pause"), "pause");
|
||||||
|
ASSERT_NE(pauseLayer, 0u);
|
||||||
|
|
||||||
|
XCEngine::UI::Runtime::UIScreenLayerOptions replacementOptions = {};
|
||||||
|
replacementOptions.debugName = "settings";
|
||||||
|
replacementOptions.acceptsInput = true;
|
||||||
|
replacementOptions.blocksLayersBelow = true;
|
||||||
|
ASSERT_TRUE(stack.ReplaceTop(
|
||||||
|
BuildScreenAsset(settingsView.Path(), "runtime.settings"),
|
||||||
|
replacementOptions));
|
||||||
|
|
||||||
|
ASSERT_EQ(stack.GetEntryCount(), 1u);
|
||||||
|
ASSERT_NE(stack.GetTop(), nullptr);
|
||||||
|
EXPECT_EQ(stack.GetTop()->asset.screenId, "runtime.settings");
|
||||||
|
EXPECT_NE(stack.GetTop()->layerId, pauseLayer);
|
||||||
|
|
||||||
|
const auto& frame = system.Update(BuildInputState(5u));
|
||||||
|
EXPECT_EQ(frame.presentedLayerCount, 1u);
|
||||||
|
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Settings Menu"));
|
||||||
|
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Pause Menu"));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user