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>) @extends gtk::Widget, gtk::Box;
136/// }
137///
138/// impl MyWidget {
139///     pub fn new() -> Self {
140///         glib::Object::new()
141///     }
142/// }
143/// ```
144///
145/// The [`CompositeTemplate`] macro can also be used with [Blueprint](https://jwestman.pages.gitlab.gnome.org/blueprint-compiler/)
146/// if the feature `blueprint` is enabled.
147/// you can use `string` or `file` relative to the project directory but not
148/// `resource`
149///
150/// ```ignore
151/// # fn main() {}
152/// use gtk::{glib, prelude::*, subclass::prelude::*};
153///
154/// mod imp {
155///     use super::*;
156///
157///     #[derive(Debug, Default, gtk::CompositeTemplate)]
158///     #[template(string = "
159///     template MyWidget : Widget {
160///         Label label {
161///             label: 'foobar';
162///         }
163///
164///         Label my_label2 {
165///             label: 'foobaz';
166///         }
167///     }
168///     ")]
169///     pub struct MyWidget {
170///         #[template_child]
171///         pub label: TemplateChild<gtk::Label>,
172///         #[template_child(id = "my_label2")]
173///         pub label2: gtk::TemplateChild<gtk::Label>,
174///     }
175///
176///     #[glib::object_subclass]
177///     impl ObjectSubclass for MyWidget {
178///         const NAME: &'static str = "MyWidget";
179///         type Type = super::MyWidget;
180///         type ParentType = gtk::Widget;
181///         fn class_init(klass: &mut Self::Class) {
182///             klass.bind_template();
183///         }
184///         fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
185///             obj.init_template();
186///         }
187///     }
188///
189///     impl ObjectImpl for MyWidget {
190///         fn dispose(&self) {
191///             while let Some(child) = self.obj().first_child() {
192///                 child.unparent();
193///             }
194///         }
195///     }
196///     impl WidgetImpl for MyWidget {}
197/// }
198///
199/// glib::wrapper! {
200///     pub struct MyWidget(ObjectSubclass<imp::MyWidget>) @extends gtk::Widget;
201/// }
202/// ```
203#[proc_macro_derive(CompositeTemplate, attributes(template, template_child))]
204pub fn composite_template_derive(input: TokenStream) -> TokenStream {
205    let input = parse_macro_input!(input as DeriveInput);
206    composite_template_derive::impl_composite_template(&input)
207        .unwrap_or_else(Error::into_compile_error)
208        .into()
209}
210
211/// Attribute macro for creating template callbacks from Rust methods.
212///
213/// Widgets with [`CompositeTemplate`] can then make use of these callbacks from
214/// within their template XML definition. The attribute must be applied to an
215/// `impl` statement of a struct. Functions marked as callbacks within the
216/// `impl` will be stored in a static array. Then, in the [`ObjectSubclass`]
217/// implementation you will need to call [`bind_template_callbacks`] and/or
218/// [`bind_template_instance_callbacks`] in the [`class_init`] function.
219///
220/// Template callbacks can be specified on both a widget's public wrapper `impl`
221/// or on its private subclass `impl`, or from external types. If callbacks are
222/// specified on the public wrapper, then `bind_template_instance_callbacks`
223/// must be called in `class_init`. If callbacks are specified on the private
224/// subclass, then `bind_template_callbacks` must be called in `class_init`. To
225/// use the callbacks from an external type, call [`T::bind_template_callbacks`]
226/// in `class_init`, where `T` is the other type. See the example below for
227/// usage of all three.
228///
229/// These callbacks can be bound using the `<signal>` or `<closure>` tags in the
230/// template file. Note that the arguments and return type will only be checked
231/// at run time when the method is invoked.
232///
233/// Template callbacks can optionally take `self` or `&self` as a first
234/// parameter. In this case, the attribute `swapped="true"` will usually have to
235/// be set on the `<signal>` or `<closure>` tag in order to invoke the function
236/// correctly. Note that by-value `self` will only work with template callbacks
237/// on the wrapper type.
238///
239/// Template callbacks that have no return value can also be `async`, in which
240/// case the callback will be spawned as new future on the default main context
241/// using [`glib::MainContext::spawn_local`]. Invoking the callback multiple
242/// times will spawn an additional future each time it is invoked. This means
243/// that multiple futures for an async callback can be active at any given time,
244/// so care must be taken to avoid any kind of data races. Async callbacks may
245/// prefer communicating back to the caller or widget over channels instead of
246/// mutating internal widget state, or may want to make use of a locking flag to
247/// ensure only one future can be active at once. Widgets may also want to show
248/// a visual indicator such as a [`Spinner`] while the future is active to
249/// communicate to the user that a background task is running.
250///
251/// The following options are supported on the attribute:
252/// - `functions` makes all callbacks use the `function` attribute by default.
253///   (see below)
254///
255/// The `template_callback` attribute is used to mark methods that will be
256/// exposed to the template scope. It can take the following options:
257/// - `name` renames the callback. Defaults to the function name if not defined.
258/// - `function` ignores the first value when calling the callback and disallows
259///   `self`.  Useful for callbacks called from `<closure>` tags.
260/// - `function = false` reverts the effects of `functions` used on the `impl`,
261///   so the callback gets the first value and can take `self` again. Mainly useful
262///   for callbacks that are invoked with `swapped="true"`.
263///
264/// The `rest` attribute can be placed on the last argument of a template
265/// callback. This attribute must be used on an argument of type
266/// <code>&\[[glib::Value]\]</code> and will pass in the remaining arguments.
267/// The first and last values will be omitted from the slice if this callback is
268/// a `function`.
269///
270/// Arguments and return types in template callbacks have some special
271/// restrictions, similar to the restrictions on [`glib::closure`]. Each
272/// argument's type must implement <code>[From]&lt;Type> for
273/// [glib::Value]</code>. The last argument can also be <code>&\[[glib::Value]\
274/// ]</code> annotated with `#[rest]` as described above. The return type of
275/// a callback, if present, must implement [`glib::FromValue`]. Type-checking of
276/// inputs and outputs is done at run-time; if the argument types or return type
277/// do not match the type of the signal or closure then the callback will panic.
278/// To implement your own type checking or to use dynamic typing, an argument's
279/// type can be left as a <code>&[glib::Value]</code>. This can also be used
280/// if you need custom unboxing, such as if the target type does not implement
281/// `FromValue`.
282///
283/// [`glib::closure`]: ../glib/macro.closure.html
284/// [`glib::wrapper`]: ../glib/macro.wrapper.html
285/// [`ObjectSubclass`]: ../glib/subclass/types/trait.ObjectSubclass.html
286/// [`class_init`]: ../glib/subclass/types/trait.ObjectSubclass.html#method.class_init
287/// [`bind_template_callbacks`]: ../gtk4/subclass/widget/trait.CompositeTemplateCallbacksClass.html#tymethod.bind_template_callbacks
288/// [`bind_template_instance_callbacks`]: ../gtk4/subclass/widget/trait.CompositeTemplateInstanceCallbacksClass.html#tymethod.bind_template_instance_callbacks
289/// [`T::bind_template_callbacks`]: ../gtk4/subclass/widget/trait.CompositeTemplateCallbacks.html#method.bind_template_callbacks
290/// [`glib::FromValue`]: ../glib/value/trait.FromValue.html
291/// [`glib::ToValue`]: ../glib/value/trait.ToValue.html
292/// [glib::Value]: ../glib/value/struct.Value.html
293/// [`glib::MainContext::spawn_local`]: ../glib/struct.MainContext.html#method.spawn_local
294/// [`Spinner`]: ../gtk4/struct.Spinner.html
295///
296/// # Example
297///
298/// ```no_run
299/// # fn main() {}
300/// use gtk::{glib, prelude::*, subclass::prelude::*};
301///
302/// mod imp {
303///     use super::*;
304///
305///     #[derive(Debug, Default, gtk::CompositeTemplate)]
306///     #[template(file = "test/template_callbacks.ui")]
307///     pub struct MyWidget {
308///         #[template_child]
309///         pub label: TemplateChild<gtk::Label>,
310///         #[template_child(id = "my_button_id")]
311///         pub button: TemplateChild<gtk::Button>,
312///     }
313///
314///     #[glib::object_subclass]
315///     impl ObjectSubclass for MyWidget {
316///         const NAME: &'static str = "MyWidget";
317///         type Type = super::MyWidget;
318///         type ParentType = gtk::Box;
319///
320///         fn class_init(klass: &mut Self::Class) {
321///             klass.bind_template();
322///             // Bind the private callbacks
323///             klass.bind_template_callbacks();
324///             // Bind the public callbacks
325///             klass.bind_template_instance_callbacks();
326///             // Bind callbacks from another struct
327///             super::Utility::bind_template_callbacks(klass);
328///         }
329///
330///         fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
331///             obj.init_template();
332///         }
333///     }
334///
335///     #[gtk::template_callbacks]
336///     impl MyWidget {
337///         #[template_callback]
338///         fn button_clicked(&self, button: &gtk::Button) {
339///             button.set_label("I was clicked!");
340///             self.label.set_label("The button was clicked!");
341///         }
342///         #[template_callback(function, name = "strlen")]
343///         fn string_length(s: &str) -> u64 {
344///             s.len() as u64
345///         }
346///     }
347///
348///     impl ObjectImpl for MyWidget {}
349///     impl WidgetImpl for MyWidget {}
350///     impl BoxImpl for MyWidget {}
351/// }
352///
353/// glib::wrapper! {
354///     pub struct MyWidget(ObjectSubclass<imp::MyWidget>) @extends gtk::Widget, gtk::Box;
355/// }
356///
357/// #[gtk::template_callbacks]
358/// impl MyWidget {
359///     pub fn new() -> Self {
360///         glib::Object::new()
361///     }
362///     #[template_callback]
363///     pub fn print_both_labels(&self) {
364///         let imp = self.imp();
365///         println!(
366///             "{} {}",
367///             imp.label.label(),
368///             imp.button.label().unwrap().as_str()
369///         );
370///     }
371/// }
372///
373/// pub struct Utility {}
374///
375/// #[gtk::template_callbacks(functions)]
376/// impl Utility {
377///     #[template_callback]
378///     fn concat_strs(#[rest] values: &[glib::Value]) -> String {
379///         let mut res = String::new();
380///         for (index, value) in values.iter().enumerate() {
381///             res.push_str(value.get::<&str>().unwrap_or_else(|e| {
382///                 panic!("Expected string value for argument {}: {}", index, e);
383///             }));
384///         }
385///         res
386///     }
387///     #[template_callback(function = false)]
388///     fn reset_label(label: &gtk::Label) {
389///         label.set_label("");
390///     }
391/// }
392/// ```
393#[proc_macro_attribute]
394pub fn template_callbacks(attr: TokenStream, item: TokenStream) -> TokenStream {
395    let args = parse_macro_input!(attr as template_callbacks_attribute::Args);
396    match syn::parse::<syn::ItemImpl>(item) {
397        Ok(input) => template_callbacks_attribute::impl_template_callbacks(input, args)
398            .unwrap_or_else(syn::Error::into_compile_error)
399            .into(),
400        Err(_) => Error::new(
401            Span::call_site(),
402            template_callbacks_attribute::WRONG_PLACE_MSG,
403        )
404        .into_compile_error()
405        .into(),
406    }
407}
408
409/// Attribute macro for declaring GTK tests.
410///
411/// Wraps the standard Rust [`test`] attribute with setup logic for GTK. All
412/// tests that call into GTK must use this attribute. This attribute can also be
413/// used on asynchronous functions; the asynchronous test will be run on the
414/// main thread context.
415///
416/// # Technical Details
417///
418/// GTK is a single-threaded library, so Rust's normal multi-threaded test
419/// behavior cannot be used. The `#[gtk::test]` attribute creates a main thread
420/// for GTK and runs all tests on that thread. This has the side effect of
421/// making all tests run serially, not in parallel.
422///
423/// [`test`]: <https://doc.rust-lang.org/std/prelude/v1/macro.test.html>
424///
425/// # Example
426///
427/// ```no_run
428/// use gtk::prelude::*;
429///
430/// #[gtk::test]
431/// fn test_button() {
432///     let button = gtk::Button::new();
433///     button.activate();
434/// }
435/// ```
436#[proc_macro_attribute]
437pub fn test(_attr: TokenStream, item: TokenStream) -> TokenStream {
438    use quote::quote;
439
440    match syn::parse::<syn::ItemFn>(item) {
441        Ok(mut input) => {
442            let crate_ident = util::crate_ident_new();
443            let block = &input.block;
444            let block = if input.sig.asyncness.is_some() {
445                quote! {
446                    #crate_ident::glib::MainContext::default().block_on(async move {
447                        #block
448                    })
449                }
450            } else {
451                quote! { #block }
452            };
453            input.sig.asyncness.take();
454
455            let attrs = &input.attrs;
456            let vis = &input.vis;
457            let sig = &input.sig;
458            let test = quote! {
459                #(#attrs)*
460                #[::std::prelude::v1::test]
461                #vis #sig {
462                    #crate_ident::test_synced(move || {
463                        #block
464                    })
465                }
466            };
467            test.into()
468        }
469        Err(_) => Error::new(
470            Span::call_site(),
471            "This macro should be used on a function definition",
472        )
473        .into_compile_error()
474        .into(),
475    }
476}