关键节点
This commit is contained in:
314
editor/src/Fields/UIEditorTextFieldInteraction.cpp
Normal file
314
editor/src/Fields/UIEditorTextFieldInteraction.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
#include <XCEditor/Fields/UIEditorTextFieldInteraction.h>
|
||||
|
||||
#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::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;
|
||||
|
||||
void SyncDisplayState(
|
||||
UIEditorTextFieldInteractionState& state,
|
||||
const UIEditorTextFieldSpec& spec,
|
||||
const UIEditorTextFieldLayout& layout) {
|
||||
auto& fieldState = state.textFieldState;
|
||||
const auto& session = state.session;
|
||||
fieldState.focused = session.focused;
|
||||
fieldState.editing = session.editing;
|
||||
fieldState.caretBlinkStartNanoseconds = session.caretBlinkStartNanoseconds;
|
||||
fieldState.displayText = session.editing
|
||||
? session.textInputState.value
|
||||
: spec.value;
|
||||
fieldState.caretOffset = session.editing
|
||||
? session.textInputState.caret
|
||||
: spec.value.size();
|
||||
if (!session.hasPointerPosition) {
|
||||
fieldState.hoveredTarget = UIEditorTextFieldHitTargetKind::None;
|
||||
return;
|
||||
}
|
||||
|
||||
fieldState.hoveredTarget =
|
||||
HitTestUIEditorTextField(layout, session.pointerPosition).kind;
|
||||
}
|
||||
|
||||
bool BeginEdit(
|
||||
UIEditorTextFieldInteractionState& state,
|
||||
const UIEditorTextFieldSpec& spec,
|
||||
bool clearText) {
|
||||
if (spec.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return BeginUIEditorEditableFieldEdit(
|
||||
state.session,
|
||||
spec.fieldId,
|
||||
UIEditorEditableFieldInvalidComponentIndex,
|
||||
spec.value,
|
||||
clearText);
|
||||
}
|
||||
|
||||
bool CommitEdit(
|
||||
UIEditorTextFieldInteractionState& state,
|
||||
UIEditorTextFieldSpec& spec,
|
||||
UIEditorTextFieldInteractionResult& result) {
|
||||
if (!state.session.editing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result.valueBefore = spec.value;
|
||||
spec.value = state.session.textInputState.value;
|
||||
result.valueAfter = spec.value;
|
||||
result.valueChanged = result.valueBefore != result.valueAfter;
|
||||
result.editCommitted = true;
|
||||
result.consumed = true;
|
||||
result.committedText = spec.value;
|
||||
CommitUIEditorEditableFieldEdit(state.session);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CancelEdit(
|
||||
UIEditorTextFieldInteractionState& state,
|
||||
const UIEditorTextFieldSpec& spec,
|
||||
UIEditorTextFieldInteractionResult& result) {
|
||||
if (!state.session.editing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CancelUIEditorEditableFieldEdit(state.session);
|
||||
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<UIInputEvent>& inputEvents,
|
||||
const UIEditorTextFieldMetrics& metrics) {
|
||||
UIEditorTextFieldLayout layout = BuildUIEditorTextFieldLayout(
|
||||
bounds,
|
||||
spec,
|
||||
metrics);
|
||||
SyncDisplayState(state, spec, layout);
|
||||
|
||||
UIEditorTextFieldInteractionResult interactionResult = {};
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
UpdateUIEditorEditableFieldPointerPosition(state.session, event);
|
||||
|
||||
UIEditorTextFieldInteractionResult eventResult = {};
|
||||
switch (event.type) {
|
||||
case UIInputEventType::FocusGained:
|
||||
eventResult.focusChanged = !state.session.focused;
|
||||
state.session.focused = true;
|
||||
break;
|
||||
|
||||
case UIInputEventType::FocusLost:
|
||||
eventResult.focusChanged = state.session.focused;
|
||||
state.session.focused = false;
|
||||
state.textFieldState.activeTarget = UIEditorTextFieldHitTargetKind::None;
|
||||
state.session.hasPointerPosition = false;
|
||||
EndUIEditorEditableFieldDrag(state.session);
|
||||
if (state.session.editing) {
|
||||
CommitEdit(state, spec, eventResult);
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerLeave:
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerButtonDown: {
|
||||
const UIEditorTextFieldHitTarget hitTarget =
|
||||
state.session.hasPointerPosition
|
||||
? HitTestUIEditorTextField(
|
||||
layout,
|
||||
state.session.pointerPosition)
|
||||
: UIEditorTextFieldHitTarget {};
|
||||
eventResult.hitTarget = hitTarget;
|
||||
|
||||
if (event.pointerButton != UIPointerButton::Left) {
|
||||
break;
|
||||
}
|
||||
|
||||
const bool insideField =
|
||||
state.session.hasPointerPosition &&
|
||||
IsUIEditorTextFieldPointInside(
|
||||
layout.bounds,
|
||||
state.session.pointerPosition);
|
||||
if (insideField) {
|
||||
eventResult.focusChanged = !state.session.focused;
|
||||
state.session.focused = true;
|
||||
state.textFieldState.activeTarget =
|
||||
hitTarget.kind == UIEditorTextFieldHitTargetKind::None
|
||||
? UIEditorTextFieldHitTargetKind::Row
|
||||
: hitTarget.kind;
|
||||
eventResult.consumed = true;
|
||||
} else {
|
||||
if (state.session.editing) {
|
||||
CommitEdit(state, spec, eventResult);
|
||||
eventResult.focusChanged = state.session.focused;
|
||||
state.session.focused = false;
|
||||
} else if (state.session.focused) {
|
||||
eventResult.focusChanged = true;
|
||||
state.session.focused = false;
|
||||
}
|
||||
state.textFieldState.activeTarget = UIEditorTextFieldHitTargetKind::None;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UIInputEventType::PointerButtonUp: {
|
||||
const UIEditorTextFieldHitTarget hitTarget =
|
||||
state.session.hasPointerPosition
|
||||
? HitTestUIEditorTextField(
|
||||
layout,
|
||||
state.session.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.session.editing) {
|
||||
eventResult.consumed = true;
|
||||
} else if (IsUIEditorEditableFieldDoubleClick(
|
||||
state.session,
|
||||
spec.fieldId,
|
||||
event,
|
||||
UIEditorEditableFieldInvalidComponentIndex)) {
|
||||
eventResult.editStarted = BeginEdit(state, spec, false);
|
||||
eventResult.consumed = true;
|
||||
} else {
|
||||
RecordUIEditorEditableFieldClick(
|
||||
state.session,
|
||||
spec.fieldId,
|
||||
event,
|
||||
UIEditorEditableFieldInvalidComponentIndex);
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
} else if (hitTarget.kind == UIEditorTextFieldHitTargetKind::Row) {
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
const auto textResult = HandleUIEditorEditableFieldKeyDown(
|
||||
state.session,
|
||||
event.keyCode,
|
||||
event.modifiers);
|
||||
if (textResult.handled) {
|
||||
eventResult.consumed = true;
|
||||
eventResult.valueBefore = spec.value;
|
||||
eventResult.valueAfter = spec.value;
|
||||
if (textResult.submitRequested) {
|
||||
CommitEdit(state, spec, eventResult);
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode == static_cast<std::int32_t>(KeyCode::Enter)) {
|
||||
eventResult.editStarted = BeginEdit(state, spec, false);
|
||||
eventResult.consumed = eventResult.editStarted;
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::Character:
|
||||
if (!state.session.focused ||
|
||||
spec.readOnly ||
|
||||
event.modifiers.control ||
|
||||
event.modifiers.alt ||
|
||||
event.modifiers.super) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!state.session.editing &&
|
||||
!IsUIEditorEditableFieldCharacterInsertable(
|
||||
event.character)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!state.session.editing) {
|
||||
eventResult.editStarted = BeginEdit(state, spec, true);
|
||||
}
|
||||
|
||||
if (InsertUIEditorEditableFieldCharacter(
|
||||
state.session,
|
||||
event.character)) {
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
layout = BuildUIEditorTextFieldLayout(bounds, spec, metrics);
|
||||
SyncDisplayState(state, spec, layout);
|
||||
if (eventResult.hitTarget.kind == UIEditorTextFieldHitTargetKind::None &&
|
||||
state.session.hasPointerPosition) {
|
||||
eventResult.hitTarget = HitTestUIEditorTextField(
|
||||
layout,
|
||||
state.session.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);
|
||||
SyncDisplayState(state, spec, layout);
|
||||
if (interactionResult.hitTarget.kind == UIEditorTextFieldHitTargetKind::None &&
|
||||
state.session.hasPointerPosition) {
|
||||
interactionResult.hitTarget = HitTestUIEditorTextField(
|
||||
layout,
|
||||
state.session.pointerPosition);
|
||||
}
|
||||
|
||||
return {
|
||||
std::move(layout),
|
||||
std::move(interactionResult)
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
Reference in New Issue
Block a user