engine: sync editor rendering and ui changes

This commit is contained in:
2026-04-08 16:09:15 +08:00
parent 31756847ab
commit 162f1cc12e
153 changed files with 4454 additions and 2990 deletions

View File

@@ -21,6 +21,8 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
test_ui_editor_theme.cpp
test_ui_editor_bool_field.cpp
test_ui_editor_bool_field_interaction.cpp
test_ui_editor_color_field.cpp
test_ui_editor_color_field_interaction.cpp
test_ui_editor_dock_host.cpp
test_ui_editor_list_view.cpp
test_ui_editor_list_view_interaction.cpp

View File

@@ -0,0 +1,140 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorColorField.h>
namespace {
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::Widgets::AppendUIEditorColorField;
using XCEngine::UI::Editor::Widgets::BuildUIEditorColorFieldLayout;
using XCEngine::UI::Editor::Widgets::FormatUIEditorColorFieldHexText;
using XCEngine::UI::Editor::Widgets::FormatUIEditorColorFieldRgbaText;
using XCEngine::UI::Editor::Widgets::HitTestUIEditorColorField;
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldSpec;
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldState;
TEST(UIEditorColorFieldTest, FormatsHexTextWithAndWithoutAlpha) {
UIEditorColorFieldSpec spec = {};
spec.value = XCEngine::UI::UIColor(1.0f, 0.5f, 0.25f, 0.75f);
spec.showAlpha = true;
EXPECT_EQ(FormatUIEditorColorFieldHexText(spec), "#FF8040BF");
spec.showAlpha = false;
EXPECT_EQ(FormatUIEditorColorFieldHexText(spec), "#FF8040");
}
TEST(UIEditorColorFieldTest, FormatsRgbaReadoutForInspectorSummary) {
UIEditorColorFieldSpec spec = {};
spec.value = XCEngine::UI::UIColor(0.25f, 0.5f, 0.75f, 1.0f);
EXPECT_EQ(FormatUIEditorColorFieldRgbaText(spec), "RGBA 64, 128, 191, 255");
}
TEST(UIEditorColorFieldTest, LayoutKeepsInspectorColumnAndCompactSwatch) {
UIEditorColorFieldSpec spec = {};
spec.fieldId = "albedo";
spec.label = "Albedo";
const auto layout = BuildUIEditorColorFieldLayout(
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
spec);
EXPECT_FLOAT_EQ(layout.swatchRect.x, 236.0f);
EXPECT_FLOAT_EQ(layout.swatchRect.width, 54.0f);
EXPECT_FLOAT_EQ(layout.swatchRect.height, 18.0f);
EXPECT_EQ(
HitTestUIEditorColorField(
layout,
false,
UIPoint(layout.swatchRect.x + 2.0f, layout.swatchRect.y + 2.0f)).kind,
UIEditorColorFieldHitTargetKind::Swatch);
}
TEST(UIEditorColorFieldTest, PopupLayoutExposesHueWheelAndChannelTargets) {
UIEditorColorFieldSpec spec = {};
spec.showAlpha = true;
const auto layout = BuildUIEditorColorFieldLayout(
UIRect(10.0f, 20.0f, 360.0f, 22.0f),
spec,
{},
UIRect(0.0f, 0.0f, 800.0f, 600.0f));
EXPECT_GT(layout.saturationValueRect.width, 0.0f);
EXPECT_GT(layout.hueWheelOuterRadius, layout.hueWheelInnerRadius);
EXPECT_GT(layout.redSliderRect.width, 0.0f);
EXPECT_GT(layout.alphaSliderRect.width, 0.0f);
EXPECT_EQ(
HitTestUIEditorColorField(
layout,
true,
UIPoint(
layout.hueWheelCenter.x + (layout.hueWheelInnerRadius + layout.hueWheelOuterRadius) * 0.5f,
layout.hueWheelCenter.y)).kind,
UIEditorColorFieldHitTargetKind::HueWheel);
EXPECT_EQ(
HitTestUIEditorColorField(
layout,
true,
UIPoint(layout.saturationValueRect.x + 5.0f, layout.saturationValueRect.y + 5.0f)).kind,
UIEditorColorFieldHitTargetKind::SaturationValue);
EXPECT_EQ(
HitTestUIEditorColorField(
layout,
true,
UIPoint(layout.redSliderRect.x + 2.0f, layout.redSliderRect.y + 2.0f)).kind,
UIEditorColorFieldHitTargetKind::RedChannel);
EXPECT_EQ(
HitTestUIEditorColorField(
layout,
true,
UIPoint(layout.alphaSliderRect.x + 2.0f, layout.alphaSliderRect.y + 2.0f)).kind,
UIEditorColorFieldHitTargetKind::AlphaChannel);
}
TEST(UIEditorColorFieldTest, PopupDrawEmitsHeaderWheelHandlesAndHexadecimalLabel) {
UIEditorColorFieldSpec spec = {};
spec.label = "Tint";
spec.value = XCEngine::UI::UIColor(0.8f, 0.4f, 0.2f, 0.5f);
spec.showAlpha = true;
UIEditorColorFieldState state = {};
state.popupOpen = true;
XCEngine::UI::UIDrawData drawData = {};
auto& drawList = drawData.EmplaceDrawList("ColorField");
AppendUIEditorColorField(
drawList,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
spec,
state,
{},
{},
UIRect(0.0f, 0.0f, 800.0f, 600.0f));
bool hasFilledCircle = false;
bool hasCircleOutline = false;
bool hasLine = false;
bool hasTitleText = false;
bool hasHexLabel = false;
for (const auto& command : drawList.GetCommands()) {
hasFilledCircle = hasFilledCircle || command.type == UIDrawCommandType::FilledCircle;
hasCircleOutline = hasCircleOutline || command.type == UIDrawCommandType::CircleOutline;
hasLine = hasLine || command.type == UIDrawCommandType::Line;
hasTitleText = hasTitleText || command.text == "Color";
hasHexLabel = hasHexLabel || command.text == "Hexadecimal";
}
EXPECT_TRUE(hasFilledCircle);
EXPECT_TRUE(hasCircleOutline);
EXPECT_TRUE(hasLine);
EXPECT_TRUE(hasTitleText);
EXPECT_TRUE(hasHexLabel);
}
} // namespace

