pane_ui 0.1.0

A RON-driven, hot-reloadable wgpu UI library with spring animations and consistent scaling
Documentation
//! # threader
//!
//! Single home for all shared runtime state.
//!
//! ## The two structs
//!
//! [`Persistent`] holds everything that survives across frames: the widget tree,
//! focus state, resources, the message channel. It is never reset between ticks.
//!
//! [`Frame`] holds everything that belongs to a single tick: dt, input, the scene
//! draw list, viewport dimensions. It is updated at the start of every frame and
//! the scene is cleared before widgets draw into it.
//!
//! ## Why this split exists
//!
//! `Persistent` owns everything that survives across frames; `Frame` owns
//! everything that belongs to a single tick. This makes ownership unambiguous:
//! - "Does this survive the frame?" → `Persistent`
//! - "Does this reset every tick?" → `Frame`
//!
//! ## `FrameCtx`
//!
//! [`FrameCtx`] wraps both so any fn that needs the world just takes one argument.
//! Copy what you need locally at the top of the fn:
//!
//! ```ignore
//! let dt    = ctx.frame.dt;
//! let input = &ctx.frame.input;
//! let debug = ctx.persistent.debug;
//! ```
//!
//! Write back explicitly when you change persistent state:
//!
//! ```ignore
//! ctx.persistent.nav.focus = Some(new_idx);
//! ctx.persistent.nav.focused_id = Some(id.to_string());
//! ```
//!
//! Never store `FrameCtx` — it is created at the top of `order::run_frame` and
//! lives only for that frame.
//!
//! ## Standalone vs overlay renderer ownership
//!
//! In **overlay** mode the caller owns the `wgpu::Device`, `wgpu::Queue`, and
//! `Pane` renderer; they are stored in `Persistent` so they survive across
//! frames.  In **standalone** mode pane owns its own window but the renderer
//! is created fresh each frame inside `api::run` — it is passed through
//! `FrameCtx::standalone_pane` for that one frame and is not stored in
//! `Persistent`.  That asymmetry is why `Persistent::renderer` is `None` in
//! standalone mode.

use std::collections::HashMap;
use std::sync::{Arc, mpsc};
use std::time::Instant;

use crate::api::PaneAction;
use crate::draw::{Pane, Scene, ShaderId};
use crate::input::Input;
use crate::loader::UiMsg;
use crate::logic::{NavState, Root};
use crate::styles::{StyleId, StyleRegistry};
use crate::textures::{TextureId, TextureRegistry};

// ── BackgroundDef ─────────────────────────────────────────────────────────────

/// Describes the optional full-screen background drawn behind all widgets.
///
/// Stored as a named struct rather than a raw tuple so field access is
/// self-documenting at every call site.
pub struct BackgroundDef {
    /// Shader used to render the background quad.
    pub shader: ShaderId,
    /// Texture (static or animated GIF) to sample.
    pub texture: TextureId,
    /// Original image width in pixels; used for aspect-ratio-correct scaling.
    pub width: u32,
    /// Original image height in pixels; used for aspect-ratio-correct scaling.
    pub height: u32,
}

// ── Persistent ────────────────────────────────────────────────────────────────

/// Everything that survives across frames.
///
/// Created once at startup and mutated in-place. The only things that replace
/// fields entirely are hot reload (rebuilds the widget tree) and root switches
/// (swaps `active_root` and clears focus).
pub struct Persistent {
    // ── Widget tree ───────────────────────────────────────────────────────────
    /// All named roots. Only `active_root` is ticked and drawn each frame.
    pub roots: HashMap<String, Root>,
    /// The root currently being rendered. None until the first root is built.
    pub active_root: Option<String>,
    /// Active page index for each tab, keyed by tab id. Mirrors how `active_root` works.
    pub tab_pages: HashMap<String, usize>,

    // ── Focus state ───────────────────────────────────────────────────────────
    /// Keyboard/gamepad nav state. Cleared atomically on root switch or mouse click.
    pub nav: NavState,

    // ── Style + texture resources ─────────────────────────────────────────────
    /// Style registry — looked up by `StyleId` during draw.
    pub styles: StyleRegistry,
    /// Map of style name → (`StyleId`, `ShaderId`). Kept after build so the
    /// runtime widget-creation API can resolve style names by string.
    pub style_map: crate::builder::StyleMap,
    /// Fallback style used for tooltips. `None` if no `default_style` was set in the RON.
    pub default_style: Option<StyleId>,
    /// Texture registry. `Some` in GPU modes, `None` in headless (no GPU available).
    pub tex_reg: Option<TextureRegistry>,

    // ── GPU device ────────────────────────────────────────────────────────────
    /// The wgpu device. `None` in headless mode.
    pub device: Option<Arc<wgpu::Device>>,
    /// The wgpu queue. `None` in headless mode.
    pub queue: Option<Arc<wgpu::Queue>>,

