relm4_macros/
lib.rs

1//! A collection of macros for gtk-rs, Relm4 and Rust in general.
2//!
3//! Docs of related crates:
4//! [relm4](https://docs.rs/relm4)
5//! | [relm4-macros](https://docs.rs/relm4_macros)
6//! | [relm4-components](https://docs.rs/relm4_components)
7//! | [relm4-css](https://docs.rs/relm4-css)
8//! | [gtk4-rs](https://gtk-rs.org/gtk4-rs/git/docs)
9//! | [gtk-rs-core](https://gtk-rs.org/gtk-rs-core/git/docs)
10//! | [libadwaita-rs](https://world.pages.gitlab.gnome.org/Rust/libadwaita-rs/git/docs/libadwaita)
11//! | [libpanel-rs](https://world.pages.gitlab.gnome.org/Rust/libpanel-rs/git/docs/libpanel)
12//!
13//! [GitHub](https://github.com/Relm4/Relm4)
14//! | [Website](https://relm4.org)
15//! | [Book](https://relm4.org/book/stable/)
16//! | [Blog](https://relm4.org/blog)
17 
18
19#![doc(html_logo_url = "https://relm4.org/icons/relm4_logo.svg")]
20#![doc(html_favicon_url = "https://relm4.org/icons/relm4_org.svg")]
21#![warn(
22    missing_debug_implementations,
23    missing_docs,
24    rust_2018_idioms,
25    unreachable_pub,
26    unused_qualifications,
27    clippy::cargo,
28    clippy::must_use_candidate
29)]
30
31use proc_macro::TokenStream;
32use syn::{ItemImpl, parse_macro_input};
33
34mod additional_fields;
35mod args;
36mod attrs;
37mod component;
38mod menu;
39mod view;
40mod visitors;
41mod widgets;
42
43#[macro_use]
44mod util;
45mod factory;
46mod token_streams;
47mod widget_template;
48
49use attrs::{Attrs, SyncOnlyAttrs};
50use menu::Menus;
51
52fn gtk_import() -> syn::Path {
53    if cfg!(feature = "relm4") {
54        util::strings_to_path(&["relm4", "gtk"])
55    } else {
56        util::strings_to_path(&["gtk"])
57    }
58}
59
60/// Macro that implements `relm4::Component` or `relm4::SimpleComponent`
61/// and generates the corresponding widgets struct.
62///
63/// # Attributes
64///
65/// To create public struct use `#[component(pub)]` or `#[component(visibility = pub)]`.
66///
67/// # Example
68///
69/// ```
70/// use relm4::prelude::*;
71/// use gtk::prelude::*;
72///
73/// #[derive(Default)]
74/// struct App {
75///     counter: u8,
76/// }
77///
78/// #[derive(Debug)]
79/// enum Msg {
80///     Increment,
81///     Decrement,
82/// }
83///
84/// #[relm4_macros::component(pub)]
85/// impl SimpleComponent for App {
86///     type Init = u8;
87///     type Input = Msg;
88///     type Output = ();
89///
90///     view! {
91///         gtk::Window {
92///             set_title: Some("Simple app"),
93///             set_default_size: (300, 100),
94///             gtk::Box {
95///                 set_orientation: gtk::Orientation::Vertical,
96///                 set_margin_all: 5,
97///                 set_spacing: 5,
98///
99///                 gtk::Button {
100///                     set_label: "Increment",
101///                     connect_clicked => Msg::Increment,
102///                 },
103///                 gtk::Button {
104///                     set_label: "Decrement",
105///                     connect_clicked[sender] => move |_| {
106///                         sender.input(Msg::Decrement);
107///                     },
108///                 },
109///                 gtk::Label {
110///                     set_margin_all: 5,
111///                     #[watch]
112///                     set_label: &format!("Counter: {}", model.counter),
113///                 }
114///             },
115///         }
116///     }
117///
118///     fn init(
119///         counter: Self::Init,
120///         root: Self::Root,
121///         sender: ComponentSender<Self>,
122///     ) -> ComponentParts<Self> {
123///         let model = Self { counter };
124///
125///         let widgets = view_output!();
126///
127///         ComponentParts { model, widgets }
128///     }
129///
130///     fn update(&mut self, msg: Msg, _sender: ComponentSender<Self>) {
131///         match msg {
132///             Msg::Increment => {
133///                 self.counter = self.counter.wrapping_add(1);
134///             }
135///             Msg::Decrement => {
136///                 self.counter = self.counter.wrapping_sub(1);
137///             }
138///         }
139///     }
140/// }
141/// ```
142///
143/// # Notes on `pre_view`
144///
145/// Using `return` in `pre_view` will cause a compiler warning.
146/// In general, you don't want to use `return` in `pre_view` as it will
147/// cause all following update functionality to be skipped.
148///
149/// ```compile_fail
150/// # use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
151/// # use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent, RelmWidgetExt};
152/// #
153/// struct App {}
154///
155/// #[relm4_macros::component]
156/// impl SimpleComponent for App {
157///       /* Code omitted */
158/// #     type Init = ();
159/// #     type Input = ();
160/// #     type Output = ();
161/// #
162/// #     view! {
163/// #         gtk::Window {}
164/// #     }
165///
166///       fn pre_view() {
167///           return;
168///       }
169/// #
170/// #     fn init(
171/// #         counter: Self::Init,
172/// #         root: &Self::Root,
173/// #         sender: ComponentSender<Self>,
174/// #     ) -> ComponentParts<Self> {
175/// #         let model = Self {};
176/// #
177/// #         let widgets = view_output!();
178/// #
179/// #         ComponentParts { model, widgets }
180/// #     }
181/// }
182/// ```
183#[proc_macro_attribute]
184pub fn component(attributes: TokenStream, input: TokenStream) -> TokenStream {
185    let global_attributes: Attrs = parse_macro_input!(attributes);
186    let backup_input = input.clone();
187    let component_impl_res = syn::parse::<ItemImpl>(input);
188
189    match component_impl_res {
190        Ok(component_impl) => component::generate_tokens(global_attributes, component_impl).into(),
191        Err(_) => util::item_impl_error(backup_input),
192    }
193}
194
195/// Macro that implements `relm4::factory::FactoryComponent` and generates the corresponding widgets struct.
196///
197/// # Attributes
198///
199/// To create public struct use `#[factory(pub)]` or `#[factory(visibility = pub)]`.
200///
201/// # Example
202///
203/// ```
204/// use relm4::prelude::*;
205/// use relm4::factory::*;
206/// use gtk::prelude::*;
207///
208/// # #[derive(Debug)]
209/// # enum AppMsg {
210/// #     AddCounter,
211/// #     RemoveCounter,
212/// #     SendFront(DynamicIndex)
213/// # }
214///
215/// #[derive(Debug)]
216/// struct Counter {
217///     value: u8,
218/// }
219///
220/// #[derive(Debug)]
221/// enum CounterMsg {
222///     Increment,
223///     Decrement,
224/// }
225///
226/// #[derive(Debug)]
227/// enum CounterOutput {
228///     SendFront(DynamicIndex),
229/// }
230///
231/// #[relm4_macros::factory(pub)]
232/// impl FactoryComponent for Counter {
233///     type CommandOutput = ();
234///     type Init = u8;
235///     type Input = CounterMsg;
236///     type Output = CounterOutput;
237///     type ParentWidget = gtk::Box;
238///
239///
240///     view! {
241///         root = gtk::Box {
242///             set_orientation: gtk::Orientation::Horizontal,
243///             set_spacing: 10,
244///
245///             #[name(label)]
246///             gtk::Label {
247///                 #[watch]
248///                 set_label: &self.value.to_string(),
249///                 set_width_chars: 3,
250///             },
251///
252///             #[name(add_button)]
253///             gtk::Button {
254///                 set_label: "+",
255///                 connect_clicked => CounterMsg::Increment,
256///             },
257///
258///             #[name(remove_button)]
259///             gtk::Button {
260///                 set_label: "-",
261///                 connect_clicked => CounterMsg::Decrement,
262///             },
263///
264///             #[name(to_front_button)]
265///             gtk::Button {
266///                 set_label: "To start",
267///                 connect_clicked[sender, index] => move |_| {
268///                     sender.output(CounterOutput::SendFront(index.clone()));
269///                 }
270///             }
271///         }
272///     }
273///
274///     fn init_model(
275///         value: Self::Init,
276///         _index: &DynamicIndex,
277///         _sender: FactorySender<Self>,
278///     ) -> Self {
279///         Self { value }
280///     }
281///
282///     fn update(&mut self, msg: Self::Input, _sender: FactorySender<Self>) {
283///         match msg {
284///             CounterMsg::Increment => {
285///                 self.value = self.value.wrapping_add(1);
286///             }
287///             CounterMsg::Decrement => {
288///                 self.value = self.value.wrapping_sub(1);
289///             }
290///         }
291///     }
292/// }
293/// ```
294///
295/// Note: the enclosing App view (which has AppMsg as its input) is responsible for adding a
296/// forward handler to the `FactoryVecDeque`, which will translate CounterOutput events into AppMsg
297/// events. For example `CounterOutput::SendFront(index) => AppMsg::SendFront(index)`
298///
299#[proc_macro_attribute]
300pub fn factory(attributes: TokenStream, input: TokenStream) -> TokenStream {
301    let attrs = parse_macro_input!(attributes);
302    let backup_input = input.clone();
303    let factory_impl_res = syn::parse::<ItemImpl>(input);
304
305    match factory_impl_res {
306        Ok(factory_impl) => factory::generate_tokens(attrs, factory_impl).into(),
307        Err(_) => util::item_impl_error(backup_input),
308    }
309}
310
311/// A macro to create menus.
312///
313/// Use
314///
315/// + `"Label text" => ActionType,` to create new entries.
316/// + `"Label text" => ActionType(value),` to create new entries with action value.
317/// + `custom: "widget_id",` to add a placeholder for custom widgets you can add later with [`set_attribute_name`](https://gtk-rs.org/gtk-rs-core/stable/0.15/docs/gio/struct.MenuItem.html#method.set_attribute_value).
318/// + `section! { ... }` to create new sections.
319///
320/// # Example
321///
322/// ```
323/// # fn gettext(string: &str) -> String {
324/// #     string.to_owned()
325/// # }
326/// #
327/// // Define some actions
328/// relm4::new_action_group!(WindowActionGroup, "win");
329/// relm4::new_stateless_action!(TestAction, WindowActionGroup, "test");
330/// relm4::new_stateful_action!(TestU8Action, WindowActionGroup, "test2", u8, u8);
331///
332/// // Create a `MenuModel` called `menu_model`
333/// relm4_macros::menu! {
334///     main_menu: {
335///         custom: "my_widget",
336///         // Translate with gettext-rs, for example.
337///         &gettext("Test") => TestAction,
338///         "Test2" => TestAction,
339///         "Test toggle" => TestU8Action(1_u8),
340///         section! {
341///             "Section test" => TestAction,
342///             "Test toggle" => TestU8Action(1_u8),
343///         },
344///         section! {
345///             "Test" => TestAction,
346///             "Test2" => TestAction,
347///             "Test Value" => TestU8Action(1_u8),
348///         }
349///     }
350/// };
351/// ```
352///
353/// # Macro expansion
354///
355/// The code generation for the example above looks like this (plus comments):
356///
357/// ```
358/// # fn gettext(string: &str) -> String {
359/// #     string.to_owned()
360/// # }
361/// #
362/// struct WindowActionGroup;
363/// impl relm4::actions::ActionGroupName for WindowActionGroup {
364///     const NAME: &'static str = "win";
365/// }
366///
367/// struct TestAction;
368/// impl relm4::actions::ActionName for TestAction {
369///     type Group = WindowActionGroup;
370///     type State = ();
371///     type Target = ();
372///
373///     const NAME: &'static str = "test";
374/// }
375///
376/// struct TestU8Action;
377/// impl relm4::actions::ActionName for TestU8Action {
378///     type Group = WindowActionGroup;
379///     type State = u8;
380///     type Target = u8;
381///
382///     const NAME: &'static str = "test2";
383/// }
384///
385/// // Main menu
386/// let main_menu = relm4::gtk::gio::Menu::new();
387///
388/// // Placeholder for custom widget
389/// let new_entry = relm4::gtk::gio::MenuItem::new(None, None);
390/// let variant = relm4::gtk::glib::variant::ToVariant::to_variant("my_widget");
391/// new_entry.set_attribute_value("custom", Some(&variant));
392/// main_menu.append_item(&new_entry);
393///
394/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item(&gettext("Test"));
395/// main_menu.append_item(&new_entry);
396/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Test2");
397/// main_menu.append_item(&new_entry);
398/// let new_entry = relm4::actions::RelmAction::<TestU8Action>::to_menu_item_with_target_value(
399///     "Test toggle",
400///     &1_u8,
401/// );
402/// main_menu.append_item(&new_entry);
403///
404/// // Section 0
405/// let _section_0 = relm4::gtk::gio::Menu::new();
406/// main_menu.append_section(None, &_section_0);
407/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Section test");
408/// _section_0.append_item(&new_entry);
409/// let new_entry = relm4::actions::RelmAction::<TestU8Action>::to_menu_item_with_target_value(
410///     "Test toggle",
411///     &1_u8,
412/// );
413/// _section_0.append_item(&new_entry);
414///
415/// // Section 1
416/// let _section_1 = relm4::gtk::gio::Menu::new();
417/// main_menu.append_section(None, &_section_1);
418/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Test");
419/// _section_1.append_item(&new_entry);
420/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Test2");
421/// _section_1.append_item(&new_entry);
422/// let new_entry = relm4::actions::RelmAction::<TestU8Action>::to_menu_item_with_target_value(
423///     "Test Value",
424///     &1_u8,
425/// );
426/// _section_1.append_item(&new_entry);
427/// ```
428#[proc_macro]
429pub fn menu(input: TokenStream) -> TokenStream {
430    let menus = parse_macro_input!(input as Menus);
431    menus.menus_stream().into()
432}
433
434/// The [`view!`] macro allows you to construct your UI easily and cleanly.
435///
436/// It does the same as inside the [`macro@component`] attribute macro,
437/// but with less features.
438///
439/// You can even use the `relm4-macros` crate independently from Relm4 to build your GTK4 UI.
440///
441/// ```no_run
442/// use gtk::prelude::{BoxExt, ButtonExt};
443/// use relm4::gtk;
444///
445/// // Creating a box with a button inside.
446/// relm4_macros::view! {
447///     vbox = gtk::Box {
448///         gtk::Button {
449///             set_label: "Click me!",
450///             connect_clicked => |_| {
451///                 println!("Hello world!");
452///             }
453///         },
454///         prepend: my_label = &gtk::Label::builder()
455///             .label("The view macro works!")
456///             .build(),
457///     }
458/// }
459///
460/// // You can simply use the vbox created in the macro.
461/// let spacing = vbox.spacing();
462/// ```
463///
464/// Also, the macro doesn't rely on any special gtk4-rs features
465/// so you can even use the macro for other purposes.
466///
467/// In this example, we use it to construct a [`Command`](std::process::Command).
468///
469/// ```
470/// use std::process::Command;
471///
472/// let path = "/";
473///
474/// relm4_macros::view! {
475///     mut process = Command::new("ls") {
476///         args: ["-la"],
477///         current_dir = mut &String {
478///             push_str: path,
479///         },
480///         env: ("HOME", "/home/relm4"),
481///     }
482/// }
483///
484/// // Output of "ls -la" at "/"
485/// dbg!(process.output());
486/// ```
487/// # Macro expansion
488///
489/// Let's have a look the this example:
490///
491/// ```no_run
492/// # use gtk::prelude::{BoxExt, ButtonExt};
493/// # use relm4::gtk;
494/// // Creating a box with a button inside.
495/// relm4_macros::view! {
496///     vbox = gtk::Box {
497///         gtk::Button {
498///             set_label: "Click me!",
499///             connect_clicked => |_| {
500///                 println!("Hello world!");
501///             }
502///         },
503///         prepend: my_label = &gtk::Label::builder()
504///             .label("The view macro works!")
505///             .build(),
506///     }
507/// }
508/// ```
509///
510/// The code generation for this example looks like this (plus comments):
511///
512/// ```no_run
513/// # use gtk::prelude::{BoxExt, ButtonExt};
514/// # use relm4::gtk;
515///
516/// // We've just used `gtk::Box` so we assume it has a `default()` method
517/// let vbox = gtk::Box::default();
518/// // `vbox` was named, yet the button doesn't have an explicit name and gets a generated one instead.
519/// let _gtk_button_5 = gtk::Button::default();
520/// // For the label, we used a manual constructor method, so no `default()` method is required.
521/// let my_label = gtk::Label::builder().label("The view macro works!").build();
522///
523/// // Connect the signal
524/// {
525///     _gtk_button_5.connect_clicked(|_| {
526///         println!("Hello world!");
527///     });
528/// }
529///
530/// // The button was added without any further instructions, so we assume `container_add()` will work.
531/// relm4::RelmContainerExt::container_add(&vbox, &_gtk_button_5);
532/// _gtk_button_5.set_label("Click me!");
533/// // For the label, we used the `prepend` method, so we don't need `container_add()` here.
534/// vbox.prepend(&my_label);
535/// ```
536///
537/// The widgets are first initialized, then signals are connected and then
538/// properties and widgets are assigned to each other.
539///
540/// The nested structure of the UI is translated into regular Rust code.
541#[proc_macro]
542pub fn view(input: TokenStream) -> TokenStream {
543    view::generate_tokens(input)
544}
545
546/// A macro to generate widget templates.
547///
548/// This macro generates a new type that implements `relm4::WidgetTemplate`.
549///
550/// # Example
551///
552/// ```
553/// use relm4::prelude::*;
554/// use gtk::prelude::*;
555///
556/// #[relm4::widget_template]
557/// impl WidgetTemplate for MyBox {
558///     view! {
559///         gtk::Box {
560///             set_margin_all: 10,
561///            // Make the boxes visible
562///             inline_css: "border: 2px solid blue",
563///         }
564///     }
565/// }
566/// ```
567///
568/// The template allows you the generate deeply nested
569/// structures. All named items will be directly accessible
570/// as a child of the template, even if they are nested.
571/// In this example the "child_label" is a template child.
572///
573/// ```
574/// # use relm4::prelude::*;
575/// # use gtk::prelude::*;
576/// #
577/// # #[relm4::widget_template]
578/// # impl WidgetTemplate for MyBox {
579/// #     view! {
580/// #         gtk::Box {
581/// #             set_margin_all: 10,
582/// #            // Make the boxes visible
583/// #             inline_css: "border: 2px solid blue",
584/// #         }
585/// #     }
586/// # }
587/// #
588/// #[relm4::widget_template]
589/// impl WidgetTemplate for MySpinner {
590///     view! {
591///         gtk::Spinner {
592///             set_spinning: true,
593///         }
594///     }
595/// }
596///
597/// #[relm4::widget_template]
598/// impl WidgetTemplate for CustomBox {
599///     view! {
600///         gtk::Box {
601///             set_orientation: gtk::Orientation::Vertical,
602///             set_margin_all: 5,
603///             set_spacing: 5,
604///
605///             #[template]
606///             MyBox {
607///                 #[template]
608///                 MySpinner,
609///
610///                 #[template]
611///                 MyBox {
612///                     #[template]
613///                     MySpinner,
614///
615///                     #[template]
616///                     MyBox {
617///                         #[template]
618///                         MySpinner,
619///
620///                         // Deeply nested!
621///                         #[name = "child_label"]
622///                         gtk::Label {
623///                             set_label: "This is a test",
624///                         }
625///                     }
626///                 }
627///             }
628///         }
629///     }
630/// }
631/// ```
632#[proc_macro_attribute]
633pub fn widget_template(attributes: TokenStream, input: TokenStream) -> TokenStream {
634    let SyncOnlyAttrs { visibility } = parse_macro_input!(attributes);
635
636    let item_impl = parse_macro_input!(input as ItemImpl);
637    widget_template::generate_tokens(visibility, item_impl).into()
638}
639
640#[cfg(test)]
641#[rustversion::all(stable, since(1.72))]
642mod test {
643    #[test]
644    fn ui() {
645        let t = trybuild::TestCases::new();
646        t.compile_fail("tests/ui/compile-fail/**/*.rs");
647    }
648}