#include #include #include namespace XCEngine::UI::Editor { namespace { using ::XCEngine::Input::KeyCode; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPointerButton; using Widgets::BuildUIEditorAssetFieldLayout; using Widgets::HasUIEditorAssetFieldValue; using Widgets::HitTestUIEditorAssetField; using Widgets::IsUIEditorAssetFieldPointInside; using Widgets::UIEditorAssetFieldHitTarget; using Widgets::UIEditorAssetFieldHitTargetKind; using Widgets::UIEditorAssetFieldLayout; using Widgets::UIEditorAssetFieldMetrics; using Widgets::UIEditorAssetFieldSpec; bool ShouldUsePointerPosition(const UIInputEvent& event) { switch (event.type) { case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerButtonDown: case UIInputEventType::PointerButtonUp: case UIInputEventType::PointerWheel: return true; default: return false; } } void SyncHoverTarget( UIEditorAssetFieldInteractionState& state, const UIEditorAssetFieldLayout& layout) { if (!state.hasPointerPosition) { state.fieldState.hoveredTarget = UIEditorAssetFieldHitTargetKind::None; return; } state.fieldState.hoveredTarget = HitTestUIEditorAssetField(layout, state.pointerPosition).kind; } bool CanRequestPicker(const UIEditorAssetFieldSpec& spec) { return !spec.readOnly && spec.showPickerButton; } bool CanClearValue(const UIEditorAssetFieldSpec& spec) { return !spec.readOnly && spec.allowClear && HasUIEditorAssetFieldValue(spec); } void ClearValue( UIEditorAssetFieldSpec& spec, UIEditorAssetFieldInteractionResult& result) { result.assetIdBefore = spec.assetId; result.displayNameBefore = spec.displayName; spec.assetId.clear(); spec.displayName.clear(); spec.statusText.clear(); result.assetIdAfter = spec.assetId; result.displayNameAfter = spec.displayName; result.clearRequested = true; result.valueChanged = true; result.consumed = true; } void RequestPicker(UIEditorAssetFieldInteractionResult& result) { result.pickerRequested = true; result.consumed = true; } void RequestActivate(UIEditorAssetFieldInteractionResult& result) { result.activateRequested = true; result.consumed = true; } } // namespace UIEditorAssetFieldInteractionFrame UpdateUIEditorAssetFieldInteraction( UIEditorAssetFieldInteractionState& state, UIEditorAssetFieldSpec& spec, const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const UIEditorAssetFieldMetrics& metrics) { UIEditorAssetFieldLayout layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); SyncHoverTarget(state, layout); UIEditorAssetFieldInteractionResult 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; } UIEditorAssetFieldInteractionResult eventResult = {}; switch (event.type) { case UIInputEventType::FocusGained: eventResult.focusChanged = !state.fieldState.focused; state.fieldState.focused = true; break; case UIInputEventType::FocusLost: eventResult.focusChanged = state.fieldState.focused; state.fieldState.focused = false; state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None; state.hasPointerPosition = false; break; case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerLeave: break; case UIInputEventType::PointerButtonDown: { const UIEditorAssetFieldHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorAssetField(layout, state.pointerPosition) : UIEditorAssetFieldHitTarget {}; eventResult.hitTarget = hitTarget; if (event.pointerButton != UIPointerButton::Left) { break; } const bool insideField = state.hasPointerPosition && IsUIEditorAssetFieldPointInside(layout.bounds, state.pointerPosition); if (insideField) { eventResult.focusChanged = !state.fieldState.focused; state.fieldState.focused = true; state.fieldState.activeTarget = hitTarget.kind == UIEditorAssetFieldHitTargetKind::None ? UIEditorAssetFieldHitTargetKind::Row : hitTarget.kind; eventResult.consumed = true; } else { if (state.fieldState.focused) { eventResult.focusChanged = true; state.fieldState.focused = false; } state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None; } break; } case UIInputEventType::PointerButtonUp: { const UIEditorAssetFieldHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorAssetField(layout, state.pointerPosition) : UIEditorAssetFieldHitTarget {}; eventResult.hitTarget = hitTarget; if (event.pointerButton == UIPointerButton::Left) { const UIEditorAssetFieldHitTargetKind activeTarget = state.fieldState.activeTarget; state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None; if (activeTarget == hitTarget.kind) { switch (activeTarget) { case UIEditorAssetFieldHitTargetKind::PickerButton: if (CanRequestPicker(spec)) { RequestPicker(eventResult); } break; case UIEditorAssetFieldHitTargetKind::ClearButton: if (CanClearValue(spec)) { ClearValue(spec, eventResult); } break; case UIEditorAssetFieldHitTargetKind::ValueBox: RequestActivate(eventResult); break; case UIEditorAssetFieldHitTargetKind::Row: eventResult.consumed = true; break; case UIEditorAssetFieldHitTargetKind::None: default: break; } } } break; } case UIInputEventType::KeyDown: if (!state.fieldState.focused) { break; } switch (static_cast(event.keyCode)) { case KeyCode::Enter: case KeyCode::Space: if (CanRequestPicker(spec)) { RequestPicker(eventResult); eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::PickerButton; } else { RequestActivate(eventResult); eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::ValueBox; } break; case KeyCode::Delete: case KeyCode::Backspace: if (CanClearValue(spec)) { ClearValue(spec, eventResult); eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::ClearButton; } break; default: break; } break; default: break; } layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); SyncHoverTarget(state, layout); if (eventResult.hitTarget.kind == UIEditorAssetFieldHitTargetKind::None && state.hasPointerPosition) { eventResult.hitTarget = HitTestUIEditorAssetField(layout, state.pointerPosition); } if (eventResult.consumed || eventResult.focusChanged || eventResult.valueChanged || eventResult.activateRequested || eventResult.pickerRequested || eventResult.clearRequested || eventResult.hitTarget.kind != UIEditorAssetFieldHitTargetKind::None) { interactionResult = std::move(eventResult); } } layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); SyncHoverTarget(state, layout); if (interactionResult.hitTarget.kind == UIEditorAssetFieldHitTargetKind::None && state.hasPointerPosition) { interactionResult.hitTarget = HitTestUIEditorAssetField(layout, state.pointerPosition); } return { std::move(layout), std::move(interactionResult) }; } } // namespace XCEngine::UI::Editor