#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 ::XCEngine::UI::Editor::Widgets::BuildUIEditorTextFieldLayout; using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorTextField; using ::XCEngine::UI::Editor::Widgets::IsUIEditorTextFieldPointInside; using ::XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTarget; using ::XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTargetKind; using ::XCEngine::UI::Editor::Widgets::UIEditorTextFieldLayout; using ::XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics; using ::XCEngine::UI::Editor::Widgets::UIEditorTextFieldSpec; void SyncDisplayState( UIEditorTextFieldInteractionState& state, const UIEditorTextFieldSpec& spec, const UIEditorTextFieldLayout& layout) { auto& fieldState = state.textFieldState; const auto& session = state.session; fieldState.focused = session.focused; fieldState.editing = session.editing; fieldState.caretBlinkStartNanoseconds = session.caretBlinkStartNanoseconds; fieldState.displayText = session.editing ? session.textInputState.value : spec.value; fieldState.caretOffset = session.editing ? session.textInputState.caret : spec.value.size(); if (!session.hasPointerPosition) { fieldState.hoveredTarget = UIEditorTextFieldHitTargetKind::None; return; } fieldState.hoveredTarget = HitTestUIEditorTextField(layout, session.pointerPosition).kind; } bool BeginEdit( UIEditorTextFieldInteractionState& state, const UIEditorTextFieldSpec& spec, bool clearText) { if (spec.readOnly) { return false; } return BeginUIEditorEditableFieldEdit( state.session, spec.fieldId, UIEditorEditableFieldInvalidComponentIndex, spec.value, clearText); } bool CommitEdit( UIEditorTextFieldInteractionState& state, UIEditorTextFieldSpec& spec, UIEditorTextFieldInteractionResult& result) { if (!state.session.editing) { return false; } result.valueBefore = spec.value; spec.value = state.session.textInputState.value; result.valueAfter = spec.value; result.valueChanged = result.valueBefore != result.valueAfter; result.editCommitted = true; result.consumed = true; result.committedText = spec.value; CommitUIEditorEditableFieldEdit(state.session); return true; } bool CancelEdit( UIEditorTextFieldInteractionState& state, const UIEditorTextFieldSpec& spec, UIEditorTextFieldInteractionResult& result) { if (!state.session.editing) { return false; } CancelUIEditorEditableFieldEdit(state.session); result.consumed = true; result.editCanceled = true; result.valueBefore = spec.value; result.valueAfter = spec.value; return true; } } // namespace UIEditorTextFieldInteractionFrame UpdateUIEditorTextFieldInteraction( UIEditorTextFieldInteractionState& state, UIEditorTextFieldSpec& spec, const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const UIEditorTextFieldMetrics& metrics) { UIEditorTextFieldLayout layout = BuildUIEditorTextFieldLayout( bounds, spec, metrics); SyncDisplayState(state, spec, layout); UIEditorTextFieldInteractionResult interactionResult = {}; for (const UIInputEvent& event : inputEvents) { UpdateUIEditorEditableFieldPointerPosition(state.session, event); UIEditorTextFieldInteractionResult eventResult = {}; switch (event.type) { case UIInputEventType::FocusGained: eventResult.focusChanged = !state.session.focused; state.session.focused = true; break; case UIInputEventType::FocusLost: eventResult.focusChanged = state.session.focused; state.session.focused = false; state.textFieldState.activeTarget = UIEditorTextFieldHitTargetKind::None; state.session.hasPointerPosition = false; EndUIEditorEditableFieldDrag(state.session); if (state.session.editing) { CommitEdit(state, spec, eventResult); } break; case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerLeave: break; case UIInputEventType::PointerButtonDown: { const UIEditorTextFieldHitTarget hitTarget = state.session.hasPointerPosition ? HitTestUIEditorTextField( layout, state.session.pointerPosition) : UIEditorTextFieldHitTarget {}; eventResult.hitTarget = hitTarget; if (event.pointerButton != UIPointerButton::Left) { break; } const bool insideField = state.session.hasPointerPosition && IsUIEditorTextFieldPointInside( layout.bounds, state.session.pointerPosition); if (insideField) { eventResult.focusChanged = !state.session.focused; state.session.focused = true; state.textFieldState.activeTarget = hitTarget.kind == UIEditorTextFieldHitTargetKind::None ? UIEditorTextFieldHitTargetKind::Row : hitTarget.kind; eventResult.consumed = true; } else { if (state.session.editing) { CommitEdit(state, spec, eventResult); eventResult.focusChanged = state.session.focused; state.session.focused = false; } else if (state.session.focused) { eventResult.focusChanged = true; state.session.focused = false; } state.textFieldState.activeTarget = UIEditorTextFieldHitTargetKind::None; } break; } case UIInputEventType::PointerButtonUp: { const UIEditorTextFieldHitTarget hitTarget = state.session.hasPointerPosition ? HitTestUIEditorTextField( layout, state.session.pointerPosition) : UIEditorTextFieldHitTarget {}; eventResult.hitTarget = hitTarget; if (event.pointerButton == UIPointerButton::Left) { const UIEditorTextFieldHitTargetKind activeTarget = state.textFieldState.activeTarget; state.textFieldState.activeTarget = UIEditorTextFieldHitTargetKind::None; if (activeTarget == UIEditorTextFieldHitTargetKind::ValueBox && hitTarget.kind == UIEditorTextFieldHitTargetKind::ValueBox) { if (state.session.editing) { eventResult.consumed = true; } else if (IsUIEditorEditableFieldDoubleClick( state.session, spec.fieldId, event, UIEditorEditableFieldInvalidComponentIndex)) { eventResult.editStarted = BeginEdit(state, spec, false); eventResult.consumed = true; } else { RecordUIEditorEditableFieldClick( state.session, spec.fieldId, event, UIEditorEditableFieldInvalidComponentIndex); eventResult.consumed = true; } } else if (hitTarget.kind == UIEditorTextFieldHitTargetKind::Row) { eventResult.consumed = true; } } break; } case UIInputEventType::KeyDown: if (!state.session.focused) { break; } if (state.session.editing) { if (event.keyCode == static_cast(KeyCode::Escape)) { CancelEdit(state, spec, eventResult); break; } const auto textResult = HandleUIEditorEditableFieldKeyDown( state.session, event.keyCode, event.modifiers); if (textResult.handled) { eventResult.consumed = true; eventResult.valueBefore = spec.value; eventResult.valueAfter = spec.value; if (textResult.submitRequested) { CommitEdit(state, spec, eventResult); } } } else if (event.keyCode == static_cast(KeyCode::Enter)) { eventResult.editStarted = BeginEdit(state, spec, false); eventResult.consumed = eventResult.editStarted; } break; case UIInputEventType::Character: if (!state.session.focused || spec.readOnly || event.modifiers.control || event.modifiers.alt || event.modifiers.super) { break; } if (!state.session.editing) { eventResult.editStarted = BeginEdit(state, spec, true); } if (InsertUIEditorEditableFieldCharacter( state.session, event.character)) { eventResult.consumed = true; } break; default: break; } layout = BuildUIEditorTextFieldLayout(bounds, spec, metrics); SyncDisplayState(state, spec, layout); if (eventResult.hitTarget.kind == UIEditorTextFieldHitTargetKind::None && state.session.hasPointerPosition) { eventResult.hitTarget = HitTestUIEditorTextField( layout, state.session.pointerPosition); } if (eventResult.consumed || eventResult.focusChanged || eventResult.valueChanged || eventResult.editStarted || eventResult.editCommitted || eventResult.editCanceled || eventResult.hitTarget.kind != UIEditorTextFieldHitTargetKind::None) { interactionResult = std::move(eventResult); } } layout = BuildUIEditorTextFieldLayout(bounds, spec, metrics); SyncDisplayState(state, spec, layout); if (interactionResult.hitTarget.kind == UIEditorTextFieldHitTargetKind::None && state.session.hasPointerPosition) { interactionResult.hitTarget = HitTestUIEditorTextField( layout, state.session.pointerPosition); } return { std::move(layout), std::move(interactionResult) }; } } // namespace XCEngine::UI::Editor