2026-04-10 00:41:28 +08:00
|
|
|
#include <XCEditor/Fields/UIEditorPropertyGridInteraction.h>
|
2026-04-07 16:57:04 +08:00
|
|
|
|
2026-04-10 00:41:28 +08:00
|
|
|
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
|
|
|
|
#include <XCEditor/Fields/UIEditorColorFieldInteraction.h>
|
2026-04-15 08:24:06 +08:00
|
|
|
#include <XCEditor/Menu/UIEditorMenuPopup.h>
|
2026-04-10 00:41:28 +08:00
|
|
|
#include <XCEditor/Fields/UIEditorNumberField.h>
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
#include <XCEngine/Input/InputTypes.h>
|
2026-04-08 02:52:28 +08:00
|
|
|
#include <XCEngine/UI/Widgets/UIPopupOverlayModel.h>
|
2026-04-07 16:57:04 +08:00
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
#include <algorithm>
|
2026-04-07 16:57:04 +08:00
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
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;
|
2026-04-08 02:52:28 +08:00
|
|
|
using ::XCEngine::UI::UISize;
|
2026-04-10 00:41:28 +08:00
|
|
|
using ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics;
|
|
|
|
|
using ::XCEngine::UI::Editor::UIEditorColorFieldInteractionFrame;
|
|
|
|
|
using ::XCEngine::UI::Editor::UIEditorColorFieldInteractionState;
|
|
|
|
|
using ::XCEngine::UI::Editor::UpdateUIEditorColorFieldInteraction;
|
2026-04-08 02:52:28 +08:00
|
|
|
using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect;
|
|
|
|
|
using ::XCEngine::UI::Widgets::UIPopupPlacement;
|
2026-04-07 16:57:04 +08:00
|
|
|
using Widgets::BuildUIEditorPropertyGridLayout;
|
2026-04-08 02:52:28 +08:00
|
|
|
using Widgets::FindUIEditorPropertyGridFieldLocation;
|
2026-04-07 16:57:04 +08:00
|
|
|
using Widgets::FindUIEditorPropertyGridVisibleFieldIndex;
|
|
|
|
|
using Widgets::HitTestUIEditorPropertyGrid;
|
|
|
|
|
using Widgets::IsUIEditorPropertyGridPointInside;
|
2026-04-08 02:52:28 +08:00
|
|
|
using Widgets::ResolveUIEditorPropertyGridFieldValueText;
|
|
|
|
|
using Widgets::UIEditorPropertyGridField;
|
|
|
|
|
using Widgets::UIEditorPropertyGridFieldKind;
|
2026-04-07 16:57:04 +08:00
|
|
|
using Widgets::UIEditorPropertyGridHitTarget;
|
|
|
|
|
using Widgets::UIEditorPropertyGridHitTargetKind;
|
|
|
|
|
using Widgets::UIEditorPropertyGridInvalidIndex;
|
2026-04-08 02:52:28 +08:00
|
|
|
using Widgets::UIEditorPropertyGridLayout;
|
|
|
|
|
using Widgets::UIEditorPropertyGridSection;
|
2026-04-10 00:41:28 +08:00
|
|
|
using Widgets::UIEditorPropertyGridColorFieldVisualState;
|
2026-04-08 02:52:28 +08:00
|
|
|
using Widgets::UIEditorMenuPopupHitTarget;
|
|
|
|
|
using Widgets::UIEditorMenuPopupHitTargetKind;
|
|
|
|
|
using Widgets::UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
using Widgets::UIEditorMenuPopupItem;
|
|
|
|
|
using Widgets::UIEditorMenuPopupLayout;
|
2026-04-10 00:41:28 +08:00
|
|
|
using Widgets::UIEditorColorFieldHitTargetKind;
|
|
|
|
|
using Widgets::UIEditorColorFieldSpec;
|
2026-04-08 02:52:28 +08:00
|
|
|
using ::XCEngine::UI::Editor::UIEditorMenuItemKind;
|
2026-04-07 16:57:04 +08:00
|
|
|
|
|
|
|
|
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>(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();
|
2026-04-08 02:52:28 +08:00
|
|
|
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<std::uint32_t>('0') &&
|
|
|
|
|
character <= static_cast<std::uint32_t>('9')) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (character == static_cast<std::uint32_t>('-') ||
|
|
|
|
|
character == static_cast<std::uint32_t>('+')) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return !field.numberValue.integerMode && character == static_cast<std::uint32_t>('.');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 00:41:28 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
UIEditorPropertyGridField* FindMutableField(
|
|
|
|
|
std::vector<UIEditorPropertyGridSection>& 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<UIEditorPropertyGridSection>& 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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 00:41:28 +08:00
|
|
|
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<UIEditorPropertyGridSection>& 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
std::vector<UIEditorMenuPopupItem> BuildPopupItems(
|
|
|
|
|
const UIEditorPropertyGridField& field) {
|
|
|
|
|
std::vector<UIEditorMenuPopupItem> 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<UIEditorPropertyGridSection>& sections,
|
|
|
|
|
const Widgets::UIEditorMenuPopupMetrics& popupMetrics,
|
|
|
|
|
UIEditorMenuPopupLayout& popupLayout,
|
|
|
|
|
std::vector<UIEditorMenuPopupItem>& 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<UIEditorPropertyGridSection>& sections,
|
|
|
|
|
const Widgets::UIEditorMenuPopupMetrics& popupMetrics) {
|
|
|
|
|
if (!state.hasPointerPosition) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorMenuPopupLayout popupLayout = {};
|
|
|
|
|
std::vector<UIEditorMenuPopupItem> popupItems = {};
|
|
|
|
|
if (!BuildPopupLayout(state, layout, sections, popupMetrics, popupLayout, popupItems)) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Widgets::HitTestUIEditorMenuPopup(popupLayout, popupItems, state.pointerPosition);
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SyncHoverTarget(
|
|
|
|
|
UIEditorPropertyGridInteractionState& state,
|
2026-04-08 02:52:28 +08:00
|
|
|
const UIEditorPropertyGridLayout& layout,
|
|
|
|
|
const std::vector<UIEditorPropertyGridSection>& sections,
|
|
|
|
|
const Widgets::UIEditorMenuPopupMetrics& popupMetrics) {
|
2026-04-07 16:57:04 +08:00
|
|
|
ClearHoverState(state);
|
|
|
|
|
if (!state.hasPointerPosition) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
const UIEditorPropertyGridHitTarget hitTarget =
|
|
|
|
|
HitTestUIEditorPropertyGrid(layout, state.pointerPosition);
|
2026-04-08 02:52:28 +08:00
|
|
|
state.propertyGridState.hoveredHitTarget = hitTarget.kind;
|
2026-04-07 16:57:04 +08:00
|
|
|
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,
|
2026-04-08 02:52:28 +08:00
|
|
|
const UIEditorPropertyGridLayout& layout,
|
|
|
|
|
const std::vector<UIEditorPropertyGridSection>& sections) {
|
2026-04-07 16:57:04 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
bool SelectVisibleField(
|
|
|
|
|
UIEditorPropertyGridInteractionState& state,
|
|
|
|
|
::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
|
|
|
|
|
const UIEditorPropertyGridLayout& layout,
|
|
|
|
|
const std::vector<UIEditorPropertyGridSection>& 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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
bool BeginFieldEdit(
|
|
|
|
|
UIEditorPropertyGridInteractionState& state,
|
|
|
|
|
::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel,
|
2026-04-08 02:52:28 +08:00
|
|
|
const UIEditorPropertyGridField& field,
|
2026-04-07 16:57:04 +08:00
|
|
|
UIEditorPropertyGridInteractionResult& result) {
|
2026-04-08 02:52:28 +08:00
|
|
|
if (field.readOnly || !IsInlineEditable(field)) {
|
2026-04-07 16:57:04 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
const std::string initialValue =
|
|
|
|
|
field.kind == UIEditorPropertyGridFieldKind::Text
|
|
|
|
|
? field.valueText
|
|
|
|
|
: ResolveUIEditorPropertyGridFieldValueText(field);
|
|
|
|
|
const bool changed = propertyEditModel.BeginEdit(field.fieldId, initialValue);
|
2026-04-07 16:57:04 +08:00
|
|
|
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,
|
2026-04-08 02:52:28 +08:00
|
|
|
std::vector<UIEditorPropertyGridSection>& sections,
|
2026-04-07 16:57:04 +08:00
|
|
|
UIEditorPropertyGridInteractionResult& result) {
|
|
|
|
|
if (!propertyEditModel.HasActiveEdit()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
UIEditorPropertyGridField* field =
|
|
|
|
|
FindMutableField(sections, propertyEditModel.GetActiveFieldId());
|
|
|
|
|
if (field == nullptr) {
|
|
|
|
|
propertyEditModel.CancelEdit();
|
|
|
|
|
state.textInputState = {};
|
2026-04-07 16:57:04 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
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();
|
2026-04-07 16:57:04 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
void ClosePopup(
|
2026-04-07 16:57:04 +08:00
|
|
|
UIEditorPropertyGridInteractionState& state,
|
|
|
|
|
UIEditorPropertyGridInteractionResult& result) {
|
2026-04-08 02:52:28 +08:00
|
|
|
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<UIEditorPropertyGridSection>& 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()) {
|
2026-04-07 16:57:04 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
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) {
|
2026-04-07 16:57:04 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
field.boolValue = !field.boolValue;
|
|
|
|
|
SetChangedValueResult(field, result);
|
2026-04-07 16:57:04 +08:00
|
|
|
result.consumed = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 00:41:28 +08:00
|
|
|
bool ProcessColorFieldEvent(
|
|
|
|
|
UIEditorPropertyGridInteractionState& state,
|
|
|
|
|
::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
|
|
|
|
|
::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel,
|
|
|
|
|
const UIEditorPropertyGridLayout& layout,
|
|
|
|
|
std::vector<UIEditorPropertyGridSection>& 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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction(
|
|
|
|
|
UIEditorPropertyGridInteractionState& state,
|
|
|
|
|
::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
|
|
|
|
|
::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
|
|
|
|
|
::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel,
|
|
|
|
|
const ::XCEngine::UI::UIRect& bounds,
|
2026-04-08 02:52:28 +08:00
|
|
|
std::vector<UIEditorPropertyGridSection>& sections,
|
2026-04-07 16:57:04 +08:00
|
|
|
const std::vector<UIInputEvent>& inputEvents,
|
2026-04-08 02:52:28 +08:00
|
|
|
const Widgets::UIEditorPropertyGridMetrics& metrics,
|
|
|
|
|
const Widgets::UIEditorMenuPopupMetrics& popupMetrics) {
|
|
|
|
|
UIEditorPropertyGridLayout layout =
|
2026-04-07 16:57:04 +08:00
|
|
|
BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics);
|
2026-04-10 00:41:28 +08:00
|
|
|
PruneColorFieldVisualStates(state, layout, sections);
|
2026-04-07 16:57:04 +08:00
|
|
|
SyncKeyboardNavigation(state, selectionModel, layout, sections);
|
2026-04-08 02:52:28 +08:00
|
|
|
SyncHoverTarget(state, layout, sections, popupMetrics);
|
2026-04-07 16:57:04 +08:00
|
|
|
|
|
|
|
|
UIEditorPropertyGridInteractionResult interactionResult = {};
|
|
|
|
|
for (const UIInputEvent& event : inputEvents) {
|
|
|
|
|
if (ShouldUsePointerPosition(event)) {
|
|
|
|
|
state.pointerPosition = event.position;
|
|
|
|
|
state.hasPointerPosition = true;
|
|
|
|
|
} else if (event.type == UIInputEventType::PointerLeave) {
|
|
|
|
|
state.hasPointerPosition = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorPropertyGridInteractionResult eventResult = {};
|
2026-04-08 02:52:28 +08:00
|
|
|
const UIEditorMenuPopupHitTarget popupHit = ResolvePopupHit(state, layout, sections, popupMetrics);
|
|
|
|
|
const UIEditorPropertyGridHitTarget hitTarget =
|
|
|
|
|
state.hasPointerPosition
|
|
|
|
|
? HitTestUIEditorPropertyGrid(layout, state.pointerPosition)
|
|
|
|
|
: UIEditorPropertyGridHitTarget {};
|
|
|
|
|
eventResult.hitTarget = hitTarget;
|
|
|
|
|
|
2026-04-10 00:41:28 +08:00
|
|
|
if (ProcessColorFieldEvent(
|
|
|
|
|
state,
|
|
|
|
|
selectionModel,
|
|
|
|
|
propertyEditModel,
|
|
|
|
|
layout,
|
|
|
|
|
sections,
|
|
|
|
|
metrics,
|
|
|
|
|
event,
|
|
|
|
|
eventResult)) {
|
|
|
|
|
MergeInteractionResult(interactionResult, eventResult);
|
|
|
|
|
layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics);
|
|
|
|
|
PruneColorFieldVisualStates(state, layout, sections);
|
|
|
|
|
SyncKeyboardNavigation(state, selectionModel, layout, sections);
|
|
|
|
|
SyncHoverTarget(state, layout, sections, popupMetrics);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
switch (event.type) {
|
|
|
|
|
case UIInputEventType::FocusGained:
|
|
|
|
|
state.propertyGridState.focused = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::FocusLost:
|
2026-04-08 02:52:28 +08:00
|
|
|
CommitActiveEdit(state, propertyEditModel, sections, eventResult);
|
|
|
|
|
ClosePopup(state, eventResult);
|
2026-04-07 16:57:04 +08:00
|
|
|
state.propertyGridState.focused = false;
|
2026-04-08 02:52:28 +08:00
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
2026-04-07 16:57:04 +08:00
|
|
|
state.hasPointerPosition = false;
|
|
|
|
|
ClearHoverState(state);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::PointerMove:
|
|
|
|
|
case UIInputEventType::PointerEnter:
|
|
|
|
|
case UIInputEventType::PointerLeave:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::PointerButtonDown: {
|
2026-04-08 02:52:28 +08:00
|
|
|
if (event.pointerButton == UIPointerButton::Left) {
|
|
|
|
|
const bool insideGrid =
|
|
|
|
|
state.hasPointerPosition &&
|
|
|
|
|
IsUIEditorPropertyGridPointInside(layout.bounds, state.pointerPosition);
|
|
|
|
|
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) {
|
|
|
|
|
state.propertyGridState.focused = true;
|
|
|
|
|
state.pressedPopupIndex = popupHit.index;
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
} else if (popupHit.kind == UIEditorMenuPopupHitTargetKind::PopupSurface) {
|
|
|
|
|
state.propertyGridState.focused = true;
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
} else if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow ||
|
|
|
|
|
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) {
|
|
|
|
|
state.propertyGridState.focused = true;
|
|
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
} else {
|
|
|
|
|
state.propertyGridState.focused = false;
|
|
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
|
|
|
|
}
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::PointerButtonUp: {
|
2026-04-08 02:52:28 +08:00
|
|
|
if (event.pointerButton == UIPointerButton::Left) {
|
|
|
|
|
const bool insideGrid =
|
|
|
|
|
state.hasPointerPosition &&
|
|
|
|
|
IsUIEditorPropertyGridPointInside(layout.bounds, state.pointerPosition);
|
|
|
|
|
|
|
|
|
|
if (state.pressedPopupIndex != UIEditorPropertyGridInvalidIndex) {
|
|
|
|
|
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item &&
|
|
|
|
|
popupHit.index == state.pressedPopupIndex) {
|
|
|
|
|
eventResult.fieldValueChanged =
|
|
|
|
|
CommitPopupSelection(state, sections, eventResult);
|
|
|
|
|
} else if (popupHit.kind == UIEditorMenuPopupHitTargetKind::None) {
|
|
|
|
|
ClosePopup(state, eventResult);
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
2026-04-08 02:52:28 +08:00
|
|
|
state.pressedPopupIndex = UIEditorPropertyGridInvalidIndex;
|
|
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::PopupSurface) {
|
|
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::None) {
|
|
|
|
|
CommitActiveEdit(state, propertyEditModel, sections, eventResult);
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
state.propertyGridState.focused = insideGrid;
|
|
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
|
|
|
|
if (insideGrid) {
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader &&
|
|
|
|
|
hitTarget.sectionIndex < sections.size()) {
|
2026-04-08 02:52:28 +08:00
|
|
|
CommitActiveEdit(state, propertyEditModel, sections, eventResult);
|
|
|
|
|
ClosePopup(state, eventResult);
|
2026-04-07 16:57:04 +08:00
|
|
|
const std::string& sectionId = sections[hitTarget.sectionIndex].sectionId;
|
|
|
|
|
eventResult.sectionToggled = expansionModel.ToggleExpanded(sectionId);
|
|
|
|
|
eventResult.toggledSectionId = sectionId;
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
state.propertyGridState.focused = true;
|
2026-04-08 02:52:28 +08:00
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow ||
|
|
|
|
|
hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) &&
|
2026-04-07 16:57:04 +08:00
|
|
|
hitTarget.sectionIndex < sections.size() &&
|
|
|
|
|
hitTarget.fieldIndex < sections[hitTarget.sectionIndex].fields.size()) {
|
2026-04-08 02:52:28 +08:00
|
|
|
UIEditorPropertyGridField& field =
|
|
|
|
|
sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex];
|
2026-04-07 16:57:04 +08:00
|
|
|
state.propertyGridState.focused = true;
|
|
|
|
|
SelectVisibleField(
|
|
|
|
|
state,
|
|
|
|
|
selectionModel,
|
|
|
|
|
layout,
|
|
|
|
|
sections,
|
|
|
|
|
hitTarget.visibleFieldIndex,
|
|
|
|
|
eventResult);
|
|
|
|
|
|
|
|
|
|
if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) {
|
2026-04-08 02:52:28 +08:00
|
|
|
if (field.kind == UIEditorPropertyGridFieldKind::Bool) {
|
|
|
|
|
CommitActiveEdit(state, propertyEditModel, sections, eventResult);
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
ToggleBoolField(field, eventResult);
|
|
|
|
|
} else if (field.kind == UIEditorPropertyGridFieldKind::Enum) {
|
|
|
|
|
CommitActiveEdit(state, propertyEditModel, sections, eventResult);
|
|
|
|
|
if (state.propertyGridState.popupFieldId == field.fieldId) {
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
} else {
|
|
|
|
|
ClearPopupState(state);
|
|
|
|
|
OpenPopup(state, field, eventResult);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
if (!(propertyEditModel.HasActiveEdit() &&
|
|
|
|
|
propertyEditModel.GetActiveFieldId() == field.fieldId)) {
|
|
|
|
|
CommitActiveEdit(state, propertyEditModel, sections, eventResult);
|
|
|
|
|
BeginFieldEdit(state, propertyEditModel, field, eventResult);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (state.propertyGridState.popupFieldId != field.fieldId) {
|
|
|
|
|
ClosePopup(state, eventResult);
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-08 02:52:28 +08:00
|
|
|
|
|
|
|
|
state.propertyGridState.pressedFieldId.clear();
|
2026-04-07 16:57:04 +08:00
|
|
|
} 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()) {
|
2026-04-08 02:52:28 +08:00
|
|
|
const UIEditorPropertyGridField& field =
|
2026-04-07 16:57:04 +08:00
|
|
|
sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex];
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::KeyDown:
|
|
|
|
|
if (!state.propertyGridState.focused) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
if (!state.propertyGridState.popupFieldId.empty()) {
|
|
|
|
|
const UIEditorPropertyGridField* popupField =
|
|
|
|
|
FindField(sections, state.propertyGridState.popupFieldId);
|
|
|
|
|
if (popupField != nullptr) {
|
|
|
|
|
switch (static_cast<KeyCode>(event.keyCode)) {
|
|
|
|
|
case KeyCode::Up:
|
|
|
|
|
MovePopupHighlight(state, *popupField, -1);
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::Down:
|
|
|
|
|
MovePopupHighlight(state, *popupField, 1);
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::Home:
|
|
|
|
|
JumpPopupHighlightToEdge(state, *popupField, false);
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::End:
|
|
|
|
|
JumpPopupHighlightToEdge(state, *popupField, true);
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::Enter:
|
|
|
|
|
case KeyCode::Space:
|
|
|
|
|
CommitPopupSelection(state, sections, eventResult);
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::Escape:
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
if (propertyEditModel.HasActiveEdit()) {
|
|
|
|
|
if (static_cast<KeyCode>(event.keyCode) == KeyCode::Escape) {
|
|
|
|
|
CancelActiveEdit(state, propertyEditModel, eventResult);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto editResult = HandleKeyDown(
|
|
|
|
|
state.textInputState,
|
|
|
|
|
event.keyCode,
|
|
|
|
|
event.modifiers,
|
|
|
|
|
{});
|
|
|
|
|
if (editResult.handled) {
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
eventResult.activeFieldId = propertyEditModel.GetActiveFieldId();
|
|
|
|
|
if (editResult.valueChanged) {
|
|
|
|
|
propertyEditModel.UpdateStagedValue(state.textInputState.value);
|
|
|
|
|
eventResult.editValueChanged = true;
|
|
|
|
|
}
|
|
|
|
|
if (editResult.submitRequested) {
|
2026-04-08 02:52:28 +08:00
|
|
|
CommitActiveEdit(state, propertyEditModel, sections, eventResult);
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
if ((static_cast<KeyCode>(event.keyCode) == KeyCode::Enter ||
|
|
|
|
|
static_cast<KeyCode>(event.keyCode) == KeyCode::Space) &&
|
2026-04-07 16:57:04 +08:00
|
|
|
selectionModel.HasSelection()) {
|
2026-04-08 02:52:28 +08:00
|
|
|
UIEditorPropertyGridField* field =
|
|
|
|
|
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<KeyCode>(event.keyCode) == KeyCode::Enter) {
|
|
|
|
|
BeginFieldEdit(state, propertyEditModel, *field, eventResult);
|
2026-04-07 16:57:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!event.modifiers.shift &&
|
|
|
|
|
!HasSystemModifiers(event.modifiers) &&
|
|
|
|
|
ApplyKeyboardNavigation(state, event.keyCode) &&
|
|
|
|
|
state.keyboardNavigation.HasCurrentIndex()) {
|
|
|
|
|
const std::size_t currentIndex = state.keyboardNavigation.GetCurrentIndex();
|
|
|
|
|
if (SelectVisibleField(
|
|
|
|
|
state,
|
|
|
|
|
selectionModel,
|
|
|
|
|
layout,
|
|
|
|
|
sections,
|
|
|
|
|
currentIndex,
|
|
|
|
|
eventResult)) {
|
|
|
|
|
eventResult.keyboardNavigated = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::Character:
|
|
|
|
|
if (!state.propertyGridState.focused ||
|
|
|
|
|
!propertyEditModel.HasActiveEdit() ||
|
|
|
|
|
HasSystemModifiers(event.modifiers)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
if (const UIEditorPropertyGridField* field =
|
|
|
|
|
FindField(sections, propertyEditModel.GetActiveFieldId());
|
|
|
|
|
field == nullptr || !IsNumberEditCharacter(*field, event.character)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 16:57:04 +08:00
|
|
|
if (InsertCharacter(state.textInputState, event.character)) {
|
|
|
|
|
propertyEditModel.UpdateStagedValue(state.textInputState.value);
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
eventResult.editValueChanged = true;
|
|
|
|
|
eventResult.activeFieldId = propertyEditModel.GetActiveFieldId();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics);
|
2026-04-10 00:41:28 +08:00
|
|
|
PruneColorFieldVisualStates(state, layout, sections);
|
2026-04-07 16:57:04 +08:00
|
|
|
SyncKeyboardNavigation(state, selectionModel, layout, sections);
|
2026-04-08 02:52:28 +08:00
|
|
|
SyncHoverTarget(state, layout, sections, popupMetrics);
|
2026-04-07 16:57:04 +08:00
|
|
|
if (eventResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None &&
|
|
|
|
|
state.hasPointerPosition) {
|
|
|
|
|
eventResult.hitTarget = HitTestUIEditorPropertyGrid(layout, state.pointerPosition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (eventResult.consumed ||
|
|
|
|
|
eventResult.sectionToggled ||
|
|
|
|
|
eventResult.selectionChanged ||
|
|
|
|
|
eventResult.keyboardNavigated ||
|
|
|
|
|
eventResult.editStarted ||
|
|
|
|
|
eventResult.editValueChanged ||
|
|
|
|
|
eventResult.editCommitted ||
|
2026-04-08 02:52:28 +08:00
|
|
|
eventResult.editCommitRejected ||
|
2026-04-07 16:57:04 +08:00
|
|
|
eventResult.editCanceled ||
|
2026-04-08 02:52:28 +08:00
|
|
|
eventResult.popupOpened ||
|
|
|
|
|
eventResult.popupClosed ||
|
|
|
|
|
eventResult.fieldValueChanged ||
|
2026-04-07 16:57:04 +08:00
|
|
|
eventResult.secondaryClicked ||
|
|
|
|
|
eventResult.hitTarget.kind != UIEditorPropertyGridHitTargetKind::None ||
|
|
|
|
|
!eventResult.toggledSectionId.empty() ||
|
|
|
|
|
!eventResult.selectedFieldId.empty() ||
|
|
|
|
|
!eventResult.activeFieldId.empty() ||
|
2026-04-08 02:52:28 +08:00
|
|
|
!eventResult.committedFieldId.empty() ||
|
|
|
|
|
!eventResult.changedFieldId.empty()) {
|
2026-04-07 16:57:04 +08:00
|
|
|
interactionResult = std::move(eventResult);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics);
|
2026-04-10 00:41:28 +08:00
|
|
|
PruneColorFieldVisualStates(state, layout, sections);
|
2026-04-07 16:57:04 +08:00
|
|
|
SyncKeyboardNavigation(state, selectionModel, layout, sections);
|
2026-04-08 02:52:28 +08:00
|
|
|
SyncHoverTarget(state, layout, sections, popupMetrics);
|
2026-04-07 16:57:04 +08:00
|
|
|
if (interactionResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None &&
|
|
|
|
|
state.hasPointerPosition) {
|
|
|
|
|
interactionResult.hitTarget = HitTestUIEditorPropertyGrid(layout, state.pointerPosition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
std::move(layout),
|
|
|
|
|
std::move(interactionResult)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor
|