yog-ui 0.38.2

Yog UI framework — flexbox layout engine + GPU rendering via yog-gfx
Documentation
//! yog-ui — retained-mode UI framework for Yog mods.
//!
//! Flexbox-inspired layout engine + GPU rendering via [`yog-gfx`].
//! Use for custom inventories, guide books, tooltips, HUD overlays.
//!
//! # Quick start
//! ```ignore
//! use yog_ui::{UiRoot, widget, Align, FlexDir, Units};
//!
//! let ui = UiRoot::new("mymod:main_menu")
//!     .style(|s| s.bg(0x88332211).padding(8.0, 8.0, 8.0, 8.0))
//!     .child(
//!         widget::panel(FlexDir::Column).gap(4.0)
//!             .child(widget::label("Hello, World!").color(0xFF_DDAA00))
//!             .child(widget::button("Click me").on_click("mymod:btn_click"))
//!     );
//! ```

pub mod component;
pub mod event;
pub mod image;
pub mod layout;
mod render;
pub mod scroll;
#[cfg(test)]
mod state_tests;
pub mod text;
pub mod textbox;
pub mod widget;

pub use event::UiEvent;
pub use layout::{hit_path, node_at, set_focus, Align, FlexDir, LayoutNode, Rect};
pub use scroll::ScrollState;
pub use textbox::{TextBoxEvent, TextBoxState};
pub use widget::{
    Action, Dock, FocusStyle, StateFlags, StateStyles, Stretch, StylePatch, Units, Widget,
};

use yog_gfx::GfxContext;

/// Top-level UI tree.  Build it, call [`layout`], then [`render`] each frame.
pub struct UiRoot {
    pub id: String,
    pub root: Widget,
    pub layout_root: LayoutNode,
    pub needs_layout: bool,
}

impl UiRoot {
    pub fn new(id: impl Into<String>, root: Widget) -> Self {
        Self {
            id: id.into(),
            root,
            layout_root: LayoutNode::default(),
            needs_layout: true,
        }
    }

    /// Recalculate layout. Call after changing the tree or on window resize.
    pub fn layout(&mut self, screen_w: f32, screen_h: f32) {
        self.layout_root = layout::compute(&self.root, screen_w, screen_h);
        self.needs_layout = false;
    }

    /// Render the UI tree via `yog-gfx` draw2d.
    /// Must be called from `on_hud_render`.
    pub fn render(&self, ctx: &GfxContext) {
        text::use_host_metrics(ctx);
        image::use_host_images(ctx);
        let d2d = ctx.draw2d();
        render::render_node(&d2d, &self.root, &self.layout_root);
    }

    /// Find the deepest clickable widget at `(mx, my)` in screen coordinates.
    pub fn hit_test(&self, mx: f32, my: f32) -> Option<&LayoutNode> {
        layout::hit_test(&self.layout_root, mx, my)
    }

    /// Resolved rect of the (first) node tagged `id` — see [`layout::find_by_id`].
    /// Call after [`Self::layout`].
    pub fn find_rect(&self, id: &str) -> Option<Rect> {
        layout::find_by_id(&self.layout_root, id)
    }

    /// Laid-out node of the (first) widget tagged `id` — rect plus scroll
    /// state, see [`layout::find_node`]. Call after [`Self::layout`].
    pub fn find_node(&self, id: &str) -> Option<&LayoutNode> {
        layout::find_node(&self.layout_root, id)
    }
}