feat(xcui): add editor command and menu foundations

This commit is contained in:
2026-04-06 18:05:34 +08:00
parent 4afeb19d25
commit f912e81ade
29 changed files with 3581 additions and 61 deletions

View File

@@ -2,9 +2,13 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
test_editor_shell_asset_validation.cpp
test_input_modifier_tracker.cpp
test_structured_editor_shell.cpp
test_ui_editor_command_dispatcher.cpp
test_ui_editor_command_registry.cpp
test_ui_editor_menu_model.cpp
test_ui_editor_panel_registry.cpp
test_ui_editor_collection_primitives.cpp
test_ui_editor_panel_chrome.cpp
test_ui_editor_shortcut_manager.cpp
test_ui_editor_workspace_controller.cpp
test_ui_editor_workspace_layout_persistence.cpp
test_ui_editor_workspace_model.cpp

View File

@@ -0,0 +1,118 @@
#include <gtest/gtest.h>
#include <XCNewEditor/Editor/UIEditorCommandDispatcher.h>
namespace {
using XCEngine::NewEditor::BuildDefaultUIEditorWorkspaceController;
using XCEngine::NewEditor::BuildUIEditorWorkspacePanel;
using XCEngine::NewEditor::BuildUIEditorWorkspaceSplit;
using XCEngine::NewEditor::BuildUIEditorWorkspaceTabStack;
using XCEngine::NewEditor::GetUIEditorCommandDispatchStatusName;
using XCEngine::NewEditor::UIEditorCommandDispatchStatus;
using XCEngine::NewEditor::UIEditorCommandDispatcher;
using XCEngine::NewEditor::UIEditorCommandEvaluationCode;
using XCEngine::NewEditor::UIEditorCommandPanelSource;
using XCEngine::NewEditor::UIEditorCommandRegistry;
using XCEngine::NewEditor::UIEditorPanelRegistry;
using XCEngine::NewEditor::UIEditorWorkspaceCommandKind;
using XCEngine::NewEditor::UIEditorWorkspaceCommandStatus;
using XCEngine::NewEditor::UIEditorWorkspaceModel;
using XCEngine::NewEditor::UIEditorWorkspaceSplitAxis;
UIEditorCommandRegistry BuildCommandRegistry() {
UIEditorCommandRegistry registry = {};
registry.commands = {
{
"workspace.hide_active",
"Hide Active",
{ UIEditorWorkspaceCommandKind::HidePanel, UIEditorCommandPanelSource::ActivePanel, {} }
},
{
"workspace.activate_details",
"Activate Details",
{ UIEditorWorkspaceCommandKind::ActivatePanel, UIEditorCommandPanelSource::FixedPanelId, "details" }
},
{
"workspace.reset",
"Reset Workspace",
{ UIEditorWorkspaceCommandKind::ResetWorkspace, UIEditorCommandPanelSource::None, {} }
}
};
return registry;
}
UIEditorPanelRegistry BuildPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "doc-a", "Document A", {}, true, true, true },
{ "doc-b", "Document B", {}, true, true, true },
{ "details", "Details", {}, true, true, true }
};
return registry;
}
UIEditorWorkspaceModel BuildWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root-split",
UIEditorWorkspaceSplitAxis::Horizontal,
0.66f,
BuildUIEditorWorkspaceTabStack(
"document-tabs",
{
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true),
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
},
0u),
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
workspace.activePanelId = "doc-a";
return workspace;
}
} // namespace
TEST(UIEditorCommandDispatcherTest, EvaluateResolvesActivePanelCommandToCurrentWorkspaceTarget) {
UIEditorCommandDispatcher dispatcher(BuildCommandRegistry());
const auto controller =
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
const auto evaluation = dispatcher.Evaluate("workspace.hide_active", controller);
EXPECT_EQ(evaluation.code, UIEditorCommandEvaluationCode::None);
EXPECT_TRUE(evaluation.executable);
EXPECT_EQ(evaluation.commandId, "workspace.hide_active");
EXPECT_EQ(evaluation.workspaceCommand.kind, UIEditorWorkspaceCommandKind::HidePanel);
EXPECT_EQ(evaluation.workspaceCommand.panelId, "doc-a");
EXPECT_EQ(evaluation.previewResult.status, UIEditorWorkspaceCommandStatus::Changed);
}
TEST(UIEditorCommandDispatcherTest, EvaluateRejectsActivePanelCommandWhenWorkspaceHasNoActivePanel) {
UIEditorCommandDispatcher dispatcher(BuildCommandRegistry());
UIEditorWorkspaceModel workspace = BuildWorkspace();
workspace.activePanelId.clear();
const auto controller =
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), workspace);
const auto evaluation = dispatcher.Evaluate("workspace.hide_active", controller);
EXPECT_EQ(evaluation.code, UIEditorCommandEvaluationCode::MissingActivePanel);
EXPECT_FALSE(evaluation.executable);
EXPECT_EQ(evaluation.previewResult.status, UIEditorWorkspaceCommandStatus::Rejected);
}
TEST(UIEditorCommandDispatcherTest, DispatchUsesResolvedWorkspaceCommandAndMutatesController) {
UIEditorCommandDispatcher dispatcher(BuildCommandRegistry());
auto controller =
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
const auto result = dispatcher.Dispatch("workspace.activate_details", controller);
EXPECT_EQ(result.status, UIEditorCommandDispatchStatus::Dispatched);
EXPECT_STREQ(GetUIEditorCommandDispatchStatusName(result.status).data(), "Dispatched");
EXPECT_TRUE(result.commandExecuted);
EXPECT_EQ(result.commandId, "workspace.activate_details");
EXPECT_EQ(result.commandResult.status, UIEditorWorkspaceCommandStatus::Changed);
EXPECT_EQ(controller.GetWorkspace().activePanelId, "details");
}

