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}