Internalize shell command dispatch handling

This commit is contained in:
2026-04-07 11:44:16 +08:00
parent 864438c508
commit 35cd535b4c
4 changed files with 144 additions and 9 deletions

View File

@@ -63,6 +63,7 @@ using XCEngine::UI::Editor::UIEditorPanelRegistry;
using XCEngine::UI::Editor::UIEditorShellInteractionFrame;
using XCEngine::UI::Editor::UIEditorShellInteractionModel;
using XCEngine::UI::Editor::UIEditorShellInteractionResult;
using XCEngine::UI::Editor::UIEditorShellInteractionServices;
using XCEngine::UI::Editor::UIEditorShellInteractionState;
using XCEngine::UI::Editor::UIEditorViewportInputBridgeFrame;
using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind;
@@ -125,6 +126,7 @@ bool HasMeaningfulInteractionResult(const UIEditorShellInteractionResult& result
result.requestPointerCapture ||
result.releasePointerCapture ||
result.commandTriggered ||
result.commandDispatched ||
result.menuMutation.changed ||
result.workspaceResult.consumed ||
!result.menuId.empty() ||
@@ -781,10 +783,15 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
return;
}
if (result.commandDispatched) {
SetDispatchResult(result.commandDispatchResult);
return;
}
if (result.commandTriggered) {
const UIEditorCommandDispatchResult dispatchResult =
m_commandDispatcher.Dispatch(result.commandId, m_controller);
SetDispatchResult(dispatchResult);
m_lastStatus = "Triggered";
m_lastMessage = "命令已命中,但当前场景没有把 dispatcher 接到 root shell。";
m_lastColor = kWarning;
return;
}
@@ -866,24 +873,28 @@ void ScenarioApp::SetDispatchResult(const UIEditorCommandDispatchResult& result)
void ScenarioApp::RenderFrame() {
UpdateLayout();
m_cachedModel = BuildInteractionModel();
UIEditorShellInteractionServices services = {};
services.commandDispatcher = &m_commandDispatcher;
m_cachedFrame = UpdateUIEditorShellInteraction(
m_interactionState,
m_controller,
m_shellRect,
m_cachedModel,
m_pendingInputEvents);
m_pendingInputEvents,
services);
m_pendingInputEvents.clear();
ApplyHostCaptureRequests(m_cachedFrame.result);
SetInteractionResult(m_cachedFrame.result);
if (m_cachedFrame.result.commandTriggered) {
if (m_cachedFrame.result.commandDispatched) {
m_cachedModel = BuildInteractionModel();
m_cachedFrame = UpdateUIEditorShellInteraction(
m_interactionState,
m_controller,
m_shellRect,
m_cachedModel,
{});
{},
services);
}
const auto* viewportFrame =
@@ -905,7 +916,7 @@ void ScenarioApp::RenderFrame() {
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单时child popup 是否直接展开,不需要额外点击。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否能正确收起 popup chain。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时会屏蔽 workspace 输入菜单关闭后workspace 交互立即恢复。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证 root shell 会继续透传 viewport / splitter 的 capture 请求,不接旧 editor 业务", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证菜单命令会在 root shell 内直接 dispatch宿主不再二次派发", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 Filehover `Workspace Tools`,点预览外空白处,再点 `Document` 或拖 splitter。", kTextWeak, 11.0f);
DrawCard(drawList, m_controlsRect, "操作", "只保留这个场景必要的控制。");
@@ -926,6 +937,21 @@ void ScenarioApp::RenderFrame() {
addStateLine("Selected Presentation", selectedPresentation, kTextPrimary, 11.0f);
addStateLine("Active Panel", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kTextPrimary, 11.0f);
addStateLine("Focused", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
addStateLine("Menu Modal", FormatBool(m_cachedFrame.result.menuModal), m_cachedFrame.result.menuModal ? kSuccess : kTextMuted, 11.0f);
addStateLine(
"Workspace Suppressed",
FormatBool(m_cachedFrame.result.workspaceInputSuppressed),
m_cachedFrame.result.workspaceInputSuppressed ? kWarning : kTextMuted,
11.0f);
addStateLine(
"Command Dispatch",
m_cachedFrame.result.commandDispatched
? std::string(GetUIEditorCommandDispatchStatusName(m_cachedFrame.result.commandDispatchResult.status))
: std::string("(none)"),
m_cachedFrame.result.commandDispatched && m_cachedFrame.result.commandDispatchResult.commandExecuted
? kSuccess
: kTextMuted,
11.0f);
addStateLine("Result", m_lastStatus, m_lastColor);
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
stateY += 34.0f;

View File

@@ -19,6 +19,10 @@ using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
using XCEngine::UI::Editor::ResolveUIEditorShellInteractionRequest;
using XCEngine::UI::Editor::UIEditorCommandDispatchStatus;
using XCEngine::UI::Editor::UIEditorCommandDispatcher;
using XCEngine::UI::Editor::UIEditorCommandPanelSource;
using XCEngine::UI::Editor::UIEditorCommandRegistry;
using XCEngine::UI::Editor::UIEditorMenuItemKind;
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
using XCEngine::UI::Editor::UIEditorPanelRegistry;
@@ -29,8 +33,11 @@ using XCEngine::UI::Editor::UIEditorShellInteractionFrame;
using XCEngine::UI::Editor::UIEditorShellInteractionMenuButtonRequest;
using XCEngine::UI::Editor::UIEditorShellInteractionModel;
using XCEngine::UI::Editor::UIEditorShellInteractionPopupItemRequest;
using XCEngine::UI::Editor::UIEditorShellInteractionServices;
using XCEngine::UI::Editor::UIEditorShellInteractionState;
using XCEngine::UI::Editor::UIEditorWorkspaceController;
using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind;
using XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus;
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
@@ -66,6 +73,33 @@ UIEditorWorkspaceModel BuildWorkspace() {
return workspace;
}
UIEditorCommandRegistry BuildCommandRegistry() {
UIEditorCommandRegistry registry = {};
registry.commands = {
{
"workspace.show_inspector",
"Show Inspector",
{ UIEditorWorkspaceCommandKind::ShowPanel, UIEditorCommandPanelSource::FixedPanelId, "inspector" }
},
{
"workspace.focus_inspector",
"Focus Inspector",
{ UIEditorWorkspaceCommandKind::ActivatePanel, UIEditorCommandPanelSource::FixedPanelId, "inspector" }
},
{
"workspace.reset_layout",
"Reset Layout",
{ UIEditorWorkspaceCommandKind::ResetWorkspace, UIEditorCommandPanelSource::None, {} }
},
{
"workspace.close",
"Close",
{ UIEditorWorkspaceCommandKind::HidePanel, UIEditorCommandPanelSource::ActivePanel, {} }
}
};
return registry;
}
UIEditorResolvedMenuModel BuildResolvedMenuModel() {
UIEditorResolvedMenuModel model = {};
@@ -334,11 +368,63 @@ TEST(UIEditorShellInteractionTest, ClickCommandReturnsDispatchHookAndClosesMenu)
{ MakeLeftPointerDown(RectCenter(commandItem->rect)) });
EXPECT_TRUE(frame.result.commandTriggered);
EXPECT_FALSE(frame.result.commandDispatched);
EXPECT_EQ(frame.result.commandId, "workspace.close");
EXPECT_FALSE(state.menuSession.HasOpenMenu());
EXPECT_TRUE(frame.request.popupRequests.empty());
}
TEST(UIEditorShellInteractionTest, ClickCommandDispatchesInsideShellAndUpdatesWorkspaceSameFrame) {
auto controller = BuildController();
const auto model = BuildInteractionModel();
const UIEditorCommandDispatcher dispatcher(BuildCommandRegistry());
UIEditorShellInteractionServices services = {};
services.commandDispatcher = &dispatcher;
UIEditorShellInteractionState state = {};
auto frame = UpdateUIEditorShellInteraction(
state,
controller,
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
model,
{},
services);
const auto* windowButton = FindMenuButton(frame, "window");
ASSERT_NE(windowButton, nullptr);
frame = UpdateUIEditorShellInteraction(
state,
controller,
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
model,
{ MakeLeftPointerDown(RectCenter(windowButton->rect)) },
services);
const auto* commandItem = FindPopupItem(frame, "window-focus-inspector");
ASSERT_NE(commandItem, nullptr);
frame = UpdateUIEditorShellInteraction(
state,
controller,
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
model,
{ MakeLeftPointerDown(RectCenter(commandItem->rect)) },
services);
EXPECT_TRUE(frame.result.commandTriggered);
EXPECT_TRUE(frame.result.commandDispatched);
EXPECT_EQ(frame.result.commandId, "workspace.focus_inspector");
EXPECT_EQ(
frame.result.commandDispatchResult.status,
UIEditorCommandDispatchStatus::Dispatched);
EXPECT_TRUE(frame.result.commandDispatchResult.commandExecuted);
EXPECT_EQ(
frame.result.commandDispatchResult.commandResult.status,
UIEditorWorkspaceCommandStatus::Changed);
EXPECT_EQ(controller.GetWorkspace().activePanelId, "inspector");
EXPECT_FALSE(state.menuSession.HasOpenMenu());
EXPECT_TRUE(frame.request.popupRequests.empty());
}
TEST(UIEditorShellInteractionTest, PointerDownOutsideDismissesWholeMenuChain) {
auto controller = BuildController();
const auto model = BuildInteractionModel();