Skip to main content

frus_widgets/
dsl.rs

1//! Syntactic sugar for describing an interface **faster**: the
2//! [`row!`](crate::row) and [`column!`](crate::column) macros, plus a few shorthand
3//! functions. Purely additive — the constructors remain available.
4
5use std::hash::Hash;
6
7use crate::align::Center;
8use crate::button::Button;
9use crate::expanded::{Expanded, Flexible};
10use crate::flex::Flex;
11use crate::keyed::Keyed;
12use crate::text::Text;
13use crate::widget::Widget;
14
15/// Shorthand: a text widget. `text("Hello")` = `Text::new("Hello")`.
16pub fn text(content: impl Into<String>) -> Text {
17    Text::new(content)
18}
19
20/// Shorthand: a flexible **spacer** that pushes its neighbours apart (`flex: 1`).
21pub fn spacer<Msg>() -> Flex<Msg> {
22    Flex::row().flex(1.0)
23}
24
25/// Shorthand: the child that takes the room its siblings left.
26/// `expanded(text(&title).ellipsis())` = `Expanded::new(...)`.
27pub fn expanded<Msg>(child: impl Widget<Msg> + 'static) -> Expanded<Msg> {
28    Expanded::new(child)
29}
30
31/// Shorthand: a child that takes **at most** the room its siblings left, and less if it
32/// wants less. `flexible(child)` = `Flexible::new(child)`.
33pub fn flexible<Msg>(child: impl Widget<Msg> + 'static) -> Flexible<Msg> {
34    Flexible::new(child)
35}
36
37/// Shorthand: the child centred in the room it is given. `center(text("Nothing yet"))` =
38/// `Center::new(...)`.
39pub fn center<Msg: Clone + 'static>(child: impl Widget<Msg> + 'static) -> Center<Msg> {
40    Center::new(child)
41}
42
43/// Shorthand: a button with its click message.
44/// `button("Ajouter", Msg::Add)` = `Button::new("Ajouter").on_press(Msg::Add)`.
45pub fn button<Msg: Clone + 'static>(
46    label: impl Into<String>,
47    on_press: impl Into<Msg>,
48) -> Button<Msg> {
49    Button::new(label).on_press(on_press)
50}
51
52/// Shorthand: gives a widget — a list item, say — a **stable identity**.
53/// `keyed(todo.id, todo_row(todo))` is `Keyed::new(todo.id, todo_row(todo))`.
54pub fn keyed<Msg>(key: impl Hash, widget: impl Widget<Msg> + 'static) -> Keyed<Msg> {
55    Keyed::new(key, widget)
56}
57
58/// A flex **row**. `row![a, b, c]` is `Flex::row().child(a).child(b).child(c)`.
59/// The result stays chainable: `row![a, b].gap(8.0).align(...)`.
60#[macro_export]
61macro_rules! row {
62    () => { $crate::Flex::row() };
63    ($($child:expr),+ $(,)?) => {
64        $crate::Flex::row()$(.child($child))+
65    };
66}
67
68/// A flex **column**. `column![a, b, c]` is `Flex::column().child(a)…`.
69/// The result stays chainable: `column![a, b].gap(16.0).padding(20.0)`.
70#[macro_export]
71macro_rules! column {
72    () => { $crate::Flex::column() };
73    ($($child:expr),+ $(,)?) => {
74        $crate::Flex::column()$(.child($child))+
75    };
76}
77
78/// An image **embedded from a file**, by path. `asset!("../assets/logo.png")` is
79/// `Image::memory(include_bytes!("../assets/logo.png"))`, and like every `include_*!`
80/// the path is relative to the **file that writes it**.
81///
82/// ```ignore
83/// asset!("../assets/logo.png").width(96.0).semantic_label("frus")
84/// ```
85///
86/// The bytes go into the binary at compile time, so there is no file to find at run
87/// time, no path to get wrong on another machine, and no asset manifest to keep in step
88/// with the source. That is what Rust gives here that a language without
89/// `include_bytes!` has to build an asset bundle to get.
90///
91/// The result is a plain [`Image`](crate::Image) and stays chainable. It is decoded once
92/// per process; see [`Image::memory`](crate::Image::memory) for the store behind it and
93/// for what a file that will not decode does.
94#[macro_export]
95macro_rules! asset {
96    ($path:literal) => {
97        $crate::Image::memory(include_bytes!($path))
98    };
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::widget::Widget;
105
106    #[test]
107    fn macros_collect_children() {
108        let col: Flex<()> = column![text("a"), text("b"), text("c")];
109        assert_eq!(Widget::<()>::children(&col).len(), 3);
110
111        let empty: Flex<()> = row![];
112        assert_eq!(Widget::<()>::children(&empty).len(), 0);
113
114        // A chainable result, plus nested rows.
115        let nested: Flex<()> =
116            column![text("title"), row![text("a"), spacer(), text("b")]].gap(8.0);
117        assert_eq!(Widget::<()>::children(&nested).len(), 2);
118    }
119
120    #[test]
121    fn button_helper_sets_message() {
122        #[derive(Clone, PartialEq, Debug)]
123        enum Msg {
124            Go,
125        }
126        let b = button("Ok", Msg::Go);
127        assert_eq!(Widget::on_click(&b), Some(Msg::Go));
128    }
129}