diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index c085448e..ef0d9154 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -43,6 +43,8 @@ set(XCUI_EDITOR_FIELD_SOURCES src/Fields/UIEditorObjectFieldInteraction.cpp src/Fields/UIEditorPropertyGrid.cpp src/Fields/UIEditorPropertyGridInteraction.cpp + src/Fields/UIEditorPropertyGridInteractionHelpers.cpp + src/Fields/UIEditorPropertyGridRendering.cpp src/Fields/UIEditorTextField.cpp src/Fields/UIEditorTextFieldInteraction.cpp src/Fields/UIEditorVector2Field.cpp diff --git a/new_editor/src/Fields/UIEditorPropertyGrid.cpp b/new_editor/src/Fields/UIEditorPropertyGrid.cpp index f02a8f04..7b7d53ce 100644 --- a/new_editor/src/Fields/UIEditorPropertyGrid.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGrid.cpp @@ -1,457 +1,12 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include "Fields/UIEditorPropertyGridInternal.h" #include -#include namespace XCEngine::UI::Editor::Widgets { -namespace { - -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; -using ::XCEngine::UI::UISize; -using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; -using ::XCEngine::UI::Widgets::UIPopupPlacement; -using ::XCEngine::UI::Editor::UIEditorMenuItemKind; - -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; -} - -UIPoint ResolveDisclosureGlyphPosition( - const UIRect& rect, - const UIEditorPropertyGridMetrics& metrics) { - return UIPoint( - rect.x + metrics.disclosureGlyphInsetX, - rect.y + metrics.sectionTextInsetY + metrics.disclosureGlyphInsetY); -} - -UIEditorBoolFieldSpec BuildBoolFieldSpec(const UIEditorPropertyGridField& field) { - UIEditorBoolFieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.value = field.boolValue; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorNumberFieldSpec BuildNumberFieldSpec(const UIEditorPropertyGridField& field) { - UIEditorNumberFieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.value = field.numberValue.value; - spec.step = field.numberValue.step; - spec.minValue = field.numberValue.minValue; - spec.maxValue = field.numberValue.maxValue; - spec.integerMode = field.numberValue.integerMode; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorEnumFieldSpec BuildEnumFieldSpec(const UIEditorPropertyGridField& field) { - UIEditorEnumFieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.options = field.enumValue.options; - spec.selectedIndex = field.enumValue.selectedIndex; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorColorFieldSpec BuildColorFieldSpec(const UIEditorPropertyGridField& field) { - UIEditorColorFieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.value = field.colorValue.value; - spec.showAlpha = field.colorValue.showAlpha; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorTextFieldSpec BuildTextFieldSpec(const UIEditorPropertyGridField& field) { - UIEditorTextFieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.value = field.valueText; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorVector2FieldSpec BuildVector2FieldSpec(const UIEditorPropertyGridField& field) { - UIEditorVector2FieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.values = field.vector2Value.values; - spec.componentLabels = field.vector2Value.componentLabels; - spec.step = field.vector2Value.step; - spec.minValue = field.vector2Value.minValue; - spec.maxValue = field.vector2Value.maxValue; - spec.integerMode = field.vector2Value.integerMode; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorVector3FieldSpec BuildVector3FieldSpec(const UIEditorPropertyGridField& field) { - UIEditorVector3FieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.values = field.vector3Value.values; - spec.componentLabels = field.vector3Value.componentLabels; - spec.step = field.vector3Value.step; - spec.minValue = field.vector3Value.minValue; - spec.maxValue = field.vector3Value.maxValue; - spec.integerMode = field.vector3Value.integerMode; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorVector4FieldSpec BuildVector4FieldSpec(const UIEditorPropertyGridField& field) { - UIEditorVector4FieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.values = field.vector4Value.values; - spec.componentLabels = field.vector4Value.componentLabels; - spec.step = field.vector4Value.step; - spec.minValue = field.vector4Value.minValue; - spec.maxValue = field.vector4Value.maxValue; - spec.integerMode = field.vector4Value.integerMode; - spec.readOnly = field.readOnly; - return spec; -} - -struct UIEditorPropertyGridFieldRects { - UIRect labelRect = {}; - UIRect valueRect = {}; -}; - -UIEditorPropertyGridFieldRects ResolveFieldRects( - const UIRect& rowRect, - const UIEditorPropertyGridField& field, - const UIEditorPropertyGridMetrics& metrics) { - switch (field.kind) { - case UIEditorPropertyGridFieldKind::Bool: { - const UIEditorBoolFieldLayout fieldLayout = BuildUIEditorBoolFieldLayout( - rowRect, - BuildBoolFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.controlRect }; - } - - case UIEditorPropertyGridFieldKind::Number: { - const UIEditorNumberFieldLayout fieldLayout = BuildUIEditorNumberFieldLayout( - rowRect, - BuildNumberFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.valueRect }; - } - - case UIEditorPropertyGridFieldKind::Enum: { - const UIEditorEnumFieldLayout fieldLayout = BuildUIEditorEnumFieldLayout( - rowRect, - BuildEnumFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.valueRect }; - } - - case UIEditorPropertyGridFieldKind::Color: { - const UIEditorColorFieldLayout fieldLayout = BuildUIEditorColorFieldLayout( - rowRect, - BuildColorFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.controlRect }; - } - - case UIEditorPropertyGridFieldKind::Vector2: { - const UIEditorVector2FieldLayout fieldLayout = BuildUIEditorVector2FieldLayout( - rowRect, - BuildVector2FieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.controlRect }; - } - - case UIEditorPropertyGridFieldKind::Vector3: { - const UIEditorVector3FieldLayout fieldLayout = BuildUIEditorVector3FieldLayout( - rowRect, - BuildVector3FieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.controlRect }; - } - - case UIEditorPropertyGridFieldKind::Vector4: { - const UIEditorVector4FieldLayout fieldLayout = BuildUIEditorVector4FieldLayout( - rowRect, - BuildVector4FieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.controlRect }; - } - - case UIEditorPropertyGridFieldKind::Text: - default: { - const UIEditorTextFieldLayout fieldLayout = BuildUIEditorTextFieldLayout( - rowRect, - BuildTextFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(metrics)); - return { fieldLayout.labelRect, fieldLayout.valueRect }; - } - } -} - -const std::string& ResolveDisplayedTextFieldValue( - const UIEditorPropertyGridField& field, - const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel) { - if (field.kind == UIEditorPropertyGridFieldKind::Text && - propertyEditModel.HasActiveEdit() && - propertyEditModel.GetActiveFieldId() == field.fieldId) { - return propertyEditModel.GetStagedValue(); - } - - return field.valueText; -} - -UIEditorBoolFieldHitTargetKind ResolveBoolHoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorBoolFieldHitTargetKind::None; - } - - return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox - ? UIEditorBoolFieldHitTargetKind::Checkbox - : UIEditorBoolFieldHitTargetKind::Row; -} - -UIEditorNumberFieldHitTargetKind ResolveNumberHoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorNumberFieldHitTargetKind::None; - } - - return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox - ? UIEditorNumberFieldHitTargetKind::ValueBox - : UIEditorNumberFieldHitTargetKind::Row; -} - -UIEditorEnumFieldHitTargetKind ResolveEnumHoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorEnumFieldHitTargetKind::None; - } - - return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox - ? UIEditorEnumFieldHitTargetKind::ValueBox - : UIEditorEnumFieldHitTargetKind::Row; -} - -UIEditorColorFieldHitTargetKind ResolveColorHoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorColorFieldHitTargetKind::None; - } - - return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox - ? UIEditorColorFieldHitTargetKind::Swatch - : UIEditorColorFieldHitTargetKind::Row; -} - -UIEditorTextFieldHitTargetKind ResolveTextHoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorTextFieldHitTargetKind::None; - } - - return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox - ? UIEditorTextFieldHitTargetKind::ValueBox - : UIEditorTextFieldHitTargetKind::Row; -} - -UIEditorVector2FieldHitTargetKind ResolveVector2HoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorVector2FieldHitTargetKind::None; - } - - return UIEditorVector2FieldHitTargetKind::Row; -} - -UIEditorVector3FieldHitTargetKind ResolveVector3HoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorVector3FieldHitTargetKind::None; - } - - return UIEditorVector3FieldHitTargetKind::Row; -} - -UIEditorVector4FieldHitTargetKind ResolveVector4HoveredTarget( - const UIEditorPropertyGridState& state, - const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorVector4FieldHitTargetKind::None; - } - - return UIEditorVector4FieldHitTargetKind::Row; -} - -std::vector BuildEnumPopupItems( - const UIEditorPropertyGridField& field) { - std::vector items = {}; - if (field.kind != UIEditorPropertyGridFieldKind::Enum) { - return items; - } - - items.reserve(field.enumValue.options.size()); - const std::size_t selectedIndex = - field.enumValue.options.empty() - ? 0u - : (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); - for (std::size_t index = 0u; index < field.enumValue.options.size(); ++index) { - UIEditorMenuPopupItem item = {}; - item.itemId = field.fieldId + "." + std::to_string(index); - item.kind = UIEditorMenuItemKind::Command; - item.label = field.enumValue.options[index]; - item.enabled = !field.readOnly; - item.checked = index == selectedIndex; - items.push_back(std::move(item)); - } - - return items; -} - -const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( - const UIEditorPropertyGridState& state, - std::string_view fieldId) { - for (const UIEditorPropertyGridColorFieldVisualState& entry : state.colorFieldStates) { - if (entry.fieldId == fieldId) { - return &entry; - } - } - - return nullptr; -} - -UIRect ResolvePopupViewportRect(const UIRect& bounds) { - return UIRect(bounds.x - 4096.0f, bounds.y - 4096.0f, 8192.0f, 8192.0f); -} - -bool BuildEnumPopupRuntime( - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const UIEditorPropertyGridState& state, - const UIEditorMenuPopupMetrics& popupMetrics, - UIEditorMenuPopupLayout& popupLayout, - UIEditorMenuPopupState& popupState, - std::vector& popupItems) { - if (state.popupFieldId.empty()) { - return false; - } - - const std::size_t visibleFieldIndex = - FindUIEditorPropertyGridVisibleFieldIndex(layout, state.popupFieldId, sections); - if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || - visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || - visibleFieldIndex >= layout.visibleFieldIndices.size()) { - return false; - } - - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - if (sectionIndex >= sections.size() || - fieldIndex >= sections[sectionIndex].fields.size()) { - return false; - } - - const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; - popupItems = BuildEnumPopupItems(field); - if (popupItems.empty()) { - return false; - } - - const float popupWidth = (std::max)( - layout.fieldValueRects[visibleFieldIndex].width, - ResolveUIEditorMenuPopupDesiredWidth(popupItems, popupMetrics)); - const float popupHeight = MeasureUIEditorMenuPopupHeight(popupItems, popupMetrics); - const auto placement = ResolvePopupPlacementRect( - layout.fieldValueRects[visibleFieldIndex], - UISize(popupWidth, popupHeight), - ResolvePopupViewportRect(layout.bounds), - UIPopupPlacement::BottomStart); - - popupLayout = BuildUIEditorMenuPopupLayout(placement.rect, popupItems, popupMetrics); - popupState.focused = state.focused || !state.popupFieldId.empty(); - popupState.hoveredIndex = - state.popupHighlightedIndex < popupItems.size() - ? state.popupHighlightedIndex - : UIEditorMenuPopupInvalidIndex; - return true; -} - -std::string FormatVector2ValueText(const UIEditorPropertyGridField& field) { - const UIEditorVector2FieldSpec spec = BuildVector2FieldSpec(field); - return FormatUIEditorVector2FieldComponentValue(spec, 0u) + ", " + - FormatUIEditorVector2FieldComponentValue(spec, 1u); -} - -std::string FormatColorValueText(const UIEditorPropertyGridField& field) { - return FormatUIEditorColorFieldHexText(BuildColorFieldSpec(field)); -} - -std::string FormatVector3ValueText(const UIEditorPropertyGridField& field) { - const UIEditorVector3FieldSpec spec = BuildVector3FieldSpec(field); - return FormatUIEditorVector3FieldComponentValue(spec, 0u) + ", " + - FormatUIEditorVector3FieldComponentValue(spec, 1u) + ", " + - FormatUIEditorVector3FieldComponentValue(spec, 2u); -} - -std::string FormatVector4ValueText(const UIEditorPropertyGridField& field) { - const UIEditorVector4FieldSpec spec = BuildVector4FieldSpec(field); - return FormatUIEditorVector4FieldComponentValue(spec, 0u) + ", " + - FormatUIEditorVector4FieldComponentValue(spec, 1u) + ", " + - FormatUIEditorVector4FieldComponentValue(spec, 2u) + ", " + - FormatUIEditorVector4FieldComponentValue(spec, 3u); -} - -} // namespace - bool IsUIEditorPropertyGridPointInside( - const UIRect& rect, - const UIPoint& point) { + 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 && @@ -492,22 +47,22 @@ std::string ResolveUIEditorPropertyGridFieldValueText( return field.boolValue ? "true" : "false"; case UIEditorPropertyGridFieldKind::Number: - return FormatUIEditorNumberFieldValue(BuildNumberFieldSpec(field)); + return FormatUIEditorNumberFieldValue(Detail::BuildNumberFieldSpec(field)); case UIEditorPropertyGridFieldKind::Enum: - return ResolveUIEditorEnumFieldValueText(BuildEnumFieldSpec(field)); + return ResolveUIEditorEnumFieldValueText(Detail::BuildEnumFieldSpec(field)); case UIEditorPropertyGridFieldKind::Color: - return FormatColorValueText(field); + return Detail::FormatColorValueText(field); case UIEditorPropertyGridFieldKind::Vector2: - return FormatVector2ValueText(field); + return Detail::FormatVector2ValueText(field); case UIEditorPropertyGridFieldKind::Vector3: - return FormatVector3ValueText(field); + return Detail::FormatVector3ValueText(field); case UIEditorPropertyGridFieldKind::Vector4: - return FormatVector4ValueText(field); + return Detail::FormatVector4ValueText(field); case UIEditorPropertyGridFieldKind::Text: default: @@ -538,44 +93,48 @@ std::size_t FindUIEditorPropertyGridVisibleFieldIndex( } UIEditorPropertyGridLayout BuildUIEditorPropertyGridLayout( - const UIRect& bounds, + const ::XCEngine::UI::UIRect& bounds, const std::vector& sections, const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel, const UIEditorPropertyGridMetrics& metrics) { UIEditorPropertyGridLayout layout = {}; - layout.bounds = UIRect( + layout.bounds = ::XCEngine::UI::UIRect( bounds.x, bounds.y, - ClampNonNegative(bounds.width), - ClampNonNegative(bounds.height)); + Detail::ClampNonNegative(bounds.width), + Detail::ClampNonNegative(bounds.height)); - const float inset = ClampNonNegative(metrics.contentInset); + const float inset = Detail::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); + 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 float headerHeight = + Detail::ResolveSectionHeaderHeight(section, metrics); const bool expanded = expansionModel.IsExpanded(section.sectionId); - const UIRect headerRect( + const ::XCEngine::UI::UIRect headerRect( contentX, cursorY, contentWidth, headerHeight); - const UIRect disclosureRect( + 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 UIRect titleRect( + const ::XCEngine::UI::UIRect titleRect( titleX, headerRect.y, - (std::max)(0.0f, headerRect.x + headerRect.width - titleX - metrics.horizontalPadding), + (std::max)( + 0.0f, + headerRect.x + headerRect.width - titleX - metrics.horizontalPadding), headerRect.height); layout.sectionIndices.push_back(sectionIndex); @@ -588,14 +147,15 @@ UIEditorPropertyGridLayout BuildUIEditorPropertyGridLayout( 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 UIRect rowRect( + const float rowHeight = + Detail::ResolveFieldRowHeight(field, metrics); + const ::XCEngine::UI::UIRect rowRect( contentX, cursorY, contentWidth, rowHeight); - const UIEditorPropertyGridFieldRects fieldRects = - ResolveFieldRects(rowRect, field, metrics); + const Detail::UIEditorPropertyGridFieldRects fieldRects = + Detail::ResolveFieldRects(rowRect, field, metrics); layout.visibleFieldSectionIndices.push_back(sectionIndex); layout.visibleFieldIndices.push_back(fieldIndex); @@ -620,11 +180,13 @@ UIEditorPropertyGridLayout BuildUIEditorPropertyGridLayout( UIEditorPropertyGridHitTarget HitTestUIEditorPropertyGrid( const UIEditorPropertyGridLayout& layout, - const UIPoint& point) { + const ::XCEngine::UI::UIPoint& point) { for (std::size_t sectionVisibleIndex = 0u; sectionVisibleIndex < layout.sectionHeaderRects.size(); ++sectionVisibleIndex) { - if (!IsUIEditorPropertyGridPointInside(layout.sectionHeaderRects[sectionVisibleIndex], point)) { + if (!IsUIEditorPropertyGridPointInside( + layout.sectionHeaderRects[sectionVisibleIndex], + point)) { continue; } @@ -637,7 +199,9 @@ UIEditorPropertyGridHitTarget HitTestUIEditorPropertyGrid( for (std::size_t visibleFieldIndex = 0u; visibleFieldIndex < layout.fieldRowRects.size(); ++visibleFieldIndex) { - if (!IsUIEditorPropertyGridPointInside(layout.fieldRowRects[visibleFieldIndex], point)) { + if (!IsUIEditorPropertyGridPointInside( + layout.fieldRowRects[visibleFieldIndex], + point)) { continue; } @@ -645,7 +209,9 @@ UIEditorPropertyGridHitTarget HitTestUIEditorPropertyGrid( target.visibleFieldIndex = visibleFieldIndex; target.sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; target.fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - target.kind = IsUIEditorPropertyGridPointInside(layout.fieldValueRects[visibleFieldIndex], point) + target.kind = IsUIEditorPropertyGridPointInside( + layout.fieldValueRects[visibleFieldIndex], + point) ? UIEditorPropertyGridHitTargetKind::ValueBox : UIEditorPropertyGridHitTargetKind::FieldRow; return target; @@ -654,357 +220,4 @@ UIEditorPropertyGridHitTarget HitTestUIEditorPropertyGrid( return {}; } -void AppendUIEditorPropertyGridBackground( - UIDrawList& drawList, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - const ::XCEngine::UI::Widgets::UIPropertyEditModel&, - 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; - - if (selected || hovered) { - drawList.AddFilledRect( - layout.fieldRowRects[visibleFieldIndex], - selected - ? (state.focused ? palette.fieldSelectedFocusedColor : palette.fieldSelectedColor) - : palette.fieldHoverColor, - metrics.cornerRounding); - } - } -} - -void AppendUIEditorPropertyGridForeground( - UIDrawList& drawList, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const UIEditorPropertyGridState& state, - const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - const UIEditorPropertyGridPalette& palette, - const UIEditorPropertyGridMetrics& metrics, - const UIEditorMenuPopupPalette& popupPalette, - const UIEditorMenuPopupMetrics& popupMetrics) { - 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), - layout.sectionExpanded[sectionVisibleIndex] ? "v" : ">", - palette.disclosureColor, - metrics.disclosureGlyphFontSize); - drawList.AddText( - UIPoint( - layout.sectionTitleRects[sectionVisibleIndex].x, - layout.sectionTitleRects[sectionVisibleIndex].y + metrics.sectionTextInsetY), - section.title, - palette.sectionTextColor, - metrics.sectionFontSize); - } - - const UIEditorBoolFieldMetrics boolMetrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldMetrics(metrics); - const UIEditorBoolFieldPalette boolPalette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldPalette(palette); - const UIEditorNumberFieldMetrics numberMetrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(metrics); - const UIEditorNumberFieldPalette numberPalette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldPalette(palette); - const UIEditorTextFieldMetrics textMetrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(metrics); - const UIEditorTextFieldPalette textPalette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette(palette); - const UIEditorVector2FieldMetrics vector2Metrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldMetrics(metrics); - const UIEditorVector2FieldPalette vector2Palette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldPalette(palette); - const UIEditorVector3FieldMetrics vector3Metrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldMetrics(metrics); - const UIEditorVector3FieldPalette vector3Palette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldPalette(palette); - const UIEditorVector4FieldMetrics vector4Metrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldMetrics(metrics); - const UIEditorVector4FieldPalette vector4Palette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldPalette(palette); - const UIEditorEnumFieldMetrics enumMetrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldMetrics(metrics); - const UIEditorEnumFieldPalette enumPalette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldPalette(palette); - const UIEditorColorFieldMetrics colorMetrics = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics(metrics); - const UIEditorColorFieldPalette colorPalette = - ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldPalette(palette); - const UIRect popupViewportRect = ResolvePopupViewportRect(layout.bounds); - - 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; - - switch (field.kind) { - case UIEditorPropertyGridFieldKind::Bool: { - UIEditorBoolFieldState fieldState = {}; - fieldState.hoveredTarget = ResolveBoolHoveredTarget(state, field); - fieldState.focused = state.focused; - fieldState.active = state.pressedFieldId == field.fieldId; - AppendUIEditorBoolField( - drawList, - layout.fieldRowRects[visibleFieldIndex], - BuildBoolFieldSpec(field), - fieldState, - boolPalette, - boolMetrics); - break; - } - - case UIEditorPropertyGridFieldKind::Number: { - UIEditorNumberFieldState fieldState = {}; - fieldState.hoveredTarget = ResolveNumberHoveredTarget(state, field); - fieldState.activeTarget = - state.pressedFieldId == field.fieldId - ? UIEditorNumberFieldHitTargetKind::ValueBox - : UIEditorNumberFieldHitTargetKind::None; - fieldState.focused = state.focused; - fieldState.editing = editing; - fieldState.displayText = editing - ? propertyEditModel.GetStagedValue() - : ResolveUIEditorPropertyGridFieldValueText(field); - AppendUIEditorNumberField( - drawList, - layout.fieldRowRects[visibleFieldIndex], - BuildNumberFieldSpec(field), - fieldState, - numberPalette, - numberMetrics); - break; - } - - case UIEditorPropertyGridFieldKind::Enum: { - UIEditorEnumFieldState fieldState = {}; - fieldState.hoveredTarget = ResolveEnumHoveredTarget(state, field); - fieldState.focused = state.focused; - fieldState.active = state.pressedFieldId == field.fieldId; - fieldState.popupOpen = state.popupFieldId == field.fieldId; - AppendUIEditorEnumField( - drawList, - layout.fieldRowRects[visibleFieldIndex], - BuildEnumFieldSpec(field), - fieldState, - enumPalette, - enumMetrics); - break; - } - - case UIEditorPropertyGridFieldKind::Color: { - UIEditorColorFieldState fieldState = {}; - if (const UIEditorPropertyGridColorFieldVisualState* visualState = - FindColorFieldVisualState(state, field.fieldId); - visualState != nullptr) { - fieldState = visualState->state; - } else { - fieldState.hoveredTarget = ResolveColorHoveredTarget(state, field); - fieldState.focused = state.focused; - } - - AppendUIEditorColorField( - drawList, - layout.fieldRowRects[visibleFieldIndex], - BuildColorFieldSpec(field), - fieldState, - colorPalette, - colorMetrics, - popupViewportRect); - break; - } - - case UIEditorPropertyGridFieldKind::Vector2: { - UIEditorVector2FieldState fieldState = {}; - fieldState.hoveredTarget = ResolveVector2HoveredTarget(state, field); - fieldState.activeTarget = - state.pressedFieldId == field.fieldId - ? UIEditorVector2FieldHitTargetKind::Row - : UIEditorVector2FieldHitTargetKind::None; - fieldState.focused = state.focused; - AppendUIEditorVector2Field( - drawList, - layout.fieldRowRects[visibleFieldIndex], - BuildVector2FieldSpec(field), - fieldState, - vector2Palette, - vector2Metrics); - break; - } - - case UIEditorPropertyGridFieldKind::Vector3: { - UIEditorVector3FieldState fieldState = {}; - fieldState.hoveredTarget = ResolveVector3HoveredTarget(state, field); - fieldState.activeTarget = - state.pressedFieldId == field.fieldId - ? UIEditorVector3FieldHitTargetKind::Row - : UIEditorVector3FieldHitTargetKind::None; - fieldState.focused = state.focused; - AppendUIEditorVector3Field( - drawList, - layout.fieldRowRects[visibleFieldIndex], - BuildVector3FieldSpec(field), - fieldState, - vector3Palette, - vector3Metrics); - break; - } - - case UIEditorPropertyGridFieldKind::Vector4: { - UIEditorVector4FieldState fieldState = {}; - fieldState.hoveredTarget = ResolveVector4HoveredTarget(state, field); - fieldState.activeTarget = - state.pressedFieldId == field.fieldId - ? UIEditorVector4FieldHitTargetKind::Row - : UIEditorVector4FieldHitTargetKind::None; - fieldState.focused = state.focused; - AppendUIEditorVector4Field( - drawList, - layout.fieldRowRects[visibleFieldIndex], - BuildVector4FieldSpec(field), - fieldState, - vector4Palette, - vector4Metrics); - break; - } - - case UIEditorPropertyGridFieldKind::Text: - default: { - const std::string& displayedValue = - ResolveDisplayedTextFieldValue(field, propertyEditModel); - UIEditorTextFieldState fieldState = {}; - fieldState.hoveredTarget = ResolveTextHoveredTarget(state, field); - fieldState.activeTarget = - state.pressedFieldId == field.fieldId - ? (state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox - ? UIEditorTextFieldHitTargetKind::ValueBox - : UIEditorTextFieldHitTargetKind::Row) - : UIEditorTextFieldHitTargetKind::None; - fieldState.focused = state.focused; - fieldState.editing = editing; - fieldState.displayText = displayedValue; - - UIEditorTextFieldSpec fieldSpec = BuildTextFieldSpec(field); - fieldSpec.value = editing ? std::string() : displayedValue; - AppendUIEditorTextField( - drawList, - layout.fieldRowRects[visibleFieldIndex], - fieldSpec, - fieldState, - textPalette, - textMetrics); - - if (editing) { - drawList.PushClipRect( - ResolveUIEditorTextClipRect( - layout.fieldValueRects[visibleFieldIndex], - metrics.tagFontSize)); - drawList.AddText( - UIPoint( - layout.fieldValueRects[visibleFieldIndex].x + - (std::max)(0.0f, layout.fieldValueRects[visibleFieldIndex].width - 34.0f), - ResolveUIEditorTextTop( - layout.fieldValueRects[visibleFieldIndex], - metrics.tagFontSize, - metrics.valueTextInsetY)), - "EDIT", - palette.editTagColor, - metrics.tagFontSize); - drawList.PopClipRect(); - } - break; - } - } - } - - drawList.PopClipRect(); - - UIEditorMenuPopupLayout popupLayout = {}; - UIEditorMenuPopupState popupState = {}; - std::vector popupItems = {}; - if (BuildEnumPopupRuntime(layout, sections, state, popupMetrics, popupLayout, popupState, popupItems)) { - AppendUIEditorMenuPopupBackground(drawList, popupLayout, popupItems, popupState, popupPalette, popupMetrics); - AppendUIEditorMenuPopupForeground(drawList, popupLayout, popupItems, popupState, popupPalette, popupMetrics); - } -} - -void AppendUIEditorPropertyGrid( - UIDrawList& drawList, - const UIRect& bounds, - const std::vector& 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 UIEditorMenuPopupPalette& popupPalette, - const UIEditorMenuPopupMetrics& popupMetrics) { - const UIEditorPropertyGridLayout layout = - BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - AppendUIEditorPropertyGridBackground( - drawList, - layout, - sections, - selectionModel, - propertyEditModel, - state, - palette, - metrics); - AppendUIEditorPropertyGridForeground( - drawList, - layout, - sections, - state, - propertyEditModel, - palette, - metrics, - popupPalette, - popupMetrics); -} - } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp index a92d863f..9854bc56 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp @@ -1,14 +1,7 @@ -#include - -#include -#include -#include -#include +#include "Fields/UIEditorPropertyGridInteractionInternal.h" #include -#include -#include #include namespace XCEngine::UI::Editor { @@ -21,19 +14,10 @@ using ::XCEngine::UI::Text::InsertCharacter; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::UISize; -using ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics; -using ::XCEngine::UI::Editor::UIEditorColorFieldInteractionFrame; -using ::XCEngine::UI::Editor::UIEditorColorFieldInteractionState; -using ::XCEngine::UI::Editor::UpdateUIEditorColorFieldInteraction; -using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; -using ::XCEngine::UI::Widgets::UIPopupPlacement; using Widgets::BuildUIEditorPropertyGridLayout; -using Widgets::FindUIEditorPropertyGridFieldLocation; -using Widgets::FindUIEditorPropertyGridVisibleFieldIndex; using Widgets::HitTestUIEditorPropertyGrid; -using Widgets::IsUIEditorPropertyGridPointInside; -using Widgets::ResolveUIEditorPropertyGridFieldValueText; +using Widgets::UIEditorMenuPopupHitTarget; +using Widgets::UIEditorMenuPopupHitTargetKind; using Widgets::UIEditorPropertyGridField; using Widgets::UIEditorPropertyGridFieldKind; using Widgets::UIEditorPropertyGridHitTarget; @@ -41,773 +25,6 @@ using Widgets::UIEditorPropertyGridHitTargetKind; using Widgets::UIEditorPropertyGridInvalidIndex; using Widgets::UIEditorPropertyGridLayout; using Widgets::UIEditorPropertyGridSection; -using Widgets::UIEditorPropertyGridColorFieldVisualState; -using Widgets::UIEditorMenuPopupHitTarget; -using Widgets::UIEditorMenuPopupHitTargetKind; -using Widgets::UIEditorMenuPopupInvalidIndex; -using Widgets::UIEditorMenuPopupItem; -using Widgets::UIEditorMenuPopupLayout; -using Widgets::UIEditorColorFieldHitTargetKind; -using Widgets::UIEditorColorFieldSpec; -using ::XCEngine::UI::Editor::UIEditorMenuItemKind; - -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - return true; - default: - return false; - } -} - -bool HasSystemModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) { - return modifiers.control || modifiers.alt || modifiers.super; -} - -bool ApplyKeyboardNavigation( - UIEditorPropertyGridInteractionState& state, - std::int32_t keyCode) { - switch (static_cast(keyCode)) { - case KeyCode::Up: - return state.keyboardNavigation.MovePrevious(); - case KeyCode::Down: - return state.keyboardNavigation.MoveNext(); - case KeyCode::Home: - return state.keyboardNavigation.MoveHome(); - case KeyCode::End: - return state.keyboardNavigation.MoveEnd(); - default: - return false; - } -} - -void ClearHoverState(UIEditorPropertyGridInteractionState& state) { - state.propertyGridState.hoveredSectionId.clear(); - state.propertyGridState.hoveredFieldId.clear(); - state.propertyGridState.hoveredHitTarget = UIEditorPropertyGridHitTargetKind::None; -} - -void ClearPopupState(UIEditorPropertyGridInteractionState& state) { - state.propertyGridState.popupFieldId.clear(); - state.propertyGridState.popupHighlightedIndex = UIEditorPropertyGridInvalidIndex; - state.pressedPopupIndex = UIEditorPropertyGridInvalidIndex; -} - -bool IsInlineEditable(const UIEditorPropertyGridField& field) { - return field.kind == UIEditorPropertyGridFieldKind::Text || - field.kind == UIEditorPropertyGridFieldKind::Number; -} - -bool IsNumberEditCharacter(const UIEditorPropertyGridField& field, std::uint32_t character) { - if (field.kind != UIEditorPropertyGridFieldKind::Number) { - return true; - } - - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; - } - - if (character == static_cast('-') || - character == static_cast('+')) { - return true; - } - - return !field.numberValue.integerMode && character == static_cast('.'); -} - -UIEditorColorFieldSpec BuildColorFieldSpec(const UIEditorPropertyGridField& field) { - UIEditorColorFieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.value = field.colorValue.value; - spec.showAlpha = field.colorValue.showAlpha; - spec.readOnly = field.readOnly; - return spec; -} - -UIEditorPropertyGridField* FindMutableField( - std::vector& sections, - std::string_view fieldId) { - const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); - if (!location.IsValid() || - location.sectionIndex >= sections.size() || - location.fieldIndex >= sections[location.sectionIndex].fields.size()) { - return nullptr; - } - - return §ions[location.sectionIndex].fields[location.fieldIndex]; -} - -const UIEditorPropertyGridField* FindField( - const std::vector& sections, - std::string_view fieldId) { - const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); - if (!location.IsValid() || - location.sectionIndex >= sections.size() || - location.fieldIndex >= sections[location.sectionIndex].fields.size()) { - return nullptr; - } - - return §ions[location.sectionIndex].fields[location.fieldIndex]; -} - -void SetChangedValueResult( - const UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - result.fieldValueChanged = true; - result.changedFieldId = field.fieldId; - result.changedValue = ResolveUIEditorPropertyGridFieldValueText(field); -} - -void MergeInteractionResult( - UIEditorPropertyGridInteractionResult& accumulated, - const UIEditorPropertyGridInteractionResult& current) { - accumulated.consumed = accumulated.consumed || current.consumed; - accumulated.sectionToggled = accumulated.sectionToggled || current.sectionToggled; - accumulated.selectionChanged = accumulated.selectionChanged || current.selectionChanged; - accumulated.keyboardNavigated = accumulated.keyboardNavigated || current.keyboardNavigated; - accumulated.editStarted = accumulated.editStarted || current.editStarted; - accumulated.editValueChanged = accumulated.editValueChanged || current.editValueChanged; - accumulated.editCommitted = accumulated.editCommitted || current.editCommitted; - accumulated.editCommitRejected = accumulated.editCommitRejected || current.editCommitRejected; - accumulated.editCanceled = accumulated.editCanceled || current.editCanceled; - accumulated.popupOpened = accumulated.popupOpened || current.popupOpened; - accumulated.popupClosed = accumulated.popupClosed || current.popupClosed; - accumulated.fieldValueChanged = accumulated.fieldValueChanged || current.fieldValueChanged; - accumulated.secondaryClicked = accumulated.secondaryClicked || current.secondaryClicked; - - if (current.hitTarget.kind != UIEditorPropertyGridHitTargetKind::None) { - accumulated.hitTarget = current.hitTarget; - } - if (!current.toggledSectionId.empty()) { - accumulated.toggledSectionId = current.toggledSectionId; - } - if (!current.selectedFieldId.empty()) { - accumulated.selectedFieldId = current.selectedFieldId; - } - if (!current.activeFieldId.empty()) { - accumulated.activeFieldId = current.activeFieldId; - } - if (!current.committedFieldId.empty()) { - accumulated.committedFieldId = current.committedFieldId; - } - if (!current.committedValue.empty()) { - accumulated.committedValue = current.committedValue; - } - if (!current.changedFieldId.empty()) { - accumulated.changedFieldId = current.changedFieldId; - } - if (!current.changedValue.empty()) { - accumulated.changedValue = current.changedValue; - } -} - -UIEditorPropertyGridColorFieldVisualState* FindMutableColorFieldVisualState( - UIEditorPropertyGridInteractionState& state, - std::string_view fieldId) { - for (UIEditorPropertyGridColorFieldVisualState& entry : state.propertyGridState.colorFieldStates) { - if (entry.fieldId == fieldId) { - return &entry; - } - } - - return nullptr; -} - -const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( - const UIEditorPropertyGridInteractionState& state, - std::string_view fieldId) { - for (const UIEditorPropertyGridColorFieldVisualState& entry : state.propertyGridState.colorFieldStates) { - if (entry.fieldId == fieldId) { - return &entry; - } - } - - return nullptr; -} - -UIEditorColorFieldInteractionState BuildColorFieldInteractionState( - const UIEditorPropertyGridInteractionState& state, - std::string_view fieldId) { - UIEditorColorFieldInteractionState interactionState = {}; - if (const UIEditorPropertyGridColorFieldVisualState* entry = - FindColorFieldVisualState(state, fieldId); - entry != nullptr) { - interactionState.colorFieldState = entry->state; - } - - interactionState.pointerPosition = state.pointerPosition; - interactionState.hasPointerPosition = state.hasPointerPosition; - return interactionState; -} - -void StoreColorFieldVisualState( - UIEditorPropertyGridInteractionState& state, - std::string_view fieldId, - const UIEditorColorFieldInteractionState& interactionState) { - if (UIEditorPropertyGridColorFieldVisualState* entry = - FindMutableColorFieldVisualState(state, fieldId); - entry != nullptr) { - entry->state = interactionState.colorFieldState; - return; - } - - UIEditorPropertyGridColorFieldVisualState entry = {}; - entry.fieldId = std::string(fieldId); - entry.state = interactionState.colorFieldState; - state.propertyGridState.colorFieldStates.push_back(std::move(entry)); -} - -void PruneColorFieldVisualStates( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections) { - const auto isVisibleColorFieldId = [&layout, §ions](std::string_view fieldId) { - const std::size_t visibleFieldIndex = - FindUIEditorPropertyGridVisibleFieldIndex(layout, fieldId, sections); - if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || - visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || - visibleFieldIndex >= layout.visibleFieldIndices.size()) { - return false; - } - - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - return sectionIndex < sections.size() && - fieldIndex < sections[sectionIndex].fields.size() && - sections[sectionIndex].fields[fieldIndex].kind == UIEditorPropertyGridFieldKind::Color; - }; - - state.propertyGridState.colorFieldStates.erase( - std::remove_if( - state.propertyGridState.colorFieldStates.begin(), - state.propertyGridState.colorFieldStates.end(), - [&isVisibleColorFieldId](const UIEditorPropertyGridColorFieldVisualState& entry) { - return !isVisibleColorFieldId(entry.fieldId); - }), - state.propertyGridState.colorFieldStates.end()); -} - -void CloseOtherColorFieldPopups( - UIEditorPropertyGridInteractionState& state, - std::string_view keepFieldId) { - for (UIEditorPropertyGridColorFieldVisualState& entry : state.propertyGridState.colorFieldStates) { - if (entry.fieldId == keepFieldId) { - continue; - } - - entry.state.popupOpen = false; - entry.state.activeTarget = UIEditorColorFieldHitTargetKind::None; - } -} - -std::vector BuildPopupItems( - const UIEditorPropertyGridField& field) { - std::vector items = {}; - if (field.kind != UIEditorPropertyGridFieldKind::Enum) { - return items; - } - - items.reserve(field.enumValue.options.size()); - const std::size_t selectedIndex = - field.enumValue.options.empty() - ? 0u - : (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); - for (std::size_t index = 0u; index < field.enumValue.options.size(); ++index) { - UIEditorMenuPopupItem item = {}; - item.itemId = field.fieldId + "." + std::to_string(index); - item.kind = UIEditorMenuItemKind::Command; - item.label = field.enumValue.options[index]; - item.enabled = !field.readOnly; - item.checked = index == selectedIndex; - items.push_back(std::move(item)); - } - - return items; -} - -::XCEngine::UI::UIRect ResolvePopupViewportRect( - const ::XCEngine::UI::UIRect& bounds) { - return ::XCEngine::UI::UIRect(bounds.x - 4096.0f, bounds.y - 4096.0f, 8192.0f, 8192.0f); -} - -bool BuildPopupLayout( - const UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const Widgets::UIEditorMenuPopupMetrics& popupMetrics, - UIEditorMenuPopupLayout& popupLayout, - std::vector& popupItems) { - if (state.propertyGridState.popupFieldId.empty()) { - return false; - } - - const std::size_t visibleFieldIndex = - FindUIEditorPropertyGridVisibleFieldIndex( - layout, - state.propertyGridState.popupFieldId, - sections); - if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || - visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || - visibleFieldIndex >= layout.visibleFieldIndices.size()) { - return false; - } - - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - if (sectionIndex >= sections.size() || - fieldIndex >= sections[sectionIndex].fields.size()) { - return false; - } - - const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; - popupItems = BuildPopupItems(field); - if (popupItems.empty()) { - return false; - } - - const float popupWidth = (std::max)( - layout.fieldValueRects[visibleFieldIndex].width, - Widgets::ResolveUIEditorMenuPopupDesiredWidth(popupItems, popupMetrics)); - const float popupHeight = Widgets::MeasureUIEditorMenuPopupHeight(popupItems, popupMetrics); - const auto placement = ResolvePopupPlacementRect( - layout.fieldValueRects[visibleFieldIndex], - UISize(popupWidth, popupHeight), - ResolvePopupViewportRect(layout.bounds), - UIPopupPlacement::BottomStart); - popupLayout = Widgets::BuildUIEditorMenuPopupLayout(placement.rect, popupItems, popupMetrics); - return true; -} - -UIEditorMenuPopupHitTarget ResolvePopupHit( - const UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { - if (!state.hasPointerPosition) { - return {}; - } - - UIEditorMenuPopupLayout popupLayout = {}; - std::vector popupItems = {}; - if (!BuildPopupLayout(state, layout, sections, popupMetrics, popupLayout, popupItems)) { - return {}; - } - - return Widgets::HitTestUIEditorMenuPopup(popupLayout, popupItems, state.pointerPosition); -} - -void SyncHoverTarget( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { - ClearHoverState(state); - if (!state.hasPointerPosition) { - return; - } - - const UIEditorMenuPopupHitTarget popupHit = ResolvePopupHit(state, layout, sections, popupMetrics); - if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) { - state.propertyGridState.popupHighlightedIndex = popupHit.index; - return; - } - if (popupHit.kind != UIEditorMenuPopupHitTargetKind::None) { - return; - } - - const UIEditorPropertyGridHitTarget hitTarget = - HitTestUIEditorPropertyGrid(layout, state.pointerPosition); - state.propertyGridState.hoveredHitTarget = hitTarget.kind; - if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader && - hitTarget.sectionIndex < sections.size()) { - state.propertyGridState.hoveredSectionId = sections[hitTarget.sectionIndex].sectionId; - return; - } - - if ((hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow || - hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) && - hitTarget.sectionIndex < sections.size() && - hitTarget.fieldIndex < sections[hitTarget.sectionIndex].fields.size()) { - state.propertyGridState.hoveredFieldId = - sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex].fieldId; - } -} - -void SyncKeyboardNavigation( - UIEditorPropertyGridInteractionState& state, - const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections) { - state.keyboardNavigation.SetItemCount(layout.visibleFieldIndices.size()); - state.keyboardNavigation.ClampToItemCount(); - - if (!selectionModel.HasSelection()) { - return; - } - - const std::size_t selectedVisibleIndex = - FindUIEditorPropertyGridVisibleFieldIndex( - layout, - selectionModel.GetSelectedId(), - sections); - if (selectedVisibleIndex == UIEditorPropertyGridInvalidIndex) { - return; - } - - if (!state.keyboardNavigation.HasCurrentIndex() || - state.keyboardNavigation.GetCurrentIndex() != selectedVisibleIndex) { - state.keyboardNavigation.SetCurrentIndex(selectedVisibleIndex); - } -} - -bool SelectVisibleField( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - std::size_t visibleFieldIndex, - UIEditorPropertyGridInteractionResult& result) { - if (visibleFieldIndex >= layout.visibleFieldIndices.size() || - visibleFieldIndex >= layout.visibleFieldSectionIndices.size()) { - return false; - } - - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - if (sectionIndex >= sections.size() || - fieldIndex >= sections[sectionIndex].fields.size()) { - return false; - } - - const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; - result.selectionChanged = selectionModel.SetSelection(field.fieldId); - result.selectedFieldId = field.fieldId; - result.consumed = true; - state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); - return true; -} - -bool BeginFieldEdit( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - const UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - if (field.readOnly || !IsInlineEditable(field)) { - return false; - } - - const std::string initialValue = - field.kind == UIEditorPropertyGridFieldKind::Text - ? field.valueText - : ResolveUIEditorPropertyGridFieldValueText(field); - const bool changed = propertyEditModel.BeginEdit(field.fieldId, initialValue); - if (!changed && - (!propertyEditModel.HasActiveEdit() || - propertyEditModel.GetActiveFieldId() != field.fieldId)) { - return false; - } - - state.textInputState.value = propertyEditModel.GetStagedValue(); - state.textInputState.caret = state.textInputState.value.size(); - result.editStarted = changed; - result.activeFieldId = field.fieldId; - result.consumed = true; - return true; -} - -bool CommitActiveEdit( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - std::vector& sections, - UIEditorPropertyGridInteractionResult& result) { - if (!propertyEditModel.HasActiveEdit()) { - return false; - } - - UIEditorPropertyGridField* field = - FindMutableField(sections, propertyEditModel.GetActiveFieldId()); - if (field == nullptr) { - propertyEditModel.CancelEdit(); - state.textInputState = {}; - return false; - } - - result.activeFieldId = field->fieldId; - if (field->kind == UIEditorPropertyGridFieldKind::Number) { - Widgets::UIEditorNumberFieldSpec spec = {}; - spec.fieldId = field->fieldId; - spec.label = field->label; - spec.value = field->numberValue.value; - spec.step = field->numberValue.step; - spec.minValue = field->numberValue.minValue; - spec.maxValue = field->numberValue.maxValue; - spec.integerMode = field->numberValue.integerMode; - spec.readOnly = field->readOnly; - - double parsedValue = field->numberValue.value; - if (!Widgets::TryParseUIEditorNumberFieldValue( - spec, - propertyEditModel.GetStagedValue(), - parsedValue)) { - result.editCommitRejected = true; - result.consumed = true; - return false; - } - - field->numberValue.value = parsedValue; - result.committedFieldId = field->fieldId; - result.committedValue = ResolveUIEditorPropertyGridFieldValueText(*field); - SetChangedValueResult(*field, result); - } else { - field->valueText = propertyEditModel.GetStagedValue(); - result.committedFieldId = field->fieldId; - result.committedValue = field->valueText; - SetChangedValueResult(*field, result); - } - - propertyEditModel.CommitEdit(); - state.textInputState = {}; - result.editCommitted = true; - result.consumed = true; - return true; -} - -bool CancelActiveEdit( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - UIEditorPropertyGridInteractionResult& result) { - if (!propertyEditModel.HasActiveEdit()) { - return false; - } - - result.activeFieldId = propertyEditModel.GetActiveFieldId(); - if (!propertyEditModel.CancelEdit()) { - return false; - } - - state.textInputState = {}; - result.editCanceled = true; - result.consumed = true; - return true; -} - -void ClosePopup( - UIEditorPropertyGridInteractionState& state, - UIEditorPropertyGridInteractionResult& result) { - if (state.propertyGridState.popupFieldId.empty()) { - return; - } - - ClearPopupState(state); - result.popupClosed = true; - result.consumed = true; -} - -void OpenPopup( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - if (field.kind != UIEditorPropertyGridFieldKind::Enum || - field.readOnly || - field.enumValue.options.empty()) { - return; - } - - if (state.propertyGridState.popupFieldId == field.fieldId) { - return; - } - - state.propertyGridState.popupFieldId = field.fieldId; - state.propertyGridState.popupHighlightedIndex = - (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); - result.popupOpened = true; - result.consumed = true; -} - -void MovePopupHighlight( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridField& field, - int delta) { - if (field.kind != UIEditorPropertyGridFieldKind::Enum || - field.enumValue.options.empty()) { - state.propertyGridState.popupHighlightedIndex = UIEditorPropertyGridInvalidIndex; - return; - } - - if (state.propertyGridState.popupHighlightedIndex == UIEditorPropertyGridInvalidIndex || - state.propertyGridState.popupHighlightedIndex >= field.enumValue.options.size()) { - state.propertyGridState.popupHighlightedIndex = - (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); - } - - const std::size_t currentIndex = state.propertyGridState.popupHighlightedIndex; - if (delta < 0) { - state.propertyGridState.popupHighlightedIndex = currentIndex == 0u ? 0u : currentIndex - 1u; - } else { - state.propertyGridState.popupHighlightedIndex = - currentIndex + 1u >= field.enumValue.options.size() - ? field.enumValue.options.size() - 1u - : currentIndex + 1u; - } -} - -void JumpPopupHighlightToEdge( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridField& field, - bool toEnd) { - if (field.kind != UIEditorPropertyGridFieldKind::Enum || - field.enumValue.options.empty()) { - state.propertyGridState.popupHighlightedIndex = UIEditorPropertyGridInvalidIndex; - return; - } - - state.propertyGridState.popupHighlightedIndex = toEnd - ? field.enumValue.options.size() - 1u - : 0u; -} - -bool CommitPopupSelection( - UIEditorPropertyGridInteractionState& state, - std::vector& sections, - UIEditorPropertyGridInteractionResult& result) { - UIEditorPropertyGridField* field = - FindMutableField(sections, state.propertyGridState.popupFieldId); - if (field == nullptr || - field->kind != UIEditorPropertyGridFieldKind::Enum || - field->readOnly || - field->enumValue.options.empty() || - state.propertyGridState.popupHighlightedIndex == UIEditorPropertyGridInvalidIndex || - state.propertyGridState.popupHighlightedIndex >= field->enumValue.options.size()) { - return false; - } - - field->enumValue.selectedIndex = state.propertyGridState.popupHighlightedIndex; - SetChangedValueResult(*field, result); - ClosePopup(state, result); - return true; -} - -bool ToggleBoolField( - UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - if (field.kind != UIEditorPropertyGridFieldKind::Bool || field.readOnly) { - return false; - } - - field.boolValue = !field.boolValue; - SetChangedValueResult(field, result); - result.consumed = true; - return true; -} - -bool ProcessColorFieldEvent( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - const UIEditorPropertyGridLayout& layout, - std::vector& sections, - const Widgets::UIEditorPropertyGridMetrics& metrics, - const UIInputEvent& event, - UIEditorPropertyGridInteractionResult& result) { - const Widgets::UIEditorColorFieldMetrics colorMetrics = - BuildUIEditorPropertyGridColorFieldMetrics(metrics); - const ::XCEngine::UI::UIRect popupViewportRect = ResolvePopupViewportRect(layout.bounds); - bool handled = false; - - for (std::size_t visibleFieldIndex = 0u; - visibleFieldIndex < layout.visibleFieldIndices.size(); - ++visibleFieldIndex) { - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - if (sectionIndex >= sections.size() || - fieldIndex >= sections[sectionIndex].fields.size()) { - continue; - } - - UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; - if (field.kind != UIEditorPropertyGridFieldKind::Color) { - continue; - } - - UIEditorColorFieldInteractionState colorState = - BuildColorFieldInteractionState(state, field.fieldId); - const bool popupWasOpen = colorState.colorFieldState.popupOpen; - UIEditorColorFieldSpec spec = BuildColorFieldSpec(field); - const UIEditorColorFieldInteractionFrame frame = - UpdateUIEditorColorFieldInteraction( - colorState, - spec, - layout.fieldRowRects[visibleFieldIndex], - { event }, - colorMetrics, - popupViewportRect); - - if (!frame.result.consumed && - frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::None && - !popupWasOpen && - !colorState.colorFieldState.popupOpen) { - continue; - } - - handled = true; - field.colorValue.value = spec.value; - field.colorValue.showAlpha = spec.showAlpha; - StoreColorFieldVisualState(state, field.fieldId, colorState); - - if (frame.result.popupOpened) { - ClosePopup(state, result); - CloseOtherColorFieldPopups(state, field.fieldId); - } - - state.propertyGridState.focused = colorState.colorFieldState.focused; - state.propertyGridState.pressedFieldId.clear(); - - if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || - frame.result.popupOpened || - frame.result.colorChanged || - popupWasOpen || - colorState.colorFieldState.popupOpen) { - result.selectionChanged = - selectionModel.SetSelection(field.fieldId) || result.selectionChanged; - result.selectedFieldId = field.fieldId; - result.activeFieldId = field.fieldId; - state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); - } - - if (propertyEditModel.HasActiveEdit() && - propertyEditModel.GetActiveFieldId() != field.fieldId && - (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || - frame.result.popupOpened)) { - CommitActiveEdit(state, propertyEditModel, sections, result); - } - - result.popupOpened = result.popupOpened || frame.result.popupOpened; - result.popupClosed = result.popupClosed || frame.result.popupClosed; - result.consumed = result.consumed || frame.result.consumed; - if (frame.result.colorChanged) { - SetChangedValueResult(field, result); - } - - if (frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::Row) { - result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::FieldRow; - result.hitTarget.sectionIndex = sectionIndex; - result.hitTarget.fieldIndex = fieldIndex; - result.hitTarget.visibleFieldIndex = visibleFieldIndex; - } else if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None) { - result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::ValueBox; - result.hitTarget.sectionIndex = sectionIndex; - result.hitTarget.fieldIndex = fieldIndex; - result.hitTarget.visibleFieldIndex = visibleFieldIndex; - } - } - - return handled; -} } // namespace @@ -823,13 +40,13 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { UIEditorPropertyGridLayout layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - PruneColorFieldVisualStates(state, layout, sections); - SyncKeyboardNavigation(state, selectionModel, layout, sections); - SyncHoverTarget(state, layout, sections, popupMetrics); + Detail::PruneColorFieldVisualStates(state, layout, sections); + Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Detail::SyncHoverTarget(state, layout, sections, popupMetrics); UIEditorPropertyGridInteractionResult interactionResult = {}; for (const UIInputEvent& event : inputEvents) { - if (ShouldUsePointerPosition(event)) { + if (Detail::ShouldUsePointerPosition(event)) { state.pointerPosition = event.position; state.hasPointerPosition = true; } else if (event.type == UIInputEventType::PointerLeave) { @@ -837,14 +54,15 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } UIEditorPropertyGridInteractionResult eventResult = {}; - const UIEditorMenuPopupHitTarget popupHit = ResolvePopupHit(state, layout, sections, popupMetrics); + const UIEditorMenuPopupHitTarget popupHit = + Detail::ResolvePopupHit(state, layout, sections, popupMetrics); const UIEditorPropertyGridHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorPropertyGrid(layout, state.pointerPosition) : UIEditorPropertyGridHitTarget {}; eventResult.hitTarget = hitTarget; - if (ProcessColorFieldEvent( + if (Detail::ProcessColorFieldEvent( state, selectionModel, propertyEditModel, @@ -853,11 +71,11 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( metrics, event, eventResult)) { - MergeInteractionResult(interactionResult, eventResult); + Detail::MergeInteractionResult(interactionResult, eventResult); layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - PruneColorFieldVisualStates(state, layout, sections); - SyncKeyboardNavigation(state, selectionModel, layout, sections); - SyncHoverTarget(state, layout, sections, popupMetrics); + Detail::PruneColorFieldVisualStates(state, layout, sections); + Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Detail::SyncHoverTarget(state, layout, sections, popupMetrics); continue; } @@ -867,12 +85,12 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( break; case UIInputEventType::FocusLost: - CommitActiveEdit(state, propertyEditModel, sections, eventResult); - ClosePopup(state, eventResult); + Detail::CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Detail::ClosePopup(state, eventResult); state.propertyGridState.focused = false; state.propertyGridState.pressedFieldId.clear(); state.hasPointerPosition = false; - ClearHoverState(state); + Detail::ClearHoverState(state); break; case UIInputEventType::PointerMove: @@ -884,7 +102,9 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (event.pointerButton == UIPointerButton::Left) { const bool insideGrid = state.hasPointerPosition && - IsUIEditorPropertyGridPointInside(layout.bounds, state.pointerPosition); + Widgets::IsUIEditorPropertyGridPointInside( + layout.bounds, + state.pointerPosition); if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) { state.propertyGridState.focused = true; state.pressedPopupIndex = popupHit.index; @@ -893,12 +113,14 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( state.propertyGridState.focused = true; eventResult.consumed = true; } else if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow || - hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) { + hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) { state.propertyGridState.focused = true; state.propertyGridState.pressedFieldId = sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex].fieldId; eventResult.consumed = true; - } else if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader || insideGrid) { + } else if (hitTarget.kind == + UIEditorPropertyGridHitTargetKind::SectionHeader || + insideGrid) { state.propertyGridState.focused = true; state.propertyGridState.pressedFieldId.clear(); eventResult.consumed = true; @@ -914,15 +136,17 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (event.pointerButton == UIPointerButton::Left) { const bool insideGrid = state.hasPointerPosition && - IsUIEditorPropertyGridPointInside(layout.bounds, state.pointerPosition); + Widgets::IsUIEditorPropertyGridPointInside( + layout.bounds, + state.pointerPosition); if (state.pressedPopupIndex != UIEditorPropertyGridInvalidIndex) { if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item && popupHit.index == state.pressedPopupIndex) { eventResult.fieldValueChanged = - CommitPopupSelection(state, sections, eventResult); + Detail::CommitPopupSelection(state, sections, eventResult); } else if (popupHit.kind == UIEditorMenuPopupHitTargetKind::None) { - ClosePopup(state, eventResult); + Detail::ClosePopup(state, eventResult); } state.pressedPopupIndex = UIEditorPropertyGridInvalidIndex; state.propertyGridState.pressedFieldId.clear(); @@ -937,8 +161,8 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::None) { - CommitActiveEdit(state, propertyEditModel, sections, eventResult); - ClosePopup(state, eventResult); + Detail::CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Detail::ClosePopup(state, eventResult); state.propertyGridState.focused = insideGrid; state.propertyGridState.pressedFieldId.clear(); if (insideGrid) { @@ -949,10 +173,12 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader && hitTarget.sectionIndex < sections.size()) { - CommitActiveEdit(state, propertyEditModel, sections, eventResult); - ClosePopup(state, eventResult); - const std::string& sectionId = sections[hitTarget.sectionIndex].sectionId; - eventResult.sectionToggled = expansionModel.ToggleExpanded(sectionId); + Detail::CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Detail::ClosePopup(state, eventResult); + const std::string& sectionId = + sections[hitTarget.sectionIndex].sectionId; + eventResult.sectionToggled = + expansionModel.ToggleExpanded(sectionId); eventResult.toggledSectionId = sectionId; eventResult.consumed = true; state.propertyGridState.focused = true; @@ -967,7 +193,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( UIEditorPropertyGridField& field = sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex]; state.propertyGridState.focused = true; - SelectVisibleField( + Detail::SelectVisibleField( state, selectionModel, layout, @@ -977,45 +203,67 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) { if (field.kind == UIEditorPropertyGridFieldKind::Bool) { - CommitActiveEdit(state, propertyEditModel, sections, eventResult); - ClosePopup(state, eventResult); - ToggleBoolField(field, eventResult); + Detail::CommitActiveEdit( + state, + propertyEditModel, + sections, + eventResult); + Detail::ClosePopup(state, eventResult); + Detail::ToggleBoolField(field, eventResult); } else if (field.kind == UIEditorPropertyGridFieldKind::Enum) { - CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Detail::CommitActiveEdit( + state, + propertyEditModel, + sections, + eventResult); if (state.propertyGridState.popupFieldId == field.fieldId) { - ClosePopup(state, eventResult); + Detail::ClosePopup(state, eventResult); } else { - ClearPopupState(state); - OpenPopup(state, field, eventResult); + Detail::ClearPopupState(state); + Detail::OpenPopup(state, field, eventResult); } } else { - ClosePopup(state, eventResult); + Detail::ClosePopup(state, eventResult); if (!(propertyEditModel.HasActiveEdit() && - propertyEditModel.GetActiveFieldId() == field.fieldId)) { - CommitActiveEdit(state, propertyEditModel, sections, eventResult); - BeginFieldEdit(state, propertyEditModel, field, eventResult); + propertyEditModel.GetActiveFieldId() == + field.fieldId)) { + Detail::CommitActiveEdit( + state, + propertyEditModel, + sections, + eventResult); + Detail::BeginFieldEdit( + state, + propertyEditModel, + field, + eventResult); } } } else if (state.propertyGridState.popupFieldId != field.fieldId) { - ClosePopup(state, eventResult); + Detail::ClosePopup(state, eventResult); } } state.propertyGridState.pressedFieldId.clear(); } else if (event.pointerButton == UIPointerButton::Right && - (hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow || - hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) && - hitTarget.sectionIndex < sections.size() && - hitTarget.fieldIndex < sections[hitTarget.sectionIndex].fields.size()) { + (hitTarget.kind == + UIEditorPropertyGridHitTargetKind::FieldRow || + hitTarget.kind == + UIEditorPropertyGridHitTargetKind::ValueBox) && + hitTarget.sectionIndex < sections.size() && + hitTarget.fieldIndex < + sections[hitTarget.sectionIndex].fields.size()) { const UIEditorPropertyGridField& field = sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex]; - eventResult.selectionChanged = selectionModel.SetSelection(field.fieldId); + eventResult.selectionChanged = + selectionModel.SetSelection(field.fieldId); eventResult.selectedFieldId = field.fieldId; eventResult.secondaryClicked = true; eventResult.consumed = true; state.propertyGridState.focused = true; if (hitTarget.visibleFieldIndex != UIEditorPropertyGridInvalidIndex) { - state.keyboardNavigation.SetCurrentIndex(hitTarget.visibleFieldIndex); + state.keyboardNavigation.SetCurrentIndex( + hitTarget.visibleFieldIndex); } } break; @@ -1027,33 +275,40 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } if (!state.propertyGridState.popupFieldId.empty()) { - const UIEditorPropertyGridField* popupField = - FindField(sections, state.propertyGridState.popupFieldId); + const UIEditorPropertyGridField* popupField = Detail::FindField( + sections, + state.propertyGridState.popupFieldId); if (popupField != nullptr) { switch (static_cast(event.keyCode)) { case KeyCode::Up: - MovePopupHighlight(state, *popupField, -1); + Detail::MovePopupHighlight(state, *popupField, -1); eventResult.consumed = true; break; case KeyCode::Down: - MovePopupHighlight(state, *popupField, 1); + Detail::MovePopupHighlight(state, *popupField, 1); eventResult.consumed = true; break; case KeyCode::Home: - JumpPopupHighlightToEdge(state, *popupField, false); + Detail::JumpPopupHighlightToEdge( + state, + *popupField, + false); eventResult.consumed = true; break; case KeyCode::End: - JumpPopupHighlightToEdge(state, *popupField, true); + Detail::JumpPopupHighlightToEdge( + state, + *popupField, + true); eventResult.consumed = true; break; case KeyCode::Enter: case KeyCode::Space: - CommitPopupSelection(state, sections, eventResult); + Detail::CommitPopupSelection(state, sections, eventResult); eventResult.consumed = true; break; case KeyCode::Escape: - ClosePopup(state, eventResult); + Detail::ClosePopup(state, eventResult); break; default: break; @@ -1064,7 +319,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (propertyEditModel.HasActiveEdit()) { if (static_cast(event.keyCode) == KeyCode::Escape) { - CancelActiveEdit(state, propertyEditModel, eventResult); + Detail::CancelActiveEdit(state, propertyEditModel, eventResult); break; } @@ -1081,7 +336,11 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( eventResult.editValueChanged = true; } if (editResult.submitRequested) { - CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Detail::CommitActiveEdit( + state, + propertyEditModel, + sections, + eventResult); } } break; @@ -1090,26 +349,34 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if ((static_cast(event.keyCode) == KeyCode::Enter || static_cast(event.keyCode) == KeyCode::Space) && selectionModel.HasSelection()) { - UIEditorPropertyGridField* field = - FindMutableField(sections, selectionModel.GetSelectedId()); + UIEditorPropertyGridField* field = Detail::FindMutableField( + sections, + selectionModel.GetSelectedId()); if (field != nullptr) { if (field->kind == UIEditorPropertyGridFieldKind::Bool) { - ToggleBoolField(*field, eventResult); - } else if (field->kind == UIEditorPropertyGridFieldKind::Enum) { - OpenPopup(state, *field, eventResult); - } else if (static_cast(event.keyCode) == KeyCode::Enter) { - BeginFieldEdit(state, propertyEditModel, *field, eventResult); + Detail::ToggleBoolField(*field, eventResult); + } else if (field->kind == + UIEditorPropertyGridFieldKind::Enum) { + Detail::OpenPopup(state, *field, eventResult); + } else if (static_cast(event.keyCode) == + KeyCode::Enter) { + Detail::BeginFieldEdit( + state, + propertyEditModel, + *field, + eventResult); } } break; } if (!event.modifiers.shift && - !HasSystemModifiers(event.modifiers) && - ApplyKeyboardNavigation(state, event.keyCode) && + !Detail::HasSystemModifiers(event.modifiers) && + Detail::ApplyKeyboardNavigation(state, event.keyCode) && state.keyboardNavigation.HasCurrentIndex()) { - const std::size_t currentIndex = state.keyboardNavigation.GetCurrentIndex(); - if (SelectVisibleField( + const std::size_t currentIndex = + state.keyboardNavigation.GetCurrentIndex(); + if (Detail::SelectVisibleField( state, selectionModel, layout, @@ -1124,13 +391,15 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( case UIInputEventType::Character: if (!state.propertyGridState.focused || !propertyEditModel.HasActiveEdit() || - HasSystemModifiers(event.modifiers)) { + Detail::HasSystemModifiers(event.modifiers)) { break; } - if (const UIEditorPropertyGridField* field = - FindField(sections, propertyEditModel.GetActiveFieldId()); - field == nullptr || !IsNumberEditCharacter(*field, event.character)) { + if (const UIEditorPropertyGridField* field = Detail::FindField( + sections, + propertyEditModel.GetActiveFieldId()); + field == nullptr || + !Detail::IsNumberEditCharacter(*field, event.character)) { break; } @@ -1147,12 +416,13 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - PruneColorFieldVisualStates(state, layout, sections); - SyncKeyboardNavigation(state, selectionModel, layout, sections); - SyncHoverTarget(state, layout, sections, popupMetrics); + Detail::PruneColorFieldVisualStates(state, layout, sections); + Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Detail::SyncHoverTarget(state, layout, sections, popupMetrics); if (eventResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None && state.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorPropertyGrid(layout, state.pointerPosition); + eventResult.hitTarget = + HitTestUIEditorPropertyGrid(layout, state.pointerPosition); } if (eventResult.consumed || @@ -1179,18 +449,16 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - PruneColorFieldVisualStates(state, layout, sections); - SyncKeyboardNavigation(state, selectionModel, layout, sections); - SyncHoverTarget(state, layout, sections, popupMetrics); + Detail::PruneColorFieldVisualStates(state, layout, sections); + Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Detail::SyncHoverTarget(state, layout, sections, popupMetrics); if (interactionResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None && state.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorPropertyGrid(layout, state.pointerPosition); + interactionResult.hitTarget = + HitTestUIEditorPropertyGrid(layout, state.pointerPosition); } - return { - std::move(layout), - std::move(interactionResult) - }; + return { std::move(layout), std::move(interactionResult) }; } } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteractionHelpers.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteractionHelpers.cpp new file mode 100644 index 00000000..4432ed98 --- /dev/null +++ b/new_editor/src/Fields/UIEditorPropertyGridInteractionHelpers.cpp @@ -0,0 +1,823 @@ +#include "Fields/UIEditorPropertyGridInteractionInternal.h" + +#include +#include +#include + +#include +#include + +#include +#include + +namespace XCEngine::UI::Editor::Detail { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::Text::HandleKeyDown; +using ::XCEngine::UI::Text::InsertCharacter; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::UISize; +using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; +using ::XCEngine::UI::Widgets::UIPopupPlacement; +using Widgets::BuildUIEditorPropertyGridLayout; +using Widgets::FindUIEditorPropertyGridFieldLocation; +using Widgets::FindUIEditorPropertyGridVisibleFieldIndex; +using Widgets::HitTestUIEditorPropertyGrid; +using Widgets::IsUIEditorPropertyGridPointInside; +using Widgets::ResolveUIEditorPropertyGridFieldValueText; +using Widgets::UIEditorColorFieldHitTargetKind; +using Widgets::UIEditorColorFieldSpec; +using Widgets::UIEditorMenuPopupHitTarget; +using Widgets::UIEditorMenuPopupHitTargetKind; +using Widgets::UIEditorMenuPopupInvalidIndex; +using Widgets::UIEditorMenuPopupItem; +using Widgets::UIEditorMenuPopupLayout; +using Widgets::UIEditorPropertyGridColorFieldVisualState; +using Widgets::UIEditorPropertyGridField; +using Widgets::UIEditorPropertyGridFieldKind; +using Widgets::UIEditorPropertyGridHitTarget; +using Widgets::UIEditorPropertyGridHitTargetKind; +using Widgets::UIEditorPropertyGridInvalidIndex; +using Widgets::UIEditorPropertyGridLayout; +using Widgets::UIEditorPropertyGridSection; + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +bool HasSystemModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) { + return modifiers.control || modifiers.alt || modifiers.super; +} + +bool ApplyKeyboardNavigation( + UIEditorPropertyGridInteractionState& state, + std::int32_t keyCode) { + switch (static_cast(keyCode)) { + case KeyCode::Up: + return state.keyboardNavigation.MovePrevious(); + case KeyCode::Down: + return state.keyboardNavigation.MoveNext(); + case KeyCode::Home: + return state.keyboardNavigation.MoveHome(); + case KeyCode::End: + return state.keyboardNavigation.MoveEnd(); + default: + return false; + } +} + +void ClearHoverState(UIEditorPropertyGridInteractionState& state) { + state.propertyGridState.hoveredSectionId.clear(); + state.propertyGridState.hoveredFieldId.clear(); + state.propertyGridState.hoveredHitTarget = UIEditorPropertyGridHitTargetKind::None; +} + +void ClearPopupState(UIEditorPropertyGridInteractionState& state) { + state.propertyGridState.popupFieldId.clear(); + state.propertyGridState.popupHighlightedIndex = UIEditorPropertyGridInvalidIndex; + state.pressedPopupIndex = UIEditorPropertyGridInvalidIndex; +} + +bool IsInlineEditable(const UIEditorPropertyGridField& field) { + return field.kind == UIEditorPropertyGridFieldKind::Text || + field.kind == UIEditorPropertyGridFieldKind::Number; +} + +bool IsNumberEditCharacter( + const UIEditorPropertyGridField& field, + std::uint32_t character) { + if (field.kind != UIEditorPropertyGridFieldKind::Number) { + return true; + } + + if (character >= static_cast('0') && + character <= static_cast('9')) { + return true; + } + + if (character == static_cast('-') || + character == static_cast('+')) { + return true; + } + + return !field.numberValue.integerMode && + character == static_cast('.'); +} + +UIEditorPropertyGridField* FindMutableField( + std::vector& sections, + std::string_view fieldId) { + const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); + if (!location.IsValid() || + location.sectionIndex >= sections.size() || + location.fieldIndex >= sections[location.sectionIndex].fields.size()) { + return nullptr; + } + + return §ions[location.sectionIndex].fields[location.fieldIndex]; +} + +const UIEditorPropertyGridField* FindField( + const std::vector& sections, + std::string_view fieldId) { + const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); + if (!location.IsValid() || + location.sectionIndex >= sections.size() || + location.fieldIndex >= sections[location.sectionIndex].fields.size()) { + return nullptr; + } + + return §ions[location.sectionIndex].fields[location.fieldIndex]; +} + +void SetChangedValueResult( + const UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + result.fieldValueChanged = true; + result.changedFieldId = field.fieldId; + result.changedValue = ResolveUIEditorPropertyGridFieldValueText(field); +} + +void MergeInteractionResult( + UIEditorPropertyGridInteractionResult& accumulated, + const UIEditorPropertyGridInteractionResult& current) { + accumulated.consumed = accumulated.consumed || current.consumed; + accumulated.sectionToggled = accumulated.sectionToggled || current.sectionToggled; + accumulated.selectionChanged = accumulated.selectionChanged || current.selectionChanged; + accumulated.keyboardNavigated = accumulated.keyboardNavigated || current.keyboardNavigated; + accumulated.editStarted = accumulated.editStarted || current.editStarted; + accumulated.editValueChanged = accumulated.editValueChanged || current.editValueChanged; + accumulated.editCommitted = accumulated.editCommitted || current.editCommitted; + accumulated.editCommitRejected = + accumulated.editCommitRejected || current.editCommitRejected; + accumulated.editCanceled = accumulated.editCanceled || current.editCanceled; + accumulated.popupOpened = accumulated.popupOpened || current.popupOpened; + accumulated.popupClosed = accumulated.popupClosed || current.popupClosed; + accumulated.fieldValueChanged = + accumulated.fieldValueChanged || current.fieldValueChanged; + accumulated.secondaryClicked = + accumulated.secondaryClicked || current.secondaryClicked; + + if (current.hitTarget.kind != UIEditorPropertyGridHitTargetKind::None) { + accumulated.hitTarget = current.hitTarget; + } + if (!current.toggledSectionId.empty()) { + accumulated.toggledSectionId = current.toggledSectionId; + } + if (!current.selectedFieldId.empty()) { + accumulated.selectedFieldId = current.selectedFieldId; + } + if (!current.activeFieldId.empty()) { + accumulated.activeFieldId = current.activeFieldId; + } + if (!current.committedFieldId.empty()) { + accumulated.committedFieldId = current.committedFieldId; + } + if (!current.committedValue.empty()) { + accumulated.committedValue = current.committedValue; + } + if (!current.changedFieldId.empty()) { + accumulated.changedFieldId = current.changedFieldId; + } + if (!current.changedValue.empty()) { + accumulated.changedValue = current.changedValue; + } +} + +UIEditorPropertyGridColorFieldVisualState* FindMutableColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + for (UIEditorPropertyGridColorFieldVisualState& entry : + state.propertyGridState.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + +const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + for (const UIEditorPropertyGridColorFieldVisualState& entry : + state.propertyGridState.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + +UIEditorColorFieldInteractionState BuildColorFieldInteractionState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + UIEditorColorFieldInteractionState interactionState = {}; + if (const UIEditorPropertyGridColorFieldVisualState* entry = + FindColorFieldVisualState(state, fieldId); + entry != nullptr) { + interactionState.colorFieldState = entry->state; + } + + interactionState.pointerPosition = state.pointerPosition; + interactionState.hasPointerPosition = state.hasPointerPosition; + return interactionState; +} + +void StoreColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId, + const UIEditorColorFieldInteractionState& interactionState) { + if (UIEditorPropertyGridColorFieldVisualState* entry = + FindMutableColorFieldVisualState(state, fieldId); + entry != nullptr) { + entry->state = interactionState.colorFieldState; + return; + } + + UIEditorPropertyGridColorFieldVisualState entry = {}; + entry.fieldId = std::string(fieldId); + entry.state = interactionState.colorFieldState; + state.propertyGridState.colorFieldStates.push_back(std::move(entry)); +} + +void PruneColorFieldVisualStates( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections) { + const auto isVisibleColorFieldId = [&layout, §ions](std::string_view fieldId) { + const std::size_t visibleFieldIndex = + FindUIEditorPropertyGridVisibleFieldIndex(layout, fieldId, sections); + if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || + visibleFieldIndex >= layout.visibleFieldIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + return sectionIndex < sections.size() && + fieldIndex < sections[sectionIndex].fields.size() && + sections[sectionIndex].fields[fieldIndex].kind == + UIEditorPropertyGridFieldKind::Color; + }; + + state.propertyGridState.colorFieldStates.erase( + std::remove_if( + state.propertyGridState.colorFieldStates.begin(), + state.propertyGridState.colorFieldStates.end(), + [&isVisibleColorFieldId]( + const UIEditorPropertyGridColorFieldVisualState& entry) { + return !isVisibleColorFieldId(entry.fieldId); + }), + state.propertyGridState.colorFieldStates.end()); +} + +void CloseOtherColorFieldPopups( + UIEditorPropertyGridInteractionState& state, + std::string_view keepFieldId) { + for (UIEditorPropertyGridColorFieldVisualState& entry : + state.propertyGridState.colorFieldStates) { + if (entry.fieldId == keepFieldId) { + continue; + } + + entry.state.popupOpen = false; + entry.state.activeTarget = UIEditorColorFieldHitTargetKind::None; + } +} + +std::vector BuildPopupItems( + const UIEditorPropertyGridField& field) { + std::vector items = {}; + if (field.kind != UIEditorPropertyGridFieldKind::Enum) { + return items; + } + + items.reserve(field.enumValue.options.size()); + const std::size_t selectedIndex = + field.enumValue.options.empty() + ? 0u + : (std::min)( + field.enumValue.selectedIndex, + field.enumValue.options.size() - 1u); + for (std::size_t index = 0u; index < field.enumValue.options.size(); ++index) { + UIEditorMenuPopupItem item = {}; + item.itemId = field.fieldId + "." + std::to_string(index); + item.kind = UIEditorMenuItemKind::Command; + item.label = field.enumValue.options[index]; + item.enabled = !field.readOnly; + item.checked = index == selectedIndex; + items.push_back(std::move(item)); + } + + return items; +} + +::XCEngine::UI::UIRect ResolvePopupViewportRect( + const ::XCEngine::UI::UIRect& bounds) { + return ::XCEngine::UI::UIRect( + bounds.x - 4096.0f, + bounds.y - 4096.0f, + 8192.0f, + 8192.0f); +} + +bool BuildPopupLayout( + const UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics, + UIEditorMenuPopupLayout& popupLayout, + std::vector& popupItems) { + if (state.propertyGridState.popupFieldId.empty()) { + return false; + } + + const std::size_t visibleFieldIndex = + FindUIEditorPropertyGridVisibleFieldIndex( + layout, + state.propertyGridState.popupFieldId, + sections); + if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || + visibleFieldIndex >= layout.visibleFieldIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + return false; + } + + const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + popupItems = BuildPopupItems(field); + if (popupItems.empty()) { + return false; + } + + const float popupWidth = (std::max)( + layout.fieldValueRects[visibleFieldIndex].width, + Widgets::ResolveUIEditorMenuPopupDesiredWidth(popupItems, popupMetrics)); + const float popupHeight = + Widgets::MeasureUIEditorMenuPopupHeight(popupItems, popupMetrics); + const auto placement = ResolvePopupPlacementRect( + layout.fieldValueRects[visibleFieldIndex], + UISize(popupWidth, popupHeight), + ResolvePopupViewportRect(layout.bounds), + UIPopupPlacement::BottomStart); + popupLayout = + Widgets::BuildUIEditorMenuPopupLayout(placement.rect, popupItems, popupMetrics); + return true; +} + +Widgets::UIEditorMenuPopupHitTarget ResolvePopupHit( + const UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { + if (!state.hasPointerPosition) { + return {}; + } + + UIEditorMenuPopupLayout popupLayout = {}; + std::vector popupItems = {}; + if (!BuildPopupLayout(state, layout, sections, popupMetrics, popupLayout, popupItems)) { + return {}; + } + + return Widgets::HitTestUIEditorMenuPopup( + popupLayout, + popupItems, + state.pointerPosition); +} + +void SyncHoverTarget( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { + ClearHoverState(state); + if (!state.hasPointerPosition) { + return; + } + + const UIEditorMenuPopupHitTarget popupHit = + ResolvePopupHit(state, layout, sections, popupMetrics); + if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) { + state.propertyGridState.popupHighlightedIndex = popupHit.index; + return; + } + if (popupHit.kind != UIEditorMenuPopupHitTargetKind::None) { + return; + } + + const UIEditorPropertyGridHitTarget hitTarget = + HitTestUIEditorPropertyGrid(layout, state.pointerPosition); + state.propertyGridState.hoveredHitTarget = hitTarget.kind; + if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader && + hitTarget.sectionIndex < sections.size()) { + state.propertyGridState.hoveredSectionId = + sections[hitTarget.sectionIndex].sectionId; + return; + } + + if ((hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow || + hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) && + hitTarget.sectionIndex < sections.size() && + hitTarget.fieldIndex < sections[hitTarget.sectionIndex].fields.size()) { + state.propertyGridState.hoveredFieldId = + sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex].fieldId; + } +} + +void SyncKeyboardNavigation( + UIEditorPropertyGridInteractionState& state, + const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections) { + state.keyboardNavigation.SetItemCount(layout.visibleFieldIndices.size()); + state.keyboardNavigation.ClampToItemCount(); + + if (!selectionModel.HasSelection()) { + return; + } + + const std::size_t selectedVisibleIndex = + FindUIEditorPropertyGridVisibleFieldIndex( + layout, + selectionModel.GetSelectedId(), + sections); + if (selectedVisibleIndex == UIEditorPropertyGridInvalidIndex) { + return; + } + + if (!state.keyboardNavigation.HasCurrentIndex() || + state.keyboardNavigation.GetCurrentIndex() != selectedVisibleIndex) { + state.keyboardNavigation.SetCurrentIndex(selectedVisibleIndex); + } +} + +bool SelectVisibleField( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + std::size_t visibleFieldIndex, + UIEditorPropertyGridInteractionResult& result) { + if (visibleFieldIndex >= layout.visibleFieldIndices.size() || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + return false; + } + + const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + result.selectionChanged = selectionModel.SetSelection(field.fieldId); + result.selectedFieldId = field.fieldId; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); + return true; +} + +bool BeginFieldEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + if (field.readOnly || !IsInlineEditable(field)) { + return false; + } + + const std::string initialValue = + field.kind == UIEditorPropertyGridFieldKind::Text + ? field.valueText + : ResolveUIEditorPropertyGridFieldValueText(field); + const bool changed = propertyEditModel.BeginEdit(field.fieldId, initialValue); + if (!changed && + (!propertyEditModel.HasActiveEdit() || + propertyEditModel.GetActiveFieldId() != field.fieldId)) { + return false; + } + + state.textInputState.value = propertyEditModel.GetStagedValue(); + state.textInputState.caret = state.textInputState.value.size(); + result.editStarted = changed; + result.activeFieldId = field.fieldId; + result.consumed = true; + return true; +} + +bool CommitActiveEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + std::vector& sections, + UIEditorPropertyGridInteractionResult& result) { + if (!propertyEditModel.HasActiveEdit()) { + return false; + } + + UIEditorPropertyGridField* field = + FindMutableField(sections, propertyEditModel.GetActiveFieldId()); + if (field == nullptr) { + propertyEditModel.CancelEdit(); + state.textInputState = {}; + return false; + } + + result.activeFieldId = field->fieldId; + if (field->kind == UIEditorPropertyGridFieldKind::Number) { + Widgets::UIEditorNumberFieldSpec spec = {}; + spec.fieldId = field->fieldId; + spec.label = field->label; + spec.value = field->numberValue.value; + spec.step = field->numberValue.step; + spec.minValue = field->numberValue.minValue; + spec.maxValue = field->numberValue.maxValue; + spec.integerMode = field->numberValue.integerMode; + spec.readOnly = field->readOnly; + + double parsedValue = field->numberValue.value; + if (!Widgets::TryParseUIEditorNumberFieldValue( + spec, + propertyEditModel.GetStagedValue(), + parsedValue)) { + result.editCommitRejected = true; + result.consumed = true; + return false; + } + + field->numberValue.value = parsedValue; + result.committedFieldId = field->fieldId; + result.committedValue = ResolveUIEditorPropertyGridFieldValueText(*field); + SetChangedValueResult(*field, result); + } else { + field->valueText = propertyEditModel.GetStagedValue(); + result.committedFieldId = field->fieldId; + result.committedValue = field->valueText; + SetChangedValueResult(*field, result); + } + + propertyEditModel.CommitEdit(); + state.textInputState = {}; + result.editCommitted = true; + result.consumed = true; + return true; +} + +bool CancelActiveEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + UIEditorPropertyGridInteractionResult& result) { + if (!propertyEditModel.HasActiveEdit()) { + return false; + } + + result.activeFieldId = propertyEditModel.GetActiveFieldId(); + if (!propertyEditModel.CancelEdit()) { + return false; + } + + state.textInputState = {}; + result.editCanceled = true; + result.consumed = true; + return true; +} + +void ClosePopup( + UIEditorPropertyGridInteractionState& state, + UIEditorPropertyGridInteractionResult& result) { + if (state.propertyGridState.popupFieldId.empty()) { + return; + } + + ClearPopupState(state); + result.popupClosed = true; + result.consumed = true; +} + +void OpenPopup( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + if (field.kind != UIEditorPropertyGridFieldKind::Enum || + field.readOnly || + field.enumValue.options.empty()) { + return; + } + + if (state.propertyGridState.popupFieldId == field.fieldId) { + return; + } + + state.propertyGridState.popupFieldId = field.fieldId; + state.propertyGridState.popupHighlightedIndex = + (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); + result.popupOpened = true; + result.consumed = true; +} + +void MovePopupHighlight( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridField& field, + int delta) { + if (field.kind != UIEditorPropertyGridFieldKind::Enum || + field.enumValue.options.empty()) { + state.propertyGridState.popupHighlightedIndex = + UIEditorPropertyGridInvalidIndex; + return; + } + + if (state.propertyGridState.popupHighlightedIndex == UIEditorPropertyGridInvalidIndex || + state.propertyGridState.popupHighlightedIndex >= field.enumValue.options.size()) { + state.propertyGridState.popupHighlightedIndex = + (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); + } + + const std::size_t currentIndex = state.propertyGridState.popupHighlightedIndex; + if (delta < 0) { + state.propertyGridState.popupHighlightedIndex = + currentIndex == 0u ? 0u : currentIndex - 1u; + } else { + state.propertyGridState.popupHighlightedIndex = + currentIndex + 1u >= field.enumValue.options.size() + ? field.enumValue.options.size() - 1u + : currentIndex + 1u; + } +} + +void JumpPopupHighlightToEdge( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridField& field, + bool toEnd) { + if (field.kind != UIEditorPropertyGridFieldKind::Enum || + field.enumValue.options.empty()) { + state.propertyGridState.popupHighlightedIndex = + UIEditorPropertyGridInvalidIndex; + return; + } + + state.propertyGridState.popupHighlightedIndex = + toEnd ? field.enumValue.options.size() - 1u : 0u; +} + +bool CommitPopupSelection( + UIEditorPropertyGridInteractionState& state, + std::vector& sections, + UIEditorPropertyGridInteractionResult& result) { + UIEditorPropertyGridField* field = + FindMutableField(sections, state.propertyGridState.popupFieldId); + if (field == nullptr || + field->kind != UIEditorPropertyGridFieldKind::Enum || + field->readOnly || + field->enumValue.options.empty() || + state.propertyGridState.popupHighlightedIndex == + UIEditorPropertyGridInvalidIndex || + state.propertyGridState.popupHighlightedIndex >= + field->enumValue.options.size()) { + return false; + } + + field->enumValue.selectedIndex = state.propertyGridState.popupHighlightedIndex; + SetChangedValueResult(*field, result); + ClosePopup(state, result); + return true; +} + +bool ToggleBoolField( + UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + if (field.kind != UIEditorPropertyGridFieldKind::Bool || field.readOnly) { + return false; + } + + field.boolValue = !field.boolValue; + SetChangedValueResult(field, result); + result.consumed = true; + return true; +} + +bool ProcessColorFieldEvent( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const UIEditorPropertyGridLayout& layout, + std::vector& sections, + const Widgets::UIEditorPropertyGridMetrics& metrics, + const UIInputEvent& event, + UIEditorPropertyGridInteractionResult& result) { + const Widgets::UIEditorColorFieldMetrics colorMetrics = + BuildUIEditorPropertyGridColorFieldMetrics(metrics); + const ::XCEngine::UI::UIRect popupViewportRect = + ResolvePopupViewportRect(layout.bounds); + bool handled = false; + + for (std::size_t visibleFieldIndex = 0u; + visibleFieldIndex < layout.visibleFieldIndices.size(); + ++visibleFieldIndex) { + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + continue; + } + + UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + if (field.kind != UIEditorPropertyGridFieldKind::Color) { + continue; + } + + UIEditorColorFieldInteractionState colorState = + BuildColorFieldInteractionState(state, field.fieldId); + const bool popupWasOpen = colorState.colorFieldState.popupOpen; + UIEditorColorFieldSpec spec = Widgets::Detail::BuildColorFieldSpec(field); + const UIEditorColorFieldInteractionFrame frame = + UpdateUIEditorColorFieldInteraction( + colorState, + spec, + layout.fieldRowRects[visibleFieldIndex], + { event }, + colorMetrics, + popupViewportRect); + + if (!frame.result.consumed && + frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::None && + !popupWasOpen && + !colorState.colorFieldState.popupOpen) { + continue; + } + + handled = true; + field.colorValue.value = spec.value; + field.colorValue.showAlpha = spec.showAlpha; + StoreColorFieldVisualState(state, field.fieldId, colorState); + + if (frame.result.popupOpened) { + ClosePopup(state, result); + CloseOtherColorFieldPopups(state, field.fieldId); + } + + state.propertyGridState.focused = colorState.colorFieldState.focused; + state.propertyGridState.pressedFieldId.clear(); + + if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || + frame.result.popupOpened || + frame.result.colorChanged || + popupWasOpen || + colorState.colorFieldState.popupOpen) { + result.selectionChanged = + selectionModel.SetSelection(field.fieldId) || result.selectionChanged; + result.selectedFieldId = field.fieldId; + result.activeFieldId = field.fieldId; + state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); + } + + if (propertyEditModel.HasActiveEdit() && + propertyEditModel.GetActiveFieldId() != field.fieldId && + (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || + frame.result.popupOpened)) { + CommitActiveEdit(state, propertyEditModel, sections, result); + } + + result.popupOpened = result.popupOpened || frame.result.popupOpened; + result.popupClosed = result.popupClosed || frame.result.popupClosed; + result.consumed = result.consumed || frame.result.consumed; + if (frame.result.colorChanged) { + SetChangedValueResult(field, result); + } + + if (frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::Row) { + result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::FieldRow; + result.hitTarget.sectionIndex = sectionIndex; + result.hitTarget.fieldIndex = fieldIndex; + result.hitTarget.visibleFieldIndex = visibleFieldIndex; + } else if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None) { + result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::ValueBox; + result.hitTarget.sectionIndex = sectionIndex; + result.hitTarget.fieldIndex = fieldIndex; + result.hitTarget.visibleFieldIndex = visibleFieldIndex; + } + } + + return handled; +} + +} // namespace XCEngine::UI::Editor::Detail diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteractionInternal.h b/new_editor/src/Fields/UIEditorPropertyGridInteractionInternal.h new file mode 100644 index 00000000..e978faa0 --- /dev/null +++ b/new_editor/src/Fields/UIEditorPropertyGridInteractionInternal.h @@ -0,0 +1,131 @@ +#pragma once + +#include "Fields/UIEditorPropertyGridInternal.h" + +#include + +namespace XCEngine::UI::Editor::Detail { + +bool ShouldUsePointerPosition(const ::XCEngine::UI::UIInputEvent& event); +bool HasSystemModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers); +bool ApplyKeyboardNavigation( + UIEditorPropertyGridInteractionState& state, + std::int32_t keyCode); +void ClearHoverState(UIEditorPropertyGridInteractionState& state); +void ClearPopupState(UIEditorPropertyGridInteractionState& state); +bool IsInlineEditable(const Widgets::UIEditorPropertyGridField& field); +bool IsNumberEditCharacter( + const Widgets::UIEditorPropertyGridField& field, + std::uint32_t character); +Widgets::UIEditorPropertyGridField* FindMutableField( + std::vector& sections, + std::string_view fieldId); +const Widgets::UIEditorPropertyGridField* FindField( + const std::vector& sections, + std::string_view fieldId); +void SetChangedValueResult( + const Widgets::UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result); +void MergeInteractionResult( + UIEditorPropertyGridInteractionResult& accumulated, + const UIEditorPropertyGridInteractionResult& current); +Widgets::UIEditorPropertyGridColorFieldVisualState* FindMutableColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId); +const Widgets::UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId); +UIEditorColorFieldInteractionState BuildColorFieldInteractionState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId); +void StoreColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId, + const UIEditorColorFieldInteractionState& interactionState); +void PruneColorFieldVisualStates( + UIEditorPropertyGridInteractionState& state, + const Widgets::UIEditorPropertyGridLayout& layout, + const std::vector& sections); +void CloseOtherColorFieldPopups( + UIEditorPropertyGridInteractionState& state, + std::string_view keepFieldId); +std::vector BuildPopupItems( + const Widgets::UIEditorPropertyGridField& field); +::XCEngine::UI::UIRect ResolvePopupViewportRect( + const ::XCEngine::UI::UIRect& bounds); +bool BuildPopupLayout( + const UIEditorPropertyGridInteractionState& state, + const Widgets::UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics, + Widgets::UIEditorMenuPopupLayout& popupLayout, + std::vector& popupItems); +Widgets::UIEditorMenuPopupHitTarget ResolvePopupHit( + const UIEditorPropertyGridInteractionState& state, + const Widgets::UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics); +void SyncHoverTarget( + UIEditorPropertyGridInteractionState& state, + const Widgets::UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics); +void SyncKeyboardNavigation( + UIEditorPropertyGridInteractionState& state, + const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorPropertyGridLayout& layout, + const std::vector& sections); +bool SelectVisibleField( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorPropertyGridLayout& layout, + const std::vector& sections, + std::size_t visibleFieldIndex, + UIEditorPropertyGridInteractionResult& result); +bool BeginFieldEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const Widgets::UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result); +bool CommitActiveEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + std::vector& sections, + UIEditorPropertyGridInteractionResult& result); +bool CancelActiveEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + UIEditorPropertyGridInteractionResult& result); +void ClosePopup( + UIEditorPropertyGridInteractionState& state, + UIEditorPropertyGridInteractionResult& result); +void OpenPopup( + UIEditorPropertyGridInteractionState& state, + const Widgets::UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result); +void MovePopupHighlight( + UIEditorPropertyGridInteractionState& state, + const Widgets::UIEditorPropertyGridField& field, + int delta); +void JumpPopupHighlightToEdge( + UIEditorPropertyGridInteractionState& state, + const Widgets::UIEditorPropertyGridField& field, + bool toEnd); +bool CommitPopupSelection( + UIEditorPropertyGridInteractionState& state, + std::vector& sections, + UIEditorPropertyGridInteractionResult& result); +bool ToggleBoolField( + Widgets::UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result); +bool ProcessColorFieldEvent( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const Widgets::UIEditorPropertyGridLayout& layout, + std::vector& sections, + const Widgets::UIEditorPropertyGridMetrics& metrics, + const ::XCEngine::UI::UIInputEvent& event, + UIEditorPropertyGridInteractionResult& result); + +} // namespace XCEngine::UI::Editor::Detail diff --git a/new_editor/src/Fields/UIEditorPropertyGridInternal.h b/new_editor/src/Fields/UIEditorPropertyGridInternal.h new file mode 100644 index 00000000..90b11d4b --- /dev/null +++ b/new_editor/src/Fields/UIEditorPropertyGridInternal.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Widgets::Detail { + +float ClampNonNegative(float value); +float ResolveSectionHeaderHeight( + const UIEditorPropertyGridSection& section, + const UIEditorPropertyGridMetrics& metrics); +float ResolveFieldRowHeight( + const UIEditorPropertyGridField& field, + const UIEditorPropertyGridMetrics& metrics); +::XCEngine::UI::UIPoint ResolveDisclosureGlyphPosition( + const ::XCEngine::UI::UIRect& rect, + const UIEditorPropertyGridMetrics& metrics); + +UIEditorBoolFieldSpec BuildBoolFieldSpec(const UIEditorPropertyGridField& field); +UIEditorNumberFieldSpec BuildNumberFieldSpec(const UIEditorPropertyGridField& field); +UIEditorEnumFieldSpec BuildEnumFieldSpec(const UIEditorPropertyGridField& field); +UIEditorColorFieldSpec BuildColorFieldSpec(const UIEditorPropertyGridField& field); +UIEditorTextFieldSpec BuildTextFieldSpec(const UIEditorPropertyGridField& field); +UIEditorVector2FieldSpec BuildVector2FieldSpec(const UIEditorPropertyGridField& field); +UIEditorVector3FieldSpec BuildVector3FieldSpec(const UIEditorPropertyGridField& field); +UIEditorVector4FieldSpec BuildVector4FieldSpec(const UIEditorPropertyGridField& field); + +struct UIEditorPropertyGridFieldRects { + ::XCEngine::UI::UIRect labelRect = {}; + ::XCEngine::UI::UIRect valueRect = {}; +}; + +UIEditorPropertyGridFieldRects ResolveFieldRects( + const ::XCEngine::UI::UIRect& rowRect, + const UIEditorPropertyGridField& field, + const UIEditorPropertyGridMetrics& metrics); + +const std::string& ResolveDisplayedTextFieldValue( + const UIEditorPropertyGridField& field, + const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel); + +UIEditorBoolFieldHitTargetKind ResolveBoolHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); +UIEditorNumberFieldHitTargetKind ResolveNumberHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); +UIEditorEnumFieldHitTargetKind ResolveEnumHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); +UIEditorColorFieldHitTargetKind ResolveColorHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); +UIEditorTextFieldHitTargetKind ResolveTextHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); +UIEditorVector2FieldHitTargetKind ResolveVector2HoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); +UIEditorVector3FieldHitTargetKind ResolveVector3HoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); +UIEditorVector4FieldHitTargetKind ResolveVector4HoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field); + +std::vector BuildEnumPopupItems( + const UIEditorPropertyGridField& field); +const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( + const UIEditorPropertyGridState& state, + std::string_view fieldId); +::XCEngine::UI::UIRect ResolvePopupViewportRect( + const ::XCEngine::UI::UIRect& bounds); +bool BuildEnumPopupRuntime( + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const UIEditorPropertyGridState& state, + const UIEditorMenuPopupMetrics& popupMetrics, + UIEditorMenuPopupLayout& popupLayout, + UIEditorMenuPopupState& popupState, + std::vector& popupItems); + +std::string FormatVector2ValueText(const UIEditorPropertyGridField& field); +std::string FormatColorValueText(const UIEditorPropertyGridField& field); +std::string FormatVector3ValueText(const UIEditorPropertyGridField& field); +std::string FormatVector4ValueText(const UIEditorPropertyGridField& field); + +} // namespace XCEngine::UI::Editor::Widgets::Detail diff --git a/new_editor/src/Fields/UIEditorPropertyGridRendering.cpp b/new_editor/src/Fields/UIEditorPropertyGridRendering.cpp new file mode 100644 index 00000000..8246c736 --- /dev/null +++ b/new_editor/src/Fields/UIEditorPropertyGridRendering.cpp @@ -0,0 +1,820 @@ +#include "Fields/UIEditorPropertyGridInternal.h" + +#include +#include +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor::Widgets::Detail { + +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; +using ::XCEngine::UI::UISize; +using ::XCEngine::UI::Editor::UIEditorMenuItemKind; +using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; +using ::XCEngine::UI::Widgets::UIPopupPlacement; + +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; +} + +UIPoint ResolveDisclosureGlyphPosition( + const UIRect& rect, + const UIEditorPropertyGridMetrics& metrics) { + return UIPoint( + rect.x + metrics.disclosureGlyphInsetX, + rect.y + metrics.sectionTextInsetY + metrics.disclosureGlyphInsetY); +} + +UIEditorBoolFieldSpec BuildBoolFieldSpec(const UIEditorPropertyGridField& field) { + UIEditorBoolFieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.value = field.boolValue; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorNumberFieldSpec BuildNumberFieldSpec(const UIEditorPropertyGridField& field) { + UIEditorNumberFieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.value = field.numberValue.value; + spec.step = field.numberValue.step; + spec.minValue = field.numberValue.minValue; + spec.maxValue = field.numberValue.maxValue; + spec.integerMode = field.numberValue.integerMode; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorEnumFieldSpec BuildEnumFieldSpec(const UIEditorPropertyGridField& field) { + UIEditorEnumFieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.options = field.enumValue.options; + spec.selectedIndex = field.enumValue.selectedIndex; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorColorFieldSpec BuildColorFieldSpec(const UIEditorPropertyGridField& field) { + UIEditorColorFieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.value = field.colorValue.value; + spec.showAlpha = field.colorValue.showAlpha; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorTextFieldSpec BuildTextFieldSpec(const UIEditorPropertyGridField& field) { + UIEditorTextFieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.value = field.valueText; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorVector2FieldSpec BuildVector2FieldSpec(const UIEditorPropertyGridField& field) { + UIEditorVector2FieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.values = field.vector2Value.values; + spec.componentLabels = field.vector2Value.componentLabels; + spec.step = field.vector2Value.step; + spec.minValue = field.vector2Value.minValue; + spec.maxValue = field.vector2Value.maxValue; + spec.integerMode = field.vector2Value.integerMode; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorVector3FieldSpec BuildVector3FieldSpec(const UIEditorPropertyGridField& field) { + UIEditorVector3FieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.values = field.vector3Value.values; + spec.componentLabels = field.vector3Value.componentLabels; + spec.step = field.vector3Value.step; + spec.minValue = field.vector3Value.minValue; + spec.maxValue = field.vector3Value.maxValue; + spec.integerMode = field.vector3Value.integerMode; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorVector4FieldSpec BuildVector4FieldSpec(const UIEditorPropertyGridField& field) { + UIEditorVector4FieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.values = field.vector4Value.values; + spec.componentLabels = field.vector4Value.componentLabels; + spec.step = field.vector4Value.step; + spec.minValue = field.vector4Value.minValue; + spec.maxValue = field.vector4Value.maxValue; + spec.integerMode = field.vector4Value.integerMode; + spec.readOnly = field.readOnly; + return spec; +} + +UIEditorPropertyGridFieldRects ResolveFieldRects( + const UIRect& rowRect, + const UIEditorPropertyGridField& field, + const UIEditorPropertyGridMetrics& metrics) { + switch (field.kind) { + case UIEditorPropertyGridFieldKind::Bool: { + const UIEditorBoolFieldLayout fieldLayout = BuildUIEditorBoolFieldLayout( + rowRect, + BuildBoolFieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.controlRect }; + } + + case UIEditorPropertyGridFieldKind::Number: { + const UIEditorNumberFieldLayout fieldLayout = BuildUIEditorNumberFieldLayout( + rowRect, + BuildNumberFieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.valueRect }; + } + + case UIEditorPropertyGridFieldKind::Enum: { + const UIEditorEnumFieldLayout fieldLayout = BuildUIEditorEnumFieldLayout( + rowRect, + BuildEnumFieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.valueRect }; + } + + case UIEditorPropertyGridFieldKind::Color: { + const UIEditorColorFieldLayout fieldLayout = BuildUIEditorColorFieldLayout( + rowRect, + BuildColorFieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.controlRect }; + } + + case UIEditorPropertyGridFieldKind::Vector2: { + const UIEditorVector2FieldLayout fieldLayout = BuildUIEditorVector2FieldLayout( + rowRect, + BuildVector2FieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.controlRect }; + } + + case UIEditorPropertyGridFieldKind::Vector3: { + const UIEditorVector3FieldLayout fieldLayout = BuildUIEditorVector3FieldLayout( + rowRect, + BuildVector3FieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.controlRect }; + } + + case UIEditorPropertyGridFieldKind::Vector4: { + const UIEditorVector4FieldLayout fieldLayout = BuildUIEditorVector4FieldLayout( + rowRect, + BuildVector4FieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.controlRect }; + } + + case UIEditorPropertyGridFieldKind::Text: + default: { + const UIEditorTextFieldLayout fieldLayout = BuildUIEditorTextFieldLayout( + rowRect, + BuildTextFieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.valueRect }; + } + } +} + +const std::string& ResolveDisplayedTextFieldValue( + const UIEditorPropertyGridField& field, + const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel) { + if (field.kind == UIEditorPropertyGridFieldKind::Text && + propertyEditModel.HasActiveEdit() && + propertyEditModel.GetActiveFieldId() == field.fieldId) { + return propertyEditModel.GetStagedValue(); + } + + return field.valueText; +} + +UIEditorBoolFieldHitTargetKind ResolveBoolHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorBoolFieldHitTargetKind::None; + } + + return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox + ? UIEditorBoolFieldHitTargetKind::Checkbox + : UIEditorBoolFieldHitTargetKind::Row; +} + +UIEditorNumberFieldHitTargetKind ResolveNumberHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorNumberFieldHitTargetKind::None; + } + + return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox + ? UIEditorNumberFieldHitTargetKind::ValueBox + : UIEditorNumberFieldHitTargetKind::Row; +} + +UIEditorEnumFieldHitTargetKind ResolveEnumHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorEnumFieldHitTargetKind::None; + } + + return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox + ? UIEditorEnumFieldHitTargetKind::ValueBox + : UIEditorEnumFieldHitTargetKind::Row; +} + +UIEditorColorFieldHitTargetKind ResolveColorHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorColorFieldHitTargetKind::None; + } + + return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox + ? UIEditorColorFieldHitTargetKind::Swatch + : UIEditorColorFieldHitTargetKind::Row; +} + +UIEditorTextFieldHitTargetKind ResolveTextHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorTextFieldHitTargetKind::None; + } + + return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox + ? UIEditorTextFieldHitTargetKind::ValueBox + : UIEditorTextFieldHitTargetKind::Row; +} + +UIEditorVector2FieldHitTargetKind ResolveVector2HoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorVector2FieldHitTargetKind::None; + } + + return UIEditorVector2FieldHitTargetKind::Row; +} + +UIEditorVector3FieldHitTargetKind ResolveVector3HoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorVector3FieldHitTargetKind::None; + } + + return UIEditorVector3FieldHitTargetKind::Row; +} + +UIEditorVector4FieldHitTargetKind ResolveVector4HoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorVector4FieldHitTargetKind::None; + } + + return UIEditorVector4FieldHitTargetKind::Row; +} + +std::vector BuildEnumPopupItems( + const UIEditorPropertyGridField& field) { + std::vector items = {}; + if (field.kind != UIEditorPropertyGridFieldKind::Enum) { + return items; + } + + items.reserve(field.enumValue.options.size()); + const std::size_t selectedIndex = + field.enumValue.options.empty() + ? 0u + : (std::min)( + field.enumValue.selectedIndex, + field.enumValue.options.size() - 1u); + for (std::size_t index = 0u; index < field.enumValue.options.size(); ++index) { + UIEditorMenuPopupItem item = {}; + item.itemId = field.fieldId + "." + std::to_string(index); + item.kind = UIEditorMenuItemKind::Command; + item.label = field.enumValue.options[index]; + item.enabled = !field.readOnly; + item.checked = index == selectedIndex; + items.push_back(std::move(item)); + } + + return items; +} + +const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( + const UIEditorPropertyGridState& state, + std::string_view fieldId) { + for (const UIEditorPropertyGridColorFieldVisualState& entry : + state.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + +UIRect ResolvePopupViewportRect(const UIRect& bounds) { + return UIRect(bounds.x - 4096.0f, bounds.y - 4096.0f, 8192.0f, 8192.0f); +} + +bool BuildEnumPopupRuntime( + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const UIEditorPropertyGridState& state, + const UIEditorMenuPopupMetrics& popupMetrics, + UIEditorMenuPopupLayout& popupLayout, + UIEditorMenuPopupState& popupState, + std::vector& popupItems) { + if (state.popupFieldId.empty()) { + return false; + } + + const std::size_t visibleFieldIndex = + FindUIEditorPropertyGridVisibleFieldIndex(layout, state.popupFieldId, sections); + if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || + visibleFieldIndex >= layout.visibleFieldIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + return false; + } + + const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + popupItems = BuildEnumPopupItems(field); + if (popupItems.empty()) { + return false; + } + + const float popupWidth = (std::max)( + layout.fieldValueRects[visibleFieldIndex].width, + ResolveUIEditorMenuPopupDesiredWidth(popupItems, popupMetrics)); + const float popupHeight = + MeasureUIEditorMenuPopupHeight(popupItems, popupMetrics); + const auto placement = ResolvePopupPlacementRect( + layout.fieldValueRects[visibleFieldIndex], + UISize(popupWidth, popupHeight), + ResolvePopupViewportRect(layout.bounds), + UIPopupPlacement::BottomStart); + + popupLayout = BuildUIEditorMenuPopupLayout( + placement.rect, + popupItems, + popupMetrics); + popupState.focused = state.focused || !state.popupFieldId.empty(); + popupState.hoveredIndex = + state.popupHighlightedIndex < popupItems.size() + ? state.popupHighlightedIndex + : UIEditorMenuPopupInvalidIndex; + return true; +} + +std::string FormatVector2ValueText(const UIEditorPropertyGridField& field) { + const UIEditorVector2FieldSpec spec = BuildVector2FieldSpec(field); + return FormatUIEditorVector2FieldComponentValue(spec, 0u) + ", " + + FormatUIEditorVector2FieldComponentValue(spec, 1u); +} + +std::string FormatColorValueText(const UIEditorPropertyGridField& field) { + return FormatUIEditorColorFieldHexText(BuildColorFieldSpec(field)); +} + +std::string FormatVector3ValueText(const UIEditorPropertyGridField& field) { + const UIEditorVector3FieldSpec spec = BuildVector3FieldSpec(field); + return FormatUIEditorVector3FieldComponentValue(spec, 0u) + ", " + + FormatUIEditorVector3FieldComponentValue(spec, 1u) + ", " + + FormatUIEditorVector3FieldComponentValue(spec, 2u); +} + +std::string FormatVector4ValueText(const UIEditorPropertyGridField& field) { + const UIEditorVector4FieldSpec spec = BuildVector4FieldSpec(field); + return FormatUIEditorVector4FieldComponentValue(spec, 0u) + ", " + + FormatUIEditorVector4FieldComponentValue(spec, 1u) + ", " + + FormatUIEditorVector4FieldComponentValue(spec, 2u) + ", " + + FormatUIEditorVector4FieldComponentValue(spec, 3u); +} + +} // namespace XCEngine::UI::Editor::Widgets::Detail + +namespace XCEngine::UI::Editor::Widgets { + +void AppendUIEditorPropertyGridBackground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const ::XCEngine::UI::Widgets::UIPropertyEditModel&, + 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; + + if (selected || hovered) { + drawList.AddFilledRect( + layout.fieldRowRects[visibleFieldIndex], + selected + ? (state.focused ? palette.fieldSelectedFocusedColor : palette.fieldSelectedColor) + : palette.fieldHoverColor, + metrics.cornerRounding); + } + } +} + +void AppendUIEditorPropertyGridForeground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const UIEditorPropertyGridState& state, + const ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const UIEditorPropertyGridPalette& palette, + const UIEditorPropertyGridMetrics& metrics, + const UIEditorMenuPopupPalette& popupPalette, + const UIEditorMenuPopupMetrics& popupMetrics) { + drawList.PushClipRect(layout.bounds); + + for (std::size_t sectionVisibleIndex = 0u; + sectionVisibleIndex < layout.sectionHeaderRects.size(); + ++sectionVisibleIndex) { + const UIEditorPropertyGridSection& section = + sections[layout.sectionIndices[sectionVisibleIndex]]; + drawList.AddText( + Detail::ResolveDisclosureGlyphPosition( + layout.sectionDisclosureRects[sectionVisibleIndex], + metrics), + layout.sectionExpanded[sectionVisibleIndex] ? "v" : ">", + palette.disclosureColor, + metrics.disclosureGlyphFontSize); + drawList.AddText( + ::XCEngine::UI::UIPoint( + layout.sectionTitleRects[sectionVisibleIndex].x, + layout.sectionTitleRects[sectionVisibleIndex].y + metrics.sectionTextInsetY), + section.title, + palette.sectionTextColor, + metrics.sectionFontSize); + } + + const UIEditorBoolFieldMetrics boolMetrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldMetrics(metrics); + const UIEditorBoolFieldPalette boolPalette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldPalette(palette); + const UIEditorNumberFieldMetrics numberMetrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(metrics); + const UIEditorNumberFieldPalette numberPalette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldPalette(palette); + const UIEditorTextFieldMetrics textMetrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(metrics); + const UIEditorTextFieldPalette textPalette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette(palette); + const UIEditorVector2FieldMetrics vector2Metrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldMetrics(metrics); + const UIEditorVector2FieldPalette vector2Palette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldPalette(palette); + const UIEditorVector3FieldMetrics vector3Metrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldMetrics(metrics); + const UIEditorVector3FieldPalette vector3Palette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldPalette(palette); + const UIEditorVector4FieldMetrics vector4Metrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldMetrics(metrics); + const UIEditorVector4FieldPalette vector4Palette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldPalette(palette); + const UIEditorEnumFieldMetrics enumMetrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldMetrics(metrics); + const UIEditorEnumFieldPalette enumPalette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldPalette(palette); + const UIEditorColorFieldMetrics colorMetrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics(metrics); + const UIEditorColorFieldPalette colorPalette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldPalette(palette); + const ::XCEngine::UI::UIRect popupViewportRect = + Detail::ResolvePopupViewportRect(layout.bounds); + + 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; + + switch (field.kind) { + case UIEditorPropertyGridFieldKind::Bool: { + UIEditorBoolFieldState fieldState = {}; + fieldState.hoveredTarget = Detail::ResolveBoolHoveredTarget(state, field); + fieldState.focused = state.focused; + fieldState.active = state.pressedFieldId == field.fieldId; + AppendUIEditorBoolField( + drawList, + layout.fieldRowRects[visibleFieldIndex], + Detail::BuildBoolFieldSpec(field), + fieldState, + boolPalette, + boolMetrics); + break; + } + + case UIEditorPropertyGridFieldKind::Number: { + UIEditorNumberFieldState fieldState = {}; + fieldState.hoveredTarget = Detail::ResolveNumberHoveredTarget(state, field); + fieldState.activeTarget = + state.pressedFieldId == field.fieldId + ? UIEditorNumberFieldHitTargetKind::ValueBox + : UIEditorNumberFieldHitTargetKind::None; + fieldState.focused = state.focused; + fieldState.editing = editing; + fieldState.displayText = editing + ? propertyEditModel.GetStagedValue() + : ResolveUIEditorPropertyGridFieldValueText(field); + AppendUIEditorNumberField( + drawList, + layout.fieldRowRects[visibleFieldIndex], + Detail::BuildNumberFieldSpec(field), + fieldState, + numberPalette, + numberMetrics); + break; + } + + case UIEditorPropertyGridFieldKind::Enum: { + UIEditorEnumFieldState fieldState = {}; + fieldState.hoveredTarget = Detail::ResolveEnumHoveredTarget(state, field); + fieldState.focused = state.focused; + fieldState.active = state.pressedFieldId == field.fieldId; + fieldState.popupOpen = state.popupFieldId == field.fieldId; + AppendUIEditorEnumField( + drawList, + layout.fieldRowRects[visibleFieldIndex], + Detail::BuildEnumFieldSpec(field), + fieldState, + enumPalette, + enumMetrics); + break; + } + + case UIEditorPropertyGridFieldKind::Color: { + UIEditorColorFieldState fieldState = {}; + if (const auto* visualState = + Detail::FindColorFieldVisualState(state, field.fieldId); + visualState != nullptr) { + fieldState = visualState->state; + } else { + fieldState.hoveredTarget = Detail::ResolveColorHoveredTarget(state, field); + fieldState.focused = state.focused; + } + + AppendUIEditorColorField( + drawList, + layout.fieldRowRects[visibleFieldIndex], + Detail::BuildColorFieldSpec(field), + fieldState, + colorPalette, + colorMetrics, + popupViewportRect); + break; + } + + case UIEditorPropertyGridFieldKind::Vector2: { + UIEditorVector2FieldState fieldState = {}; + fieldState.hoveredTarget = Detail::ResolveVector2HoveredTarget(state, field); + fieldState.activeTarget = + state.pressedFieldId == field.fieldId + ? UIEditorVector2FieldHitTargetKind::Row + : UIEditorVector2FieldHitTargetKind::None; + fieldState.focused = state.focused; + AppendUIEditorVector2Field( + drawList, + layout.fieldRowRects[visibleFieldIndex], + Detail::BuildVector2FieldSpec(field), + fieldState, + vector2Palette, + vector2Metrics); + break; + } + + case UIEditorPropertyGridFieldKind::Vector3: { + UIEditorVector3FieldState fieldState = {}; + fieldState.hoveredTarget = Detail::ResolveVector3HoveredTarget(state, field); + fieldState.activeTarget = + state.pressedFieldId == field.fieldId + ? UIEditorVector3FieldHitTargetKind::Row + : UIEditorVector3FieldHitTargetKind::None; + fieldState.focused = state.focused; + AppendUIEditorVector3Field( + drawList, + layout.fieldRowRects[visibleFieldIndex], + Detail::BuildVector3FieldSpec(field), + fieldState, + vector3Palette, + vector3Metrics); + break; + } + + case UIEditorPropertyGridFieldKind::Vector4: { + UIEditorVector4FieldState fieldState = {}; + fieldState.hoveredTarget = Detail::ResolveVector4HoveredTarget(state, field); + fieldState.activeTarget = + state.pressedFieldId == field.fieldId + ? UIEditorVector4FieldHitTargetKind::Row + : UIEditorVector4FieldHitTargetKind::None; + fieldState.focused = state.focused; + AppendUIEditorVector4Field( + drawList, + layout.fieldRowRects[visibleFieldIndex], + Detail::BuildVector4FieldSpec(field), + fieldState, + vector4Palette, + vector4Metrics); + break; + } + + case UIEditorPropertyGridFieldKind::Text: + default: { + const std::string& displayedValue = + Detail::ResolveDisplayedTextFieldValue(field, propertyEditModel); + UIEditorTextFieldState fieldState = {}; + fieldState.hoveredTarget = Detail::ResolveTextHoveredTarget(state, field); + fieldState.activeTarget = + state.pressedFieldId == field.fieldId + ? (state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox + ? UIEditorTextFieldHitTargetKind::ValueBox + : UIEditorTextFieldHitTargetKind::Row) + : UIEditorTextFieldHitTargetKind::None; + fieldState.focused = state.focused; + fieldState.editing = editing; + fieldState.displayText = displayedValue; + + UIEditorTextFieldSpec fieldSpec = Detail::BuildTextFieldSpec(field); + fieldSpec.value = editing ? std::string() : displayedValue; + AppendUIEditorTextField( + drawList, + layout.fieldRowRects[visibleFieldIndex], + fieldSpec, + fieldState, + textPalette, + textMetrics); + + if (editing) { + drawList.PushClipRect( + ResolveUIEditorTextClipRect( + layout.fieldValueRects[visibleFieldIndex], + metrics.tagFontSize)); + drawList.AddText( + ::XCEngine::UI::UIPoint( + layout.fieldValueRects[visibleFieldIndex].x + + (std::max)( + 0.0f, + layout.fieldValueRects[visibleFieldIndex].width - 34.0f), + ResolveUIEditorTextTop( + layout.fieldValueRects[visibleFieldIndex], + metrics.tagFontSize, + metrics.valueTextInsetY)), + "EDIT", + palette.editTagColor, + metrics.tagFontSize); + drawList.PopClipRect(); + } + break; + } + } + } + + drawList.PopClipRect(); + + UIEditorMenuPopupLayout popupLayout = {}; + UIEditorMenuPopupState popupState = {}; + std::vector popupItems = {}; + if (Detail::BuildEnumPopupRuntime( + layout, + sections, + state, + popupMetrics, + popupLayout, + popupState, + popupItems)) { + AppendUIEditorMenuPopupBackground( + drawList, + popupLayout, + popupItems, + popupState, + popupPalette, + popupMetrics); + AppendUIEditorMenuPopupForeground( + drawList, + popupLayout, + popupItems, + popupState, + popupPalette, + popupMetrics); + } +} + +void AppendUIEditorPropertyGrid( + ::XCEngine::UI::UIDrawList& drawList, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& 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 UIEditorMenuPopupPalette& popupPalette, + const UIEditorMenuPopupMetrics& popupMetrics) { + const UIEditorPropertyGridLayout layout = + BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); + AppendUIEditorPropertyGridBackground( + drawList, + layout, + sections, + selectionModel, + propertyEditModel, + state, + palette, + metrics); + AppendUIEditorPropertyGridForeground( + drawList, + layout, + sections, + state, + propertyEditModel, + palette, + metrics, + popupPalette, + popupMetrics); +} + +} // namespace XCEngine::UI::Editor::Widgets