# 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`.