Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# Editor Shell
|
||||
|
||||
The Stage-6 docking **shell** is the editor's host frame: the top menu bar,
|
||||
the bottom status bar, the dockable panel area, the Preferences window, and
|
||||
the wiring between every Stage-6 framework piece — command stack, project
|
||||
system, settings, file watcher, and the
|
||||
[module → editor extension API](editor-extensions.md).
|
||||
|
||||
Lives in [`oxide_editor::shell`](../editor/src/shell.rs) (the library) with
|
||||
[`oxide-editor`](../editor/src/main.rs) (the binary) acting as glue: open a
|
||||
window, run the egui paint pump, run the 3D viewport, hand events to the
|
||||
shell. Splitting the shell into the library lets it be unit-tested without
|
||||
spinning up a window.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ File Edit View Project Modules Help ⚙ Prefs │ ← menu bar
|
||||
├────────────┬───────────────────────────┬────────────────────┤
|
||||
│ │ │ │
|
||||
│ Hierarchy │ Viewport │ Inspector │
|
||||
│ │ │ │
|
||||
│ ├───────────────────────────┤ │
|
||||
│ │ Project │ Console │ │
|
||||
│ │ │ │
|
||||
├────────────┴───────────────────────────┴────────────────────┤
|
||||
│ Reloaded 3 asset(s) modules: 0 undo: 2 │ ← status bar
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Panels are dockable: drag a tab to re-dock, resize splits, or pop it out as a
|
||||
floating window — provided by [`egui_dock`](https://docs.rs/egui_dock). The
|
||||
default layout is built once in [`Shell::default_dock`]; persisting the user's
|
||||
layout across restarts is a later refinement.
|
||||
|
||||
## What's wired to what
|
||||
|
||||
| Shell surface | Backing system |
|
||||
|---------------|----------------|
|
||||
| File menu → New / Open / Save / Recent / Quit | [`Project`](projects.md) + `RecentProjects` + `Shell::take_quit_request` |
|
||||
| Edit menu → Undo / Redo (Ctrl+Z / Ctrl+Y) | [`CommandStack`](../editor/src/command.rs) |
|
||||
| View menu → Show/Hide panel toggles | dock state |
|
||||
| Project menu → Open project info | `EditorState::project` |
|
||||
| Modules menu → module-contributed items | [`EditorExtensions`](editor-extensions.md) |
|
||||
| Help → About | static info |
|
||||
| Status bar → live hint (last action, errors) | `StatusLine` (timed TTL) |
|
||||
| Status bar → module / undo counters | `EditorExtensions` + `CommandStack` |
|
||||
| Preferences window → settings sections + module on/off | [`Settings`](settings.md) + `EditorExtensions` |
|
||||
| File watcher (on project open) → `AssetServer::reload_path` | [`FileWatcher`](file-watching.md) |
|
||||
|
||||
## Commands and undo
|
||||
|
||||
`Edit` menu shows the labels of the next undo / redo entry; `Ctrl+Z` /
|
||||
`Ctrl+Y` (also `Ctrl+Shift+Z`) drive them. The shortcut router is
|
||||
[`Shell::try_consume_shortcut`] — same path the menu uses, so the unit tests
|
||||
exercise the real flow.
|
||||
|
||||
The first wired commands ([`SetTransformCmd`], [`RenameCmd`]) live in
|
||||
[`oxide_editor::commands`](../editor/src/commands.rs).
|
||||
[`SetTransformCmd::merge`] coalesces consecutive edits to the same entity, so
|
||||
a slider drag (or a future gizmo drag) is **one** undo entry instead of one
|
||||
per frame.
|
||||
|
||||
Structural edits (spawn / despawn / reparent / change mesh) still bypass the
|
||||
stack today — round-tripping a despawn through undo needs stable entity ids,
|
||||
a Stage-7 design step alongside the gizmos.
|
||||
|
||||
## File watcher
|
||||
|
||||
[`Shell::open_project`] (or `create_project`) attaches a
|
||||
[`FileWatcher`](file-watching.md) over the project's `assets/`, `scenes/`, and
|
||||
`scripts/` directories (~150 ms debounce window). The shell's
|
||||
[`frame_tick`](../editor/src/shell.rs) pumps events through
|
||||
[`reload_changed_assets`](file-watching.md) each frame, so editing a file
|
||||
externally hot-reloads any handle that was already loaded. Closing the
|
||||
project tears the watcher down.
|
||||
|
||||
Backends that don't deliver events (some sandboxed CI environments) log a
|
||||
warning and let the editor keep running — the watcher is best-effort.
|
||||
|
||||
## Module integration
|
||||
|
||||
Anything a module registers via the [extension API](editor-extensions.md) is
|
||||
hosted by the shell with no editor-source edits:
|
||||
|
||||
- **Menu items** appear under the `Modules` top menu (shown only when at
|
||||
least one item is registered).
|
||||
- **Panels** appear in the dock as `PanelKind::Custom(name)` tabs, rendered
|
||||
through the module's `FnMut(&mut egui::Ui)` closure.
|
||||
- **Settings pages** + module on/off checkboxes are surfaced in the
|
||||
Preferences window's sidebar.
|
||||
- **Component inspectors** (Stage 7+) will be looked up by reflection name
|
||||
when the Inspector encounters a selection holding that component.
|
||||
|
||||
Disabling a module in Preferences hides every contribution at once but keeps
|
||||
it registered — re-enabling restores it instantly, no shell rebuild.
|
||||
|
||||
## Why a `Shell` library
|
||||
|
||||
A few reasons it lives in `editor/src/shell.rs` rather than `main.rs`:
|
||||
|
||||
- **Unit-testable behavior.** Shortcut routing, project open/close, recent
|
||||
list updates, command stack lifecycle — all exercised without a window.
|
||||
The maintainer's manual pass focuses on what tests *can't* prove: how the
|
||||
UI looks and feels.
|
||||
- **Reusable in tests and future hosts.** A headless reproducer for a UI bug
|
||||
can drive the shell directly; an alternate front-end (web, embedded) could
|
||||
reuse it.
|
||||
- **Separation of concerns.** The binary stays a thin runner — window event
|
||||
loop, 3D viewport, egui paint pump — while the shell owns editor state,
|
||||
layout, and the framework wiring.
|
||||
|
||||
## Viewport camera modes (Stage 7)
|
||||
|
||||
The viewport has two camera schemes; the active one is toggled with
|
||||
**F** (the default binding for the `editor.camera.toggle_flythrough`
|
||||
action) while the cursor is over the Viewport tab. Bindings live in
|
||||
`EditorState::actions` ([`ActionMap`](input.md)) registered with editor
|
||||
defaults at startup; the [Input Bindings](#input-bindings-preferences-page)
|
||||
preferences page exposes them for remapping.
|
||||
|
||||
| Mode | Controls |
|
||||
|------|----------|
|
||||
| **Orbit** (default) | L-drag = orbit · R-drag = pan · scroll = zoom · click = pick |
|
||||
| **Flythrough** | WASD = forward/back + strafe · QE = down/up · Shift = sprint · R-drag = look · scroll = adjust move speed · click = pick |
|
||||
|
||||
Toggling preserves pose: the new camera lands looking at the same view
|
||||
the previous one was showing, so the scene doesn't snap.
|
||||
|
||||
## Input Bindings preferences page (Stage 7 piece 5)
|
||||
|
||||
The Preferences window's `input.bindings` section renders a rich page
|
||||
listing every registered editor action — buttons, 1D axes, 2D axes — with
|
||||
its current bindings. Each binding cell:
|
||||
|
||||
- Clicking it arms a **capture** for that slot. The page shows
|
||||
"Press a key…"; the next non-`Escape` key or mouse button press
|
||||
becomes the binding. `Escape` cancels.
|
||||
- `✕` removes that binding.
|
||||
- `+` (per direction or per action) starts an append capture so the user
|
||||
can add a binding without replacing one.
|
||||
- `↺` (per action) restores that action to its code-defined defaults.
|
||||
- A global **Restore all defaults** button at the top resets every
|
||||
action.
|
||||
|
||||
Edits flow through [`Shell`](../editor/src/shell.rs)'s
|
||||
`try_complete_capture`, which mutates `EditorState::actions`, syncs the
|
||||
new bindings into the `input.bindings` settings section via
|
||||
`sync_action_overrides_to_settings`, and flips a `bindings_dirty` flag.
|
||||
The host runner reads-and-clears the flag each frame and writes the
|
||||
preferences file to `$XDG_CONFIG_HOME/oxide/editor.ron` (or
|
||||
`$HOME/.config/oxide/editor.ron`). On startup the editor reads that
|
||||
file, calls `Settings::import`, and `apply_action_overrides_from_settings`
|
||||
layers the user's remap on top of the defaults — so a remap survives a
|
||||
restart, and removing an action from code never breaks an old file
|
||||
(unknown sections are silently skipped).
|
||||
|
||||
The page is intentionally a built-in Shell feature rather than going
|
||||
through `EditorExtensions::add_settings_page`: it needs to mutate
|
||||
`EditorState::actions` while a capture is in flight, which is more
|
||||
direct from the Shell than through the extension API's `FnMut(&mut Ui)`
|
||||
contract.
|
||||
|
||||
## What's not yet here
|
||||
|
||||
| Feature | Where it lands |
|
||||
|---------|---------------|
|
||||
| Native New/Open dialogs (`rfd` or similar) | Polish; the in-app text-path modals fill the gap today |
|
||||
| ~~3D viewport with its own projection sized to the Viewport tab~~ | ✅ Landed in Stage 7 piece 6b: `FrameContext::viewport_rect` restricts the wgpu viewport and drives the projection aspect; `Viewport::pick` rebases the cursor to tab-local NDC. |
|
||||
| Layout persistence across restarts | After settings sections are richer (Preferences-driven) |
|
||||
| Inspector via reflection-keyed component editors | Stage 7 alongside the gizmos |
|
||||
| Undo/redo for spawn/despawn/reparent | Stage 7 — needs stable entity ids |
|
||||
| Console wired to a real log feed / Stage-10 terminal | Stage 10 |
|
||||
|
||||
[`Shell::default_dock`]: ../editor/src/shell.rs
|
||||
[`Shell::try_consume_shortcut`]: ../editor/src/shell.rs
|
||||
[`Shell::open_project`]: ../editor/src/shell.rs
|
||||
[`SetTransformCmd`]: ../editor/src/commands.rs
|
||||
[`SetTransformCmd::merge`]: ../editor/src/commands.rs
|
||||
[`RenameCmd`]: ../editor/src/commands.rs
|
||||
Reference in New Issue
Block a user