View File

@@ -0,0 +1,75 @@
#include <gtest/gtest.h>
#include <XCNewEditor/Editor/UIEditorCommandRegistry.h>
namespace {
using XCEngine::NewEditor::FindUIEditorCommandDescriptor;
using XCEngine::NewEditor::UIEditorCommandDescriptor;
using XCEngine::NewEditor::UIEditorCommandPanelSource;
using XCEngine::NewEditor::UIEditorCommandRegistry;
using XCEngine::NewEditor::UIEditorCommandRegistryValidationCode;
using XCEngine::NewEditor::UIEditorWorkspaceCommandKind;
using XCEngine::NewEditor::ValidateUIEditorCommandRegistry;
UIEditorCommandRegistry BuildCommandRegistry() {
UIEditorCommandRegistry registry = {};
registry.commands = {
{
"workspace.hide_active",
"Hide Active",
{ UIEditorWorkspaceCommandKind::HidePanel, UIEditorCommandPanelSource::ActivePanel, {} }
},
{
"workspace.activate_details",
"Activate Details",
{ UIEditorWorkspaceCommandKind::ActivatePanel, UIEditorCommandPanelSource::FixedPanelId, "details" }
},
{
"workspace.reset",
"Reset Workspace",
{ UIEditorWorkspaceCommandKind::ResetWorkspace, UIEditorCommandPanelSource::None, {} }
}
};
return registry;
}
} // namespace
TEST(UIEditorCommandRegistryTest, FindDescriptorReturnsRegisteredCommand) {
const UIEditorCommandRegistry registry = BuildCommandRegistry();
const UIEditorCommandDescriptor* descriptor =
FindUIEditorCommandDescriptor(registry, "workspace.activate_details");
ASSERT_NE(descriptor, nullptr);
EXPECT_EQ(descriptor->displayName, "Activate Details");
}
TEST(UIEditorCommandRegistryTest, ValidationRejectsDuplicateCommandIdsAndMissingDisplayName) {
UIEditorCommandRegistry registry = BuildCommandRegistry();
registry.commands.push_back(registry.commands.front());
auto validation = ValidateUIEditorCommandRegistry(registry);
EXPECT_EQ(validation.code, UIEditorCommandRegistryValidationCode::DuplicateCommandId);
registry = BuildCommandRegistry();
registry.commands[1].displayName.clear();
validation = ValidateUIEditorCommandRegistry(registry);
EXPECT_EQ(validation.code, UIEditorCommandRegistryValidationCode::EmptyDisplayName);
}
TEST(UIEditorCommandRegistryTest, ValidationRejectsInvalidPanelSourceUsage) {
UIEditorCommandRegistry registry = BuildCommandRegistry();
registry.commands[0].workspaceCommand.panelSource = UIEditorCommandPanelSource::None;
auto validation = ValidateUIEditorCommandRegistry(registry);
EXPECT_EQ(validation.code, UIEditorCommandRegistryValidationCode::MissingPanelSource);
registry = BuildCommandRegistry();
registry.commands[2].workspaceCommand.panelSource =
UIEditorCommandPanelSource::FixedPanelId;
registry.commands[2].workspaceCommand.panelId = "details";
validation = ValidateUIEditorCommandRegistry(registry);
EXPECT_EQ(validation.code, UIEditorCommandRegistryValidationCode::UnexpectedPanelSource);
}

