Add editor list, scroll, and property grid widgets
This commit is contained in:
416
new_editor/src/Widgets/UIEditorPropertyGrid.cpp
Normal file
416
new_editor/src/Widgets/UIEditorPropertyGrid.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user