530 lines
18 KiB
Markdown
530 lines
18 KiB
Markdown
# NewEditor Workspace Utility Window Semantic Split Plan
|
|
|
|
Date: 2026-04-22
|
|
Status: Complete and Archived
|
|
|
|
## 0. Execution Progress
|
|
|
|
### Completed on 2026-04-22
|
|
|
|
Phase A shell/content split foundation is now in place.
|
|
|
|
Completed changes:
|
|
|
|
1. `EditorWindow` no longer constructs itself from `UIEditorWorkspaceController`
|
|
2. `EditorWindowRuntimeController` no longer directly owns `m_workspaceController`
|
|
3. `EditorWindowHostRuntime::CreateEditorWindow(...)` no longer accepts raw workspace controller input
|
|
4. a new content abstraction now exists:
|
|
- `EditorWindowContentController`
|
|
- `EditorWorkspaceWindowContentController`
|
|
5. current workspace-backed behavior has been moved behind the new content layer without changing workspace semantics
|
|
6. detached workspace chrome policy now reads window-content capabilities instead of shell-owned workspace state
|
|
7. `XCUIEditorApp` Debug build passes after this phase
|
|
|
|
What is intentionally not done yet:
|
|
|
|
1. utility window domain does not exist yet
|
|
2. color picker still opens through detached panel workflow
|
|
3. `toolWindow` still exists in workspace panel descriptors as legacy leakage to remove in later phases
|
|
|
|
This means the root shell/content ownership boundary has been extracted first, while feature semantics remain unchanged for safety.
|
|
|
|
### Completed on 2026-04-22
|
|
|
|
Phase C, Phase D, and Phase E implementation are now in place.
|
|
|
|
Completed changes:
|
|
|
|
1. a first-class utility window domain now exists:
|
|
- `EditorUtilityWindowKind`
|
|
- `EditorWindowOpenUtilityWindowRequest`
|
|
- `EditorUtilityWindowCoordinator`
|
|
- `EditorUtilityWindowRegistry`
|
|
2. color picker open intent no longer routes through detached workspace mutation
|
|
3. the color picker now opens through utility-window coordination and utility content
|
|
4. color picker has been removed from:
|
|
- `UIEditorPanelRegistry`
|
|
- shell hosted-panel composition
|
|
- workspace detached-panel request plumbing
|
|
5. `openDetachedPanel` has been deleted from frame transfer routing
|
|
6. `UIEditorWindowWorkspaceController::OpenPanelInNewWindow(...)` has been deleted
|
|
7. `toolWindow` and related workspace utility inference have been deleted
|
|
8. detached floating-window placement logic is now shared instead of duplicated
|
|
9. `XCUIEditorApp` Debug build passes after this phase
|
|
|
|
### Verified and Archived on 2026-04-22
|
|
|
|
Section 10 manual verification has now passed.
|
|
|
|
Verified outcomes:
|
|
|
|
1. workspace regression checks passed
|
|
2. utility window checks passed
|
|
3. cross-boundary checks passed
|
|
4. `XCUIEditorApp` Debug build passes
|
|
5. `XCUIEditorApp` Release build passes
|
|
|
|
## 1. Objective
|
|
|
|
This plan solves the problem from the architectural root, not by adding another special case.
|
|
|
|
The goal is to completely separate:
|
|
|
|
1. native window shell
|
|
2. workspace-backed dockable windows
|
|
3. utility windows that are inherently standalone
|
|
|
|
The immediate trigger is the color picker window, but the real issue is broader:
|
|
|
|
1. `EditorWindow` is still specialized around `UIEditorWorkspaceController`
|
|
2. utility windows are currently modeled as panels with a `toolWindow` flag
|
|
3. the color picker is opened through `openDetachedPanel`
|
|
4. utility window policy is inferred indirectly from workspace root content
|
|
|
|
This architecture is semantically wrong.
|
|
|
|
If a window is inherently standalone and can never merge back into the main workspace, it must not live inside the workspace panel model.
|
|
|
|
## 2. Architectural Judgment
|
|
|
|
The current implementation shares too much.
|
|
|
|
Sharing the native host is correct:
|
|
|
|
1. HWND lifetime
|
|
2. D3D render loop
|
|
3. DPI handling
|
|
4. input capture
|
|
5. title bar / chrome
|
|
6. screenshot and frame pacing
|
|
|
|
Sharing the workspace panel abstraction is not correct for the color picker.
|
|
|
|
The color picker is currently treated as:
|
|
|
|
1. a panel descriptor in `UIEditorPanelRegistry`
|
|
2. a hosted panel inside shell composition
|
|
3. a detached panel opened via `OpenPanelInNewWindow(...)`
|
|
4. a single-root detached workspace that is merely styled as a tool window
|
|
|
|
That means the color picker still belongs to the dock / detach / re-dock / transfer universe.
|
|
|
|
This is the wrong owner domain.
|
|
|
|
## 3. Confirmed Root Cause
|
|
|
|
The root cause is not one bad function.
|
|
|
|
The root cause is that the codebase currently conflates two different semantics:
|
|
|
|
1. workspace window
|
|
- owns a `UIEditorWorkspaceController`
|
|
- participates in layout, docking, tab stacks, cross-window transfer
|
|
- can detach and re-merge
|
|
2. utility window
|
|
- owns standalone tool content
|
|
- does not belong to workspace layout
|
|
- does not expose `nodeId` / `panelId` docking semantics
|
|
- cannot merge into the main window
|
|
|
|
Today those two semantics are both forced through `EditorWindow` + `UIEditorWorkspaceController`.
|
|
|
|
That is why the color picker can only exist as a fake panel.
|
|
|
|
## 4. Current Coupling Points
|
|
|
|
These are the concrete places where the semantic mistake is encoded today:
|
|
|
|
1. `new_editor/app/Platform/Win32/EditorWindow.h`
|
|
- `EditorWindow` constructor directly requires `UIEditorWorkspaceController`
|
|
2. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h`
|
|
- runtime directly stores `m_workspaceController`
|
|
3. `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h`
|
|
- utility-open intent is modeled as `EditorWindowOpenDetachedPanelRequest`
|
|
4. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp`
|
|
- color picker open is converted into `openDetachedPanel`
|
|
5. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp`
|
|
- color picker window creation is routed through workspace mutation
|
|
6. `new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp`
|
|
- `OpenPanelInNewWindow(...)` is used to create the color picker window
|
|
7. `new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h`
|
|
- `toolWindow` is attached to panel descriptors
|
|
8. `new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp`
|
|
- utility semantics are inferred from detached workspace root content
|
|
9. `new_editor/app/Composition/EditorShellAssetBuilder.cpp`
|
|
- color picker is registered as a panel
|
|
10. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp`
|
|
- color picker lives inside hosted panel update / dispatch infrastructure
|
|
11. `new_editor/app/State/EditorColorPickerToolState.h`
|
|
- color picker open intent is expressed as `requestOpenDetachedPanel`
|
|
|
|
This is not one bug. This is an ownership boundary failure.
|
|
|
|
## 5. Refactor Red Lines
|
|
|
|
The refactor must not degrade into any of the following:
|
|
|
|
1. keeping the color picker in `UIEditorPanelRegistry` and merely adding more `if (panelId == "color-picker")` branches
|
|
2. keeping `toolWindow` as the long-term mechanism for non-workspace windows
|
|
3. keeping `openDetachedPanel` as the generic request type for utility windows
|
|
4. allowing utility windows to remain inside `UIEditorWindowWorkspaceSet`
|
|
5. preserving `nodeId` / `panelId` transfer semantics for utility windows
|
|
6. building a second special-case bypass inside `EditorWindowWorkspaceCoordinator`
|
|
7. renaming the current path without changing ownership
|
|
|
|
If any of those remain in the end state, the architecture is still wrong.
|
|
|
|
## 6. Target End State
|
|
|
|
The correct end state has three layers.
|
|
|
|
### 6.1 Native Shell Layer
|
|
|
|
A shared native shell owns only platform and rendering responsibilities:
|
|
|
|
1. HWND creation and destruction
|
|
2. D3D renderer / swap chain / present loop
|
|
3. window chrome and title
|
|
4. DPI, sizing, screenshot, pointer capture plumbing
|
|
5. input event collection and dispatch to content
|
|
|
|
This layer must not know whether the content is workspace-backed or utility-backed.
|
|
|
|
Suggested shape:
|
|
|
|
1. `EditorNativeWindowShell`
|
|
2. `EditorNativeWindowRuntime`
|
|
3. `IEditorWindowContentController`
|
|
|
|
The names can change, but the boundary cannot.
|
|
|
|
### 6.2 Workspace Window Layer
|
|
|
|
Workspace windows are the only windows allowed to own:
|
|
|
|
1. `UIEditorWorkspaceController`
|
|
2. `EditorShellRuntime`
|
|
3. dock host interaction state
|
|
4. detached panel transfer requests
|
|
5. cross-window tab drag and dock / re-dock semantics
|
|
|
|
A workspace window can:
|
|
|
|
1. represent the main window
|
|
2. represent a detached panel window
|
|
3. merge back into another workspace window
|
|
|
|
Suggested shape:
|
|
|
|
1. `EditorWorkspaceWindowContent`
|
|
2. `EditorWorkspaceWindowCoordinator`
|
|
3. `EditorWorkspaceWindowTransferRequests`
|
|
|
|
### 6.3 Utility Window Layer
|
|
|
|
Utility windows are standalone tool surfaces.
|
|
|
|
They must not own:
|
|
|
|
1. `UIEditorWorkspaceController`
|
|
2. dock layout
|
|
3. tab stack state
|
|
4. `nodeId`
|
|
5. `panelId` workspace transfer semantics
|
|
|
|
They may own:
|
|
|
|
1. dedicated tool state
|
|
2. dedicated rendering and input logic
|
|
3. explicit request / response channels to editor features
|
|
4. separate focus / reuse / close rules
|
|
|
|
Suggested shape:
|
|
|
|
1. `EditorUtilityWindowContent`
|
|
2. `EditorUtilityWindowCoordinator`
|
|
3. `EditorOpenUtilityWindowRequest`
|
|
4. `EditorUtilityWindowKind`
|
|
5. `EditorColorPickerUtilityWindowContent`
|
|
|
|
## 7. Required Ownership Changes
|
|
|
|
The refactor must move ownership, not just code location.
|
|
|
|
### 7.1 Window Identity
|
|
|
|
Today:
|
|
|
|
1. detached color picker window identity is derived from `panelId`
|
|
2. detached utility behavior is inferred from the root workspace panel descriptor
|
|
|
|
After refactor:
|
|
|
|
1. workspace windows are identified inside workspace domain
|
|
2. utility windows are identified inside utility domain
|
|
3. utility window identity is no longer a panel identity
|
|
|
|
### 7.2 Open Intent
|
|
|
|
Today:
|
|
|
|
1. color picker open intent means "open this panel in a detached window"
|
|
|
|
After refactor:
|
|
|
|
1. color picker open intent means "open or focus utility window of kind color picker"
|
|
2. the request carries explicit tool payload:
|
|
- initial color
|
|
- alpha mode
|
|
- inspector target binding
|
|
- focus / reuse policy
|
|
|
|
### 7.3 Persistence
|
|
|
|
Today:
|
|
|
|
1. utility-like windows still pass through workspace layout concepts
|
|
|
|
After refactor:
|
|
|
|
1. workspace window state remains in workspace layout persistence
|
|
2. utility window state uses separate persistence or no persistence
|
|
3. utility windows never enter `UIEditorWindowWorkspaceSet`
|
|
|
|
### 7.4 Visual Policy
|
|
|
|
Today:
|
|
|
|
1. tool-window policy is inferred from detached workspace content
|
|
|
|
After refactor:
|
|
|
|
1. workspace detached-window policy is only about workspace windows
|
|
2. utility-window policy belongs to utility window descriptors or utility content
|
|
|
|
## 8. Migration Strategy
|
|
|
|
The migration must happen in strict phases.
|
|
|
|
### Phase A. Extract a Content-Neutral Native Window Shell
|
|
|
|
Goal:
|
|
|
|
1. stop making the native window host depend directly on `UIEditorWorkspaceController`
|
|
|
|
Required changes:
|
|
|
|
1. split the current `EditorWindow` responsibilities into:
|
|
- shell responsibilities
|
|
- content responsibilities
|
|
2. extract a content interface for:
|
|
- update
|
|
- draw
|
|
- input dispatch
|
|
- external preview / title contributions if needed
|
|
3. make `EditorWindowHostRuntime::CreateEditorWindow(...)` create a shell plus content, not a workspace-bound window object
|
|
|
|
Success criteria:
|
|
|
|
1. a shell instance can host workspace content
|
|
2. a shell instance can host non-workspace content
|
|
3. no shell constructor directly requires `UIEditorWorkspaceController`
|
|
|
|
Red line:
|
|
|
|
1. do not keep a hidden `m_workspaceController` in shell-level runtime
|
|
|
|
### Phase B. Move Existing Workspace Windows onto the New Content Layer
|
|
|
|
Goal:
|
|
|
|
1. preserve all existing main-window and detached-panel behavior while isolating it into workspace-specific content
|
|
|
|
Required changes:
|
|
|
|
1. create `EditorWorkspaceWindowContent`
|
|
2. move current workspace-only responsibilities into it:
|
|
- `UIEditorWorkspaceController`
|
|
- `EditorShellRuntime`
|
|
- workspace frame orchestration
|
|
- dock transfer request generation
|
|
3. keep `EditorWindowWorkspaceCoordinator` but narrow its responsibility to workspace windows only
|
|
|
|
Success criteria:
|
|
|
|
1. main window still works
|
|
2. detached panel windows still work
|
|
3. cross-window tab drag and re-dock still work
|
|
4. utility windows are still not introduced yet, but the shell is already ready for them
|
|
|
|
### Phase C. Introduce a Real Utility Window Domain
|
|
|
|
Goal:
|
|
|
|
1. create a first-class utility window pipeline independent from workspace mutation
|
|
|
|
Required changes:
|
|
|
|
1. add utility window request types
|
|
2. add `EditorUtilityWindowCoordinator`
|
|
3. add utility window registry / descriptor model
|
|
4. define utility window lifecycle:
|
|
- open
|
|
- reuse
|
|
- focus
|
|
- close
|
|
- app-shutdown behavior
|
|
5. define utility window payload passing
|
|
|
|
Success criteria:
|
|
|
|
1. utility windows can be created without touching `UIEditorWindowWorkspaceController`
|
|
2. utility windows can be reused without entering workspace state
|
|
3. utility windows do not generate dock transfer requests
|
|
|
|
### Phase D. Migrate Color Picker Out of the Panel System
|
|
|
|
Goal:
|
|
|
|
1. make the color picker the first true utility window
|
|
|
|
Required changes:
|
|
|
|
1. remove color picker from `UIEditorPanelRegistry`
|
|
2. remove color picker from shell hosted-panel composition
|
|
3. replace `requestOpenDetachedPanel` with utility-window open intent
|
|
4. implement `EditorColorPickerUtilityWindowContent`
|
|
5. wire inspector -> color picker communication through explicit tool contracts instead of workspace panel visibility
|
|
|
|
Success criteria:
|
|
|
|
1. opening the color picker never calls `OpenPanelInNewWindow(...)`
|
|
2. color picker never appears in workspace visible panels
|
|
3. color picker never participates in detach / re-dock / cross-window panel transfer
|
|
4. color picker still updates inspector-bound color correctly
|
|
|
|
### Phase E. Delete Transitional Workspace Utility Leakage
|
|
|
|
Goal:
|
|
|
|
1. remove all leftover semantic leakage after color picker migration
|
|
|
|
Required deletions:
|
|
|
|
1. remove `toolWindow` from `UIEditorPanelDescriptor`
|
|
2. remove color-picker-specific detached-panel request plumbing
|
|
3. remove utility inference from `UIEditorDetachedWindowPolicy`
|
|
4. remove any remaining workspace special cases that exist only for the old color picker path
|
|
|
|
Success criteria:
|
|
|
|
1. workspace code no longer knows the color picker exists
|
|
2. utility code no longer depends on panel registry membership
|
|
3. no root-content-based inference is needed to determine window kind
|
|
|
|
## 9. Files Expected to Change
|
|
|
|
This refactor is architecture-level and will necessarily touch a broad slice.
|
|
|
|
### Shell / Host Layer
|
|
|
|
1. `new_editor/app/Platform/Win32/EditorWindow.h`
|
|
2. `new_editor/app/Platform/Win32/EditorWindow.cpp`
|
|
3. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h`
|
|
4. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp`
|
|
5. `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h`
|
|
6. `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp`
|
|
7. `new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp`
|
|
8. `new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp`
|
|
|
|
### Workspace Layer
|
|
|
|
1. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h`
|
|
2. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp`
|
|
3. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp`
|
|
4. `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h`
|
|
5. `new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp`
|
|
6. `new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp`
|
|
|
|
### Utility Layer
|
|
|
|
1. new utility window coordinator files
|
|
2. new utility window descriptor / request files
|
|
3. new color picker utility content files
|
|
4. possibly a host-level coordinator that routes shell instances to workspace or utility content
|
|
|
|
### Color Picker / Feature Layer
|
|
|
|
1. `new_editor/app/State/EditorColorPickerToolState.h`
|
|
2. `new_editor/app/State/EditorColorPickerToolState.cpp`
|
|
3. `new_editor/app/Features/ColorPicker/ColorPickerPanel.h`
|
|
4. `new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp`
|
|
5. `new_editor/app/Composition/EditorShellAssetBuilder.cpp`
|
|
6. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp`
|
|
7. `new_editor/app/Composition/EditorShellRuntime.h`
|
|
8. `new_editor/app/Composition/EditorShellRuntime.cpp`
|
|
9. `new_editor/app/Composition/EditorShellDrawComposer.cpp`
|
|
|
|
## 10. Verification Matrix
|
|
|
|
This refactor is not done unless all of the following are checked.
|
|
|
|
### Workspace Regression Checks
|
|
|
|
1. main window render / input / title behavior is intact
|
|
2. panel detach still creates detached workspace windows
|
|
3. detached workspace windows can re-dock
|
|
4. cross-window tab drag-drop still works
|
|
5. workspace close path still closes detached workspace windows correctly
|
|
6. workspace layout persistence still excludes utility windows
|
|
|
|
### Utility Window Checks
|
|
|
|
1. opening color picker creates or focuses a utility window, not a detached panel window
|
|
2. color picker cannot be docked into the main window
|
|
3. color picker cannot be merged by tab drag
|
|
4. color picker does not appear in workspace visible-panel lists
|
|
5. color picker close does not mutate workspace layout
|
|
6. color picker reuse policy is correct:
|
|
- single-instance reuse or explicit multi-instance rule, but not accidental workspace reuse
|
|
|
|
### Cross-Boundary Checks
|
|
|
|
1. inspector -> color picker color synchronization still works
|
|
2. closing the main app tears down utility windows correctly
|
|
3. focus changes between workspace and utility windows do not break input capture
|
|
4. utility windows do not accidentally receive workspace drop preview / transfer state
|
|
|
|
### Build Checks
|
|
|
|
1. `XCUIEditorApp` Debug build passes
|
|
2. `XCUIEditorApp` Release build passes if used by the team
|
|
|
|
## 11. Completion Criteria
|
|
|
|
This refactor is complete only when all of these are true:
|
|
|
|
1. no utility window is represented as a workspace panel
|
|
2. `EditorWindow` shell no longer requires `UIEditorWorkspaceController`
|
|
3. workspace and utility windows share only native shell infrastructure
|
|
4. `toolWindow` is not the long-term carrier of utility-window semantics
|
|
5. the color picker opens through utility window coordination, not workspace mutation
|
|
6. workspace code can be reasoned about without referencing utility windows
|
|
7. utility windows can be reasoned about without referencing dock / transfer semantics
|
|
|
|
## 12. Final Statement
|
|
|
|
The correct fix is not:
|
|
|
|
1. a new bypass for the color picker
|
|
2. another flag on panel descriptors
|
|
3. another branch inside workspace mutation
|
|
|
|
The correct fix is to restore the missing architectural boundary:
|
|
|
|
1. native shell is shared
|
|
2. workspace semantics are isolated
|
|
3. utility semantics are isolated
|
|
|
|
Only after this split is in place will the color picker stop being a fake detached panel and become a real standalone editor window type.
|