Skip to main content

yog_ui/widget/
builder.rs

1//! The builder API: a widget's style and children set call by call.
2
3use super::{
4    Action, Dock, FocusStyle, HAlign, StateFlags, Stretch, Style, StylePatch, Track, Units, VAlign,
5    Widget, WidgetKind,
6};
7use yog_gfx::{Color, Rect};
8
9use crate::layout::{Align, FlexDir};
10
11impl Widget {
12    pub(crate) fn new(kind: WidgetKind) -> Self {
13        let flex_dir = if let WidgetKind::Panel(dir) = &kind {
14            *dir
15        } else {
16            FlexDir::Column
17        };
18        Self {
19            kind,
20            id: None,
21            style: Style::default(),
22            flex_dir,
23            children: Vec::new(),
24            on_click: None,
25            enabled: true,
26            focused: false,
27            action: None,
28            visible: true,
29            state: StateFlags::NONE,
30            states: None,
31            tooltip: None,
32        }
33    }
34
35    /// See [`Self::tooltip`]; empty = none.
36    pub fn tooltip(mut self, text: impl Into<String>) -> Self {
37        self.set_tooltip(text);
38        self
39    }
40    /// Sets (empty: clears) [`Self::tooltip`].
41    pub fn set_tooltip(&mut self, text: impl Into<String>) {
42        let t = text.into();
43        self.tooltip = (!t.is_empty()).then_some(t);
44    }
45
46    /// Typed click target — see [`Action`].
47    pub fn action(mut self, a: Action) -> Self {
48        self.action = Some(a);
49        self
50    }
51    /// `false` = collapsed — see [`Widget::visible`].
52    pub fn visible(mut self, v: bool) -> Self {
53        self.visible = v;
54        self
55    }
56    /// Adds a paint-only override applied while the state contains `when`
57    /// (later calls win over earlier ones).
58    pub fn state_style(mut self, when: StateFlags, patch: StylePatch) -> Self {
59        self.states
60            .get_or_insert_with(Default::default)
61            .rules
62            .push((when, patch));
63        self
64    }
65
66    /// The descendant at child-index `path` (`&[]` = self); `None` past the
67    /// tree.
68    pub fn get_path(&self, path: &[u16]) -> Option<&Widget> {
69        let mut w = self;
70        for &i in path {
71            w = w.children.get(i as usize)?;
72        }
73        Some(w)
74    }
75    /// [`Self::get_path`], mutably.
76    pub fn get_path_mut(&mut self, path: &[u16]) -> Option<&mut Widget> {
77        let mut w = self;
78        for &i in path {
79            w = w.children.get_mut(i as usize)?;
80        }
81        Some(w)
82    }
83
84    /// Whether clicking it does something (an [`Action`] or `on_click`).
85    pub fn is_interactive(&self) -> bool {
86        self.action.is_some() || self.on_click.is_some()
87    }
88
89    /// Sets (or clears) `mask` on this widget and its descendants, stopping
90    /// at descendants that are interactive themselves (a button in a row
91    /// keeps its own hover) — so a row's labels follow the row's state.
92    pub fn set_state_inherited(&mut self, mask: StateFlags, on: bool) {
93        self.state.set(mask, on);
94        for c in &mut self.children {
95            if !c.is_interactive() {
96                c.set_state_inherited(mask, on);
97            }
98        }
99    }
100
101    /// Clears `mask` on the whole subtree (interactive descendants too).
102    pub fn clear_state_deep(&mut self, mask: StateFlags) {
103        self.state.remove(mask);
104        for c in &mut self.children {
105            c.clear_state_deep(mask);
106        }
107    }
108
109    /// The style drawn: [`Self::style`] plus the [`Self::states`] patches
110    /// matching [`Self::state`] (with DISABLED while `enabled` is false).
111    pub fn effective_style(&self) -> std::borrow::Cow<'_, Style> {
112        let mut state = self.state;
113        if !self.enabled {
114            state.insert(StateFlags::DISABLED);
115        }
116        self.effective_style_in(state)
117    }
118
119    /// [`Self::effective_style`] for an explicit `state`.
120    pub fn effective_style_in(&self, state: StateFlags) -> std::borrow::Cow<'_, Style> {
121        let Some(states) = &self.states else {
122            return std::borrow::Cow::Borrowed(&self.style);
123        };
124        let mut matching = states
125            .rules
126            .iter()
127            .filter(|(when, _)| !when.is_empty() && state.contains(*when))
128            .peekable();
129        if matching.peek().is_none() {
130            return std::borrow::Cow::Borrowed(&self.style);
131        }
132        let mut s = self.style.clone();
133        for (_, patch) in matching {
134            patch.apply(&mut s);
135        }
136        std::borrow::Cow::Owned(s)
137    }
138
139    pub fn id(mut self, id: impl Into<String>) -> Self {
140        self.id = Some(id.into());
141        self
142    }
143    pub fn on_click(mut self, ev: impl Into<String>) -> Self {
144        self.on_click = Some(ev.into());
145        self
146    }
147    pub fn child(mut self, w: Widget) -> Self {
148        self.children.push(w);
149        self
150    }
151
152    /// Accepts a plain px value (`0.0` = auto) or a [`Units`] directly —
153    /// e.g. `.w(18.0)` or `.w(Units::Percent(60.0))`.
154    pub fn w(mut self, w: impl Into<Units>) -> Self {
155        self.style.w = w.into();
156        self
157    }
158    pub fn h(mut self, h: impl Into<Units>) -> Self {
159        self.style.h = h.into();
160        self
161    }
162    pub fn min_w(mut self, v: f32) -> Self {
163        self.style.min_w = v;
164        self
165    }
166    pub fn min_h(mut self, v: f32) -> Self {
167        self.style.min_h = v;
168        self
169    }
170    pub fn flex(mut self, v: f32) -> Self {
171        self.style.flex = v;
172        self
173    }
174    pub fn flex_dir(mut self, v: FlexDir) -> Self {
175        self.flex_dir = v;
176        self
177    }
178    pub fn gap(mut self, v: f32) -> Self {
179        self.style.gap = v;
180        self
181    }
182    pub fn bg(mut self, v: Color) -> Self {
183        self.style.bg = v;
184        self
185    }
186    /// See [`Style::bg_image`].
187    pub fn bg_image(mut self, source: impl Into<String>) -> Self {
188        self.style.bg_image = source.into();
189        self
190    }
191    pub fn color(mut self, v: Color) -> Self {
192        self.style.color = v;
193        self
194    }
195    pub fn align(mut self, v: Align) -> Self {
196        self.style.align = v;
197        self
198    }
199    pub fn font_scale(mut self, v: f32) -> Self {
200        self.style.font_scale = v;
201        self
202    }
203    pub fn shadow(mut self, v: bool) -> Self {
204        self.style.text_shadow = v;
205        self
206    }
207    pub fn no_wrap(mut self) -> Self {
208        self.style.no_wrap = true;
209        self
210    }
211    pub fn focus_style(mut self, v: FocusStyle) -> Self {
212        self.style.focus_style = v;
213        self
214    }
215    pub fn focus_color(mut self, v: Color) -> Self {
216        self.style.focus_color = v;
217        self
218    }
219    pub fn dock(mut self, v: Dock) -> Self {
220        self.style.dock = v;
221        self
222    }
223    pub fn enabled(mut self, v: bool) -> Self {
224        self.enabled = v;
225        self
226    }
227    pub fn focused(mut self, v: bool) -> Self {
228        self.focused = v;
229        self
230    }
231    /// Position absolutely at `(x, y)` inside the parent's content box — see [`Style::pos`].
232    pub fn at(mut self, x: f32, y: f32) -> Self {
233        self.style.pos = Some((x, y));
234        self
235    }
236    /// Single line, cut with `...` when too wide — see [`Style::ellipsis`].
237    pub fn ellipsis(mut self) -> Self {
238        self.style.no_wrap = true;
239        self.style.ellipsis = true;
240        self
241    }
242    /// Clip children to this widget's rect — see [`Style::clip`].
243    pub fn clip(mut self, v: bool) -> Self {
244        self.style.clip = v;
245        self
246    }
247    /// Vertical scroll offset of the children — see [`Style::scroll_y`].
248    pub fn scroll_y(mut self, v: f32) -> Self {
249        self.style.scroll_y = v;
250        self
251    }
252    /// Draw a vertical scrollbar on overflow — see [`Style::scrollbar`].
253    pub fn scrollbar(mut self, v: bool) -> Self {
254        self.style.scrollbar = v;
255        self
256    }
257    /// Font for this widget's text — see [`Style::font`].
258    pub fn font(mut self, font: impl Into<String>) -> Self {
259        self.style.font = font.into();
260        self
261    }
262    /// Text box: greyed text shown while it's empty.
263    pub fn placeholder(mut self, text: impl Into<String>) -> Self {
264        if let WidgetKind::TextBox(t) = &mut self.kind {
265            t.placeholder = text.into();
266        }
267        self
268    }
269    /// Text box: longest text allowed, in characters (0 = unlimited).
270    pub fn max_length(mut self, n: usize) -> Self {
271        if let WidgetKind::TextBox(t) = &mut self.kind {
272            t.max_length = n;
273        }
274        self
275    }
276    /// Text box: event name raised when the text changes.
277    pub fn on_change(mut self, ev: impl Into<String>) -> Self {
278        if let WidgetKind::TextBox(t) = &mut self.kind {
279            t.on_change = Some(ev.into());
280        }
281        self
282    }
283    /// Text box: event name raised by Enter.
284    pub fn on_submit(mut self, ev: impl Into<String>) -> Self {
285        if let WidgetKind::TextBox(t) = &mut self.kind {
286            t.on_submit = Some(ev.into());
287        }
288        self
289    }
290    /// Image: how it fills the widget (WPF's `Stretch`).
291    pub fn stretch(mut self, stretch: Stretch) -> Self {
292        if let WidgetKind::Image(i) = &mut self.kind {
293            i.stretch = stretch;
294        }
295        self
296    }
297    /// Image: draw only `region` of it (texture pixels).
298    pub fn region(mut self, region: Rect) -> Self {
299        if let WidgetKind::Image(i) = &mut self.kind {
300            i.region = Some(region);
301        }
302        self
303    }
304    /// Image: multiply its colors by `tint`.
305    pub fn tint(mut self, tint: Color) -> Self {
306        if let WidgetKind::Image(i) = &mut self.kind {
307            i.tint = tint;
308        }
309        self
310    }
311    /// Adds every widget in `children`, in order.
312    pub fn children(mut self, children: impl IntoIterator<Item = Widget>) -> Self {
313        self.children.extend(children);
314        self
315    }
316
317    /// The first widget in this subtree (depth-first, self included) whose
318    /// [`Widget::id`] is `id` — for changing a built tree before laying it
319    /// out (dynamic text, enabled state, ...).
320    pub fn find_mut(&mut self, id: &str) -> Option<&mut Widget> {
321        if self.id.as_deref() == Some(id) {
322            return Some(self);
323        }
324        self.children.iter_mut().find_map(|c| c.find_mut(id))
325    }
326
327    /// Replaces the text of a label, button or text box; `false` for any
328    /// other kind.
329    pub fn set_text(&mut self, text: impl Into<String>) -> bool {
330        match &mut self.kind {
331            WidgetKind::Label(t) | WidgetKind::Button(t) => {
332                *t = text.into();
333                true
334            }
335            WidgetKind::TextBox(t) => {
336                t.text = text.into();
337                true
338            }
339            _ => false,
340        }
341    }
342
343    /// [`set_text`](Self::set_text) as a builder call.
344    pub fn with_text(mut self, text: impl Into<String>) -> Self {
345        self.set_text(text);
346        self
347    }
348
349    /// Replaces an image's source or a slot's item id (other kinds: no-op).
350    pub fn with_source(mut self, source: impl Into<String>) -> Self {
351        match &mut self.kind {
352            WidgetKind::Image(i) => i.source = source.into(),
353            WidgetKind::Surface(id) => *id = source.into(),
354            WidgetKind::ItemSlot(id) | WidgetKind::InvSlot { item_id: id, .. } => {
355                *id = source.into()
356            }
357            _ => {}
358        }
359        self
360    }
361
362    /// Replaces an inventory slot's stack count (other kinds: no-op).
363    pub fn with_count(mut self, n: u32) -> Self {
364        if let WidgetKind::InvSlot { count, .. } = &mut self.kind {
365            *count = n;
366        }
367        self
368    }
369
370    pub fn padding(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
371        self.style.pad = [top, right, bottom, left];
372        self
373    }
374    pub fn margin(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
375        self.style.margin = [top, right, bottom, left];
376        self
377    }
378
379    /// Puts it in grid cell `(row, col)` (spanning one row and column) —
380    /// WPF's `Grid.Row` / `Grid.Column`. See [`Self::span`].
381    pub fn cell(mut self, row: u16, col: u16) -> Self {
382        self.style.cell.row = row;
383        self.style.cell.col = col;
384        self
385    }
386    /// Spans `rows` × `cols` grid cells from its [`Self::cell`] — WPF's
387    /// `Grid.RowSpan` / `Grid.ColumnSpan` (0 counts as 1).
388    pub fn span(mut self, rows: u16, cols: u16) -> Self {
389        self.style.cell.row_span = rows.max(1);
390        self.style.cell.col_span = cols.max(1);
391        self
392    }
393    /// Horizontal placement in its slot — see [`HAlign`].
394    pub fn halign(mut self, v: HAlign) -> Self {
395        self.style.halign = Some(v);
396        self
397    }
398    /// Vertical placement in its slot — see [`VAlign`].
399    pub fn valign(mut self, v: VAlign) -> Self {
400        self.style.valign = Some(v);
401        self
402    }
403
404    /// Grid: its rows (no rows = one `*` row).
405    pub fn rows(mut self, rows: impl IntoIterator<Item = impl Into<Track>>) -> Self {
406        if let WidgetKind::Grid(g) = &mut self.kind {
407            g.rows = rows.into_iter().map(Into::into).collect();
408        }
409        self
410    }
411    /// Grid: its columns (no columns = one `*` column).
412    pub fn cols(mut self, cols: impl IntoIterator<Item = impl Into<Track>>) -> Self {
413        if let WidgetKind::Grid(g) = &mut self.kind {
414            g.cols = cols.into_iter().map(Into::into).collect();
415        }
416        self
417    }
418    /// Grid: space between rows.
419    pub fn row_spacing(mut self, v: f32) -> Self {
420        if let WidgetKind::Grid(g) = &mut self.kind {
421            g.row_spacing = v;
422        }
423        self
424    }
425    /// Grid: space between columns.
426    pub fn col_spacing(mut self, v: f32) -> Self {
427        if let WidgetKind::Grid(g) = &mut self.kind {
428            g.col_spacing = v;
429        }
430        self
431    }
432}