refactor(new_editor): unify panel input and rename primitives

This commit is contained in:
2026-04-19 03:23:16 +08:00
parent c59cd83c38
commit 78bcd2e9ca
17 changed files with 652 additions and 338 deletions

View File

@@ -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

View File

@@ -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);
}

View 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);
}

View 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));
}