#include #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::BuildUIEditorVector3FieldLayout; using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector3FieldComponentValue; using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector3Field; using ::XCEngine::UI::Editor::Widgets::IsUIEditorVector3FieldPointInside; using ::XCEngine::UI::Editor::Widgets::NormalizeUIEditorVector3FieldComponentValue; using ::XCEngine::UI::Editor::Widgets::TryParseUIEditorVector3FieldComponentValue; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTarget; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTargetKind; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldInvalidComponentIndex; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldLayout; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldMetrics; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldSpec; constexpr std::size_t kComponentCount = 3u; constexpr std::size_t kLastComponentIndex = 2u; bool IsPermittedCharacter( const UIEditorVector3FieldSpec& spec, std::uint32_t character) { if (character >= static_cast('0') && character <= static_cast('9')) { return true; } if (character == static_cast('-') || character == static_cast('+')) { return true; } return !spec.integerMode && character == static_cast('.'); } std::size_t ResolveSelectedComponentIndex( const UIEditorVector3FieldInteractionState& state) { return state.vector3FieldState.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex ? 0u : state.vector3FieldState.selectedComponentIndex; } double ResolveDragSensitivity(const UIEditorVector3FieldSpec& spec) { if (spec.step != 0.0) { return spec.step; } return spec.integerMode ? 1.0 : 0.1; } void SyncDisplayState( UIEditorVector3FieldInteractionState& state, const UIEditorVector3FieldSpec& spec, const UIEditorVector3FieldLayout& layout) { auto& fieldState = state.vector3FieldState; const auto& session = state.session; fieldState.focused = session.focused; fieldState.editing = session.editing; fieldState.caretBlinkStartNanoseconds = session.caretBlinkStartNanoseconds; for (std::size_t componentIndex = 0u; componentIndex < kComponentCount; ++componentIndex) { fieldState.displayTexts[componentIndex] = session.editing && fieldState.selectedComponentIndex == componentIndex ? session.textInputState.value : FormatUIEditorVector3FieldComponentValue(spec, componentIndex); } if (session.editing && fieldState.selectedComponentIndex != UIEditorVector3FieldInvalidComponentIndex) { fieldState.caretOffset = session.textInputState.caret; } else if (fieldState.selectedComponentIndex != UIEditorVector3FieldInvalidComponentIndex) { fieldState.caretOffset = fieldState.displayTexts[fieldState.selectedComponentIndex].size(); } else { fieldState.caretOffset = 0u; } if (!session.hasPointerPosition) { fieldState.hoveredTarget = UIEditorVector3FieldHitTargetKind::None; fieldState.hoveredComponentIndex = UIEditorVector3FieldInvalidComponentIndex; return; } const UIEditorVector3FieldHitTarget hitTarget = HitTestUIEditorVector3Field(layout, session.pointerPosition); fieldState.hoveredTarget = hitTarget.kind; fieldState.hoveredComponentIndex = hitTarget.componentIndex; } bool SelectComponent( UIEditorVector3FieldInteractionState& state, std::size_t componentIndex, UIEditorVector3FieldInteractionResult& result) { if (componentIndex >= kComponentCount) { return false; } const std::size_t before = ResolveSelectedComponentIndex(state); state.vector3FieldState.selectedComponentIndex = componentIndex; state.session.activeComponentIndex = componentIndex; result.selectionChanged = before != componentIndex; result.selectedComponentIndex = componentIndex; return true; } bool MoveSelection( UIEditorVector3FieldInteractionState& state, int direction, UIEditorVector3FieldInteractionResult& result) { const std::size_t before = ResolveSelectedComponentIndex(state); const std::size_t after = direction < 0 ? (before == 0u ? 0u : before - 1u) : (before >= kLastComponentIndex ? kLastComponentIndex : before + 1u); state.vector3FieldState.selectedComponentIndex = after; state.session.activeComponentIndex = after; result.selectionChanged = before != after; result.selectedComponentIndex = after; result.consumed = true; return true; } bool BeginEdit( UIEditorVector3FieldInteractionState& state, const UIEditorVector3FieldSpec& spec, std::size_t componentIndex, bool clearText) { if (spec.readOnly || componentIndex >= spec.values.size()) { return false; } state.vector3FieldState.selectedComponentIndex = componentIndex; return BeginUIEditorEditableFieldEdit( state.session, spec.fieldId, componentIndex, FormatUIEditorVector3FieldComponentValue(spec, componentIndex), clearText); } bool CommitEdit( UIEditorVector3FieldInteractionState& state, UIEditorVector3FieldSpec& spec, UIEditorVector3FieldInteractionResult& result) { const std::size_t componentIndex = state.vector3FieldState.selectedComponentIndex; if (!state.session.editing || componentIndex >= spec.values.size()) { return false; } double parsedValue = spec.values[componentIndex]; if (!TryParseUIEditorVector3FieldComponentValue( spec, state.session.textInputState.value, parsedValue)) { result.consumed = true; result.editCommitRejected = true; return false; } result.valuesBefore = spec.values; spec.values[componentIndex] = NormalizeUIEditorVector3FieldComponentValue(spec, parsedValue); result.valuesAfter = spec.values; result.valueChanged = result.valuesBefore != result.valuesAfter; result.editCommitted = true; result.consumed = true; result.changedComponentIndex = componentIndex; result.selectedComponentIndex = componentIndex; result.committedText = FormatUIEditorVector3FieldComponentValue(spec, componentIndex); CommitUIEditorEditableFieldEdit(state.session); return true; } bool CancelEdit( UIEditorVector3FieldInteractionState& state, const UIEditorVector3FieldSpec& spec, UIEditorVector3FieldInteractionResult& result) { const std::size_t componentIndex = state.vector3FieldState.selectedComponentIndex; if (!state.session.editing || componentIndex >= spec.values.size()) { return false; } CancelUIEditorEditableFieldEdit(state.session); result.consumed = true; result.editCanceled = true; result.valuesBefore = spec.values; result.valuesAfter = spec.values; result.selectedComponentIndex = componentIndex; return true; } bool ApplyStep( UIEditorVector3FieldInteractionState& state, UIEditorVector3FieldSpec& spec, double direction, bool snapToEdge, UIEditorVector3FieldInteractionResult& result) { if (spec.readOnly) { return false; } const std::size_t componentIndex = ResolveSelectedComponentIndex(state); state.vector3FieldState.selectedComponentIndex = componentIndex; state.session.activeComponentIndex = componentIndex; if (state.session.editing && !CommitEdit(state, spec, result)) { return result.editCommitRejected; } result.valuesBefore = spec.values; if (snapToEdge) { spec.values[componentIndex] = direction < 0.0 ? NormalizeUIEditorVector3FieldComponentValue(spec, spec.minValue) : NormalizeUIEditorVector3FieldComponentValue(spec, spec.maxValue); } else { const double step = spec.step == 0.0 ? 1.0 : spec.step; spec.values[componentIndex] = NormalizeUIEditorVector3FieldComponentValue( spec, spec.values[componentIndex] + step * direction); result.stepDelta = step * direction; } result.valuesAfter = spec.values; result.stepApplied = true; result.valueChanged = result.valuesBefore != result.valuesAfter; result.changedComponentIndex = componentIndex; result.selectedComponentIndex = componentIndex; result.consumed = true; return true; } bool ApplyDrag( UIEditorVector3FieldInteractionState& state, UIEditorVector3FieldSpec& spec, std::size_t componentIndex, UIEditorVector3FieldInteractionResult& result) { if (spec.readOnly || state.session.editing || componentIndex >= spec.values.size()) { return false; } result.valuesBefore = spec.values; spec.values[componentIndex] = NormalizeUIEditorVector3FieldComponentValue( spec, state.session.dragStartValue + ResolveUIEditorEditableFieldDragDelta( state.session, ResolveDragSensitivity(spec))); result.valuesAfter = spec.values; result.valueChanged = result.valuesBefore != result.valuesAfter; result.changedComponentIndex = componentIndex; result.selectedComponentIndex = componentIndex; result.consumed = true; return true; } } // namespace UIEditorVector3FieldInteractionFrame UpdateUIEditorVector3FieldInteraction( UIEditorVector3FieldInteractionState& state, UIEditorVector3FieldSpec& spec, const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const UIEditorVector3FieldMetrics& metrics) { UIEditorVector3FieldLayout layout = BuildUIEditorVector3FieldLayout( bounds, spec, metrics); SyncDisplayState(state, spec, layout); UIEditorVector3FieldInteractionResult interactionResult = {}; interactionResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; for (const UIInputEvent& event : inputEvents) { UpdateUIEditorEditableFieldPointerPosition(state.session, event); UIEditorVector3FieldInteractionResult eventResult = {}; eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; switch (event.type) { case UIInputEventType::FocusGained: eventResult.focusChanged = !state.session.focused; state.session.focused = true; if (state.vector3FieldState.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex) { state.vector3FieldState.selectedComponentIndex = 0u; } state.session.activeComponentIndex = state.vector3FieldState.selectedComponentIndex; eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; break; case UIInputEventType::FocusLost: eventResult.focusChanged = state.session.focused; state.session.focused = false; state.vector3FieldState.activeTarget = UIEditorVector3FieldHitTargetKind::None; state.vector3FieldState.activeComponentIndex = UIEditorVector3FieldInvalidComponentIndex; state.session.hasPointerPosition = false; EndUIEditorEditableFieldDrag(state.session); if (state.session.editing) { CommitEdit(state, spec, eventResult); if (eventResult.editCommitRejected) { CancelEdit(state, spec, eventResult); } } break; case UIInputEventType::PointerMove: TryActivateUIEditorEditableFieldDrag(state.session, event); if (state.session.dragActive && state.vector3FieldState.activeTarget == UIEditorVector3FieldHitTargetKind::Component && state.vector3FieldState.activeComponentIndex != UIEditorVector3FieldInvalidComponentIndex) { ApplyDrag( state, spec, state.vector3FieldState.activeComponentIndex, eventResult); } break; case UIInputEventType::PointerEnter: case UIInputEventType::PointerLeave: break; case UIInputEventType::PointerButtonDown: { const UIEditorVector3FieldHitTarget hitTarget = state.session.hasPointerPosition ? HitTestUIEditorVector3Field( layout, state.session.pointerPosition) : UIEditorVector3FieldHitTarget {}; eventResult.hitTarget = hitTarget; if (event.pointerButton != UIPointerButton::Left) { break; } const bool insideField = state.session.hasPointerPosition && IsUIEditorVector3FieldPointInside( layout.bounds, state.session.pointerPosition); if (insideField) { eventResult.focusChanged = !state.session.focused; state.session.focused = true; state.vector3FieldState.activeTarget = hitTarget.kind == UIEditorVector3FieldHitTargetKind::None ? UIEditorVector3FieldHitTargetKind::Row : hitTarget.kind; state.vector3FieldState.activeComponentIndex = hitTarget.componentIndex; if (hitTarget.kind == UIEditorVector3FieldHitTargetKind::Component && !state.session.editing) { ArmUIEditorEditableFieldDrag( state.session, spec.fieldId, hitTarget.componentIndex, hitTarget.componentIndex < spec.values.size() ? spec.values[hitTarget.componentIndex] : 0.0); SelectComponent(state, hitTarget.componentIndex, eventResult); } else { EndUIEditorEditableFieldDrag(state.session); if (state.vector3FieldState.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex) { state.vector3FieldState.selectedComponentIndex = 0u; state.session.activeComponentIndex = 0u; eventResult.selectedComponentIndex = 0u; } } eventResult.consumed = true; } else { if (state.session.editing) { CommitEdit(state, spec, eventResult); if (!eventResult.editCommitRejected) { eventResult.focusChanged = state.session.focused; state.session.focused = false; } } else if (state.session.focused) { eventResult.focusChanged = true; state.session.focused = false; } state.vector3FieldState.activeTarget = UIEditorVector3FieldHitTargetKind::None; state.vector3FieldState.activeComponentIndex = UIEditorVector3FieldInvalidComponentIndex; EndUIEditorEditableFieldDrag(state.session); } break; } case UIInputEventType::PointerButtonUp: { const UIEditorVector3FieldHitTarget hitTarget = state.session.hasPointerPosition ? HitTestUIEditorVector3Field( layout, state.session.pointerPosition) : UIEditorVector3FieldHitTarget {}; eventResult.hitTarget = hitTarget; if (event.pointerButton == UIPointerButton::Left) { const UIEditorVector3FieldHitTargetKind activeTarget = state.vector3FieldState.activeTarget; const std::size_t activeComponentIndex = state.vector3FieldState.activeComponentIndex; state.vector3FieldState.activeTarget = UIEditorVector3FieldHitTargetKind::None; state.vector3FieldState.activeComponentIndex = UIEditorVector3FieldInvalidComponentIndex; if (activeTarget == UIEditorVector3FieldHitTargetKind::Component && hitTarget.kind == UIEditorVector3FieldHitTargetKind::Component && activeComponentIndex == hitTarget.componentIndex) { SelectComponent(state, hitTarget.componentIndex, eventResult); if (state.session.dragActive) { eventResult.consumed = true; } else if (!state.session.editing && IsUIEditorEditableFieldDoubleClick( state.session, spec.fieldId, event, hitTarget.componentIndex)) { eventResult.editStarted = BeginEdit(state, spec, hitTarget.componentIndex, false); eventResult.consumed = true; } else { RecordUIEditorEditableFieldClick( state.session, spec.fieldId, event, hitTarget.componentIndex); eventResult.consumed = true; } } else if (hitTarget.kind == UIEditorVector3FieldHitTargetKind::Row) { eventResult.consumed = true; } EndUIEditorEditableFieldDrag(state.session); } 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; } if (event.keyCode == static_cast(KeyCode::Tab)) { if (CommitEdit(state, spec, eventResult)) { MoveSelection( state, event.modifiers.shift ? -1 : 1, eventResult); } eventResult.consumed = true; break; } const auto textResult = HandleUIEditorEditableFieldKeyDown( state.session, event.keyCode, event.modifiers); if (textResult.handled) { eventResult.consumed = true; eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; if (textResult.submitRequested) { CommitEdit(state, spec, eventResult); } } } else { switch (static_cast(event.keyCode)) { case KeyCode::Left: MoveSelection(state, -1, eventResult); break; case KeyCode::Right: case KeyCode::Tab: MoveSelection( state, static_cast(event.keyCode) == KeyCode::Tab && event.modifiers.shift ? -1 : 1, eventResult); break; case KeyCode::Up: ApplyStep(state, spec, 1.0, false, eventResult); break; case KeyCode::Down: ApplyStep(state, spec, -1.0, false, eventResult); break; case KeyCode::Home: ApplyStep(state, spec, -1.0, true, eventResult); break; case KeyCode::End: ApplyStep(state, spec, 1.0, true, eventResult); break; case KeyCode::Enter: eventResult.selectedComponentIndex = ResolveSelectedComponentIndex(state); eventResult.editStarted = BeginEdit( state, spec, eventResult.selectedComponentIndex, false); eventResult.consumed = eventResult.editStarted; break; default: break; } } break; case UIInputEventType::Character: if (!state.session.focused || spec.readOnly || event.modifiers.control || event.modifiers.alt || event.modifiers.super || !IsPermittedCharacter(spec, event.character)) { break; } if (state.vector3FieldState.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex) { state.vector3FieldState.selectedComponentIndex = 0u; } state.session.activeComponentIndex = state.vector3FieldState.selectedComponentIndex; eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; if (!state.session.editing) { eventResult.editStarted = BeginEdit( state, spec, state.vector3FieldState.selectedComponentIndex, true); } if (InsertUIEditorEditableFieldCharacter( state.session, event.character)) { eventResult.consumed = true; } break; default: break; } layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); SyncDisplayState(state, spec, layout); if (eventResult.hitTarget.kind == UIEditorVector3FieldHitTargetKind::None && state.session.hasPointerPosition) { eventResult.hitTarget = HitTestUIEditorVector3Field( layout, state.session.pointerPosition); } if (eventResult.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex) { eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; } if (eventResult.consumed || eventResult.focusChanged || eventResult.valueChanged || eventResult.stepApplied || eventResult.selectionChanged || eventResult.editStarted || eventResult.editCommitted || eventResult.editCommitRejected || eventResult.editCanceled || eventResult.hitTarget.kind != UIEditorVector3FieldHitTargetKind::None) { interactionResult = std::move(eventResult); } } layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); SyncDisplayState(state, spec, layout); if (interactionResult.hitTarget.kind == UIEditorVector3FieldHitTargetKind::None && state.session.hasPointerPosition) { interactionResult.hitTarget = HitTestUIEditorVector3Field( layout, state.session.pointerPosition); } if (interactionResult.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex) { interactionResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; } return { std::move(layout), std::move(interactionResult) }; } } // namespace XCEngine::UI::Editor