View File

@@ -0,0 +1,223 @@
#include <gtest/gtest.h>
#include <XCNewEditor/Editor/UIEditorMenuModel.h>
#include <XCNewEditor/Editor/UIEditorShortcutManager.h>
#include <XCEngine/Input/InputTypes.h>
namespace {
using XCEngine::Input::KeyCode;
using XCEngine::NewEditor::BuildDefaultUIEditorWorkspaceController;
using XCEngine::NewEditor::BuildUIEditorWorkspacePanel;
using XCEngine::NewEditor::BuildUIEditorWorkspaceSplit;
using XCEngine::NewEditor::BuildUIEditorWorkspaceTabStack;
using XCEngine::NewEditor::BuildUIEditorResolvedMenuModel;
using XCEngine::NewEditor::UIEditorCommandDispatcher;
using XCEngine::NewEditor::UIEditorCommandPanelSource;
using XCEngine::NewEditor::UIEditorCommandRegistry;
using XCEngine::NewEditor::UIEditorMenuCheckedStateSource;
using XCEngine::NewEditor::UIEditorMenuItemDescriptor;
using XCEngine::NewEditor::UIEditorMenuItemKind;
using XCEngine::NewEditor::UIEditorMenuModel;
using XCEngine::NewEditor::UIEditorMenuModelValidationCode;
using XCEngine::NewEditor::UIEditorPanelRegistry;
using XCEngine::NewEditor::UIEditorShortcutManager;
using XCEngine::NewEditor::UIEditorWorkspaceCommandKind;
using XCEngine::NewEditor::UIEditorWorkspaceCommandStatus;
using XCEngine::NewEditor::UIEditorWorkspaceModel;
using XCEngine::NewEditor::UIEditorWorkspaceSplitAxis;
using XCEngine::NewEditor::ValidateUIEditorMenuModel;
using XCEngine::UI::UIInputEventType;
using XCEngine::UI::UIShortcutBinding;
using XCEngine::UI::UIShortcutScope;
UIEditorCommandRegistry BuildCommandRegistry() {
UIEditorCommandRegistry registry = {};
registry.commands = {
{
"workspace.show_details",
"Show Details",
{ UIEditorWorkspaceCommandKind::ShowPanel, UIEditorCommandPanelSource::FixedPanelId, "details" }
},
{
"workspace.hide_active",
"Hide Active",
{ UIEditorWorkspaceCommandKind::HidePanel, UIEditorCommandPanelSource::ActivePanel, {} }
},
{
"workspace.reset",
"Reset Workspace",
{ UIEditorWorkspaceCommandKind::ResetWorkspace, UIEditorCommandPanelSource::None, {} }
}
};
return registry;
}
UIEditorPanelRegistry BuildPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "doc-a", "Document A", {}, true, true, true },
{ "doc-b", "Document B", {}, true, true, true },
{ "details", "Details", {}, true, true, true }
};
return registry;
}
UIEditorWorkspaceModel BuildWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root-split",
UIEditorWorkspaceSplitAxis::Horizontal,
0.66f,
BuildUIEditorWorkspaceTabStack(
"document-tabs",
{
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true),
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
},
0u),
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
workspace.activePanelId = "doc-a";
return workspace;
}
UIShortcutBinding MakeBinding(
std::string commandId,
UIShortcutScope scope,
XCEngine::UI::UIElementId ownerId,
KeyCode keyCode) {
UIShortcutBinding binding = {};
binding.commandId = std::move(commandId);
binding.scope = scope;
binding.ownerId = ownerId;
binding.triggerEventType = UIInputEventType::KeyDown;
binding.chord.keyCode = static_cast<std::int32_t>(keyCode);
binding.chord.modifiers.control = true;
return binding;
}
UIEditorMenuModel BuildMenuModel() {
UIEditorMenuModel model = {};
model.menus = {
{
"window",
"Window",
{
{
UIEditorMenuItemKind::Command,
"show-details",
{},
"workspace.show_details",
{ UIEditorMenuCheckedStateSource::PanelVisible, "details" },
{}
},
{
UIEditorMenuItemKind::Separator,
"separator-1",
{},
{},
{},
{}
},
{
UIEditorMenuItemKind::Submenu,
"layout",
"Layout",
{},
{},
{
{
UIEditorMenuItemKind::Command,
"reset-layout",
{},
"workspace.reset",
{},
{}
}
}
}
}
}
};
return model;
}
} // namespace
TEST(UIEditorMenuModelTest, ValidationRejectsUnknownCommandAndInvalidSubmenuShape) {
UIEditorMenuModel model = BuildMenuModel();
model.menus[0].items[0].commandId = "missing.command";
auto validation = ValidateUIEditorMenuModel(model, BuildCommandRegistry());
EXPECT_EQ(validation.code, UIEditorMenuModelValidationCode::UnknownCommandId);
model = BuildMenuModel();
model.menus[0].items[2].children.clear();
validation = ValidateUIEditorMenuModel(model, BuildCommandRegistry());
EXPECT_EQ(validation.code, UIEditorMenuModelValidationCode::SubmenuEmptyChildren);
}
TEST(UIEditorMenuModelTest, ResolvedModelUsesCommandDisplayNameShortcutTextAndCheckedState) {
UIEditorShortcutManager shortcutManager(BuildCommandRegistry());
shortcutManager.RegisterBinding(
MakeBinding("workspace.reset", UIShortcutScope::Panel, 22u, KeyCode::P));
shortcutManager.RegisterBinding(
MakeBinding("workspace.reset", UIShortcutScope::Global, 0u, KeyCode::R));
const auto controller =
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
const UIEditorCommandDispatcher dispatcher(BuildCommandRegistry());
const auto resolved = BuildUIEditorResolvedMenuModel(
BuildMenuModel(),
dispatcher,
controller,
&shortcutManager);
ASSERT_EQ(resolved.menus.size(), 1u);
ASSERT_EQ(resolved.menus[0].items.size(), 3u);
EXPECT_EQ(resolved.menus[0].items[0].label, "Show Details");
EXPECT_TRUE(resolved.menus[0].items[0].checked);
const auto& submenu = resolved.menus[0].items[2];
ASSERT_EQ(submenu.children.size(), 1u);
EXPECT_EQ(submenu.children[0].label, "Reset Workspace");
EXPECT_EQ(submenu.children[0].shortcutText, "Ctrl+R");
EXPECT_TRUE(submenu.children[0].enabled);
}
TEST(UIEditorMenuModelTest, ResolvedModelDisablesCommandWhenEvaluationCannotResolveActivePanel) {
UIEditorMenuModel model = {};
model.menus = {
{
"window",
"Window",
{
{
UIEditorMenuItemKind::Command,
"hide-active",
{},
"workspace.hide_active",
{},
{}
}
}
}
};
UIEditorWorkspaceModel workspace = BuildWorkspace();
workspace.activePanelId.clear();
const auto controller =
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), workspace);
const UIEditorCommandDispatcher dispatcher(BuildCommandRegistry());
const auto resolved = BuildUIEditorResolvedMenuModel(model, dispatcher, controller);
ASSERT_EQ(resolved.menus.size(), 1u);
ASSERT_EQ(resolved.menus[0].items.size(), 1u);
EXPECT_FALSE(resolved.menus[0].items[0].enabled);
EXPECT_EQ(
resolved.menus[0].items[0].previewStatus,
UIEditorWorkspaceCommandStatus::Rejected);
}

