# retroglyph-window - Complete API Documentation > Shared winit windowing layer for retroglyph's windowed backends **Version:** 0.0.0 **Authors:** Matan Lurey **License:** MIT **Repository:** https://github.com/crates-lurey-io/retroglyph **Keywords:** roguelike, terminal, grid, gamedev Generated: 2026-08-05 20:21:10 UTC Created by: [cargo-llms-txt](https://github.com/masinc/cargo-llms-txt) ## Table of Contents ### src/backend.rs - pub struct WindowBackend - impl WindowBackend - impl Output for WindowBackend - impl Input for WindowBackend - impl Cursor for WindowBackend - impl Output for tests::NullPresenter - impl Presenter for tests::NullPresenter ### src/lib.rs - pub mod atlas - pub mod backend - pub mod clipboard - pub mod font - pub mod geometry - pub mod palette - pub mod presenter - pub mod sprite_cache - pub mod tileset - pub mod web - pub mod winit - pub use backend::WindowBackend - pub use clipboard::SystemClipboard - pub use clipboard::{Clipboard, ClipboardError} - pub use geometry::CellGeometry - pub use presenter::{GenericSurfaceError, Presenter, RecoverableError, WindowHandle, cell_art_glyph} - pub use raw_window_handle ### src/palette.rs - pub const DEFAULT_FG - pub const DEFAULT_BG ### src/sprite_cache.rs - pub struct Sprite - impl Sprite - pub struct SpriteCache - impl SpriteCache - impl Default for SpriteCache - pub struct SpriteTint - impl SpriteTint - pub fn warn_sprite_needs_span - pub fn warn_tint_needs_sprite ### src/geometry.rs - pub struct CellGeometry - impl CellGeometry ### src/web.rs - pub fn winit_canvas ### src/font.rs - pub struct BitmapFont - impl BitmapFont - impl PartialEq for BitmapFont - impl Eq for BitmapFont - pub struct ResolvedGlyph - impl ResolvedGlyph - pub struct FontChain - impl From for FontChain - impl FontChain - pub mod unscii16 - pub const unscii16::FONT - pub mod legacy_computing - pub mod legacy_computing::blocks - pub const legacy_computing::blocks::FONT - pub mod legacy_computing::braille - pub const legacy_computing::braille::FONT ### src/clipboard.rs - pub trait Clipboard - pub struct ClipboardError - impl ClipboardError - impl fmt::Display for ClipboardError - impl std::error::Error for ClipboardError - pub struct SystemClipboard - impl fmt::Debug for SystemClipboard - impl SystemClipboard - impl Clipboard for SystemClipboard - impl Clipboard for tests::FakeClipboard ### src/tileset.rs - pub enum SheetColor - pub enum SpriteAlign - impl SpriteAlign - pub enum TilesetError - impl fmt::Display for TilesetError - impl std::error::Error for TilesetError - pub enum Codepage - impl Codepage - pub const CP437_TO_UNICODE - pub struct TilesetOptions - impl TilesetOptions - pub struct TilesetBuilder - impl TilesetBuilder ### src/presenter.rs - pub trait WindowHandle - impl WindowHandle for T - pub trait RecoverableError - impl RecoverableError for core::convert::Infallible - pub enum GenericSurfaceError - impl fmt::Display for GenericSurfaceError - impl std::error::Error for GenericSurfaceError - impl RecoverableError for GenericSurfaceError - pub trait Presenter - pub fn cell_art_glyph ### src/atlas.rs - pub const ATLAS_COLS - pub const ATLAS_ROWS - pub const SLOTS_PER_LAYER - pub const MAX_SLOTS - pub fn addressable_glyphs - pub struct AtlasGeometry - impl AtlasGeometry - pub struct AtlasData - impl AtlasData - pub struct GlyphAtlas - impl GlyphAtlas ### src/winit/mod.rs - pub mod run - pub mod translate - pub use run::{EventProxy, EventProxyClosed, WindowConfig, run_app, run_app_with_proxy, run_app_with_typed_proxy, run_windowed, run_windowed_with_proxy, run_windowed_with_typed_proxy} ### src/winit/run.rs - pub struct EventProxy - impl Clone for EventProxy - impl fmt::Debug for EventProxy - impl EventProxy - pub struct EventProxyClosed - impl EventProxyClosed - impl fmt::Display for EventProxyClosed - impl std::error::Error for EventProxyClosed - pub struct WindowConfig - impl WindowConfig - pub fn run_windowed - pub fn run_windowed_with_proxy - pub fn run_windowed_with_typed_proxy - pub fn run_app - pub fn run_app_with_proxy - pub fn run_app_with_typed_proxy - impl From for WindowAttrs - impl Default for WindowAttrs - impl WindowApp - impl ApplicationHandler for WindowApp - impl WindowApp - impl Default for tests::MockPresenter - impl Output for tests::MockPresenter - impl Presenter for tests::MockPresenter - impl Output for tests::RecordingPresenter - impl Presenter for tests::RecordingPresenter - impl Output for tests::FailingPresenter - impl Presenter for tests::FailingPresenter - impl crate::presenter::RecoverableError for tests::Unknown - impl core::fmt::Display for tests::UnrecoverableError - impl crate::presenter::RecoverableError for tests::UnrecoverableError - impl Output for tests::FatalPresenter - impl Presenter for tests::FatalPresenter - impl Output for tests::GridRecordingPresenter - impl Presenter for tests::GridRecordingPresenter ### src/winit/translate.rs - pub fn translate_ime - pub fn translate_key - pub fn translate_key_location - pub fn translate_physical_pos - pub fn translate_pixel_to_cell - pub fn translate_mouse_button - pub fn translate_key_event_kind - pub fn translate_modifiers --- ## README.md ### retroglyph-window Shared winit windowing layer for retroglyph's windowed backends Part of the [retroglyph](https://github.com/crates-lurey-io/retroglyph) workspace. --- ## src/backend.rs ### WindowBackend ```rust #[derive(Debug)] pub struct WindowBackend

