9eead719b0
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>
145 lines
5.4 KiB
Markdown
145 lines
5.4 KiB
Markdown
# 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`.
|