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