View File

@@ -0,0 +1,138 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorColorFieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>
namespace {
using XCEngine::Input::KeyCode;
using XCEngine::UI::UIInputEvent;
using XCEngine::UI::UIInputEventType;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIPointerButton;
using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::UIEditorColorFieldInteractionState;
using XCEngine::UI::Editor::UpdateUIEditorColorFieldInteraction;
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldSpec;
UIInputEvent MakePointer(UIInputEventType type, float x, float y, UIPointerButton button = UIPointerButton::None) {
UIInputEvent event = {};
event.type = type;
event.position = UIPoint(x, y);
event.pointerButton = button;
return event;
}
UIInputEvent MakeKey(KeyCode keyCode) {
UIInputEvent event = {};
event.type = UIInputEventType::KeyDown;
event.keyCode = static_cast<std::int32_t>(keyCode);
return event;
}
TEST(UIEditorColorFieldInteractionTest, ClickSwatchOpensPopupAndEscapeClosesIt) {
UIEditorColorFieldSpec spec = {};
spec.fieldId = "tint";
spec.label = "Tint";
spec.showAlpha = true;
spec.value = XCEngine::UI::UIColor(0.8f, 0.4f, 0.2f, 0.5f);
UIEditorColorFieldInteractionState state = {};
auto frame = UpdateUIEditorColorFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 32.0f),
{});
frame = UpdateUIEditorColorFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 32.0f),
{
MakePointer(
UIInputEventType::PointerButtonDown,
frame.layout.swatchRect.x + 2.0f,
frame.layout.swatchRect.y + 2.0f,
UIPointerButton::Left),
MakePointer(
UIInputEventType::PointerButtonUp,
frame.layout.swatchRect.x + 2.0f,
frame.layout.swatchRect.y + 2.0f,
UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.popupOpened);
EXPECT_TRUE(state.colorFieldState.popupOpen);
frame = UpdateUIEditorColorFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 32.0f),
{ MakeKey(KeyCode::Escape) });
EXPECT_TRUE(frame.result.popupClosed);
EXPECT_FALSE(state.colorFieldState.popupOpen);
}
TEST(UIEditorColorFieldInteractionTest, DraggingHueWheelAndAlphaChannelUpdatesColor) {
UIEditorColorFieldSpec spec = {};
spec.fieldId = "tint";
spec.label = "Tint";
spec.showAlpha = true;
spec.value = XCEngine::UI::UIColor(1.0f, 0.0f, 0.0f, 1.0f);
UIEditorColorFieldInteractionState state = {};
auto frame = UpdateUIEditorColorFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 32.0f),
{});
frame = UpdateUIEditorColorFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 32.0f),
{
MakePointer(
UIInputEventType::PointerButtonDown,
frame.layout.swatchRect.x + 2.0f,
frame.layout.swatchRect.y + 2.0f,
UIPointerButton::Left),
MakePointer(
UIInputEventType::PointerButtonUp,
frame.layout.swatchRect.x + 2.0f,
frame.layout.swatchRect.y + 2.0f,
UIPointerButton::Left)
});
ASSERT_TRUE(state.colorFieldState.popupOpen);
const float hueRadius = (frame.layout.hueWheelInnerRadius + frame.layout.hueWheelOuterRadius) * 0.5f;
const float hueX = frame.layout.hueWheelCenter.x - hueRadius;
const float hueY = frame.layout.hueWheelCenter.y;
frame = UpdateUIEditorColorFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 32.0f),
{
MakePointer(UIInputEventType::PointerButtonDown, hueX, hueY, UIPointerButton::Left),
MakePointer(UIInputEventType::PointerMove, hueX, hueY),
MakePointer(UIInputEventType::PointerButtonUp, hueX, hueY, UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.colorChanged);
EXPECT_GT(spec.value.b, 0.0f);
const float alphaX = frame.layout.alphaSliderRect.x + frame.layout.alphaSliderRect.width * 0.25f;
const float alphaY = frame.layout.alphaSliderRect.y + frame.layout.alphaSliderRect.height * 0.5f;
frame = UpdateUIEditorColorFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 32.0f),
{
MakePointer(UIInputEventType::PointerButtonDown, alphaX, alphaY, UIPointerButton::Left),
MakePointer(UIInputEventType::PointerMove, alphaX, alphaY),
MakePointer(UIInputEventType::PointerButtonUp, alphaX, alphaY, UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.colorChanged);
EXPECT_LT(spec.value.a, 0.5f);
}
} // namespace

