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