Files
XCEngine/new_editor/src/Collections/UIEditorListViewInteraction.cpp

434 lines
16 KiB
C++

#include <XCEditor/Collections/UIEditorListViewInteraction.h>
#include <XCEngine/Input/InputTypes.h>
#include <algorithm>
#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::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<Widgets::UIEditorListViewItem>& 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<Widgets::UIEditorListViewItem>& 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<Widgets::UIEditorListViewItem>& 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<Widgets::UIEditorListViewItem>& 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<Widgets::UIEditorListViewItem>& 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<std::string> 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<Widgets::UIEditorListViewItem>& items,
std::int32_t keyCode,
UIEditorListViewInteractionResult& result,
bool extendSelectionRange) {
switch (static_cast<KeyCode>(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<Widgets::UIEditorListViewItem>& items,
const std::vector<UIInputEvent>& 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<std::int32_t>(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