#include #include #include #include namespace XCEngine::UI::Editor { namespace { using ::XCEngine::Input::KeyCode; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPointerButton; using Widgets::BuildUIEditorListViewLayout; using Widgets::FindUIEditorListViewItemIndex; using Widgets::HitTestUIEditorListView; using Widgets::IsUIEditorListViewPointInside; using Widgets::UIEditorListViewHitTarget; using Widgets::UIEditorListViewHitTargetKind; using Widgets::UIEditorListViewInvalidIndex; 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( UIEditorListViewInteractionState& state, const Widgets::UIEditorListViewLayout& layout, const std::vector& items) { state.listViewState.hoveredItemId.clear(); if (!state.hasPointerPosition) { return; } const UIEditorListViewHitTarget hitTarget = HitTestUIEditorListView(layout, state.pointerPosition); if (hitTarget.itemIndex < items.size()) { state.listViewState.hoveredItemId = items[hitTarget.itemIndex].itemId; } } void SyncKeyboardNavigation( UIEditorListViewInteractionState& state, const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const std::vector& items) { state.keyboardNavigation.SetItemCount(items.size()); state.keyboardNavigation.ClampToItemCount(); if (!selectionModel.HasSelection()) { return; } const std::size_t selectedIndex = FindUIEditorListViewItemIndex(items, selectionModel.GetSelectedId()); if (selectedIndex == UIEditorListViewInvalidIndex) { return; } if (!state.keyboardNavigation.HasCurrentIndex() || state.keyboardNavigation.GetCurrentIndex() != selectedIndex) { state.keyboardNavigation.SetCurrentIndex(selectedIndex); } } void SyncSelectionAnchor( UIEditorListViewInteractionState& state, const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel) { if (!selectionModel.HasSelection()) { state.selectionAnchorId.clear(); return; } if (state.selectionAnchorId.empty() || !selectionModel.IsSelected(state.selectionAnchorId)) { state.selectionAnchorId = selectionModel.GetSelectedId(); } } bool SelectItem( UIEditorListViewInteractionState& state, ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const std::vector& items, std::size_t itemIndex, UIEditorListViewInteractionResult& result, bool markKeyboardNavigation) { if (itemIndex >= items.size()) { return false; } const Widgets::UIEditorListViewItem& item = items[itemIndex]; result.selectionChanged = selectionModel.SetSelection(item.itemId); result.selectedItemId = item.itemId; result.selectedIndex = itemIndex; result.keyboardNavigated = markKeyboardNavigation; result.consumed = true; state.keyboardNavigation.SetCurrentIndex(itemIndex); state.selectionAnchorId = item.itemId; return true; } bool ToggleItemSelection( UIEditorListViewInteractionState& state, ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const std::vector& items, std::size_t itemIndex, UIEditorListViewInteractionResult& result) { if (itemIndex >= items.size()) { return false; } const Widgets::UIEditorListViewItem& item = items[itemIndex]; result.selectionChanged = selectionModel.ToggleSelectionMembership(item.itemId, true); result.selectedItemId = item.itemId; result.selectedIndex = itemIndex; result.consumed = true; state.keyboardNavigation.SetCurrentIndex(itemIndex); state.selectionAnchorId = item.itemId; return true; } bool SelectItemRange( UIEditorListViewInteractionState& state, ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const std::vector& items, std::size_t itemIndex, UIEditorListViewInteractionResult& result, bool markKeyboardNavigation) { if (itemIndex >= items.size()) { return false; } std::size_t anchorIndex = UIEditorListViewInvalidIndex; if (!state.selectionAnchorId.empty()) { anchorIndex = FindUIEditorListViewItemIndex(items, state.selectionAnchorId); } if (anchorIndex == UIEditorListViewInvalidIndex) { return SelectItem( state, selectionModel, items, itemIndex, result, markKeyboardNavigation); } const std::size_t rangeBegin = (std::min)(anchorIndex, itemIndex); const std::size_t rangeEnd = (std::max)(anchorIndex, itemIndex); std::vector selectedIds = {}; selectedIds.reserve(rangeEnd - rangeBegin + 1u); for (std::size_t index = rangeBegin; index <= rangeEnd; ++index) { selectedIds.push_back(items[index].itemId); } const Widgets::UIEditorListViewItem& item = items[itemIndex]; result.selectionChanged = selectionModel.SetSelections(std::move(selectedIds), item.itemId); result.selectedItemId = item.itemId; result.selectedIndex = itemIndex; result.keyboardNavigated = markKeyboardNavigation; result.consumed = true; state.keyboardNavigation.SetCurrentIndex(itemIndex, false); return true; } bool ApplyKeyboardNavigation( UIEditorListViewInteractionState& state, ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const std::vector& items, std::int32_t keyCode, UIEditorListViewInteractionResult& result, bool extendSelectionRange) { switch (static_cast(keyCode)) { case KeyCode::Up: if (!state.keyboardNavigation.MovePrevious()) { return false; } break; case KeyCode::Down: if (!state.keyboardNavigation.MoveNext()) { return false; } break; case KeyCode::Home: if (!state.keyboardNavigation.MoveHome()) { return false; } break; case KeyCode::End: if (!state.keyboardNavigation.MoveEnd()) { return false; } break; default: return false; } if (!state.keyboardNavigation.HasCurrentIndex()) { return false; } return extendSelectionRange ? SelectItemRange( state, selectionModel, items, state.keyboardNavigation.GetCurrentIndex(), result, true) : SelectItem( state, selectionModel, items, state.keyboardNavigation.GetCurrentIndex(), result, true); } } // namespace UIEditorListViewInteractionFrame UpdateUIEditorListViewInteraction( UIEditorListViewInteractionState& state, ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, const ::XCEngine::UI::UIRect& bounds, const std::vector& items, const std::vector& inputEvents, const Widgets::UIEditorListViewMetrics& metrics) { Widgets::UIEditorListViewLayout layout = BuildUIEditorListViewLayout(bounds, items, metrics); SyncKeyboardNavigation(state, selectionModel, items); SyncSelectionAnchor(state, selectionModel); SyncHoverTarget(state, layout, items); UIEditorListViewInteractionResult 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; } UIEditorListViewInteractionResult eventResult = {}; switch (event.type) { case UIInputEventType::FocusGained: state.listViewState.focused = true; break; case UIInputEventType::FocusLost: state.listViewState.focused = false; state.hasPointerPosition = false; state.listViewState.hoveredItemId.clear(); break; case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerLeave: break; case UIInputEventType::PointerButtonDown: { const UIEditorListViewHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorListView(layout, state.pointerPosition) : UIEditorListViewHitTarget {}; eventResult.hitTarget = hitTarget; const bool insideList = state.hasPointerPosition && IsUIEditorListViewPointInside(layout.bounds, state.pointerPosition); if ((event.pointerButton == UIPointerButton::Left || event.pointerButton == UIPointerButton::Right) && hitTarget.kind == UIEditorListViewHitTargetKind::Row) { state.listViewState.focused = true; eventResult.consumed = true; } else if (event.pointerButton == UIPointerButton::Left && insideList) { state.listViewState.focused = true; eventResult.consumed = true; } else if (event.pointerButton == UIPointerButton::Left) { state.listViewState.focused = false; } break; } case UIInputEventType::PointerButtonUp: { const UIEditorListViewHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorListView(layout, state.pointerPosition) : UIEditorListViewHitTarget {}; eventResult.hitTarget = hitTarget; const bool insideList = state.hasPointerPosition && IsUIEditorListViewPointInside(layout.bounds, state.pointerPosition); if (hitTarget.itemIndex >= items.size()) { if (event.pointerButton == UIPointerButton::Left && insideList) { state.listViewState.focused = true; eventResult.consumed = true; } else if (event.pointerButton == UIPointerButton::Left) { state.listViewState.focused = false; } break; } const Widgets::UIEditorListViewItem& item = items[hitTarget.itemIndex]; if (event.pointerButton == UIPointerButton::Left && hitTarget.kind == UIEditorListViewHitTargetKind::Row) { if (event.modifiers.shift) { SelectItemRange( state, selectionModel, items, hitTarget.itemIndex, eventResult, false); } else if (event.modifiers.control) { ToggleItemSelection( state, selectionModel, items, hitTarget.itemIndex, eventResult); } else { SelectItem( state, selectionModel, items, hitTarget.itemIndex, eventResult, false); } state.listViewState.focused = true; } else if (event.pointerButton == UIPointerButton::Right && hitTarget.kind == UIEditorListViewHitTargetKind::Row) { if (selectionModel.IsSelected(item.itemId)) { selectionModel.SetPrimarySelection(item.itemId); eventResult.selectionChanged = false; eventResult.selectedItemId = item.itemId; eventResult.selectedIndex = hitTarget.itemIndex; eventResult.consumed = true; state.keyboardNavigation.SetCurrentIndex(hitTarget.itemIndex); state.selectionAnchorId = item.itemId; } else { SelectItem( state, selectionModel, items, hitTarget.itemIndex, eventResult, false); } eventResult.secondaryClicked = true; eventResult.consumed = true; state.listViewState.focused = true; } break; } case UIInputEventType::KeyDown: if (state.listViewState.focused && !event.modifiers.control && !event.modifiers.alt && !event.modifiers.super) { if (event.keyCode == static_cast(KeyCode::F2) && selectionModel.HasSelection()) { const std::string& selectedItemId = selectionModel.GetSelectedId(); eventResult.renameRequested = true; eventResult.renameItemId = selectedItemId; eventResult.selectedItemId = selectedItemId; eventResult.selectedIndex = FindUIEditorListViewItemIndex(items, selectedItemId); eventResult.consumed = true; } else if (ApplyKeyboardNavigation( state, selectionModel, items, event.keyCode, eventResult, event.modifiers.shift)) { eventResult.consumed = true; } } break; default: break; } layout = BuildUIEditorListViewLayout(bounds, items, metrics); SyncKeyboardNavigation(state, selectionModel, items); SyncSelectionAnchor(state, selectionModel); SyncHoverTarget(state, layout, items); if (eventResult.hitTarget.kind == UIEditorListViewHitTargetKind::None && state.hasPointerPosition) { eventResult.hitTarget = HitTestUIEditorListView(layout, state.pointerPosition); } if (eventResult.consumed || eventResult.selectionChanged || eventResult.keyboardNavigated || eventResult.secondaryClicked || eventResult.renameRequested || eventResult.hitTarget.kind != UIEditorListViewHitTargetKind::None || !eventResult.selectedItemId.empty() || !eventResult.renameItemId.empty()) { interactionResult = std::move(eventResult); } } layout = BuildUIEditorListViewLayout(bounds, items, metrics); SyncKeyboardNavigation(state, selectionModel, items); SyncSelectionAnchor(state, selectionModel); SyncHoverTarget(state, layout, items); if (interactionResult.hitTarget.kind == UIEditorListViewHitTargetKind::None && state.hasPointerPosition) { interactionResult.hitTarget = HitTestUIEditorListView(layout, state.pointerPosition); } return { std::move(layout), std::move(interactionResult) }; } } // namespace XCEngine::UI::Editor