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

330 lines
12 KiB
C++

#include <XCEditor/Collections/UIEditorTabStripInteraction.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::BuildUIEditorTabStripLayout;
using Widgets::HitTestUIEditorTabStrip;
using Widgets::ResolveUIEditorTabStripSelectedIndex;
using Widgets::UIEditorTabStripHitTarget;
using Widgets::UIEditorTabStripHitTargetKind;
using Widgets::UIEditorTabStripInvalidIndex;
bool ShouldUsePointerPosition(const UIInputEvent& event) {
switch (event.type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
return true;
default:
return false;
}
}
bool HasNavigationModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) {
return modifiers.shift || modifiers.control || modifiers.alt || modifiers.super;
}
bool IsPointInside(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
bool AreEquivalentTargets(
const UIEditorTabStripHitTarget& lhs,
const UIEditorTabStripHitTarget& rhs) {
return lhs.kind == rhs.kind && lhs.index == rhs.index;
}
void ClearHoverState(UIEditorTabStripInteractionState& state) {
state.tabStripState.hoveredIndex = UIEditorTabStripInvalidIndex;
state.tabStripState.closeHoveredIndex = UIEditorTabStripInvalidIndex;
}
void SyncHoverTarget(
UIEditorTabStripInteractionState& state,
const Widgets::UIEditorTabStripLayout& layout) {
ClearHoverState(state);
if (!state.hasPointerPosition) {
return;
}
const UIEditorTabStripHitTarget hitTarget =
HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition);
if (hitTarget.index == UIEditorTabStripInvalidIndex) {
return;
}
switch (hitTarget.kind) {
case UIEditorTabStripHitTargetKind::CloseButton:
state.tabStripState.hoveredIndex = hitTarget.index;
state.tabStripState.closeHoveredIndex = hitTarget.index;
break;
case UIEditorTabStripHitTargetKind::Tab:
state.tabStripState.hoveredIndex = hitTarget.index;
break;
default:
break;
}
}
void SyncSelectionState(
UIEditorTabStripInteractionState& state,
std::string_view selectedTabId,
const std::vector<Widgets::UIEditorTabStripItem>& items) {
state.navigationModel.SetItemCount(items.size());
const std::size_t fallbackIndex =
state.navigationModel.HasSelection()
? state.navigationModel.GetSelectedIndex()
: UIEditorTabStripInvalidIndex;
const std::size_t resolvedSelectedIndex =
ResolveUIEditorTabStripSelectedIndex(items, selectedTabId, fallbackIndex);
if (resolvedSelectedIndex != UIEditorTabStripInvalidIndex) {
state.navigationModel.SetSelectedIndex(resolvedSelectedIndex);
}
state.tabStripState.selectedIndex =
state.navigationModel.HasSelection()
? state.navigationModel.GetSelectedIndex()
: UIEditorTabStripInvalidIndex;
}
bool SelectTab(
UIEditorTabStripInteractionState& state,
std::string& selectedTabId,
const std::vector<Widgets::UIEditorTabStripItem>& items,
std::size_t selectedIndex,
UIEditorTabStripInteractionResult& result,
bool keyboardNavigated) {
if (selectedIndex >= items.size()) {
return false;
}
state.navigationModel.SetSelectedIndex(selectedIndex);
state.tabStripState.selectedIndex = selectedIndex;
const std::string& tabId = items[selectedIndex].tabId;
result.selectionChanged = selectedTabId != tabId;
selectedTabId = tabId;
result.keyboardNavigated = keyboardNavigated;
result.selectedTabId = tabId;
result.selectedIndex = selectedIndex;
result.consumed = true;
return true;
}
bool ApplyKeyboardNavigation(
UIEditorTabStripInteractionState& state,
std::int32_t keyCode) {
switch (static_cast<KeyCode>(keyCode)) {
case KeyCode::Left:
return state.navigationModel.SelectPrevious();
case KeyCode::Right:
return state.navigationModel.SelectNext();
case KeyCode::Home:
return state.navigationModel.SelectFirst();
case KeyCode::End:
return state.navigationModel.SelectLast();
default:
return false;
}
}
} // namespace
UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction(
UIEditorTabStripInteractionState& state,
std::string& selectedTabId,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<Widgets::UIEditorTabStripItem>& items,
const std::vector<UIInputEvent>& inputEvents,
const Widgets::UIEditorTabStripMetrics& metrics) {
SyncSelectionState(state, selectedTabId, items);
Widgets::UIEditorTabStripLayout layout =
BuildUIEditorTabStripLayout(bounds, items, state.tabStripState, metrics);
SyncHoverTarget(state, layout);
UIEditorTabStripInteractionResult 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;
}
UIEditorTabStripInteractionResult eventResult = {};
eventResult.hitTarget =
state.hasPointerPosition
? HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition)
: UIEditorTabStripHitTarget {};
switch (event.type) {
case UIInputEventType::FocusGained:
state.tabStripState.focused = true;
break;
case UIInputEventType::FocusLost:
state.tabStripState.focused = false;
state.hasPointerPosition = false;
state.pressedTarget = {};
ClearHoverState(state);
break;
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerLeave:
break;
case UIInputEventType::PointerButtonDown: {
if (event.pointerButton != UIPointerButton::Left) {
break;
}
state.pressedTarget = eventResult.hitTarget;
if (eventResult.hitTarget.kind != UIEditorTabStripHitTargetKind::None ||
(state.hasPointerPosition && IsPointInside(layout.bounds, state.pointerPosition))) {
state.tabStripState.focused = true;
eventResult.consumed = true;
} else {
state.tabStripState.focused = false;
state.pressedTarget = {};
}
break;
}
case UIInputEventType::PointerButtonUp: {
if (event.pointerButton != UIPointerButton::Left) {
break;
}
const bool insideStrip =
state.hasPointerPosition && IsPointInside(layout.bounds, state.pointerPosition);
const bool matchedPressedTarget =
AreEquivalentTargets(state.pressedTarget, eventResult.hitTarget);
if (matchedPressedTarget) {
switch (eventResult.hitTarget.kind) {
case UIEditorTabStripHitTargetKind::CloseButton:
if (eventResult.hitTarget.index < items.size() &&
items[eventResult.hitTarget.index].closable) {
eventResult.closeRequested = true;
eventResult.closedTabId = items[eventResult.hitTarget.index].tabId;
eventResult.closedIndex = eventResult.hitTarget.index;
eventResult.consumed = true;
state.tabStripState.focused = true;
} else if (insideStrip) {
state.tabStripState.focused = true;
eventResult.consumed = true;
}
break;
case UIEditorTabStripHitTargetKind::Tab:
SelectTab(
state,
selectedTabId,
items,
eventResult.hitTarget.index,
eventResult,
false);
state.tabStripState.focused = true;
break;
case UIEditorTabStripHitTargetKind::HeaderBackground:
case UIEditorTabStripHitTargetKind::Content:
state.tabStripState.focused = true;
eventResult.consumed = true;
break;
case UIEditorTabStripHitTargetKind::None:
default:
if (!insideStrip) {
state.tabStripState.focused = false;
}
break;
}
} else if (!insideStrip) {
state.tabStripState.focused = false;
} else if (eventResult.hitTarget.kind == UIEditorTabStripHitTargetKind::HeaderBackground ||
eventResult.hitTarget.kind == UIEditorTabStripHitTargetKind::Content) {
state.tabStripState.focused = true;
eventResult.consumed = true;
}
state.pressedTarget = {};
break;
}
case UIInputEventType::KeyDown:
if (state.tabStripState.focused &&
!HasNavigationModifiers(event.modifiers) &&
ApplyKeyboardNavigation(state, event.keyCode) &&
state.navigationModel.HasSelection()) {
const std::size_t selectedIndex = state.navigationModel.GetSelectedIndex();
SelectTab(
state,
selectedTabId,
items,
selectedIndex,
eventResult,
true);
}
break;
default:
break;
}
SyncSelectionState(state, selectedTabId, items);
layout = BuildUIEditorTabStripLayout(bounds, items, state.tabStripState, metrics);
SyncHoverTarget(state, layout);
if (eventResult.hitTarget.kind == UIEditorTabStripHitTargetKind::None &&
state.hasPointerPosition) {
eventResult.hitTarget =
HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition);
}
if (eventResult.consumed ||
eventResult.selectionChanged ||
eventResult.closeRequested ||
eventResult.keyboardNavigated ||
eventResult.hitTarget.kind != UIEditorTabStripHitTargetKind::None ||
!eventResult.selectedTabId.empty() ||
!eventResult.closedTabId.empty()) {
interactionResult = std::move(eventResult);
}
}
SyncSelectionState(state, selectedTabId, items);
layout = BuildUIEditorTabStripLayout(bounds, items, state.tabStripState, metrics);
SyncHoverTarget(state, layout);
if (interactionResult.hitTarget.kind == UIEditorTabStripHitTargetKind::None &&
state.hasPointerPosition) {
interactionResult.hitTarget =
HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition);
}
return {
std::move(layout),
std::move(interactionResult),
state.tabStripState.focused
};
}
} // namespace XCEngine::UI::Editor