Internalize shell command dispatch handling
This commit is contained in:
@@ -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), "建议操作:点击 File,hover `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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user