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