Files
XCEngine/editor/AGENTS.md

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.txt always adds editor/ before tests/.
  • editor/CMakeLists.txt always builds XCUIEditor, a static library made from editor/include/XCEditor/** plus editor/src/**.
  • XCUIEditor outputs XCUIEditor.lib and is the reusable, platform-neutral editor UI layer. It links against XCEngine and is covered mainly by tests/UI/Editor/unit.
  • XCEditor is built when XCENGINE_BUILD_XCUI_EDITOR_APP=ON; that mode requires XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON. The executable target is named XCEditor, but its output name is XCEngine.
  • 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, or XCUIEditorHost unless the product architecture is explicitly changed.
  • Keep the CMake target named XCEditor so it does not collide with the engine library target named XCEngine; keep the built executable output name as XCEngine.
  • 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 private src/**/Internal.h helpers. It should stay independent of app, Win32, D3D12, and project runtime concerns.
  • app/Bootstrap/ contains process startup and shutdown through Application.
  • 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/ owns HWND, 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 keeps app/Composition/** from depending on individual feature-panel classes.
  • app/Project/, app/Scene/, and app/State/ hold application services that panels should use instead of owning global state themselves.

Layering

  • Keep XCUIEditor below the app. Public headers under include/XCEditor must not include app/**, Win32, D3D12, or feature-panel headers.
  • Keep platform specifics in app/Platform/Win32; keep GPU-window specifics in app/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, and EditorWindowSystem.
  • Use the existing service objects for app state: EditorContext, EditorProjectRuntime, EditorSceneRuntime, EditorSelectionService, EditorCommandFocusService, and utility-window request state.

Startup Flow

  1. app/main.cpp enters RunXCEditor, which creates Application.
  2. Application::Initialize resolves the repo root, enables DPI awareness, initializes runtime tracing under the executable logs/ directory, and installs the unhandled-exception crash trace hook.
  3. EditorContext::Initialize builds 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.
  4. Application registers the Win32 window class, creates the Win32 system interaction host, bootstraps EditorWindowSystem with the primary workspace state, creates EditorWindowHostRuntime, creates the D3D12 render runtime factory, and constructs EditorWindowManager.
  5. EditorWindowManager::CreateWorkspaceWindow creates an EditorWindowInstance, attaches an EditorWorkspaceWindowContentController, asks the host runtime to create the native window, and initializes the per-window render/content runtime.
  6. 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.
  7. Application::WndProc forwards registered windows to EditorWindowMessageDispatcher; unknown messages fall back to DefWindowProcW.

Frame Runtime Ownership

  • EditorContext owns the app-level shell asset, editor session, command routing, selection/focus services, project runtime, scene runtime, color picker state, and pending utility-window requests.
  • EditorWindowSystem owns the authoritative UIEditorWindowWorkspaceSet. Workspace windows should derive their local projection from this state rather than storing independent layout truth.
  • EditorWorkspaceWindowContentController asks EditorWindowSystem for a live UIEditorWorkspaceController every frame. A live controller writes mutations through to the authoritative window workspace state; a copied controller is only a preview.
  • EditorShellRuntime owns 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.
  • EditorWindowRuntimeController owns the content controller, render runtime, screenshot controller, title-bar logo texture, text measurer access, DPI scale, and frame-rate display.
  • EditorWindowInstance is the managed host-window object. It owns lifecycle state and bridges its native peer to the runtime controller.
  • EditorWindow in app/Platform/Win32/Windowing owns 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 UIEditorWindowWorkspaceSet stored by EditorWindowSystem.
  • 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 through ValidateWindowSet, build a synchronization plan, apply host-window actions, then commit the plan back to EditorWindowSystem.
  • EditorWindowSynchronizationPlanner compares 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::RefreshWindowPresentation is 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.

  • EditorWindowFrameOrchestrator converts shell results into EditorWindowFrameTransferRequests.
  • Workspace requests currently include beginGlobalTabDrag and detachPanel. These are consumed by EditorWindowWorkspaceCoordinator.
  • Utility requests currently include openUtilityWindow. These are produced from EditorContext::RequestOpenUtilityWindow and consumed by EditorUtilityWindowCoordinator.
  • 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 an EditorUtilityWindowPanel, 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.cpp defines 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 naming HierarchyPanel, ProjectPanel, InspectorPanel, ConsolePanel, or SceneViewportFeature.
  • Hosted panels receive mounted bounds and filtered input through UIEditorHostedPanelDispatchFrame; concrete workspace-panel adapters resolve their own dispatch entries from that frame.
  • EditorShellHostedPanelCoordinator filters hosted-panel input when shell capture owns the pointer stream, then updates EditorWorkspacePanelRuntimeSet. 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.
  • EditorHostCommandBridge routes host commands to focused edit routes, project asset routes, scene routes, inspector routes, workspace commands, or the app exit handler.
  • WorkspaceEventSync consumes generic EditorWorkspacePanelFrameEvent entries 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 ViewportShell presentations. 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.
  • SceneViewportFeature owns SceneViewportController and SceneViewportRenderService.
  • ViewportHostService is the per-window manager for viewport requests, render-target allocation, retirement, fallback clearing, and dispatch to the attached ViewportRenderHost.
  • SceneViewportRenderService renders scene content through the engine renderer and owns object-id picking state. Keep editor picking/render support behind XCENGINE_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/** and src/**, with unit coverage in tests/UI/Editor/unit.
  • Add product editor behavior to app/Features/**. Workspace panels must enter composition through EditorWorkspacePanelRegistry.* and the EditorWorkspacePanel adapter interface; do not include concrete feature panel headers from EditorShellRuntime, EditorShellHostedPanelCoordinator, EditorShellDrawComposer, or EditorShellSessionCoordinator.
  • 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, an EditorUtilityWindowPanel, 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; EditorContext and runtime services own persistent selection, command focus, project, and scene state.
  • Release UI textures and GPU resources in Shutdown paths. 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

  • XCEditor is 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, or XCUIEditorHost in 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 XCEditor is 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>, and XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=<n>.
  • editor/CMakeLists.txt
  • editor/app/Bootstrap/Application.cpp
  • editor/app/Composition/EditorContext.cpp
  • editor/app/Composition/EditorShellRuntime.cpp
  • editor/app/Composition/EditorShellAssetBuilder.cpp
  • editor/app/Windowing/EditorWindowManager.cpp
  • editor/app/Windowing/EditorWindowInstance.cpp
  • editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp
  • editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp
  • editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp
  • editor/include/XCEditor/Windowing/System/EditorWindowSystem.h
  • editor/src/Windowing/System/EditorWindowSystem.cpp
  • editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h
  • editor/src/Workspace/UIEditorWorkspaceController.cpp
  • editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceController.h
  • editor/src/Workspace/UIEditorWindowWorkspaceController.cpp
  • editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp
  • editor/app/Rendering/Host/EditorWindowRenderRuntime.h
  • editor/app/Rendering/Viewport/ViewportHostService.h
  • tests/UI/Editor/unit/CMakeLists.txt
  • tests/UI/Editor/smoke/CMakeLists.txt
  • tests/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 EditorWindowSystem before 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 through EditorWindowSynchronizationPlanner.
  • Utility windows are app content controllers backed by EditorUtilityWindowPanel; they are not workspace windows.
  • Viewport rendering is routed through ViewportHostService and 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 XCEditor target with output name XCEngine, and manual-validation hosts use the shared Core UI validation host instead of an editor app host target.
  • Workspace panel runtimes are now behind EditorWorkspacePanelRuntimeSet and EditorWorkspacePanelRegistry.*. app/Composition/** no longer directly owns or dispatches concrete Console, Hierarchy, Inspector, Project, or Scene panel classes.