#include "ProductShellAsset.h" #include #include #include namespace XCEngine::UI::Editor::App { namespace { using XCEngine::Input::KeyCode; using XCEngine::UI::UIInputEventType; using XCEngine::UI::UIShortcutBinding; using XCEngine::UI::UIShortcutScope; using Widgets::UIEditorStatusBarSegment; using Widgets::UIEditorStatusBarSlot; using Widgets::UIEditorStatusBarTextTone; UIEditorPanelRegistry BuildPanelRegistry() { UIEditorPanelRegistry registry = {}; registry.panels = { { "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::HostedContent, true, false, false }, { "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, { "game", "Game", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, { "inspector", "Inspector", UIEditorPanelPresentationKind::HostedContent, true, false, false }, { "console", "Console", UIEditorPanelPresentationKind::HostedContent, true, false, false }, { "project", "Project", UIEditorPanelPresentationKind::HostedContent, false, false, false } }; return registry; } UIEditorWorkspaceModel BuildWorkspace() { UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSplit( "workspace-root", UIEditorWorkspaceSplitAxis::Vertical, 0.75f, BuildUIEditorWorkspaceSplit( "workspace-top", UIEditorWorkspaceSplitAxis::Horizontal, 0.7875f, BuildUIEditorWorkspaceSplit( "workspace-main", UIEditorWorkspaceSplitAxis::Horizontal, 0.19047619f, BuildUIEditorWorkspaceSingleTabStack( "hierarchy-panel", "hierarchy", "Hierarchy", true), BuildUIEditorWorkspaceTabStack( "center-tabs", { BuildUIEditorWorkspacePanel( "scene-panel", "scene", "Scene", false), BuildUIEditorWorkspacePanel( "game-panel", "game", "Game", false) }, 0u)), BuildUIEditorWorkspaceSingleTabStack( "inspector-panel", "inspector", "Inspector", true)), BuildUIEditorWorkspaceTabStack( "bottom-tabs", { BuildUIEditorWorkspacePanel( "console-panel", "console", "Console", true), BuildUIEditorWorkspacePanel( "project-panel", "project", "Project", false) }, 1u)); workspace.activePanelId = "scene"; return workspace; } UIEditorCommandDescriptor BuildHostCommand( std::string commandId, std::string displayName) { UIEditorCommandDescriptor command = {}; command.commandId = std::move(commandId); command.displayName = std::move(displayName); command.kind = UIEditorCommandKind::Host; command.workspaceCommand.panelSource = UIEditorCommandPanelSource::None; command.workspaceCommand.panelId.clear(); return command; } UIEditorCommandDescriptor BuildWorkspaceCommand( std::string commandId, std::string displayName, UIEditorWorkspaceCommandKind kind, std::string panelId = {}) { UIEditorCommandDescriptor command = {}; command.commandId = std::move(commandId); command.displayName = std::move(displayName); command.workspaceCommand.kind = kind; if (kind == UIEditorWorkspaceCommandKind::ResetWorkspace) { command.workspaceCommand.panelSource = UIEditorCommandPanelSource::None; } else { command.workspaceCommand.panelSource = UIEditorCommandPanelSource::FixedPanelId; command.workspaceCommand.panelId = std::move(panelId); } return command; } UIEditorCommandRegistry BuildCommandRegistry() { UIEditorCommandRegistry registry = {}; registry.commands = { BuildHostCommand("file.new_project", "New Project..."), BuildHostCommand("file.open_project", "Open Project..."), BuildHostCommand("file.save_project", "Save Project"), BuildHostCommand("file.new_scene", "New Scene"), BuildHostCommand("file.open_scene", "Open Scene"), BuildHostCommand("file.save_scene", "Save Scene"), BuildHostCommand("file.save_scene_as", "Save Scene As..."), BuildHostCommand("file.exit", "Exit"), BuildHostCommand("edit.undo", "Undo"), BuildHostCommand("edit.redo", "Redo"), BuildHostCommand("edit.cut", "Cut"), BuildHostCommand("edit.copy", "Copy"), BuildHostCommand("edit.paste", "Paste"), BuildHostCommand("edit.duplicate", "Duplicate"), BuildHostCommand("edit.delete", "Delete"), BuildHostCommand("edit.rename", "Rename"), BuildHostCommand("assets.reimport_selected", "Reimport Selected Asset"), BuildHostCommand("assets.reimport_all", "Reimport All Assets"), BuildHostCommand("assets.clear_library", "Clear Library"), BuildHostCommand("run.play", "Play"), BuildHostCommand("run.pause", "Pause"), BuildHostCommand("run.step", "Step"), BuildHostCommand("scripts.rebuild", "Rebuild Script Assemblies"), BuildHostCommand("help.about", "About"), BuildWorkspaceCommand( "view.reset_layout", "Reset Layout", UIEditorWorkspaceCommandKind::ResetWorkspace), BuildWorkspaceCommand( "view.activate_hierarchy", "Hierarchy", UIEditorWorkspaceCommandKind::ActivatePanel, "hierarchy"), BuildWorkspaceCommand( "view.activate_scene", "Scene", UIEditorWorkspaceCommandKind::ActivatePanel, "scene"), BuildWorkspaceCommand( "view.activate_game", "Game", UIEditorWorkspaceCommandKind::ActivatePanel, "game"), BuildWorkspaceCommand( "view.activate_inspector", "Inspector", UIEditorWorkspaceCommandKind::ActivatePanel, "inspector"), BuildWorkspaceCommand( "view.activate_console", "Console", UIEditorWorkspaceCommandKind::ActivatePanel, "console"), BuildWorkspaceCommand( "view.activate_project", "Project", UIEditorWorkspaceCommandKind::ActivatePanel, "project") }; return registry; } UIEditorMenuItemDescriptor BuildCommandItem( std::string itemId, std::string label, std::string commandId, UIEditorMenuCheckedStateBinding checkedState = {}) { UIEditorMenuItemDescriptor item = {}; item.kind = UIEditorMenuItemKind::Command; item.itemId = std::move(itemId); item.label = std::move(label); item.commandId = std::move(commandId); item.checkedState = std::move(checkedState); return item; } UIEditorMenuItemDescriptor BuildSeparatorItem(std::string itemId) { UIEditorMenuItemDescriptor item = {}; item.kind = UIEditorMenuItemKind::Separator; item.itemId = std::move(itemId); return item; } UIEditorMenuItemDescriptor BuildSubmenuItem( std::string itemId, std::string label, std::vector children) { UIEditorMenuItemDescriptor item = {}; item.kind = UIEditorMenuItemKind::Submenu; item.itemId = std::move(itemId); item.label = std::move(label); item.children = std::move(children); return item; } UIEditorMenuModel BuildMenuModel() { UIEditorMenuModel model = {}; UIEditorMenuDescriptor fileMenu = {}; fileMenu.menuId = "file"; fileMenu.label = "File"; fileMenu.items = { BuildCommandItem("file-new-project", "New Project...", "file.new_project"), BuildCommandItem("file-open-project", "Open Project...", "file.open_project"), BuildCommandItem("file-save-project", "Save Project", "file.save_project"), BuildSeparatorItem("file-separator-project"), BuildCommandItem("file-new-scene", "New Scene", "file.new_scene"), BuildCommandItem("file-open-scene", "Open Scene", "file.open_scene"), BuildCommandItem("file-save-scene", "Save Scene", "file.save_scene"), BuildCommandItem("file-save-scene-as", "Save Scene As...", "file.save_scene_as"), BuildSeparatorItem("file-separator-exit"), BuildCommandItem("file-exit", "Exit", "file.exit") }; UIEditorMenuDescriptor editMenu = {}; editMenu.menuId = "edit"; editMenu.label = "Edit"; editMenu.items = { BuildCommandItem("edit-undo", "Undo", "edit.undo"), BuildCommandItem("edit-redo", "Redo", "edit.redo"), BuildSeparatorItem("edit-separator-history"), BuildCommandItem("edit-cut", "Cut", "edit.cut"), BuildCommandItem("edit-copy", "Copy", "edit.copy"), BuildCommandItem("edit-paste", "Paste", "edit.paste"), BuildCommandItem("edit-duplicate", "Duplicate", "edit.duplicate"), BuildCommandItem("edit-delete", "Delete", "edit.delete"), BuildCommandItem("edit-rename", "Rename", "edit.rename") }; UIEditorMenuDescriptor assetsMenu = {}; assetsMenu.menuId = "assets"; assetsMenu.label = "Assets"; assetsMenu.items = { BuildCommandItem("assets-reimport-selected", "Reimport Selected Asset", "assets.reimport_selected"), BuildCommandItem("assets-reimport-all", "Reimport All Assets", "assets.reimport_all"), BuildSeparatorItem("assets-separator-clear"), BuildCommandItem("assets-clear-library", "Clear Library", "assets.clear_library") }; UIEditorMenuDescriptor runMenu = {}; runMenu.menuId = "run"; runMenu.label = "Run"; runMenu.items = { BuildCommandItem("run-play", "Play", "run.play"), BuildCommandItem("run-pause", "Pause", "run.pause"), BuildCommandItem("run-step", "Step", "run.step") }; UIEditorMenuDescriptor scriptsMenu = {}; scriptsMenu.menuId = "scripts"; scriptsMenu.label = "Scripts"; scriptsMenu.items = { BuildCommandItem("scripts-rebuild", "Rebuild Script Assemblies", "scripts.rebuild") }; UIEditorMenuCheckedStateBinding hierarchyActive = { UIEditorMenuCheckedStateSource::PanelActive, "hierarchy" }; UIEditorMenuCheckedStateBinding sceneActive = { UIEditorMenuCheckedStateSource::PanelActive, "scene" }; UIEditorMenuCheckedStateBinding gameActive = { UIEditorMenuCheckedStateSource::PanelActive, "game" }; UIEditorMenuCheckedStateBinding inspectorActive = { UIEditorMenuCheckedStateSource::PanelActive, "inspector" }; UIEditorMenuCheckedStateBinding consoleActive = { UIEditorMenuCheckedStateSource::PanelActive, "console" }; UIEditorMenuCheckedStateBinding projectActive = { UIEditorMenuCheckedStateSource::PanelActive, "project" }; UIEditorMenuDescriptor viewMenu = {}; viewMenu.menuId = "view"; viewMenu.label = "View"; viewMenu.items = { BuildCommandItem("view-reset-layout", "Reset Layout", "view.reset_layout"), BuildSeparatorItem("view-separator-panels"), BuildSubmenuItem( "view-panels", "Panels", { BuildCommandItem("view-panel-hierarchy", "Hierarchy", "view.activate_hierarchy", hierarchyActive), BuildCommandItem("view-panel-scene", "Scene", "view.activate_scene", sceneActive), BuildCommandItem("view-panel-game", "Game", "view.activate_game", gameActive), BuildCommandItem("view-panel-inspector", "Inspector", "view.activate_inspector", inspectorActive), BuildCommandItem("view-panel-console", "Console", "view.activate_console", consoleActive), BuildCommandItem("view-panel-project", "Project", "view.activate_project", projectActive) }) }; UIEditorMenuDescriptor helpMenu = {}; helpMenu.menuId = "help"; helpMenu.label = "Help"; helpMenu.items = { BuildCommandItem("help-about", "About", "help.about") }; model.menus = { std::move(fileMenu), std::move(editMenu), std::move(assetsMenu), std::move(runMenu), std::move(scriptsMenu), std::move(viewMenu), std::move(helpMenu) }; return model; } UIShortcutBinding BuildBinding( std::string commandId, std::int32_t keyCode, bool control = false, bool shift = false, bool alt = false) { UIShortcutBinding binding = {}; binding.scope = UIShortcutScope::Global; binding.triggerEventType = UIInputEventType::KeyDown; binding.commandId = std::move(commandId); binding.chord.keyCode = keyCode; binding.chord.modifiers.control = control; binding.chord.modifiers.shift = shift; binding.chord.modifiers.alt = alt; return binding; } std::vector BuildShortcutBindings() { return { BuildBinding("file.new_scene", static_cast(KeyCode::N), true), BuildBinding("file.open_scene", static_cast(KeyCode::O), true), BuildBinding("file.save_scene", static_cast(KeyCode::S), true), BuildBinding("file.save_scene_as", static_cast(KeyCode::S), true, true), BuildBinding("edit.undo", static_cast(KeyCode::Z), true), BuildBinding("edit.redo", static_cast(KeyCode::Y), true), BuildBinding("edit.cut", static_cast(KeyCode::X), true), BuildBinding("edit.copy", static_cast(KeyCode::C), true), BuildBinding("edit.paste", static_cast(KeyCode::V), true), BuildBinding("edit.duplicate", static_cast(KeyCode::D), true), BuildBinding("edit.delete", static_cast(KeyCode::Delete)), BuildBinding("edit.rename", static_cast(KeyCode::F2)), BuildBinding("run.play", static_cast(KeyCode::F5)), BuildBinding("run.pause", static_cast(KeyCode::F6)), BuildBinding("run.step", static_cast(KeyCode::F7)), BuildBinding("file.exit", static_cast(KeyCode::F4), false, false, true) }; } UIEditorStatusBarSegment BuildStatusSegment( std::string segmentId, std::string label, UIEditorStatusBarSlot slot, UIEditorStatusBarTextTone tone, float desiredWidth, bool showSeparator) { UIEditorStatusBarSegment segment = {}; segment.segmentId = std::move(segmentId); segment.label = std::move(label); segment.slot = slot; segment.tone = tone; segment.interactive = false; segment.desiredWidth = desiredWidth; segment.showSeparator = showSeparator; return segment; } UIEditorShellToolbarButton BuildToolbarButton( std::string buttonId, UIEditorShellToolbarGlyph glyph) { UIEditorShellToolbarButton button = {}; button.buttonId = std::move(buttonId); button.glyph = glyph; button.enabled = true; return button; } UIEditorWorkspacePanelPresentationModel BuildPlaceholderPresentation( std::string panelId) { UIEditorWorkspacePanelPresentationModel presentation = {}; presentation.panelId = std::move(panelId); presentation.kind = UIEditorPanelPresentationKind::Placeholder; return presentation; } UIEditorWorkspacePanelPresentationModel BuildHostedContentPresentation( std::string panelId) { UIEditorWorkspacePanelPresentationModel presentation = {}; presentation.panelId = std::move(panelId); presentation.kind = UIEditorPanelPresentationKind::HostedContent; return presentation; } UIEditorWorkspacePanelPresentationModel BuildViewportPresentation( std::string panelId, std::string title, std::string subtitle) { UIEditorWorkspacePanelPresentationModel presentation = {}; presentation.panelId = std::move(panelId); presentation.kind = UIEditorPanelPresentationKind::ViewportShell; presentation.viewportShellModel.spec.chrome.title = std::move(title); presentation.viewportShellModel.spec.chrome.subtitle = {}; presentation.viewportShellModel.spec.chrome.showTopBar = false; presentation.viewportShellModel.spec.chrome.showBottomBar = false; presentation.viewportShellModel.frame.statusText.clear(); return presentation; } UIEditorShellInteractionDefinition BuildBaseShellDefinition() { UIEditorShellInteractionDefinition definition = {}; definition.menuModel = BuildMenuModel(); definition.toolbarButtons = { BuildToolbarButton("run.play", UIEditorShellToolbarGlyph::Play), BuildToolbarButton("run.pause", UIEditorShellToolbarGlyph::Pause), BuildToolbarButton("run.step", UIEditorShellToolbarGlyph::Step) }; definition.statusSegments = {}; definition.workspacePresentations = { BuildHostedContentPresentation("hierarchy"), BuildViewportPresentation("scene", "Scene", {}), BuildViewportPresentation("game", "Game", {}), BuildHostedContentPresentation("inspector"), BuildHostedContentPresentation("console"), BuildHostedContentPresentation("project") }; return definition; } std::string ResolvePanelTitle( const UIEditorPanelRegistry& registry, std::string_view panelId) { if (const UIEditorPanelDescriptor* descriptor = FindUIEditorPanelDescriptor(registry, panelId); descriptor != nullptr) { return descriptor->defaultTitle; } return panelId.empty() ? std::string("(none)") : std::string(panelId); } } // namespace EditorShellAsset BuildProductShellAsset(const std::filesystem::path& repoRoot) { EditorShellAsset asset = {}; asset.screenId = "editor.product.shell"; asset.captureRootPath = (repoRoot / "new_editor/captures").lexically_normal(); asset.panelRegistry = BuildPanelRegistry(); asset.workspace = BuildWorkspace(); asset.workspaceSession = BuildDefaultUIEditorWorkspaceSession(asset.panelRegistry, asset.workspace); asset.shellDefinition = BuildBaseShellDefinition(); asset.shortcutAsset.commandRegistry = BuildCommandRegistry(); asset.shortcutAsset.bindings = BuildShortcutBindings(); return asset; } UIEditorShellInteractionDefinition BuildProductShellInteractionDefinition( const EditorShellAsset& asset, const UIEditorWorkspaceController& controller, std::string_view statusText, std::string_view captureText, ProductEditorShellVariant variant) { UIEditorShellInteractionDefinition definition = asset.shellDefinition; if (variant == ProductEditorShellVariant::DetachedWindow) { definition.menuModel = {}; definition.toolbarButtons.clear(); definition.statusSegments.clear(); } const std::string activeTitle = ResolvePanelTitle(asset.panelRegistry, controller.GetWorkspace().activePanelId); const std::string resolvedStatus = statusText.empty() ? std::string("Shell ready") : std::string(statusText); const std::string resolvedCapture = captureText.empty() ? std::string("F12 -> Screenshot") : std::string(captureText); for (UIEditorStatusBarSegment& segment : definition.statusSegments) { if (segment.segmentId == "editor-status") { segment.label = resolvedStatus; } else if (segment.segmentId == "capture") { segment.label = resolvedCapture; } else if (segment.segmentId == "active-panel") { segment.label = activeTitle; } } return definition; } } // namespace XCEngine::UI::Editor::App