diff --git a/new_editor/include/XCEditor/Core/UIEditorShellInteraction.h b/new_editor/include/XCEditor/Core/UIEditorShellInteraction.h index b3a57615..33e97382 100644 --- a/new_editor/include/XCEditor/Core/UIEditorShellInteraction.h +++ b/new_editor/include/XCEditor/Core/UIEditorShellInteraction.h @@ -14,6 +14,12 @@ namespace XCEngine::UI::Editor { +struct UIEditorShellInteractionDefinition { + UIEditorMenuModel menuModel = {}; + std::vector statusSegments = {}; + std::vector workspacePresentations = {}; +}; + struct UIEditorShellInteractionModel { UIEditorResolvedMenuModel resolvedMenuModel = {}; std::vector statusSegments = {}; @@ -41,6 +47,7 @@ struct UIEditorShellInteractionPalette { struct UIEditorShellInteractionServices { const UIEditorCommandDispatcher* commandDispatcher = nullptr; + const UIEditorShortcutManager* shortcutManager = nullptr; }; struct UIEditorShellInteractionMenuButtonRequest { @@ -112,6 +119,7 @@ struct UIEditorShellInteractionPopupFrame { }; struct UIEditorShellInteractionFrame { + UIEditorShellInteractionModel model = {}; UIEditorShellInteractionRequest request = {}; UIEditorShellComposeFrame shellFrame = {}; UIEditorWorkspaceInteractionFrame workspaceInteractionFrame = {}; @@ -124,6 +132,11 @@ struct UIEditorShellInteractionFrame { bool focused = false; }; +UIEditorShellInteractionModel ResolveUIEditorShellInteractionModel( + const UIEditorWorkspaceController& controller, + const UIEditorShellInteractionDefinition& definition, + const UIEditorShellInteractionServices& services = {}); + UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest( const ::XCEngine::UI::UIRect& bounds, const UIEditorWorkspaceController& controller, @@ -141,6 +154,23 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( const UIEditorShellInteractionServices& services = {}, const UIEditorShellInteractionMetrics& metrics = {}); +UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest( + const ::XCEngine::UI::UIRect& bounds, + const UIEditorWorkspaceController& controller, + const UIEditorShellInteractionDefinition& definition, + const UIEditorShellInteractionState& state = {}, + const UIEditorShellInteractionMetrics& metrics = {}, + const UIEditorShellInteractionServices& services = {}); + +UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( + UIEditorShellInteractionState& state, + UIEditorWorkspaceController& controller, + const ::XCEngine::UI::UIRect& bounds, + const UIEditorShellInteractionDefinition& definition, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const UIEditorShellInteractionServices& services = {}, + const UIEditorShellInteractionMetrics& metrics = {}); + void AppendUIEditorShellInteraction( ::XCEngine::UI::UIDrawList& drawList, const UIEditorShellInteractionFrame& frame, @@ -149,4 +179,11 @@ void AppendUIEditorShellInteraction( const UIEditorShellInteractionPalette& palette = {}, const UIEditorShellInteractionMetrics& metrics = {}); +void AppendUIEditorShellInteraction( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorShellInteractionFrame& frame, + const UIEditorShellInteractionState& state, + const UIEditorShellInteractionPalette& palette = {}, + const UIEditorShellInteractionMetrics& metrics = {}); + } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Core/UIEditorShellInteraction.cpp b/new_editor/src/Core/UIEditorShellInteraction.cpp index e2dbc3cd..66bd15cf 100644 --- a/new_editor/src/Core/UIEditorShellInteraction.cpp +++ b/new_editor/src/Core/UIEditorShellInteraction.cpp @@ -232,6 +232,12 @@ bool HasMeaningfulInteractionResult( !result.commandId.empty(); } +bool ShouldRefreshResolvedShellModel( + const UIEditorShellInteractionResult& result) { + return result.commandDispatched || + result.workspaceResult.dockHostResult.commandExecuted; +} + BuildRequestOutput BuildRequest( const UIRect& bounds, const UIEditorWorkspaceController& controller, @@ -468,6 +474,23 @@ std::vector FilterWorkspaceInputEvents( } // namespace +UIEditorShellInteractionModel ResolveUIEditorShellInteractionModel( + const UIEditorWorkspaceController& controller, + const UIEditorShellInteractionDefinition& definition, + const UIEditorShellInteractionServices& services) { + UIEditorShellInteractionModel model = {}; + if (services.commandDispatcher != nullptr) { + model.resolvedMenuModel = BuildUIEditorResolvedMenuModel( + definition.menuModel, + *services.commandDispatcher, + controller, + services.shortcutManager); + } + model.statusSegments = definition.statusSegments; + model.workspacePresentations = definition.workspacePresentations; + return model; +} + UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest( const UIRect& bounds, const UIEditorWorkspaceController& controller, @@ -728,10 +751,67 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( interactionResult.workspaceResult.viewportInputFrame; interactionResult.consumed = interactionResult.consumed || interactionResult.workspaceResult.consumed; + frame.model = model; frame.result = std::move(interactionResult); return frame; } +UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest( + const UIRect& bounds, + const UIEditorWorkspaceController& controller, + const UIEditorShellInteractionDefinition& definition, + const UIEditorShellInteractionState& state, + const UIEditorShellInteractionMetrics& metrics, + const UIEditorShellInteractionServices& services) { + const UIEditorShellInteractionModel model = + ResolveUIEditorShellInteractionModel(controller, definition, services); + return ResolveUIEditorShellInteractionRequest( + bounds, + controller, + model, + state, + metrics, + services); +} + +UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( + UIEditorShellInteractionState& state, + UIEditorWorkspaceController& controller, + const UIRect& bounds, + const UIEditorShellInteractionDefinition& definition, + const std::vector& inputEvents, + const UIEditorShellInteractionServices& services, + const UIEditorShellInteractionMetrics& metrics) { + UIEditorShellInteractionModel model = + ResolveUIEditorShellInteractionModel(controller, definition, services); + UIEditorShellInteractionFrame frame = + UpdateUIEditorShellInteraction( + state, + controller, + bounds, + model, + inputEvents, + services, + metrics); + if (!ShouldRefreshResolvedShellModel(frame.result)) { + return frame; + } + + UIEditorShellInteractionModel refreshedModel = + ResolveUIEditorShellInteractionModel(controller, definition, services); + UIEditorShellInteractionFrame refreshedFrame = + UpdateUIEditorShellInteraction( + state, + controller, + bounds, + refreshedModel, + {}, + services, + metrics); + refreshedFrame.result = frame.result; + return refreshedFrame; +} + void AppendUIEditorShellInteraction( ::XCEngine::UI::UIDrawList& drawList, const UIEditorShellInteractionFrame& frame, @@ -773,4 +853,19 @@ void AppendUIEditorShellInteraction( } } +void AppendUIEditorShellInteraction( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorShellInteractionFrame& frame, + const UIEditorShellInteractionState& state, + const UIEditorShellInteractionPalette& palette, + const UIEditorShellInteractionMetrics& metrics) { + AppendUIEditorShellInteraction( + drawList, + frame, + frame.model, + state, + palette, + metrics); +} + } // namespace XCEngine::UI::Editor diff --git a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp index 9d9fd4b0..e3902291 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp +++ b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp @@ -41,7 +41,6 @@ using XCEngine::UI::UIPointerButton; using XCEngine::UI::UIRect; using XCEngine::UI::Editor::AppendUIEditorShellInteraction; using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController; -using XCEngine::UI::Editor::BuildUIEditorResolvedMenuModel; using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel; using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit; using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; @@ -60,8 +59,8 @@ using XCEngine::UI::Editor::UIEditorMenuItemKind; using XCEngine::UI::Editor::UIEditorMenuModel; using XCEngine::UI::Editor::UIEditorPanelPresentationKind; using XCEngine::UI::Editor::UIEditorPanelRegistry; +using XCEngine::UI::Editor::UIEditorShellInteractionDefinition; 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; @@ -395,7 +394,7 @@ private: void HandleLeftButtonDown(float x, float y); void HandleLeftButtonUp(float x, float y); void ExecuteAction(ActionId action); - UIEditorShellInteractionModel BuildInteractionModel() const; + UIEditorShellInteractionDefinition BuildInteractionDefinition() const; bool HasInteractiveCaptureState() const; void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result); void SetInteractionResult(const UIEditorShellInteractionResult& result); @@ -411,7 +410,6 @@ private: UIEditorCommandDispatcher m_commandDispatcher = {}; UIEditorMenuModel m_menuModel = {}; UIEditorShellInteractionState m_interactionState = {}; - UIEditorShellInteractionModel m_cachedModel = {}; UIEditorShellInteractionFrame m_cachedFrame = {}; std::vector m_pendingInputEvents = {}; std::vector m_buttons = {}; @@ -629,7 +627,6 @@ void ScenarioApp::ResetScenario() { m_commandDispatcher = UIEditorCommandDispatcher(BuildCommandRegistry()); m_menuModel = BuildMenuModel(); m_interactionState = {}; - m_cachedModel = {}; m_cachedFrame = {}; m_pendingInputEvents.clear(); m_lastStatus = "Ready"; @@ -734,14 +731,10 @@ void ScenarioApp::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& } } -UIEditorShellInteractionModel ScenarioApp::BuildInteractionModel() const { - UIEditorShellInteractionModel model = {}; - model.resolvedMenuModel = BuildUIEditorResolvedMenuModel( - m_menuModel, - m_commandDispatcher, - m_controller, - nullptr); - model.statusSegments = { +UIEditorShellInteractionDefinition ScenarioApp::BuildInteractionDefinition() const { + UIEditorShellInteractionDefinition definition = {}; + definition.menuModel = m_menuModel; + definition.statusSegments = { UIEditorStatusBarSegment{ "mode", "Shell Contract", @@ -774,8 +767,8 @@ UIEditorShellInteractionModel ScenarioApp::BuildInteractionModel() const { presentation.viewportShellModel.frame.hasTexture = false; presentation.viewportShellModel.frame.statusText = "这里只验证 Editor 根壳交互,不接旧 editor 业务面板。"; - model.workspacePresentations = { presentation }; - return model; + definition.workspacePresentations = { presentation }; + return definition; } void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& result) { @@ -872,31 +865,20 @@ void ScenarioApp::SetDispatchResult(const UIEditorCommandDispatchResult& result) void ScenarioApp::RenderFrame() { UpdateLayout(); - m_cachedModel = BuildInteractionModel(); + const UIEditorShellInteractionDefinition definition = BuildInteractionDefinition(); UIEditorShellInteractionServices services = {}; services.commandDispatcher = &m_commandDispatcher; m_cachedFrame = UpdateUIEditorShellInteraction( m_interactionState, m_controller, m_shellRect, - m_cachedModel, + definition, m_pendingInputEvents, services); m_pendingInputEvents.clear(); ApplyHostCaptureRequests(m_cachedFrame.result); SetInteractionResult(m_cachedFrame.result); - if (m_cachedFrame.result.commandDispatched) { - m_cachedModel = BuildInteractionModel(); - m_cachedFrame = UpdateUIEditorShellInteraction( - m_interactionState, - m_controller, - m_shellRect, - m_cachedModel, - {}, - services); - } - const auto* viewportFrame = FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.workspaceInteractionFrame.composeFrame, "scene"); const std::string selectedPresentation = @@ -968,7 +950,7 @@ void ScenarioApp::RenderFrame() { 11.0f); DrawCard(drawList, m_previewRect, "Preview", "真实 UIEditorShellInteraction 预览,不接旧 editor 业务。"); - AppendUIEditorShellInteraction(drawList, m_cachedFrame, m_cachedModel, m_interactionState); + AppendUIEditorShellInteraction(drawList, m_cachedFrame, m_interactionState); const bool framePresented = m_renderer.Render(drawData); m_autoScreenshot.CaptureIfRequested( diff --git a/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp index fef3285a..64071b49 100644 --- a/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp @@ -24,12 +24,17 @@ using XCEngine::UI::Editor::UIEditorCommandDispatchStatus; using XCEngine::UI::Editor::UIEditorCommandDispatcher; using XCEngine::UI::Editor::UIEditorCommandPanelSource; using XCEngine::UI::Editor::UIEditorCommandRegistry; +using XCEngine::UI::Editor::UIEditorMenuCheckedStateSource; using XCEngine::UI::Editor::UIEditorMenuItemKind; +using XCEngine::UI::Editor::UIEditorMenuDescriptor; +using XCEngine::UI::Editor::UIEditorMenuItemDescriptor; +using XCEngine::UI::Editor::UIEditorMenuModel; using XCEngine::UI::Editor::UIEditorPanelPresentationKind; using XCEngine::UI::Editor::UIEditorPanelRegistry; using XCEngine::UI::Editor::UIEditorResolvedMenuDescriptor; using XCEngine::UI::Editor::UIEditorResolvedMenuItem; using XCEngine::UI::Editor::UIEditorResolvedMenuModel; +using XCEngine::UI::Editor::UIEditorShellInteractionDefinition; using XCEngine::UI::Editor::UIEditorShellInteractionFrame; using XCEngine::UI::Editor::UIEditorShellInteractionMenuButtonRequest; using XCEngine::UI::Editor::UIEditorShellInteractionModel; @@ -185,6 +190,31 @@ UIEditorShellInteractionModel BuildInteractionModel() { return model; } +UIEditorShellInteractionDefinition BuildInteractionDefinition() { + UIEditorMenuItemDescriptor focusInspector = {}; + focusInspector.kind = UIEditorMenuItemKind::Command; + focusInspector.itemId = "window-focus-inspector"; + focusInspector.label = "Focus Inspector"; + focusInspector.commandId = "workspace.focus_inspector"; + focusInspector.checkedState = { + UIEditorMenuCheckedStateSource::PanelActive, + "inspector" + }; + + UIEditorMenuDescriptor windowMenu = {}; + windowMenu.menuId = "window"; + windowMenu.label = "Window"; + windowMenu.items = { focusInspector }; + + UIEditorShellInteractionDefinition definition = {}; + definition.menuModel.menus = { windowMenu }; + definition.statusSegments = { + { "mode", "Shell Contract", UIEditorStatusBarSlot::Leading, {}, true, true, 78.0f } + }; + definition.workspacePresentations = BuildInteractionModel().workspacePresentations; + return definition; +} + UIEditorWorkspaceController BuildController() { return BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); } @@ -426,6 +456,59 @@ TEST(UIEditorShellInteractionTest, ClickCommandDispatchesInsideShellAndUpdatesWo EXPECT_TRUE(frame.request.popupRequests.empty()); } +TEST(UIEditorShellInteractionTest, DefinitionContractRefreshesResolvedMenuAfterCommandDispatch) { + auto controller = BuildController(); + const UIEditorShellInteractionDefinition definition = BuildInteractionDefinition(); + 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), + definition, + {}, + services); + ASSERT_EQ(frame.model.resolvedMenuModel.menus.size(), 1u); + ASSERT_EQ(frame.model.resolvedMenuModel.menus.front().items.size(), 1u); + EXPECT_FALSE(frame.model.resolvedMenuModel.menus.front().items.front().checked); + + const auto request = ResolveUIEditorShellInteractionRequest( + UIRect(0.0f, 0.0f, 1280.0f, 720.0f), + controller, + definition, + state, + {}, + services); + ASSERT_EQ(request.menuButtons.size(), 1u); + + frame = UpdateUIEditorShellInteraction( + state, + controller, + UIRect(0.0f, 0.0f, 1280.0f, 720.0f), + definition, + { MakeLeftPointerDown(RectCenter(request.menuButtons.front().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), + definition, + { MakeLeftPointerDown(RectCenter(commandItem->rect)) }, + services); + + EXPECT_TRUE(frame.result.commandDispatched); + EXPECT_EQ(controller.GetWorkspace().activePanelId, "inspector"); + ASSERT_EQ(frame.model.resolvedMenuModel.menus.size(), 1u); + ASSERT_EQ(frame.model.resolvedMenuModel.menus.front().items.size(), 1u); + EXPECT_TRUE(frame.model.resolvedMenuModel.menus.front().items.front().checked); +} + TEST(UIEditorShellInteractionTest, PointerDownOutsideDismissesWholeMenuChain) { auto controller = BuildController(); const auto model = BuildInteractionModel();