520 lines
20 KiB
C++
520 lines
20 KiB
C++
#include "ProductShellAsset.h"
|
|
|
|
#include <XCEngine/Input/InputTypes.h>
|
|
#include <XCEngine/UI/Input/UIShortcutRegistry.h>
|
|
|
|
#include <utility>
|
|
|
|
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<UIEditorMenuItemDescriptor> 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<UIShortcutBinding> BuildShortcutBindings() {
|
|
return {
|
|
BuildBinding("file.new_scene", static_cast<std::int32_t>(KeyCode::N), true),
|
|
BuildBinding("file.open_scene", static_cast<std::int32_t>(KeyCode::O), true),
|
|
BuildBinding("file.save_scene", static_cast<std::int32_t>(KeyCode::S), true),
|
|
BuildBinding("file.save_scene_as", static_cast<std::int32_t>(KeyCode::S), true, true),
|
|
BuildBinding("edit.undo", static_cast<std::int32_t>(KeyCode::Z), true),
|
|
BuildBinding("edit.redo", static_cast<std::int32_t>(KeyCode::Y), true),
|
|
BuildBinding("edit.cut", static_cast<std::int32_t>(KeyCode::X), true),
|
|
BuildBinding("edit.copy", static_cast<std::int32_t>(KeyCode::C), true),
|
|
BuildBinding("edit.paste", static_cast<std::int32_t>(KeyCode::V), true),
|
|
BuildBinding("edit.duplicate", static_cast<std::int32_t>(KeyCode::D), true),
|
|
BuildBinding("edit.delete", static_cast<std::int32_t>(KeyCode::Delete)),
|
|
BuildBinding("edit.rename", static_cast<std::int32_t>(KeyCode::F2)),
|
|
BuildBinding("run.play", static_cast<std::int32_t>(KeyCode::F5)),
|
|
BuildBinding("run.pause", static_cast<std::int32_t>(KeyCode::F6)),
|
|
BuildBinding("run.step", static_cast<std::int32_t>(KeyCode::F7)),
|
|
BuildBinding("file.exit", static_cast<std::int32_t>(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
|