Add XCUI input focus shortcut MVP

This commit is contained in:
2026-04-04 18:55:20 +08:00
parent a9bf9ef35c
commit 611ca705c8
17 changed files with 1342 additions and 0 deletions

View File

@@ -405,6 +405,35 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Platform/Windows/WindowsInputModule.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Platform/Windows/WindowsInputModule.cpp
# UI
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Types.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIInvalidation.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIViewModel.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIBuildContext.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIElementTree.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIContext.h
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Core/UIBuildContext.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Core/UIElementTree.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Layout/LayoutTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Layout/LayoutEngine.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/Theme.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleSet.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleResolver.h
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/StyleTypes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/Theme.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/StyleResolver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIInputPath.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIFocusController.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIShortcutRegistry.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIInputRouter.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIInputDispatcher.h
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Input/UIInputPath.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Input/UIFocusController.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Input/UIShortcutRegistry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Input/UIInputRouter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Input/UIInputDispatcher.cpp
# Input
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputEvent.h

View File

@@ -0,0 +1,65 @@
#pragma once
#include "UIInputPath.h"
namespace XCEngine {
namespace UI {
struct UIFocusChange {
UIInputPath previousPath = {};
UIInputPath currentPath = {};
UIInputPath lostPath = {};
UIInputPath gainedPath = {};
bool Changed() const {
return previousPath != currentPath;
}
};
class UIFocusController {
public:
const UIInputPath& GetFocusedPath() const {
return m_focusedPath;
}
const UIInputPath& GetActivePath() const {
return m_activePath;
}
const UIInputPath& GetPointerCapturePath() const {
return m_pointerCapturePath;
}
bool HasFocus() const {
return !m_focusedPath.Empty();
}
bool HasActivePath() const {
return !m_activePath.Empty();
}
bool HasPointerCapture() const {
return !m_pointerCapturePath.Empty();
}
UIFocusChange SetFocusedPath(const UIInputPath& path);
UIFocusChange ClearFocus();
void SetActivePath(const UIInputPath& path);
void ClearActivePath();
void SetPointerCapturePath(const UIInputPath& path);
void ClearPointerCapturePath();
bool IsFocused(UIElementId elementId) const;
bool IsActive(UIElementId elementId) const;
bool IsCapturingPointer(UIElementId elementId) const;
private:
UIInputPath m_focusedPath = {};
UIInputPath m_activePath = {};
UIInputPath m_pointerCapturePath = {};
};
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,110 @@
#pragma once
#include "UIFocusController.h"
#include "UIInputRouter.h"
#include "UIShortcutRegistry.h"
#include <string>
#include <utility>
namespace XCEngine {
namespace UI {
struct UIInputDispatcherOptions {
bool pointerDownChangesFocus = true;
bool pointerDownStartsActivePath = true;
UIPointerButton focusTransferButton = UIPointerButton::Left;
};
struct UIInputDispatchSummary {
UIFocusChange focusChange = {};
UIInputDispatchResult routing = {};
bool shortcutHandled = false;
std::string commandId = {};
bool Handled() const {
return shortcutHandled || routing.handled;
}
};
class UIInputDispatcher {
public:
explicit UIInputDispatcher(
const UIInputDispatcherOptions& options = UIInputDispatcherOptions())
: m_options(options) {
}
UIFocusController& GetFocusController() {
return m_focusController;
}
const UIFocusController& GetFocusController() const {
return m_focusController;
}
UIShortcutRegistry& GetShortcutRegistry() {
return m_shortcutRegistry;
}
const UIShortcutRegistry& GetShortcutRegistry() const {
return m_shortcutRegistry;
}
template <typename HandlerFn>
UIInputDispatchSummary Dispatch(
const UIInputEvent& event,
const UIInputPath& hoveredPath,
HandlerFn&& handler) {
UIInputDispatchSummary summary = {};
if (ShouldTransferFocusOnPointerDown(event, hoveredPath)) {
summary.focusChange = m_focusController.SetFocusedPath(hoveredPath);
}
if (ShouldStartActivePathOnPointerDown(event, hoveredPath)) {
const UIInputPath& capturePath = m_focusController.GetPointerCapturePath();
m_focusController.SetActivePath(capturePath.Empty() ? hoveredPath : capturePath);
}
const UIShortcutContext shortcutContext = {
m_focusController.GetFocusedPath(),
m_focusController.GetActivePath(),
hoveredPath };
const UIShortcutMatch shortcutMatch = m_shortcutRegistry.Match(event, shortcutContext);
if (shortcutMatch.matched) {
summary.shortcutHandled = true;
summary.commandId = shortcutMatch.binding.commandId;
return FinalizeDispatch(event, std::move(summary));
}
UIInputRouteContext routeContext = {};
routeContext.hoveredPath = hoveredPath;
routeContext.focusedPath = m_focusController.GetFocusedPath();
routeContext.capturePath = m_focusController.GetPointerCapturePath();
summary.routing = UIInputRouter::Dispatch(
event,
routeContext,
std::forward<HandlerFn>(handler));
return FinalizeDispatch(event, std::move(summary));
}
private:
bool ShouldTransferFocusOnPointerDown(
const UIInputEvent& event,
const UIInputPath& hoveredPath) const;
bool ShouldStartActivePathOnPointerDown(
const UIInputEvent& event,
const UIInputPath& hoveredPath) const;
UIInputDispatchSummary FinalizeDispatch(
const UIInputEvent& event,
UIInputDispatchSummary&& summary);
UIInputDispatcherOptions m_options = {};
UIFocusController m_focusController = {};
UIShortcutRegistry m_shortcutRegistry = {};
};
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,56 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <vector>
namespace XCEngine {
namespace UI {
using UIElementId = std::uint64_t;
struct UIInputPath {
std::vector<UIElementId> elements = {};
UIInputPath() = default;
UIInputPath(std::initializer_list<UIElementId> values)
: elements(values) {
}
bool Empty() const {
return elements.empty();
}
std::size_t Size() const {
return elements.size();
}
UIElementId Root() const {
return elements.empty() ? 0u : elements.front();
}
UIElementId Target() const {
return elements.empty() ? 0u : elements.back();
}
bool Contains(UIElementId elementId) const;
void Clear() {
elements.clear();
}
};
bool operator==(const UIInputPath& lhs, const UIInputPath& rhs);
bool operator!=(const UIInputPath& lhs, const UIInputPath& rhs);
std::size_t GetUIInputPathCommonPrefixLength(
const UIInputPath& lhs,
const UIInputPath& rhs);
UIInputPath BuildUIInputPathSuffix(
const UIInputPath& path,
std::size_t startIndex);
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,104 @@
#pragma once
#include "UIInputPath.h"
#include <XCEngine/UI/Types.h>
#include <cstdint>
#include <utility>
#include <vector>
namespace XCEngine {
namespace UI {
enum class UIInputRoutingPhase : std::uint8_t {
Capture = 0,
Target,
Bubble
};
enum class UIInputTargetKind : std::uint8_t {
None = 0,
Hovered,
Focused,
Captured
};
struct UIInputRouteContext {
UIInputPath hoveredPath = {};
UIInputPath focusedPath = {};
UIInputPath capturePath = {};
};
struct UIInputRoutingStep {
UIElementId elementId = 0;
UIInputRoutingPhase phase = UIInputRoutingPhase::Target;
bool isTargetElement = false;
};
struct UIInputRoutingPlan {
UIInputTargetKind targetKind = UIInputTargetKind::None;
UIInputPath targetPath = {};
std::vector<UIInputRoutingStep> steps = {};
bool HasTargetPath() const {
return !targetPath.Empty();
}
};
struct UIInputDispatchRequest {
const UIInputEvent* event = nullptr;
UIElementId elementId = 0;
UIInputRoutingPhase phase = UIInputRoutingPhase::Target;
UIInputTargetKind targetKind = UIInputTargetKind::None;
bool isTargetElement = false;
};
struct UIInputDispatchDecision {
bool handled = false;
bool stopPropagation = false;
};
struct UIInputDispatchResult {
UIInputRoutingPlan plan = {};
bool handled = false;
};
class UIInputRouter {
public:
static bool IsPointerEvent(UIInputEventType type);
static bool IsKeyboardEvent(UIInputEventType type);
static UIInputRoutingPlan BuildRoutingPlan(
const UIInputEvent& event,
const UIInputRouteContext& context);
template <typename HandlerFn>
static UIInputDispatchResult Dispatch(
const UIInputEvent& event,
const UIInputRouteContext& context,
HandlerFn&& handler) {
UIInputDispatchResult result = {};
result.plan = BuildRoutingPlan(event, context);
for (const UIInputRoutingStep& step : result.plan.steps) {
UIInputDispatchRequest request = {};
request.event = &event;
request.elementId = step.elementId;
request.phase = step.phase;
request.targetKind = result.plan.targetKind;
request.isTargetElement = step.isTargetElement;
const UIInputDispatchDecision decision = handler(request);
result.handled = result.handled || decision.handled;
if (decision.stopPropagation) {
break;
}
}
return result;
}
};
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,67 @@
#pragma once
#include "UIInputPath.h"
#include <XCEngine/UI/Types.h>
#include <cstdint>
#include <string>
#include <vector>
namespace XCEngine {
namespace UI {
enum class UIShortcutScope : std::uint8_t {
Global = 0,
Window,
Panel,
Widget
};
struct UIShortcutChord {
std::int32_t keyCode = 0;
UIInputModifiers modifiers = {};
bool allowRepeat = false;
};
struct UIShortcutBinding {
std::uint64_t bindingId = 0;
UIShortcutScope scope = UIShortcutScope::Global;
UIElementId ownerId = 0;
UIInputEventType triggerEventType = UIInputEventType::KeyDown;
UIShortcutChord chord = {};
std::string commandId = {};
};
struct UIShortcutContext {
UIInputPath focusedPath = {};
UIInputPath activePath = {};
UIInputPath hoveredPath = {};
};
struct UIShortcutMatch {
bool matched = false;
UIShortcutBinding binding = {};
};
class UIShortcutRegistry {
public:
std::uint64_t RegisterBinding(const UIShortcutBinding& binding);
bool UnregisterBinding(std::uint64_t bindingId);
void Clear();
const std::vector<UIShortcutBinding>& GetBindings() const {
return m_bindings;
}
UIShortcutMatch Match(
const UIInputEvent& event,
const UIShortcutContext& context) const;
private:
std::vector<UIShortcutBinding> m_bindings = {};
std::uint64_t m_nextBindingId = 1;
};
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,102 @@
#pragma once
#include <cstdint>
namespace XCEngine {
namespace UI {
struct UIPoint {
float x = 0.0f;
float y = 0.0f;
constexpr UIPoint() = default;
constexpr UIPoint(float xValue, float yValue)
: x(xValue)
, y(yValue) {
}
};
struct UISize {
float width = 0.0f;
float height = 0.0f;
constexpr UISize() = default;
constexpr UISize(float widthValue, float heightValue)
: width(widthValue)
, height(heightValue) {
}
};
struct UIRect {
float x = 0.0f;
float y = 0.0f;
float width = 0.0f;
float height = 0.0f;
constexpr UIRect() = default;
constexpr UIRect(float xValue, float yValue, float widthValue, float heightValue)
: x(xValue)
, y(yValue)
, width(widthValue)
, height(heightValue) {
}
};
struct UITextureHandle {
std::uintptr_t nativeHandle = 0;
std::uint32_t width = 0;
std::uint32_t height = 0;
constexpr bool IsValid() const {
return nativeHandle != 0 && width > 0 && height > 0;
}
};
enum class UIPointerButton : std::uint8_t {
None = 0,
Left,
Right,
Middle,
X1,
X2
};
enum class UIInputEventType : std::uint8_t {
None = 0,
PointerMove,
PointerEnter,
PointerLeave,
PointerButtonDown,
PointerButtonUp,
PointerWheel,
KeyDown,
KeyUp,
Character,
FocusGained,
FocusLost
};
struct UIInputModifiers {
bool shift = false;
bool control = false;
bool alt = false;
bool super = false;
};
struct UIInputEvent {
UIInputEventType type = UIInputEventType::None;
UIPoint position = {};
UIPoint delta = {};
float wheelDelta = 0.0f;
std::int32_t keyCode = 0;
std::uint32_t character = 0;
std::uint32_t pointerId = 0;
std::uint64_t timestampNanoseconds = 0;
UIPointerButton pointerButton = UIPointerButton::None;
UIInputModifiers modifiers = {};
bool repeat = false;
bool synthetic = false;
};
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,63 @@
#include <XCEngine/UI/Input/UIFocusController.h>
namespace XCEngine {
namespace UI {
namespace {
UIFocusChange BuildFocusChange(
const UIInputPath& previousPath,
const UIInputPath& currentPath) {
UIFocusChange change = {};
change.previousPath = previousPath;
change.currentPath = currentPath;
const std::size_t commonPrefixLength =
GetUIInputPathCommonPrefixLength(previousPath, currentPath);
change.lostPath = BuildUIInputPathSuffix(previousPath, commonPrefixLength);
change.gainedPath = BuildUIInputPathSuffix(currentPath, commonPrefixLength);
return change;
}
} // namespace
UIFocusChange UIFocusController::SetFocusedPath(const UIInputPath& path) {
const UIInputPath previousPath = m_focusedPath;
m_focusedPath = path;
return BuildFocusChange(previousPath, m_focusedPath);
}
UIFocusChange UIFocusController::ClearFocus() {
return SetFocusedPath({});
}
void UIFocusController::SetActivePath(const UIInputPath& path) {
m_activePath = path;
}
void UIFocusController::ClearActivePath() {
m_activePath.Clear();
}
void UIFocusController::SetPointerCapturePath(const UIInputPath& path) {
m_pointerCapturePath = path;
}
void UIFocusController::ClearPointerCapturePath() {
m_pointerCapturePath.Clear();
}
bool UIFocusController::IsFocused(UIElementId elementId) const {
return m_focusedPath.Contains(elementId);
}
bool UIFocusController::IsActive(UIElementId elementId) const {
return m_activePath.Contains(elementId);
}
bool UIFocusController::IsCapturingPointer(UIElementId elementId) const {
return m_pointerCapturePath.Contains(elementId);
}
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,34 @@
#include <XCEngine/UI/Input/UIInputDispatcher.h>
namespace XCEngine {
namespace UI {
bool UIInputDispatcher::ShouldTransferFocusOnPointerDown(
const UIInputEvent& event,
const UIInputPath& hoveredPath) const {
return m_options.pointerDownChangesFocus &&
event.type == UIInputEventType::PointerButtonDown &&
event.pointerButton == m_options.focusTransferButton &&
!hoveredPath.Empty();
}
bool UIInputDispatcher::ShouldStartActivePathOnPointerDown(
const UIInputEvent& event,
const UIInputPath& hoveredPath) const {
return m_options.pointerDownStartsActivePath &&
event.type == UIInputEventType::PointerButtonDown &&
!hoveredPath.Empty();
}
UIInputDispatchSummary UIInputDispatcher::FinalizeDispatch(
const UIInputEvent& event,
UIInputDispatchSummary&& summary) {
if (event.type == UIInputEventType::PointerButtonUp) {
m_focusController.ClearActivePath();
}
return std::move(summary);
}
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,45 @@
#include <XCEngine/UI/Input/UIInputPath.h>
#include <algorithm>
#include <cstddef>
namespace XCEngine {
namespace UI {
bool UIInputPath::Contains(UIElementId elementId) const {
return std::find(elements.begin(), elements.end(), elementId) != elements.end();
}
bool operator==(const UIInputPath& lhs, const UIInputPath& rhs) {
return lhs.elements == rhs.elements;
}
bool operator!=(const UIInputPath& lhs, const UIInputPath& rhs) {
return !(lhs == rhs);
}
std::size_t GetUIInputPathCommonPrefixLength(
const UIInputPath& lhs,
const UIInputPath& rhs) {
const std::size_t limit = (std::min)(lhs.elements.size(), rhs.elements.size());
std::size_t index = 0;
while (index < limit && lhs.elements[index] == rhs.elements[index]) {
++index;
}
return index;
}
UIInputPath BuildUIInputPathSuffix(
const UIInputPath& path,
std::size_t startIndex) {
UIInputPath suffix = {};
if (startIndex >= path.elements.size()) {
return suffix;
}
suffix.elements.assign(path.elements.begin() + static_cast<std::ptrdiff_t>(startIndex), path.elements.end());
return suffix;
}
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,86 @@
#include <XCEngine/UI/Input/UIInputRouter.h>
namespace XCEngine {
namespace UI {
namespace {
UIInputRoutingPlan BuildPlanForTargetPath(
UIInputTargetKind targetKind,
const UIInputPath& path) {
UIInputRoutingPlan plan = {};
plan.targetKind = targetKind;
plan.targetPath = path;
if (path.Empty()) {
return plan;
}
const std::size_t size = path.elements.size();
for (std::size_t index = 0; index + 1 < size; ++index) {
UIInputRoutingStep step = {};
step.elementId = path.elements[index];
step.phase = UIInputRoutingPhase::Capture;
plan.steps.push_back(step);
}
UIInputRoutingStep targetStep = {};
targetStep.elementId = path.Target();
targetStep.phase = UIInputRoutingPhase::Target;
targetStep.isTargetElement = true;
plan.steps.push_back(targetStep);
for (std::size_t index = size; index > 1; --index) {
UIInputRoutingStep bubbleStep = {};
bubbleStep.elementId = path.elements[index - 2];
bubbleStep.phase = UIInputRoutingPhase::Bubble;
plan.steps.push_back(bubbleStep);
}
return plan;
}
} // namespace
bool UIInputRouter::IsPointerEvent(UIInputEventType type) {
switch (type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerLeave:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
case UIInputEventType::PointerWheel:
return true;
default:
return false;
}
}
bool UIInputRouter::IsKeyboardEvent(UIInputEventType type) {
return type == UIInputEventType::KeyDown ||
type == UIInputEventType::KeyUp ||
type == UIInputEventType::Character;
}
UIInputRoutingPlan UIInputRouter::BuildRoutingPlan(
const UIInputEvent& event,
const UIInputRouteContext& context) {
if (IsPointerEvent(event.type)) {
if (!context.capturePath.Empty()) {
return BuildPlanForTargetPath(UIInputTargetKind::Captured, context.capturePath);
}
return BuildPlanForTargetPath(UIInputTargetKind::Hovered, context.hoveredPath);
}
if (IsKeyboardEvent(event.type) ||
event.type == UIInputEventType::FocusGained ||
event.type == UIInputEventType::FocusLost) {
return BuildPlanForTargetPath(UIInputTargetKind::Focused, context.focusedPath);
}
return {};
}
} // namespace UI
} // namespace XCEngine

View File

@@ -0,0 +1,121 @@
#include <XCEngine/UI/Input/UIShortcutRegistry.h>
namespace XCEngine {
namespace UI {
namespace {
const UIInputPath& ResolvePrimaryShortcutPath(const UIShortcutContext& context) {
if (!context.activePath.Empty()) {
return context.activePath;
}
if (!context.focusedPath.Empty()) {
return context.focusedPath;
}
return context.hoveredPath;
}
bool ModifiersEqual(
const UIInputModifiers& lhs,
const UIInputModifiers& rhs) {
return lhs.shift == rhs.shift &&
lhs.control == rhs.control &&
lhs.alt == rhs.alt &&
lhs.super == rhs.super;
}
bool IsBindingActive(
const UIShortcutBinding& binding,
const UIShortcutContext& context) {
if (binding.scope == UIShortcutScope::Global) {
return true;
}
if (binding.ownerId == 0) {
return false;
}
return ResolvePrimaryShortcutPath(context).Contains(binding.ownerId);
}
bool DoesBindingMatchEvent(
const UIShortcutBinding& binding,
const UIInputEvent& event) {
return binding.triggerEventType == event.type &&
binding.chord.keyCode == event.keyCode &&
ModifiersEqual(binding.chord.modifiers, event.modifiers) &&
(binding.chord.allowRepeat || !event.repeat);
}
int GetShortcutScopePriority(UIShortcutScope scope) {
switch (scope) {
case UIShortcutScope::Widget:
return 3;
case UIShortcutScope::Panel:
return 2;
case UIShortcutScope::Window:
return 1;
case UIShortcutScope::Global:
default:
return 0;
}
}
} // namespace
std::uint64_t UIShortcutRegistry::RegisterBinding(const UIShortcutBinding& binding) {
UIShortcutBinding storedBinding = binding;
storedBinding.bindingId = storedBinding.bindingId != 0 ? storedBinding.bindingId : m_nextBindingId++;
if (storedBinding.bindingId >= m_nextBindingId) {
m_nextBindingId = storedBinding.bindingId + 1;
}
m_bindings.push_back(storedBinding);
return m_bindings.back().bindingId;
}
bool UIShortcutRegistry::UnregisterBinding(std::uint64_t bindingId) {
for (auto it = m_bindings.begin(); it != m_bindings.end(); ++it) {
if (it->bindingId != bindingId) {
continue;
}
m_bindings.erase(it);
return true;
}
return false;
}
void UIShortcutRegistry::Clear() {
m_bindings.clear();
}
UIShortcutMatch UIShortcutRegistry::Match(
const UIInputEvent& event,
const UIShortcutContext& context) const {
UIShortcutMatch bestMatch = {};
int bestPriority = -1;
for (const UIShortcutBinding& binding : m_bindings) {
if (!DoesBindingMatchEvent(binding, event) || !IsBindingActive(binding, context)) {
continue;
}
const int priority = GetShortcutScopePriority(binding.scope);
if (!bestMatch.matched ||
priority > bestPriority ||
(priority == bestPriority && binding.bindingId > bestMatch.binding.bindingId)) {
bestMatch.matched = true;
bestMatch.binding = binding;
bestPriority = priority;
}
}
return bestMatch;
}
} // namespace UI
} // namespace XCEngine