macro_reference/
macro_reference.rs

1// Catch potential problems with clippy and the macro
2#![warn(
3    missing_debug_implementations,
4    rust_2018_idioms,
5    unreachable_pub,
6    unused_qualifications,
7    clippy::cargo,
8    clippy::must_use_candidate,
9    clippy::used_underscore_binding
10)]
11
12use gtk::prelude::{
13    BoxExt, ButtonExt, GridExt, GtkWindowExt, ObjectExt, OrientableExt, ToggleButtonExt, WidgetExt,
14};
15use relm4::{ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};
16
17#[tracker::track]
18struct App {
19    value: u8,
20}
21
22#[derive(Debug)]
23enum Msg {
24    Increment,
25    Decrement,
26}
27
28struct AppInit {
29    counter: u8,
30}
31
32#[relm4::component(pub)]
33impl SimpleComponent for App {
34    type Init = AppInit;
35    type Input = Msg;
36    type Output = ();
37    // AppWidgets is generated by the macro,
38    // Can be omitted in most cases.
39    type Widgets = AppWidgets;
40
41    view! {
42        #[root]
43        #[name(main_window)]
44        gtk::Window {
45            set_title: Some("Macro reference example"),
46            set_default_size: (300, 100),
47
48            gtk::Box {
49                set_orientation: gtk::Orientation::Vertical,
50                set_spacing: 5,
51                set_margin_all: 5,
52
53                append: inc_button = &gtk::Button {
54                    set_label: "Increment",
55                    // Only set this if `icon_name` is Some
56                    set_icon_name?: icon_name,
57                    connect_clicked[sender] => move |_| {
58                        sender.input(Msg::Increment);
59                    }
60                },
61
62                gtk::Button {
63                    set_label: "Decrement",
64                    connect_clicked => Msg::Decrement,
65                },
66
67                gtk::Grid {
68                    attach[1, 1, 1, 1] = &gtk::Label {
69                        set_label: "Count to 10 to see if the tracker works!",
70                        // Alternative: #[track = "counter.value % 10 == 0"]
71                        #[track(skip_init, counter.value % 10 == 0)]
72                        set_label: &format!("Grid works! ({})", counter.value),
73                    }
74                },
75
76                // A conditional widget
77                // Alternative: #[transition = "SlideLeft"]
78                #[transition(SlideLeft)]
79                append = if counter.value % 2 == 0 {
80                    gtk::Label {
81                        set_label: "Value is even",
82                    }
83                } else if counter.value % 3 == 0 {
84                    gtk::Label {
85                        set_label: "Value is dividable by 3",
86                    }
87                } else {
88                    gtk::Label {
89                        set_label: "Value is odd",
90                    }
91                },
92
93                #[transition = "SlideRight"]
94                append: match_stack = match counter.value {
95                    (0..=2) => {
96                        gtk::Label {
97                            set_label: "Value is smaller than 3",
98                        }
99                    },
100                    _ => {
101                        gtk::Label {
102                            set_label: "Value is higher than 2",
103                        }
104                    },
105                },
106
107                append = &gtk::Label,
108
109                gtk::Label::builder()
110                    .label("Builder pattern works!")
111                    .selectable(true)
112                    .build(),
113
114                gtk::Label::new(Some("Constructors work!")),
115
116                /// Counter label
117                #[name = "counter_label"]
118                gtk::Label {
119                    // Mirror property "label" for widget "bind_label"
120                    #[chain(build())]
121                    bind_property: ("label", &bind_label, "label"),
122                    set_label: "Click the counter so see the value!",
123
124                    #[watch(skip_init)]
125                    set_label: &format!("Counter: {}", counter.value),
126                    #[track]
127                    set_margin_all: counter.value.into(),
128                },
129
130                #[name = "bind_label"]
131                gtk::Label {},
132
133                // You can also use returned widgets
134                gtk::Stack {
135                    add_child = &gtk::Label {
136                        set_label: "placeholder",
137                    } -> {
138                        // Set the title of the stack page
139                        set_title: "page title",
140                    }
141                },
142
143                gtk::ToggleButton {
144                    set_label: "Counter is even",
145                    #[watch]
146                    #[block_signal(toggle_handler)]
147                    set_active: counter.value % 2 == 0,
148
149                    connect_toggled[sender] => move |_| {
150                        sender.input(Msg::Increment);
151                    } @toggle_handler,
152                },
153
154                #[local]
155                local_label -> gtk::Label {
156                    set_opacity: 0.7,
157                },
158
159                #[local_ref]
160                local_ref_label -> gtk::Label {
161                    set_opacity: 0.7,
162                    set_size_request: (40, 40),
163                },
164            }
165        },
166        gtk::Window {
167            set_title: Some("Another window"),
168            set_default_size: (300, 100),
169            set_transient_for: Some(&main_window),
170            set_visible: false,
171            // Empty args
172            grab_focus: (),
173
174            #[watch]
175            set_visible: counter.value == 42,
176
177            #[name = "my_label_name"]
178            gtk::Label {
179                set_label: "You made it to 42!",
180            }
181        }
182    }
183
184    // Initialize the component.
185    fn init(
186        init: Self::Init,
187        renamed_root: Self::Root,
188        sender: ComponentSender<Self>,
189    ) -> ComponentParts<Self> {
190        let counter = App {
191            value: init.counter,
192            tracker: 0,
193        };
194
195        // Set icon name randomly to Some("go-up-symbolic") or None
196        let icon_name = rand::random::<bool>().then_some("go-up-symbolic");
197
198        let local_label = gtk::Label::new(Some("local_label"));
199        let local_ref_label_value = gtk::Label::new(Some("local_ref_label"));
200        let local_ref_label = &local_ref_label_value;
201        // Insert the macro code generation here
202        let widgets = view_output!();
203
204        ComponentParts {
205            model: counter,
206            widgets,
207        }
208    }
209
210    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
211        self.reset();
212        match msg {
213            Msg::Increment => {
214                self.set_value(self.value.wrapping_add(1));
215            }
216            Msg::Decrement => {
217                self.set_value(self.value.wrapping_sub(1));
218            }
219        }
220    }
221}
222
223fn main() {
224    let app = RelmApp::new("relm4.example.macro_reference");
225    app.run::<App>(AppInit { counter: 0 });
226}