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}