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

627 lines
24 KiB
C++
Raw Normal View History

2026-04-10 00:41:28 +08:00
#include <XCEditor/Fields/UIEditorVector3FieldInteraction.h>
2026-04-08 02:52:28 +08:00
#include <XCEngine/Input/InputTypes.h>
2026-04-08 02:52:28 +08:00
#include <algorithm>
2026-04-08 02:52:28 +08:00
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
using ::XCEngine::Input::KeyCode;
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIPointerButton;
2026-04-08 02:52:28 +08:00
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;
2026-04-08 02:52:28 +08:00
bool IsPermittedCharacter(
const UIEditorVector3FieldSpec& 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>('.');
}
2026-04-08 02:52:28 +08:00
std::size_t ResolveSelectedComponentIndex(
const UIEditorVector3FieldInteractionState& state) {
return state.vector3FieldState.selectedComponentIndex ==
UIEditorVector3FieldInvalidComponentIndex
? 0u
: state.vector3FieldState.selectedComponentIndex;
}
2026-04-08 02:52:28 +08:00
double ResolveDragSensitivity(const UIEditorVector3FieldSpec& spec) {
if (spec.step != 0.0) {
return spec.step;
2026-04-08 02:52:28 +08:00
}
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;
2026-04-08 02:52:28 +08:00
}
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;
2026-04-08 02:52:28 +08:00
}
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;
2026-04-08 02:52:28 +08:00
}
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;
2026-04-08 02:52:28 +08:00
}
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;
2026-04-08 02:52:28 +08:00
}
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;
}
2026-04-08 02:52:28 +08:00
} // namespace
UIEditorVector3FieldInteractionFrame UpdateUIEditorVector3FieldInteraction(
UIEditorVector3FieldInteractionState& state,
UIEditorVector3FieldSpec& spec,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIInputEvent>& 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<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 = 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<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 =
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;
}
2026-04-08 02:52:28 +08:00
return {
std::move(layout),
std::move(interactionResult)
2026-04-08 02:52:28 +08:00
};
}
} // namespace XCEngine::UI::Editor