    // ── GPU renderer ──────────────────────────────────────────────────────────
    /// The wgpu renderer. `None` in headless mode.
    /// Always `Some` in standalone and overlay modes.
    pub renderer: Option<Pane>,

    // ── Pending actions ───────────────────────────────────────────────────────
    /// Actions accumulated during `check_messages` this frame.
    /// Drained by the mode-specific caller (`run`, `PaneOverlay::draw`, etc.)
    /// after `run_frame` returns and returned to the user.
    pub pending_actions: Vec<PaneAction>,

    // ── Background ────────────────────────────────────────────────────────────
    /// Background image or GIF drawn behind all widgets each frame.
    /// `None` if no background was configured in the RON.
    pub background: Option<BackgroundDef>,
    /// Optional solid clear color drawn before all widgets. None = transparent/default surface.
    pub clear_color: Option<crate::draw::Color>,

    // ── Message bus ───────────────────────────────────────────────────────────
    /// Send end of the internal message channel.
    /// Widget fns send `UiMsg` here when a button is pressed, slider moved, etc.
    pub tx: mpsc::Sender<UiMsg>,
    /// Receive end of the same channel. Drained once per frame in `check_messages`.
    /// `pub(crate)` — only `logic::check_messages` should drain this.
    pub(crate) rx: mpsc::Receiver<UiMsg>,

    // ── Hot reload ────────────────────────────────────────────────────────────
    /// Path to the root `.ron` file. Used to reload from disk on `UiMsg::Reload`.
    pub ron_path: String,

    // ── Debug ─────────────────────────────────────────────────────────────────
    /// When true, log every internal `UiMsg` to stdout. Set via `PANE_DEBUG` env var
    /// or `PaneOverlay::set_debug`. (Visual overlay not yet implemented.)
    pub debug: bool,
    /// Allows `press_by_id` to fire in headless mode. Always true in headless,
    /// always false in GPU modes.
    pub headless_accessible: bool,

    // ── On-screen keyboard ────────────────────────────────────────────────────
    /// On-screen keyboard overlay. Opens automatically when a [`crate::items::TextBox`] is focused
    /// via controller and no hardware keyboard has been used recently.
    pub osk: crate::keyboard::OskState,
}

// ── Frame ─────────────────────────────────────────────────────────────────────

/// Everything that belongs to a single tick.
///
/// `dt` and `last_tick` are updated at the start of each frame by `logic::tick_dt`.
/// `scene` is cleared at the start of each frame (`scene.clear()` in `order::run_frame`)
/// and populated by widget draw calls before being submitted to the GPU.
/// `input` is updated by the platform event loop and reset at the end of each
/// frame by `Input::begin_frame`.
pub struct Frame {
    // ── Timing ────────────────────────────────────────────────────────────────
    /// Seconds elapsed since the previous frame. Capped at 0.1 to prevent
    /// physics and animation from exploding after a hitch or debugger pause.
    pub dt: f32,
    /// Wall-clock time at the end of the previous frame. Used to compute `dt`.
    pub last_tick: Instant,

    // ── Viewport ──────────────────────────────────────────────────────────────
    /// Window width in logical pixels. Used for layout, hit-testing, and the
    /// `grid_width` coordinate system.
    pub pw: f32,
    /// Window height in logical pixels. Always 1080 units in pane's coordinate
    /// system regardless of actual resolution.
    pub ph: f32,

    // ── Input ─────────────────────────────────────────────────────────────────
    /// All input state for this frame: mouse, keyboard, gamepad, scroll.
    /// Populated by the platform event loop before `run_frame` is called.
    pub input: Input,

    // ── Scene ─────────────────────────────────────────────────────────────────
    /// The draw list for this frame. Cleared at frame start, populated by
    /// widget draw calls, submitted to the GPU in `order::present`.
    pub scene: Scene,
}

// ── FrameCtx ──────────────────────────────────────────────────────────────────

/// The single argument every per-frame fn takes.
///
/// Gives read/write access to both [`Persistent`] and [`Frame`] without having
/// to pass them as separate arguments everywhere. Created at the top of
/// `order::run_frame` and never stored — its lifetime is exactly one frame.
pub struct FrameCtx<'a> {
    pub persistent: &'a mut Persistent,
    pub frame: &'a mut Frame,
    /// The renderer for standalone mode, threaded through for one frame.
    ///
    /// In standalone mode pane owns its window and creates the renderer itself;
    /// it is passed here for the duration of `run_frame` rather than stored in
    /// `Persistent`.  In overlay and headless modes this is `None` — the renderer
    /// (if any) lives in `persistent.renderer` instead.
    ///
    /// See the module-level doc for the full explanation of the standalone vs
    /// overlay ownership split.
    pub standalone_pane: Option<&'a mut crate::draw::Pane>,
}