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