Build XCUI splitter foundation and test harness

This commit is contained in:
2026-04-06 03:17:53 +08:00
parent dc17685099
commit c7dc8d7484
77 changed files with 4749 additions and 542 deletions

View File

@@ -1,8 +1,19 @@
#include <XCEngine/UI/Input/UIInputDispatcher.h>
#include <XCEngine/Input/InputTypes.h>
namespace XCEngine {
namespace UI {
namespace {
bool IsKeyboardActivationKey(std::int32_t keyCode) {
return keyCode == static_cast<std::int32_t>(Input::KeyCode::Enter) ||
keyCode == static_cast<std::int32_t>(Input::KeyCode::Space);
}
} // namespace
bool UIInputDispatcher::ShouldTransferFocusOnPointerDown(
const UIInputEvent& event,
const UIInputPath& hoveredPath) const {
@@ -20,10 +31,52 @@ bool UIInputDispatcher::ShouldStartActivePathOnPointerDown(
!hoveredPath.Empty();
}
bool UIInputDispatcher::ShouldStartActivePathOnKeyDown(
const UIInputEvent& event) const {
return event.type == UIInputEventType::KeyDown &&
!m_focusController.GetFocusedPath().Empty() &&
IsKeyboardActivationKey(event.keyCode);
}
bool UIInputDispatcher::ShouldClearActivePathOnKeyUp(
const UIInputEvent& event) const {
return event.type == UIInputEventType::KeyUp &&
IsKeyboardActivationKey(event.keyCode);
}
UIShortcutContext UIInputDispatcher::BuildEffectiveShortcutContext(
const UIInputPath& hoveredPath) const {
UIShortcutContext context = m_shortcutContext;
context.focusedPath = m_focusController.GetFocusedPath();
context.activePath = m_focusController.GetActivePath();
context.hoveredPath = hoveredPath;
if (context.commandScope.path.Empty()) {
context.commandScope.path = !context.focusedPath.Empty()
? context.focusedPath
: context.activePath;
}
if (context.commandScope.widgetId == 0 &&
!context.commandScope.path.Empty()) {
context.commandScope.widgetId = context.commandScope.path.Target();
}
return context;
}
bool UIInputDispatcher::ShouldSuppressShortcutMatch(
const UIInputEvent&,
const UIShortcutMatch& shortcutMatch,
const UIShortcutContext& shortcutContext) const {
return shortcutMatch.matched && shortcutContext.textInputActive;
}
UIInputDispatchSummary UIInputDispatcher::FinalizeDispatch(
const UIInputEvent& event,
UIInputDispatchSummary&& summary) {
if (event.type == UIInputEventType::PointerButtonUp) {
if (event.type == UIInputEventType::PointerButtonUp ||
ShouldClearActivePathOnKeyUp(event)) {
m_focusController.ClearActivePath();
}

View File

@@ -6,14 +6,18 @@ namespace UI {
namespace {
const UIInputPath& ResolvePrimaryShortcutPath(const UIShortcutContext& context) {
if (!context.activePath.Empty()) {
return context.activePath;
if (!context.commandScope.path.Empty()) {
return context.commandScope.path;
}
if (!context.focusedPath.Empty()) {
return context.focusedPath;
}
if (!context.activePath.Empty()) {
return context.activePath;
}
return context.hoveredPath;
}
@@ -26,6 +30,28 @@ bool ModifiersEqual(
lhs.super == rhs.super;
}
UIElementId ResolveScopedOwnerId(
UIShortcutScope scope,
const UIShortcutContext& context) {
switch (scope) {
case UIShortcutScope::Window:
return context.commandScope.windowId;
case UIShortcutScope::Panel:
return context.commandScope.panelId;
case UIShortcutScope::Widget:
if (context.commandScope.widgetId != 0) {
return context.commandScope.widgetId;
}
return context.commandScope.path.Empty()
? 0
: context.commandScope.path.Target();
case UIShortcutScope::Global:
default:
return 0;
}
}
bool IsBindingActive(
const UIShortcutBinding& binding,
const UIShortcutContext& context) {
@@ -37,6 +63,11 @@ bool IsBindingActive(
return false;
}
const UIElementId resolvedOwnerId = ResolveScopedOwnerId(binding.scope, context);
if (resolvedOwnerId != 0) {
return binding.ownerId == resolvedOwnerId;
}
return ResolvePrimaryShortcutPath(context).Contains(binding.ownerId);
}
@@ -91,6 +122,7 @@ bool UIShortcutRegistry::UnregisterBinding(std::uint64_t bindingId) {
void UIShortcutRegistry::Clear() {
m_bindings.clear();
m_nextBindingId = 1;
}
UIShortcutMatch UIShortcutRegistry::Match(

File diff suppressed because it is too large Load Diff

View File

@@ -1,115 +0,0 @@
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
namespace XCEngine {
namespace UI {
namespace Widgets {
namespace {
float ResolveFloatToken(
const Style::UITheme& theme,
const char* tokenName,
float fallbackValue) {
const Style::UITokenResolveResult result =
theme.ResolveToken(tokenName, Style::UIStyleValueType::Float);
if (result.status != Style::UITokenResolveStatus::Resolved) {
return fallbackValue;
}
const float* value = result.value.TryGetFloat();
return value != nullptr ? *value : fallbackValue;
}
} // namespace
UIEditorCollectionPrimitiveKind ClassifyUIEditorCollectionPrimitive(std::string_view tagName) {
if (tagName == "ScrollView") {
return UIEditorCollectionPrimitiveKind::ScrollView;
}
if (tagName == "TreeView") {
return UIEditorCollectionPrimitiveKind::TreeView;
}
if (tagName == "TreeItem") {
return UIEditorCollectionPrimitiveKind::TreeItem;
}
if (tagName == "ListView") {
return UIEditorCollectionPrimitiveKind::ListView;
}
if (tagName == "ListItem") {
return UIEditorCollectionPrimitiveKind::ListItem;
}
if (tagName == "PropertySection") {
return UIEditorCollectionPrimitiveKind::PropertySection;
}
if (tagName == "FieldRow") {
return UIEditorCollectionPrimitiveKind::FieldRow;
}
return UIEditorCollectionPrimitiveKind::None;
}
bool IsUIEditorCollectionPrimitiveContainer(UIEditorCollectionPrimitiveKind kind) {
return kind == UIEditorCollectionPrimitiveKind::ScrollView ||
kind == UIEditorCollectionPrimitiveKind::TreeView ||
kind == UIEditorCollectionPrimitiveKind::ListView ||
kind == UIEditorCollectionPrimitiveKind::PropertySection;
}
bool UsesUIEditorCollectionPrimitiveColumnLayout(UIEditorCollectionPrimitiveKind kind) {
return kind == UIEditorCollectionPrimitiveKind::TreeView ||
kind == UIEditorCollectionPrimitiveKind::ListView ||
kind == UIEditorCollectionPrimitiveKind::PropertySection;
}
bool IsUIEditorCollectionPrimitiveHoverable(UIEditorCollectionPrimitiveKind kind) {
return kind == UIEditorCollectionPrimitiveKind::TreeItem ||
kind == UIEditorCollectionPrimitiveKind::ListItem ||
kind == UIEditorCollectionPrimitiveKind::PropertySection ||
kind == UIEditorCollectionPrimitiveKind::FieldRow;
}
bool DoesUIEditorCollectionPrimitiveClipChildren(UIEditorCollectionPrimitiveKind kind) {
return kind == UIEditorCollectionPrimitiveKind::ScrollView ||
kind == UIEditorCollectionPrimitiveKind::TreeView ||
kind == UIEditorCollectionPrimitiveKind::ListView;
}
float ResolveUIEditorCollectionPrimitivePadding(
UIEditorCollectionPrimitiveKind kind,
const Style::UITheme& theme) {
return kind == UIEditorCollectionPrimitiveKind::TreeView ||
kind == UIEditorCollectionPrimitiveKind::ListView ||
kind == UIEditorCollectionPrimitiveKind::PropertySection
? ResolveFloatToken(theme, "space.cardInset", 12.0f)
: 0.0f;
}
float ResolveUIEditorCollectionPrimitiveDefaultHeight(
UIEditorCollectionPrimitiveKind kind,
const Style::UITheme& theme) {
switch (kind) {
case UIEditorCollectionPrimitiveKind::TreeItem:
return ResolveFloatToken(theme, "size.treeItemHeight", 28.0f);
case UIEditorCollectionPrimitiveKind::ListItem:
return ResolveFloatToken(theme, "size.listItemHeight", 60.0f);
case UIEditorCollectionPrimitiveKind::FieldRow:
return ResolveFloatToken(theme, "size.fieldRowHeight", 32.0f);
case UIEditorCollectionPrimitiveKind::PropertySection:
return ResolveFloatToken(theme, "size.propertySectionHeight", 148.0f);
default:
return 0.0f;
}
}
float ResolveUIEditorCollectionPrimitiveIndent(
UIEditorCollectionPrimitiveKind kind,
const Style::UITheme& theme,
float indentLevel) {
return kind == UIEditorCollectionPrimitiveKind::TreeItem
? indentLevel * ResolveFloatToken(theme, "size.treeIndent", 18.0f)
: 0.0f;
}
} // namespace Widgets
} // namespace UI
} // namespace XCEngine