261 lines
9.0 KiB
C++
261 lines
9.0 KiB
C++
|
|
#include <XCEditor/Fields/UIEditorAssetFieldInteraction.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 Widgets::BuildUIEditorAssetFieldLayout;
|
||
|
|
using Widgets::HasUIEditorAssetFieldValue;
|
||
|
|
using Widgets::HitTestUIEditorAssetField;
|
||
|
|
using Widgets::IsUIEditorAssetFieldPointInside;
|
||
|
|
using Widgets::UIEditorAssetFieldHitTarget;
|
||
|
|
using Widgets::UIEditorAssetFieldHitTargetKind;
|
||
|
|
using Widgets::UIEditorAssetFieldLayout;
|
||
|
|
using Widgets::UIEditorAssetFieldMetrics;
|
||
|
|
using Widgets::UIEditorAssetFieldSpec;
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void SyncHoverTarget(
|
||
|
|
UIEditorAssetFieldInteractionState& state,
|
||
|
|
const UIEditorAssetFieldLayout& layout) {
|
||
|
|
if (!state.hasPointerPosition) {
|
||
|
|
state.fieldState.hoveredTarget = UIEditorAssetFieldHitTargetKind::None;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
state.fieldState.hoveredTarget = HitTestUIEditorAssetField(layout, state.pointerPosition).kind;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CanRequestPicker(const UIEditorAssetFieldSpec& spec) {
|
||
|
|
return !spec.readOnly && spec.showPickerButton;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CanClearValue(const UIEditorAssetFieldSpec& spec) {
|
||
|
|
return !spec.readOnly && spec.allowClear && HasUIEditorAssetFieldValue(spec);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ClearValue(
|
||
|
|
UIEditorAssetFieldSpec& spec,
|
||
|
|
UIEditorAssetFieldInteractionResult& result) {
|
||
|
|
result.assetIdBefore = spec.assetId;
|
||
|
|
result.displayNameBefore = spec.displayName;
|
||
|
|
spec.assetId.clear();
|
||
|
|
spec.displayName.clear();
|
||
|
|
spec.statusText.clear();
|
||
|
|
result.assetIdAfter = spec.assetId;
|
||
|
|
result.displayNameAfter = spec.displayName;
|
||
|
|
result.clearRequested = true;
|
||
|
|
result.valueChanged = true;
|
||
|
|
result.consumed = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void RequestPicker(UIEditorAssetFieldInteractionResult& result) {
|
||
|
|
result.pickerRequested = true;
|
||
|
|
result.consumed = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void RequestActivate(UIEditorAssetFieldInteractionResult& result) {
|
||
|
|
result.activateRequested = true;
|
||
|
|
result.consumed = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
UIEditorAssetFieldInteractionFrame UpdateUIEditorAssetFieldInteraction(
|
||
|
|
UIEditorAssetFieldInteractionState& state,
|
||
|
|
UIEditorAssetFieldSpec& spec,
|
||
|
|
const ::XCEngine::UI::UIRect& bounds,
|
||
|
|
const std::vector<UIInputEvent>& inputEvents,
|
||
|
|
const UIEditorAssetFieldMetrics& metrics) {
|
||
|
|
UIEditorAssetFieldLayout layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics);
|
||
|
|
SyncHoverTarget(state, layout);
|
||
|
|
|
||
|
|
UIEditorAssetFieldInteractionResult interactionResult = {};
|
||
|
|
for (const UIInputEvent& event : inputEvents) {
|
||
|
|
if (ShouldUsePointerPosition(event)) {
|
||
|
|
state.pointerPosition = event.position;
|
||
|
|
state.hasPointerPosition = true;
|
||
|
|
} else if (event.type == UIInputEventType::PointerLeave) {
|
||
|
|
state.hasPointerPosition = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
UIEditorAssetFieldInteractionResult eventResult = {};
|
||
|
|
switch (event.type) {
|
||
|
|
case UIInputEventType::FocusGained:
|
||
|
|
eventResult.focusChanged = !state.fieldState.focused;
|
||
|
|
state.fieldState.focused = true;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case UIInputEventType::FocusLost:
|
||
|
|
eventResult.focusChanged = state.fieldState.focused;
|
||
|
|
state.fieldState.focused = false;
|
||
|
|
state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None;
|
||
|
|
state.hasPointerPosition = false;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case UIInputEventType::PointerMove:
|
||
|
|
case UIInputEventType::PointerEnter:
|
||
|
|
case UIInputEventType::PointerLeave:
|
||
|
|
break;
|
||
|
|
|
||
|
|
case UIInputEventType::PointerButtonDown: {
|
||
|
|
const UIEditorAssetFieldHitTarget hitTarget =
|
||
|
|
state.hasPointerPosition
|
||
|
|
? HitTestUIEditorAssetField(layout, state.pointerPosition)
|
||
|
|
: UIEditorAssetFieldHitTarget {};
|
||
|
|
eventResult.hitTarget = hitTarget;
|
||
|
|
|
||
|
|
if (event.pointerButton != UIPointerButton::Left) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
const bool insideField =
|
||
|
|
state.hasPointerPosition &&
|
||
|
|
IsUIEditorAssetFieldPointInside(layout.bounds, state.pointerPosition);
|
||
|
|
if (insideField) {
|
||
|
|
eventResult.focusChanged = !state.fieldState.focused;
|
||
|
|
state.fieldState.focused = true;
|
||
|
|
state.fieldState.activeTarget =
|
||
|
|
hitTarget.kind == UIEditorAssetFieldHitTargetKind::None
|
||
|
|
? UIEditorAssetFieldHitTargetKind::Row
|
||
|
|
: hitTarget.kind;
|
||
|
|
eventResult.consumed = true;
|
||
|
|
} else {
|
||
|
|
if (state.fieldState.focused) {
|
||
|
|
eventResult.focusChanged = true;
|
||
|
|
state.fieldState.focused = false;
|
||
|
|
}
|
||
|
|
state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case UIInputEventType::PointerButtonUp: {
|
||
|
|
const UIEditorAssetFieldHitTarget hitTarget =
|
||
|
|
state.hasPointerPosition
|
||
|
|
? HitTestUIEditorAssetField(layout, state.pointerPosition)
|
||
|
|
: UIEditorAssetFieldHitTarget {};
|
||
|
|
eventResult.hitTarget = hitTarget;
|
||
|
|
|
||
|
|
if (event.pointerButton == UIPointerButton::Left) {
|
||
|
|
const UIEditorAssetFieldHitTargetKind activeTarget = state.fieldState.activeTarget;
|
||
|
|
state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None;
|
||
|
|
|
||
|
|
if (activeTarget == hitTarget.kind) {
|
||
|
|
switch (activeTarget) {
|
||
|
|
case UIEditorAssetFieldHitTargetKind::PickerButton:
|
||
|
|
if (CanRequestPicker(spec)) {
|
||
|
|
RequestPicker(eventResult);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case UIEditorAssetFieldHitTargetKind::ClearButton:
|
||
|
|
if (CanClearValue(spec)) {
|
||
|
|
ClearValue(spec, eventResult);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case UIEditorAssetFieldHitTargetKind::ValueBox:
|
||
|
|
RequestActivate(eventResult);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case UIEditorAssetFieldHitTargetKind::Row:
|
||
|
|
eventResult.consumed = true;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case UIEditorAssetFieldHitTargetKind::None:
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case UIInputEventType::KeyDown:
|
||
|
|
if (!state.fieldState.focused) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (static_cast<KeyCode>(event.keyCode)) {
|
||
|
|
case KeyCode::Enter:
|
||
|
|
case KeyCode::Space:
|
||
|
|
if (CanRequestPicker(spec)) {
|
||
|
|
RequestPicker(eventResult);
|
||
|
|
eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::PickerButton;
|
||
|
|
} else {
|
||
|
|
RequestActivate(eventResult);
|
||
|
|
eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::ValueBox;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case KeyCode::Delete:
|
||
|
|
case KeyCode::Backspace:
|
||
|
|
if (CanClearValue(spec)) {
|
||
|
|
ClearValue(spec, eventResult);
|
||
|
|
eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::ClearButton;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics);
|
||
|
|
SyncHoverTarget(state, layout);
|
||
|
|
if (eventResult.hitTarget.kind == UIEditorAssetFieldHitTargetKind::None &&
|
||
|
|
state.hasPointerPosition) {
|
||
|
|
eventResult.hitTarget = HitTestUIEditorAssetField(layout, state.pointerPosition);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (eventResult.consumed ||
|
||
|
|
eventResult.focusChanged ||
|
||
|
|
eventResult.valueChanged ||
|
||
|
|
eventResult.activateRequested ||
|
||
|
|
eventResult.pickerRequested ||
|
||
|
|
eventResult.clearRequested ||
|
||
|
|
eventResult.hitTarget.kind != UIEditorAssetFieldHitTargetKind::None) {
|
||
|
|
interactionResult = std::move(eventResult);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics);
|
||
|
|
SyncHoverTarget(state, layout);
|
||
|
|
if (interactionResult.hitTarget.kind == UIEditorAssetFieldHitTargetKind::None &&
|
||
|
|
state.hasPointerPosition) {
|
||
|
|
interactionResult.hitTarget = HitTestUIEditorAssetField(layout, state.pointerPosition);
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
std::move(layout),
|
||
|
|
std::move(interactionResult)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace XCEngine::UI::Editor
|