Files
XCEngine/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp

564 lines
22 KiB
C++
Raw Normal View History

2026-04-10 00:41:28 +08:00
#include <XCEditor/Fields/UIEditorVector2FieldInteraction.h>
2026-04-08 02:52:28 +08:00
#include <XCEngine/Input/InputTypes.h>
#include <utility>
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::BuildUIEditorVector2FieldLayout;
using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector2FieldComponentValue;
using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector2Field;
using ::XCEngine::UI::Editor::Widgets::IsUIEditorVector2FieldPointInside;
using ::XCEngine::UI::Editor::Widgets::NormalizeUIEditorVector2FieldComponentValue;
using ::XCEngine::UI::Editor::Widgets::TryParseUIEditorVector2FieldComponentValue;
using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTarget;
using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTargetKind;
using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldInvalidComponentIndex;
using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldLayout;
using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldMetrics;
using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldSpec;
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;
}
}
std::size_t ResolveFallbackSelectedComponentIndex(
const UIEditorVector2FieldInteractionState& state) {
return state.vector2FieldState.selectedComponentIndex ==
UIEditorVector2FieldInvalidComponentIndex
? 0u
: state.vector2FieldState.selectedComponentIndex;
}
std::string BuildComponentEditFieldId(
const UIEditorVector2FieldSpec& spec,
std::size_t componentIndex) {
return spec.fieldId + "." + std::to_string(componentIndex);
}
bool IsPermittedCharacter(
const UIEditorVector2FieldSpec& spec,
std::uint32_t character) {
if (character >= static_cast<std::uint32_t>('0') &&
character <= static_cast<std::uint32_t>('9')) {
return true;
}
if (character == static_cast<std::uint32_t>('-') ||
character == static_cast<std::uint32_t>('+')) {
return true;
}
return !spec.integerMode && character == static_cast<std::uint32_t>('.');
}
void SyncDisplayTexts(
UIEditorVector2FieldInteractionState& state,
const UIEditorVector2FieldSpec& spec) {
for (std::size_t componentIndex = 0u;
componentIndex < state.vector2FieldState.displayTexts.size();
++componentIndex) {
if (state.vector2FieldState.editing &&
state.vector2FieldState.selectedComponentIndex == componentIndex) {
continue;
}
state.vector2FieldState.displayTexts[componentIndex] =
FormatUIEditorVector2FieldComponentValue(spec, componentIndex);
}
}
void SyncHoverTarget(
UIEditorVector2FieldInteractionState& state,
const UIEditorVector2FieldLayout& layout) {
if (!state.hasPointerPosition) {
state.vector2FieldState.hoveredTarget = UIEditorVector2FieldHitTargetKind::None;
state.vector2FieldState.hoveredComponentIndex = UIEditorVector2FieldInvalidComponentIndex;
return;
}
const UIEditorVector2FieldHitTarget hitTarget =
HitTestUIEditorVector2Field(layout, state.pointerPosition);
state.vector2FieldState.hoveredTarget = hitTarget.kind;
state.vector2FieldState.hoveredComponentIndex = hitTarget.componentIndex;
}
bool MoveSelection(
UIEditorVector2FieldInteractionState& state,
int direction,
UIEditorVector2FieldInteractionResult& result) {
const std::size_t before = ResolveFallbackSelectedComponentIndex(state);
const std::size_t after =
direction < 0
? (before == 0u ? 0u : before - 1u)
: (before >= 1u ? 1u : before + 1u);
state.vector2FieldState.selectedComponentIndex = after;
result.selectionChanged = before != after;
result.selectedComponentIndex = after;
result.consumed = true;
return true;
}
bool SelectComponent(
UIEditorVector2FieldInteractionState& state,
std::size_t componentIndex,
UIEditorVector2FieldInteractionResult& result) {
if (componentIndex >= 2u) {
return false;
}
const std::size_t before = ResolveFallbackSelectedComponentIndex(state);
state.vector2FieldState.selectedComponentIndex = componentIndex;
result.selectionChanged = before != componentIndex;
result.selectedComponentIndex = componentIndex;
return true;
}
bool BeginEdit(
UIEditorVector2FieldInteractionState& state,
const UIEditorVector2FieldSpec& spec,
std::size_t componentIndex,
bool clearText) {
if (spec.readOnly || componentIndex >= spec.values.size()) {
return false;
}
const std::string baseline =
FormatUIEditorVector2FieldComponentValue(spec, componentIndex);
const std::string editFieldId =
BuildComponentEditFieldId(spec, componentIndex);
const bool changed = state.editModel.BeginEdit(editFieldId, baseline);
if (!changed &&
state.editModel.HasActiveEdit() &&
state.editModel.GetActiveFieldId() != editFieldId) {
return false;
}
if (!changed &&
state.vector2FieldState.editing &&
state.vector2FieldState.selectedComponentIndex == componentIndex) {
return false;
}
state.vector2FieldState.selectedComponentIndex = componentIndex;
state.vector2FieldState.editing = true;
state.textInputState.value = clearText ? std::string() : baseline;
state.textInputState.caret = state.textInputState.value.size();
state.editModel.UpdateStagedValue(state.textInputState.value);
state.vector2FieldState.displayTexts[componentIndex] = state.textInputState.value;
return true;
}
bool CommitEdit(
UIEditorVector2FieldInteractionState& state,
UIEditorVector2FieldSpec& spec,
UIEditorVector2FieldInteractionResult& result) {
if (!state.vector2FieldState.editing ||
!state.editModel.HasActiveEdit() ||
state.vector2FieldState.selectedComponentIndex >= spec.values.size()) {
return false;
}
const std::size_t componentIndex = state.vector2FieldState.selectedComponentIndex;
double parsedValue = spec.values[componentIndex];
if (!TryParseUIEditorVector2FieldComponentValue(
spec,
state.textInputState.value,
parsedValue)) {
result.consumed = true;
result.editCommitRejected = true;
return false;
}
result.valuesBefore = spec.values;
spec.values[componentIndex] = NormalizeUIEditorVector2FieldComponentValue(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 =
FormatUIEditorVector2FieldComponentValue(spec, componentIndex);
state.editModel.CommitEdit();
state.textInputState = {};
state.vector2FieldState.editing = false;
state.vector2FieldState.displayTexts[componentIndex] = result.committedText;
return true;
}
bool CancelEdit(
UIEditorVector2FieldInteractionState& state,
const UIEditorVector2FieldSpec& spec,
UIEditorVector2FieldInteractionResult& result) {
if (!state.vector2FieldState.editing ||
!state.editModel.HasActiveEdit() ||
state.vector2FieldState.selectedComponentIndex >= spec.values.size()) {
return false;
}
const std::size_t componentIndex = state.vector2FieldState.selectedComponentIndex;
state.editModel.CancelEdit();
state.textInputState = {};
state.vector2FieldState.editing = false;
state.vector2FieldState.displayTexts[componentIndex] =
FormatUIEditorVector2FieldComponentValue(spec, componentIndex);
result.consumed = true;
result.editCanceled = true;
result.valuesBefore = spec.values;
result.valuesAfter = spec.values;
result.selectedComponentIndex = componentIndex;
return true;
}
bool ApplyStep(
UIEditorVector2FieldInteractionState& state,
UIEditorVector2FieldSpec& spec,
double direction,
bool snapToEdge,
UIEditorVector2FieldInteractionResult& result) {
if (spec.readOnly) {
return false;
}
const std::size_t componentIndex = ResolveFallbackSelectedComponentIndex(state);
state.vector2FieldState.selectedComponentIndex = componentIndex;
if (state.vector2FieldState.editing &&
!CommitEdit(state, spec, result)) {
return result.editCommitRejected;
}
result.valuesBefore = spec.values;
if (snapToEdge) {
spec.values[componentIndex] =
direction < 0.0
? NormalizeUIEditorVector2FieldComponentValue(spec, spec.minValue)
: NormalizeUIEditorVector2FieldComponentValue(spec, spec.maxValue);
} else {
const double step = spec.step == 0.0 ? 1.0 : spec.step;
spec.values[componentIndex] = NormalizeUIEditorVector2FieldComponentValue(
spec,
spec.values[componentIndex] + step * direction);
result.stepDelta = step * direction;
}
result.valuesAfter = spec.values;
result.stepApplied = true;
result.valueChanged = result.valuesBefore != result.valuesAfter || result.valueChanged;
result.changedComponentIndex = componentIndex;
result.selectedComponentIndex = componentIndex;
result.consumed = true;
state.vector2FieldState.displayTexts[componentIndex] =
FormatUIEditorVector2FieldComponentValue(spec, componentIndex);
return true;
}
} // namespace
UIEditorVector2FieldInteractionFrame UpdateUIEditorVector2FieldInteraction(
UIEditorVector2FieldInteractionState& state,
UIEditorVector2FieldSpec& spec,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIInputEvent>& inputEvents,
const UIEditorVector2FieldMetrics& metrics) {
UIEditorVector2FieldLayout layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics);
SyncDisplayTexts(state, spec);
SyncHoverTarget(state, layout);
UIEditorVector2FieldInteractionResult interactionResult = {};
interactionResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex;
for (const UIInputEvent& event : inputEvents) {
if (ShouldUsePointerPosition(event)) {
state.pointerPosition = event.position;
state.hasPointerPosition = true;
} else if (event.type == UIInputEventType::PointerLeave) {
state.hasPointerPosition = false;
}
UIEditorVector2FieldInteractionResult eventResult = {};
eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex;
switch (event.type) {
case UIInputEventType::FocusGained:
eventResult.focusChanged = !state.vector2FieldState.focused;
state.vector2FieldState.focused = true;
if (state.vector2FieldState.selectedComponentIndex ==
UIEditorVector2FieldInvalidComponentIndex) {
state.vector2FieldState.selectedComponentIndex = 0u;
}
eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex;
break;
case UIInputEventType::FocusLost:
eventResult.focusChanged = state.vector2FieldState.focused;
state.vector2FieldState.focused = false;
state.vector2FieldState.activeTarget = UIEditorVector2FieldHitTargetKind::None;
state.vector2FieldState.activeComponentIndex = UIEditorVector2FieldInvalidComponentIndex;
state.hasPointerPosition = false;
if (state.vector2FieldState.editing) {
CommitEdit(state, spec, eventResult);
if (eventResult.editCommitRejected) {
CancelEdit(state, spec, eventResult);
}
}
break;
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerLeave:
break;
case UIInputEventType::PointerButtonDown: {
const UIEditorVector2FieldHitTarget hitTarget =
state.hasPointerPosition
? HitTestUIEditorVector2Field(layout, state.pointerPosition)
: UIEditorVector2FieldHitTarget {};
eventResult.hitTarget = hitTarget;
if (event.pointerButton != UIPointerButton::Left) {
break;
}
const bool insideField =
state.hasPointerPosition &&
IsUIEditorVector2FieldPointInside(layout.bounds, state.pointerPosition);
if (insideField) {
eventResult.focusChanged = !state.vector2FieldState.focused;
state.vector2FieldState.focused = true;
state.vector2FieldState.activeTarget = hitTarget.kind == UIEditorVector2FieldHitTargetKind::None
? UIEditorVector2FieldHitTargetKind::Row
: hitTarget.kind;
state.vector2FieldState.activeComponentIndex = hitTarget.componentIndex;
if (hitTarget.kind == UIEditorVector2FieldHitTargetKind::Component) {
SelectComponent(state, hitTarget.componentIndex, eventResult);
} else if (state.vector2FieldState.selectedComponentIndex ==
UIEditorVector2FieldInvalidComponentIndex) {
state.vector2FieldState.selectedComponentIndex = 0u;
eventResult.selectedComponentIndex = 0u;
}
eventResult.consumed = true;
} else {
if (state.vector2FieldState.editing) {
CommitEdit(state, spec, eventResult);
if (!eventResult.editCommitRejected) {
eventResult.focusChanged = state.vector2FieldState.focused;
state.vector2FieldState.focused = false;
}
} else if (state.vector2FieldState.focused) {
eventResult.focusChanged = true;
state.vector2FieldState.focused = false;
}
state.vector2FieldState.activeTarget = UIEditorVector2FieldHitTargetKind::None;
state.vector2FieldState.activeComponentIndex = UIEditorVector2FieldInvalidComponentIndex;
}
break;
}
case UIInputEventType::PointerButtonUp: {
const UIEditorVector2FieldHitTarget hitTarget =
state.hasPointerPosition
? HitTestUIEditorVector2Field(layout, state.pointerPosition)
: UIEditorVector2FieldHitTarget {};
eventResult.hitTarget = hitTarget;
if (event.pointerButton == UIPointerButton::Left) {
const UIEditorVector2FieldHitTargetKind activeTarget = state.vector2FieldState.activeTarget;
const std::size_t activeComponentIndex = state.vector2FieldState.activeComponentIndex;
state.vector2FieldState.activeTarget = UIEditorVector2FieldHitTargetKind::None;
state.vector2FieldState.activeComponentIndex = UIEditorVector2FieldInvalidComponentIndex;
if (activeTarget == UIEditorVector2FieldHitTargetKind::Component &&
hitTarget.kind == UIEditorVector2FieldHitTargetKind::Component &&
activeComponentIndex == hitTarget.componentIndex) {
SelectComponent(state, hitTarget.componentIndex, eventResult);
if (!state.vector2FieldState.editing) {
eventResult.editStarted =
BeginEdit(state, spec, hitTarget.componentIndex, false);
}
eventResult.consumed = true;
} else if (hitTarget.kind == UIEditorVector2FieldHitTargetKind::Row) {
eventResult.consumed = true;
}
}
break;
}
case UIInputEventType::KeyDown:
if (!state.vector2FieldState.focused) {
break;
}
if (state.vector2FieldState.editing) {
if (event.keyCode == static_cast<std::int32_t>(KeyCode::Escape)) {
CancelEdit(state, spec, eventResult);
break;
}
if (event.keyCode == static_cast<std::int32_t>(KeyCode::Tab)) {
if (CommitEdit(state, spec, eventResult)) {
MoveSelection(
state,
event.modifiers.shift ? -1 : 1,
eventResult);
}
eventResult.consumed = true;
break;
}
const auto textResult =
HandleKeyDown(state.textInputState, event.keyCode, event.modifiers);
if (textResult.handled) {
state.editModel.UpdateStagedValue(state.textInputState.value);
state.vector2FieldState.displayTexts[state.vector2FieldState.selectedComponentIndex] =
state.textInputState.value;
eventResult.consumed = true;
eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex;
if (textResult.submitRequested) {
CommitEdit(state, spec, eventResult);
}
break;
}
} else {
switch (static_cast<KeyCode>(event.keyCode)) {
case KeyCode::Left:
MoveSelection(state, -1, eventResult);
break;
case KeyCode::Right:
case KeyCode::Tab:
MoveSelection(
state,
static_cast<KeyCode>(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 = ResolveFallbackSelectedComponentIndex(state);
eventResult.editStarted = BeginEdit(
state,
spec,
eventResult.selectedComponentIndex,
false);
eventResult.consumed = eventResult.editStarted;
break;
default:
break;
}
}
break;
case UIInputEventType::Character:
if (!state.vector2FieldState.focused ||
spec.readOnly ||
event.modifiers.control ||
event.modifiers.alt ||
event.modifiers.super ||
!IsPermittedCharacter(spec, event.character)) {
break;
}
if (state.vector2FieldState.selectedComponentIndex ==
UIEditorVector2FieldInvalidComponentIndex) {
state.vector2FieldState.selectedComponentIndex = 0u;
}
eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex;
if (!state.vector2FieldState.editing) {
eventResult.editStarted = BeginEdit(
state,
spec,
state.vector2FieldState.selectedComponentIndex,
true);
}
if (InsertCharacter(state.textInputState, event.character)) {
state.editModel.UpdateStagedValue(state.textInputState.value);
state.vector2FieldState.displayTexts[state.vector2FieldState.selectedComponentIndex] =
state.textInputState.value;
eventResult.consumed = true;
}
break;
default:
break;
}
layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics);
SyncDisplayTexts(state, spec);
SyncHoverTarget(state, layout);
if (eventResult.hitTarget.kind == UIEditorVector2FieldHitTargetKind::None &&
state.hasPointerPosition) {
eventResult.hitTarget = HitTestUIEditorVector2Field(layout, state.pointerPosition);
}
if (eventResult.selectedComponentIndex == UIEditorVector2FieldInvalidComponentIndex) {
eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex;
}
if (eventResult.consumed ||
eventResult.focusChanged ||
eventResult.valueChanged ||
eventResult.stepApplied ||
eventResult.selectionChanged ||
eventResult.editStarted ||
eventResult.editCommitted ||
eventResult.editCommitRejected ||
eventResult.editCanceled ||
eventResult.hitTarget.kind != UIEditorVector2FieldHitTargetKind::None) {
interactionResult = std::move(eventResult);
}
}
layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics);
SyncDisplayTexts(state, spec);
SyncHoverTarget(state, layout);
if (interactionResult.hitTarget.kind == UIEditorVector2FieldHitTargetKind::None &&
state.hasPointerPosition) {
interactionResult.hitTarget = HitTestUIEditorVector2Field(layout, state.pointerPosition);
}
if (interactionResult.selectedComponentIndex == UIEditorVector2FieldInvalidComponentIndex) {
interactionResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex;
}
return {
std::move(layout),
std::move(interactionResult)
};
}
} // namespace XCEngine::UI::Editor