diff --git a/docs/plan/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_2026-04-22.md b/docs/used/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_完成归档_2026-04-22.md similarity index 90% rename from docs/plan/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_2026-04-22.md rename to docs/used/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_完成归档_2026-04-22.md index 674350bb..e6c8de71 100644 --- a/docs/plan/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_2026-04-22.md +++ b/docs/used/NewEditor_WindowWorkspaceSingleSourceRefactorPlan_完成归档_2026-04-22.md @@ -1,25 +1,30 @@ # NewEditor Window Workspace Single-Source Refactor Plan Date: 2026-04-22 -Status: In Progress +Status: Completed ## Progress Completed on 2026-04-22: -1. `EditorWindowWorkspaceCoordinator::BuildWorkspaceMutationController()` no longer reads mutation input from `EditorWindowWorkspaceStore` mirror -2. mutation input is now built from live `EditorWindow` controllers at call time -3. Debug build of `XCUIEditorApp` passed -4. manual verification passed for: +1. `EditorWindowWorkspaceCoordinator::BuildWorkspaceMutationController()` now builds mutation input from live `EditorWindow` controllers at call time +2. `EditorWindowHostRuntime::RenderAllWindows()` no longer performs per-frame workspace projection commit +3. detached window title refresh now runs as an explicit live-window frame step instead of piggybacking on projection +4. `EditorWindowWorkspaceStore` no longer persists mirrored `workspace/session`; it is reduced to panel-registry-backed validation only +5. dead projection / mirrored-workspace bridge APIs were removed from `EditorWindowWorkspaceStore` +6. close-path workspace description now derives from live windows instead of the old store mirror +7. `XCUIEditorApp` Debug rebuild passed after the final mirror-removal changes +8. step-1 manual verification already passed before the final mirror-removal phase: - detach - re-dock - open / close baseline behavior - detached title baseline behavior -Next: +Archived on 2026-04-22: -1. split detached title refresh from frame-time projection -2. then remove the remaining per-frame mirror commit path +1. final detached-window / re-dock / close-path verification has been accepted +2. the refactor no longer depends on per-frame mirrored workspace projection +3. the remaining unrelated editor follow-up bugs are handled outside this plan scope ## 1. Objective 这份计划只处理 `new_editor` 当前最严重的一处结构性冗余: diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index d6210e1d..c92e58cc 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -307,7 +307,8 @@ UIRect HierarchyPanel::BuildRenameBounds( } bool HierarchyPanel::HasActivePointerCapture() const { - return TreeDrag::HasActivePointerCapture(m_dragState); + return TreeDrag::HasActivePointerCapture(m_dragState) || + HasActiveUIEditorTreeViewPointerCapture(m_treeInteractionState); } const std::vector& HierarchyPanel::GetFrameEvents() const { diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index e2850c7b..344ca637 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -2,6 +2,7 @@ #include "Composition/EditorContext.h" #include "State/EditorColorPickerToolState.h" +#include #include #include #include @@ -39,6 +40,89 @@ constexpr UIColor kTitleColor(0.930f, 0.930f, 0.930f, 1.0f); constexpr UIColor kSubtitleColor(0.660f, 0.660f, 0.660f, 1.0f); constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); +void OffsetRectY(UIRect& rect, float deltaY) { + rect.y += deltaY; +} + +bool ShouldOffsetPointerEventPosition(UIInputEventType type) { + switch (type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + return true; + default: + return false; + } +} + +std::vector BuildScrolledPropertyGridInputEvents( + const std::vector& inputEvents, + float verticalOffset) { + std::vector adjustedEvents = inputEvents; + for (UIInputEvent& event : adjustedEvents) { + if (ShouldOffsetPointerEventPosition(event.type)) { + event.position.y += verticalOffset; + } + } + return adjustedEvents; +} + +Widgets::UIEditorPropertyGridLayout TranslatePropertyGridLayoutForScroll( + const Widgets::UIEditorPropertyGridLayout& layout, + float verticalOffset) { + Widgets::UIEditorPropertyGridLayout translated = layout; + if (verticalOffset == 0.0f) { + return translated; + } + + const float deltaY = -verticalOffset; + for (UIRect& rect : translated.sectionHeaderRects) { + OffsetRectY(rect, deltaY); + } + for (UIRect& rect : translated.sectionDisclosureRects) { + OffsetRectY(rect, deltaY); + } + for (UIRect& rect : translated.sectionTitleRects) { + OffsetRectY(rect, deltaY); + } + for (UIRect& rect : translated.fieldRowRects) { + OffsetRectY(rect, deltaY); + } + for (UIRect& rect : translated.fieldLabelRects) { + OffsetRectY(rect, deltaY); + } + for (UIRect& rect : translated.fieldValueRects) { + OffsetRectY(rect, deltaY); + } + return translated; +} + +float ResolveAddComponentButtonTop( + const Widgets::UIEditorPropertyGridLayout& gridLayout, + const UIRect& contentBounds) { + if (!gridLayout.fieldRowRects.empty()) { + const UIRect& lastFieldRect = gridLayout.fieldRowRects.back(); + return lastFieldRect.y + lastFieldRect.height + kAddComponentButtonTopGap; + } + if (!gridLayout.sectionHeaderRects.empty()) { + const UIRect& lastSectionRect = gridLayout.sectionHeaderRects.back(); + return lastSectionRect.y + lastSectionRect.height + kAddComponentButtonTopGap; + } + return contentBounds.y; +} + +Widgets::UIEditorScrollViewPalette BuildInspectorScrollPalette() { + Widgets::UIEditorScrollViewPalette palette = + ::XCEngine::UI::Editor::ResolveUIEditorScrollViewPalette(); + palette.surfaceColor.a = 0.0f; + palette.borderColor.a = 0.0f; + palette.focusedBorderColor.a = 0.0f; + return palette; +} + float ResolveInspectorHorizontalPadding( const InspectorPresentationModel& presentation) { return presentation.showHeader ? kPanelPadding : 0.0f; @@ -138,12 +222,19 @@ void InspectorPanel::ResetInteractionState() { m_fieldSelection.ClearSelection(); m_sectionExpansion.Clear(); m_propertyEditModel = {}; + m_scrollInteractionState = {}; + m_scrollFrame = {}; + m_scrollVerticalOffset = 0.0f; m_interactionState = {}; m_gridFrame = {}; m_lastAppliedColorPickerRevision = 0u; ResetAddComponentButtonState(); } +bool InspectorPanel::HasActivePointerCapture() const { + return HasActiveUIEditorScrollViewPointerCapture(m_scrollInteractionState); +} + void InspectorPanel::SyncExpansionState(bool subjectChanged) { if (subjectChanged) { m_sectionExpansion.Clear(); @@ -213,7 +304,7 @@ bool InspectorPanel::ShouldShowAddComponentButton() const { return m_subject.kind == InspectorSubjectKind::SceneObject; } -UIRect InspectorPanel::BuildGridBounds() const { +UIRect InspectorPanel::BuildScrollViewportBounds() const { const float horizontalPadding = ResolveInspectorHorizontalPadding(m_presentation); const float topPadding = ResolveInspectorTopPadding(m_presentation); @@ -227,31 +318,82 @@ UIRect InspectorPanel::BuildGridBounds() const { return UIRect(x, y, width, height); } -UIRect InspectorPanel::BuildAddComponentButtonRect() const { +UIRect InspectorPanel::BuildAddComponentButtonRect( + const Widgets::UIEditorPropertyGridLayout& gridLayout, + const UIRect& contentBounds) const { if (!ShouldShowAddComponentButton()) { return {}; } - const float horizontalPadding = - ResolveInspectorHorizontalPadding(m_presentation); - const float bottomPadding = ResolveInspectorBottomPadding(m_presentation); - float buttonTop = BuildGridBounds().y; - if (!m_gridFrame.layout.fieldRowRects.empty()) { - const UIRect& lastFieldRect = m_gridFrame.layout.fieldRowRects.back(); - buttonTop = lastFieldRect.y + lastFieldRect.height + kAddComponentButtonTopGap; - } else if (!m_gridFrame.layout.sectionHeaderRects.empty()) { - const UIRect& lastSectionRect = m_gridFrame.layout.sectionHeaderRects.back(); - buttonTop = lastSectionRect.y + lastSectionRect.height + kAddComponentButtonTopGap; + return UIRect( + contentBounds.x, + ResolveAddComponentButtonTop(gridLayout, contentBounds), + contentBounds.width, + kAddComponentButtonHeight); +} + +float InspectorPanel::MeasureScrollableContentHeight( + const UIRect& contentBounds) const { + float contentBottom = contentBounds.y; + Widgets::UIEditorPropertyGridLayout layout = {}; + if (!m_presentation.sections.empty()) { + layout = Widgets::BuildUIEditorPropertyGridLayout( + contentBounds, + m_presentation.sections, + m_sectionExpansion, + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); + if (!layout.fieldRowRects.empty()) { + const UIRect& lastFieldRect = layout.fieldRowRects.back(); + contentBottom = lastFieldRect.y + lastFieldRect.height; + } else if (!layout.sectionHeaderRects.empty()) { + const UIRect& lastSectionRect = layout.sectionHeaderRects.back(); + contentBottom = lastSectionRect.y + lastSectionRect.height; + } } - const float maxVisibleTop = - m_bounds.y + m_bounds.height - bottomPadding - kAddComponentButtonHeight; - buttonTop = (std::min)(buttonTop, maxVisibleTop); - return UIRect( - m_bounds.x + horizontalPadding, - buttonTop, - (std::max)(m_bounds.width - horizontalPadding * 2.0f, 0.0f), - kAddComponentButtonHeight); + if (ShouldShowAddComponentButton()) { + const UIRect buttonRect = BuildAddComponentButtonRect(layout, contentBounds); + contentBottom = (std::max)(contentBottom, buttonRect.y + buttonRect.height); + } + + return (std::max)(contentBottom - contentBounds.y, 0.0f); +} + +void InspectorPanel::RebuildScrollableLayout() { + m_scrollFrame = {}; + m_gridFrame.layout = {}; + + const UIRect viewportBounds = BuildScrollViewportBounds(); + if (viewportBounds.width <= 0.0f || viewportBounds.height <= 0.0f) { + m_scrollVerticalOffset = 0.0f; + return; + } + + const Widgets::UIEditorScrollViewMetrics& scrollMetrics = + ::XCEngine::UI::Editor::ResolveUIEditorScrollViewMetrics(); + const float contentHeight = MeasureScrollableContentHeight(viewportBounds); + m_scrollVerticalOffset = Widgets::ClampUIEditorScrollViewOffset( + viewportBounds, + contentHeight, + m_scrollVerticalOffset, + scrollMetrics); + m_scrollFrame.layout = Widgets::BuildUIEditorScrollViewLayout( + viewportBounds, + contentHeight, + m_scrollVerticalOffset, + scrollMetrics); + m_scrollFrame.result.verticalOffset = m_scrollVerticalOffset; + + if (!m_presentation.sections.empty()) { + const Widgets::UIEditorPropertyGridLayout unscrolledLayout = + Widgets::BuildUIEditorPropertyGridLayout( + m_scrollFrame.layout.contentRect, + m_presentation.sections, + m_sectionExpansion, + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); + m_gridFrame.layout = + TranslatePropertyGridLayoutForScroll(unscrolledLayout, m_scrollVerticalOffset); + } } const InspectorPresentationComponentBinding* InspectorPanel::FindSelectedComponentBinding() const { @@ -473,7 +615,8 @@ void InspectorPanel::UpdateAddComponentButton( return; } - const UIRect buttonRect = BuildAddComponentButtonRect(); + const UIRect buttonRect = + BuildAddComponentButtonRect(m_gridFrame.layout, m_scrollFrame.layout.contentRect); if (buttonRect.width <= 0.0f || buttonRect.height <= 0.0f) { ResetAddComponentButtonState(); return; @@ -603,14 +746,17 @@ void InspectorPanel::Update( inputEvents, UIEditorPanelInputFilterOptions{ .allowPointerInBounds = dispatchEntry.allowInteraction, - .allowPointerWhileCaptured = false, + .allowPointerWhileCaptured = HasActivePointerCapture(), .allowKeyboardInput = dispatchEntry.focused, .allowFocusEvents = dispatchEntry.focused || + HasActivePointerCapture() || dispatchEntry.focusGained || dispatchEntry.focusLost, .includePointerLeave = - dispatchEntry.allowInteraction || dispatchEntry.focused + dispatchEntry.allowInteraction || + dispatchEntry.focused || + HasActivePointerCapture() }, dispatchEntry.focusGained, dispatchEntry.focusLost); @@ -620,31 +766,61 @@ void InspectorPanel::Update( filteredEvents, m_bounds, dispatchEntry.allowInteraction); - m_gridFrame = {}; - if (!m_presentation.sections.empty()) { - m_gridFrame = UpdateUIEditorPropertyGridInteraction( + + RebuildScrollableLayout(); + const UIRect scrollViewportBounds = BuildScrollViewportBounds(); + if (scrollViewportBounds.width > 0.0f && scrollViewportBounds.height > 0.0f) { + m_scrollInteractionState.scrollViewState.focused = + m_interactionState.propertyGridState.focused; + m_scrollFrame = UpdateUIEditorScrollViewInteraction( + m_scrollInteractionState, + m_scrollVerticalOffset, + scrollViewportBounds, + MeasureScrollableContentHeight(scrollViewportBounds), + filteredEvents, + ::XCEngine::UI::Editor::ResolveUIEditorScrollViewMetrics()); + if (m_scrollFrame.result.focusChanged) { + m_interactionState.propertyGridState.focused = + m_scrollInteractionState.scrollViewState.focused; + } + } else { + m_scrollFrame = {}; + m_scrollVerticalOffset = 0.0f; + } + + m_gridFrame.result = {}; + if (!m_presentation.sections.empty() && + m_scrollFrame.layout.contentRect.width > 0.0f && + m_scrollFrame.layout.contentRect.height > 0.0f) { + const std::vector gridEvents = + BuildScrolledPropertyGridInputEvents(filteredEvents, m_scrollVerticalOffset); + const UIEditorPropertyGridInteractionFrame interactionFrame = + UpdateUIEditorPropertyGridInteraction( m_interactionState, m_fieldSelection, m_sectionExpansion, m_propertyEditModel, - BuildGridBounds(), + m_scrollFrame.layout.contentRect, m_presentation.sections, - filteredEvents, + gridEvents, ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); - if (m_gridFrame.result.pickerRequested && - !m_gridFrame.result.requestedFieldId.empty()) { - RequestColorPicker(context, m_gridFrame.result.requestedFieldId); + m_gridFrame.result = interactionFrame.result; + if (interactionFrame.result.pickerRequested && + !interactionFrame.result.requestedFieldId.empty()) { + RequestColorPicker(context, interactionFrame.result.requestedFieldId); } - if (m_gridFrame.result.fieldValueChanged && - !m_gridFrame.result.changedFieldId.empty()) { - if (ApplyChangedField(m_gridFrame.result.changedFieldId)) { + if (interactionFrame.result.fieldValueChanged && + !interactionFrame.result.changedFieldId.empty()) { + if (ApplyChangedField(interactionFrame.result.changedFieldId)) { RefreshPresentation(context, false); } else { ForceResyncPresentation(context); } } } + + RebuildScrollableLayout(); UpdateAddComponentButton(context, filteredEvents); } @@ -682,50 +858,69 @@ void InspectorPanel::Append(UIDrawList& drawList) const { kSubtitleFontSize); } + if (m_scrollFrame.layout.bounds.width <= 0.0f || + m_scrollFrame.layout.bounds.height <= 0.0f) { + return; + } + + Widgets::AppendUIEditorScrollViewBackground( + drawList, + m_scrollFrame.layout, + m_scrollInteractionState.scrollViewState, + BuildInspectorScrollPalette(), + ::XCEngine::UI::Editor::ResolveUIEditorScrollViewMetrics()); + + drawList.PushClipRect(m_scrollFrame.layout.contentRect); if (!m_presentation.sections.empty()) { - Widgets::AppendUIEditorPropertyGrid( + Widgets::AppendUIEditorPropertyGridBackground( drawList, - BuildGridBounds(), + m_gridFrame.layout, m_presentation.sections, m_fieldSelection, - m_sectionExpansion, m_propertyEditModel, m_interactionState.propertyGridState, ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(), ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); + Widgets::AppendUIEditorPropertyGridForeground( + drawList, + m_gridFrame.layout, + m_presentation.sections, + m_fieldSelection, + m_interactionState.propertyGridState, + m_propertyEditModel, + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(), + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); } - if (!ShouldShowAddComponentButton()) { - return; + if (ShouldShowAddComponentButton()) { + const UIRect buttonRect = + BuildAddComponentButtonRect(m_gridFrame.layout, m_scrollFrame.layout.contentRect); + if (buttonRect.width > 0.0f && buttonRect.height > 0.0f) { + const Widgets::UIEditorPropertyGridPalette& palette = + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(); + const Widgets::UIEditorPropertyGridMetrics& metrics = + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics(); + drawList.AddFilledRect( + buttonRect, + ResolveAddComponentButtonFillColor( + m_addComponentButtonHovered, + m_addComponentButtonPressed), + metrics.valueBoxRounding); + drawList.AddRectOutline( + buttonRect, + palette.valueBoxBorderColor, + metrics.borderThickness, + metrics.valueBoxRounding); + drawList.AddText( + UIPoint( + ResolveCenteredTextX(buttonRect, "Add Component", kAddComponentButtonFontSize), + ResolveTextTop(buttonRect.y, buttonRect.height, kAddComponentButtonFontSize)), + "Add Component", + palette.valueTextColor, + kAddComponentButtonFontSize); + } } - - const UIRect buttonRect = BuildAddComponentButtonRect(); - if (buttonRect.width <= 0.0f || buttonRect.height <= 0.0f) { - return; - } - - const Widgets::UIEditorPropertyGridPalette& palette = - ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(); - const Widgets::UIEditorPropertyGridMetrics& metrics = - ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics(); - drawList.AddFilledRect( - buttonRect, - ResolveAddComponentButtonFillColor( - m_addComponentButtonHovered, - m_addComponentButtonPressed), - metrics.valueBoxRounding); - drawList.AddRectOutline( - buttonRect, - palette.valueBoxBorderColor, - metrics.borderThickness, - metrics.valueBoxRounding); - drawList.AddText( - UIPoint( - ResolveCenteredTextX(buttonRect, "Add Component", kAddComponentButtonFontSize), - ResolveTextTop(buttonRect.y, buttonRect.height, kAddComponentButtonFontSize)), - "Add Component", - palette.valueTextColor, - kAddComponentButtonFontSize); + drawList.PopClipRect(); } UIEditorHostCommandEvaluationResult InspectorPanel::EvaluateEditCommand( diff --git a/new_editor/app/Features/Inspector/InspectorPanel.h b/new_editor/app/Features/Inspector/InspectorPanel.h index 09264835..a32eacea 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.h +++ b/new_editor/app/Features/Inspector/InspectorPanel.h @@ -4,6 +4,7 @@ #include "Features/Inspector/InspectorSubject.h" #include "Commands/EditorEditCommandRoute.h" +#include #include #include @@ -40,13 +41,19 @@ public: private: void ResetPanelState(); void ResetInteractionState(); + bool HasActivePointerCapture() const; void SyncExpansionState(bool subjectChanged); void SyncSelectionState(); std::string BuildSubjectKey() const; float ResolveHeaderHeight() const; bool ShouldShowAddComponentButton() const; - ::XCEngine::UI::UIRect BuildGridBounds() const; - ::XCEngine::UI::UIRect BuildAddComponentButtonRect() const; + ::XCEngine::UI::UIRect BuildScrollViewportBounds() const; + ::XCEngine::UI::UIRect BuildAddComponentButtonRect( + const Widgets::UIEditorPropertyGridLayout& gridLayout, + const ::XCEngine::UI::UIRect& contentBounds) const; + float MeasureScrollableContentHeight( + const ::XCEngine::UI::UIRect& contentBounds) const; + void RebuildScrollableLayout(); const InspectorPresentationComponentBinding* FindSelectedComponentBinding() const; const Widgets::UIEditorPropertyGridField* FindField(std::string_view fieldId) const; Widgets::UIEditorPropertyGridField* FindMutableField(std::string_view fieldId); @@ -72,6 +79,9 @@ private: ::XCEngine::UI::Widgets::UISelectionModel m_fieldSelection = {}; ::XCEngine::UI::Widgets::UIExpansionModel m_sectionExpansion = {}; ::XCEngine::UI::Widgets::UIPropertyEditModel m_propertyEditModel = {}; + UIEditorScrollViewInteractionState m_scrollInteractionState = {}; + UIEditorScrollViewInteractionFrame m_scrollFrame = {}; + float m_scrollVerticalOffset = 0.0f; UIEditorPropertyGridInteractionState m_interactionState = {}; UIEditorPropertyGridInteractionFrame m_gridFrame = {}; std::unordered_set m_knownSectionIds = {}; diff --git a/new_editor/include/XCEditor/Collections/UIEditorScrollViewInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorScrollViewInteraction.h index 4efb614c..5d652d1d 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorScrollViewInteraction.h +++ b/new_editor/include/XCEditor/Collections/UIEditorScrollViewInteraction.h @@ -31,6 +31,11 @@ struct UIEditorScrollViewInteractionFrame { UIEditorScrollViewInteractionResult result = {}; }; +inline bool HasActiveUIEditorScrollViewPointerCapture( + const UIEditorScrollViewInteractionState& state) { + return state.scrollViewState.draggingScrollbarThumb; +} + UIEditorScrollViewInteractionFrame UpdateUIEditorScrollViewInteraction( UIEditorScrollViewInteractionState& state, float& verticalOffset, diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h index d423c9f8..5b421845 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h @@ -48,6 +48,11 @@ struct UIEditorTreeViewInteractionFrame { UIEditorTreeViewInteractionResult result = {}; }; +inline bool HasActiveUIEditorTreeViewPointerCapture( + const UIEditorTreeViewInteractionState& state) { + return HasActiveUIEditorScrollViewPointerCapture(state.scrollViewInteractionState); +} + std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorHostedTreeViewInputEvents( const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, diff --git a/new_editor/src/Collections/UIEditorScrollViewInteraction.cpp b/new_editor/src/Collections/UIEditorScrollViewInteraction.cpp index 3efd330a..2cbd0fcf 100644 --- a/new_editor/src/Collections/UIEditorScrollViewInteraction.cpp +++ b/new_editor/src/Collections/UIEditorScrollViewInteraction.cpp @@ -66,6 +66,20 @@ float ResolveThumbDragOffset( return state.thumbDragStartOffset + offsetDelta; } +bool ShouldReleaseThumbDragForEvent(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + return !event.modifiers.leftMouse; + default: + return false; + } +} + } // namespace UIEditorScrollViewInteractionFrame UpdateUIEditorScrollViewInteraction( @@ -90,6 +104,12 @@ UIEditorScrollViewInteractionFrame UpdateUIEditorScrollViewInteraction( } UIEditorScrollViewInteractionResult eventResult = {}; + if (state.scrollViewState.draggingScrollbarThumb && + ShouldReleaseThumbDragForEvent(event)) { + state.scrollViewState.draggingScrollbarThumb = false; + eventResult.endedThumbDrag = true; + } + switch (event.type) { case UIInputEventType::FocusGained: { const bool wasFocused = state.scrollViewState.focused;