#include "EditorShellAssetBuilder.h" #include #include #include #include #include #include "Composition/EditorPanelIds.h" #include #include namespace XCEngine::UI::Editor::App { UIEditorPanelRegistry BuildEditorPanelRegistry(); UIEditorWorkspaceModel BuildEditorWorkspaceModel( const UIEditorPanelRegistry& panelRegistry); UIEditorCommandRegistry BuildEditorCommandRegistry(); UIEditorMenuModel BuildEditorMenuModel(); std::vector<::XCEngine::UI::UIShortcutBinding> BuildEditorShortcutBindings(); UIEditorShellInteractionDefinition BuildBaseEditorShellDefinition( const UIEditorPanelRegistry& panelRegistry); std::string ResolveEditorPanelTitle( const UIEditorPanelRegistry& registry, std::string_view panelId); } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { namespace { UIEditorShellToolbarButton BuildToolbarButton( std::string buttonId, BuiltInIconKind iconKind) { UIEditorShellToolbarButton button = {}; button.buttonId = std::move(buttonId); button.iconKind = static_cast(iconKind); button.enabled = true; return button; } UIEditorWorkspacePanelPresentationModel BuildViewportPresentation( const UIEditorPanelDescriptor& descriptor) { UIEditorWorkspacePanelPresentationModel presentation = {}; presentation.panelId = descriptor.panelId; presentation.kind = descriptor.presentationKind; presentation.viewportShellModel.spec = descriptor.viewportShellSpec; if (presentation.viewportShellModel.spec.chrome.title.empty()) { presentation.viewportShellModel.spec.chrome.title = descriptor.defaultTitle; } return presentation; } UIEditorWorkspacePanelPresentationModel BuildPanelPresentation( const UIEditorPanelDescriptor& descriptor) { if (descriptor.presentationKind == UIEditorPanelPresentationKind::ViewportShell) { return BuildViewportPresentation(descriptor); } UIEditorWorkspacePanelPresentationModel presentation = {}; presentation.panelId = descriptor.panelId; presentation.kind = descriptor.presentationKind; return presentation; } } // namespace UIEditorShellInteractionDefinition BuildBaseEditorShellDefinition( const UIEditorPanelRegistry& panelRegistry) { UIEditorShellInteractionDefinition definition = {}; definition.menuModel = BuildEditorMenuModel(); definition.toolbarButtons = { BuildToolbarButton("run.play", BuiltInIconKind::PlayButton), BuildToolbarButton("run.pause", BuiltInIconKind::PauseButton), BuildToolbarButton("run.step", BuiltInIconKind::StepButton) }; definition.statusSegments = {}; definition.workspacePresentations.reserve(panelRegistry.panels.size()); for (const UIEditorPanelDescriptor& descriptor : panelRegistry.panels) { definition.workspacePresentations.push_back(BuildPanelPresentation(descriptor)); } return definition; } std::string ResolveEditorPanelTitle( 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 XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { namespace { using XCEngine::Input::KeyCode; using XCEngine::UI::UIInputEventType; using XCEngine::UI::UIShortcutBinding; using XCEngine::UI::UIShortcutScope; 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; } 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; } } // namespace UIEditorCommandRegistry BuildEditorCommandRegistry() { 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.create_folder", "Create Folder"), BuildHostCommand("assets.create_material", "Create Material"), BuildHostCommand("assets.copy_path", "Copy Path"), BuildHostCommand("assets.show_in_explorer", "Show in Explorer"), 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, std::string(kHierarchyPanelId)), BuildWorkspaceCommand( "view.activate_scene", "Scene", UIEditorWorkspaceCommandKind::ActivatePanel, std::string(kScenePanelId)), BuildWorkspaceCommand( "view.activate_game", "Game", UIEditorWorkspaceCommandKind::ActivatePanel, std::string(kGamePanelId)), BuildWorkspaceCommand( "view.activate_inspector", "Inspector", UIEditorWorkspaceCommandKind::ActivatePanel, std::string(kInspectorPanelId)), BuildWorkspaceCommand( "view.activate_console", "Console", UIEditorWorkspaceCommandKind::ActivatePanel, std::string(kConsolePanelId)), BuildWorkspaceCommand( "view.activate_project", "Project", UIEditorWorkspaceCommandKind::ActivatePanel, std::string(kProjectPanelId)) }; return registry; } std::vector BuildEditorShortcutBindings() { 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("assets.create_folder", static_cast(KeyCode::N), true, true), 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) }; } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { namespace { UIEditorPanelDescriptor BuildHostedContentPanelDescriptor( std::string_view panelId, std::string_view title, bool placeholder, bool canHide, bool canClose) { UIEditorPanelDescriptor descriptor = {}; descriptor.panelId = std::string(panelId); descriptor.defaultTitle = std::string(title); descriptor.presentationKind = UIEditorPanelPresentationKind::HostedContent; descriptor.placeholder = placeholder; descriptor.canHide = canHide; descriptor.canClose = canClose; return descriptor; } UIEditorPanelDescriptor BuildToolWindowHostedContentPanelDescriptor( std::string_view panelId, std::string_view title, float preferredWidth, float preferredHeight, float minimumWidth, float minimumHeight) { UIEditorPanelDescriptor descriptor = BuildHostedContentPanelDescriptor(panelId, title, false, true, true); descriptor.toolWindow = true; descriptor.preferredDetachedWindowSize = ::XCEngine::UI::UISize(preferredWidth, preferredHeight); descriptor.minimumDetachedWindowSize = ::XCEngine::UI::UISize(minimumWidth, minimumHeight); return descriptor; } UIEditorPanelDescriptor BuildViewportPanelDescriptor( std::string_view panelId, std::string_view title, bool canHide, bool canClose, bool showTopBar, bool showBottomBar) { UIEditorPanelDescriptor descriptor = {}; descriptor.panelId = std::string(panelId); descriptor.defaultTitle = std::string(title); descriptor.presentationKind = UIEditorPanelPresentationKind::ViewportShell; descriptor.placeholder = false; descriptor.canHide = canHide; descriptor.canClose = canClose; descriptor.viewportShellSpec.chrome.title = descriptor.defaultTitle; descriptor.viewportShellSpec.chrome.showTopBar = showTopBar; descriptor.viewportShellSpec.chrome.showBottomBar = showBottomBar; return descriptor; } const UIEditorPanelDescriptor& RequirePanelDescriptor( const UIEditorPanelRegistry& registry, std::string_view panelId) { if (const UIEditorPanelDescriptor* descriptor = FindUIEditorPanelDescriptor(registry, panelId); descriptor != nullptr) { return *descriptor; } static const UIEditorPanelDescriptor fallback = {}; return fallback; } } // namespace UIEditorPanelRegistry BuildEditorPanelRegistry() { UIEditorPanelRegistry registry = {}; registry.panels = { BuildHostedContentPanelDescriptor(kHierarchyPanelId, kHierarchyPanelTitle, true, false, false), BuildViewportPanelDescriptor(kScenePanelId, kScenePanelTitle, false, false, false, false), BuildViewportPanelDescriptor(kGamePanelId, kGamePanelTitle, false, false, false, false), BuildHostedContentPanelDescriptor(kInspectorPanelId, kInspectorPanelTitle, true, false, false), BuildHostedContentPanelDescriptor(kConsolePanelId, kConsolePanelTitle, true, false, false), BuildHostedContentPanelDescriptor(kProjectPanelId, kProjectPanelTitle, false, false, false), BuildToolWindowHostedContentPanelDescriptor( kColorPickerPanelId, kColorPickerPanelTitle, 352.0f, 500.0f, 320.0f, 460.0f) }; return registry; } UIEditorWorkspaceModel BuildEditorWorkspaceModel( const UIEditorPanelRegistry& panelRegistry) { const UIEditorPanelDescriptor& hierarchy = RequirePanelDescriptor(panelRegistry, kHierarchyPanelId); const UIEditorPanelDescriptor& scene = RequirePanelDescriptor(panelRegistry, kScenePanelId); const UIEditorPanelDescriptor& game = RequirePanelDescriptor(panelRegistry, kGamePanelId); const UIEditorPanelDescriptor& inspector = RequirePanelDescriptor(panelRegistry, kInspectorPanelId); const UIEditorPanelDescriptor& console = RequirePanelDescriptor(panelRegistry, kConsolePanelId); const UIEditorPanelDescriptor& project = RequirePanelDescriptor(panelRegistry, kProjectPanelId); 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", std::string(kHierarchyPanelId), hierarchy.defaultTitle, hierarchy.placeholder), BuildUIEditorWorkspaceTabStack( "center-tabs", { BuildUIEditorWorkspacePanel( "scene-panel", std::string(kScenePanelId), scene.defaultTitle, scene.placeholder), BuildUIEditorWorkspacePanel( "game-panel", std::string(kGamePanelId), game.defaultTitle, game.placeholder) }, 0u)), BuildUIEditorWorkspaceSingleTabStack( "inspector-panel", std::string(kInspectorPanelId), inspector.defaultTitle, inspector.placeholder)), BuildUIEditorWorkspaceTabStack( "bottom-tabs", { BuildUIEditorWorkspacePanel( "console-panel", std::string(kConsolePanelId), console.defaultTitle, console.placeholder), BuildUIEditorWorkspacePanel( "project-panel", std::string(kProjectPanelId), project.defaultTitle, project.placeholder) }, 1u)); workspace.activePanelId = std::string(kScenePanelId); return workspace; } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { namespace { 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; } } // namespace UIEditorMenuModel BuildEditorMenuModel() { 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 = { BuildSubmenuItem( "assets-create", "Create", { BuildCommandItem("assets-create-folder", "Folder", "assets.create_folder"), BuildCommandItem("assets-create-material", "Material", "assets.create_material") }), BuildSeparatorItem("assets-separator-create"), BuildCommandItem("assets-show-in-explorer", "Show in Explorer", "assets.show_in_explorer"), BuildCommandItem("assets-copy-path", "Copy Path", "assets.copy_path"), BuildSeparatorItem("assets-separator-utility"), 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, std::string(kHierarchyPanelId) }; UIEditorMenuCheckedStateBinding sceneActive = { UIEditorMenuCheckedStateSource::PanelActive, std::string(kScenePanelId) }; UIEditorMenuCheckedStateBinding gameActive = { UIEditorMenuCheckedStateSource::PanelActive, std::string(kGamePanelId) }; UIEditorMenuCheckedStateBinding inspectorActive = { UIEditorMenuCheckedStateSource::PanelActive, std::string(kInspectorPanelId) }; UIEditorMenuCheckedStateBinding consoleActive = { UIEditorMenuCheckedStateSource::PanelActive, std::string(kConsolePanelId) }; UIEditorMenuCheckedStateBinding projectActive = { UIEditorMenuCheckedStateSource::PanelActive, std::string(kProjectPanelId) }; 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; } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { EditorShellAsset BuildEditorApplicationShellAsset(const std::filesystem::path& repoRoot) { EditorShellAsset asset = {}; asset.screenId = "editor.shell"; asset.captureRootPath = (repoRoot / "new_editor/captures").lexically_normal(); asset.panelRegistry = BuildEditorPanelRegistry(); asset.workspace = BuildEditorWorkspaceModel(asset.panelRegistry); asset.workspaceSession = BuildDefaultUIEditorWorkspaceSession(asset.panelRegistry, asset.workspace); asset.shellDefinition = BuildBaseEditorShellDefinition(asset.panelRegistry); asset.shortcutAsset.commandRegistry = BuildEditorCommandRegistry(); asset.shortcutAsset.bindings = BuildEditorShortcutBindings(); return asset; } UIEditorShellInteractionDefinition BuildEditorApplicationShellInteractionDefinition( const EditorShellAsset& asset, const UIEditorWorkspaceController& controller, std::string_view statusText, std::string_view captureText, EditorShellVariant variant) { UIEditorShellInteractionDefinition definition = asset.shellDefinition; if (variant == EditorShellVariant::DetachedWindow) { definition.menuModel = {}; definition.toolbarButtons.clear(); definition.statusSegments.clear(); } const std::string activeTitle = ResolveEditorPanelTitle(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 (Widgets::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