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]<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: >k::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: >k::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}