18 KiB
18 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.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 app target owns Win32 hosting, D3D12 window rendering, project/scene runtime wiring, real editor panels, utility windows, and screenshots.
- The app is intentionally packaged as one executable target. Do not restore
internal app split targets such as
XCUIEditorAppLib,XCUIEditorAppCore, orXCUIEditorHostunless the product architecture is explicitly changed. - 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/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 single composition entry point for workspace panel runtimes. It owns the concrete workspace panel adapters and keepsapp/Composition/**from depending on individual feature-panel classes.app/Project/,app/Scene/, andapp/State/hold application services that panels should use instead of owning global state themselves.
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
EditorUtilityWindowRegistry.cpp. 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
EditorPanelIds.h; prefer these constants over raw string literals in app code. - Workspace panels are adapted behind
EditorWorkspacePanelRuntimeSet. 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.
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
EditorUtilityWindowKind,EditorUtilityWindowRegistry, anEditorUtilityWindowPanel, and 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 Debt
XCEditoris the intentional single executable app target. App-layer boundaries are maintained through directory ownership, namespaces, app/service interfaces, focused reusable-layer tests, manual-validation hosts, and smoke diagnostics, not through internal app static-library targets.- Legacy app-split CMake targets have been removed. Do not reference
XCUIEditorAppLib,XCUIEditorAppCore, orXCUIEditorHostin new build logic or tests. - 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 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/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. - Viewport rendering is routed through
ViewportHostServiceand the per-window render runtime rather than directly from shell composition. - The old app-split target residue was removed from CMake and tests. The app
remains a single
XCEditortarget with output nameXCEngine, and manual-validation hosts use the shared Core UI validation host instead of an editor app host target. - Workspace panel runtimes are now behind
EditorWorkspacePanelRuntimeSetandEditorWorkspacePanelRegistry.*.app/Composition/**no longer directly owns or dispatches concrete Console, Hierarchy, Inspector, Project, or Scene panel classes.