diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 17471026..5679257d 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -67,6 +67,7 @@ set(XCUI_EDITOR_COLLECTION_SOURCES src/Collections/UIEditorListViewInteraction.cpp src/Collections/UIEditorScrollView.cpp src/Collections/UIEditorScrollViewInteraction.cpp + src/Collections/UIEditorTreePanelBehavior.cpp src/Collections/UIEditorTabStrip.cpp src/Collections/UIEditorTabStripInteraction.cpp src/Collections/UIEditorTreeView.cpp diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index a50afeb5..18cc8407 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -2,6 +2,7 @@ #include "Scene/EditorSceneRuntime.h" +#include #include #include @@ -321,28 +322,15 @@ void HierarchyPanel::SyncTreeFocusState( UIRect HierarchyPanel::BuildRenameBounds( std::string_view itemId, const Widgets::UIEditorTreeViewLayout& layout) const { - if (itemId.empty()) { - return {}; - } - - const std::size_t visibleIndex = - FindVisibleIndexForItemId(layout, m_treeItems, itemId); - if (visibleIndex == UIEditorTreeViewInvalidIndex || - visibleIndex >= layout.rowRects.size() || - visibleIndex >= layout.labelRects.size()) { - return {}; - } - const Widgets::UIEditorTextFieldMetrics hostedMetrics = BuildUIEditorPropertyGridTextFieldMetrics( ResolveUIEditorPropertyGridMetrics(), ResolveUIEditorTextFieldMetrics()); - const UIRect& rowRect = layout.rowRects[visibleIndex]; - const UIRect& labelRect = layout.labelRects[visibleIndex]; - const float x = (std::max)(rowRect.x, labelRect.x - hostedMetrics.valueTextInsetX); - const float right = rowRect.x + rowRect.width - 8.0f; - const float width = (std::max)(120.0f, right - x); - return UIRect(x, rowRect.y, width, rowRect.height); + return BuildUIEditorTreePanelInlineRenameBounds( + layout, + m_treeItems, + itemId, + hostedMetrics); } bool HierarchyPanel::WantsHostPointerCapture() const { @@ -464,12 +452,14 @@ void HierarchyPanel::ProcessDragAndFrameEvents( const UIRect& bounds, bool allowInteraction, bool panelActive) { - const std::vector filteredEvents = FilterHierarchyInputEvents( + const std::vector filteredEvents = FilterUIEditorTreePanelInputEvents( bounds, inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); + UIEditorTreePanelInputFilterOptions{ + .allowInteraction = allowInteraction, + .panelActive = panelActive, + .captureActive = HasActivePointerCapture() + }); if (m_treeFrame.result.selectionChanged) { SyncSceneRuntimeSelectionFromTree(); @@ -565,12 +555,14 @@ void HierarchyPanel::Update( } m_visible = true; - const std::vector filteredEvents = FilterHierarchyInputEvents( + const std::vector filteredEvents = FilterUIEditorTreePanelInputEvents( panelState->bounds, inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); + UIEditorTreePanelInputFilterOptions{ + .allowInteraction = allowInteraction, + .panelActive = panelActive, + .captureActive = HasActivePointerCapture() + }); SyncTreeFocusState(filteredEvents); const Widgets::UIEditorTreeViewMetrics treeMetrics = @@ -591,11 +583,12 @@ void HierarchyPanel::Update( } const std::vector interactionEvents = - TreeDrag::BuildInteractionInputEvents( + BuildUIEditorTreePanelInteractionInputEvents( m_dragState, layout, m_treeItems, filteredEvents, + false, kDragThreshold); m_treeFrame = UpdateUIEditorTreeViewInteraction( m_treeInteractionState, @@ -655,18 +648,10 @@ void HierarchyPanel::Append(UIDrawList& drawList) const { BuildUIEditorPropertyGridTextFieldMetrics( ResolveUIEditorPropertyGridMetrics(), ResolveUIEditorTextFieldMetrics())); - Widgets::AppendUIEditorTextFieldBackground( + AppendUIEditorInlineRenameSession( drawList, - m_renameFrame.layout, - m_renameState.textFieldSpec, - m_renameState.textFieldInteraction.textFieldState, - textFieldPalette, - textFieldMetrics); - Widgets::AppendUIEditorTextFieldForeground( - drawList, - m_renameFrame.layout, - m_renameState.textFieldSpec, - m_renameState.textFieldInteraction.textFieldState, + m_renameFrame, + m_renameState, textFieldPalette, textFieldMetrics); } @@ -684,11 +669,11 @@ void HierarchyPanel::Append(UIDrawList& drawList) const { return; } - const std::size_t visibleIndex = TreeDrag::FindVisibleIndexForItemId( + const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex( m_treeFrame.layout, m_treeItems, m_dragState.dropTargetItemId); - if (visibleIndex == UIEditorTreeViewInvalidIndex || + if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex || visibleIndex >= m_treeFrame.layout.rowRects.size()) { return; } diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.cpp index a287c16b..0049b971 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.cpp @@ -1,106 +1,11 @@ #include "HierarchyPanelInternal.h" -#include - namespace XCEngine::UI::Editor::App::HierarchyPanelInternal { -using ::XCEngine::UI::UIInputEventType; -using Widgets::HitTestUIEditorTreeView; - -bool ContainsPoint(const UIRect& rect, const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - -float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) { - const float dx = lhs.x - rhs.x; - const float dy = lhs.y - rhs.y; - return dx * dx + dy * dy; -} - ::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons) { return icons != nullptr ? icons->Resolve(BuiltInIconKind::GameObject) : ::XCEngine::UI::UITextureHandle {}; } -std::vector FilterHierarchyInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive, - bool captureActive) { - if (!allowInteraction && !captureActive) { - return {}; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - if (captureActive || ContainsPoint(bounds, event.position)) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::PointerLeave: - filteredEvents.push_back(event); - break; - case UIInputEventType::FocusGained: - case UIInputEventType::FocusLost: - if (panelActive || captureActive) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::KeyDown: - case UIInputEventType::KeyUp: - case UIInputEventType::Character: - if (panelActive) { - filteredEvents.push_back(event); - } - break; - default: - break; - } - } - - return filteredEvents; -} - -const Widgets::UIEditorTreeViewItem* ResolveHitItem( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const UIPoint& point, - UIEditorTreeViewHitTarget* hitTargetOutput) { - const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point); - if (hitTargetOutput != nullptr) { - *hitTargetOutput = hitTarget; - } - - if (hitTarget.itemIndex >= items.size()) { - return nullptr; - } - - return &items[hitTarget.itemIndex]; -} - -std::size_t FindVisibleIndexForItemId( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId) { - for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) { - const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; - if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { - return visibleIndex; - } - } - - return UIEditorTreeViewInvalidIndex; -} - } // namespace XCEngine::UI::Editor::App::HierarchyPanelInternal diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h index cae353d9..8a42684e 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h @@ -5,42 +5,15 @@ #include "Rendering/Assets/BuiltInIcons.h" #include "Composition/EditorPanelIds.h" -#include #include -#include -#include - namespace XCEngine::UI::Editor::App::HierarchyPanelInternal { using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; -using Widgets::UIEditorTreeViewHitTarget; -using Widgets::UIEditorTreeViewHitTargetKind; -using Widgets::UIEditorTreeViewInvalidIndex; inline constexpr float kDragThreshold = 4.0f; inline constexpr UIColor kDragPreviewColor(0.92f, 0.92f, 0.92f, 0.42f); -bool ContainsPoint(const UIRect& rect, const UIPoint& point); -float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs); ::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons); -std::vector FilterHierarchyInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive, - bool captureActive); -const Widgets::UIEditorTreeViewItem* ResolveHitItem( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const UIPoint& point, - UIEditorTreeViewHitTarget* hitTargetOutput = nullptr); -std::size_t FindVisibleIndexForItemId( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId); } // namespace XCEngine::UI::Editor::App::HierarchyPanelInternal diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index 41fbb1ea..0ad32788 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -2,6 +2,7 @@ #include "Composition/EditorPanelIds.h" #include +#include #include #include "Features/Inspector/Components/IInspectorComponentEditor.h" @@ -18,7 +19,6 @@ namespace { using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; @@ -32,63 +32,11 @@ 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); -bool ContainsPoint(const UIRect& rect, const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - float ResolveTextTop(float rectY, float rectHeight, float fontSize) { const float lineHeight = fontSize * 1.6f; return rectY + std::floor((rectHeight - lineHeight) * 0.5f); } -std::vector FilterInspectorInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { - if (!allowInteraction && !panelActive) { - return {}; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - if (allowInteraction && ContainsPoint(bounds, event.position)) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::PointerLeave: - filteredEvents.push_back(event); - break; - case UIInputEventType::FocusGained: - case UIInputEventType::FocusLost: - if (panelActive) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::KeyDown: - case UIInputEventType::KeyUp: - case UIInputEventType::Character: - if (panelActive) { - filteredEvents.push_back(event); - } - break; - default: - break; - } - } - - return filteredEvents; -} - UIEditorHostCommandEvaluationResult BuildEvaluationResult( bool executable, std::string message) { @@ -317,11 +265,16 @@ void InspectorPanel::Update( } const std::vector filteredEvents = - FilterInspectorInputEvents( + FilterUIEditorPanelInputEvents( m_bounds, inputEvents, - allowInteraction, - panelActive); + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = allowInteraction, + .allowPointerWhileCaptured = false, + .allowKeyboardInput = panelActive, + .allowFocusEvents = panelActive, + .includePointerLeave = allowInteraction || panelActive + }); m_gridFrame = UpdateUIEditorPropertyGridInteraction( m_interactionState, m_fieldSelection, diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index 70003877..c9b914a6 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -2,7 +2,9 @@ #include "Project/EditorProjectRuntime.h" +#include #include +#include #include #include "Internal/StringEncoding.h" @@ -358,22 +360,11 @@ UIRect ProjectPanel::BuildRenameBounds( ResolveUIEditorTextFieldMetrics()); if (surface == RenameSurface::Tree) { - const std::size_t visibleIndex = TreeDrag::FindVisibleIndexForItemId( + return BuildUIEditorTreePanelInlineRenameBounds( m_treeFrame.layout, GetBrowserModel().GetTreeItems(), - itemId); - if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex || - visibleIndex >= m_treeFrame.layout.rowRects.size() || - visibleIndex >= m_treeFrame.layout.labelRects.size()) { - return {}; - } - - const UIRect& rowRect = m_treeFrame.layout.rowRects[visibleIndex]; - const UIRect& labelRect = m_treeFrame.layout.labelRects[visibleIndex]; - const float x = (std::max)(rowRect.x, labelRect.x - hostedMetrics.valueTextInsetX); - const float right = rowRect.x + rowRect.width - 8.0f; - const float width = (std::max)(120.0f, right - x); - return UIRect(x, rowRect.y, width, rowRect.height); + itemId, + hostedMetrics); } if (surface == RenameSurface::Grid) { @@ -962,14 +953,16 @@ std::vector ProjectPanel::BuildTreeInteractionInputEvents( bool allowInteraction, bool panelActive) const { const std::vector rawEvents = - FilterProjectPanelInputEvents( + FilterUIEditorPanelInputEvents( bounds, inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); - const std::vector treeRawEvents = - FilterTreeInputEvents(rawEvents, m_splitterDragging || m_assetDragState.dragging); + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = allowInteraction, + .allowPointerWhileCaptured = HasActivePointerCapture(), + .allowKeyboardInput = panelActive, + .allowFocusEvents = panelActive || HasActivePointerCapture(), + .includePointerLeave = allowInteraction || HasActivePointerCapture() + }); const Widgets::UIEditorTreeViewLayout layout = m_treeFrame.layout.bounds.width > 0.0f @@ -979,11 +972,12 @@ std::vector ProjectPanel::BuildTreeInteractionInputEvents( GetBrowserModel().GetTreeItems(), m_folderExpansion, ResolveUIEditorTreeViewMetrics()); - return TreeDrag::BuildInteractionInputEvents( + return BuildUIEditorTreePanelInteractionInputEvents( m_treeDragState, layout, GetBrowserModel().GetTreeItems(), - treeRawEvents); + rawEvents, + m_splitterDragging || m_assetDragState.dragging); } UIEditorHostCommandEvaluationResult ProjectPanel::EvaluateAssetCommand( @@ -1360,12 +1354,16 @@ void ProjectPanel::Update( m_visible = true; SyncAssetSelectionFromRuntime(); const std::vector filteredEvents = - FilterProjectPanelInputEvents( + FilterUIEditorPanelInputEvents( panelState->bounds, inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = allowInteraction, + .allowPointerWhileCaptured = HasActivePointerCapture(), + .allowKeyboardInput = panelActive, + .allowFocusEvents = panelActive || HasActivePointerCapture(), + .includePointerLeave = allowInteraction || HasActivePointerCapture() + }); m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); m_layout = BuildLayout(panelState->bounds); @@ -1482,7 +1480,9 @@ void ProjectPanel::Update( m_treeDragState, m_treeFrame.layout, GetBrowserModel().GetTreeItems(), - FilterTreeInputEvents(filteredEvents, m_splitterDragging || m_assetDragState.dragging), + FilterUIEditorTreePanelPointerInputEvents( + filteredEvents, + m_splitterDragging || m_assetDragState.dragging), m_layout.treeRect, treeDragCallbacks); if (treeDragResult.dropCommitted) { @@ -2041,7 +2041,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const { 1.0f, 0.0f); } else { - const std::size_t visibleIndex = TreeDrag::FindVisibleIndexForItemId( + const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex( m_treeFrame.layout, GetBrowserModel().GetTreeItems(), m_treeDragState.dropTargetItemId); @@ -2059,7 +2059,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const { if (m_assetDragState.dragging && m_assetDragState.validDropTarget && m_assetDropTargetSurface == DropTargetSurface::Tree) { - const std::size_t visibleIndex = TreeDrag::FindVisibleIndexForItemId( + const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex( m_treeFrame.layout, GetBrowserModel().GetTreeItems(), m_assetDragState.dropTargetItemId); @@ -2157,18 +2157,10 @@ void ProjectPanel::Append(UIDrawList& drawList) const { BuildUIEditorPropertyGridTextFieldMetrics( ResolveUIEditorPropertyGridMetrics(), ResolveUIEditorTextFieldMetrics())); - Widgets::AppendUIEditorTextFieldBackground( + AppendUIEditorInlineRenameSession( drawList, - m_renameFrame.layout, - m_renameState.textFieldSpec, - m_renameState.textFieldInteraction.textFieldState, - textFieldPalette, - textFieldMetrics); - Widgets::AppendUIEditorTextFieldForeground( - drawList, - m_renameFrame.layout, - m_renameState.textFieldSpec, - m_renameState.textFieldInteraction.textFieldState, + m_renameFrame, + m_renameState, textFieldPalette, textFieldMetrics); } diff --git a/new_editor/app/Features/Project/ProjectPanelInternal.cpp b/new_editor/app/Features/Project/ProjectPanelInternal.cpp index 35562ebf..d8c29782 100644 --- a/new_editor/app/Features/Project/ProjectPanelInternal.cpp +++ b/new_editor/app/Features/Project/ProjectPanelInternal.cpp @@ -39,77 +39,6 @@ float MeasureTextWidth( return static_cast(text.size()) * fontSize * 0.56f; } -std::vector FilterProjectPanelInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive, - bool captureActive) { - if (!allowInteraction && !captureActive) { - return {}; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - if (captureActive || ContainsPoint(bounds, event.position)) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::PointerLeave: - filteredEvents.push_back(event); - break; - case UIInputEventType::FocusGained: - case UIInputEventType::FocusLost: - if (panelActive || captureActive) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::KeyDown: - case UIInputEventType::KeyUp: - case UIInputEventType::Character: - if (panelActive) { - filteredEvents.push_back(event); - } - break; - default: - break; - } - } - - return filteredEvents; -} - -std::vector FilterTreeInputEvents( - const std::vector& inputEvents, - bool suppressPointerInput) { - if (!suppressPointerInput) { - return inputEvents; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - case UIInputEventType::PointerEnter: - break; - default: - filteredEvents.push_back(event); - break; - } - } - return filteredEvents; -} - ::XCEngine::UI::UITextureHandle ResolveFolderIcon(const BuiltInIcons* icons) { return icons != nullptr ? icons->Resolve(BuiltInIconKind::Folder) diff --git a/new_editor/app/Features/Project/ProjectPanelInternal.h b/new_editor/app/Features/Project/ProjectPanelInternal.h index b894a8a3..89169a0b 100644 --- a/new_editor/app/Features/Project/ProjectPanelInternal.h +++ b/new_editor/app/Features/Project/ProjectPanelInternal.h @@ -64,15 +64,6 @@ float MeasureTextWidth( const UIEditorTextMeasurer* textMeasurer, std::string_view text, float fontSize); -std::vector FilterProjectPanelInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive, - bool captureActive); -std::vector FilterTreeInputEvents( - const std::vector& inputEvents, - bool suppressPointerInput); ::XCEngine::UI::UITextureHandle ResolveFolderIcon(const BuiltInIcons* icons); float ClampNavigationWidth(float value, float totalWidth); void AppendTilePreview( diff --git a/new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h b/new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h index 93846975..4472582d 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h +++ b/new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h @@ -45,6 +45,13 @@ Widgets::UIEditorTextFieldMetrics BuildUIEditorInlineRenameTextFieldMetrics( const ::XCEngine::UI::UIRect& bounds, const Widgets::UIEditorTextFieldMetrics& metrics = {}); +void AppendUIEditorInlineRenameSession( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorInlineRenameSessionFrame& frame, + const UIEditorInlineRenameSessionState& state, + const Widgets::UIEditorTextFieldPalette& palette = {}, + const Widgets::UIEditorTextFieldMetrics& metrics = {}); + UIEditorInlineRenameSessionFrame UpdateUIEditorInlineRenameSession( UIEditorInlineRenameSessionState& state, const UIEditorInlineRenameSessionRequest& request, diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h b/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h new file mode 100644 index 00000000..b99d5c19 --- /dev/null +++ b/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorTreePanelInputFilterOptions { + bool allowInteraction = false; + bool panelActive = false; + bool captureActive = false; +}; + +std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorTreePanelInputEvents( + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const UIEditorTreePanelInputFilterOptions& options); + +std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorTreePanelPointerInputEvents( + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + bool suppressPointerInput); + +std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorTreePanelInteractionInputEvents( + const Collections::TreeDragDrop::State& dragState, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + bool suppressPointerInput = false, + float dragThreshold = Collections::TreeDragDrop::kDefaultDragThreshold); + +std::size_t FindUIEditorTreePanelVisibleItemIndex( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId); + +::XCEngine::UI::UIRect BuildUIEditorTreePanelInlineRenameBounds( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId, + const Widgets::UIEditorTextFieldMetrics& hostedMetrics, + float minWidth = 120.0f, + float trailingPadding = 8.0f); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h b/new_editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h new file mode 100644 index 00000000..cd109369 --- /dev/null +++ b/new_editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorPanelInputFilterOptions { + bool allowPointerInBounds = false; + bool allowPointerWhileCaptured = false; + bool allowKeyboardInput = false; + bool allowFocusEvents = false; + bool includePointerLeave = false; +}; + +inline std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorPanelInputEvents( + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const UIEditorPanelInputFilterOptions& options) { + using ::XCEngine::UI::UIInputEvent; + using ::XCEngine::UI::UIInputEventType; + + if (!options.allowPointerInBounds && + !options.allowPointerWhileCaptured && + !options.allowKeyboardInput && + !options.allowFocusEvents && + !options.includePointerLeave) { + return {}; + } + + auto containsPoint = [&bounds](const ::XCEngine::UI::UIPoint& point) { + return point.x >= bounds.x && + point.x <= bounds.x + bounds.width && + point.y >= bounds.y && + point.y <= bounds.y + bounds.height; + }; + + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size()); + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + if (options.allowPointerWhileCaptured || + (options.allowPointerInBounds && containsPoint(event.position))) { + filteredEvents.push_back(event); + } + break; + + case UIInputEventType::PointerLeave: + if (options.includePointerLeave) { + filteredEvents.push_back(event); + } + break; + + case UIInputEventType::FocusGained: + case UIInputEventType::FocusLost: + if (options.allowFocusEvents) { + filteredEvents.push_back(event); + } + break; + + case UIInputEventType::KeyDown: + case UIInputEventType::KeyUp: + case UIInputEventType::Character: + if (options.allowKeyboardInput) { + filteredEvents.push_back(event); + } + break; + + default: + break; + } + } + + return filteredEvents; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Collections/UIEditorInlineRenameSession.cpp b/new_editor/src/Collections/UIEditorInlineRenameSession.cpp index e7792774..0a022978 100644 --- a/new_editor/src/Collections/UIEditorInlineRenameSession.cpp +++ b/new_editor/src/Collections/UIEditorInlineRenameSession.cpp @@ -17,6 +17,32 @@ Widgets::UIEditorTextFieldMetrics BuildUIEditorInlineRenameTextFieldMetrics( return resolved; } +void AppendUIEditorInlineRenameSession( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorInlineRenameSessionFrame& frame, + const UIEditorInlineRenameSessionState& state, + const Widgets::UIEditorTextFieldPalette& palette, + const Widgets::UIEditorTextFieldMetrics& metrics) { + if (!state.active) { + return; + } + + Widgets::AppendUIEditorTextFieldBackground( + drawList, + frame.layout, + state.textFieldSpec, + state.textFieldInteraction.textFieldState, + palette, + metrics); + Widgets::AppendUIEditorTextFieldForeground( + drawList, + frame.layout, + state.textFieldSpec, + state.textFieldInteraction.textFieldState, + palette, + metrics); +} + namespace { void ResetSession(UIEditorInlineRenameSessionState& state) { diff --git a/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp b/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp new file mode 100644 index 00000000..b0878292 --- /dev/null +++ b/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp @@ -0,0 +1,126 @@ +#include +#include + +#include + +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +bool ContainsPoint(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +} // namespace + +std::vector FilterUIEditorTreePanelInputEvents( + const UIRect& bounds, + const std::vector& inputEvents, + const UIEditorTreePanelInputFilterOptions& options) { + return FilterUIEditorPanelInputEvents( + bounds, + inputEvents, + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = options.allowInteraction, + .allowPointerWhileCaptured = options.captureActive, + .allowKeyboardInput = options.panelActive, + .allowFocusEvents = options.panelActive || options.captureActive, + .includePointerLeave = options.allowInteraction || options.captureActive + }); +} + +std::vector FilterUIEditorTreePanelPointerInputEvents( + const std::vector& inputEvents, + bool suppressPointerInput) { + if (!suppressPointerInput) { + return inputEvents; + } + + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size()); + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + case UIInputEventType::PointerEnter: + break; + + default: + filteredEvents.push_back(event); + break; + } + } + + return filteredEvents; +} + +std::vector BuildUIEditorTreePanelInteractionInputEvents( + const Collections::TreeDragDrop::State& dragState, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + const std::vector& inputEvents, + bool suppressPointerInput, + float dragThreshold) { + return Collections::TreeDragDrop::BuildInteractionInputEvents( + dragState, + layout, + items, + FilterUIEditorTreePanelPointerInputEvents(inputEvents, suppressPointerInput), + dragThreshold); +} + +std::size_t FindUIEditorTreePanelVisibleItemIndex( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId) { + for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); + ++visibleIndex) { + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { + return visibleIndex; + } + } + + return Widgets::UIEditorTreeViewInvalidIndex; +} + +UIRect BuildUIEditorTreePanelInlineRenameBounds( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId, + const Widgets::UIEditorTextFieldMetrics& hostedMetrics, + float minWidth, + float trailingPadding) { + if (itemId.empty()) { + return {}; + } + + const std::size_t visibleIndex = + FindUIEditorTreePanelVisibleItemIndex(layout, items, itemId); + if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex || + visibleIndex >= layout.rowRects.size() || + visibleIndex >= layout.labelRects.size()) { + return {}; + } + + const UIRect& rowRect = layout.rowRects[visibleIndex]; + const UIRect& labelRect = layout.labelRects[visibleIndex]; + const float x = (std::max)(rowRect.x, labelRect.x - hostedMetrics.valueTextInsetX); + const float right = rowRect.x + rowRect.width - trailingPadding; + const float width = (std::max)(minWidth, right - x); + return UIRect(x, rowRect.y, width, rowRect.height); +} + +} // namespace XCEngine::UI::Editor diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index 4e03cacd..79144eae 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -9,6 +9,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES test_ui_editor_menu_popup.cpp test_ui_editor_panel_content_host.cpp test_ui_editor_panel_host_lifecycle.cpp + test_ui_editor_panel_input_filter.cpp test_ui_editor_property_grid.cpp test_ui_editor_property_grid_interaction.cpp test_ui_editor_shell_compose.cpp @@ -47,6 +48,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES test_ui_editor_status_bar.cpp test_ui_editor_tab_strip.cpp test_ui_editor_tab_strip_interaction.cpp + test_ui_editor_tree_panel_behavior.cpp test_ui_editor_tree_view.cpp test_ui_editor_tree_view_interaction.cpp test_ui_editor_viewport_input_bridge.cpp diff --git a/tests/UI/Editor/unit/test_ui_editor_inline_rename_session.cpp b/tests/UI/Editor/unit/test_ui_editor_inline_rename_session.cpp index ec6fedc6..79a94749 100644 --- a/tests/UI/Editor/unit/test_ui_editor_inline_rename_session.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_inline_rename_session.cpp @@ -7,10 +7,14 @@ namespace { using XCEngine::Input::KeyCode; +using XCEngine::UI::UIDrawList; +using XCEngine::UI::UIDrawCommandType; using XCEngine::UI::UIInputEvent; using XCEngine::UI::UIInputEventType; using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::AppendUIEditorInlineRenameSession; using XCEngine::UI::Editor::UIEditorInlineRenameSessionRequest; +using XCEngine::UI::Editor::UIEditorInlineRenameSessionFrame; using XCEngine::UI::Editor::UIEditorInlineRenameSessionState; using XCEngine::UI::Editor::UpdateUIEditorInlineRenameSession; @@ -122,3 +126,19 @@ TEST(UIEditorInlineRenameSessionTest, FocusLostCommitsEditedValue) { EXPECT_EQ(frame.result.valueAfter, "CameraX"); EXPECT_FALSE(state.active); } + +TEST(UIEditorInlineRenameSessionTest, AppendUsesActiveSessionStateToEmitTextFieldCommands) { + UIEditorInlineRenameSessionState state = {}; + UIDrawList drawList("InlineRename"); + const UIEditorInlineRenameSessionFrame inactiveFrame = {}; + + AppendUIEditorInlineRenameSession(drawList, inactiveFrame, state); + EXPECT_TRUE(drawList.Empty()); + + const UIEditorInlineRenameSessionFrame activeFrame = + UpdateUIEditorInlineRenameSession(state, MakeRequest(true), {}); + AppendUIEditorInlineRenameSession(drawList, activeFrame, state); + + ASSERT_FALSE(drawList.Empty()); + EXPECT_EQ(drawList.GetCommands().front().type, UIDrawCommandType::FilledRect); +} 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 new file mode 100644 index 00000000..a47c2205 --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp @@ -0,0 +1,95 @@ +#include + +#include + +namespace { + +using XCEngine::UI::Editor::FilterUIEditorPanelInputEvents; +using XCEngine::UI::Editor::UIEditorPanelInputFilterOptions; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::UIRect; + +UIInputEvent MakePointerMove(float x, float y) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerMove; + event.position = UIPoint(x, y); + return event; +} + +UIInputEvent MakePointerButtonDown(float x, float y) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerButtonDown; + event.position = UIPoint(x, y); + event.pointerButton = UIPointerButton::Left; + return event; +} + +UIInputEvent MakeKeyDown() { + UIInputEvent event = {}; + event.type = UIInputEventType::KeyDown; + return event; +} + +UIInputEvent MakeFocusLost() { + UIInputEvent event = {}; + event.type = UIInputEventType::FocusLost; + return event; +} + +UIInputEvent MakePointerLeave() { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerLeave; + return event; +} + +} // namespace + +TEST(UIEditorPanelInputFilterTests, FiltersPointerByBoundsAndPreservesKeyboardAndFocusChannels) { + const std::vector filtered = + FilterUIEditorPanelInputEvents( + UIRect(0.0f, 0.0f, 100.0f, 100.0f), + { + MakePointerMove(20.0f, 20.0f), + MakePointerButtonDown(140.0f, 20.0f), + MakeKeyDown(), + MakeFocusLost(), + MakePointerLeave() + }, + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = true, + .allowPointerWhileCaptured = false, + .allowKeyboardInput = true, + .allowFocusEvents = true, + .includePointerLeave = true + }); + + ASSERT_EQ(filtered.size(), 4u); + EXPECT_EQ(filtered[0].type, UIInputEventType::PointerMove); + EXPECT_EQ(filtered[1].type, UIInputEventType::KeyDown); + EXPECT_EQ(filtered[2].type, UIInputEventType::FocusLost); + EXPECT_EQ(filtered[3].type, UIInputEventType::PointerLeave); +} + +TEST(UIEditorPanelInputFilterTests, CapturedPointerEventsBypassBoundsWhenEnabled) { + const std::vector filtered = + FilterUIEditorPanelInputEvents( + UIRect(0.0f, 0.0f, 100.0f, 100.0f), + { + MakePointerMove(140.0f, 20.0f), + MakePointerButtonDown(180.0f, 32.0f) + }, + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = false, + .allowPointerWhileCaptured = true, + .allowKeyboardInput = false, + .allowFocusEvents = false, + .includePointerLeave = false + }); + + ASSERT_EQ(filtered.size(), 2u); + EXPECT_EQ(filtered[0].type, UIInputEventType::PointerMove); + EXPECT_EQ(filtered[1].type, UIInputEventType::PointerButtonDown); +} diff --git a/tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp b/tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp new file mode 100644 index 00000000..c9dcd709 --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp @@ -0,0 +1,177 @@ +#include + +#include + +#include + +#include + +namespace { + +namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop; +namespace Widgets = XCEngine::UI::Editor::Widgets; + +using XCEngine::UI::Editor::BuildUIEditorTreePanelInlineRenameBounds; +using XCEngine::UI::Editor::BuildUIEditorTreePanelInteractionInputEvents; +using XCEngine::UI::Editor::FilterUIEditorTreePanelInputEvents; +using XCEngine::UI::Editor::FilterUIEditorTreePanelPointerInputEvents; +using XCEngine::UI::Editor::FindUIEditorTreePanelVisibleItemIndex; +using XCEngine::UI::Editor::UIEditorTreePanelInputFilterOptions; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::UIRect; + +UIInputEvent MakePointerMove(float x, float y) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerMove; + event.position = UIPoint(x, y); + return event; +} + +UIInputEvent MakePointerButtonDown(float x, float y) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerButtonDown; + event.position = UIPoint(x, y); + event.pointerButton = UIPointerButton::Left; + return event; +} + +UIInputEvent MakePointerButtonUp(float x, float y) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerButtonUp; + event.position = UIPoint(x, y); + event.pointerButton = UIPointerButton::Left; + return event; +} + +UIInputEvent MakeKeyDown() { + UIInputEvent event = {}; + event.type = UIInputEventType::KeyDown; + return event; +} + +UIInputEvent MakeFocusLost() { + UIInputEvent event = {}; + event.type = UIInputEventType::FocusLost; + return event; +} + +struct TreeFixture { + std::vector items = {}; + ::XCEngine::UI::Widgets::UIExpansionModel expansion = {}; + Widgets::UIEditorTreeViewLayout layout = {}; +}; + +TreeFixture BuildTreeFixture() { + TreeFixture fixture = {}; + fixture.items = { + Widgets::UIEditorTreeViewItem{ .itemId = "root", .label = "Root" }, + Widgets::UIEditorTreeViewItem{ .itemId = "child", .label = "Child", .depth = 1u } + }; + fixture.expansion.Expand("root"); + fixture.layout = Widgets::BuildUIEditorTreeViewLayout( + UIRect(0.0f, 0.0f, 240.0f, 120.0f), + fixture.items, + fixture.expansion); + return fixture; +} + +} // namespace + +TEST(UIEditorTreePanelBehaviorTests, FilterPanelInputHonorsBoundsAndPanelActivity) { + const std::vector filtered = + FilterUIEditorTreePanelInputEvents( + UIRect(0.0f, 0.0f, 100.0f, 100.0f), + { + MakePointerMove(40.0f, 40.0f), + MakePointerMove(140.0f, 40.0f), + MakeKeyDown(), + MakeFocusLost() + }, + UIEditorTreePanelInputFilterOptions{ + .allowInteraction = true, + .panelActive = true, + .captureActive = false + }); + + ASSERT_EQ(filtered.size(), 3u); + EXPECT_EQ(filtered[0].type, UIInputEventType::PointerMove); + EXPECT_EQ(filtered[1].type, UIInputEventType::KeyDown); + EXPECT_EQ(filtered[2].type, UIInputEventType::FocusLost); +} + +TEST(UIEditorTreePanelBehaviorTests, FilterPointerInputSuppressesPointerOnlyEvents) { + const std::vector filtered = + FilterUIEditorTreePanelPointerInputEvents( + { + MakePointerMove(10.0f, 10.0f), + MakePointerButtonDown(10.0f, 10.0f), + MakeKeyDown(), + MakeFocusLost() + }, + true); + + ASSERT_EQ(filtered.size(), 2u); + EXPECT_EQ(filtered[0].type, UIInputEventType::KeyDown); + EXPECT_EQ(filtered[1].type, UIInputEventType::FocusLost); +} + +TEST(UIEditorTreePanelBehaviorTests, BuildInteractionInputEventsSuppressesDragGesturePreview) { + const TreeFixture fixture = BuildTreeFixture(); + TreeDrag::State dragState = {}; + ASSERT_GE(fixture.layout.rowRects.size(), 2u); + const UIRect sourceRow = fixture.layout.rowRects[1]; + const UIRect targetRow = fixture.layout.rowRects[0]; + + const std::vector filtered = + BuildUIEditorTreePanelInteractionInputEvents( + dragState, + fixture.layout, + fixture.items, + { + MakePointerButtonDown( + sourceRow.x + 12.0f, + sourceRow.y + sourceRow.height * 0.5f), + MakePointerMove( + sourceRow.x + 24.0f, + sourceRow.y + sourceRow.height * 0.5f), + MakePointerMove( + targetRow.x + 12.0f, + targetRow.y + targetRow.height * 0.5f), + MakePointerButtonUp( + targetRow.x + 12.0f, + targetRow.y + targetRow.height * 0.5f), + MakeFocusLost() + }); + + ASSERT_EQ(filtered.size(), 2u); + EXPECT_EQ(filtered[0].type, UIInputEventType::PointerButtonDown); + EXPECT_EQ(filtered[1].type, UIInputEventType::FocusLost); +} + +TEST(UIEditorTreePanelBehaviorTests, VisibleIndexAndRenameBoundsFollowCurrentLayout) { + const TreeFixture fixture = BuildTreeFixture(); + const Widgets::UIEditorTextFieldMetrics hostedMetrics = { + .valueTextInsetX = 8.0f + }; + + const std::size_t visibleIndex = + FindUIEditorTreePanelVisibleItemIndex(fixture.layout, fixture.items, "child"); + ASSERT_EQ(visibleIndex, 1u); + + const UIRect bounds = + BuildUIEditorTreePanelInlineRenameBounds( + fixture.layout, + fixture.items, + "child", + hostedMetrics); + const UIRect& rowRect = fixture.layout.rowRects[visibleIndex]; + const UIRect& labelRect = fixture.layout.labelRects[visibleIndex]; + + EXPECT_FLOAT_EQ(bounds.x, (std::max)(rowRect.x, labelRect.x - hostedMetrics.valueTextInsetX)); + EXPECT_FLOAT_EQ(bounds.y, rowRect.y); + EXPECT_FLOAT_EQ(bounds.height, rowRect.height); + EXPECT_FLOAT_EQ(bounds.width, (std::max)(120.0f, rowRect.x + rowRect.width - 8.0f - bounds.x)); +}