gtk4_macros/
lib.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3//! # GTK 4 Macros
4//!
5//! The crate aims to provide useful macros to use with the GTK 4 Rust bindings.
6
7mod attribute_parser;
8#[cfg(feature = "blueprint")]
9mod blueprint;
10mod composite_template_derive;
11mod template_callbacks_attribute;
12mod util;
13
14use proc_macro::TokenStream;
15use proc_macro2::Span;
16use syn::{parse_macro_input, DeriveInput, Error};
17
18/// That macro includes and compiles blueprint file by path relative to project
19/// rood
20///
21/// It expected to run inside composite_template_derive, not by users
22#[cfg(feature = "blueprint")]
23#[proc_macro]
24#[doc(hidden)]
25pub fn include_blueprint(input: TokenStream) -> TokenStream {
26    use quote::quote;
27
28    let tokens: Vec<_> = input.into_iter().collect();
29
30    if tokens.len() != 1 {
31        return Error::new(Span::call_site(), "File name not found")
32            .into_compile_error()
33            .into();
34    }
35
36    let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
37
38    let file_name = tokens[0].to_string();
39    let file_name = file_name.trim();
40    let file_name = &file_name[1..file_name.len() - 1];
41
42    let path = std::path::Path::new(&root).join(file_name);
43
44    if !path.exists() {
45        return Error::new(
46            Span::call_site(),
47            format!("{} not found", &path.to_string_lossy()),
48        )
49        .into_compile_error()
50        .into();
51    }
52
53    let path = path.to_string_lossy().to_string();
54
55    let template = match std::fs::read_to_string(&path) {
56        Ok(content) => blueprint::compile_blueprint(content.as_bytes()).unwrap(),
57        Err(err) => {
58            return Error::new(Span::call_site(), err)
59                .into_compile_error()
60                .into()
61        }
62    };
63
64    quote!({
65        // Compiler reruns macro if file changed
66        _ = include_str!(#path);
67        #template
68    })
69    .into()
70}
71
72/// Derive macro for using a composite template in a widget.
73///
74/// The `template` attribute specifies where the template should be loaded
75/// from;  it can be a `file`, a `resource`, or a `string`.
76///
77/// The `template_child` attribute is used to mark all internal widgets
78/// we need to have programmatic access to. It can take two parameters:
79/// - `id` which defaults to the item name if not defined
80/// - `internal` whether the child should be accessible as an
81///   “internal-child”, defaults to `false`
82///
83/// # Example
84///
85/// Specify that `MyWidget` is using a composite template and load the
86/// template file the `composite_template.ui` file.
87///
88/// Then, in the [`ObjectSubclass`] implementation you will need to call
89/// [`bind_template`] in the [`class_init`] function, and [`init_template`] in
90/// [`instance_init`] function.
91///
92/// [`ObjectSubclass`]: ../glib/subclass/types/trait.ObjectSubclass.html
93/// [`bind_template`]: ../gtk4/subclass/widget/trait.CompositeTemplate.html#tymethod.bind_template
94/// [`class_init`]: ../glib/subclass/types/trait.ObjectSubclass.html#method.class_init
95/// [`init_template`]: ../gtk4/subclass/prelude/trait.CompositeTemplateInitializingExt.html#tymethod.init_template
96/// [`instance_init`]: ../glib/subclass/types/trait.ObjectSubclass.html#method.instance_init
97///
98/// ```no_run
99/// # fn main() {}
100/// use gtk::{glib, prelude::*, subclass::prelude::*};
101///
102/// mod imp {
103///     use super::*;
104///
105///     #[derive(Debug, Default, gtk::CompositeTemplate)]
106///     #[template(file = "test/template.ui")]
107///     pub struct MyWidget {
108///         #[template_child]
109///         pub label: TemplateChild<gtk::Label>,
110///         #[template_child(id = "my_button_id")]
111///         pub button: TemplateChild<gtk::Button>,
112///     }
113///
114///     #[glib::object_subclass]
115///     impl ObjectSubclass for MyWidget {
116///         const NAME: &'static str = "MyWidget";
117///         type Type = super::MyWidget;
118///         type ParentType = gtk::Box;
119///
120///         fn class_init(klass: &mut Self::Class) {
121///             klass.bind_template();
122///         }
123///
124///         fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
125///             obj.init_template();
126///         }
127///     }
128///
129///     impl ObjectImpl for MyWidget {}
130///     impl WidgetImpl for MyWidget {}
131///     impl BoxImpl for MyWidget {}
132/// }
133///
134/// glib::wrapper! {
135///     pub struct MyWidget(ObjectSubclass<imp::MyWidget>)
136///     @extends gtk::Widget, gtk::Box,
137///     @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
138/// }
139///
140/// impl MyWidget {
141///     pub fn new() -> Self {
142///         glib::Object::new()
143///     }
144/// }
145/// ```
146///
147/// The [`CompositeTemplate`] macro can also be used with [Blueprint](https://jwestman.pages.gitlab.gnome.org/blueprint-compiler/)
148/// if the feature `blueprint` is enabled.
149/// you can use `string` or `file` relative to the project directory but not
150/// `resource`
151///
152/// ```ignore
153/// # fn main() {}
154/// use gtk::{glib, prelude::*, subclass::prelude::*};
155///
156/// mod imp {
157///     use super::*;
158///
159///     #[derive(Debug, Default, gtk::CompositeTemplate)]
160///     #[template(string = "
161///     using Gtk 4.0;
162///     template $MyWidget : Widget {
163///         Label label {
164///             label: 'foobar';
165///         }
166///
167///         Label my_label2 {
168///             label: 'foobaz';
169///         }
170///     }
171///     ")]
172///     pub struct MyWidget {
173///         #[template_child]
174///         pub label: TemplateChild<gtk::Label>,
175///         #[template_child(id = "my_label2")]
176///         pub label2: gtk::TemplateChild<gtk::Label>,
177///     }
178///
179///     #[glib::object_subclass]
180///     impl ObjectSubclass for MyWidget {
181///         const NAME: &'static str = "MyWidget";
182///         type Type = super::MyWidget;
183///         type ParentType = gtk::Widget;
184///         fn class_init(klass: &mut Self::Class) {
185///             klass.bind_template();
186///         }
187///         fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
188///             obj.init_template();
189///         }
190///     }
191///
192///     impl ObjectImpl for MyWidget {
193///         fn dispose(&self) {
194///             while let Some(child) = self.obj().first_child() {
195///                 child.unparent();
196///             }
197///         }
198///     }
199///     impl WidgetImpl for MyWidget {}
200/// }
201///
202/// glib::wrapper! {
203///     pub struct MyWidget(ObjectSubclass<imp::MyWidget>)
204///     @extends gtk::Widget,
205///     @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
206/// }
207/// ```
208#[proc_macro_derive(CompositeTemplate, attributes(template, template_child))]
209pub fn composite_template_derive(input: TokenStream) -> TokenStream {
210    let input = parse_macro_input!(input as DeriveInput);
211    composite_template_derive::impl_composite_template(&input)
212        .unwrap_or_else(Error::into_compile_error)
213        .into()
214}
215
216/// Attribute macro for creating template callbacks from Rust methods.
217///
218/// Widgets with [`CompositeTemplate`] can then make use of these callbacks from
219/// within their template XML definition. The attribute must be applied to an
220/// `impl` statement of a struct. Functions marked as callbacks within the
221/// `impl` will be stored in a static array. Then, in the [`ObjectSubclass`]
222/// implementation you will need to call [`bind_template_callbacks`] and/or
223/// [`bind_template_instance_callbacks`] in the [`class_init`] function.
224///
225/// Template callbacks can be specified on both a widget's public wrapper `impl`
226/// or on its private subclass `impl`, or from external types. If callbacks are
227/// specified on the public wrapper, then `bind_template_instance_callbacks`
228/// must be called in `class_init`. If callbacks are specified on the private
229/// subclass, then `bind_template_callbacks` must be called in `class_init`. To
230/// use the callbacks from an external type, call [`T::bind_template_callbacks`]
231/// in `class_init`, where `T` is the other type. See the example below for
232/// usage of all three.
233///
234/// These callbacks can be bound using the `<signal>` or `<closure>` tags in the
235/// template file. Note that the arguments and return type will only be checked
236/// at run time when the method is invoked.
237///
238/// Template callbacks can optionally take `self` or `&self` as a first
239/// parameter. In this case, the attribute `swapped="true"` will usually have to
240/// be set on the `<signal>` or `<closure>` tag in order to invoke the function
241/// correctly. Note that by-value `self` will only work with template callbacks
242/// on the wrapper type.
243///
244/// Template callbacks that have no return value can also be `async`, in which
245/// case the callback will be spawned as new future on the default main context
246/// using [`glib::MainContext::spawn_local`]. Invoking the callback multiple
247/// times will spawn an additional future each time it is invoked. This means
248/// that multiple futures for an async callback can be active at any given time,
249/// so care must be taken to avoid any kind of data races. Async callbacks may
250/// prefer communicating back to the caller or widget over channels instead of
251/// mutating internal widget state, or may want to make use of a locking flag to
252/// ensure only one future can be active at once. Widgets may also want to show
253/// a visual indicator such as a [`Spinner`] while the future is active to
254/// communicate to the user that a background task is running.
255///
256/// The following options are supported on the attribute:
257/// - `functions` makes all callbacks use the `function` attribute by default.
258///   (see below)
259///
260/// The `template_callback` attribute is used to mark methods that will be
261/// exposed to the template scope. It can take the following options:
262/// - `name` renames the callback. Defaults to the function name if not defined.
263/// - `function` ignores the first value when calling the callback and disallows
264///   `self`.  Useful for callbacks called from `<closure>` tags.
265/// - `function = false` reverts the effects of `functions` used on the `impl`,
266///   so the callback gets the first value and can take `self` again. Mainly useful
267///   for callbacks that are invoked with `swapped="true"`.
268///
269/// The `rest` attribute can be placed on the last argument of a template
270/// callback. This attribute must be used on an argument of type
271/// <code>&\[[glib::Value]\]</code> and will pass in the remaining arguments.
272/// The first and last values will be omitted from the slice if this callback is
273/// a `function`.
274///
275/// Arguments and return types in template callbacks have some special
276/// restrictions, similar to the restrictions on [`glib::closure`]. Each
277/// argument's type must implement <code>[From]&lt;Type> for
278/// [glib::Value]</code>. The last argument can also be <code>&\[[glib::Value]\
279/// ]</code> annotated with `#[rest]` as described above. The return type of
280/// a callback, if present, must implement [`glib::FromValue`]. Type-checking of
281/// inputs and outputs is done at run-time; if the argument types or return type
282/// do not match the type of the signal or closure then the callback will panic.
283/// To implement your own type checking or to use dynamic typing, an argument's
284/// type can be left as a <code>&[glib::Value]</code>. This can also be used
285/// if you need custom unboxing, such as if the target type does not implement
286/// `FromValue`.
287///
288/// [`glib::closure`]: ../glib/macro.closure.html
289/// [`glib::wrapper`]: ../glib/macro.wrapper.html
290/// [`ObjectSubclass`]: ../glib/subclass/types/trait.ObjectSubclass.html
291/// [`class_init`]: ../glib/subclass/types/trait.ObjectSubclass.html#method.class_init
292/// [`bind_template_callbacks`]: ../gtk4/subclass/widget/trait.CompositeTemplateCallbacksClass.html#tymethod.bind_template_callbacks
293/// [`bind_template_instance_callbacks`]: ../gtk4/subclass/widget/trait.CompositeTemplateInstanceCallbacksClass.html#tymethod.bind_template_instance_callbacks
294/// [`T::bind_template_callbacks`]: ../gtk4/subclass/widget/trait.CompositeTemplateCallbacks.html#method.bind_template_callbacks
295/// [`glib::FromValue`]: ../glib/value/trait.FromValue.html
296/// [`glib::ToValue`]: ../glib/value/trait.ToValue.html
297/// [glib::Value]: ../glib/value/struct.Value.html
298/// [`glib::MainContext::spawn_local`]: ../glib/struct.MainContext.html#method.spawn_local
299/// [`Spinner`]: ../gtk4/struct.Spinner.html
300///
301/// # Example
302///
303/// ```no_run
304/// # fn main() {}
305/// use gtk::{glib, prelude::*, subclass::prelude::*};
306///
307/// mod imp {
308///     use super::*;
309///
310///     #[derive(Debug, Default, gtk::CompositeTemplate)]
311///     #[template(file = "test/template_callbacks.ui")]
312///     pub struct MyWidget {
313///         #[template_child]
314///         pub label: TemplateChild<gtk::Label>,
315///         #[template_child(id = "my_button_id")]
316///         pub button: TemplateChild<gtk::Button>,
317///     }
318///
319///     #[glib::object_subclass]
320///     impl ObjectSubclass for MyWidget {
321///         const NAME: &'static str = "MyWidget";
322///         type Type = super::MyWidget;
323///         type ParentType = gtk::Box;
324///
325///         fn class_init(klass: &mut Self::Class) {
326///             klass.bind_template();
327///             // Bind the private callbacks
328///             klass.bind_template_callbacks();
329///             // Bind the public callbacks
330///             klass.bind_template_instance_callbacks();
331///             // Bind callbacks from another struct
332///             super::Utility::bind_template_callbacks(klass);
333///         }
334///
335///         fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
336///             obj.init_template();
337///         }
338///     }
339///
340///     #[gtk::template_callbacks]
341///     impl MyWidget {
342///         #[template_callback]
343///         fn button_clicked(&self, button: &gtk::Button) {
344///             button.set_label("I was clicked!");
345///             self.label.set_label("The button was clicked!");
346///         }
347///         #[template_callback(function, name = "strlen")]
348///         fn string_length(s: &str) -> u64 {
349///             s.len() as u64
350///         }
351///     }
352///
353///     impl ObjectImpl for MyWidget {}
354///     impl WidgetImpl for MyWidget {}
355///     impl BoxImpl for MyWidget {}
356/// }
357///
358/// glib::wrapper! {
359///     pub struct MyWidget(ObjectSubclass<imp::MyWidget>)
360///     @extends gtk::Widget, gtk::Box,
361///     @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
362/// }
363///
364/// #[gtk::template_callbacks]
365/// impl MyWidget {
366///     pub fn new() -> Self {
367///         glib::Object::new()
368///     }
369///     #[template_callback]
370///     pub fn print_both_labels(&self) {
371///         let imp = self.imp();
372///         println!(
373///             "{} {}",
374///             imp.label.label(),
375///             imp.button.label().unwrap().as_str()
376///         );
377///     }
378/// }
379///
380/// pub struct Utility {}
381///
382/// #[gtk::template_callbacks(functions)]
383/// impl Utility {
384///     #[template_callback]
385///     fn concat_strs(#[rest] values: &[glib::Value]) -> String {
386///         let mut res = String::new();
387///         for (index, value) in values.iter().enumerate() {
388///             res.push_str(value.get::<&str>().unwrap_or_else(|e| {
389///                 panic!("Expected string value for argument {}: {}", index, e);
390///             }));
391///         }
392///         res
393///     }
394///     #[template_callback(function = false)]
395///     fn reset_label(label: &gtk::Label) {
396///         label.set_label("");
397///     }
398/// }
399/// ```
400#[proc_macro_attribute]
401pub fn template_callbacks(attr: TokenStream, item: TokenStream) -> TokenStream {
402    let args = parse_macro_input!(attr as template_callbacks_attribute::Args);
403    match syn::parse::<syn::ItemImpl>(item) {
404        Ok(input) => template_callbacks_attribute::impl_template_callbacks(input, args)
405            .unwrap_or_else(syn::Error::into_compile_error)
406            .into(),
407        Err(_) => Error::new(
408            Span::call_site(),
409            template_callbacks_attribute::WRONG_PLACE_MSG,
410        )
411        .into_compile_error()
412        .into(),
413    }
414}
415
416/// Attribute macro for declaring GTK tests.
417///
418/// Wraps the standard Rust [`test`] attribute with setup logic for GTK. All
419/// tests that call into GTK must use this attribute. This attribute can also be
420/// used on asynchronous functions; the asynchronous test will be run on the
421/// main thread context.
422///
423/// # Technical Details
424///
425/// GTK is a single-threaded library, so Rust's normal multi-threaded test
426/// behavior cannot be used. The `#[gtk::test]` attribute creates a main thread
427/// for GTK and runs all tests on that thread. This has the side effect of
428/// making all tests run serially, not in parallel.
429///
430/// [`test`]: <https://doc.rust-lang.org/std/prelude/v1/macro.test.html>
431///
432/// # Example
433///
434/// ```no_run
435/// use gtk::prelude::*;
436///
437/// #[gtk::test]
438/// fn test_button() {
439///     let button = gtk::Button::new();
440///     button.activate();
441/// }
442/// ```
443#[proc_macro_attribute]
444pub fn test(_attr: TokenStream, item: TokenStream) -> TokenStream {
445    use quote::quote;
446
447    match syn::parse::<syn::ItemFn>(item) {
448        Ok(mut input) => {
449            let crate_ident = util::crate_ident_new();
450            let block = &input.block;
451            let block = if input.sig.asyncness.is_some() {
452                quote! {
453                    #crate_ident::glib::MainContext::default().block_on(async move {
454                        #block
455                    })
456                }
457            } else {
458                quote! { #block }
459            };
460            input.sig.asyncness.take();
461
462            let attrs = &input.attrs;
463            let vis = &input.vis;
464            let sig = &input.sig;
465            let test = quote! {
466                #(#attrs)*
467                #[::std::prelude::v1::test]
468                #vis #sig {
469                    #crate_ident::test_synced(move || {
470                        #block
471                    })
472                }
473            };
474            test.into()
475        }
476        Err(_) => Error::new(
477            Span::call_site(),
478            "This macro should be used on a function definition",
479        )
480        .into_compile_error()
481        .into(),
482    }
483}