Skip to main content

yog_ui/
lib.rs

1//! yog-ui — retained-mode UI framework for Yog mods.
2//!
3//! Flexbox-inspired layout engine and a WPF-style [`grid`](widget::grid)
4//! (rows/columns: pixels, `Auto`, `*`) + GPU rendering via [`yog-gfx`].
5//! Use for custom inventories, guide books, tooltips, HUD overlays.
6//!
7//! # Quick start
8//! ```ignore
9//! use yog_ui::{UiRoot, widget, Align, FlexDir, Units};
10//!
11//! let ui = UiRoot::new("mymod:main_menu")
12//!     .style(|s| s.bg(0x88332211).padding(8.0, 8.0, 8.0, 8.0))
13//!     .child(
14//!         widget::panel(FlexDir::Column).gap(4.0)
15//!             .child(widget::label("Hello, World!").color(0xFF_DDAA00))
16//!             .child(widget::button("Click me").on_click("mymod:btn_click"))
17//!     );
18//! ```
19
20pub mod component;
21pub mod event;
22pub mod image;
23pub mod layout;
24mod render;
25pub mod scroll;
26#[cfg(test)]
27mod state_tests;
28pub mod text;
29pub mod textbox;
30pub mod widget;
31
32pub use event::UiEvent;
33pub use layout::{hit_path, node_at, set_focus, Align, FlexDir, GridLines, LayoutNode, Rect};
34pub use scroll::ScrollState;
35pub use textbox::{TextBoxEvent, TextBoxState};
36pub use widget::{
37    Action, Cell, Dock, FocusStyle, GridLength, GridSpec, HAlign, StateFlags, StateStyles, Stretch,
38    StylePatch, Track, Units, VAlign, Widget,
39};
40
41use yog_gfx::GfxContext;
42
43/// Top-level UI tree.  Build it, call [`layout`], then [`render`] each frame.
44pub struct UiRoot {
45    pub id: String,
46    pub root: Widget,
47    pub layout_root: LayoutNode,
48    pub needs_layout: bool,
49}
50
51impl UiRoot {
52    pub fn new(id: impl Into<String>, root: Widget) -> Self {
53        Self {
54            id: id.into(),
55            root,
56            layout_root: LayoutNode::default(),
57            needs_layout: true,
58        }
59    }
60
61    /// Recalculate layout. Call after changing the tree or on window resize.
62    pub fn layout(&mut self, screen_w: f32, screen_h: f32) {
63        self.layout_root = layout::compute(&self.root, screen_w, screen_h);
64        self.needs_layout = false;
65    }
66
67    /// Render the UI tree via `yog-gfx` draw2d.
68    /// Must be called from `on_hud_render`.
69    pub fn render(&self, ctx: &GfxContext) {
70        text::use_host_metrics(ctx);
71        image::use_host_images(ctx);
72        let d2d = ctx.draw2d();
73        render::render_node(&d2d, &self.root, &self.layout_root);
74    }
75
76    /// Find the deepest clickable widget at `(mx, my)` in screen coordinates.
77    pub fn hit_test(&self, mx: f32, my: f32) -> Option<&LayoutNode> {
78        layout::hit_test(&self.layout_root, mx, my)
79    }
80
81    /// Resolved rect of the (first) node tagged `id` — see [`layout::find_by_id`].
82    /// Call after [`Self::layout`].
83    pub fn find_rect(&self, id: &str) -> Option<Rect> {
84        layout::find_by_id(&self.layout_root, id)
85    }
86
87    /// Laid-out node of the (first) widget tagged `id` — rect plus scroll
88    /// state, see [`layout::find_node`]. Call after [`Self::layout`].
89    pub fn find_node(&self, id: &str) -> Option<&LayoutNode> {
90        layout::find_node(&self.layout_root, id)
91    }
92}