Refactor XCUI editor module layout
This commit is contained in:
433
new_editor/src/Collections/UIEditorListViewInteraction.cpp
Normal file
433
new_editor/src/Collections/UIEditorListViewInteraction.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user