Files
XCEngine/new_editor/src/Widgets/UIEditorPropertyGrid.cpp

417 lines
16 KiB
C++
Raw Normal View History

#include <XCEditor/Widgets/UIEditorPropertyGrid.h>
#include <algorithm>
namespace XCEngine::UI::Editor::Widgets {
namespace {
float ClampNonNegative(float value) {
return (std::max)(value, 0.0f);
}
float ResolveSectionHeaderHeight(
const UIEditorPropertyGridSection& section,
const UIEditorPropertyGridMetrics& metrics) {
return section.desiredHeaderHeight > 0.0f
? section.desiredHeaderHeight
: metrics.sectionHeaderHeight;
}
float ResolveFieldRowHeight(
const UIEditorPropertyGridField& field,
const UIEditorPropertyGridMetrics& metrics) {
return field.desiredHeight > 0.0f
? field.desiredHeight
: metrics.fieldRowHeight;
}
::XCEngine::UI::UIPoint ResolveDisclosureGlyphPosition(
const ::XCEngine::UI::UIRect& rect,
float textInsetY) {
return ::XCEngine::UI::UIPoint(rect.x + 2.0f, rect.y + textInsetY - 1.0f);
}
const std::string& ResolveDisplayedFieldValue(
const UIEditorPropertyGridField& field,
const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel) {
if (propertyEditModel.HasActiveEdit() &&
propertyEditModel.GetActiveFieldId() == field.fieldId) {
return propertyEditModel.GetStagedValue();
}
return field.valueText;
}
} // namespace
bool IsUIEditorPropertyGridPointInside(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIPoint& point) {
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 {};
}
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(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorPropertyGridSection>& sections,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const UIEditorPropertyGridMetrics& metrics) {
UIEditorPropertyGridLayout layout = {};
layout.bounds = ::XCEngine::UI::UIRect(
bounds.x,
bounds.y,
ClampNonNegative(bounds.width),
ClampNonNegative(bounds.height));
const float inset = ClampNonNegative(metrics.contentInset);
const float contentX = layout.bounds.x + inset;
const float contentY = layout.bounds.y + inset;
const float contentWidth = (std::max)(0.0f, layout.bounds.width - inset * 2.0f);
float cursorY = contentY;
for (std::size_t sectionIndex = 0u; sectionIndex < sections.size(); ++sectionIndex) {
const UIEditorPropertyGridSection& section = sections[sectionIndex];
const float headerHeight = ResolveSectionHeaderHeight(section, metrics);
const bool expanded = expansionModel.IsExpanded(section.sectionId);
const ::XCEngine::UI::UIRect headerRect(
contentX,
cursorY,
contentWidth,
headerHeight);
const ::XCEngine::UI::UIRect disclosureRect(
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;
const ::XCEngine::UI::UIRect titleRect(
titleX,
headerRect.y,
(std::max)(0.0f, headerRect.x + headerRect.width - titleX - metrics.horizontalPadding),
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];
const float rowHeight = ResolveFieldRowHeight(field, metrics);
const ::XCEngine::UI::UIRect rowRect(
contentX,
cursorY,
contentWidth,
rowHeight);
const float controlX =
(std::min)(
rowRect.x + rowRect.width - metrics.horizontalPadding,
rowRect.x + metrics.controlColumnStart);
const float labelWidth =
(std::max)(
0.0f,
controlX - rowRect.x - metrics.horizontalPadding - metrics.labelControlGap);
const ::XCEngine::UI::UIRect labelRect(
rowRect.x + metrics.horizontalPadding,
rowRect.y,
labelWidth,
rowRect.height);
const ::XCEngine::UI::UIRect valueRect(
controlX,
rowRect.y + metrics.valueBoxInsetY,
(std::max)(
0.0f,
rowRect.x + rowRect.width - controlX - metrics.horizontalPadding),
(std::max)(0.0f, rowRect.height - metrics.valueBoxInsetY * 2.0f));
layout.visibleFieldSectionIndices.push_back(sectionIndex);
layout.visibleFieldIndices.push_back(fieldIndex);
layout.fieldRowRects.push_back(rowRect);
layout.fieldLabelRects.push_back(labelRect);
layout.fieldValueRects.push_back(valueRect);
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,
const ::XCEngine::UI::UIPoint& point) {
for (std::size_t sectionVisibleIndex = 0u;
sectionVisibleIndex < layout.sectionHeaderRects.size();
++sectionVisibleIndex) {
if (!IsUIEditorPropertyGridPointInside(layout.sectionHeaderRects[sectionVisibleIndex], point)) {
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) {
if (!IsUIEditorPropertyGridPointInside(layout.fieldRowRects[visibleFieldIndex], point)) {
continue;
}
UIEditorPropertyGridHitTarget target = {};
target.visibleFieldIndex = visibleFieldIndex;
target.sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex];
target.fieldIndex = layout.visibleFieldIndices[visibleFieldIndex];
target.kind = IsUIEditorPropertyGridPointInside(layout.fieldValueRects[visibleFieldIndex], point)
? UIEditorPropertyGridHitTargetKind::ValueBox
: UIEditorPropertyGridHitTargetKind::FieldRow;
return target;
}
return {};
}
void AppendUIEditorPropertyGridBackground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorPropertyGridLayout& layout,
const std::vector<UIEditorPropertyGridSection>& sections,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel,
const UIEditorPropertyGridState& state,
const UIEditorPropertyGridPalette& palette,
const UIEditorPropertyGridMetrics& metrics) {
drawList.AddFilledRect(layout.bounds, palette.surfaceColor, metrics.cornerRounding);
drawList.AddRectOutline(
layout.bounds,
state.focused ? palette.focusedBorderColor : palette.borderColor,
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
metrics.cornerRounding);
for (std::size_t sectionVisibleIndex = 0u;
sectionVisibleIndex < layout.sectionHeaderRects.size();
++sectionVisibleIndex) {
const UIEditorPropertyGridSection& section =
sections[layout.sectionIndices[sectionVisibleIndex]];
const bool hovered =
state.hoveredFieldId.empty() &&
state.hoveredSectionId == section.sectionId;
drawList.AddFilledRect(
layout.sectionHeaderRects[sectionVisibleIndex],
hovered ? palette.sectionHeaderHoverColor : palette.sectionHeaderColor,
metrics.cornerRounding);
}
for (std::size_t visibleFieldIndex = 0u;
visibleFieldIndex < layout.fieldRowRects.size();
++visibleFieldIndex) {
const UIEditorPropertyGridSection& section =
sections[layout.visibleFieldSectionIndices[visibleFieldIndex]];
const UIEditorPropertyGridField& field =
section.fields[layout.visibleFieldIndices[visibleFieldIndex]];
const bool selected = selectionModel.IsSelected(field.fieldId);
const bool hovered = state.hoveredFieldId == field.fieldId;
const bool editing = propertyEditModel.HasActiveEdit() &&
propertyEditModel.GetActiveFieldId() == field.fieldId;
if (selected || hovered) {
drawList.AddFilledRect(
layout.fieldRowRects[visibleFieldIndex],
selected
? (state.focused ? palette.fieldSelectedFocusedColor : palette.fieldSelectedColor)
: palette.fieldHoverColor,
metrics.cornerRounding);
}
const ::XCEngine::UI::UIColor valueBoxColor =
field.readOnly
? palette.valueBoxReadOnlyColor
: (editing
? palette.valueBoxEditingColor
: (hovered ? palette.valueBoxHoverColor : palette.valueBoxColor));
drawList.AddFilledRect(
layout.fieldValueRects[visibleFieldIndex],
valueBoxColor,
metrics.valueBoxRounding);
drawList.AddRectOutline(
layout.fieldValueRects[visibleFieldIndex],
editing ? palette.valueBoxEditingBorderColor : palette.valueBoxBorderColor,
editing ? metrics.editOutlineThickness : metrics.borderThickness,
metrics.valueBoxRounding);
}
}
void AppendUIEditorPropertyGridForeground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorPropertyGridLayout& layout,
const std::vector<UIEditorPropertyGridSection>& sections,
const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel,
const UIEditorPropertyGridPalette& palette,
const UIEditorPropertyGridMetrics& metrics) {
drawList.PushClipRect(layout.bounds);
for (std::size_t sectionVisibleIndex = 0u;
sectionVisibleIndex < layout.sectionHeaderRects.size();
++sectionVisibleIndex) {
const UIEditorPropertyGridSection& section =
sections[layout.sectionIndices[sectionVisibleIndex]];
drawList.AddText(
ResolveDisclosureGlyphPosition(
layout.sectionDisclosureRects[sectionVisibleIndex],
metrics.sectionTextInsetY),
layout.sectionExpanded[sectionVisibleIndex] ? "v" : ">",
palette.disclosureColor,
12.0f);
drawList.AddText(
::XCEngine::UI::UIPoint(
layout.sectionTitleRects[sectionVisibleIndex].x,
layout.sectionTitleRects[sectionVisibleIndex].y + metrics.sectionTextInsetY),
section.title,
palette.sectionTextColor,
12.0f);
}
for (std::size_t visibleFieldIndex = 0u;
visibleFieldIndex < layout.fieldRowRects.size();
++visibleFieldIndex) {
const UIEditorPropertyGridSection& section =
sections[layout.visibleFieldSectionIndices[visibleFieldIndex]];
const UIEditorPropertyGridField& field =
section.fields[layout.visibleFieldIndices[visibleFieldIndex]];
const bool editing = propertyEditModel.HasActiveEdit() &&
propertyEditModel.GetActiveFieldId() == field.fieldId;
const std::string& displayedValue =
ResolveDisplayedFieldValue(field, propertyEditModel);
drawList.PushClipRect(layout.fieldLabelRects[visibleFieldIndex]);
drawList.AddText(
::XCEngine::UI::UIPoint(
layout.fieldLabelRects[visibleFieldIndex].x,
layout.fieldLabelRects[visibleFieldIndex].y + metrics.labelTextInsetY),
field.label,
palette.labelTextColor,
12.0f);
drawList.PopClipRect();
drawList.PushClipRect(layout.fieldValueRects[visibleFieldIndex]);
drawList.AddText(
::XCEngine::UI::UIPoint(
layout.fieldValueRects[visibleFieldIndex].x + metrics.valueBoxInsetX,
layout.fieldValueRects[visibleFieldIndex].y + metrics.valueTextInsetY),
displayedValue,
field.readOnly ? palette.readOnlyValueTextColor : palette.valueTextColor,
12.0f);
if (editing) {
drawList.AddText(
::XCEngine::UI::UIPoint(
layout.fieldValueRects[visibleFieldIndex].x +
(std::max)(0.0f, layout.fieldValueRects[visibleFieldIndex].width - 34.0f),
layout.fieldValueRects[visibleFieldIndex].y + metrics.valueTextInsetY),
"EDIT",
palette.editTagColor,
11.0f);
}
drawList.PopClipRect();
}
drawList.PopClipRect();
}
void AppendUIEditorPropertyGrid(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorPropertyGridSection>& sections,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel,
const UIEditorPropertyGridState& state,
const UIEditorPropertyGridPalette& palette,
const UIEditorPropertyGridMetrics& metrics) {
const UIEditorPropertyGridLayout layout =
BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics);
AppendUIEditorPropertyGridBackground(
drawList,
layout,
sections,
selectionModel,
propertyEditModel,
state,
palette,
metrics);
AppendUIEditorPropertyGridForeground(
drawList,
layout,
sections,
propertyEditModel,
palette,
metrics);
}
} // namespace XCEngine::UI::Editor::Widgets