From f74503c2a5d2b3c1ef5c2e2214d14f10181c4ed6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 29 Apr 2026 17:34:31 +0800 Subject: [PATCH] Add workspace panel overlay routing --- .../Composition/EditorShellDrawComposer.cpp | 1 + .../EditorShellSessionCoordinator.cpp | 6 +- .../EditorWorkspacePanelRuntime.cpp | 29 +++++ .../EditorWorkspacePanelRuntime.h | 7 ++ .../Features/EditorWorkspacePanelRegistry.cpp | 16 +++ .../app/Features/Inspector/InspectorPanel.cpp | 75 ++++++++++++- .../app/Features/Inspector/InspectorPanel.h | 7 ++ editor/app/Features/Project/ProjectPanel.cpp | 103 +++++++++++++++--- editor/app/Features/Project/ProjectPanel.h | 4 + .../XCEditor/Fields/UIEditorPropertyGrid.h | 15 ++- .../Foundation/UIEditorPanelInputFilter.h | 36 +++++- .../XCEditor/Shell/UIEditorShellInteraction.h | 3 + .../Workspace/UIEditorWorkspaceInputOwner.h | 2 + .../Workspace/UIEditorWorkspaceInteraction.h | 2 + .../Workspace/UIEditorWorkspacePanelOverlay.h | 15 +++ editor/src/Fields/UIEditorPropertyGrid.cpp | 79 +++++++++----- editor/src/Shell/UIEditorShellInteraction.cpp | 2 + .../Workspace/UIEditorWorkspaceInputOwner.cpp | 38 +++++++ .../UIEditorWorkspaceInteraction.cpp | 3 + .../test_ui_editor_panel_input_filter.cpp | 26 +++++ .../test_ui_editor_workspace_interaction.cpp | 42 +++++++ 21 files changed, 458 insertions(+), 53 deletions(-) create mode 100644 editor/include/XCEditor/Workspace/UIEditorWorkspacePanelOverlay.h diff --git a/editor/app/Composition/EditorShellDrawComposer.cpp b/editor/app/Composition/EditorShellDrawComposer.cpp index d797cd26..9313def4 100644 --- a/editor/app/Composition/EditorShellDrawComposer.cpp +++ b/editor/app/Composition/EditorShellDrawComposer.cpp @@ -153,6 +153,7 @@ void EditorShellDrawComposer::Append( palette.shellPalette, metrics.shellMetrics); }); + context.workspacePanels.AppendOverlayDrawPackets(drawData); AppendDrawPacket( drawData, "XCEditorShell.Popups", diff --git a/editor/app/Composition/EditorShellSessionCoordinator.cpp b/editor/app/Composition/EditorShellSessionCoordinator.cpp index efcb8529..67243611 100644 --- a/editor/app/Composition/EditorShellSessionCoordinator.cpp +++ b/editor/app/Composition/EditorShellSessionCoordinator.cpp @@ -7,10 +7,14 @@ namespace XCEngine::UI::Editor::App { UIEditorShellInteractionDefinition EditorShellSessionCoordinator::PrepareShellDefinition( const EditorShellSessionCoordinatorContext& context) const { context.workspacePanels.PrepareForShellDefinition(context.workspaceController); - return context.frameServices.BuildShellDefinition( + UIEditorShellInteractionDefinition definition = + context.frameServices.BuildShellDefinition( context.workspaceController, context.captureText, context.shellVariant); + definition.panelOverlayRegions = + context.workspacePanels.CollectPanelOverlayRegions(); + return definition; } void EditorShellSessionCoordinator::FinalizeFrame( diff --git a/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.cpp b/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.cpp index 77622197..25b510d7 100644 --- a/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.cpp +++ b/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.cpp @@ -89,6 +89,35 @@ void EditorWorkspacePanelRuntimeSet::AppendDrawPackets( } } +void EditorWorkspacePanelRuntimeSet::AppendOverlayDrawPackets( + ::XCEngine::UI::UIDrawData& drawData) const { + for (const std::unique_ptr& panel : m_panels) { + AppendDrawPacket( + drawData, + std::string(panel->GetDrawListId()) + ".Overlay", + [&](::XCEngine::UI::UIDrawList& drawList) { + panel->AppendOverlay(drawList); + }); + } +} + +std::vector +EditorWorkspacePanelRuntimeSet::CollectPanelOverlayRegions() const { + std::vector regions = {}; + for (const std::unique_ptr& panel : m_panels) { + const std::vector<::XCEngine::UI::UIRect> panelBounds = + panel->CollectInteractiveOverlayBounds(); + regions.reserve(regions.size() + panelBounds.size()); + for (const ::XCEngine::UI::UIRect& bounds : panelBounds) { + UIEditorWorkspacePanelOverlayRegion region = {}; + region.panelId = std::string(panel->GetPanelId()); + region.bounds = bounds; + regions.push_back(std::move(region)); + } + } + return regions; +} + bool EditorWorkspacePanelRuntimeSet::HasActivePointerCapture() const { for (const std::unique_ptr& panel : m_panels) { if (panel->HasActivePointerCapture()) { diff --git a/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h b/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h index aaf91c81..f5b7cc63 100644 --- a/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h +++ b/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -99,6 +100,10 @@ public: } virtual void Update(const EditorWorkspacePanelUpdateContext& context) = 0; virtual void Append(::XCEngine::UI::UIDrawList& drawList) const = 0; + virtual void AppendOverlay(::XCEngine::UI::UIDrawList& drawList) const {} + virtual std::vector<::XCEngine::UI::UIRect> CollectInteractiveOverlayBounds() const { + return {}; + } virtual bool HasActivePointerCapture() const { return false; } @@ -132,6 +137,8 @@ public: const EditorWorkspacePanelUpdateContext& context, EditorWorkspacePanelUpdatePhase phase); void AppendDrawPackets(::XCEngine::UI::UIDrawData& drawData) const; + void AppendOverlayDrawPackets(::XCEngine::UI::UIDrawData& drawData) const; + std::vector CollectPanelOverlayRegions() const; bool HasActivePointerCapture() const; EditorWorkspacePanelCursorKind GetHostedContentCursorKind() const; diff --git a/editor/app/Features/EditorWorkspacePanelRegistry.cpp b/editor/app/Features/EditorWorkspacePanelRegistry.cpp index 4570f957..defd46a2 100644 --- a/editor/app/Features/EditorWorkspacePanelRegistry.cpp +++ b/editor/app/Features/EditorWorkspacePanelRegistry.cpp @@ -337,6 +337,14 @@ public: m_panel.Append(drawList); } + void AppendOverlay(::XCEngine::UI::UIDrawList& drawList) const override { + m_panel.AppendOverlay(drawList); + } + + std::vector<::XCEngine::UI::UIRect> CollectInteractiveOverlayBounds() const override { + return m_panel.CollectInteractiveOverlayBounds(); + } + void Initialize(const EditorWorkspacePanelInitializationContext& context) override { m_textMeasurer = &context.textMeasurer; } @@ -409,6 +417,14 @@ public: m_panel.Append(drawList); } + void AppendOverlay(::XCEngine::UI::UIDrawList& drawList) const override { + m_panel.AppendOverlay(drawList); + } + + std::vector<::XCEngine::UI::UIRect> CollectInteractiveOverlayBounds() const override { + return m_panel.CollectInteractiveOverlayBounds(); + } + bool HasActivePointerCapture() const override { return m_panel.HasActivePointerCapture(); } diff --git a/editor/app/Features/Inspector/InspectorPanel.cpp b/editor/app/Features/Inspector/InspectorPanel.cpp index 01b216fb..8f0b4230 100644 --- a/editor/app/Features/Inspector/InspectorPanel.cpp +++ b/editor/app/Features/Inspector/InspectorPanel.cpp @@ -792,6 +792,9 @@ void InspectorPanel::Update( RefreshPresentation(context, subjectChanged); ApplyColorPickerToolValue(context); + RebuildScrollableLayout(); + + const std::vector popupBounds = CollectInteractiveOverlayBounds(); const std::vector filteredEvents = BuildUIEditorPanelInputEvents( @@ -812,7 +815,8 @@ void InspectorPanel::Update( HasActivePointerCapture() }, dispatchEntry.focusGained, - dispatchEntry.focusLost); + dispatchEntry.focusLost, + popupBounds.empty() ? nullptr : &popupBounds); TryClaimHostedPanelCommandFocus( m_commandFocusService, EditorActionRoute::Inspector, @@ -820,7 +824,6 @@ void InspectorPanel::Update( m_bounds, dispatchEntry.allowInteraction); - RebuildScrollableLayout(); const UIRect scrollViewportBounds = BuildScrollViewportBounds(); if (scrollViewportBounds.width > 0.0f && scrollViewportBounds.height > 0.0f) { m_scrollInteractionState.scrollViewState.focused = @@ -951,7 +954,8 @@ void InspectorPanel::Append(UIDrawList& drawList) const { ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics(), {}, {}, - m_textMeasurer); + m_textMeasurer, + false); } if (ShouldShowAddComponentButton()) { @@ -985,6 +989,71 @@ void InspectorPanel::Append(UIDrawList& drawList) const { drawList.PopClipRect(); } +void InspectorPanel::AppendOverlay(UIDrawList& drawList) const { + if (!m_visible) { + return; + } + + Widgets::UIEditorMenuPopupLayout popupLayout = {}; + Widgets::UIEditorMenuPopupState popupState = {}; + std::vector popupItems = {}; + if (!BuildPropertyGridPopupRuntime(popupLayout, popupState, popupItems)) { + return; + } + + const Widgets::UIEditorMenuPopupMetrics& popupMetrics = + ResolveUIEditorMenuPopupMetrics(); + const Widgets::UIEditorMenuPopupPalette& popupPalette = + ResolveUIEditorMenuPopupPalette(); + Widgets::AppendUIEditorMenuPopupBackground( + drawList, + popupLayout, + popupItems, + popupState, + popupPalette, + popupMetrics); + Widgets::AppendUIEditorMenuPopupForeground( + drawList, + popupLayout, + popupItems, + popupState, + popupPalette, + popupMetrics); +} + +std::vector InspectorPanel::CollectInteractiveOverlayBounds() const { + Widgets::UIEditorMenuPopupLayout popupLayout = {}; + Widgets::UIEditorMenuPopupState popupState = {}; + std::vector popupItems = {}; + if (!BuildPropertyGridPopupRuntime(popupLayout, popupState, popupItems) || + popupLayout.popupRect.width <= 0.0f || + popupLayout.popupRect.height <= 0.0f) { + return {}; + } + + return { popupLayout.popupRect }; +} + +bool InspectorPanel::BuildPropertyGridPopupRuntime( + Widgets::UIEditorMenuPopupLayout& popupLayout, + Widgets::UIEditorMenuPopupState& popupState, + std::vector& popupItems) const { + if (!m_visible || + m_gridFrame.layout.bounds.width <= 0.0f || + m_gridFrame.layout.bounds.height <= 0.0f) { + return false; + } + + return Widgets::BuildUIEditorPropertyGridPopupRuntime( + m_gridFrame.layout, + m_presentation.sections, + m_interactionState.propertyGridState, + popupLayout, + popupState, + popupItems, + ResolveUIEditorMenuPopupMetrics()); +} + UIEditorHostCommandEvaluationResult InspectorPanel::EvaluateEditCommand( std::string_view commandId) const { const InspectorPresentationComponentBinding* binding = diff --git a/editor/app/Features/Inspector/InspectorPanel.h b/editor/app/Features/Inspector/InspectorPanel.h index 8e834a20..208b3caa 100644 --- a/editor/app/Features/Inspector/InspectorPanel.h +++ b/editor/app/Features/Inspector/InspectorPanel.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,8 @@ public: const UIEditorHostedPanelDispatchEntry& dispatchEntry, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); void Append(::XCEngine::UI::UIDrawList& drawList) const; + void AppendOverlay(::XCEngine::UI::UIDrawList& drawList) const; + std::vector<::XCEngine::UI::UIRect> CollectInteractiveOverlayBounds() const; UIEditorHostCommandEvaluationResult EvaluateEditCommand( std::string_view commandId) const override; @@ -87,6 +90,10 @@ private: bool ApplyColorPickerToolValue(InspectorPanelContext& context); void RequestColorPicker(InspectorPanelContext& context, std::string_view fieldId); bool HandleActivatedField(std::string_view fieldId); + bool BuildPropertyGridPopupRuntime( + Widgets::UIEditorMenuPopupLayout& popupLayout, + Widgets::UIEditorMenuPopupState& popupState, + std::vector& popupItems) const; void ResetAddComponentButtonState(); void UpdateAddComponentButton( InspectorPanelContext& context, diff --git a/editor/app/Features/Project/ProjectPanel.cpp b/editor/app/Features/Project/ProjectPanel.cpp index ce5d4e56..f1058679 100644 --- a/editor/app/Features/Project/ProjectPanel.cpp +++ b/editor/app/Features/Project/ProjectPanel.cpp @@ -265,6 +265,23 @@ bool HasValidBounds(const UIRect& bounds) { return bounds.width > 0.0f && bounds.height > 0.0f; } +bool IsContentBlockedWhileContextMenuOpen(UIInputEventType type) { + switch (type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + case UIInputEventType::KeyDown: + case UIInputEventType::KeyUp: + case UIInputEventType::Character: + return true; + default: + return false; + } +} + constexpr auto kGridDoubleClickInterval = std::chrono::milliseconds(400); Widgets::UIEditorMenuPopupItem BuildContextMenuCommandItem( @@ -1272,6 +1289,22 @@ std::vector ProjectPanel::BuildTreeInteractionInputEvents( m_splitterDragging || m_assetDragState.dragging); } +std::vector ProjectPanel::BuildPanelContentInputEvents( + const std::vector& inputEvents) const { + if (!m_contextMenu.open) { + return inputEvents; + } + + std::vector contentEvents = {}; + contentEvents.reserve(inputEvents.size()); + for (const UIInputEvent& event : inputEvents) { + if (!IsContentBlockedWhileContextMenuOpen(event.type)) { + contentEvents.push_back(event); + } + } + return contentEvents; +} + UIEditorHostCommandEvaluationResult ProjectPanel::EvaluateAssetCommand( std::string_view commandId) const { return EvaluateAssetCommand(commandId, {}, false); @@ -1668,7 +1701,7 @@ void ProjectPanel::Update( RebuildWindowTreeItems(); m_visible = true; SyncAssetSelectionFromRuntime(); - const std::vector filteredEvents = + const std::vector baseFilteredEvents = BuildUIEditorPanelInputEvents( dispatchEntry.bounds, inputEvents, @@ -1686,16 +1719,12 @@ void ProjectPanel::Update( }, dispatchEntry.focusGained, dispatchEntry.focusLost); - TryClaimHostedPanelCommandFocus( - m_commandFocusService, - EditorActionRoute::Project, - filteredEvents, - dispatchEntry.bounds, - dispatchEntry.allowInteraction); + const std::vector baseContentEvents = + BuildPanelContentInputEvents(baseFilteredEvents); m_navigationWidth = ClampNavigationWidth(m_navigationWidth, dispatchEntry.bounds.width); const Widgets::UIEditorTreeViewMetrics treeMetrics = - RebuildPanelLayout(dispatchEntry.bounds, filteredEvents); + RebuildPanelLayout(dispatchEntry.bounds, baseContentEvents); const auto refreshBrowserLayout = [&]() { RebuildBrowserScrollLayout(); ApplyBrowserLayout( @@ -1703,6 +1732,40 @@ void ProjectPanel::Update( m_browserScrollFrame.layout.contentRect, m_browserVerticalOffset); }; + if (m_contextMenu.open) { + RebuildContextMenu(); + } + + const std::vector overlayBounds = CollectInteractiveOverlayBounds(); + const std::vector filteredEvents = + BuildUIEditorPanelInputEvents( + dispatchEntry.bounds, + inputEvents, + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = dispatchEntry.allowInteraction, + .allowPointerWhileCaptured = HasActivePointerCapture(), + .allowKeyboardInput = dispatchEntry.focused, + .allowFocusEvents = + dispatchEntry.focused || + HasActivePointerCapture() || + dispatchEntry.focusGained || + dispatchEntry.focusLost, + .includePointerLeave = + dispatchEntry.allowInteraction || HasActivePointerCapture() + }, + dispatchEntry.focusGained, + dispatchEntry.focusLost, + overlayBounds.empty() ? nullptr : &overlayBounds); + const std::vector contentEvents = + BuildPanelContentInputEvents(filteredEvents); + + TryClaimHostedPanelCommandFocus( + m_commandFocusService, + EditorActionRoute::Project, + filteredEvents, + dispatchEntry.bounds, + dispatchEntry.allowInteraction); + const auto updateBrowserScrollInteraction = [&]() { RebuildBrowserScrollLayout(); if (HasValidBounds(m_layout.browserBodyRect)) { @@ -1711,7 +1774,7 @@ void ProjectPanel::Update( m_browserVerticalOffset, m_layout.browserBodyRect, m_browserScrollFrame.layout.contentHeight, - filteredEvents, + contentEvents, ResolveUIEditorScrollViewMetrics()); } else { m_browserScrollFrame = {}; @@ -1723,9 +1786,6 @@ void ProjectPanel::Update( m_browserScrollFrame.layout.contentRect, m_browserVerticalOffset); }; - if (m_contextMenu.open) { - RebuildContextMenu(); - } m_treeFrame.result = {}; if ((m_renameState.active || !m_pendingRenameItemId.empty()) && @@ -1743,7 +1803,7 @@ void ProjectPanel::Update( const std::vector treeEvents = BuildTreeInteractionInputEvents( - inputEvents, + contentEvents, m_layout.treeRect, dispatchEntry); ::XCEngine::UI::Widgets::UIExpansionModel* activeExpansionModel = @@ -1835,7 +1895,7 @@ void ProjectPanel::Update( m_treeDragState, m_treeFrame.layout, GetPresentedWindowTreeItems(), - filteredEvents, + contentEvents, m_layout.treeRect, treeDragCallbacks, TreeDrag::kDefaultDragThreshold, @@ -1930,7 +1990,7 @@ void ProjectPanel::Update( const GridDrag::ProcessResult assetDragResult = GridDrag::ProcessInputEvents( m_assetDragState, - filteredEvents, + contentEvents, assetDragCallbacks); m_assetDropTargetSurface = m_assetDragState.dragging && m_assetDragState.validDropTarget @@ -2551,9 +2611,22 @@ void ProjectPanel::Append(UIDrawList& drawList) const { } drawList.PopClipRect(); +} + +void ProjectPanel::AppendOverlay(UIDrawList& drawList) const { AppendContextMenu(drawList); } +std::vector ProjectPanel::CollectInteractiveOverlayBounds() const { + if (!m_contextMenu.open || + m_contextMenu.items.empty() || + !HasValidBounds(m_contextMenu.layout.popupRect)) { + return {}; + } + + return { m_contextMenu.layout.popupRect }; +} + } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Features/Project/ProjectPanel.h b/editor/app/Features/Project/ProjectPanel.h index 7c124b36..353f7aa0 100644 --- a/editor/app/Features/Project/ProjectPanel.h +++ b/editor/app/Features/Project/ProjectPanel.h @@ -96,6 +96,8 @@ public: const UIEditorHostedPanelDispatchEntry& dispatchEntry, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); void Append(::XCEngine::UI::UIDrawList& drawList) const; + void AppendOverlay(::XCEngine::UI::UIDrawList& drawList) const; + std::vector<::XCEngine::UI::UIRect> CollectInteractiveOverlayBounds() const; CursorKind GetCursorKind() const; bool HasActivePointerCapture() const; @@ -216,6 +218,8 @@ private: void ClearRenameState(); void SyncSelectionsFromRuntime(); void SyncAssetSelectionFromRuntime(); + std::vector<::XCEngine::UI::UIInputEvent> BuildPanelContentInputEvents( + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents) const; Widgets::UIEditorTreeViewMetrics RebuildPanelLayout( const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& treeHostInputEvents = {}); diff --git a/editor/include/XCEditor/Fields/UIEditorPropertyGrid.h b/editor/include/XCEditor/Fields/UIEditorPropertyGrid.h index 977a468b..f113d3e0 100644 --- a/editor/include/XCEditor/Fields/UIEditorPropertyGrid.h +++ b/editor/include/XCEditor/Fields/UIEditorPropertyGrid.h @@ -338,7 +338,17 @@ void AppendUIEditorPropertyGridForeground( const UIEditorPropertyGridMetrics& metrics = {}, const UIEditorMenuPopupPalette& popupPalette = {}, const UIEditorMenuPopupMetrics& popupMetrics = {}, - const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr); + const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr, + bool appendPopup = true); + +bool BuildUIEditorPropertyGridPopupRuntime( + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const UIEditorPropertyGridState& state, + UIEditorMenuPopupLayout& popupLayout, + UIEditorMenuPopupState& popupState, + std::vector& popupItems, + const UIEditorMenuPopupMetrics& popupMetrics = {}); void AppendUIEditorPropertyGrid( ::XCEngine::UI::UIDrawList& drawList, @@ -352,6 +362,7 @@ void AppendUIEditorPropertyGrid( const UIEditorPropertyGridMetrics& metrics = {}, const UIEditorMenuPopupPalette& popupPalette = {}, const UIEditorMenuPopupMetrics& popupMetrics = {}, - const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr); + const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr, + bool appendPopup = true); } // namespace XCEngine::UI::Editor::Widgets diff --git a/editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h b/editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h index 43fd36f5..71386ded 100644 --- a/editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h +++ b/editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h @@ -24,7 +24,8 @@ inline ::XCEngine::UI::UIInputEvent BuildUIEditorPanelFocusEvent( inline std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorPanelInputEvents( const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const UIEditorPanelInputFilterOptions& options) { + const UIEditorPanelInputFilterOptions& options, + const std::vector<::XCEngine::UI::UIRect>* extraPointerBounds = nullptr) { using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; @@ -42,6 +43,27 @@ inline std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorPanelInputEvents( point.y >= bounds.y && point.y <= bounds.y + bounds.height; }; + const auto isPointInAllowedPointerBounds = + [&containsPoint, extraPointerBounds](const ::XCEngine::UI::UIPoint& point) { + if (containsPoint(point)) { + return true; + } + + if (extraPointerBounds == nullptr) { + return false; + } + + for (const ::XCEngine::UI::UIRect& rect : *extraPointerBounds) { + if (point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height) { + return true; + } + } + + return false; + }; std::vector filteredEvents = {}; filteredEvents.reserve(inputEvents.size()); @@ -52,7 +74,8 @@ inline std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorPanelInputEvents( case UIInputEventType::PointerButtonUp: case UIInputEventType::PointerWheel: if (options.allowPointerWhileCaptured || - (options.allowPointerInBounds && containsPoint(event.position))) { + (options.allowPointerInBounds && + isPointInAllowedPointerBounds(event.position))) { filteredEvents.push_back(event); } break; @@ -91,9 +114,14 @@ inline std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorPanelInputEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const UIEditorPanelInputFilterOptions& options, bool synthesizeFocusGained = false, - bool synthesizeFocusLost = false) { + bool synthesizeFocusLost = false, + const std::vector<::XCEngine::UI::UIRect>* extraPointerBounds = nullptr) { std::vector<::XCEngine::UI::UIInputEvent> filteredEvents = - FilterUIEditorPanelInputEvents(bounds, inputEvents, options); + FilterUIEditorPanelInputEvents( + bounds, + inputEvents, + options, + extraPointerBounds); if (!synthesizeFocusGained && !synthesizeFocusLost) { return filteredEvents; } diff --git a/editor/include/XCEditor/Shell/UIEditorShellInteraction.h b/editor/include/XCEditor/Shell/UIEditorShellInteraction.h index c5c52849..00e7f281 100644 --- a/editor/include/XCEditor/Shell/UIEditorShellInteraction.h +++ b/editor/include/XCEditor/Shell/UIEditorShellInteraction.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ struct UIEditorShellInteractionDefinition { std::vector toolbarButtons = {}; std::vector statusSegments = {}; std::vector workspacePresentations = {}; + std::vector panelOverlayRegions = {}; }; struct UIEditorShellInteractionModel { @@ -28,6 +30,7 @@ struct UIEditorShellInteractionModel { std::vector toolbarButtons = {}; std::vector statusSegments = {}; std::vector workspacePresentations = {}; + std::vector panelOverlayRegions = {}; }; struct UIEditorShellInteractionState { diff --git a/editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h b/editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h index 0ec3be03..2a616c0b 100644 --- a/editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h +++ b/editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h @@ -2,6 +2,7 @@ #include #include +#include #include @@ -44,6 +45,7 @@ bool IsUIEditorWorkspaceViewportInputOwner( UIEditorWorkspaceInputOwner ResolveUIEditorWorkspacePointerInputOwner( const Widgets::UIEditorDockHostLayout& dockHostLayout, const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector& panelOverlayRegions, const ::XCEngine::UI::UIPoint& pointerPosition); UIEditorWorkspaceInputOwner NormalizeUIEditorWorkspaceInputOwner( diff --git a/editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h b/editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h index 979e857f..27986197 100644 --- a/editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h +++ b/editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -15,6 +16,7 @@ namespace XCEngine::UI::Editor { struct UIEditorWorkspaceInteractionModel { std::vector workspacePresentations = {}; + std::vector panelOverlayRegions = {}; }; struct UIEditorWorkspaceInteractionState { diff --git a/editor/include/XCEditor/Workspace/UIEditorWorkspacePanelOverlay.h b/editor/include/XCEditor/Workspace/UIEditorWorkspacePanelOverlay.h new file mode 100644 index 00000000..00e68a06 --- /dev/null +++ b/editor/include/XCEditor/Workspace/UIEditorWorkspacePanelOverlay.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorWorkspacePanelOverlayRegion { + std::string panelId = {}; + ::XCEngine::UI::UIRect bounds = {}; + bool interactive = true; +}; + +} // namespace XCEngine::UI::Editor diff --git a/editor/src/Fields/UIEditorPropertyGrid.cpp b/editor/src/Fields/UIEditorPropertyGrid.cpp index b3163be5..161fced3 100644 --- a/editor/src/Fields/UIEditorPropertyGrid.cpp +++ b/editor/src/Fields/UIEditorPropertyGrid.cpp @@ -894,7 +894,8 @@ void AppendUIEditorPropertyGridForeground( const UIEditorPropertyGridMetrics& metrics, const UIEditorMenuPopupPalette& popupPalette, const UIEditorMenuPopupMetrics& popupMetrics, - const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer) { + const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer, + bool appendPopup) { (void)propertyEditModel; drawList.PushClipRect(layout.bounds); @@ -1236,34 +1237,54 @@ void AppendUIEditorPropertyGridForeground( drawList.PopClipRect(); - UIEditorMenuPopupLayout popupLayout = {}; - UIEditorMenuPopupState popupState = {}; - std::vector popupItems = {}; - if (Internal::BuildEnumPopupRuntime( - layout, - sections, - state, - popupMetrics, - popupLayout, - popupState, - popupItems)) { - AppendUIEditorMenuPopupBackground( - drawList, - popupLayout, - popupItems, - popupState, - popupPalette, - popupMetrics); - AppendUIEditorMenuPopupForeground( - drawList, - popupLayout, - popupItems, - popupState, - popupPalette, - popupMetrics); + if (appendPopup) { + UIEditorMenuPopupLayout popupLayout = {}; + UIEditorMenuPopupState popupState = {}; + std::vector popupItems = {}; + if (Internal::BuildEnumPopupRuntime( + layout, + sections, + state, + popupMetrics, + popupLayout, + popupState, + popupItems)) { + AppendUIEditorMenuPopupBackground( + drawList, + popupLayout, + popupItems, + popupState, + popupPalette, + popupMetrics); + AppendUIEditorMenuPopupForeground( + drawList, + popupLayout, + popupItems, + popupState, + popupPalette, + popupMetrics); + } } } +bool BuildUIEditorPropertyGridPopupRuntime( + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const UIEditorPropertyGridState& state, + UIEditorMenuPopupLayout& popupLayout, + UIEditorMenuPopupState& popupState, + std::vector& popupItems, + const UIEditorMenuPopupMetrics& popupMetrics) { + return Internal::BuildEnumPopupRuntime( + layout, + sections, + state, + popupMetrics, + popupLayout, + popupState, + popupItems); +} + void AppendUIEditorPropertyGrid( ::XCEngine::UI::UIDrawList& drawList, const ::XCEngine::UI::UIRect& bounds, @@ -1276,7 +1297,8 @@ void AppendUIEditorPropertyGrid( const UIEditorPropertyGridMetrics& metrics, const UIEditorMenuPopupPalette& popupPalette, const UIEditorMenuPopupMetrics& popupMetrics, - const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer) { + const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer, + bool appendPopup) { const UIEditorPropertyGridLayout layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); AppendUIEditorPropertyGridBackground( @@ -1299,7 +1321,8 @@ void AppendUIEditorPropertyGrid( metrics, popupPalette, popupMetrics, - textMeasurer); + textMeasurer, + appendPopup); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/editor/src/Shell/UIEditorShellInteraction.cpp b/editor/src/Shell/UIEditorShellInteraction.cpp index 1819d857..b7beee26 100644 --- a/editor/src/Shell/UIEditorShellInteraction.cpp +++ b/editor/src/Shell/UIEditorShellInteraction.cpp @@ -577,6 +577,7 @@ UIEditorShellInteractionModel ResolveUIEditorShellInteractionModel( services); model.statusSegments = definition.statusSegments; model.workspacePresentations = definition.workspacePresentations; + model.panelOverlayRegions = definition.panelOverlayRegions; return model; } @@ -826,6 +827,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( Internal::FilterWorkspaceInputEvents(inputEvents, menuModalDuringFrame); UIEditorWorkspaceInteractionModel workspaceModel = {}; workspaceModel.workspacePresentations = model.workspacePresentations; + workspaceModel.panelOverlayRegions = model.panelOverlayRegions; UIEditorWorkspaceInteractionFrame workspaceInteractionFrame = UpdateUIEditorWorkspaceInteraction( state.workspaceInteractionState, diff --git a/editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp b/editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp index 5b61ea24..ceb808ad 100644 --- a/editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp +++ b/editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp @@ -25,6 +25,20 @@ bool IsMountedContentPanelValid( return panelState.mounted && HasUsableBounds(panelState.bounds); } +const UIEditorPanelContentHostPanelState* FindMountedContentPanelById( + const UIEditorPanelContentHostFrame& contentHostFrame, + std::string_view panelId) { + for (const UIEditorPanelContentHostPanelState& panelState : + contentHostFrame.panelStates) { + if (IsMountedContentPanelValid(panelState) && + panelState.panelId == panelId) { + return &panelState; + } + } + + return nullptr; +} + } // namespace std::string_view GetUIEditorWorkspaceInputOwnerKindName( @@ -71,7 +85,31 @@ bool IsUIEditorWorkspaceViewportInputOwner( UIEditorWorkspaceInputOwner ResolveUIEditorWorkspacePointerInputOwner( const Widgets::UIEditorDockHostLayout& dockHostLayout, const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector& panelOverlayRegions, const UIPoint& pointerPosition) { + for (std::size_t index = panelOverlayRegions.size(); index > 0u; --index) { + const UIEditorWorkspacePanelOverlayRegion& region = + panelOverlayRegions[index - 1u]; + if (!region.interactive || + !HasUsableBounds(region.bounds) || + !ContainsPoint(region.bounds, pointerPosition)) { + continue; + } + + const UIEditorPanelContentHostPanelState* panelState = + FindMountedContentPanelById(contentHostFrame, region.panelId); + if (panelState == nullptr) { + continue; + } + + UIEditorWorkspaceInputOwner owner = {}; + owner.panelId = panelState->panelId; + owner.kind = panelState->kind == UIEditorPanelPresentationKind::ViewportShell + ? UIEditorWorkspaceInputOwnerKind::Viewport + : UIEditorWorkspaceInputOwnerKind::HostedPanel; + return owner; + } + for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { if (!IsMountedContentPanelValid(panelState) || diff --git a/editor/src/Workspace/UIEditorWorkspaceInteraction.cpp b/editor/src/Workspace/UIEditorWorkspaceInteraction.cpp index c635b315..3c7999ac 100644 --- a/editor/src/Workspace/UIEditorWorkspaceInteraction.cpp +++ b/editor/src/Workspace/UIEditorWorkspaceInteraction.cpp @@ -24,6 +24,7 @@ UIEditorWorkspaceInputOwner ResolveNextInputOwner( UIEditorWorkspaceInputOwner currentOwner, const Widgets::UIEditorDockHostLayout& dockHostLayout, const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector& panelOverlayRegions, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents) { using ::XCEngine::UI::UIInputEventType; @@ -41,6 +42,7 @@ UIEditorWorkspaceInputOwner ResolveNextInputOwner( currentOwner = ResolveUIEditorWorkspacePointerInputOwner( dockHostLayout, contentHostFrame, + panelOverlayRegions, event.position); break; @@ -104,6 +106,7 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( state.inputOwner, previewRequest.dockHostLayout, previewContentHostFrame, + model.panelOverlayRegions, inputEvents); frame.inputOwner = state.inputOwner; frame.inputOwnerChanged = !AreUIEditorWorkspaceInputOwnersEquivalent( diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp index f1aad2c0..6bd2a3b6 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp @@ -95,6 +95,32 @@ TEST(UIEditorPanelInputFilterTests, CapturedPointerEventsBypassBoundsWhenEnabled EXPECT_EQ(filtered[1].type, UIInputEventType::PointerButtonDown); } +TEST(UIEditorPanelInputFilterTests, ExtraOverlayBoundsAllowPointerEventsOutsidePanelRect) { + const std::vector overlayBounds = { + UIRect(120.0f, 10.0f, 80.0f, 80.0f) + }; + const std::vector filtered = + FilterUIEditorPanelInputEvents( + UIRect(0.0f, 0.0f, 100.0f, 100.0f), + { + MakePointerMove(150.0f, 30.0f), + MakePointerButtonDown(160.0f, 35.0f), + MakePointerMove(240.0f, 30.0f) + }, + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = true, + .allowPointerWhileCaptured = false, + .allowKeyboardInput = false, + .allowFocusEvents = false, + .includePointerLeave = false + }, + &overlayBounds); + + ASSERT_EQ(filtered.size(), 2u); + EXPECT_EQ(filtered[0].type, UIInputEventType::PointerMove); + EXPECT_EQ(filtered[1].type, UIInputEventType::PointerButtonDown); +} + TEST(UIEditorPanelInputFilterTests, SyntheticFocusTransitionsArePrependedToFilteredEvents) { const std::vector filtered = BuildUIEditorPanelInputEvents( diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp index 5f44b462..b25951d3 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp @@ -208,6 +208,48 @@ TEST(UIEditorWorkspaceInteractionTest, HostedPanelPointerDownTransfersOwnerAndCl EXPECT_TRUE(frame.result.viewportInputFrame.captureEnded); } +TEST(UIEditorWorkspaceInteractionTest, OverlayRegionOutsidePanelBoundsResolvesHostedPanelOwner) { + auto controller = + BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + UIEditorWorkspaceInteractionState state = {}; + UIEditorWorkspaceInteractionModel model = BuildInteractionModel(); + + auto frame = UpdateUIEditorWorkspaceInteraction( + state, + controller, + UIRect(0.0f, 0.0f, 1280.0f, 720.0f), + model, + {}); + const auto* viewportFrame = + FindUIEditorWorkspaceViewportPresentationFrame(frame.composeFrame, "viewport"); + ASSERT_NE(viewportFrame, nullptr); + const UIPoint viewportCenter = + RectCenter(viewportFrame->viewportShellFrame.slotLayout.inputRect); + + model.panelOverlayRegions = { + { + "details", + UIRect(viewportCenter.x - 24.0f, viewportCenter.y - 24.0f, 48.0f, 48.0f), + true + } + }; + frame = UpdateUIEditorWorkspaceInteraction( + state, + controller, + UIRect(0.0f, 0.0f, 1280.0f, 720.0f), + model, + { + MakePointerEvent( + UIInputEventType::PointerButtonDown, + viewportCenter.x, + viewportCenter.y, + UIPointerButton::Left) + }); + + EXPECT_TRUE(frame.inputOwnerChanged); + EXPECT_TRUE(IsUIEditorWorkspaceHostedPanelInputOwner(frame.inputOwner, "details")); +} + TEST(UIEditorWorkspaceInteractionTest, PointerUpInsideViewportBubblesPointerReleaseRequest) { auto controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());