Skip to main content

yog_ui/
widget.rs

1//! Widget types and styling.
2
3use crate::layout::{Align, FlexDir};
4
5/// A single widget in the UI tree.
6#[derive(Debug, Clone)]
7pub struct Widget {
8    pub kind:     WidgetKind,
9    pub id:       Option<String>,
10    pub style:    Style,
11    pub flex_dir: FlexDir,
12    pub children: Vec<Widget>,
13    pub on_click: Option<String>,
14    pub enabled:  bool,
15    pub focused:  bool,
16}
17
18#[derive(Debug, Clone)]
19pub enum WidgetKind {
20    /// Container — arranges children according to `flex_dir`.
21    Panel(FlexDir),
22    /// Static or dynamic text label.
23    Label(String),
24    /// Clickable button with text.
25    Button(String),
26    /// Minecraft item icon (static, known at build time).
27    ItemSlot(String),
28    /// Dynamic inventory slot — queries slot data at render time via JNI.
29    InvSlot(usize),
30    /// Minecraft texture blit (via `draw2d_mc_tex`).
31    McImage { id: String, img_w: f32, img_h: f32 },
32    /// Invisible spacer.
33    Spacer,
34}
35
36/// WinForms-style dock — which edge(s) the widget attaches to inside its parent.
37///
38/// - `None`   — normal flex positioning (default)
39/// - `Fill`   — stretch to fill all remaining space (both axes)
40/// - `Left`   — full height, natural width, hugs left edge; other children flow right
41/// - `Right`  — full height, natural width, hugs right edge
42/// - `Top`    — full width, natural height, hugs top edge; other children flow down
43/// - `Bottom` — full width, natural height, hugs bottom edge
44#[derive(Debug, Clone, Copy, PartialEq, Default)]
45pub enum Dock { #[default] None, Fill, Left, Right, Top, Bottom }
46
47/// How a focused widget shows its focus indicator.
48#[derive(Debug, Clone, Copy, PartialEq)]
49pub enum FocusStyle {
50    /// 1px outline using `focus_color` (default, amber).
51    Outline,
52    /// Solid background fill using `focus_color`.
53    Fill,
54    /// No visual indicator.
55    None,
56}
57
58impl Default for FocusStyle { fn default() -> Self { FocusStyle::Outline } }
59
60/// Visual and layout style for a widget.
61#[derive(Debug, Clone)]
62pub struct Style {
63    pub w:      f32,   // explicit width; 0 = auto
64    pub h:      f32,   // explicit height; 0 = auto
65    pub min_w:  f32,
66    pub min_h:  f32,
67    pub flex:   f32,   // grow factor inside flex container (main axis)
68    pub dock:   Dock,  // WinForms-style edge attachment
69    pub gap:    f32,   // spacing between children
70    pub pad:    [f32; 4],  // top, right, bottom, left — space INSIDE the border
71    pub margin: [f32; 4],  // top, right, bottom, left — space OUTSIDE the border
72    pub bg:     u32,   // background colour 0xAARRGGBB; 0 = transparent
73    pub color:  u32,   // text colour
74    pub align:  Align,
75    pub font_scale:  f32,
76    pub text_shadow: bool, // MC drop-shadow behind text (HUD default; books disable it)
77    pub focus_style: FocusStyle,
78    pub focus_color: u32,  // 0 = default amber 0xFF_FFE040
79    /// Labels/buttons: never word-wrap, even if narrower than the text.
80    /// For single-line titles/headers that must stay on one line — matches
81    /// Patchouli, where such text overflows its box horizontally rather than
82    /// wrapping onto a second line.
83    pub no_wrap: bool,
84}
85
86impl Default for Style {
87    fn default() -> Self {
88        Self {
89            w: 0.0, h: 0.0, min_w: 4.0, min_h: 4.0, flex: 0.0, dock: Dock::None, gap: 2.0,
90            pad: [0.0; 4], margin: [0.0; 4], bg: 0, color: 0xFF_CCCCAA,
91            align: Align::Start, font_scale: 1.0, text_shadow: true,
92            focus_style: FocusStyle::default(), focus_color: 0, no_wrap: false,
93        }
94    }
95}
96
97// ── Builder API ───────────────────────────────────────────────────────────────
98
99impl Widget {
100    fn new(kind: WidgetKind) -> Self {
101        let flex_dir = if let WidgetKind::Panel(dir) = &kind { *dir } else { FlexDir::Column };
102        Self { kind, id: None, style: Style::default(), flex_dir,
103               children: Vec::new(), on_click: None, enabled: true, focused: false }
104    }
105
106    pub fn id(mut self, id: impl Into<String>) -> Self { self.id = Some(id.into()); self }
107    pub fn on_click(mut self, ev: impl Into<String>) -> Self { self.on_click = Some(ev.into()); self }
108    pub fn child(mut self, w: Widget) -> Self { self.children.push(w); self }
109
110    pub fn w(mut self, w: f32) -> Self { self.style.w = w; self }
111    pub fn h(mut self, h: f32) -> Self { self.style.h = h; self }
112    pub fn min_w(mut self, v: f32) -> Self { self.style.min_w = v; self }
113    pub fn min_h(mut self, v: f32) -> Self { self.style.min_h = v; self }
114    pub fn flex(mut self, v: f32) -> Self { self.style.flex = v; self }
115    pub fn flex_dir(mut self, v: FlexDir) -> Self { self.flex_dir = v; self }
116    pub fn gap(mut self, v: f32) -> Self { self.style.gap = v; self }
117    pub fn bg(mut self, v: u32) -> Self { self.style.bg = v; self }
118    pub fn color(mut self, v: u32) -> Self { self.style.color = v; self }
119    pub fn align(mut self, v: Align) -> Self { self.style.align = v; self }
120    pub fn font_scale(mut self, v: f32) -> Self { self.style.font_scale = v; self }
121    pub fn shadow(mut self, v: bool) -> Self { self.style.text_shadow = v; self }
122    pub fn no_wrap(mut self) -> Self { self.style.no_wrap = true; self }
123    pub fn focus_style(mut self, v: FocusStyle) -> Self { self.style.focus_style = v; self }
124    pub fn focus_color(mut self, v: u32) -> Self { self.style.focus_color = v; self }
125    pub fn dock(mut self, v: Dock) -> Self { self.style.dock = v; self }
126    pub fn enabled(mut self, v: bool) -> Self { self.enabled = v; self }
127    pub fn focused(mut self, v: bool) -> Self { self.focused = v; self }
128    pub fn padding(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
129        self.style.pad = [top, right, bottom, left]; self
130    }
131    pub fn margin(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
132        self.style.margin = [top, right, bottom, left]; self
133    }
134}
135
136// ── Widget constructors ───────────────────────────────────────────────────────
137
138pub fn panel(dir: FlexDir) -> Widget { Widget::new(WidgetKind::Panel(dir)) }
139pub fn label(text: impl Into<String>) -> Widget { Widget::new(WidgetKind::Label(text.into())) }
140pub fn button(text: impl Into<String>) -> Widget { Widget::new(WidgetKind::Button(text.into())) }
141pub fn item_slot(item_id: impl Into<String>) -> Widget { Widget::new(WidgetKind::ItemSlot(item_id.into())) }
142pub fn inv_slot(index: usize) -> Widget { Widget::new(WidgetKind::InvSlot(index)).w(18.0).h(18.0) }
143pub fn mc_image(id: impl Into<String>, img_w: f32, img_h: f32) -> Widget {
144    Widget::new(WidgetKind::McImage { id: id.into(), img_w, img_h })
145        .w(img_w).h(img_h)
146}
147pub fn spacer() -> Widget { Widget::new(WidgetKind::Spacer) }