relm4_macros/
lib.rs

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