2026-04-10 00:41:28 +08:00
|
|
|
#include <XCEditor/Fields/UIEditorVector3FieldInteraction.h>
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
#include <XCEngine/Input/InputTypes.h>
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
#include <algorithm>
|
2026-04-08 02:52:28 +08:00
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
constexpr std::size_t kComponentCount = 3u;
|
|
|
|
|
constexpr std::size_t kLastComponentIndex = 2u;
|
2026-04-08 02:52:28 +08:00
|
|
|
|
2026-04-21 00:57:14 +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
|
|
|
|
2026-04-21 00:57:14 +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
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
double ResolveDragSensitivity(const UIEditorVector3FieldSpec& spec) {
|
|
|
|
|
if (spec.step != 0.0) {
|
|
|
|
|
return spec.step;
|
2026-04-08 02:52:28 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 00:57:14 +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
|
|
|
}
|
|
|
|
|
|
2026-04-21 00:57:14 +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
|
|
|
}
|
|
|
|
|
|
2026-04-21 00:57:14 +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
|
|
|
}
|
|
|
|
|
|
2026-04-21 00:57:14 +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
|
|
|
}
|
|
|
|
|
|
2026-04-21 00:57:14 +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
|
|
|
}
|
2026-04-21 00:57:14 +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) {
|
2026-04-21 00:57:14 +08:00
|
|
|
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),
|
2026-04-21 00:57:14 +08:00
|
|
|
std::move(interactionResult)
|
2026-04-08 02:52:28 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor
|