22 KiB
22 KiB
XCUI Editor Agent Guide
This file is a working map for agents changing editor/. It is not a
substitute for reading the code. If this guide conflicts with the current
implementation, trust the implementation and update this guide in the same
change.
Build Shape
- The root
CMakeLists.txtalways addseditor/beforetests/. editor/CMakeLists.txtalways buildsXCUIEditor, a static library made fromeditor/include/XCEditor/**pluseditor/src/**.XCUIEditoroutputsXCUIEditor.liband is the reusable, platform-neutral editor UI layer. It links againstXCEngineand is covered mainly bytests/UI/Editor/unit.- The production editor target shape is fixed at three targets:
XCUIEditor, the reusable editor UI library;XCEditorCore, the editor product-core library; andXCEditor, the thin product executable. XCEditorCoreoutputsXCEditorCore.liband owns product editor logic that should be buildable without the concrete Win32/D3D12 executable host: composition, commands, state, project and scene services, feature panels, utility-window descriptors, viewport services, and editor window core.XCEditoris built whenXCENGINE_BUILD_XCUI_EDITOR_APP=ON; that mode requiresXCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON. The executable target is namedXCEditor, but its output name isXCEngine.- The executable target owns process startup, Win32 hosting, D3D12 window rendering, native resources, and the final composition-root wiring.
- Keep the CMake target named
XCEditorso it does not collide with the engine library target namedXCEngine; keep the built executable output name asXCEngine. - Shared code uses C++20 and MSVC
/utf-8; keep new files ASCII unless the surrounding file already uses another encoding for a real reason.
Directory Map
include/XCEditor/is the public editor UI API: collections, docking, fields, foundation, menus, panels, shell, viewport, widgets, windowing, and workspace.src/implements that public API and may include privatesrc/**/Internal.hhelpers. It should stay independent of app, Win32, D3D12, and project runtime concerns.app/Bootstrap/contains process startup and shutdown throughApplication.app/Core/contains app-level contracts and shared app model definitions that are not owned by a concrete feature or composition implementation. Current examples are panel IDs, shared window type contracts, the workspace-panel runtime interface, and utility-window runtime/descriptors.app/Composition/builds the editor shell asset, coordinates shell update phases, and connects app state to hosted panel runtimes.app/Windowing/owns window instances, content controllers, frame transfer requests, lifecycle, workspace synchronization, and utility-window creation.app/Platform/Win32/ownsHWND, message dispatch, native pointer capture, borderless chrome, DPI, placement, and Win32 system integration.app/Rendering/owns editor-window rendering hosts, D3D12 UI rendering, built-in icons, viewport render targets, object picking, and scene viewport passes.app/Features/contains user-facing panels and editor tools: Hierarchy, Scene viewport, Inspector, Project, Console, Color Picker, and component UI.app/Features/EditorWorkspacePanelRegistry.*is the concrete workspace panel factory. It owns the concrete workspace panel adapters. Shared workspace-panel runtime interfaces live underapp/Core/WorkspacePanelssoapp/Composition/**can depend on the contract without depending on the feature registry.app/Features/EditorUtilityWindowRegistry.*is the concrete utility-window panel factory. Shared utility-window contracts and descriptors live underapp/Core/UtilityWindowsso windowing can resolve utility window shape without constructing concrete feature panels.app/Services/Project/,app/Scene/, andapp/State/hold application services that panels should use instead of owning global state themselves.ProjectBrowserModelandEditorProjectRuntimelive underapp/Services/Project; project panels adapt those service models into widget-specific tree/list presentation data.
Layering
- Keep
XCUIEditorbelow the app. Public headers underinclude/XCEditormust not includeapp/**, Win32, D3D12, or feature-panel headers. - Keep platform specifics in
app/Platform/Win32; keep GPU-window specifics inapp/Rendering/D3D12. - Keep shell composition and widget logic data-driven. The reusable layer should emit frames, layouts, draw data, command results, and transfer requests; the app decides how those requests affect native windows and engine services.
- Use existing controller types for mutation:
UIEditorWorkspaceController,UIEditorWindowWorkspaceController, andEditorWindowSystem. - Use the existing service objects for app state:
EditorContext,EditorProjectRuntime,EditorSceneRuntime,EditorSelectionService,EditorCommandFocusService, and utility-window request state.
Startup Flow
app/main.cppentersRunXCEditor, which createsApplication.Application::Initializeresolves the repo root, enables DPI awareness, initializes runtime tracing under the executablelogs/directory, and installs the unhandled-exception crash trace hook.EditorContext::Initializebuilds and validates the shell asset, initializes project and scene runtimes, resets selection/focus/tool state, builds command and shortcut services, and marks the shell ready.Applicationregisters the Win32 window class, creates the Win32 system interaction host, bootstrapsEditorWindowSystemwith the primary workspace state, createsEditorWindowHostRuntime, creates the D3D12 render runtime factory, and constructsEditorWindowManager.EditorWindowManager::CreateWorkspaceWindowcreates anEditorWindowInstance, attaches anEditorWorkspaceWindowContentController, asks the host runtime to create the native window, and initializes the per-window render/content runtime.- The main loop drains up to 64 Win32 messages per tick, updates async resource loads, reaps destroyed windows, renders all running windows, and honors smoke-test auto-exit settings.
Application::WndProcforwards registered windows toEditorWindowMessageDispatcher; unknown messages fall back toDefWindowProcW.
Frame Runtime Ownership
EditorContextowns the app-level shell asset, editor session, command routing, selection/focus services, project runtime, scene runtime, color picker state, and pending utility-window requests.EditorWindowSystemowns the authoritativeUIEditorWindowWorkspaceSet. Workspace windows should derive their local projection from this state rather than storing independent layout truth.EditorWorkspaceWindowContentControllerasksEditorWindowSystemfor a liveUIEditorWorkspaceControllerevery frame. A live controller writes mutations through to the authoritative window workspace state; a copied controller is only a preview.EditorShellRuntimeowns per-window interactive runtime state: viewport host service, built-in icons, the workspace panel runtime set, shell interaction state/frame, splitter correction state, trace entries, draw composer, interaction engine, hosted panel coordinator, and session coordinator.EditorWindowRuntimeControllerowns the content controller, render runtime, screenshot controller, title-bar logo texture, text measurer access, DPI scale, and frame-rate display.EditorWindowInstanceis the managed host-window object. It owns lifecycle state and bridges its native peer to the runtime controller.EditorWindowinapp/Platform/Win32/Windowingowns the native peer behavior:HWND, queued input, pointer capture, chrome interaction, resize snapshots, cursor feedback, invalidation, and native destruction.
Window Authority Model
- The authoritative window/workspace state is the
UIEditorWindowWorkspaceSetstored byEditorWindowSystem. - Presentation data is derived from state through
BuildEditorWorkspaceWindowProjection; do not hand-maintain detached window title, tab-strip title, primary flag, or minimum size in separate places. - Workspace mutations that can affect multiple windows should start with
EditorWindowSystem::BuildWorkspaceMutationController, validate throughValidateWindowSet, build a synchronization plan, apply host-window actions, then commit the plan back toEditorWindowSystem. EditorWindowSynchronizationPlannercompares target workspace state against host snapshots and emits create/update/close actions. Extend this planner and its tests when changing workspace-window synchronization rules.- Destroying the primary workspace window intentionally closes remaining workspace windows. Destroying a detached workspace window reconciles the authoritative window set and refreshes the remaining windows.
EditorWindowWorkspaceCoordinator::RefreshWindowPresentationis the normal path for refreshing projections and titles after a frame.
Frame Transfer Requests
Frame transfer requests are the app boundary for work that cannot be committed inside pure shell/widget code.
EditorWindowFrameOrchestratorconverts shell results intoEditorWindowFrameTransferRequests.- Workspace requests currently include
beginGlobalTabDraganddetachPanel. These are consumed byEditorWindowWorkspaceCoordinator. - Utility requests currently include
openUtilityWindow. These are produced fromEditorContext::RequestOpenUtilityWindowand consumed byEditorUtilityWindowCoordinator. - Panels and shell code should request window-level work through transfer requests or context request state, not by creating/destroying native windows directly.
- Global tab drag uses screen coordinates, a captured owner window, an external
dock-host drop preview on the target window, and
EditorWindowPointerCaptureOwner::GlobalTabDrag.
Window Categories
- Workspace windows use
EditorWorkspaceWindowContentController. They expose workspace, dock-host, input-feedback, title-bar, and viewport-rendering capabilities. - Detached workspace windows are still workspace windows. Their projection can enable the detached title-bar tab strip and derive the window title from the single visible panel.
- Utility windows use
EditorUtilityWindowContentController. They host anEditorUtilityWindowPanel, do not participate in workspace synchronization, and currently do not expose dock-host or viewport capabilities. - Utility descriptors live in
app/Core/UtilityWindows/EditorUtilityWindowRegistry.cpp. Concrete utility panels are created byapp/Features/EditorUtilityWindowRegistry.*and injected through the window content factory. Current utility windows are Color Picker and Add Component; both are single-instance topmost tool windows.
Shell, Panels, And Commands
EditorShellAssetBuilder.cppdefines the product shell: panel registry, default workspace layout, command registry, menus, toolbar buttons, shortcuts, capture root, and status-bar definitions.- Panel IDs are centralized in
app/Core/Panels/EditorPanelIds.h; prefer these constants over raw string literals in app code. - Workspace panels are adapted behind
EditorWorkspacePanelRuntimeSet, whose contract lives inapp/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h. Composition code should ask that runtime set to initialize, prepare command routes, update, draw, expose cursor/capture state, and collect frame events instead of directly namingHierarchyPanel,ProjectPanel,InspectorPanel,ConsolePanel, orSceneViewportFeature. - Hosted panels receive mounted bounds and filtered input through
UIEditorHostedPanelDispatchFrame; concrete workspace-panel adapters resolve their own dispatch entries from that frame. EditorShellHostedPanelCoordinatorfilters hosted-panel input when shell capture owns the pointer stream, then updatesEditorWorkspacePanelRuntimeSet. Concrete adapters wire app services into their panels and the runtime set syncs command focus before after-focus panels such as Console read session state.EditorHostCommandBridgeroutes host commands to focused edit routes, project asset routes, scene routes, inspector routes, workspace commands, or the app exit handler.WorkspaceEventSyncconsumes genericEditorWorkspacePanelFrameEvententries from the workspace panel runtime set. Concrete hierarchy/project event formatting lives behind the feature registry, while this sync layer owns selection-backed session state, scene-open requests, status updates, and runtime trace entries.- Project services must not include
app/Features/**. Keep project filesystem state and command-target resolution inapp/Services/Project; keepUIEditorTreeViewItemand other widget presentation projections inFeatures/Project/ProjectPanel.
Viewport Rendering
- Scene and Game are
ViewportShellpresentations. The Scene panel is the active editor viewport implementation; Game is registered as a viewport panel but is not equivalent to the runtime game view yet. SceneViewportFeatureownsSceneViewportControllerandSceneViewportRenderService.ViewportHostServiceis the per-window manager for viewport requests, render-target allocation, retirement, fallback clearing, and dispatch to the attachedViewportRenderHost.SceneViewportRenderServicerenders scene content through the engine renderer and owns object-id picking state. Keep editor picking/render support behindXCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT.- D3D12 window rendering is behind the abstract
Rendering::Host::EditorWindowRenderRuntime; use the host interfaces when adding app-level features.
Modification Rules
- Add reusable widgets, docking/workspace behavior, shell interaction, or field
logic to
include/XCEditor/**andsrc/**, with unit coverage intests/UI/Editor/unit. - Add product editor behavior to
app/Features/**. Workspace panels must enter composition throughEditorWorkspacePanelRegistry.*and theEditorWorkspacePaneladapter interface; do not include concrete feature panel headers fromEditorShellRuntime,EditorShellHostedPanelCoordinator,EditorShellDrawComposer, orEditorShellSessionCoordinator. - Add new workspace panels by updating the panel ID constants, panel registry, default workspace model, shell presentation, workspace panel runtime adapter, and command/menu entries together.
- Add new utility windows through
app/Core/UtilityWindowsfor the kind, shared descriptor, andEditorUtilityWindowPanelcontract; add the concrete panel factory case inapp/Features/EditorUtilityWindowRegistry.*; then wire the context/request path. Do not model utility windows as workspace panels unless they really dock inside the workspace. - Add new commands in the command registry first, then menu/shortcut bindings, host command bridge routing, and focused route tests.
- Keep workspace/window mutations transactional. Save the previous state, validate after mutation, and roll back on invalid output, matching existing controller patterns.
- Keep per-frame transient panel events separate from persistent app services.
Panels may emit frame events;
EditorContextand runtime services own persistent selection, command focus, project, and scene state. - Release UI textures and GPU resources in
Shutdownpaths. D3D12 shutdown should wait for GPU idle before releasing window resources. - Do not write new manual-validation screenshots under the source tree. Shared manual-validation hosts should write captures under the active build directory.
Current Architecture Notes
XCEditorCoreis the app/product-core target. It exists to make the app boundary build-visible before finer-grained directories are cleaned up.XCEditoris the thin executable host that wiresXCEditorCoreto concrete Win32 and D3D12 implementations. Do not move concrete platform/render host code intoXCEditorCorewithout first introducing neutral host contracts.- Legacy fine-grained app-split CMake targets are retired architecture history.
Current production editor targets are
XCUIEditor,XCEditorCore, andXCEditor. - Existing source-tree capture history is present under some manual-validation scenarios, but new captures should go to the build output directory.
- Runtime trace files are the main app smoke diagnostic. Avoid replacing trace-backed diagnostics with UI-only feedback that tests cannot observe.
Validation
- Build shared editor UI after reusable-layer changes:
cmake --build <build-dir> --config Debug --target XCUIEditor. - Build product editor core after app composition, service, feature, windowing,
or host-contract changes:
cmake --build <build-dir> --config Debug --target XCEditorCore. - Build the app after startup, windowing, rendering, feature-panel, or resource
changes:
cmake --build <build-dir> --config Debug --target XCEditor. - Build reusable editor unit tests:
cmake --build <build-dir> --config Debug --target editor_ui_tests. - Build window synchronization tests:
cmake --build <build-dir> --config Debug --target editor_windowing_phase1_tests. - Build aggregate editor unit targets:
cmake --build <build-dir> --config Debug --target editor_ui_unit_tests. - Build app smoke support:
cmake --build <build-dir> --config Debug --target editor_ui_smoke_targets. - Run app smoke through CTest when
XCEditoris enabled:ctest -C Debug -R xceditor_smoke --output-on-failure. - Useful environment flags:
XCUIEDITOR_VERBOSE_TRACE=1,XCUI_AUTO_CAPTURE_ON_STARTUP=1,XCUIEDITOR_SMOKE_TEST=1,XCUIEDITOR_SMOKE_TEST_DURATION_SECONDS=<n>, andXCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=<n>.
Recommended Reading
editor/CMakeLists.txteditor/app/Bootstrap/Application.cppeditor/app/Composition/EditorContext.cppeditor/app/Services/Project/EditorProjectRuntime.cppeditor/app/Services/Project/ProjectBrowserModel.cppeditor/app/Composition/EditorShellRuntime.cppeditor/app/Composition/EditorShellAssetBuilder.cppeditor/app/Windowing/EditorWindowManager.cppeditor/app/Windowing/EditorWindowInstance.cppeditor/app/Windowing/Content/EditorWorkspaceWindowContentController.cppeditor/app/Windowing/Frame/EditorWindowFrameOrchestrator.cppeditor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cppeditor/include/XCEditor/Windowing/System/EditorWindowSystem.heditor/src/Windowing/System/EditorWindowSystem.cppeditor/include/XCEditor/Workspace/UIEditorWorkspaceController.heditor/src/Workspace/UIEditorWorkspaceController.cppeditor/include/XCEditor/Workspace/UIEditorWindowWorkspaceController.heditor/src/Workspace/UIEditorWindowWorkspaceController.cppeditor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cppeditor/app/Rendering/Host/EditorWindowRenderRuntime.heditor/app/Rendering/Viewport/ViewportHostService.htests/UI/Editor/unit/CMakeLists.txttests/UI/Editor/smoke/CMakeLists.txttests/UI/Editor/manual_validation/README.md
Recent Cuts
This section is intentionally important. Keep it as the running ledger of architecture cuts that have already been made, so future agents do not re-litigate or accidentally undo them. Add concrete entries here whenever a change closes a boundary, removes an obsolete path, or establishes a new ownership rule.
- Primary workspace state is bootstrapped into
EditorWindowSystembefore native window creation. - Workspace content now pulls a live authoritative workspace controller every frame instead of trusting a stale local copy.
- Panel detach and cross-window tab drag are frame transfer requests handled by
EditorWindowWorkspaceCoordinator, then synchronized throughEditorWindowSynchronizationPlanner. - Utility windows are app content controllers backed by
EditorUtilityWindowPanel; they are not workspace windows. - Utility-window runtime contracts and descriptors now live under
app/Core/UtilityWindows. Concrete Color Picker/Add Component construction is owned byapp/Features/EditorUtilityWindowRegistry.*and injected fromApplication, soapp/Windowing/**no longer constructs concrete utility feature panels. - Shared window category, lifecycle, chrome-policy, and native-host-policy
contracts now live under
app/Core/Windowing; the oldapp/Windowing/Host/EditorWindowTypes.hpath is only a compatibility forwarding header. - Viewport rendering is routed through
ViewportHostServiceand the per-window render runtime rather than directly from shell composition. - The old fine-grained app-split target residue was removed from CMake and
tests. The current app split is coarse:
XCEditorCorefor product logic andXCEditorfor the executable host with output nameXCEngine. - Workspace panel runtimes are now behind
EditorWorkspacePanelRuntimeSetandEditorWorkspacePanelRegistry.*.app/Composition/**no longer directly owns or dispatches concrete Console, Hierarchy, Inspector, Project, or Scene panel classes. - Shared app panel contracts started moving into
app/Core: panel IDs now live underapp/Core/Panels, and the workspace-panel runtime interface lives underapp/Core/WorkspacePanels.Applicationis the current composition root for the concrete workspace panel factory. - Project service ownership moved out of
app/Features:ProjectBrowserModelandEditorProjectRuntimenow live underapp/Services/Project, and the Project panel owns the widget-tree projection instead of making the service model depend onUIEditorTreeViewItem.