#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::Text::HandleKeyDown; using ::XCEngine::UI::Text::InsertCharacter; 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; 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 SyncDisplayText( UIEditorTextFieldInteractionState& state, const UIEditorTextFieldSpec& spec) { if (!state.textFieldState.editing) { state.textFieldState.displayText = spec.value; } } void SyncHoverTarget( UIEditorTextFieldInteractionState& state, const UIEditorTextFieldLayout& layout) { if (!state.hasPointerPosition) { state.textFieldState.hoveredTarget = UIEditorTextFieldHitTargetKind::None; return; } state.textFieldState.hoveredTarget = HitTestUIEditorTextField(layout, state.pointerPosition).kind; } bool BeginEdit( UIEditorTextFieldInteractionState& state, const UIEditorTextFieldSpec& spec, bool clearText) { if (spec.readOnly) { return false; } const bool changed = state.editModel.BeginEdit(spec.fieldId, spec.value); if (!changed && state.editModel.HasActiveEdit() && state.editModel.GetActiveFieldId() != spec.fieldId) { return false; } if (!changed && state.textFieldState.editing) { return false; } state.textFieldState.editing = true; state.textInputState.value = clearText ? std::string() : spec.value; state.textInputState.caret = state.textInputState.value.size(); state.editModel.UpdateStagedValue(state.textInputState.value); state.textFieldState.displayText = state.textInputState.value; return true; } bool CommitEdit( UIEditorTextFieldInteractionState& state, UIEditorTextFieldSpec& spec, UIEditorTextFieldInteractionResult& result) { if (!state.textFieldState.editing || !state.editModel.HasActiveEdit()) { return false; } result.valueBefore = spec.value; spec.value = state.textInputState.value; result.valueAfter = spec.value; result.valueChanged = result.valueBefore != result.valueAfter; result.editCommitted = true; result.consumed = true; result.committedText = spec.value; state.editModel.CommitEdit(); state.textInputState = {}; state.textFieldState.editing = false; state.textFieldState.displayText = spec.value; return true; } bool CancelEdit( UIEditorTextFieldInteractionState& state, const UIEditorTextFieldSpec& spec, UIEditorTextFieldInteractionResult& result) { if (!state.textFieldState.editing || !state.editModel.HasActiveEdit()) { return false; } state.editModel.CancelEdit(); state.textInputState = {}; state.textFieldState.editing = false; state.textFieldState.displayText = spec.value; 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); SyncDisplayText(state, spec); SyncHoverTarget(state, layout); UIEditorTextFieldInteractionResult 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; } UIEditorTextFieldInteractionResult eventResult = {}; switch (event.type) { case UIInputEventType::FocusGained: eventResult.focusChanged = !state.textFieldState.focused; state.textFieldState.focused = true; break; case UIInputEventType::FocusLost: eventResult.focusChanged = state.textFieldState.focused; state.textFieldState.focused = false; state.textFieldState.activeTarget = UIEditorTextFieldHitTargetKind::None; state.hasPointerPosition = false; if (state.textFieldState.editing) { CommitEdit(state, spec, eventResult); } break; case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerLeave: break; case UIInputEventType::PointerButtonDown: { const UIEditorTextFieldHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorTextField(layout, state.pointerPosition) : UIEditorTextFieldHitTarget {}; eventResult.hitTarget = hitTarget; if (event.pointerButton != UIPointerButton::Left) { break; } const bool insideField = state.hasPointerPosition && IsUIEditorTextFieldPointInside(layout.bounds, state.pointerPosition); if (insideField) { eventResult.focusChanged = !state.textFieldState.focused; state.textFieldState.focused = true; state.textFieldState.activeTarget = hitTarget.kind == UIEditorTextFieldHitTargetKind::None ? UIEditorTextFieldHitTargetKind::Row : hitTarget.kind; eventResult.consumed = true; } else { if (state.textFieldState.editing) { CommitEdit(state, spec, eventResult); eventResult.focusChanged = state.textFieldState.focused; state.textFieldState.focused = false; } else if (state.textFieldState.focused) { eventResult.focusChanged = true; state.textFieldState.focused = false; } state.textFieldState.activeTarget = UIEditorTextFieldHitTargetKind::None; } break; } case UIInputEventType::PointerButtonUp: { const UIEditorTextFieldHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorTextField(layout, state.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.textFieldState.editing) { eventResult.editStarted = BeginEdit(state, spec, false); } eventResult.consumed = true; } else if (hitTarget.kind == UIEditorTextFieldHitTargetKind::Row) { eventResult.consumed = true; } } break; } case UIInputEventType::KeyDown: if (!state.textFieldState.focused) { break; } if (state.textFieldState.editing) { if (event.keyCode == static_cast(KeyCode::Escape)) { CancelEdit(state, spec, eventResult); break; } const auto textResult = HandleKeyDown(state.textInputState, event.keyCode, event.modifiers); if (textResult.handled) { state.editModel.UpdateStagedValue(state.textInputState.value); state.textFieldState.displayText = state.textInputState.value; 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.textFieldState.focused || spec.readOnly || event.modifiers.control || event.modifiers.alt || event.modifiers.super) { break; } if (!state.textFieldState.editing) { eventResult.editStarted = BeginEdit(state, spec, true); } if (InsertCharacter(state.textInputState, event.character)) { state.editModel.UpdateStagedValue(state.textInputState.value); state.textFieldState.displayText = state.textInputState.value; eventResult.consumed = true; } break; default: break; } layout = BuildUIEditorTextFieldLayout(bounds, spec, metrics); SyncDisplayText(state, spec); SyncHoverTarget(state, layout); if (eventResult.hitTarget.kind == UIEditorTextFieldHitTargetKind::None && state.hasPointerPosition) { eventResult.hitTarget = HitTestUIEditorTextField(layout, state.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); SyncDisplayText(state, spec); SyncHoverTarget(state, layout); if (interactionResult.hitTarget.kind == UIEditorTextFieldHitTargetKind::None && state.hasPointerPosition) { interactionResult.hitTarget = HitTestUIEditorTextField(layout, state.pointerPosition); } return { std::move(layout), std::move(interactionResult) }; } } // namespace XCEngine::UI::Editor