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:
Homer Simpson
2026-07-05 20:41:02 +02:00
parent 2afb56b329
commit 9eead719b0
157 changed files with 47270 additions and 2 deletions
+144
View File
@@ -0,0 +1,144 @@
# Windowing & the Application Loop
Stage 2 reference for `oxide_engine::window` — opening a window, running the
event loop, and receiving raw input. For what happens *inside* a frame (GPU
setup, clearing, resize handling) see [render-context.md](render-context.md).
## Overview
The window module wraps [`winit`](https://docs.rs/winit) so applications never
talk to the event loop directly. You implement the `WindowApp` trait, hand it
to `run()` together with a `WindowConfig`, and the engine:
1. creates the window and the GPU [`RenderContext`](render-context.md),
2. calls `WindowApp::init` once,
3. then loops: forwards every raw window event to `WindowApp::event`, calls
`WindowApp::update` once per frame, and clears + presents the surface.
The loop runs in `Poll` mode (continuous rendering, as a game expects), not
event-driven `Wait` mode (as a desktop utility would use).
> **Stage 6 rename.** This trait was originally `App`. Stage 6 renamed it to
> `WindowApp` so the engine's [`oxide_engine::app::App`](modules.md) container
> (scene, assets, scheduled systems) could live in the prelude unambiguously.
> The two cover different roles: this trait is the per-frame window/event
> handler; the container is engine state your handler typically wraps around.
## Minimal application
```rust
use oxide_engine::prelude::*;
use oxide_engine::window::event::{Key, NamedKey, ElementState, WindowEvent};
#[derive(Default)]
struct MyApp;
impl WindowApp for MyApp {
fn init(&mut self, ctx: &mut AppCtx<'_>) {
ctx.set_clear_color(Color::rgb(0.39, 0.58, 0.93));
}
fn event(&mut self, ctx: &mut AppCtx<'_>, event: &WindowEvent) {
if let WindowEvent::KeyboardInput { event: key, .. } = event {
if key.state == ElementState::Pressed
&& key.logical_key == Key::Named(NamedKey::Escape)
{
ctx.request_exit();
}
}
}
fn update(&mut self, ctx: &mut AppCtx<'_>) {
let _seconds_since_last_frame = ctx.dt;
}
}
fn main() -> anyhow::Result<()> {
run(WindowConfig::default(), MyApp)
}
```
`run()` blocks the calling thread until the app exits — an OS requirement (the
event loop must own the main thread), not an engine choice.
## `WindowConfig`
Initial window settings. All fields are plain data:
| Field | Default | Meaning |
|-------|---------|---------|
| `title` | `"Oxide"` | Window title |
| `width`, `height` | 1280 × 720 | Initial inner size, logical pixels |
| `resizable` | `true` | Whether the user can resize |
| `clear_color` | `Color::BLACK` | Initial per-frame clear color |
## The `WindowApp` trait
Three callbacks, all optional (empty default bodies):
- **`init(ctx)`** — once, after the window and GPU exist, before the first
frame. Set the title, clear color, load resources.
- **`event(ctx, event)`** — for *every* raw `WindowEvent`, including ones the
engine also reacts to (close request, resize), so apps can observe
everything. Stage 2 exposes events untranslated; the
[Stage-7 input system](input.md) layers per-key edge detection and
remappable named actions on top, surfaced through `ctx.input()`.
- **`update(ctx)`** — once per frame, before the frame is cleared and
presented. `ctx.dt` is the seconds elapsed since the previous frame (`0.0`
on the first).
Per frame the order is: pending `event` calls → `update` → render.
## `AppCtx`
Every callback receives `&mut AppCtx`, the engine state an app may touch:
| Member | Purpose |
|--------|---------|
| `dt` | Frame delta time in seconds (field) |
| `set_clear_color(color)` / `clear_color()` | Per-frame clear color; changes apply on the next frame |
| `size()` | Current surface size in physical pixels |
| `set_title(title)` | Change the window title |
| `request_exit()` | Leave the event loop after the current callback |
| `render()` | Direct access to the [`RenderContext`](render-context.md) |
| `input()` | The per-frame [`InputState`](input.md) snapshot |
## Raw event types
`oxide_engine::window::event` re-exports the `winit` event vocabulary
(`WindowEvent`, `KeyEvent`, `MouseButton`, `ElementState`, `KeyCode`,
`PhysicalKey`, `Key`, `NamedKey`, `ModifiersState`, …) so applications don't
need their own `winit` dependency. The whole crates are also available as
`oxide_engine::winit` and `oxide_engine::wgpu` for anything not curated.
Two keyboard representations matter:
- `KeyEvent::physical_key` (`PhysicalKey::Code(KeyCode::KeyW)`) — the physical
key position, layout-independent. Use for game-style controls.
- `KeyEvent::logical_key` (`Key::Named(NamedKey::Escape)` or
`Key::Character(…)`) — what the key means under the user's layout. Use for
shortcuts and text.
## Engine-handled events
The runner reacts to these before forwarding them:
| Event | Engine behavior |
|-------|-----------------|
| `CloseRequested` | Exits the loop (apps can't veto it in Stage 2) |
| `Resized` | Reconfigures the surface (see [render-context.md](render-context.md)) |
| `RedrawRequested` | Computes `dt`, calls `update`, renders the frame |
Errors during window/GPU creation or rendering are returned from `run()`;
winit callbacks can't propagate `Result`, so the runner stashes the first
error and exits the loop.
## Trying it
```sh
cargo run -p oxide-examples --bin hello_window
```
Keys `1``5` switch clear-color presets, `Space` cycles, `Esc` quits; average
FPS is logged once per second. The editor (`cargo run -p oxide-editor`) uses
the same infrastructure and quits with `Ctrl+Q`.