{ } ``` A [`Backend`](retroglyph_core::backend::Backend) built from a [`Presenter`] plus an input event queue. [`Input`] and [`Output`] are independent facets of `Backend`, which does not fit a window as one type: some event loop owns input, while a per-renderer surface owns output. `WindowBackend` reunites the two (implementing `Output` by delegating to `P`, `Input` via its own event queue, and the no-op default `Cursor`), so [`Terminal`](retroglyph_core::terminal::Terminal) gets the full `Backend` it needs, while renderer crates implement only [`Presenter`]. See the crate-level [Architecture](crate#architecture) section for the data-flow diagram. Because `WindowBackend` owns input, a [`Presenter`] should **not** implement [`Input`] or [`Cursor`] itself for windowed use: those impls would be dead (the event loop pushes to *this* queue, not the presenter's) and would silently miss the `Mouse(Moved)` coalescing that [`push_event`](WindowBackend::push_event) applies. A presenter that also wants a direct headless `Terminal` input path (as `retroglyph-software` does for pixel tests) may still implement `Input` for that path, accepting that a bare queue does not coalesce; a presenter with no such path (as `retroglyph-gl`) implements only `Presenter`. With the `winit` feature enabled, `winit::run_windowed` and `winit::run_app` own the event loop, call `push_event` as winit events are translated, and call [`Presenter::present`] once per frame; callers never touch `WindowBackend` directly. With `winit` disabled, `retroglyph-window` exports no event loop at all: a caller driving its own loop (SDL2, tao, a custom driver) constructs `WindowBackend::new(presenter)` itself, calls `push_event` for each translated input event, and calls `Terminal::present` (which drives `Presenter::flush`) plus `presenter_mut().present()` once per frame. #### Examples ``` use retroglyph_core::backend::{Backend, DrawCell, Input, Output}; use retroglyph_core::event::Event; use retroglyph_core::grid::{Pos, Size}; use retroglyph_core::terminal::Terminal; use retroglyph_core::tile::Tile; use retroglyph_window::{Presenter, WindowBackend, WindowHandle}; use std::sync::Arc; use std::time::Duration; struct NullPresenter; impl Output for NullPresenter { type Error = core::convert::Infallible; fn draw<'a, I>(&mut self, _content: I) -> Result<(), Self::Error> where I: Iterator>, { Ok(()) } fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error> where I: Iterator>, { Ok(()) } fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } fn size(&self) -> Size { Size::new(4, 2) } fn clear(&mut self) -> Result<(), Self::Error> { Ok(()) } fn resize(&mut self, _size: Size) {} } impl Presenter for NullPresenter { type SurfaceError = core::convert::Infallible; fn init_surface(&mut self, _window: Arc) -> Result<(), Self::SurfaceError> { Ok(()) } fn resize_surface(&mut self, _width: u32, _height: u32) {} fn present(&mut self) -> Result<(), Self::SurfaceError> { Ok(()) } fn cell_size(&self) -> (u32, u32) { (8, 16) } } // A caller driving its own loop (SDL2, tao, a hand-rolled driver) builds // `WindowBackend` directly, no `winit` feature required. let backend = WindowBackend::new(NullPresenter); let mut term = Terminal::new(backend); // The loop pushes each translated input event onto the queue... term.backend_mut().push_event(Event::FocusGained); // ...and the app drains it through the normal `Terminal` polling API, // which never blocks for `WindowBackend`. while term.poll(Duration::ZERO).is_some() {} // Once per frame: `Terminal::present` diffs the grid and drives // `Presenter::flush`, then the caller drives `Presenter::present` itself // to push pixels to the window. term.present().unwrap(); term.backend_mut().presenter_mut().present().unwrap(); ``` [`poll_event`](Input::poll_event) never blocks: frame timing is owned by the event loop, not by input waits. ### impl WindowBackend ```rust impl

WindowBackend { pub fn new(presenter: P) -> Self; pub fn presenter(&self) -> &P; pub fn presenter_mut(&mut self) -> &mut P; pub fn into_presenter(self) -> P; } ``` ### impl Output for WindowBackend ```rust impl

Output for WindowBackend { } ``` ### impl Input for WindowBackend ```rust impl

Input for WindowBackend { } ``` ### impl Cursor for WindowBackend ```rust impl

Cursor for WindowBackend { } ``` ### impl Output for NullPresenter ```rust impl Output for NullPresenter { } ``` ### impl Presenter for NullPresenter ```rust impl Presenter for NullPresenter { } ``` ## src/lib.rs ### backend::WindowBackend ```rust pub use backend::WindowBackend; ``` ### clipboard::SystemClipboard ```rust pub use clipboard::SystemClipboard; ``` ### clipboard::{Clipboard, ClipboardError} ```rust pub use clipboard::{Clipboard, ClipboardError}; ``` ### geometry::CellGeometry ```rust pub use geometry::CellGeometry; ``` ### presenter::{GenericSurfaceError, Presenter, RecoverableError, WindowHandle, cell_art_glyph} ```rust pub use presenter::{GenericSurfaceError, Presenter, RecoverableError, WindowHandle, cell_art_glyph}; ``` ### raw_window_handle ```rust pub use raw_window_handle; ``` ## src/palette.rs ### DEFAULT_FG ```rust pub const DEFAULT_FG: (u8, u8, u8) ``` Foreground for [`Color::Default`](retroglyph_core::color::Color::Default): a light grey, matching a typical terminal's default text color. ### DEFAULT_BG ```rust pub const DEFAULT_BG: (u8, u8, u8) ``` Background for [`Color::Default`](retroglyph_core::color::Color::Default): black. ## src/sprite_cache.rs ### Sprite ```rust #[derive(Debug, Clone)] pub struct Sprite { pub pixels: Vec, pub pixel_width: u32, pub pixel_height: u32, pub align: SpriteAlign, pub color: SheetColor, } ``` A decoded, ready-to-blit sprite. ### impl Sprite ```rust impl Sprite { pub fn align_offset(&self, span_w: u16, span_h: u16, glyph_w: u8, glyph_h: u8) -> (i16, i16); } ``` ### SpriteCache ```rust #[derive(Debug)] pub struct SpriteCache { } ``` Cache of decoded sprites, keyed by Unicode codepoint. #### Reload / hot-swap is not supported [`load`](Self::load) is append-only: it decodes a tileset and merges its sprites into the existing map, with later registrations winning on codepoint collision (see [`load`](Self::load) docs). There is no `unload` or `clear`, and nothing observes or invalidates sprites already handed out via [`get`](Self::get). This is a deliberate scope decision, not an oversight: games generally don't hot-swap tilesets at runtime, and a `SpriteCache` is only ever populated once, when a backend is built. If you need to iterate on a sprite sheet (e.g. during dev-mode asset editing) or otherwise want a tileset change to take effect, rebuild the whole renderer from a fresh backend configuration rather than mutating an existing cache in place. ### impl SpriteCache ```rust impl SpriteCache { pub fn new() -> Self; pub fn get(&self, ch: char) -> Option<&Sprite>; pub fn iter(&self) -> impl Trait; pub fn is_empty(&self) -> bool; pub fn from_tilesets(opts: &[TilesetOptions]) -> Result; pub fn load(&mut self, opts: &TilesetOptions) -> Result<(), TilesetError>; } ``` ### impl Default for SpriteCache ```rust impl Default for SpriteCache { } ``` ### SpriteTint ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct SpriteTint { pub mask: Tint, pub tint: Tint, } ``` The complete recolouring one sprite goes through in one cell: the sheet's own treatment, then the cell's tint. Two stages rather than one because they do not always fold together. A [`SheetColor::Mask`] sheet is a multiply by the cell's foreground, and a multiply composes with another multiply, but not with a [`Tint::Mix`]: "colour this mask red, then flash it half-way to white" is two operations and cannot be written as one. Both pixel backends resolve through here, so a sprite recoloured on the software rasteriser and the same sprite recoloured in the GL fragment shader cannot disagree. The GL side uploads the two stages as instance attributes and mirrors [`apply`](Self::apply)'s order. ### impl SpriteTint ```rust impl SpriteTint { pub fn resolve(sheet: SheetColor, fg: Color, tint: Tint, default_fg: (u8, u8, u8)) -> Self; pub fn is_identity(&self) -> bool; pub fn apply(&self, rgb: (u8, u8, u8)) -> (u8, u8, u8); } ``` ### warn_sprite_needs_span ```rust pub fn warn_sprite_needs_span(seen: &mut BTreeSet, glyph: char, sprite: (u32, u32), cell: (u32, u32)) -> bool ``` Warns, at most once per glyph, that `glyph`'s sprite is larger than one cell but was drawn without a span reserving the cells it covers. For backend implementors: both graphical backends call this from their sprite blit, so the diagnostic and the fix it names are identical on each. Such a sprite still draws at its natural size, but its pixels land in neighbouring cells that go on painting their own background and glyph over it, which is a confusing thing to debug from the rendered output alone. `sprite` and `cell` are `(width, height)` in unscaled pixels; a sprite fitting within `cell` on both axes is silent. `seen` is caller-owned state so a redraw loop reports each offending glyph once rather than every frame; entries are only ever added. Returns whether a warning was emitted, which is always `false` in a build that compiles diagnostics out: the size comparison, the `seen` bookkeeping, and the message all sit inside [`dev_only!`], so a release build does none of them. See [`BuildMode`](retroglyph_core::dev::BuildMode). ### warn_tint_needs_sprite ```rust pub fn warn_tint_needs_sprite(seen: &mut BTreeSet, glyph: char, tint: Tint) -> bool ``` Warns, at most once per glyph, that `glyph` carries a tint but resolved to a bitmap font glyph rather than a sprite, so the tint was silently dropped. This is #537's exact trap: a font glyph is `fg`-coloured, so a cell that falls back to one still visibly changes colour when a tint is set, and it is easy to conclude the tint took effect when in fact nothing read it. Both pixel backends call this from the branch that already knows the sprite cache missed for this glyph, so the diagnostic and the fix it names are identical on each. `tint` is the cell's own tint; a tint whose [`is_identity`](Tint::is_identity) is `true` (including [`Tint::None`]) has nothing to drop and is silent. `seen` is caller-owned state so a redraw loop reports each offending glyph once rather than every frame; entries are only ever added. Returns whether a warning was emitted, which is always `false` in a build that compiles diagnostics out: the identity check, the `seen` bookkeeping, and the message all sit inside [`dev_only!`], so a release build does none of them. See [`BuildMode`](retroglyph_core::dev::BuildMode). ## src/geometry.rs ### CellGeometry ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CellGeometry { pub glyph_w: u8, pub glyph_h: u8, pub scale: u16, } ``` The pixel geometry of a fixed cell grid: a glyph size and an integer scale. The single code embodiment of [`Presenter::cell_size`](crate::Presenter::cell_size)'s contract: physical pixels, `glyph x scale`, never DPI-auto-scaled. Every graphical backend stores one of these and returns [`cell_size`](Self::cell_size) from `Presenter::cell_size`, rather than re-deriving `glyph_w * scale` (and `cols * cell_w` for the surface) per backend in its own integer types, which lets the shared rule drift. ### impl CellGeometry ```rust impl CellGeometry { pub fn new(glyph_w: u8, glyph_h: u8, scale: u16) -> Self; pub fn cell_size(&self) -> (u32, u32); pub fn surface_size(&self, cols: u16, rows: u16) -> (u32, u32); pub fn pixel_to_cell(&self, x: f64, y: f64) -> Pos; } ``` ## src/web.rs ### winit_canvas ```rust pub fn winit_canvas() -> Result ``` Finds winit's `` element via the DOM. #### Errors Returns a message describing the failure if the global `Window`, `Document`, or canvas element cannot be obtained. Each caller wraps the message in its own surface-error type. ## src/font.rs ### BitmapFont ```rust #[derive(Debug, Clone, Copy)] pub struct BitmapFont { } ``` A 1-bit-per-pixel bitmap glyph font. `Copy` because it is just a static reference plus a few small fields. ### impl BitmapFont ```rust impl BitmapFont { pub fn new(data: &'static [u8], glyph_width: u8, glyph_height: u8, glyph_count: u16) -> Self; pub fn with_charset(data: &'static [u8], glyph_width: u8, glyph_height: u8, glyph_count: u16, charset: &'static [(char, u8)]) -> Self; pub fn rows(&self, index: u8) -> &[u8]; pub fn glyph_pixels(&self, index: u8) -> impl Trait; pub fn glyph_width(&self) -> u8; pub fn glyph_height(&self) -> u8; pub fn glyph_count(&self) -> u16; pub fn glyph_index(&self, ch: char) -> Option; } ``` ### impl PartialEq for BitmapFont ```rust impl PartialEq for BitmapFont { } ``` ### impl Eq for BitmapFont ```rust impl Eq for BitmapFont { } ``` ### ResolvedGlyph ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ResolvedGlyph { } ``` A glyph resolved from a [`FontChain`]: the glyph index plus the specific [`BitmapFont`] it came from, since each font in a chain owns its own bitmap data. ### impl ResolvedGlyph ```rust impl ResolvedGlyph { pub fn font(&self) -> BitmapFont; pub fn font_index(&self) -> usize; pub fn index(&self) -> u8; pub fn is_notdef(&self) -> bool; pub fn rows(&self) -> &[u8]; } ``` ### FontChain ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct FontChain<'a> { } ``` The glyph source a backend draws from: a primary [`BitmapFont`] plus an ordered list of fallback fonts. This is the only character-to-glyph path the bundled pixel backends have. A single font is a chain of one (`FontChain::from(font)`), so `SoftwareBackendBuilder::font` and `GlBackendBuilder::font` both take an `impl Into>` and there is no second, chain-blind route that could quietly ignore a font's declared repertoire. [`resolve`](Self::resolve) tries the primary font first, then each fallback in order, and only if every font misses substitutes the solid block (`'█'`) from the first font in the chain that has one. This lets a caller layer, say, an ASCII or partial-coverage primary font with one or more broader fallback fonts, so a char missing from the primary doesn't automatically become a solid block if some other font in the chain actually has it. This type ships **no bundled fallback font data**: every font in the chain, primary or fallback, is supplied by the caller. Bundling a ready-to-use Latin-1/Extended or sub-cell (quadrant/sextant/braille) fallback font is a natural follow-up now that this mechanism is reachable end to end, but is out of scope here. A fallback font only extends the chain's repertoire if it declares coverage for the characters it is meant to answer for. A [`BitmapFont::new`] font is always resolved through the built-in CP437 table, so stacking several CP437 fonts in a chain never reaches past CP437: every font in the chain answers the identical question. To actually extend coverage (e.g. quadrants, sextants, braille, none of which CP437 has a mapping for), build the fallback font with [`BitmapFont::with_charset`] and an explicit table covering those codepoints. Until a chain does, `retroglyph_core::symbols`'s `quantize_quadrant`/`quantize_sextant` glyphs render as a solid block on the pixel backends; see those functions' docs. #### Examples ``` use retroglyph_window::font::{BitmapFont, FontChain}; static ASCII: [u8; 128 * 16] = [0; 128 * 16]; static QUADRANTS: [u8; 3 * 16] = [0; 3 * 16]; const QUADRANT_CHARSET: [(char, u8); 3] = [('▘', 0), ('▝', 1), ('▖', 2)]; const PRIMARY: BitmapFont = BitmapFont::new(&ASCII, 8, 16, 128); const SUBCELL: BitmapFont = BitmapFont::with_charset(&QUADRANTS, 8, 16, 3, &QUADRANT_CHARSET); static FALLBACKS: [BitmapFont; 1] = [SUBCELL]; let chain = FontChain::new(PRIMARY, &FALLBACKS); let quadrant = chain.resolve('▘').expect("covered by the fallback font"); assert_eq!(quadrant.font_index(), 1); assert!(!quadrant.is_notdef()); ``` ### impl From for FontChain ```rust impl From for FontChain { } ``` ### impl FontChain ```rust impl<'a> FontChain { pub fn new(primary: BitmapFont, fallbacks: &'a [BitmapFont]) -> Self; pub fn fonts(&self) -> impl Trait; pub fn font_count(&self) -> usize; pub fn glyph_size(&self) -> Option<(u8, u8)>; pub fn resolve(&self, ch: char) -> Option; } ``` ### FONT ```rust pub const FONT: BitmapFont ``` A [`BitmapFont`] backed by the embedded Unscii 16 glyph data. ### FONT ```rust pub const FONT: BitmapFont ``` A [`BitmapFont`] backed by the generated quadrant/sextant/bar/block glyph data. Built with [`BitmapFont::with_charset`] (not [`BitmapFont::new`]): none of these codepoints are in the CP437 table this crate's default mapping uses, so this font declares its own explicit `char` -> glyph-index table instead. ### FONT ```rust pub const FONT: BitmapFont ``` A [`BitmapFont`] backed by the generated braille glyph data. Built with [`BitmapFont::with_charset`] (not [`BitmapFont::new`]): braille codepoints are not in the CP437 table this crate's default mapping uses, so this font declares its own explicit `char` -> glyph-index table instead. ## src/clipboard.rs ### Clipboard ```rust pub trait Clipboard { fn get_text(&mut self) -> Result; fn set_text(&mut self, text: String) -> Result<(), ClipboardError>; } ``` Read/write access to a text clipboard. A trait rather than a single concrete type so callers can substitute a fake for testing -- see this module's doc comment. #### Examples ``` use retroglyph_window::{Clipboard, ClipboardError}; #[derive(Default)] struct FakeClipboard { contents: Option, } impl Clipboard for FakeClipboard { fn get_text(&mut self) -> Result { self.contents .clone() .ok_or_else(|| ClipboardError::new("clipboard is empty")) } fn set_text(&mut self, text: String) -> Result<(), ClipboardError> { self.contents = Some(text); Ok(()) } } let mut clip = FakeClipboard::default(); clip.set_text("hello".to_string())?; assert_eq!(clip.get_text()?, "hello"); #### Ok::<(), ClipboardError>(()) ``` ### ClipboardError ```rust #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClipboardError(); ``` Error returned by [`Clipboard::get_text`]/[`Clipboard::set_text`]. An opaque, message-carrying wrapper rather than an enum of specific failure causes: the two implementations this crate ships (arboard on native, a test fake) fail for platform- or fake-specific reasons that don't share a meaningful common taxonomy, so the message is kept as the one thing that's actually useful across both: surfacing it in logs/error messages. ### impl ClipboardError ```rust impl ClipboardError { pub fn new(message: impl Trait) -> Self; } ``` ### impl fmt::Display for ClipboardError ```rust impl fmt::Display for ClipboardError { } ``` ### impl std::error::Error for ClipboardError ```rust impl std::error::Error for ClipboardError { } ``` ### SystemClipboard ```rust #[cfg(not, not (target_arch = "wasm32"))] pub struct SystemClipboard(); ``` The native OS clipboard, backed by [`arboard`]. Not available on `wasm32`: the browser clipboard API (`navigator.clipboard`) is async-only (returns a `Promise`), which does not fit [`Clipboard`]'s synchronous methods, and `arboard` itself does not build for `wasm32-unknown-unknown`: see this crate's `Cargo.toml` for the target-gating. ### impl fmt::Debug for SystemClipboard ```rust impl fmt::Debug for SystemClipboard { } ``` ### impl SystemClipboard ```rust impl SystemClipboard { pub fn new() -> Result; } ``` ### impl Clipboard for SystemClipboard ```rust impl Clipboard for SystemClipboard { } ``` ### impl Clipboard for FakeClipboard ```rust impl Clipboard for FakeClipboard { } ``` ## src/tileset.rs ### SheetColor ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum SheetColor { Art, Mask, } ``` What a tileset's pixels mean, which decides how its sprites respond to the cell's foreground color. This is a fact about how the artwork was authored, not about any one draw call, which is why it sits on the tileset rather than at the call site. A sheet of full-color terrain and a sheet of white icon masks can be loaded side by side and each behave correctly. Orthogonal to [`Tint`](retroglyph_core::color::Tint), which is per-cell and applies on top: see [`Surface::with_tint`](retroglyph_core::surface::Surface::with_tint). Open question (retroglyph#559): a sheet mixing mask tiles and full-colour art tiles has no way to say so today, since this is a sheet-wide setting. The likely answer is to split such a sheet into two `TilesetOptions` loads, one per `SheetColor`, rather than adding a per-tile escape hatch here. That is untested against a real mixed asset and not resolved by this type as written. ### SpriteAlign ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum SpriteAlign { TopLeft, Top, TopRight, Left, Center, Right, BottomLeft, Bottom, BottomRight, } ``` Where a sprite sits inside the multi-cell box a span reserves for it. Geometry only: alignment moves a sprite's pixels, it never changes their color. See [`TilesetOptions`] for how a sprite's color relates to the cell's style. Only observable when the reserved box is larger than the sprite's own pixels, i.e. when [`Surface::put_span`](retroglyph_core::surface::Surface::put_span) declares more cells than the artwork fills. A sprite drawn into a box its art exactly fills (the common case) renders identically under every variant. Mirrors `BearLibTerminal`'s tileset `align=` option. ### impl SpriteAlign ```rust impl SpriteAlign { pub fn offset(self, sprite_w: u32, sprite_h: u32, box_w: u32, box_h: u32) -> (i16, i16); pub fn offset_in_span(self, sprite_w: u32, sprite_h: u32, span_w: u16, span_h: u16, glyph_w: u8, glyph_h: u8) -> (i16, i16); } ``` ### TilesetError ```rust #[derive(Debug)] pub enum TilesetError { ImageDecode(String), InvalidDimensions(u32, u32, u16, u16), EmptyCodepage, ZeroTileSize, TooManyColumns(u16, u32), } ``` Errors that can occur during tileset validation or decoding. ### impl fmt::Display for TilesetError ```rust impl fmt::Display for TilesetError { } ``` ### impl std::error::Error for TilesetError ```rust impl std::error::Error for TilesetError { } ``` ### Codepage ```rust #[derive(Debug, Clone, PartialEq, Eq)] pub enum Codepage { Cp437, Unicode { start: char }, Identity, Custom(Vec), } ``` Maps row-major tile indices in a sprite sheet to Unicode codepoints. `#[non_exhaustive]` allows adding new variants (e.g. `Cp1252`) without a semver break. ### impl Codepage ```rust impl Codepage { pub fn codepoint(&self, i: usize) -> Option; pub fn len(&self) -> Option; pub fn is_empty(&self) -> bool; } ``` ### CP437_TO_UNICODE ```rust pub const CP437_TO_UNICODE: [char; N] ``` Standard IBM CP437 to Unicode mapping, 256 entries. ### TilesetOptions ```rust #[derive(Debug, Clone, PartialEq, Eq)] pub struct TilesetOptions { pub bytes: Vec, pub tile_width: u16, pub tile_height: u16, pub columns: Option, pub codepage: Codepage, pub align: SpriteAlign, pub color: SheetColor, pub transparent_color: Option<(u8, u8, u8)>, } ``` Options for loading a single tileset (sprite sheet). #### Sprites carry their own color By default ([`SheetColor::Art`]) a tileset's artwork is composited verbatim: the cell's [`Style::fg`](retroglyph_core::color::Style::fg) does not tint it, so a full-color sheet renders exactly as authored. The cell's background is still painted behind the sprite and shows through its transparent pixels. A sheet authored as white-on-transparent masks declares [`SheetColor::Mask`] instead, and its sprites are colored by the cell's foreground the way a bitmap font glyph is. Recoloring one piece of artwork per cell (biome variants, damage flashes) is a per-draw decision rather than a sheet-wide one, and goes through [`Surface::with_tint`](retroglyph_core::surface::Surface::with_tint). ### impl TilesetOptions ```rust impl TilesetOptions { pub fn builder(bytes: Vec) -> TilesetBuilder; } ``` ### TilesetBuilder ```rust pub struct TilesetBuilder { } ``` Builder for [`TilesetOptions`]. Construct via [`TilesetOptions::builder`]. [`columns`](TilesetBuilder::columns) defaults to `image_width / tile_width`, so you usually don't need to set it explicitly. [`codepage`](TilesetBuilder::codepage) defaults to [`Codepage::Cp437`]. #### Examples Standard CP437 tileset: ```no_run use retroglyph_window::tileset::TilesetOptions; let png: Vec = std::fs::read("assets/cp437_16x16.png").unwrap(); let opts = TilesetOptions::builder(png) .tile_size(16, 16) // codepage defaults to Cp437 .build() .unwrap(); ``` Private-use sprite sheet addressed by index, centred in whatever box a span reserves: ```no_run use retroglyph_window::tileset::{Codepage, SpriteAlign, TilesetOptions}; let png: Vec = std::fs::read("assets/sprites.png").unwrap(); let opts = TilesetOptions::builder(png) .tile_size(32, 32) .codepage(Codepage::Identity) // tile 0 = '\0', tile 1 = '\x01', … .align(SpriteAlign::Center) .build() .unwrap(); ``` How many cells a sprite occupies is a per-write decision, not a tileset-wide one: declare it with [`Surface::put_span`](retroglyph_core::surface::Surface::put_span) at the draw call. Unicode private-use area sprite sheet: ```no_run use retroglyph_window::tileset::TilesetOptions; let png: Vec = std::fs::read("assets/monsters.png").unwrap(); let opts = TilesetOptions::builder(png) .tile_size(16, 16) .start_codepoint('\u{E000}') // maps to Unicode PUA starting at U+E000 .build() .unwrap(); ``` ### impl TilesetBuilder ```rust impl TilesetBuilder { pub fn tile_size(self, width: u16, height: u16) -> Self; pub fn columns(self, cols: u16) -> Self; pub fn codepage(self, codepage: Codepage) -> Self; pub fn start_codepoint(self, start: char) -> Self; pub fn align(self, align: SpriteAlign) -> Self; pub fn color(self, color: SheetColor) -> Self; pub fn mask(self) -> Self; pub fn transparent_color(self, r: u8, g: u8, b: u8) -> Self; pub fn build(self) -> Result; } ``` ## src/presenter.rs ### WindowHandle ```rust pub trait WindowHandle: HasWindowHandle + HasDisplayHandle + Send + Sync { } ``` A window/display handle pair, as one trait. Presenters receive [`raw-window-handle`](raw_window_handle) types, not a concrete `winit::window::Window`: softbuffer, wgpu, and glutin all accept these handles directly, so any windowing library that produces them can drive the same presenter, and only this crate depends on winit itself. `raw-window-handle` has no combined trait, and surface libraries need to *own* the handle (softbuffer stores it for the surface's lifetime), so presenters receive `Arc`: rwh implements the handle traits for `Arc`, so the trait object passes straight into `softbuffer::Surface::new` / `wgpu::Instance::create_surface`. `Send + Sync` is part of the trait rather than left to each implementation because a trait object erases auto traits its trait doesn't name, and `wgpu::Instance::create_surface` requires them: its safe entry point takes ownership of a `Send + Sync` handle, and the alternative that doesn't is `unsafe`. Declaring them here is what makes `Arc` usable with it. Every windowing library that produces `raw-window-handle` types satisfies this already (`winit::window::Window` does on every platform). #### Examples Blanket-implemented for any type implementing both `raw-window-handle` traits; there is nothing to implement directly on `WindowHandle` itself. ``` use raw_window_handle::{ DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle as RawWindowHandle, }; use retroglyph_window::WindowHandle; struct NoWindow; impl HasWindowHandle for NoWindow { fn window_handle(&self) -> Result, HandleError> { Err(HandleError::NotSupported) } } impl HasDisplayHandle for NoWindow { fn display_handle(&self) -> Result, HandleError> { Err(HandleError::NotSupported) } } fn assert_is_window_handle(_handle: &T) {} assert_is_window_handle(&NoWindow); ``` ### impl WindowHandle for T ```rust impl WindowHandle for T { } ``` ### RecoverableError ```rust pub trait RecoverableError: core::fmt::Debug + core::fmt::Display { fn is_recoverable(&self) -> bool; } ``` A surface-lifecycle error that can optionally signal whether it's worth retrying. [`Presenter::SurfaceError`] is a per-implementation associated type: softbuffer's error enum has no `Lost`/`Outdated`/`Timeout` discrimination the way `wgpu::SurfaceError` does, so today's only backend (`SoftwareRenderer`) has no structured way to say "this specific failure is fatal, don't bother retrying." [`is_recoverable`](Self::is_recoverable) is that hook: a presenter with real error categories can override it to return `false` for a truly fatal failure, while every presenter that doesn't need the distinction (including every backend that exists in this crate today) can implement this trait with an empty body and inherit the default `true`. Not blanket-implemented for every `Debug + Display` type: that would make it impossible for any concrete error type to override [`is_recoverable`](Self::is_recoverable) at all (a specific `impl` would conflict with the blanket one), defeating the point of the trait. Instead, each `SurfaceError` type needs one explicit (and usually empty) `impl RecoverableError for ...` block: see `retroglyph_software`'s `SurfaceError` for the minimal case that just inherits the default. #### Examples ``` use core::fmt; use retroglyph_window::RecoverableError; #[derive(Debug)] enum MySurfaceError { Init, Lost, } impl fmt::Display for MySurfaceError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Init => write!(f, "surface init failed"), Self::Lost => write!(f, "surface lost"), } } } impl RecoverableError for MySurfaceError { fn is_recoverable(&self) -> bool { // Init failures are fatal; a lost surface may come back. matches!(self, Self::Lost) } } assert!(!MySurfaceError::Init.is_recoverable()); assert!(MySurfaceError::Lost.is_recoverable()); ``` ### impl RecoverableError for core::convert::Infallible ```rust impl RecoverableError for core::convert::Infallible { } ``` ### GenericSurfaceError ```rust #[derive(Debug)] pub enum GenericSurfaceError { Init(String), Present(String), } ``` A ready-made, string-backed [`SurfaceError`](Presenter::SurfaceError) for presenters whose underlying surface library reports failures as opaque strings rather than a structured error enum. Several presenter backends (e.g. `retroglyph-gl`'s native/wasm split, or a future softbuffer backend) need only two buckets ("surface/context creation failed" (fatal) and "presenting a frame failed" (potentially recoverable)) and would otherwise each hand-roll the same `enum { Init(String), Present(String) }` plus [`RecoverableError`] impl. This type is that common shape, provided once here so backends can reuse it directly instead of duplicating it. ### impl fmt::Display for GenericSurfaceError ```rust impl fmt::Display for GenericSurfaceError { } ``` ### impl std::error::Error for GenericSurfaceError ```rust impl std::error::Error for GenericSurfaceError { } ``` ### impl RecoverableError for GenericSurfaceError ```rust impl RecoverableError for GenericSurfaceError { } ``` ### Presenter ```rust pub trait Presenter: Output { type SurfaceError; fn init_surface(&mut self, window: Arc) -> Result<(), Self::SurfaceError>; fn resize_surface(&mut self, width: u32, height: u32); fn scale_factor_changed(&mut self, _scale_factor: f64); fn present(&mut self) -> Result<(), Self::SurfaceError>; fn cell_size(&self) -> (u32, u32); fn geometry(&self) -> CellGeometry; } ``` A renderer that rasterizes grid content and presents it to a window surface. A supertrait of [`Output`], adding the surface lifecycle (`init_surface`, `resize_surface`, `present`, `cell_size`) that the event loop drives. Every `Presenter` implementation is an `Output` implementation for free: [`WindowBackend`](crate::WindowBackend) delegates its own `Output` impl straight through to `P: Presenter`, with no duplicated method bodies. #### Sub-cell offsets and spill A [`Tile`]'s `dx`/`dy` shift its glyph within, and past, its cell. This is a cross-backend rendering contract: the CPU rasterizer (`retroglyph-software`) and the GPU ones (`retroglyph-gl`, `retroglyph-wgpu`) must produce the same pixels, so it is specified here once instead of in mirrored per-backend comments that reference each other (and drift when only one is touched). A `Presenter` that honors sub-cell offsets must obey all four points: - `dx`/`dy` are in **unscaled font pixels** (a presenter multiplies by its own integer scale); negative `dx` shifts the glyph left, negative `dy` up. - The cell's **background fill is always the full, unshifted cell** rectangle. An offset moves only the glyph, never the background. - An offset glyph **may spill past its cell edge into neighboring cells**, and that spill is **uniform in all four directions**: a glyph pushed right/down onto a later neighbor spills the same way as one pushed left/up onto an earlier neighbor. - The mechanism that guarantees that uniformity is a **two-pass draw**: lay down *every* cell's background first, then draw *every* cell's (offset) glyph over the result. Interleaving the two per cell would let a later cell's background overwrite an earlier neighbor's spilled glyph, breaking spill in the right/down directions only. The offset *application* is not shared code: the GPU backends shift a quad's vertex position in their vertex shader, `retroglyph-software` shifts `origin_x`/`origin_y` in a CPU blit: irreducibly different mechanics that must nonetheless agree on the four points above. #### Examples ``` use retroglyph_core::backend::{DrawCell, Output}; use retroglyph_core::grid::Size; use retroglyph_window::{Presenter, WindowHandle}; use std::sync::Arc; struct NullPresenter; impl Output for NullPresenter { type Error = core::convert::Infallible; fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error> where I: Iterator>, { Ok(()) } fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } fn size(&self) -> Size { Size::new(4, 2) } fn clear(&mut self) -> Result<(), Self::Error> { Ok(()) } } impl Presenter for NullPresenter { type SurfaceError = core::convert::Infallible; fn init_surface(&mut self, _window: Arc) -> Result<(), Self::SurfaceError> { Ok(()) } fn resize_surface(&mut self, _width: u32, _height: u32) {} fn present(&mut self) -> Result<(), Self::SurfaceError> { Ok(()) } fn cell_size(&self) -> (u32, u32) { (8, 16) } } ``` ### cell_art_glyph ```rust pub fn cell_art_glyph(tile: &Tile) -> Option ``` The glyph a `Presenter` should paint art (a bitmap-font glyph or a tileset sprite) for, or `None` when this cell draws none. Both pixel backends (`retroglyph-software`, `retroglyph-gl`) ask this same question at several points in their draw path (sprite-vs-font dispatch, font fallback, whether a cell counts as "occupied" for compositing), and used to each answer it independently, which let them drift (retroglyph#762). This is the one place that decides it: - A [`TileFlags::SPAN_COVERED`](retroglyph_core::tile::TileFlags::SPAN_COVERED) cell (see [`Tile::span_offset`]) draws no art of its own: the span's anchor already drew one piece of artwork across the whole footprint, and this cell's glyph is only that artwork's text fallback for backends that can't draw it. - An [`is_empty`](Tile::is_empty) tile draws no art: nothing has been written to it, so it is transparent when compositing layers. This is the canonical blank rule, matching [`Grid::flatten_into`](retroglyph_core::grid::Grid::flatten_into) and the cell backends; comparing the glyph itself against `' '` is both slower (it can't be decided without the glyph) and wrong for a font whose space glyph isn't blank. Neither check depends on whether a sprite exists for the glyph: that dispatch (sprite vs. bitmap font) is a separate, backend-specific decision made *after* this one, once a caller knows a cell draws art at all. ## src/atlas.rs ### ATLAS_COLS ```rust pub const ATLAS_COLS: u32 ``` Glyph columns packed into one array layer. ### ATLAS_ROWS ```rust pub const ATLAS_ROWS: u32 ``` Glyph rows packed into one array layer. ### SLOTS_PER_LAYER ```rust pub const SLOTS_PER_LAYER: u32 ``` Glyph slots per array layer (`ATLAS_COLS * ATLAS_ROWS`). ### MAX_SLOTS ```rust pub const MAX_SLOTS: u32 ``` The number of slots the atlas can address, set by the `u16` slot id an instance buffer carries. A [`FontChain`] with more glyphs than this cannot be packed; a backend's builder is expected to reject one, since [`GlyphAtlas::resolve`] has no slot to name them with. ### addressable_glyphs ```rust pub fn addressable_glyphs(font: &BitmapFont) -> u32 ``` The number of atlas slots `font` occupies: its glyph count, capped at the 256 a `u8` glyph index can address (see [`BitmapFont::rows`]). A font that declares more glyphs than that has no way to name them, so the atlas doesn't reserve slots for them either. ### AtlasGeometry ```rust #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct AtlasGeometry { pub cell_w: u32, pub cell_h: u32, pub layers: u32, } ``` The packing of glyph cells into an array texture: a fixed [`ATLAS_COLS`]x[`ATLAS_ROWS`] grid of `cell_w`x`cell_h` glyph cells per layer, across `layers` layers. ### impl AtlasGeometry ```rust impl AtlasGeometry { pub fn new(cell_w: u32, cell_h: u32, capacity: u32) -> Self; pub fn tex_w(&self) -> u32; pub fn tex_h(&self) -> u32; pub fn locate(slot: u32) -> (u32, u32, u32); } ``` ### AtlasData ```rust #[derive(Clone, Debug)] pub struct AtlasData { pub geometry: AtlasGeometry, pub coverage: Vec, } ``` The CPU-side coverage buffer for a whole atlas, grid-packed per [`AtlasGeometry`]. ### impl AtlasData ```rust impl AtlasData { pub fn build(fonts: &FontChain<'static>, cell_size: (u32, u32)) -> Self; } ``` ### GlyphAtlas ```rust #[derive(Clone, Debug)] pub struct GlyphAtlas { } ``` A static [`FontChain`] plus the `char` -> slot map for its grid-packed atlas. Every glyph of every font in the chain is uploaded once; a character maps to a flat slot, which is the font's base offset in the atlas plus that font's own glyph index. A renderer never sees characters past this point: [`resolve`](Self::resolve) hands back a `u16` that goes straight into an instance buffer. ### impl GlyphAtlas ```rust impl GlyphAtlas { pub fn new(fonts: FontChain<'static>, glyph_size: (u8, u8)) -> Self; pub fn cell_size(&self) -> (u32, u32); pub fn space_slot(&self) -> u16; pub fn slot_count(&self) -> u32; pub fn fonts(&self) -> &FontChain<'static>; pub fn data(&self) -> AtlasData; pub fn resolve(&self, ch: char) -> Option; } ``` ## src/winit/mod.rs ### run::{EventProxy, EventProxyClosed, WindowConfig, run_app, run_app_with_proxy, run_app_with_typed_proxy, run_windowed, run_windowed_with_proxy, run_windowed_with_typed_proxy} ```rust pub use run::{EventProxy, EventProxyClosed, WindowConfig, run_app, run_app_with_proxy, run_app_with_typed_proxy, run_windowed, run_windowed_with_proxy, run_windowed_with_typed_proxy}; ``` ## src/winit/web.rs ## src/winit/run.rs ### EventProxy ```rust pub struct EventProxy(); ``` A thread-safe handle for injecting application-defined events into a running windowed event loop from another thread (network, audio, timer, ...). Obtained via the `on_proxy` callback passed to [`run_windowed_with_proxy`]/ [`run_app_with_proxy`] (payload fixed to `u64`, delivered as [`Event::Custom`]) or [`run_windowed_with_typed_proxy`]/[`run_app_with_typed_proxy`] (any `T: Send + 'static`, delivered to a caller-supplied handler), invoked synchronously right after the event loop (and this proxy) is created, before the loop starts blocking the calling thread. Clone it freely to hand a copy to each worker thread that needs to wake the loop; wraps winit's own [`EventLoopProxy`](winit::event_loop::EventLoopProxy), which is `Send + Sync` for any `T: Send + 'static` payload. `T` defaults to `u64` (the payload [`Event::Custom`] itself carries), so existing code naming the bare `EventProxy` type (from before this type became generic) keeps compiling unchanged. ### impl Clone for EventProxy ```rust impl Clone for EventProxy { } ``` ### impl fmt::Debug for EventProxy ```rust impl fmt::Debug for EventProxy { } ``` ### impl EventProxy ```rust impl EventProxy { pub fn send_event(&self, payload: T) -> Result<(), EventProxyClosed>; } ``` ### EventProxyClosed ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct EventProxyClosed(); ``` Error returned by [`EventProxy::send_event`] when the event loop it targets has already exited. ### impl EventProxyClosed ```rust impl EventProxyClosed { pub fn into_inner(self) -> T; } ``` ### impl fmt::Display for EventProxyClosed ```rust impl fmt::Display for EventProxyClosed { } ``` ### impl std::error::Error for EventProxyClosed ```rust impl std::error::Error for EventProxyClosed { } ``` ### WindowConfig ```rust pub struct WindowConfig { } ``` Window configuration for [`run_windowed`] / [`run_app`]. Renderer-agnostic: pixel dimensions, not grid/font/scale. Use [`fit`](Self::fit) to derive the pixel size from a presenter's own cell geometry. Several builder methods below ([`resizable`](Self::resizable), [`decorations`](Self::decorations), [`transparency`](Self::transparency), [`fullscreen`](Self::fullscreen)) target an OS-level window control that a `wasm32` canvas doesn't have; on that target winit's web backend either ignores the value outright or can't reliably apply it (see each method for which, and why). The value is still applied for source-level parity with native either way, so the same call chain compiles and runs on both targets, it just may not visibly do anything in the browser. ### impl WindowConfig ```rust impl WindowConfig { pub fn fit(presenter: &P, title: impl Trait, target_fps: Option, event_driven: bool) -> Self; pub fn title(&self) -> &str; pub fn width(&self) -> u32; pub fn height(&self) -> u32; pub fn animated(presenter: &P, title: impl Trait, fps: u32) -> Self; pub fn target_fps(&self) -> Option; pub fn event_driven(&self) -> bool; pub fn fill_viewport(self, fill_viewport: bool) -> Self; pub fn resizable(self, resizable: bool) -> Self; pub fn decorations(self, decorations: bool) -> Self; pub fn min_size(self, width: u32, height: u32) -> Self; pub fn max_size(self, width: u32, height: u32) -> Self; pub fn initial_position(self, x: i32, y: i32) -> Self; pub fn fullscreen(self, fullscreen: bool) -> Self; pub fn transparency(self, transparency: bool) -> Self; } ``` ### run_windowed ```rust pub fn run_windowed(config: WindowConfig, presenter: P, app_loop: F) -> Result<(), winit::error::EventLoopError> where P: Presenter + 'static, F: FnMut + 'static ``` Open a window and drive `app_loop` from the winit event loop. On native this blocks the calling thread until the loop exits; on wasm it returns immediately and the loop continues on `requestAnimationFrame`. The closure receives `&mut Terminal>` and is called on every frame tick. Window close pushes [`Event::Close`] into the event queue rather than exiting: the game decides when to terminate. #### Presenting is automatic Unlike [`run_blocking`](retroglyph_core::app::run_blocking), this driver calls [`Terminal::present`] for you, once, right after `app_loop` returns each frame: you no longer need to (and, for a stale-content bug fixed by this behavior, should not rely on remembering to) call it yourself inside `app_loop`. Calling it yourself is still supported and has no ill effect (the driver detects it already ran and skips its own call), for example if you also want to call [`Terminal::present`] to observe its `Result` directly. #### Errors Returns [`winit::error::EventLoopError`] if the event loop cannot be created or fails while running. ### run_windowed_with_proxy ```rust pub fn run_windowed_with_proxy(config: WindowConfig, presenter: P, app_loop: F, on_proxy: O) -> Result<(), winit::error::EventLoopError> where P: Presenter + 'static, F: FnMut + 'static, O: FnOnce ``` Same as [`run_windowed`], but also hands `on_proxy` an [`EventProxy`] for injecting cross-thread events. `on_proxy` is called synchronously right after the event loop (and the proxy) is created, before this function starts blocking the calling thread on native. Use this over [`run_windowed`] whenever another thread (network, audio, timer, ...) needs to wake the event loop and deliver an [`Event::Custom`] to the app; `on_proxy` is the hook to hand a clone of the proxy off to that thread before the loop takes over the calling thread. The injected payload is always a `u64`, delivered as [`Event::Custom`] through the app's normal `poll_event`/frame loop; see [`run_windowed_with_typed_proxy`] if a worker thread needs to hand back a real payload (a loaded asset, a network response) instead of a correlation id into a side table. See [`run_windowed`]'s "Presenting is automatic" section: this function shares the same automatic-present behavior; `app_loop` no longer needs to call [`Terminal::present`] itself. #### Examples ```no_run use retroglyph_core::event::Event; use retroglyph_software::SoftwareBackendBuilder; use retroglyph_window::winit::{WindowConfig, run_windowed_with_proxy}; use std::time::Duration; let renderer = SoftwareBackendBuilder::new() .grid_size(80, 25) .scale(2) .build() .expect("backend init failed") .into_renderer() .expect("renderer init failed"); let config = WindowConfig::fit(&renderer, "My Game", None, true); run_windowed_with_proxy( config, renderer, move |term| { if let Some(Event::Custom(id)) = term.poll(Duration::from_millis(16)) { // Handle the tick/network/audio result tagged `id`. println!("got custom event {id}"); } }, |proxy| { // Runs before the blocking call below starts, so the proxy can be // handed off to a worker thread up front. std::thread::spawn(move || loop { std::thread::sleep(Duration::from_secs(1)); if proxy.send_event(1).is_err() { break; // The window closed; stop ticking. } }); }, ) .expect("event loop failed"); ``` #### Errors Returns [`winit::error::EventLoopError`] if the event loop cannot be created or fails while running. ### run_windowed_with_typed_proxy ```rust pub fn run_windowed_with_typed_proxy(config: WindowConfig, presenter: P, app_loop: F, on_proxy: O, on_custom_event: D) -> Result<(), winit::error::EventLoopError> where T: Send + 'static, P: Presenter + 'static, F: FnMut + 'static, O: FnOnce, D: FnMut + 'static ``` Same as [`run_windowed_with_proxy`], but the injected payload can be any `T: Send + 'static` instead of a fixed `u64`. A `T` payload never becomes a [`retroglyph_core::event::Event`]: [`Event::Custom`] is fixed to `u64` (see its doc comment for why), so genericizing it would be a breaking change to [`retroglyph_core`] far larger than this API needs. Instead, each injected `T` is handed directly to `on_custom_event`, called synchronously from winit's `user_event` callback with the same `&mut Terminal>` `app_loop` receives on redraw, so a handler that wants the result to affect the next frame just needs to record it in state the closures share, or push its own backend-agnostic event/marker for `app_loop` to notice. See [`run_windowed`]'s "Presenting is automatic" section: this function shares the same automatic-present behavior; `app_loop` no longer needs to call [`Terminal::present`] itself. This delivery is a side channel, not a queued [`Event`]: `on_custom_event` runs as soon as winit dispatches the `user_event`, which can be before `app_loop` next drains earlier-queued window/input events via [`poll`](retroglyph_core::terminal::Terminal::poll). Don't assume a `T` arrives interleaved with the `poll()` stream in send order relative to those events; if that matters, use [`run_windowed_with_proxy`]'s plain `u64`/[`Event::Custom`] path instead, which does interleave on the backend's own FIFO. #### Examples ```no_run use retroglyph_software::SoftwareBackendBuilder; use retroglyph_window::winit::{WindowConfig, run_windowed_with_typed_proxy}; use std::time::Duration; enum WorkerResult { AssetLoaded { name: String, bytes: Vec }, } let renderer = SoftwareBackendBuilder::new() .grid_size(80, 25) .scale(2) .build() .expect("backend init failed") .into_renderer() .expect("renderer init failed"); let config = WindowConfig::fit(&renderer, "My Game", None, true); run_windowed_with_typed_proxy( config, renderer, move |term| { let _ = term.poll(Duration::from_millis(16)); }, |proxy| { std::thread::spawn(move || { let bytes = std::fs::read("asset.bin").unwrap_or_default(); let _ = proxy.send_event(WorkerResult::AssetLoaded { name: "asset.bin".into(), bytes, }); }); }, |result: WorkerResult, _term| match result { WorkerResult::AssetLoaded { name, bytes } => { println!("loaded {name}: {} bytes", bytes.len()); } }, ) .expect("event loop failed"); ``` #### Errors Returns [`winit::error::EventLoopError`] if the event loop cannot be created or fails while running. ### run_app ```rust pub fn run_app(config: WindowConfig, presenter: P, app: A) -> Result<(), winit::error::EventLoopError> where P: Presenter + 'static, A: retroglyph_core::app::App + 'static ``` Drive an [`App`](retroglyph_core::app::App) from the windowed event loop. This is the inverted driver: winit owns the event loop and calls back into the app on each redraw, rather than the app owning a `while` loop. Each frame builds a [`Frame`](retroglyph_core::app::Frame) with a wall-clock `dt` measured via [`web_time::Instant`]: a plain [`std::time::Instant`] re-export on native, backed by the browser's `Performance.now()` on `wasm32` (where `std::time::Instant` itself is unavailable). Calls [`App::update`](retroglyph_core::app::App::update). On [`Flow::Exit`](retroglyph_core::app::Flow) the event loop exits gracefully (via [`ActiveEventLoop::exit`]) instead of force-exiting the process, so the stack unwinds normally and `Drop` impls up the call chain (unflushed writes, GPU/surface teardown, app-level RAII) run before the process exits. This works the same on wasm: winit's web backend implements `ActiveEventLoop::exit` by stopping its `requestAnimationFrame`-driven runner rather than leaving it a no-op. See [`run_windowed`]'s "Presenting is automatic" section: the app's [`update`](retroglyph_core::app::App::update) implementation no longer needs to call [`Terminal::present`] itself here either, this driver presents automatically after each call, except on [`Flow::Idle`](retroglyph_core::app::Flow::Idle), where the present is skipped entirely and the previous frame stays on screen. #### Resizing is not automatic This driver does not resize the [`Terminal`] itself. On every window resize it pushes [`Event::Resize`] with the new cell dimensions; the app must poll that event and call [`Terminal::resize`] to resize the terminal's own grid buffers. #### Errors Returns [`winit::error::EventLoopError`] if the event loop cannot be created or fails while running. ### run_app_with_proxy ```rust pub fn run_app_with_proxy(config: WindowConfig, presenter: P, app: A, on_proxy: O) -> Result<(), winit::error::EventLoopError> where P: Presenter + 'static, A: retroglyph_core::app::App + 'static, O: FnOnce ``` Same as [`run_app`], but also hands `on_proxy` an [`EventProxy`] for injecting cross-thread events. See [`run_windowed_with_proxy`] for when/why to use the `_with_proxy` variant over the plain one. The injected payload is always a `u64`, delivered as [`Event::Custom`]; see [`run_app_with_typed_proxy`] for injecting any `T: Send + 'static`. See [`run_app`]'s "Presenting is automatic" section: this function shares the same automatic-present behavior. #### Errors Returns [`winit::error::EventLoopError`] if the event loop cannot be created or fails while running. ### run_app_with_typed_proxy ```rust pub fn run_app_with_typed_proxy(config: WindowConfig, presenter: P, app: A, on_proxy: O, on_custom_event: D) -> Result<(), winit::error::EventLoopError> where T: Send + 'static, P: Presenter + 'static, A: retroglyph_core::app::App + 'static, O: FnOnce, D: FnMut + 'static ``` Same as [`run_app_with_proxy`], but the injected payload can be any `T: Send + 'static` instead of a fixed `u64`. See [`run_windowed_with_typed_proxy`] for the same generalization on the raw closure-based driver, including why a non-`u64` payload bypasses [`retroglyph_core::event::Event`] entirely and goes straight to `on_custom_event`. See [`run_app`]'s "Presenting is automatic" section: this function shares the same automatic-present behavior. #### Errors Returns [`winit::error::EventLoopError`] if the event loop cannot be created or fails while running. ### impl From<&WindowConfig> for WindowAttrs ```rust impl From<&WindowConfig> for WindowAttrs { } ``` ### impl Default for WindowAttrs ```rust impl Default for WindowAttrs { } ``` ### impl WindowApp ```rust impl WindowApp { } ``` ### impl ApplicationHandler for WindowApp ```rust impl ApplicationHandler for WindowApp { } ``` ### impl WindowApp ```rust impl WindowApp { } ``` ### impl Default for MockPresenter ```rust impl Default for MockPresenter { } ``` ### impl Output for MockPresenter ```rust impl Output for MockPresenter { } ``` ### impl Presenter for MockPresenter ```rust impl Presenter for MockPresenter { } ``` ### impl Output for RecordingPresenter ```rust impl Output for RecordingPresenter { } ``` ### impl Presenter for RecordingPresenter ```rust impl Presenter for RecordingPresenter { } ``` ### impl Output for FailingPresenter ```rust impl Output for FailingPresenter { } ``` ### impl Presenter for FailingPresenter ```rust impl Presenter for FailingPresenter { } ``` ### impl crate::presenter::RecoverableError for Unknown ```rust impl crate::presenter::RecoverableError for Unknown { } ``` ### impl core::fmt::Display for UnrecoverableError ```rust impl core::fmt::Display for UnrecoverableError { } ``` ### impl crate::presenter::RecoverableError for UnrecoverableError ```rust impl crate::presenter::RecoverableError for UnrecoverableError { } ``` ### impl Output for FatalPresenter ```rust impl Output for FatalPresenter { } ``` ### impl Presenter for FatalPresenter ```rust impl Presenter for FatalPresenter { } ``` ### impl Output for GridRecordingPresenter ```rust impl Output for GridRecordingPresenter { } ``` ### impl Presenter for GridRecordingPresenter ```rust impl Presenter for GridRecordingPresenter { } ``` ## src/winit/translate.rs ### translate_ime ```rust pub fn translate_ime(ime: winit::event::Ime) -> Option ``` Translates a winit [`Ime`](winit::event::Ime) event into an [`Event`]. Only [`Ime::Commit`](winit::event::Ime) carries a complete, atomic block of text: the same shape as the crossterm backend's `Event::Paste` (see its handling of `crossterm::event::Event ::Paste` in `crates/crossterm/src/lib.rs`), so a commit is mapped to [`Event::Paste`] rather than adding a new `Event` variant: `Event` is `#[non_exhaustive]`, so a new variant would be backward-compatible for exhaustive-matching consumers (per issue #267), but there is no need for a new one when an existing variant already fits the shape of the data. `Ime::Enabled`, `Ime::Preedit` (in-progress composition, not yet committed), and `Ime::Disabled` have no existing-`Event` equivalent and are dropped: an app that wants live preedit rendering is out of scope for this landable-sized change (see issue #296). An empty commit (`Ime::Commit(String::new())`) is also dropped: winit can send an empty commit as part of clearing composition state, and forwarding it would deliver a spurious empty paste. ### translate_key ```rust pub fn translate_key(input: winit::event::KeyEvent, modifiers: KeyModifiers) -> Option ``` Translates a winit key event into an [`Event`]. Reports [`KeyEventKind::Press`], [`KeyEventKind::Repeat`] (winit's `repeat` flag), and [`KeyEventKind::Release`]. Returns `None` only for keys we don't map. ### translate_key_location ```rust pub fn translate_key_location(location: winit::keyboard::KeyLocation) -> KeyLocation ``` Maps winit's [`KeyLocation`](winit::keyboard::KeyLocation) 1:1 onto our [`KeyLocation`]. ### translate_physical_pos ```rust pub fn translate_physical_pos(x: f64, y: f64) -> PhysicalPos ``` Converts a raw f64 cursor position to a [`PhysicalPos`]. `f64.max(0.0) as u32`: the `.max(0.0)` clamp makes sign loss intentional. Truncation of the fractional part is also intentional: pixel coordinates are always integers. ### translate_pixel_to_cell ```rust pub fn translate_pixel_to_cell(px_x: f64, px_y: f64, cell_w: u32, cell_h: u32) -> Pos ``` Converts physical pixel coordinates to a grid cell [`Pos`], given a raw `cell_w`/`cell_h` pixel size. Clamps to `u16::MAX` so out-of-bounds cursor positions (negative or extremely large) don't panic: the game loop is responsible for bounds-checking against the terminal size. `run.rs`'s own cursor/mouse handlers call [`CellGeometry::pixel_to_cell`](crate::geometry::CellGeometry::pixel_to_cell) via [`Presenter::geometry`](crate::presenter::Presenter::geometry) directly rather than this function, since they have a full `Presenter` (and so a `CellGeometry`) available. This is kept as a separate public function for callers that only have a raw `cell_w`/`cell_h` pixel size on hand, not a `CellGeometry`; both share the same private clamp/divide helper so the two can't drift apart (see retroglyph#821). ### translate_mouse_button ```rust pub fn translate_mouse_button(button: winit::event::MouseButton) -> Option ``` Translates a winit [`winit::event::MouseButton`] into our [`MouseButton`]. Returns `None` for side buttons and other unrecognized buttons. ### translate_key_event_kind ```rust pub fn translate_key_event_kind(state: winit::event::ElementState, repeat: bool) -> KeyEventKind ``` Maps a winit key `state`/`repeat` pair to a [`KeyEventKind`]. ### translate_modifiers ```rust pub fn translate_modifiers(state: winit::keyboard::ModifiersState) -> KeyModifiers ``` Translates winit modifier state into our [`KeyModifiers`].