aetna-core 0.3.4

Aetna — backend-agnostic UI library core
Documentation
//! [`UiState`] — the renderer's interaction-state side store.
//!
//! Holds pointer position, hovered/pressed/focused targets, per-node
//! scroll offsets, the app-supplied hotkey registry, and the per-(node,
//! prop) animation map. The host doesn't touch this directly; backend
//! runners such as `aetna_wgpu::Runner` own one and route input events
//! through it.
//!
//! Visual delta application: if `pressed` is set, that node renders with
//! `state = Press`. Otherwise, if `hovered` is set, that node renders
//! with `state = Hover`. Press takes precedence so clicking a button
//! that's also hovered shows the press visual, not the hover visual.
//! Focus is independent of both — the focus ring is its own envelope.

mod animation;
mod click;
mod cursor;
mod focus;
mod interaction;
mod keyboard;
pub(crate) mod query;
mod scroll;
mod selection;
mod toast;
mod types;
mod widget_state;

use std::fmt::Debug;
// `web_time::Instant` is API-identical to `std::time::Instant` on
// native and uses `performance.now()` on wasm32 — std's `Instant::now()`
// panics in the browser because there is no monotonic clock there.

use crate::event::{KeyModifiers, PointerButton, UiTarget};

pub use types::{AnimationMode, EnvelopeKind, ScrollMetrics, ThumbDrag, WidgetState};
pub(crate) use types::{
    SelectionDrag, SelectionDragGranularity, VirtualAnchor, caret_blink_alpha_for,
};

use types::{
    AnimationState, CaretState, ClickState, FocusState, HotkeyState, LayoutState,
    NodeInteractionState, PopoverFocusState, ScrollState, SelectionState, ToastState, TooltipState,
    WidgetStateStore,
};

/// Internal UI state — interaction trackers + the side maps the library
/// writes during layout / state-apply / animation-tick passes. Owned by
/// the renderer; the host doesn't interact with this directly.
///
/// The side maps replace the per-node bookkeeping fields that used to
/// live on `El` (computed rect, interaction state, envelope amounts).
/// Keying is by `El::computed_id`, the path-shaped string assigned by
/// the layout pass.
#[derive(Default)]
pub struct UiState {
    /// Last known pointer position in **logical** pixels. `None` until
    /// the pointer enters the window.
    pub pointer_pos: Option<(f32, f32)>,
    pub hovered: Option<UiTarget>,
    pub pressed: Option<UiTarget>,
    /// Secondary / middle button down-target, kept on a separate
    /// channel so it doesn't fight the primary `pressed` envelope or
    /// move focus. Carries the button kind so `pointer_up` knows which
    /// click variant to emit. Cleared by `pointer_up` matching the
    /// same button.
    pub(crate) pressed_secondary: Option<(UiTarget, PointerButton)>,
    /// URL of the text-link run under a primary press, when present.
    /// Set by `pointer_down` from `hit_test::link_at`; consumed by
    /// `pointer_up`, which emits `UiEventKind::LinkActivated` only
    /// when the up position lands on the same link URL — same
    /// press-then-confirm contract as a normal `Click`.
    pub(crate) pressed_link: Option<String>,
    /// URL of the text-link run currently under the pointer (no
    /// button press required). Tracked by `pointer_moved` so the
    /// cursor resolver can return [`crate::cursor::Cursor::Pointer`]
    /// over links without the text leaves having to be keyed
    /// hover-test targets. Cleared on `pointer_left`.
    pub(crate) hovered_link: Option<String>,
    pub focused: Option<UiTarget>,
    /// Whether the focused element should display its focus ring.
    /// Tracks the web platform's `:focus-visible` heuristic: keyboard
    /// focus (Tab, arrow-nav) raises the flag; pointer-down clears it.
    /// Widgets where the ring belongs even on click — text inputs and
    /// text areas, where the ring communicates "this surface is now
    /// active" beyond the caret alone — opt back in via
    /// [`crate::tree::El::always_show_focus_ring`].
    pub focus_visible: bool,
    pub(crate) focus: FocusState,
    /// Mirror of the application's current
    /// [`crate::selection::Selection`]. Set by the host runner once
    /// per frame from [`crate::event::App::selection`]; read by the
    /// painter to draw highlight bands and by the selection manager
    /// to know what's currently active when extending a drag.
    pub current_selection: crate::selection::Selection,
    /// Internal selection traversal and drag state.
    pub(crate) selection: SelectionState,
    pub(crate) click: ClickState,
    pub(crate) caret: CaretState,
    pub(crate) popover_focus: PopoverFocusState,
    pub(crate) tooltip: TooltipState,
    pub(crate) scroll: ScrollState,
    /// Runtime-managed toast notification queue and id allocator.
    pub(crate) toast: ToastState,
    /// App-declared keyboard shortcuts and their action names.
    pub(crate) hotkeys: HotkeyState,
    /// Visual prop animations, state envelopes, and animation pacing.
    pub(crate) animation: AnimationState,

    // ---- side maps (formerly El bookkeeping) ----
    /// Layout-owned rect and key-index side maps.
    pub(crate) layout: LayoutState,
    /// Per-node interaction states derived from focused/pressed/hovered
    /// trackers by [`Self::apply_to_state`].
    pub(crate) node_states: NodeInteractionState,
    /// Per-(node, type) widget state buckets. The library owns the
    /// storage but never reads the values — they're for widget authors
    /// to stash text-input carets, dropdown open flags, etc. Entries
    /// are GC'd alongside envelopes/animations when a node leaves the
    /// tree (see [`Self::tick_visual_animations`]).
    widget_states: WidgetStateStore,
    /// Last known keyboard modifier mask. Updated by the host runner
    /// from winit's `ModifiersChanged`; pointer events stamp this
    /// value into their `UiEvent.modifiers` so widgets that need to
    /// detect Shift+click / Ctrl+drag can read it without separate
    /// plumbing.
    pub modifiers: KeyModifiers,
}

impl UiState {
    pub fn new() -> Self {
        Self::default()
    }
}

impl Debug for UiState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UiState")
            .field("pointer_pos", &self.pointer_pos)
            .field("hovered", &self.hovered)
            .field("pressed", &self.pressed)
            .field("focused", &self.focused)
            .field("focus_visible", &self.focus_visible)
            .field("focus", &self.focus)
            .field("popover_focus", &self.popover_focus)
            .field("click", &self.click)
            .field("caret", &self.caret)
            .field("scroll", &self.scroll)
            .field("toast", &self.toast)
            .field("tooltip", &self.tooltip)
            .field("hotkeys", &self.hotkeys)
            .field("animation", &self.animation)
            .field("layout", &self.layout)
            .field("node_states", &self.node_states)
            .field("modifiers", &self.modifiers)
            .field("widget_states", &self.widget_states)
            .finish()
    }
}

#[cfg(test)]
mod tests;