# Editor overhaul
The owner asked for a large design and feature update of the egui editor,
aiming at a real-engine editor. The visual and interaction direction is
Blender + Godot. The work is split into waves; each wave ships working,
tested code before the next begins.
## Wave 1 (in progress): visual pass, inspector, hierarchy
### Visual design pass
- New `EditorTheme` palette: Godot-style dark neutral panels with one accent
color, Blender-style compact spacing and area headers.
- Every dock area gets a header strip with an editor-type switcher (the
`dock.rs` area tree stays the layout model).
- One toolbar style and one icon set across all panels.
- Theme and UI scale persist per user (roadmap Milestone 6, "DPI scaling,
font configuration, and theme persistence").
### Inspector (property-row system)
- Module `src/editor/inspector/`:
- `widgets.rs`: Godot-style property rows (label left, widget right) and
shared widgets: vec3 with colored X/Y/Z fields, color, angle in degrees,
enum, asset handle.
- `json.rs`: custom (game-defined) components are stored as JSON. Their
values render as typed widgets: numbers as drag values, bools as
checkboxes, 3-number arrays as vec3, strings as text, nested objects as
sub-sections. Raw JSON text editing is removed from the normal path.
- `mod.rs`: the Inspector panel; built-in components are drawn as
collapsible sections using the shared widgets.
- Every edit still flows through the snapshot undo system.
### Hierarchy
- Search/filter box.
- Per-type icons (camera, light, mesh, empty).
- Blender-style visibility eye toggle.
- Double-click inline rename.
- Drag and drop reparenting (roadmap Milestone 6).
- Right-click context menu: add child, duplicate, rename, delete.
- Multi-select (Ctrl toggles, Shift selects a range). The selection has a
primary entity; the gizmo and Inspector act on the primary, delete and
duplicate act on the whole selection.
### Wave 1 progress
Done (unit tests in `editor::hierarchy`, `editor::inspector::{widgets, json}`):
- Palette, compact density, flat list rows, area chrome.
- Hierarchy: search that keeps ancestors of matches, per-type icons, eye
toggle (`EntityRequest::SetVisible`, undoable), double-click / F2 rename,
Delete deletes the selection, dragging a selected row reparents the
whole selection, multi-select with Ctrl and Shift and a
primary object (`EditorState::selection` + `selected`), delete and
duplicate act on the whole selection.
- Inspector module with property rows, axis-colored vec3, color, angle,
choice, and Godot-style collapsible sections; custom JSON components are
edited as typed fields live, and drags coalesce into one Undo step.
- UI scale and text size persist per user (`EditorPreferences`, View >
UI Scale / Text Size). There is one palette, so no theme choice is stored
yet. Widgets with a hard-coded `FontId` ignore the text size.
- Editor-type icon in each area header.
- Hiding a parent hides its children in rendering.
- Viewport shows bounds for every selected object (primary in yellow, others
in orange); Shift-click in the viewport adds, activates, or deselects
Blender style.
- Hierarchy rows hidden by themselves or an ancestor are dimmed.
- Scene View mouse navigation: middle drag orbits around a pivot in front of
the camera, Shift + middle drag pans, the wheel dollies toward the pivot,
and F (focus) moves the pivot onto the selected object. Right drag still
flies.
- Project panel "Scene Rendering" section: Quality (Auto, Eco, Balanced,
High) and Culling (Auto, Off, Frustum, Frustum + Occlusion). Both are
saved in the scene file (format 6) and load with the scene in the
exported game. Edits are undoable scene changes (unit test
`scene_render_settings_edits_are_undoable_scene_changes`).
- Inspector "Material" section for the selected Mesh Renderer: model, alpha
mode and cutoff, base color and opacity, metallic, roughness, emissive,
and the five texture slots. Each slot picks a loaded texture or loads an
image file (copied into the project `assets` folder). "New Material"
gives the object its own default material. Editing a material that other
renderers share copies it first. Edits are undoable (unit test
`material_edits_copy_shared_materials_and_edit_owned_ones_in_place`).
- Native file dialogs run on a worker thread (`editor::file_dialogs`), so
the window keeps answering the window manager and is not reported as "not
responding". While a dialog is open, a small modal blocks the panels.
- Cameras and lights draw Blender-style wire shapes in the Scene View
(camera frustum with an up triangle, sun with rays and a direction line,
point light circles, spot cone) in the selection colors. Clicking within
6 points of a shape selects the object (unit test
`clicks_near_light_and_camera_shapes_select_them`).
- glTF models are added to the scene like Blender's importer: one new empty
object named after the file holds the file's node tree (names, local
transforms, meshes, materials, cameras, lights). "Add to Scene" in the
Assets panel and "Import Files..." both do this, select the new root, and
are undoable. The old "Imported glTF primitives / Use on Selected" list is
gone. Primitives without normals get flat face normals, as glTF requires
(unit test `added_models_keep_their_node_tree_under_one_saved_root`).
- Assets area is a Godot-style FileSystem tree (`editor::assets_panel`):
folders first and foldable, type icons, colored type badges, a filter
that opens folders on the way to matches, and hidden `.rmesh`/`.rtexture`
caches. Double-click adds a model to the scene or puts an image on the
selected object as its base color; right-click lists the actions; models
drag into the Scene View. The "Loaded textures" text list is gone (unit
test `rows_put_folders_first_hide_caches_and_fold`).
- Image rows show a thumbnail (larger on hover) and drag onto a Hierarchy
object (base color) or an Inspector texture slot (unit tests
`image_rows_decode_a_few_thumbnails_per_frame`,
`dropping_an_image_on_hierarchy_rows_and_texture_slots_assigns_it`).
Open:
- Toolbar consistency pass.
- egui's built-in Ctrl +/- zoom is not persisted; only View > UI Scale is.
- Asset handle fields show the raw key; show the asset path and accept drops
once the asset browser exists.
## Backlog (later waves)
Kept here so it is not lost between sessions. Roadmap milestones that own an
item are named in parentheses.
### Scene editing UX
- Frame all (Home) and Blender numpad views (front, side, top) (M6).
- Selection outline readable behind other geometry (M6).
- Camera and light shapes: the camera frame is fixed at 16:9, the point
light has three circles instead of a view-facing one, and there are no
per-type icons. Hidden objects still draw their shape.
- Gizmo polish: local/global modes, snapping, visual restyle (M6).
- Multiple viewports and orthographic views (M20).
- Game components the editor has no registration for (for example a
game's `coin_run.spin`) load as `UnregisteredComponents` and save back
unchanged, but the Inspector does not show them. Show them as read-only
JSON first, then editable JSON validated on the next game load.
- Scene instances (prefabs): changes to placed objects save as overrides,
but nothing shows which fields are overridden. The Inspector should mark
overridden fields with a per-field revert arrow, as Godot does (only
whole-object and whole-instance revert exist). Unpack is always complete;
a one-level unpack that keeps nested instances linked is missing, and
Undo does not restore a source file written by Apply to Source. The
Hierarchy should mark instance roots and tint their children,
editing `source` in the Inspector should re-read the scene at once, and
scenes should also be draggable into the Scene View like models.
- Reflected Inspector gaps: string-keyed maps (such as `TileMap.tiles`)
edit existing entries but cannot add or rename keys; field `doc` hints
are not shown as tooltips; the asset drop-down for handle fields lists
only assets that are already loaded, with no file browser or drag from
the Assets panel.
### Asset workflow
- Asset browser: a grid view, thumbnails decoded on a worker and refreshed
when the file changes, and dropped models placed under the cursor instead
of at the origin (M6).
- glTF import: vertex colors (`COLOR_0`), `KHR_texture_transform`, and
import options (scale, up axis, "apply transforms") are not read.
Re-adding a file imports its meshes again instead of sharing them.
- Typed asset assignment follow-up: standalone reusable collider shape
assets do not exist yet. Mesh colliders use the renderer's typed mesh
handle, while primitive collider shapes are typed enum variants.
- Per-asset import settings and re-import (M20). The library and CLI side
exists (`crate::asset_import`, `rusting asset import|reimport|list`,
`<file>.rmeta` sidecars with ID, settings, dependencies and provenance).
The asset browser has a Replace⦠action with a dry-run preview and hides
`.rmeta` sidecars. It still needs an import dialog with provenance fields
(its Import button copies files without a sidecar), license/changed/
not-imported badges, and a Generate dialog that runs a `project.json`
generator hook with a prompt (`rusting asset generate` covers it today).
- Art-direction presets: `rusting preset apply` has no editor picker yet.
Presets should skip ACES tone mapping and keep HUD size hierarchy on 2D
unlit scenes; the starter exercise corrects both by patch today.
Edit mode does not run App schedules, so `rusting.background` only shows
in Play; the viewport should read it directly.
- Image textures always load as sRGB, both from the Inspector and from
scene files. Normal, metal/rough, and occlusion maps need a linear load
path, and the scene file needs to store the color space per slot.
- Material assets as files (`.rmaterial`) that several scenes share. Today
every scene stores its materials inline.
- Data assets: Inspector edits to a `.rdata` file are file writes with no
undo. The Handle field picker lists only data assets that are already
loaded, so it cannot pick an unloaded `.rdata` file. Renamed or removed
fields fail to load instead of migrating. A unique value cannot be
saved out as a new `.rdata` file yet (Godot's Save As on a sub-resource),
and every scene load or Inspector edit of a unique value adds a copy
while the old one stays in memory. A `.rdata` file open in the Inspector
does not re-read changes made outside the editor; the next edit
overwrites them.
- LOD group authoring: edit a mesh's `.rlod` levels and hand-over values in
the Inspector, preview the active level in the viewport, and hot reload
`.rlod` files. Import glTF `MSFT_lod` into LOD groups.
### Panels and tools
- Console: engine code (renderer, physics, runtime) has no log sink, so
only editor messages reach it. Status lines get their level from
keywords; give `scene_message` a level instead.
- Profiler follow-up: add per-system CPU spans, capture/export, and dedicated
GPU allocation accounting to the M6 dockable history panel (M20).
- Render settings and physics diagnostics follow-up: expose more optional
device capabilities and explain capacity warnings inline. The M6 panels
already show `RenderCapacityDiagnostics`, including missing asset counts.
- Environment panel for the scene-wide `AmbientLight`, `SkyLight`, and
`ToneMapping` components, like Godot's `WorldEnvironment` (M6).
- Shortcut follow-ups: show each binding next to its Edit menu entry, allow
more than one key per action (Blender deletes with both X and Delete), and
make modal keys (Escape cancels a gizmo drag) rebindable.
- Global scaling at arbitrary object angles is axis weighted because scene
transforms store rotation and scale without shear; support exact global
scaling if the scene transform format gains a shear component.
- Project settings and input map editors (M20).
- Project-wide search (M20).
- 2D path: Edit mode does not run App schedules, so a `rusting.tile_map`
shows no tiles until Play. Run `build_tile_maps` in Edit mode (or draw a
preview), and add a tile painter.
### Play workflow
- Native Stop/Restart and embedded Preview with Pause/Step are implemented
in M6. Preview runs the editor's ECS systems and restores the authored
scene, undo history, and transient entities on Stop.
- Reload Code: reload automatically when a project `.rs` file is saved.
Keep resources across a reload (they need a reflection registration
first). A reload restarts the game process, so the window and GPU
resources are created again; the Windows path, where a running game's
executable is locked, has not been tried.
- Remote scene tree and inspector for a running game (M20).
- Show `RuntimeUi` in the embedded Preview. Update systems already draw
into the App's `RuntimeUi` pass while Preview runs, but the Game area does
not paint that output or forward pointer and key input to it. Native Play
shows it because the game window runner paints it.
### Rendering integration
- Smoke-test editor window resize and close-button shutdown by hand after
the `EditorWindowRunner::draw` split; an 8-second launch passed (M1).
- Engine-owned egui painter is done (`rendering::egui_painter`). Texture
uploads wait on their own submission; replace with a staging ring if the
stall shows up in profiles.
- Golden-image test for editor compositing (M6 exit gate).
- Show the resolved quality profile when Quality is Auto, and the LOD
groups loaded next to meshes, in the stats area.
- Profiler area with per-pass GPU timestamps and CPU/GPU history is done.
Add per-pass history selection and frame capture/export (M20).
- Anti-aliasing and shadow quality in Render Settings are session
options. Save them in project settings (or `SceneRenderSettings`, which
needs cooked scene format 8 with a legacy struct for format 7).
- Render Bounds are edited as numbers in the Inspector only. Add viewport
handles to drag box faces and the sphere radius, plus a "Fit to Mesh"
action. Multi-selection edits only the active object.
### Deferred from the `issues.md` audit
Items the audit found but left open, with the reason for each.
- Partial instance-buffer uploads. They need one instance buffer per frame
in flight plus dirty tracking. The unused `dirty_ranges` code was removed.
- Reuse scratch `Vec`s in extraction, and avoid cloning blended instances
before sorting. Minor cost.
- Sweep file-backed meshes and textures after a scene `Replace` load. Ten
call sites load with `Replace`, and game code can hold path-loaded handles
without `retain`, so an automatic sweep could free live assets. It needs
handle ownership first.
- Make `Assets::len()` O(1) and move the hot-reload file scan to a worker.
Both scans are small today.
- Remove the legacy engine's double fence wait. Removing it creates a
write-after-read hazard on the indirect and physics buffers against the
previous frame's draw. Check on real hardware with validation layers.
- Rewrite the legacy camera sign convention and remove `camera_rotate`. Needs
visual checks. Only the `cgmath` removal was done.
- Replace the remaining `unwrap` calls in the legacy engine with errors.
- Enable device features at device creation instead of assuming them.
- Cache the Hierarchy tree and rebuild it only on change detection.
- Check the resource-state hazard with validation layers on real hardware.
- Use the GPU pose in the editor during Play. Bodies with
`PhysicsSyncMode::SelectedState` or `FullState` get a `GpuStateMirror`,
but picking, focus, the gizmo, and Save still read the authored
`Transform`. Picking and focus should prefer the mirror when present; a
"Keep simulated pose" action should copy it into `Transform` through undo.
### Outside scene edits
- The editor reloads a clean scene when `rusting scene patch` or another
tool changes its file, and refuses one Save when it has unsaved edits.
Add a conflict dialog that shows the patch diff and offers keep mine,
take theirs, or a per-field merge. The watch compares the file time
first; switch to a file watcher if coarse clocks miss changes.