Skip to main content

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