View File

@@ -0,0 +1,194 @@
#include <gtest/gtest.h>
#include <XCNewEditor/Editor/UIEditorShortcutManager.h>
#include <XCEngine/Input/InputTypes.h>
namespace {
using XCEngine::Input::KeyCode;
using XCEngine::NewEditor::BuildDefaultUIEditorWorkspaceController;
using XCEngine::NewEditor::BuildUIEditorWorkspacePanel;
using XCEngine::NewEditor::BuildUIEditorWorkspaceSplit;
using XCEngine::NewEditor::BuildUIEditorWorkspaceTabStack;
using XCEngine::NewEditor::UIEditorCommandPanelSource;
using XCEngine::NewEditor::UIEditorCommandRegistry;
using XCEngine::NewEditor::UIEditorShortcutDispatchStatus;
using XCEngine::NewEditor::UIEditorShortcutManager;
using XCEngine::NewEditor::UIEditorShortcutManagerValidationCode;
using XCEngine::NewEditor::UIEditorWorkspaceCommandKind;
using XCEngine::NewEditor::UIEditorWorkspaceCommandStatus;
using XCEngine::NewEditor::UIEditorWorkspaceModel;
using XCEngine::NewEditor::UIEditorWorkspaceSplitAxis;
using XCEngine::UI::UIInputEvent;
using XCEngine::UI::UIInputEventType;
using XCEngine::UI::UIShortcutBinding;
using XCEngine::UI::UIShortcutContext;
using XCEngine::UI::UIShortcutScope;
UIEditorCommandRegistry BuildCommandRegistry() {
UIEditorCommandRegistry registry = {};
registry.commands = {
{
"workspace.hide_active",
"Hide Active",
{ UIEditorWorkspaceCommandKind::HidePanel, UIEditorCommandPanelSource::ActivePanel, {} }
},
{
"workspace.activate_details",
"Activate Details",
{ UIEditorWorkspaceCommandKind::ActivatePanel, UIEditorCommandPanelSource::FixedPanelId, "details" }
},
{
"workspace.reset",
"Reset Workspace",
{ UIEditorWorkspaceCommandKind::ResetWorkspace, UIEditorCommandPanelSource::None, {} }
}
};
return registry;
}
UIEditorWorkspaceModel BuildWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root-split",
UIEditorWorkspaceSplitAxis::Horizontal,
0.66f,
BuildUIEditorWorkspaceTabStack(
"document-tabs",
{
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true),
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
},
0u),
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
workspace.activePanelId = "doc-a";
return workspace;
}
UIShortcutBinding MakeBinding(
std::string commandId,
UIShortcutScope scope,
XCEngine::UI::UIElementId ownerId,
KeyCode keyCode,
bool control = true) {
UIShortcutBinding binding = {};
binding.commandId = std::move(commandId);
binding.scope = scope;
binding.ownerId = ownerId;
binding.triggerEventType = UIInputEventType::KeyDown;
binding.chord.keyCode = static_cast<std::int32_t>(keyCode);
binding.chord.modifiers.control = control;
return binding;
}
UIInputEvent MakeCtrlKeyEvent(KeyCode keyCode) {
UIInputEvent event = {};
event.type = UIInputEventType::KeyDown;
event.keyCode = static_cast<std::int32_t>(keyCode);
event.modifiers.control = true;
return event;
}
UIShortcutContext BuildPanelShortcutContext(XCEngine::UI::UIElementId panelOwnerId) {
UIShortcutContext context = {};
context.commandScope.path = { 10u, panelOwnerId, 30u };
context.commandScope.windowId = 10u;
context.commandScope.panelId = panelOwnerId;
context.commandScope.widgetId = 30u;
return context;
}
} // namespace
TEST(UIEditorShortcutManagerTest, ValidationRejectsUnknownCommandAndConflictingChord) {
UIEditorShortcutManager manager(BuildCommandRegistry());
manager.RegisterBinding(MakeBinding("missing.command", UIShortcutScope::Global, 0u, KeyCode::H));
auto validation = manager.ValidateConfiguration();
EXPECT_EQ(validation.code, UIEditorShortcutManagerValidationCode::UnknownCommandId);
manager = UIEditorShortcutManager(BuildCommandRegistry());
manager.RegisterBinding(MakeBinding("workspace.hide_active", UIShortcutScope::Global, 0u, KeyCode::H));
manager.RegisterBinding(MakeBinding("workspace.reset", UIShortcutScope::Global, 0u, KeyCode::H));
validation = manager.ValidateConfiguration();
EXPECT_EQ(validation.code, UIEditorShortcutManagerValidationCode::ConflictingBinding);
}
TEST(UIEditorShortcutManagerTest, DispatchUsesActivePanelSourceForWorkspaceCommand) {
UIEditorShortcutManager manager(BuildCommandRegistry());
manager.RegisterBinding(MakeBinding("workspace.hide_active", UIShortcutScope::Global, 0u, KeyCode::H));
auto controller =
BuildDefaultUIEditorWorkspaceController(
XCEngine::NewEditor::UIEditorPanelRegistry{
{
{ "doc-a", "Document A", {}, true, true, true },
{ "doc-b", "Document B", {}, true, true, true },
{ "details", "Details", {}, true, true, true }
}
},
BuildWorkspace());
const auto result =
manager.Dispatch(MakeCtrlKeyEvent(KeyCode::H), {}, controller);
EXPECT_EQ(result.status, UIEditorShortcutDispatchStatus::Dispatched);
EXPECT_TRUE(result.commandExecuted);
EXPECT_EQ(result.commandId, "workspace.hide_active");
EXPECT_EQ(result.commandResult.status, UIEditorWorkspaceCommandStatus::Changed);
EXPECT_EQ(controller.GetWorkspace().activePanelId, "doc-b");
}
TEST(UIEditorShortcutManagerTest, DispatchPrefersPanelScopeBindingOverGlobalBinding) {
UIEditorShortcutManager manager(BuildCommandRegistry());
manager.RegisterBinding(MakeBinding("workspace.reset", UIShortcutScope::Global, 0u, KeyCode::P));
manager.RegisterBinding(MakeBinding("workspace.activate_details", UIShortcutScope::Panel, 20u, KeyCode::P));
auto controller =
BuildDefaultUIEditorWorkspaceController(
XCEngine::NewEditor::UIEditorPanelRegistry{
{
{ "doc-a", "Document A", {}, true, true, true },
{ "doc-b", "Document B", {}, true, true, true },
{ "details", "Details", {}, true, true, true }
}
},
BuildWorkspace());
const auto result = manager.Dispatch(
MakeCtrlKeyEvent(KeyCode::P),
BuildPanelShortcutContext(20u),
controller);
EXPECT_EQ(result.status, UIEditorShortcutDispatchStatus::Dispatched);
EXPECT_EQ(result.commandId, "workspace.activate_details");
EXPECT_EQ(result.commandResult.status, UIEditorWorkspaceCommandStatus::Changed);
EXPECT_EQ(controller.GetWorkspace().activePanelId, "details");
}
TEST(UIEditorShortcutManagerTest, DispatchSuppressesMatchedShortcutWhenTextInputIsActive) {
UIEditorShortcutManager manager(BuildCommandRegistry());
manager.RegisterBinding(MakeBinding("workspace.hide_active", UIShortcutScope::Global, 0u, KeyCode::H));
auto controller =
BuildDefaultUIEditorWorkspaceController(
XCEngine::NewEditor::UIEditorPanelRegistry{
{
{ "doc-a", "Document A", {}, true, true, true },
{ "doc-b", "Document B", {}, true, true, true },
{ "details", "Details", {}, true, true, true }
}
},
BuildWorkspace());
UIShortcutContext context = {};
context.textInputActive = true;
const auto result =
manager.Dispatch(MakeCtrlKeyEvent(KeyCode::H), context, controller);
EXPECT_EQ(result.status, UIEditorShortcutDispatchStatus::Suppressed);
EXPECT_FALSE(result.commandExecuted);
EXPECT_EQ(controller.GetWorkspace().activePanelId, "doc-a");
}