2026-04-15 11:42:56 +08:00
|
|
|
#include "Fields/UIEditorPropertyGridInternal.h"
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::Widgets {
|
|
|
|
|
|
|
|
|
|
bool IsUIEditorPropertyGridPointInside(
|
2026-04-15 11:42:56 +08:00
|
|
|
const ::XCEngine::UI::UIRect& rect,
|
|
|
|
|
const ::XCEngine::UI::UIPoint& point) {
|
2026-04-07 16:57:04 +08:00
|
|
|
return point.x >= rect.x &&
|
|
|
|
|
point.x <= rect.x + rect.width &&
|
|
|
|
|
point.y >= rect.y &&
|
|
|
|
|
point.y <= rect.y + rect.height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::size_t FindUIEditorPropertyGridSectionIndex(
|
|
|
|
|
const std::vector<UIEditorPropertyGridSection>& sections,
|
|
|
|
|
std::string_view sectionId) {
|
|
|
|
|
for (std::size_t sectionIndex = 0u; sectionIndex < sections.size(); ++sectionIndex) {
|
|
|
|
|
if (sections[sectionIndex].sectionId == sectionId) {
|
|
|
|
|
return sectionIndex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return UIEditorPropertyGridInvalidIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorPropertyGridFieldLocation FindUIEditorPropertyGridFieldLocation(
|
|
|
|
|
const std::vector<UIEditorPropertyGridSection>& sections,
|
|
|
|
|
std::string_view fieldId) {
|
|
|
|
|
for (std::size_t sectionIndex = 0u; sectionIndex < sections.size(); ++sectionIndex) {
|
|
|
|
|
const auto& fields = sections[sectionIndex].fields;
|
|
|
|
|
for (std::size_t fieldIndex = 0u; fieldIndex < fields.size(); ++fieldIndex) {
|
|
|
|
|
if (fields[fieldIndex].fieldId == fieldId) {
|
|
|
|
|
return { sectionIndex, fieldIndex };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
std::string ResolveUIEditorPropertyGridFieldValueText(
|
|
|
|
|
const UIEditorPropertyGridField& field) {
|
|
|
|
|
switch (field.kind) {
|
|
|
|
|
case UIEditorPropertyGridFieldKind::Bool:
|
|
|
|
|
return field.boolValue ? "true" : "false";
|
|
|
|
|
|
|
|
|
|
case UIEditorPropertyGridFieldKind::Number:
|
2026-04-15 11:42:56 +08:00
|
|
|
return FormatUIEditorNumberFieldValue(Detail::BuildNumberFieldSpec(field));
|
2026-04-08 02:52:28 +08:00
|
|
|
|
|
|
|
|
case UIEditorPropertyGridFieldKind::Enum:
|
2026-04-15 11:42:56 +08:00
|
|
|
return ResolveUIEditorEnumFieldValueText(Detail::BuildEnumFieldSpec(field));
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-10 00:41:28 +08:00
|
|
|
case UIEditorPropertyGridFieldKind::Color:
|
2026-04-15 11:42:56 +08:00
|
|
|
return Detail::FormatColorValueText(field);
|
2026-04-10 00:41:28 +08:00
|
|
|
|
2026-04-08 03:23:15 +08:00
|
|
|
case UIEditorPropertyGridFieldKind::Vector2:
|
2026-04-15 11:42:56 +08:00
|
|
|
return Detail::FormatVector2ValueText(field);
|
2026-04-08 03:23:15 +08:00
|
|
|
|
|
|
|
|
case UIEditorPropertyGridFieldKind::Vector3:
|
2026-04-15 11:42:56 +08:00
|
|
|
return Detail::FormatVector3ValueText(field);
|
2026-04-08 03:23:15 +08:00
|
|
|
|
|
|
|
|
case UIEditorPropertyGridFieldKind::Vector4:
|
2026-04-15 11:42:56 +08:00
|
|
|
return Detail::FormatVector4ValueText(field);
|
2026-04-08 03:23:15 +08:00
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
case UIEditorPropertyGridFieldKind::Text:
|
|
|
|
|
default:
|
|
|
|
|
return field.valueText;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
std::size_t FindUIEditorPropertyGridVisibleFieldIndex(
|
|
|
|
|
const UIEditorPropertyGridLayout& layout,
|
|
|
|
|
std::string_view fieldId,
|
|
|
|
|
const std::vector<UIEditorPropertyGridSection>& sections) {
|
|
|
|
|
for (std::size_t visibleIndex = 0u;
|
|
|
|
|
visibleIndex < layout.visibleFieldIndices.size();
|
|
|
|
|
++visibleIndex) {
|
|
|
|
|
const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleIndex];
|
|
|
|
|
const std::size_t fieldIndex = layout.visibleFieldIndices[visibleIndex];
|
|
|
|
|
if (sectionIndex >= sections.size() ||
|
|
|
|
|
fieldIndex >= sections[sectionIndex].fields.size()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sections[sectionIndex].fields[fieldIndex].fieldId == fieldId) {
|
|
|
|
|
return visibleIndex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return UIEditorPropertyGridInvalidIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorPropertyGridLayout BuildUIEditorPropertyGridLayout(
|
2026-04-15 11:42:56 +08:00
|
|
|
const ::XCEngine::UI::UIRect& bounds,
|
2026-04-07 16:57:04 +08:00
|
|
|
const std::vector<UIEditorPropertyGridSection>& sections,
|
|
|
|
|
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
|
|
|
|
|
const UIEditorPropertyGridMetrics& metrics) {
|
|
|
|
|
UIEditorPropertyGridLayout layout = {};
|
2026-04-15 11:42:56 +08:00
|
|
|
layout.bounds = ::XCEngine::UI::UIRect(
|
2026-04-07 16:57:04 +08:00
|
|
|
bounds.x,
|
|
|
|
|
bounds.y,
|
2026-04-15 11:42:56 +08:00
|
|
|
Detail::ClampNonNegative(bounds.width),
|
|
|
|
|
Detail::ClampNonNegative(bounds.height));
|
2026-04-07 16:57:04 +08:00
|
|
|
|
2026-04-15 11:42:56 +08:00
|
|
|
const float inset = Detail::ClampNonNegative(metrics.contentInset);
|
2026-04-07 16:57:04 +08:00
|
|
|
const float contentX = layout.bounds.x + inset;
|
|
|
|
|
const float contentY = layout.bounds.y + inset;
|
2026-04-15 11:42:56 +08:00
|
|
|
const float contentWidth =
|
|
|
|
|
(std::max)(0.0f, layout.bounds.width - inset * 2.0f);
|
2026-04-07 16:57:04 +08:00
|
|
|
|
|
|
|
|
float cursorY = contentY;
|
|
|
|
|
for (std::size_t sectionIndex = 0u; sectionIndex < sections.size(); ++sectionIndex) {
|
|
|
|
|
const UIEditorPropertyGridSection& section = sections[sectionIndex];
|
2026-04-15 11:42:56 +08:00
|
|
|
const float headerHeight =
|
|
|
|
|
Detail::ResolveSectionHeaderHeight(section, metrics);
|
2026-04-07 16:57:04 +08:00
|
|
|
const bool expanded = expansionModel.IsExpanded(section.sectionId);
|
|
|
|
|
|
2026-04-15 11:42:56 +08:00
|
|
|
const ::XCEngine::UI::UIRect headerRect(
|
2026-04-07 16:57:04 +08:00
|
|
|
contentX,
|
|
|
|
|
cursorY,
|
|
|
|
|
contentWidth,
|
|
|
|
|
headerHeight);
|
2026-04-15 11:42:56 +08:00
|
|
|
const ::XCEngine::UI::UIRect disclosureRect(
|
2026-04-07 16:57:04 +08:00
|
|
|
headerRect.x + metrics.horizontalPadding,
|
|
|
|
|
headerRect.y + (headerRect.height - metrics.disclosureExtent) * 0.5f,
|
|
|
|
|
metrics.disclosureExtent,
|
|
|
|
|
metrics.disclosureExtent);
|
|
|
|
|
const float titleX =
|
|
|
|
|
disclosureRect.x + disclosureRect.width + metrics.disclosureLabelGap;
|
2026-04-15 11:42:56 +08:00
|
|
|
const ::XCEngine::UI::UIRect titleRect(
|
2026-04-07 16:57:04 +08:00
|
|
|
titleX,
|
|
|
|
|
headerRect.y,
|
2026-04-15 11:42:56 +08:00
|
|
|
(std::max)(
|
|
|
|
|
0.0f,
|
|
|
|
|
headerRect.x + headerRect.width - titleX - metrics.horizontalPadding),
|
2026-04-07 16:57:04 +08:00
|
|
|
headerRect.height);
|
|
|
|
|
|
|
|
|
|
layout.sectionIndices.push_back(sectionIndex);
|
|
|
|
|
layout.sectionHeaderRects.push_back(headerRect);
|
|
|
|
|
layout.sectionDisclosureRects.push_back(disclosureRect);
|
|
|
|
|
layout.sectionTitleRects.push_back(titleRect);
|
|
|
|
|
layout.sectionExpanded.push_back(expanded);
|
|
|
|
|
|
|
|
|
|
cursorY += headerHeight;
|
|
|
|
|
if (expanded) {
|
|
|
|
|
for (std::size_t fieldIndex = 0u; fieldIndex < section.fields.size(); ++fieldIndex) {
|
|
|
|
|
const UIEditorPropertyGridField& field = section.fields[fieldIndex];
|
2026-04-15 11:42:56 +08:00
|
|
|
const float rowHeight =
|
|
|
|
|
Detail::ResolveFieldRowHeight(field, metrics);
|
|
|
|
|
const ::XCEngine::UI::UIRect rowRect(
|
2026-04-07 16:57:04 +08:00
|
|
|
contentX,
|
|
|
|
|
cursorY,
|
|
|
|
|
contentWidth,
|
|
|
|
|
rowHeight);
|
2026-04-15 11:42:56 +08:00
|
|
|
const Detail::UIEditorPropertyGridFieldRects fieldRects =
|
|
|
|
|
Detail::ResolveFieldRects(rowRect, field, metrics);
|
2026-04-07 16:57:04 +08:00
|
|
|
|
|
|
|
|
layout.visibleFieldSectionIndices.push_back(sectionIndex);
|
|
|
|
|
layout.visibleFieldIndices.push_back(fieldIndex);
|
|
|
|
|
layout.fieldRowRects.push_back(rowRect);
|
2026-04-08 02:52:28 +08:00
|
|
|
layout.fieldLabelRects.push_back(fieldRects.labelRect);
|
|
|
|
|
layout.fieldValueRects.push_back(fieldRects.valueRect);
|
2026-04-07 16:57:04 +08:00
|
|
|
layout.fieldReadOnly.push_back(field.readOnly);
|
|
|
|
|
|
|
|
|
|
cursorY += rowHeight + metrics.rowGap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!section.fields.empty()) {
|
|
|
|
|
cursorY -= metrics.rowGap;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cursorY += metrics.sectionGap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return layout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorPropertyGridHitTarget HitTestUIEditorPropertyGrid(
|
|
|
|
|
const UIEditorPropertyGridLayout& layout,
|
2026-04-15 11:42:56 +08:00
|
|
|
const ::XCEngine::UI::UIPoint& point) {
|
2026-04-07 16:57:04 +08:00
|
|
|
for (std::size_t sectionVisibleIndex = 0u;
|
|
|
|
|
sectionVisibleIndex < layout.sectionHeaderRects.size();
|
|
|
|
|
++sectionVisibleIndex) {
|
2026-04-15 11:42:56 +08:00
|
|
|
if (!IsUIEditorPropertyGridPointInside(
|
|
|
|
|
layout.sectionHeaderRects[sectionVisibleIndex],
|
|
|
|
|
point)) {
|
2026-04-07 16:57:04 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorPropertyGridHitTarget target = {};
|
|
|
|
|
target.kind = UIEditorPropertyGridHitTargetKind::SectionHeader;
|
|
|
|
|
target.sectionIndex = layout.sectionIndices[sectionVisibleIndex];
|
|
|
|
|
return target;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (std::size_t visibleFieldIndex = 0u;
|
|
|
|
|
visibleFieldIndex < layout.fieldRowRects.size();
|
|
|
|
|
++visibleFieldIndex) {
|
2026-04-15 11:42:56 +08:00
|
|
|
if (!IsUIEditorPropertyGridPointInside(
|
|
|
|
|
layout.fieldRowRects[visibleFieldIndex],
|
|
|
|
|
point)) {
|
2026-04-07 16:57:04 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorPropertyGridHitTarget target = {};
|
|
|
|
|
target.visibleFieldIndex = visibleFieldIndex;
|
|
|
|
|
target.sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex];
|
|
|
|
|
target.fieldIndex = layout.visibleFieldIndices[visibleFieldIndex];
|
2026-04-15 11:42:56 +08:00
|
|
|
target.kind = IsUIEditorPropertyGridPointInside(
|
|
|
|
|
layout.fieldValueRects[visibleFieldIndex],
|
|
|
|
|
point)
|
2026-04-07 16:57:04 +08:00
|
|
|
? UIEditorPropertyGridHitTargetKind::ValueBox
|
|
|
|
|
: UIEditorPropertyGridHitTargetKind::FieldRow;
|
|
|
|
|
return target;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::Widgets
|