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,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`.
|
||||
Reference in New Issue
Block a user