refactor(new_editor): unify panel input and rename primitives
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
|
||||
#include <XCEditor/Collections/UIEditorTreePanelBehavior.h>
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
|
||||
@@ -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<UIInputEvent> filteredEvents = FilterHierarchyInputEvents(
|
||||
const std::vector<UIInputEvent> 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<UIInputEvent> filteredEvents = FilterHierarchyInputEvents(
|
||||
const std::vector<UIInputEvent> 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<UIInputEvent> 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;
|
||||
}
|
||||
|
||||
@@ -1,106 +1,11 @@
|
||||
#include "HierarchyPanelInternal.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
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<UIInputEvent> FilterHierarchyInputEvents(
|
||||
const UIRect& bounds,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool allowInteraction,
|
||||
bool panelActive,
|
||||
bool captureActive) {
|
||||
if (!allowInteraction && !captureActive) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> 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<Widgets::UIEditorTreeViewItem>& 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<Widgets::UIEditorTreeViewItem>& 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
|
||||
|
||||
@@ -5,42 +5,15 @@
|
||||
#include "Rendering/Assets/BuiltInIcons.h"
|
||||
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Collections/UIEditorTreeView.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
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<UIInputEvent> FilterHierarchyInputEvents(
|
||||
const UIRect& bounds,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool allowInteraction,
|
||||
bool panelActive,
|
||||
bool captureActive);
|
||||
const Widgets::UIEditorTreeViewItem* ResolveHitItem(
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
const UIPoint& point,
|
||||
UIEditorTreeViewHitTarget* hitTargetOutput = nullptr);
|
||||
std::size_t FindVisibleIndexForItemId(
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
std::string_view itemId);
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App::HierarchyPanelInternal
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Foundation/UIEditorPanelInputFilter.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
#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<UIInputEvent> FilterInspectorInputEvents(
|
||||
const UIRect& bounds,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool allowInteraction,
|
||||
bool panelActive) {
|
||||
if (!allowInteraction && !panelActive) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> 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<UIInputEvent> 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,
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include "Project/EditorProjectRuntime.h"
|
||||
|
||||
#include <XCEditor/Collections/UIEditorTreePanelBehavior.h>
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Foundation/UIEditorPanelInputFilter.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
|
||||
#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<UIInputEvent> ProjectPanel::BuildTreeInteractionInputEvents(
|
||||
bool allowInteraction,
|
||||
bool panelActive) const {
|
||||
const std::vector<UIInputEvent> rawEvents =
|
||||
FilterProjectPanelInputEvents(
|
||||
FilterUIEditorPanelInputEvents(
|
||||
bounds,
|
||||
inputEvents,
|
||||
allowInteraction,
|
||||
panelActive,
|
||||
HasActivePointerCapture());
|
||||
const std::vector<UIInputEvent> 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<UIInputEvent> 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<UIInputEvent> 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);
|
||||
}
|
||||
|
||||
@@ -39,77 +39,6 @@ float MeasureTextWidth(
|
||||
return static_cast<float>(text.size()) * fontSize * 0.56f;
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> FilterProjectPanelInputEvents(
|
||||
const UIRect& bounds,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool allowInteraction,
|
||||
bool panelActive,
|
||||
bool captureActive) {
|
||||
if (!allowInteraction && !captureActive) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> 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<UIInputEvent> FilterTreeInputEvents(
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool suppressPointerInput) {
|
||||
if (!suppressPointerInput) {
|
||||
return inputEvents;
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> 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)
|
||||
|
||||
@@ -64,15 +64,6 @@ float MeasureTextWidth(
|
||||
const UIEditorTextMeasurer* textMeasurer,
|
||||
std::string_view text,
|
||||
float fontSize);
|
||||
std::vector<UIInputEvent> FilterProjectPanelInputEvents(
|
||||
const UIRect& bounds,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool allowInteraction,
|
||||
bool panelActive,
|
||||
bool captureActive);
|
||||
std::vector<UIInputEvent> FilterTreeInputEvents(
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool suppressPointerInput);
|
||||
::XCEngine::UI::UITextureHandle ResolveFolderIcon(const BuiltInIcons* icons);
|
||||
float ClampNavigationWidth(float value, float totalWidth);
|
||||
void AppendTilePreview(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Collections/UIEditorTreeDragDrop.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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<Widgets::UIEditorTreeViewItem>& 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<Widgets::UIEditorTreeViewItem>& items,
|
||||
std::string_view itemId);
|
||||
|
||||
::XCEngine::UI::UIRect BuildUIEditorTreePanelInlineRenameBounds(
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
std::string_view itemId,
|
||||
const Widgets::UIEditorTextFieldMetrics& hostedMetrics,
|
||||
float minWidth = 120.0f,
|
||||
float trailingPadding = 8.0f);
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<UIInputEvent> 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
|
||||
@@ -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) {
|
||||
|
||||
126
new_editor/src/Collections/UIEditorTreePanelBehavior.cpp
Normal file
126
new_editor/src/Collections/UIEditorTreePanelBehavior.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <XCEditor/Collections/UIEditorTreePanelBehavior.h>
|
||||
#include <XCEditor/Foundation/UIEditorPanelInputFilter.h>
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<UIInputEvent> FilterUIEditorTreePanelInputEvents(
|
||||
const UIRect& bounds,
|
||||
const std::vector<UIInputEvent>& 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<UIInputEvent> FilterUIEditorTreePanelPointerInputEvents(
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool suppressPointerInput) {
|
||||
if (!suppressPointerInput) {
|
||||
return inputEvents;
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> 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<UIInputEvent> BuildUIEditorTreePanelInteractionInputEvents(
|
||||
const Collections::TreeDragDrop::State& dragState,
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
const std::vector<UIInputEvent>& 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<Widgets::UIEditorTreeViewItem>& 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<Widgets::UIEditorTreeViewItem>& 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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
95
tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp
Normal file
95
tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorPanelInputFilter.h>
|
||||
|
||||
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<UIInputEvent> 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<UIInputEvent> 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);
|
||||
}
|
||||
177
tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp
Normal file
177
tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Collections/UIEditorTreePanelBehavior.h>
|
||||
|
||||
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<Widgets::UIEditorTreeViewItem> 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<UIInputEvent> 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<UIInputEvent> 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<UIInputEvent> 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));
|
||||
}
|
||||
Reference in New Issue
Block a user