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