Skip to content

Commit ef79f7c

Browse files
authored
Merge pull request #3137 from Kryptos-FR/feature/editor-contributor-docs
docs: add editor contributor documentation
1 parent 7a54c1e commit ef79f7c

6 files changed

Lines changed: 599 additions & 0 deletions

File tree

docs/editor/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Editor Framework — Overview
2+
3+
Stride's editor is built on a ViewModel layer that sits above Quantum and the asset system. Every mutation goes through a transaction stack that feeds undo/redo. A separate selection history stack, sharing the same low-level infrastructure, powers back/forward navigation. Both systems use `ITransactionStack` and `Operation` from the `Stride.Core.Transactions` namespace (assembly: `Stride.Core.Design`).
4+
5+
## Shared Infrastructure
6+
7+
```mermaid
8+
flowchart TD
9+
A["ITransactionStack + Operation<br/>Stride.Core.Design · Stride.Core.Transactions<br/>sources/core/Stride.Core.Design/Transactions/<br/>Shared interface and base type — each consumer creates its own instance"]
10+
B["IUndoRedoService<br/>Stride.Core.Presentation<br/>sources/presentation/Stride.Core.Presentation/Services/<br/>Wraps stack with naming, dirtiable sync, and save-point tracking"]
11+
C["SelectionService<br/>Stride.Core.Assets.Editor<br/>sources/editor/Stride.Core.Assets.Editor/Services/<br/>Separate unbounded stack; records selection snapshots"]
12+
13+
A -. "uses" .-> B
14+
A -. "uses" .-> C
15+
```
16+
17+
## Projects
18+
19+
The editor codebase spans `sources/presentation/` (MVVM framework, Quantum-to-UI binding, shared controls) and `sources/editor/` (editor infrastructure and concrete asset editors). ViewModels are platform-agnostic; WPF coupling belongs in XAML files, code-behind, and WPF-specific service implementations. See [projects.md](projects.md) for the full project map and assembly reference.
20+
21+
## When You Need These Systems
22+
23+
> **Decision tree:**
24+
>
25+
> - Wrapping a property or collection mutation so it is undoable?
26+
> **`IUndoRedoService.CreateTransaction()` + `AnonymousDirtyingOperation`.** See [undo-redo.md](undo-redo.md).
27+
>
28+
> - Writing a reusable operation that merges consecutive edits on the same target?
29+
> **`DirtyingOperation` subclass + `IMergeableOperation`.** See [undo-redo.md](undo-redo.md).
30+
>
31+
> - Tracking which objects are "dirty" (unsaved) after changes?
32+
> **`IDirtiable` / `DirtiableManager`.** See [undo-redo.md](undo-redo.md).
33+
>
34+
> - Mutating a value through a Quantum node presenter (property grid edit)?
35+
> **No `PushOperation` needed**`ContentValueChangeOperation` is pushed automatically by the Quantum infrastructure. See [undo-redo.md](undo-redo.md#how-quantum-feeds-the-stack-automatically).
36+
>
37+
> - Understanding how back/forward selection history works?
38+
> **`SelectionService`.** See [navigation.md](navigation.md).
39+
>
40+
> - Creating a dedicated editing surface for a new asset type?
41+
> **Write a custom editor.** See [custom-editor.md](custom-editor.md).
42+
>
43+
> - Understanding or modifying an existing editor?
44+
> **Existing editors catalogue.** See [editors.md](editors.md).
45+
46+
## Spoke Files
47+
48+
| File | Covers |
49+
|---|---|
50+
| [undo-redo.md](undo-redo.md) | `ITransactionStack`, `IUndoRedoService`, `DirtyingOperation`, `IMergeableOperation`, `IDirtiable`, dirty-flag synchronisation |
51+
| [navigation.md](navigation.md) | `SelectionService`, selection history snapshots, back/forward navigation |
52+
| [projects.md](projects.md) | Project inventory, WPF boundary rule, assembly map |
53+
| [custom-editor.md](custom-editor.md) | Custom asset editor: base class, registration, lifecycle, services, MVVM patterns |
54+
| [editors.md](editors.md) | Existing editors catalogue: SpriteSheet, Scene, Prefab, UIPage, UILibrary, GraphicsCompositor, Script, VisualScript |

docs/editor/custom-editor.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Writing a Custom Asset Editor
2+
3+
## Role
4+
5+
A custom editor is a ViewModel + View pair registered against an `AssetViewModel` type. When the user double-clicks the asset in GameStudio, the framework instantiates the registered view, binds it to the registered ViewModel, and calls `InitializeEditor`. The ViewModel drives all logic; the View is pure WPF XAML bound to it.
6+
7+
## Choosing a Base Class
8+
9+
| Base class | Use when | What it adds |
10+
|---|---|---|
11+
| `AssetEditorViewModel` | Simple editor with no game viewport and no hierarchical parts (e.g. sprite sheet, graphics compositor, script) | Asset ownership, `Initialize`/`Destroy` lifecycle, `IUndoRedoService`, `SessionViewModel` |
12+
| `GameEditorViewModel` | Editor that needs a live game instance for rendering (rarely subclassed directly — prefer the composite variant below) | `IEditorGameController` integration, game startup/shutdown, error recovery |
13+
| `AssetCompositeHierarchyEditorViewModel<TAssetPartDesign, TAssetPart, TItemViewModel>` | Asset that contains a tree of selectable parts (scenes, prefabs, UI pages) | Selection tracking, copy/cut/paste/delete/duplicate for hierarchy parts, part ViewModel factory |
14+
15+
## Registration
16+
17+
Two attributes are required. Both are discovered automatically by `AssetsEditorPlugin` via reflection at startup — no manual registration needed.
18+
19+
```csharp
20+
// On the editor ViewModel class — maps AssetViewModel subtype → editor ViewModel type.
21+
[AssetEditorViewModel<%%AssetName%%ViewModel>]
22+
public sealed class %%AssetName%%EditorViewModel : AssetEditorViewModel
23+
{
24+
public %%AssetName%%EditorViewModel([NotNull] %%AssetName%%ViewModel asset)
25+
: base(asset) { }
26+
}
27+
28+
// On the view code-behind — maps editor ViewModel type → view type.
29+
[AssetEditorView<%%AssetName%%EditorViewModel>]
30+
public partial class %%AssetName%%EditorView : UserControl, IEditorView { ... }
31+
```
32+
33+
Both classes must live in `Stride.Assets.Presentation` (or an assembly loaded as a plugin via `AssetsEditorPlugin`).
34+
35+
## Lifecycle
36+
37+
**1. Construction** — synchronous; `base(asset)` is the only required call; do not perform async work here.
38+
39+
**2. `Initialize()`**
40+
41+
```csharp
42+
public override async Task<bool> Initialize()
43+
{
44+
// Load resources, set up bindings, register selection scope.
45+
// Return false to abort — the editor will not open and Destroy() will be called.
46+
return true;
47+
}
48+
```
49+
50+
**3. Active editing** — user interacts; ViewModel handles commands; all mutations go through `UndoRedoService.CreateTransaction()` (see [undo-redo.md](undo-redo.md)).
51+
52+
**4. `PreviewClose(bool? save)`**
53+
54+
```csharp
55+
public override bool PreviewClose(bool? save)
56+
{
57+
if (save == null)
58+
{
59+
// Ask user — show a dialog via ServiceProvider.Get<IEditorDialogService>().
60+
// Return false to cancel close.
61+
}
62+
// save == true → force-save; save == false → discard.
63+
return true;
64+
}
65+
```
66+
67+
**5. `Destroy()`** — inherited from the MVVM base infrastructure (`DispatcherViewModel`/`ViewModelBase`), not declared on `AssetEditorViewModel` itself; synchronous; unhook all events, stop game instance if any, release resources; must not throw; always call `base.Destroy()`.
68+
69+
## The View
70+
71+
Implement `IEditorView` in the code-behind. The XAML file contains only layout and data bindings — no business logic.
72+
73+
```csharp
74+
[AssetEditorView<%%AssetName%%EditorViewModel>]
75+
public partial class %%AssetName%%EditorView : UserControl, IEditorView
76+
{
77+
private readonly TaskCompletionSource editorInitializationTcs = new();
78+
79+
public object DataContext
80+
{
81+
get => base.DataContext;
82+
set => base.DataContext = value;
83+
}
84+
85+
public Task EditorInitialization => editorInitializationTcs.Task;
86+
87+
public async Task<bool> InitializeEditor(IAssetEditorViewModel editor)
88+
{
89+
if (!await editor.Initialize())
90+
{
91+
editor.Destroy();
92+
return false;
93+
}
94+
// Wire up anything that requires the initialized ViewModel here
95+
// (e.g. inject the game viewport: somePanel.Content = myEditor.Controller.EditorHost).
96+
editorInitializationTcs.SetResult();
97+
return true;
98+
}
99+
}
100+
```
101+
102+
## Services
103+
104+
Access services via `ServiceProvider` (available on `AssetEditorViewModel`):
105+
106+
| Service | Access | Purpose |
107+
|---|---|---|
108+
| `IUndoRedoService` | `ServiceProvider.Get<IUndoRedoService>()` | Wrap mutations in transactions — see [undo-redo.md](undo-redo.md) |
109+
| `IDispatcherService` | `ServiceProvider.Get<IDispatcherService>()` | Invoke code on the UI thread from a background thread |
110+
| `IEditorDialogService` | `ServiceProvider.Get<IEditorDialogService>()` | Show dialogs, message boxes, and file pickers |
111+
| `SelectionService` | `ServiceProvider.Get<SelectionService>()` | Register selection scope for back/forward navigation — see [navigation.md](navigation.md) |
112+
| `IAssetEditorsManager` | `ServiceProvider.TryGet<IAssetEditorsManager>()` | Open or close other asset editors programmatically |
113+
114+
Use `TryGet<T>()` for optional services; `Get<T>()` throws if the service is not registered.
115+
116+
`UndoRedoService` is also available as a shorthand property on `AssetEditorViewModel` (equivalent to `ServiceProvider.Get<IUndoRedoService>()`).
117+
118+
## MVVM Patterns
119+
120+
### Binding a property with automatic undo/redo
121+
122+
`MemberGraphNodeBinding<T>` wraps a Quantum `IMemberNode`; get/set route through the binding and undo/redo is handled automatically. Obtain the root `IObjectNode` via `Session.AssetNodeContainer` (see [quantum/asset-graph.md](../quantum/asset-graph.md)):
123+
124+
```csharp
125+
private readonly MemberGraphNodeBinding<Color> colorBinding;
126+
127+
public %%AssetName%%EditorViewModel([NotNull] %%AssetName%%ViewModel asset)
128+
: base(asset)
129+
{
130+
// rootNode is an IObjectNode obtained via Session.AssetNodeContainer.
131+
// See docs/quantum/asset-graph.md for how to retrieve it.
132+
colorBinding = new MemberGraphNodeBinding<Color>(
133+
rootNode[nameof(%%AssetName%%.Color)], // IMemberNode
134+
nameof(%%AssetName%%EditorViewModel.Color), // ViewModel property name
135+
OnPropertyChanging,
136+
OnPropertyChanged,
137+
UndoRedoService);
138+
}
139+
140+
public Color Color { get => colorBinding.Value; set => colorBinding.Value = value; }
141+
```
142+
143+
### Manual transaction wrapping
144+
145+
For mutations that bypass the node graph (direct collection changes, renaming, structural operations):
146+
147+
```csharp
148+
using (var transaction = UndoRedoService.CreateTransaction())
149+
{
150+
// perform mutations here
151+
UndoRedoService.SetName(transaction, "Descriptive operation name");
152+
}
153+
```
154+
155+
See [undo-redo.md](undo-redo.md#wrapping-a-mutation) for the full pattern including `AnonymousDirtyingOperation`.
156+
157+
### Commands
158+
159+
```csharp
160+
public ICommandBase DoSomethingCommand { get; }
161+
162+
public %%AssetName%%EditorViewModel([NotNull] %%AssetName%%ViewModel asset)
163+
: base(asset)
164+
{
165+
DoSomethingCommand = new AnonymousTaskCommand(ServiceProvider, DoSomethingAsync);
166+
}
167+
168+
private async Task DoSomethingAsync()
169+
{
170+
using var transaction = UndoRedoService.CreateTransaction();
171+
// ...
172+
UndoRedoService.SetName(transaction, "Do something");
173+
}
174+
```
175+
176+
## Assembly Placement
177+
178+
| File | Path |
179+
|---|---|
180+
| `%%AssetName%%EditorViewModel.cs` | `sources/editor/Stride.Assets.Presentation/AssetEditors/%%EditorName%%/ViewModels/` |
181+
| `%%AssetName%%EditorView.xaml` | `sources/editor/Stride.Assets.Presentation/AssetEditors/%%EditorName%%/Views/` |
182+
| `%%AssetName%%EditorView.xaml.cs` | `sources/editor/Stride.Assets.Presentation/AssetEditors/%%EditorName%%/Views/` |

docs/editor/editors.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Existing Asset Editors
2+
3+
## Overview
4+
5+
All concrete asset editors live in `Stride.Assets.Presentation` under `sources/editor/Stride.Assets.Presentation/AssetEditors/`. Most editor folders contain a `ViewModels/` subdirectory and a `Views/` subdirectory; ScriptEditor and VisualScriptEditor are exceptions where files sit flat at the folder root. The table below is the entry point for locating any existing editor.
6+
7+
## Editors
8+
9+
| Editor | Asset type | Base class | Game viewport | Folder |
10+
|---|---|---|---|---|
11+
| `SpriteSheetEditorViewModel` | `SpriteSheetAsset` | `AssetEditorViewModel` | No | `SpriteEditor/` |
12+
| `SceneEditorViewModel` | `SceneAsset` | `EntityHierarchyEditorViewModel` | Yes | `EntityHierarchyEditor/` |
13+
| `PrefabEditorViewModel` | `PrefabAsset` | `EntityHierarchyEditorViewModel` | Yes | `EntityHierarchyEditor/` |
14+
| `UIPageEditorViewModel` | `UIPageAsset` | `AssetCompositeHierarchyEditorViewModel` | Yes | `UIPageEditor/` |
15+
| `UILibraryEditorViewModel` | `UILibraryAsset` | `AssetCompositeHierarchyEditorViewModel` | Yes | `UILibraryEditor/` |
16+
| `GraphicsCompositorEditorViewModel` | `GraphicsCompositorAsset` | `AssetEditorViewModel` | No | `GraphicsCompositorEditor/` |
17+
| `ScriptEditorViewModel` | Script assets | `AssetEditorViewModel` | No | `ScriptEditor/` |
18+
| `VisualScriptEditorViewModel` | `VisualScriptAsset` | `AssetEditorViewModel` | No | `VisualScriptEditor/` |
19+
20+
### SpriteSheetEditorViewModel
21+
22+
**What it does:** Lets the user define sprites within a texture — regions, pivot points, borders, and animation frames. Renders a preview using its own lightweight `ViewportViewModel` rather than a full game instance.
23+
24+
**Key types:**
25+
26+
| Class | Role |
27+
|---|---|
28+
| `SpriteSheetEditorViewModel` | Editor ViewModel |
29+
| `SpriteEditorView` | XAML view |
30+
| `ViewportViewModel` | Lightweight render preview (no full game loop) |
31+
32+
**Notable:**
33+
- Uses `ViewportViewModel` for rendering instead of `IEditorGameController` — lighter than a full game editor.
34+
- Implements `IAddChildViewModel` to support dragging textures onto the editor to add new sprites.
35+
36+
### SceneEditorViewModel / PrefabEditorViewModel
37+
38+
**What it does:** Full 3D scene and prefab editing with an entity hierarchy tree, transform gizmos, camera controls, and a live game viewport. Scene and prefab share the same base infrastructure with thin concrete subclasses.
39+
40+
**Key types:**
41+
42+
| Class | Role |
43+
|---|---|
44+
| `EntityHierarchyEditorViewModel` | Shared base ViewModel (`EntityHierarchyEditor/ViewModels/`) |
45+
| `SceneEditorViewModel` | Thin subclass for scenes |
46+
| `PrefabEditorViewModel` | Thin subclass for prefabs |
47+
| `EntityViewModel` | Part ViewModel for each entity in the hierarchy |
48+
| `EditorCameraViewModel` | Camera movement and controls (lives in `GameEditor/ViewModels/`, shared infrastructure) |
49+
| `EntityGizmosViewModel` | Gizmo overlay (translate/rotate/scale handles) |
50+
| `EntityHierarchyEditorView` | Abstract base view |
51+
| `SceneEditorView` / `PrefabEditorView` | Concrete views |
52+
53+
**Notable:**
54+
- Most logic is in `EntityHierarchyEditorViewModel`; the concrete subclasses are thin.
55+
- Scene and prefab differ primarily in which hierarchy root they load and whether archetype (prefab base) linking is active.
56+
- The view code-behind injects the game host into the XAML panel: `SceneView.Content = hierarchyEditor.Controller.EditorHost`.
57+
58+
### UIPageEditorViewModel / UILibraryEditorViewModel
59+
60+
**What it does:** WYSIWYG editing of UI hierarchies — pages (full-screen layouts) and libraries (reusable component collections). Renders elements in a live game viewport with selection adorners, resize handles, and snap guidelines.
61+
62+
**Key types:**
63+
64+
| Class | Role |
65+
|---|---|
66+
| `UIEditorBaseViewModel` | Shared base ViewModel (`UIEditor/ViewModels/`) |
67+
| `UIPageEditorViewModel` | Subclass for UI pages |
68+
| `UILibraryEditorViewModel` | Subclass for UI libraries |
69+
| `UIElementViewModel` | Part ViewModel for each UI element |
70+
| `UIEditorView` | Abstract base view (`UIEditor/Views/`) |
71+
| `UIPageEditorView` / `UILibraryEditorView` | Concrete views |
72+
73+
**Notable:**
74+
- The adorner overlay (guidelines, resize handles) is rendered as a WPF layer on top of the game viewport — one of the few places where WPF and game rendering are explicitly composited.
75+
- `UIEditorBaseViewModel` handles element factories, zoom/pan state, and selection; subclasses add only asset-type-specific root handling.
76+
77+
### GraphicsCompositorEditorViewModel
78+
79+
**What it does:** Visual node-graph editor for the render pipeline. Nodes represent render features and render stages; edges represent data flow between them.
80+
81+
**Key types:**
82+
83+
| Class | Role |
84+
|---|---|
85+
| `GraphicsCompositorEditorViewModel` | Editor ViewModel (`GraphicsCompositorEditor/ViewModels/`) |
86+
| `GraphicsCompositorEditorView` | XAML view |
87+
| `SharedRendererFactoryViewModel` | Factory/list for shared renderer blocks |
88+
| `RenderStageViewModel` | Node ViewModel for render stages |
89+
90+
**Notable:**
91+
- Uses `Stride.Core.Presentation.Graph` for the WPF node-graph canvas.
92+
- Does **not** use `IEditorGameController` — no live game instance; the compositor is a pure data-structure editor.
93+
94+
### ScriptEditorViewModel
95+
96+
**What it does:** Opens a script asset in an embedded code editor. Provides compilation feedback and basic IDE integration within GameStudio.
97+
98+
**Key types:**
99+
100+
| Class | Role |
101+
|---|---|
102+
| `ScriptEditorViewModel` | Editor ViewModel (`ScriptEditor/`) |
103+
| `ScriptEditorView` | XAML view |
104+
105+
**Notable:**
106+
- The lightest editor — mostly a shell around the embedded code editor control.
107+
- Undo/redo is delegated to the code editor itself rather than `IUndoRedoService`; the standard transaction infrastructure does not apply here.
108+
109+
### VisualScriptEditorViewModel
110+
111+
**What it does:** Node-graph editor for visual scripting. Blocks represent operations; edges represent data and control flow between them.
112+
113+
**Key types:**
114+
115+
| Class | Role |
116+
|---|---|
117+
| `VisualScriptEditorViewModel` | Editor ViewModel (`VisualScriptEditor/`) |
118+
| `VisualScriptMethodEditorViewModel` | Per-method graph editing |
119+
| `VisualScriptBlockViewModel` | Node ViewModel for each block |
120+
| `VisualScriptLinkViewModel` | Edge ViewModel for each connection |
121+
122+
**Notable:**
123+
- Similar to `GraphicsCompositorEditorViewModel` in structure: a pure data-structure editor with no game instance.
124+
- ViewModel files sit at the folder root (no `ViewModels/` subdir); views are in `Views/` as usual.
125+
126+
## Shared Game Editor Infrastructure
127+
128+
All game-viewport editors (Scene, Prefab, UIPage, UILibrary) inherit from `GameEditorViewModel` and run a game instance via `IEditorGameController`. The controller manages the game loop on a background thread; results are marshalled back to the ViewModel via `IDispatcherService`.
129+
130+
**Key implication for contributors:** property changes in these editors can originate from either the UI thread (user interaction) or the game thread (simulation update). Code that modifies ViewModel state from the game thread must dispatch to the UI thread via `Asset.Dispatcher` (`AssetEditorViewModel.Asset` inherits `Dispatcher` from `DispatcherViewModel`):
131+
132+
```csharp
133+
Asset.Dispatcher.InvokeAsync(() =>
134+
{
135+
// safe to update ViewModel properties here
136+
});
137+
```

0 commit comments

Comments
 (0)