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