View File

@@ -37,6 +37,28 @@ Style::UITheme BuildEditorFieldTheme() {
definition.SetToken("editor.size.field.dropdown_arrow_width", Style::UIStyleValue(14.0f));
definition.SetToken("editor.space.field.dropdown_arrow_inset_x", Style::UIStyleValue(4.0f));
definition.SetToken("editor.space.field.dropdown_arrow_inset_y", Style::UIStyleValue(4.0f));
definition.SetToken("editor.size.field.color_popup_width", Style::UIStyleValue(320.0f));
definition.SetToken("editor.size.field.color_popup_top_row", Style::UIStyleValue(36.0f));
definition.SetToken("editor.size.field.color_preview_width", Style::UIStyleValue(108.0f));
definition.SetToken("editor.size.field.color_preview_height", Style::UIStyleValue(26.0f));
definition.SetToken("editor.size.field.color_wheel_outer_radius", Style::UIStyleValue(112.0f));
definition.SetToken("editor.size.field.color_wheel_ring_thickness", Style::UIStyleValue(22.0f));
definition.SetToken("editor.size.field.color_sv_square", Style::UIStyleValue(118.0f));
definition.SetToken("editor.size.field.color_wheel_region_height", Style::UIStyleValue(224.0f));
definition.SetToken("editor.size.field.color_channel_row_height", Style::UIStyleValue(21.0f));
definition.SetToken("editor.size.field.color_numeric_box_width", Style::UIStyleValue(66.0f));
definition.SetToken("editor.size.field.color_channel_label_width", Style::UIStyleValue(14.0f));
definition.SetToken("editor.size.field.color_hex_label_width", Style::UIStyleValue(90.0f));
definition.SetToken("editor.space.field.color_control_row_spacing", Style::UIStyleValue(7.0f));
definition.SetToken("editor.space.field.color_popup_field_inset", Style::UIStyleValue(5.0f));
definition.SetToken("editor.color.field.color_popup_surface", Style::UIStyleValue(Math::Color(0.22f, 0.22f, 0.22f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_header", Style::UIStyleValue(Math::Color(0.48f, 0.28f, 0.10f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_close", Style::UIStyleValue(Math::Color(0.74f, 0.34f, 0.32f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_close_hover", Style::UIStyleValue(Math::Color(0.80f, 0.38f, 0.36f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_slider_border", Style::UIStyleValue(Math::Color(0.11f, 0.11f, 0.11f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_numeric_box", Style::UIStyleValue(Math::Color(0.17f, 0.17f, 0.17f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_numeric_box_text", Style::UIStyleValue(Math::Color(0.93f, 0.93f, 0.93f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_handle_outline", Style::UIStyleValue(Math::Color(0.10f, 0.10f, 0.10f, 0.5f)));
definition.SetToken("editor.radius.field.row", Style::UIStyleValue(2.0f));
definition.SetToken("editor.radius.field.control", Style::UIStyleValue(2.0f));
definition.SetToken("editor.border.field", Style::UIStyleValue(1.0f));
@@ -244,6 +266,25 @@ TEST(UIEditorThemeTest, FieldResolversReadEditorThemeTokens) {
EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 10.0f);
EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.88f);
const auto colorMetrics = Editor::ResolveUIEditorColorFieldMetrics(theme);
const auto colorPalette = Editor::ResolveUIEditorColorFieldPalette(theme);
EXPECT_FLOAT_EQ(colorMetrics.popupWidth, 320.0f);
EXPECT_FLOAT_EQ(colorMetrics.popupTopRowHeight, 36.0f);
EXPECT_FLOAT_EQ(colorMetrics.popupPreviewWidth, 108.0f);
EXPECT_FLOAT_EQ(colorMetrics.wheelOuterRadius, 112.0f);
EXPECT_FLOAT_EQ(colorMetrics.wheelRingThickness, 22.0f);
EXPECT_FLOAT_EQ(colorMetrics.saturationValueSize, 118.0f);
EXPECT_FLOAT_EQ(colorMetrics.channelRowHeight, 21.0f);
EXPECT_FLOAT_EQ(colorMetrics.numericBoxWidth, 66.0f);
EXPECT_FLOAT_EQ(colorMetrics.hexLabelWidth, 90.0f);
EXPECT_FLOAT_EQ(colorPalette.popupColor.r, 0.22f);
EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.48f);
EXPECT_FLOAT_EQ(colorPalette.closeButtonColor.r, 0.74f);
EXPECT_FLOAT_EQ(colorPalette.closeButtonHoverColor.r, 0.80f);
EXPECT_FLOAT_EQ(colorPalette.sliderBorderColor.r, 0.11f);
EXPECT_FLOAT_EQ(colorPalette.numericBoxTextColor.r, 0.93f);
EXPECT_FLOAT_EQ(colorPalette.handleStrokeColor.r, 0.10f);
const auto popupMetrics = Editor::ResolveUIEditorMenuPopupMetrics(theme);
const auto popupPalette = Editor::ResolveUIEditorMenuPopupPalette(theme);
EXPECT_FLOAT_EQ(popupMetrics.contentPaddingX, 6.0f);
@@ -386,6 +427,18 @@ TEST(UIEditorThemeTest, HostedFieldBuildersInheritPropertyGridMetricsAndPalette)
EXPECT_FLOAT_EQ(enumMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 12.0f);
EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.9f);
const auto colorMetrics = Editor::BuildUIEditorHostedColorFieldMetrics(propertyMetrics);
const auto colorPalette = Editor::BuildUIEditorHostedColorFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(colorMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(colorMetrics.swatchInsetY, 2.0f);
EXPECT_FLOAT_EQ(colorMetrics.labelFontSize, 10.0f);
EXPECT_FLOAT_EQ(colorMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(colorMetrics.popupHeaderHeight, 30.0f);
EXPECT_FLOAT_EQ(colorPalette.labelColor.r, 0.8f);
EXPECT_FLOAT_EQ(colorPalette.popupBorderColor.r, 0.15f);
EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.43f);
EXPECT_FLOAT_EQ(colorPalette.swatchBorderColor.r, 0.5f);
}
} // namespace