Add shell definition contract

This commit is contained in:
2026-04-07 12:09:26 +08:00
parent 3def94d0e0
commit fb8ef25ff3
4 changed files with 226 additions and 29 deletions

View File

@@ -14,6 +14,12 @@
namespace XCEngine::UI::Editor { namespace XCEngine::UI::Editor {
struct UIEditorShellInteractionDefinition {
UIEditorMenuModel menuModel = {};
std::vector<Widgets::UIEditorStatusBarSegment> statusSegments = {};
std::vector<UIEditorWorkspacePanelPresentationModel> workspacePresentations = {};
};
struct UIEditorShellInteractionModel { struct UIEditorShellInteractionModel {
UIEditorResolvedMenuModel resolvedMenuModel = {}; UIEditorResolvedMenuModel resolvedMenuModel = {};
std::vector<Widgets::UIEditorStatusBarSegment> statusSegments = {}; std::vector<Widgets::UIEditorStatusBarSegment> statusSegments = {};
@@ -41,6 +47,7 @@ struct UIEditorShellInteractionPalette {
struct UIEditorShellInteractionServices { struct UIEditorShellInteractionServices {
const UIEditorCommandDispatcher* commandDispatcher = nullptr; const UIEditorCommandDispatcher* commandDispatcher = nullptr;
const UIEditorShortcutManager* shortcutManager = nullptr;
}; };
struct UIEditorShellInteractionMenuButtonRequest { struct UIEditorShellInteractionMenuButtonRequest {
@@ -112,6 +119,7 @@ struct UIEditorShellInteractionPopupFrame {
}; };
struct UIEditorShellInteractionFrame { struct UIEditorShellInteractionFrame {
UIEditorShellInteractionModel model = {};
UIEditorShellInteractionRequest request = {}; UIEditorShellInteractionRequest request = {};
UIEditorShellComposeFrame shellFrame = {}; UIEditorShellComposeFrame shellFrame = {};
UIEditorWorkspaceInteractionFrame workspaceInteractionFrame = {}; UIEditorWorkspaceInteractionFrame workspaceInteractionFrame = {};
@@ -124,6 +132,11 @@ struct UIEditorShellInteractionFrame {
bool focused = false; bool focused = false;
}; };
UIEditorShellInteractionModel ResolveUIEditorShellInteractionModel(
const UIEditorWorkspaceController& controller,
const UIEditorShellInteractionDefinition& definition,
const UIEditorShellInteractionServices& services = {});
UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest( UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
const ::XCEngine::UI::UIRect& bounds, const ::XCEngine::UI::UIRect& bounds,
const UIEditorWorkspaceController& controller, const UIEditorWorkspaceController& controller,
@@ -141,6 +154,23 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
const UIEditorShellInteractionServices& services = {}, const UIEditorShellInteractionServices& services = {},
const UIEditorShellInteractionMetrics& metrics = {}); 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( void AppendUIEditorShellInteraction(
::XCEngine::UI::UIDrawList& drawList, ::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellInteractionFrame& frame, const UIEditorShellInteractionFrame& frame,
@@ -149,4 +179,11 @@ void AppendUIEditorShellInteraction(
const UIEditorShellInteractionPalette& palette = {}, const UIEditorShellInteractionPalette& palette = {},
const UIEditorShellInteractionMetrics& metrics = {}); 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 } // namespace XCEngine::UI::Editor

View File

@@ -232,6 +232,12 @@ bool HasMeaningfulInteractionResult(
!result.commandId.empty(); !result.commandId.empty();
} }
bool ShouldRefreshResolvedShellModel(
const UIEditorShellInteractionResult& result) {
return result.commandDispatched ||
result.workspaceResult.dockHostResult.commandExecuted;
}
BuildRequestOutput BuildRequest( BuildRequestOutput BuildRequest(
const UIRect& bounds, const UIRect& bounds,
const UIEditorWorkspaceController& controller, const UIEditorWorkspaceController& controller,
@@ -468,6 +474,23 @@ std::vector<UIInputEvent> FilterWorkspaceInputEvents(
} // namespace } // 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( UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
const UIRect& bounds, const UIRect& bounds,
const UIEditorWorkspaceController& controller, const UIEditorWorkspaceController& controller,
@@ -728,10 +751,67 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
interactionResult.workspaceResult.viewportInputFrame; interactionResult.workspaceResult.viewportInputFrame;
interactionResult.consumed = interactionResult.consumed =
interactionResult.consumed || interactionResult.workspaceResult.consumed; interactionResult.consumed || interactionResult.workspaceResult.consumed;
frame.model = model;
frame.result = std::move(interactionResult); frame.result = std::move(interactionResult);
return frame; 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<UIInputEvent>& 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( void AppendUIEditorShellInteraction(
::XCEngine::UI::UIDrawList& drawList, ::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellInteractionFrame& frame, 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 } // namespace XCEngine::UI::Editor

View File

@@ -41,7 +41,6 @@ using XCEngine::UI::UIPointerButton;
using XCEngine::UI::UIRect; using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::AppendUIEditorShellInteraction; using XCEngine::UI::Editor::AppendUIEditorShellInteraction;
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController; using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController;
using XCEngine::UI::Editor::BuildUIEditorResolvedMenuModel;
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel; using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit; using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
@@ -60,8 +59,8 @@ using XCEngine::UI::Editor::UIEditorMenuItemKind;
using XCEngine::UI::Editor::UIEditorMenuModel; using XCEngine::UI::Editor::UIEditorMenuModel;
using XCEngine::UI::Editor::UIEditorPanelPresentationKind; using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
using XCEngine::UI::Editor::UIEditorPanelRegistry; using XCEngine::UI::Editor::UIEditorPanelRegistry;
using XCEngine::UI::Editor::UIEditorShellInteractionDefinition;
using XCEngine::UI::Editor::UIEditorShellInteractionFrame; using XCEngine::UI::Editor::UIEditorShellInteractionFrame;
using XCEngine::UI::Editor::UIEditorShellInteractionModel;
using XCEngine::UI::Editor::UIEditorShellInteractionResult; using XCEngine::UI::Editor::UIEditorShellInteractionResult;
using XCEngine::UI::Editor::UIEditorShellInteractionServices; using XCEngine::UI::Editor::UIEditorShellInteractionServices;
using XCEngine::UI::Editor::UIEditorShellInteractionState; using XCEngine::UI::Editor::UIEditorShellInteractionState;
@@ -395,7 +394,7 @@ private:
void HandleLeftButtonDown(float x, float y); void HandleLeftButtonDown(float x, float y);
void HandleLeftButtonUp(float x, float y); void HandleLeftButtonUp(float x, float y);
void ExecuteAction(ActionId action); void ExecuteAction(ActionId action);
UIEditorShellInteractionModel BuildInteractionModel() const; UIEditorShellInteractionDefinition BuildInteractionDefinition() const;
bool HasInteractiveCaptureState() const; bool HasInteractiveCaptureState() const;
void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result); void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result);
void SetInteractionResult(const UIEditorShellInteractionResult& result); void SetInteractionResult(const UIEditorShellInteractionResult& result);
@@ -411,7 +410,6 @@ private:
UIEditorCommandDispatcher m_commandDispatcher = {}; UIEditorCommandDispatcher m_commandDispatcher = {};
UIEditorMenuModel m_menuModel = {}; UIEditorMenuModel m_menuModel = {};
UIEditorShellInteractionState m_interactionState = {}; UIEditorShellInteractionState m_interactionState = {};
UIEditorShellInteractionModel m_cachedModel = {};
UIEditorShellInteractionFrame m_cachedFrame = {}; UIEditorShellInteractionFrame m_cachedFrame = {};
std::vector<UIInputEvent> m_pendingInputEvents = {}; std::vector<UIInputEvent> m_pendingInputEvents = {};
std::vector<ButtonState> m_buttons = {}; std::vector<ButtonState> m_buttons = {};
@@ -629,7 +627,6 @@ void ScenarioApp::ResetScenario() {
m_commandDispatcher = UIEditorCommandDispatcher(BuildCommandRegistry()); m_commandDispatcher = UIEditorCommandDispatcher(BuildCommandRegistry());
m_menuModel = BuildMenuModel(); m_menuModel = BuildMenuModel();
m_interactionState = {}; m_interactionState = {};
m_cachedModel = {};
m_cachedFrame = {}; m_cachedFrame = {};
m_pendingInputEvents.clear(); m_pendingInputEvents.clear();
m_lastStatus = "Ready"; m_lastStatus = "Ready";
@@ -734,14 +731,10 @@ void ScenarioApp::ApplyHostCaptureRequests(const UIEditorShellInteractionResult&
} }
} }
UIEditorShellInteractionModel ScenarioApp::BuildInteractionModel() const { UIEditorShellInteractionDefinition ScenarioApp::BuildInteractionDefinition() const {
UIEditorShellInteractionModel model = {}; UIEditorShellInteractionDefinition definition = {};
model.resolvedMenuModel = BuildUIEditorResolvedMenuModel( definition.menuModel = m_menuModel;
m_menuModel, definition.statusSegments = {
m_commandDispatcher,
m_controller,
nullptr);
model.statusSegments = {
UIEditorStatusBarSegment{ UIEditorStatusBarSegment{
"mode", "mode",
"Shell Contract", "Shell Contract",
@@ -774,8 +767,8 @@ UIEditorShellInteractionModel ScenarioApp::BuildInteractionModel() const {
presentation.viewportShellModel.frame.hasTexture = false; presentation.viewportShellModel.frame.hasTexture = false;
presentation.viewportShellModel.frame.statusText = presentation.viewportShellModel.frame.statusText =
"这里只验证 Editor 根壳交互,不接旧 editor 业务面板。"; "这里只验证 Editor 根壳交互,不接旧 editor 业务面板。";
model.workspacePresentations = { presentation }; definition.workspacePresentations = { presentation };
return model; return definition;
} }
void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& result) { void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& result) {
@@ -872,31 +865,20 @@ void ScenarioApp::SetDispatchResult(const UIEditorCommandDispatchResult& result)
void ScenarioApp::RenderFrame() { void ScenarioApp::RenderFrame() {
UpdateLayout(); UpdateLayout();
m_cachedModel = BuildInteractionModel(); const UIEditorShellInteractionDefinition definition = BuildInteractionDefinition();
UIEditorShellInteractionServices services = {}; UIEditorShellInteractionServices services = {};
services.commandDispatcher = &m_commandDispatcher; services.commandDispatcher = &m_commandDispatcher;
m_cachedFrame = UpdateUIEditorShellInteraction( m_cachedFrame = UpdateUIEditorShellInteraction(
m_interactionState, m_interactionState,
m_controller, m_controller,
m_shellRect, m_shellRect,
m_cachedModel, definition,
m_pendingInputEvents, m_pendingInputEvents,
services); services);
m_pendingInputEvents.clear(); m_pendingInputEvents.clear();
ApplyHostCaptureRequests(m_cachedFrame.result); ApplyHostCaptureRequests(m_cachedFrame.result);
SetInteractionResult(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 = const auto* viewportFrame =
FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.workspaceInteractionFrame.composeFrame, "scene"); FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.workspaceInteractionFrame.composeFrame, "scene");
const std::string selectedPresentation = const std::string selectedPresentation =
@@ -968,7 +950,7 @@ void ScenarioApp::RenderFrame() {
11.0f); 11.0f);
DrawCard(drawList, m_previewRect, "Preview", "真实 UIEditorShellInteraction 预览,不接旧 editor 业务。"); 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); const bool framePresented = m_renderer.Render(drawData);
m_autoScreenshot.CaptureIfRequested( m_autoScreenshot.CaptureIfRequested(

View File

@@ -24,12 +24,17 @@ using XCEngine::UI::Editor::UIEditorCommandDispatchStatus;
using XCEngine::UI::Editor::UIEditorCommandDispatcher; using XCEngine::UI::Editor::UIEditorCommandDispatcher;
using XCEngine::UI::Editor::UIEditorCommandPanelSource; using XCEngine::UI::Editor::UIEditorCommandPanelSource;
using XCEngine::UI::Editor::UIEditorCommandRegistry; using XCEngine::UI::Editor::UIEditorCommandRegistry;
using XCEngine::UI::Editor::UIEditorMenuCheckedStateSource;
using XCEngine::UI::Editor::UIEditorMenuItemKind; 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::UIEditorPanelPresentationKind;
using XCEngine::UI::Editor::UIEditorPanelRegistry; using XCEngine::UI::Editor::UIEditorPanelRegistry;
using XCEngine::UI::Editor::UIEditorResolvedMenuDescriptor; using XCEngine::UI::Editor::UIEditorResolvedMenuDescriptor;
using XCEngine::UI::Editor::UIEditorResolvedMenuItem; using XCEngine::UI::Editor::UIEditorResolvedMenuItem;
using XCEngine::UI::Editor::UIEditorResolvedMenuModel; using XCEngine::UI::Editor::UIEditorResolvedMenuModel;
using XCEngine::UI::Editor::UIEditorShellInteractionDefinition;
using XCEngine::UI::Editor::UIEditorShellInteractionFrame; using XCEngine::UI::Editor::UIEditorShellInteractionFrame;
using XCEngine::UI::Editor::UIEditorShellInteractionMenuButtonRequest; using XCEngine::UI::Editor::UIEditorShellInteractionMenuButtonRequest;
using XCEngine::UI::Editor::UIEditorShellInteractionModel; using XCEngine::UI::Editor::UIEditorShellInteractionModel;
@@ -185,6 +190,31 @@ UIEditorShellInteractionModel BuildInteractionModel() {
return model; 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() { UIEditorWorkspaceController BuildController() {
return BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); return BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
} }
@@ -426,6 +456,59 @@ TEST(UIEditorShellInteractionTest, ClickCommandDispatchesInsideShellAndUpdatesWo
EXPECT_TRUE(frame.request.popupRequests.empty()); 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) { TEST(UIEditorShellInteractionTest, PointerDownOutsideDismissesWholeMenuChain) {
auto controller = BuildController(); auto controller = BuildController();
const auto model = BuildInteractionModel(); const auto model = BuildInteractionModel();