2026-04-10 00:41:28 +08:00
|
|
|
#include <XCEditor/Fields/UIEditorEnumFieldInteraction.h>
|
2026-04-07 17:55:42 +08:00
|
|
|
|
|
|
|
|
#include <XCEngine/Input/InputTypes.h>
|
2026-04-08 02:52:28 +08:00
|
|
|
#include <XCEngine/UI/Widgets/UIPopupOverlayModel.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <utility>
|
2026-04-07 17:55:42 +08:00
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
using ::XCEngine::Input::KeyCode;
|
|
|
|
|
using ::XCEngine::UI::UIInputEvent;
|
|
|
|
|
using ::XCEngine::UI::UIInputEventType;
|
|
|
|
|
using ::XCEngine::UI::UIPointerButton;
|
2026-04-08 02:52:28 +08:00
|
|
|
using ::XCEngine::UI::UISize;
|
|
|
|
|
using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect;
|
|
|
|
|
using ::XCEngine::UI::Widgets::UIPopupPlacement;
|
2026-04-07 17:55:42 +08:00
|
|
|
using Widgets::BuildUIEditorEnumFieldLayout;
|
2026-04-08 02:52:28 +08:00
|
|
|
using Widgets::BuildUIEditorMenuPopupLayout;
|
2026-04-07 17:55:42 +08:00
|
|
|
using Widgets::HitTestUIEditorEnumField;
|
2026-04-08 02:52:28 +08:00
|
|
|
using Widgets::HitTestUIEditorMenuPopup;
|
|
|
|
|
using Widgets::MeasureUIEditorMenuPopupHeight;
|
|
|
|
|
using Widgets::ResolveUIEditorMenuPopupDesiredWidth;
|
|
|
|
|
using Widgets::UIEditorEnumFieldHitTarget;
|
2026-04-07 17:55:42 +08:00
|
|
|
using Widgets::UIEditorEnumFieldHitTargetKind;
|
2026-04-08 02:52:28 +08:00
|
|
|
using Widgets::UIEditorEnumFieldLayout;
|
|
|
|
|
using Widgets::UIEditorEnumFieldMetrics;
|
|
|
|
|
using Widgets::UIEditorEnumFieldSpec;
|
|
|
|
|
using Widgets::UIEditorMenuPopupHitTarget;
|
|
|
|
|
using Widgets::UIEditorMenuPopupHitTargetKind;
|
|
|
|
|
using Widgets::UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
using Widgets::UIEditorMenuPopupItem;
|
|
|
|
|
using Widgets::UIEditorMenuPopupLayout;
|
|
|
|
|
using Widgets::UIEditorMenuPopupMetrics;
|
|
|
|
|
using Widgets::UIEditorMenuPopupState;
|
|
|
|
|
using ::XCEngine::UI::Editor::UIEditorMenuItemKind;
|
2026-04-07 17:55:42 +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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
std::size_t ClampSelectedIndex(std::size_t selectedIndex, const UIEditorEnumFieldSpec& spec) {
|
2026-04-07 17:55:42 +08:00
|
|
|
if (spec.options.empty()) {
|
2026-04-08 02:52:28 +08:00
|
|
|
return 0u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (std::min)(selectedIndex, spec.options.size() - 1u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::XCEngine::UI::UIRect ResolveViewportRect(
|
|
|
|
|
const ::XCEngine::UI::UIRect& bounds,
|
|
|
|
|
const ::XCEngine::UI::UIRect& viewportRect) {
|
|
|
|
|
if (viewportRect.width > 0.0f && viewportRect.height > 0.0f) {
|
|
|
|
|
return viewportRect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ::XCEngine::UI::UIRect(bounds.x - 4096.0f, bounds.y - 4096.0f, 8192.0f, 8192.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<UIEditorMenuPopupItem> BuildPopupItems(
|
|
|
|
|
const UIEditorEnumFieldSpec& spec,
|
|
|
|
|
std::size_t selectedIndex) {
|
|
|
|
|
std::vector<UIEditorMenuPopupItem> items = {};
|
|
|
|
|
items.reserve(spec.options.size());
|
|
|
|
|
|
|
|
|
|
const std::size_t clampedIndex = ClampSelectedIndex(selectedIndex, spec);
|
|
|
|
|
for (std::size_t index = 0u; index < spec.options.size(); ++index) {
|
|
|
|
|
UIEditorMenuPopupItem item = {};
|
|
|
|
|
item.itemId = spec.fieldId + "." + std::to_string(index);
|
|
|
|
|
item.kind = UIEditorMenuItemKind::Command;
|
|
|
|
|
item.label = spec.options[index];
|
|
|
|
|
item.enabled = !spec.readOnly;
|
|
|
|
|
item.checked = index == clampedIndex;
|
|
|
|
|
items.push_back(std::move(item));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return items;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorMenuPopupLayout BuildPopupLayout(
|
|
|
|
|
const UIEditorEnumFieldLayout& fieldLayout,
|
|
|
|
|
const std::vector<UIEditorMenuPopupItem>& popupItems,
|
|
|
|
|
const UIEditorMenuPopupMetrics& popupMetrics,
|
|
|
|
|
const ::XCEngine::UI::UIRect& viewportRect) {
|
|
|
|
|
if (popupItems.empty()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float popupWidth = (std::max)(
|
|
|
|
|
fieldLayout.valueRect.width,
|
|
|
|
|
ResolveUIEditorMenuPopupDesiredWidth(popupItems, popupMetrics));
|
|
|
|
|
const float popupHeight = MeasureUIEditorMenuPopupHeight(popupItems, popupMetrics);
|
|
|
|
|
const auto placement = ResolvePopupPlacementRect(
|
|
|
|
|
fieldLayout.valueRect,
|
|
|
|
|
UISize(popupWidth, popupHeight),
|
|
|
|
|
viewportRect,
|
|
|
|
|
UIPopupPlacement::BottomStart);
|
|
|
|
|
return BuildUIEditorMenuPopupLayout(placement.rect, popupItems, popupMetrics);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SyncControlHover(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
const UIEditorEnumFieldLayout& layout) {
|
|
|
|
|
if (!state.hasPointerPosition) {
|
|
|
|
|
state.fieldState.hoveredTarget = UIEditorEnumFieldHitTargetKind::None;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.fieldState.hoveredTarget = HitTestUIEditorEnumField(layout, state.pointerPosition).kind;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorMenuPopupHitTarget ResolvePopupHit(
|
|
|
|
|
const UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
const UIEditorMenuPopupLayout& popupLayout,
|
|
|
|
|
const std::vector<UIEditorMenuPopupItem>& popupItems) {
|
|
|
|
|
if (!state.popupOpen || !state.hasPointerPosition) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return HitTestUIEditorMenuPopup(popupLayout, popupItems, state.pointerPosition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SyncPopupHover(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
const UIEditorMenuPopupLayout& popupLayout,
|
|
|
|
|
const std::vector<UIEditorMenuPopupItem>& popupItems,
|
|
|
|
|
std::size_t selectedIndex) {
|
|
|
|
|
if (!state.popupOpen) {
|
|
|
|
|
state.highlightedIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (popupItems.empty()) {
|
|
|
|
|
state.popupOpen = false;
|
|
|
|
|
state.highlightedIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorMenuPopupHitTarget popupHit = ResolvePopupHit(state, popupLayout, popupItems);
|
|
|
|
|
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) {
|
|
|
|
|
state.highlightedIndex = popupHit.index;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state.highlightedIndex == UIEditorMenuPopupInvalidIndex ||
|
|
|
|
|
state.highlightedIndex >= popupItems.size()) {
|
|
|
|
|
state.highlightedIndex = (std::min)(selectedIndex, popupItems.size() - 1u);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenPopup(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
const UIEditorEnumFieldSpec& spec,
|
|
|
|
|
std::size_t selectedIndex,
|
|
|
|
|
UIEditorEnumFieldInteractionResult& result) {
|
|
|
|
|
if (spec.readOnly || spec.options.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!state.popupOpen) {
|
|
|
|
|
state.popupOpen = true;
|
|
|
|
|
state.highlightedIndex = ClampSelectedIndex(selectedIndex, spec);
|
|
|
|
|
result.popupOpened = true;
|
|
|
|
|
result.consumed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClosePopup(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
UIEditorEnumFieldInteractionResult& result) {
|
|
|
|
|
if (!state.popupOpen) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.popupOpen = false;
|
|
|
|
|
state.pressedPopupIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
state.highlightedIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
result.popupClosed = true;
|
|
|
|
|
result.consumed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MovePopupHighlight(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
const UIEditorEnumFieldSpec& spec,
|
|
|
|
|
int delta) {
|
|
|
|
|
if (spec.options.empty()) {
|
|
|
|
|
state.highlightedIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
return;
|
2026-04-07 17:55:42 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
if (state.highlightedIndex == UIEditorMenuPopupInvalidIndex ||
|
|
|
|
|
state.highlightedIndex >= spec.options.size()) {
|
|
|
|
|
state.highlightedIndex = ClampSelectedIndex(0u, spec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::size_t currentIndex = state.highlightedIndex;
|
|
|
|
|
if (delta < 0) {
|
|
|
|
|
state.highlightedIndex = currentIndex == 0u ? 0u : currentIndex - 1u;
|
2026-04-07 17:55:42 +08:00
|
|
|
} else {
|
2026-04-08 02:52:28 +08:00
|
|
|
state.highlightedIndex =
|
|
|
|
|
currentIndex + 1u >= spec.options.size() ? spec.options.size() - 1u : currentIndex + 1u;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JumpPopupHighlightToEdge(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
const UIEditorEnumFieldSpec& spec,
|
|
|
|
|
bool toEnd) {
|
|
|
|
|
if (spec.options.empty()) {
|
|
|
|
|
state.highlightedIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
return;
|
2026-04-07 17:55:42 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
state.highlightedIndex = toEnd ? spec.options.size() - 1u : 0u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TrySelectHighlighted(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
std::size_t& selectedIndex,
|
|
|
|
|
const UIEditorEnumFieldSpec& spec,
|
|
|
|
|
UIEditorEnumFieldInteractionResult& result) {
|
|
|
|
|
if (!state.popupOpen ||
|
|
|
|
|
spec.readOnly ||
|
|
|
|
|
spec.options.empty() ||
|
|
|
|
|
state.highlightedIndex == UIEditorMenuPopupInvalidIndex ||
|
|
|
|
|
state.highlightedIndex >= spec.options.size()) {
|
2026-04-07 17:55:42 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
selectedIndex = state.highlightedIndex;
|
|
|
|
|
result.selectionChanged = true;
|
|
|
|
|
result.selectedIndex = selectedIndex;
|
|
|
|
|
result.popupItemIndex = state.highlightedIndex;
|
|
|
|
|
ClosePopup(state, result);
|
2026-04-07 17:55:42 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
UIEditorEnumFieldInteractionFrame UpdateUIEditorEnumFieldInteraction(
|
|
|
|
|
UIEditorEnumFieldInteractionState& state,
|
|
|
|
|
std::size_t& selectedIndex,
|
|
|
|
|
const ::XCEngine::UI::UIRect& bounds,
|
2026-04-08 02:52:28 +08:00
|
|
|
const UIEditorEnumFieldSpec& spec,
|
2026-04-07 17:55:42 +08:00
|
|
|
const std::vector<UIInputEvent>& inputEvents,
|
2026-04-08 02:52:28 +08:00
|
|
|
const UIEditorEnumFieldMetrics& metrics,
|
|
|
|
|
const UIEditorMenuPopupMetrics& popupMetrics,
|
|
|
|
|
const ::XCEngine::UI::UIRect& viewportRect) {
|
|
|
|
|
UIEditorEnumFieldSpec resolvedSpec = spec;
|
|
|
|
|
selectedIndex = ClampSelectedIndex(selectedIndex, resolvedSpec);
|
2026-04-07 17:55:42 +08:00
|
|
|
resolvedSpec.selectedIndex = selectedIndex;
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
UIEditorEnumFieldLayout layout = BuildUIEditorEnumFieldLayout(bounds, resolvedSpec, metrics);
|
|
|
|
|
const ::XCEngine::UI::UIRect resolvedViewport = ResolveViewportRect(bounds, viewportRect);
|
|
|
|
|
std::vector<UIEditorMenuPopupItem> popupItems =
|
|
|
|
|
state.popupOpen ? BuildPopupItems(resolvedSpec, selectedIndex) : std::vector<UIEditorMenuPopupItem> {};
|
|
|
|
|
UIEditorMenuPopupLayout popupLayout =
|
|
|
|
|
state.popupOpen ? BuildPopupLayout(layout, popupItems, popupMetrics, resolvedViewport) : UIEditorMenuPopupLayout {};
|
|
|
|
|
|
|
|
|
|
SyncControlHover(state, layout);
|
|
|
|
|
SyncPopupHover(state, popupLayout, popupItems, selectedIndex);
|
|
|
|
|
state.fieldState.popupOpen = state.popupOpen;
|
2026-04-07 17:55:42 +08:00
|
|
|
|
|
|
|
|
UIEditorEnumFieldInteractionResult 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIEditorEnumFieldInteractionResult eventResult = {};
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
case UIInputEventType::FocusGained:
|
2026-04-08 02:52:28 +08:00
|
|
|
eventResult.focusedChanged = !state.fieldState.focused;
|
2026-04-07 17:55:42 +08:00
|
|
|
state.fieldState.focused = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::FocusLost:
|
2026-04-08 02:52:28 +08:00
|
|
|
eventResult.focusedChanged = state.fieldState.focused;
|
2026-04-07 17:55:42 +08:00
|
|
|
state.fieldState.focused = false;
|
|
|
|
|
state.fieldState.active = false;
|
|
|
|
|
state.hasPointerPosition = false;
|
|
|
|
|
state.fieldState.hoveredTarget = UIEditorEnumFieldHitTargetKind::None;
|
2026-04-08 02:52:28 +08:00
|
|
|
ClosePopup(state, eventResult);
|
2026-04-07 17:55:42 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case UIInputEventType::PointerMove:
|
|
|
|
|
case UIInputEventType::PointerEnter:
|
|
|
|
|
case UIInputEventType::PointerLeave:
|
|
|
|
|
break;
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
case UIInputEventType::PointerButtonDown: {
|
|
|
|
|
if (event.pointerButton != UIPointerButton::Left) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorEnumFieldHitTarget fieldHit =
|
|
|
|
|
state.hasPointerPosition ? HitTestUIEditorEnumField(layout, state.pointerPosition) : UIEditorEnumFieldHitTarget {};
|
|
|
|
|
const UIEditorMenuPopupHitTarget popupHit = ResolvePopupHit(state, popupLayout, popupItems);
|
|
|
|
|
eventResult.hitTarget = fieldHit;
|
|
|
|
|
|
|
|
|
|
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) {
|
|
|
|
|
state.fieldState.focused = true;
|
|
|
|
|
state.pressedPopupIndex = popupHit.index;
|
|
|
|
|
state.highlightedIndex = popupHit.index;
|
|
|
|
|
eventResult.popupItemIndex = popupHit.index;
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
} else if (popupHit.kind == UIEditorMenuPopupHitTargetKind::PopupSurface) {
|
|
|
|
|
state.fieldState.focused = true;
|
|
|
|
|
state.pressedPopupIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
} else if (
|
|
|
|
|
fieldHit.kind == UIEditorEnumFieldHitTargetKind::ValueBox ||
|
|
|
|
|
fieldHit.kind == UIEditorEnumFieldHitTargetKind::DropdownArrow) {
|
|
|
|
|
eventResult.focusedChanged = !state.fieldState.focused;
|
2026-04-07 17:55:42 +08:00
|
|
|
state.fieldState.focused = true;
|
|
|
|
|
state.fieldState.active = true;
|
|
|
|
|
eventResult.consumed = true;
|
2026-04-08 02:52:28 +08:00
|
|
|
} else {
|
|
|
|
|
if (state.popupOpen) {
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
}
|
|
|
|
|
if (state.fieldState.focused) {
|
|
|
|
|
eventResult.focusedChanged = true;
|
|
|
|
|
state.fieldState.focused = false;
|
|
|
|
|
}
|
|
|
|
|
state.fieldState.active = false;
|
|
|
|
|
state.pressedPopupIndex = UIEditorMenuPopupInvalidIndex;
|
2026-04-07 17:55:42 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2026-04-08 02:52:28 +08:00
|
|
|
}
|
2026-04-07 17:55:42 +08:00
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
case UIInputEventType::PointerButtonUp: {
|
|
|
|
|
if (event.pointerButton != UIPointerButton::Left) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorEnumFieldHitTarget fieldHit =
|
|
|
|
|
state.hasPointerPosition ? HitTestUIEditorEnumField(layout, state.pointerPosition) : UIEditorEnumFieldHitTarget {};
|
|
|
|
|
const UIEditorMenuPopupHitTarget popupHit = ResolvePopupHit(state, popupLayout, popupItems);
|
|
|
|
|
eventResult.hitTarget = fieldHit;
|
|
|
|
|
|
|
|
|
|
if (state.pressedPopupIndex != UIEditorMenuPopupInvalidIndex) {
|
|
|
|
|
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item &&
|
|
|
|
|
popupHit.index == state.pressedPopupIndex &&
|
|
|
|
|
popupHit.index < resolvedSpec.options.size()) {
|
|
|
|
|
state.highlightedIndex = popupHit.index;
|
|
|
|
|
selectedIndex = popupHit.index;
|
2026-04-07 17:55:42 +08:00
|
|
|
eventResult.selectionChanged = true;
|
|
|
|
|
eventResult.selectedIndex = selectedIndex;
|
2026-04-08 02:52:28 +08:00
|
|
|
eventResult.popupItemIndex = popupHit.index;
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
}
|
|
|
|
|
state.pressedPopupIndex = UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
} else if (state.fieldState.active) {
|
|
|
|
|
state.fieldState.active = false;
|
|
|
|
|
if (fieldHit.kind == UIEditorEnumFieldHitTargetKind::ValueBox ||
|
|
|
|
|
fieldHit.kind == UIEditorEnumFieldHitTargetKind::DropdownArrow) {
|
|
|
|
|
if (state.popupOpen) {
|
|
|
|
|
ClosePopup(state, eventResult);
|
|
|
|
|
} else {
|
|
|
|
|
OpenPopup(state, resolvedSpec, selectedIndex, eventResult);
|
|
|
|
|
}
|
2026-04-07 17:55:42 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-04-08 02:52:28 +08:00
|
|
|
}
|
2026-04-07 17:55:42 +08:00
|
|
|
|
|
|
|
|
case UIInputEventType::KeyDown:
|
2026-04-08 02:52:28 +08:00
|
|
|
if (!state.fieldState.focused) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state.popupOpen) {
|
2026-04-07 17:55:42 +08:00
|
|
|
switch (static_cast<KeyCode>(event.keyCode)) {
|
2026-04-08 02:52:28 +08:00
|
|
|
case KeyCode::Up:
|
|
|
|
|
MovePopupHighlight(state, resolvedSpec, -1);
|
|
|
|
|
eventResult.consumed = true;
|
2026-04-07 17:55:42 +08:00
|
|
|
break;
|
2026-04-08 02:52:28 +08:00
|
|
|
|
|
|
|
|
case KeyCode::Down:
|
|
|
|
|
MovePopupHighlight(state, resolvedSpec, 1);
|
|
|
|
|
eventResult.consumed = true;
|
2026-04-07 17:55:42 +08:00
|
|
|
break;
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-07 17:55:42 +08:00
|
|
|
case KeyCode::Home:
|
2026-04-08 02:52:28 +08:00
|
|
|
JumpPopupHighlightToEdge(state, resolvedSpec, false);
|
|
|
|
|
eventResult.consumed = true;
|
2026-04-07 17:55:42 +08:00
|
|
|
break;
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-07 17:55:42 +08:00
|
|
|
case KeyCode::End:
|
2026-04-08 02:52:28 +08:00
|
|
|
JumpPopupHighlightToEdge(state, resolvedSpec, true);
|
|
|
|
|
eventResult.consumed = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case KeyCode::Enter:
|
|
|
|
|
case KeyCode::Space:
|
|
|
|
|
TrySelectHighlighted(state, selectedIndex, resolvedSpec, eventResult);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case KeyCode::Escape:
|
|
|
|
|
ClosePopup(state, eventResult);
|
2026-04-07 17:55:42 +08:00
|
|
|
break;
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-07 17:55:42 +08:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-04-08 02:52:28 +08:00
|
|
|
} else {
|
|
|
|
|
switch (static_cast<KeyCode>(event.keyCode)) {
|
|
|
|
|
case KeyCode::Enter:
|
|
|
|
|
case KeyCode::Space:
|
|
|
|
|
case KeyCode::Down:
|
|
|
|
|
case KeyCode::Up:
|
|
|
|
|
OpenPopup(state, resolvedSpec, selectedIndex, eventResult);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2026-04-07 17:55:42 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
selectedIndex = ClampSelectedIndex(selectedIndex, resolvedSpec);
|
2026-04-07 17:55:42 +08:00
|
|
|
resolvedSpec.selectedIndex = selectedIndex;
|
|
|
|
|
layout = BuildUIEditorEnumFieldLayout(bounds, resolvedSpec, metrics);
|
2026-04-08 02:52:28 +08:00
|
|
|
popupItems = state.popupOpen ? BuildPopupItems(resolvedSpec, selectedIndex) : std::vector<UIEditorMenuPopupItem> {};
|
|
|
|
|
popupLayout = state.popupOpen
|
|
|
|
|
? BuildPopupLayout(layout, popupItems, popupMetrics, resolvedViewport)
|
|
|
|
|
: UIEditorMenuPopupLayout {};
|
|
|
|
|
SyncControlHover(state, layout);
|
|
|
|
|
SyncPopupHover(state, popupLayout, popupItems, selectedIndex);
|
|
|
|
|
state.fieldState.popupOpen = state.popupOpen;
|
2026-04-07 17:55:42 +08:00
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
if (eventResult.selectionChanged ||
|
|
|
|
|
eventResult.popupOpened ||
|
|
|
|
|
eventResult.popupClosed ||
|
|
|
|
|
eventResult.focusedChanged ||
|
|
|
|
|
eventResult.consumed ||
|
|
|
|
|
eventResult.hitTarget.kind != UIEditorEnumFieldHitTargetKind::None ||
|
|
|
|
|
eventResult.popupItemIndex != UIEditorMenuPopupInvalidIndex) {
|
|
|
|
|
interactionResult = std::move(eventResult);
|
2026-04-07 17:55:42 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
resolvedSpec.selectedIndex = selectedIndex;
|
|
|
|
|
layout = BuildUIEditorEnumFieldLayout(bounds, resolvedSpec, metrics);
|
|
|
|
|
popupItems = state.popupOpen ? BuildPopupItems(resolvedSpec, selectedIndex) : std::vector<UIEditorMenuPopupItem> {};
|
|
|
|
|
popupLayout = state.popupOpen
|
|
|
|
|
? BuildPopupLayout(layout, popupItems, popupMetrics, resolvedViewport)
|
|
|
|
|
: UIEditorMenuPopupLayout {};
|
|
|
|
|
SyncControlHover(state, layout);
|
|
|
|
|
SyncPopupHover(state, popupLayout, popupItems, selectedIndex);
|
|
|
|
|
state.fieldState.popupOpen = state.popupOpen;
|
|
|
|
|
|
|
|
|
|
UIEditorMenuPopupState popupState = {};
|
|
|
|
|
popupState.focused = state.fieldState.focused || state.popupOpen;
|
|
|
|
|
popupState.hoveredIndex = state.popupOpen ? state.highlightedIndex : UIEditorMenuPopupInvalidIndex;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
std::move(layout),
|
|
|
|
|
std::move(popupLayout),
|
|
|
|
|
std::move(popupState),
|
|
|
|
|
std::move(popupItems),
|
|
|
|
|
state.popupOpen,
|
|
|
|
|
std::move(interactionResult)
|
|
|
|
|
};
|
2026-04-07 17:55:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor
|