gtk4_macros/
composite_template_derive.rs1#[cfg(feature = "xml_validation")]
4use std::collections::HashMap;
5
6use proc_macro2::{Span, TokenStream};
7#[cfg(feature = "xml_validation")]
8use quick_xml::name::QName;
9use quote::quote;
10use syn::{Data, Error, Result};
11
12#[cfg(feature = "blueprint")]
13use crate::blueprint::*;
14use crate::{attribute_parser::*, util::*};
15
16fn gen_set_template(source: &TemplateSource, crate_ident: &proc_macro2::Ident) -> TokenStream {
17 match source {
18 TemplateSource::File(file) => {
19 let template = if file.ends_with(".blp") {
20 if cfg!(feature = "blueprint") {
21 quote! {
22 #crate_ident::gtk4_macros::include_blueprint!(#file).as_bytes()
23 }
24 } else {
25 panic!("blueprint feature is disabled")
26 }
27 } else {
28 quote! {
29 include_bytes!(#file)
30 }
31 };
32
33 quote! {
34 #crate_ident::subclass::widget::WidgetClassExt::set_template_static(
35 klass,
36 #template,
37 );
38 }
39 }
40 TemplateSource::Resource(resource) => quote! {
41 #crate_ident::subclass::widget::WidgetClassExt::set_template_from_resource(
42 klass,
43 &#resource,
44 );
45 },
46 TemplateSource::Xml(template) => quote! {
47 #crate_ident::subclass::widget::WidgetClassExt::set_template_static(
48 klass,
49 #template.as_bytes(),
50 );
51 },
52 #[cfg(feature = "blueprint")]
53 TemplateSource::Blueprint(blueprint) => {
54 let template =
55 compile_blueprint(blueprint.as_bytes()).expect("can't compile blueprint");
56
57 quote! {
58 #crate_ident::subclass::widget::WidgetClassExt::set_template_static(
59 klass,
60 #template.as_bytes(),
61 );
62 }
63 }
64 }
65}
66
67#[cfg(feature = "xml_validation")]
68fn check_template_fields(source: &TemplateSource, fields: &[AttributedField]) -> Result<()> {
69 #[allow(unused_assignments)]
70 let xml = match source {
71 TemplateSource::Xml(template) => template,
72 _ => return Ok(()),
73 };
74
75 let mut reader = quick_xml::Reader::from_str(xml);
76 let mut ids_left = fields
77 .iter()
78 .map(|field| match field.attr.ty {
79 FieldAttributeType::TemplateChild => (field.id(), field.id_span()),
80 })
81 .collect::<HashMap<_, _>>();
82
83 loop {
84 use quick_xml::events::Event;
85
86 let event = reader.read_event();
87 let elem = match &event {
88 Ok(Event::Start(e)) => Some(e),
89 Ok(Event::Empty(e)) => Some(e),
90 Ok(Event::Eof) => break,
91 Err(e) => {
92 return Err(Error::new(
93 Span::call_site(),
94 format!(
95 "Failed reading template XML at position {}: {:?}",
96 reader.buffer_position(),
97 e
98 ),
99 ));
100 }
101 _ => None,
102 };
103 if let Some(e) = elem {
104 let name = e.name();
105 if name == QName("object") || name == QName("template") {
106 let id = e
107 .attributes()
108 .find_map(|a| a.ok().and_then(|a| (a.key == QName("id")).then_some(a)));
109 let id = id.as_ref().map(|a| a.value.as_ref());
110 if let Some(id) = id {
111 ids_left.remove(id);
112 }
113 }
114 }
115 }
116
117 if let Some((name, span)) = ids_left.into_iter().next() {
118 return Err(Error::new(
119 span,
120 format!("Template child with id `{name}` not found in template XML",),
121 ));
122 }
123
124 Ok(())
125}
126
127fn gen_template_child_bindings(fields: &[AttributedField]) -> TokenStream {
128 let crate_ident = crate_ident_new();
129
130 let recurse = fields.iter().map(|field| match field.attr.ty {
131 FieldAttributeType::TemplateChild => {
132 let mut value_id = None::<&str>;
133 let ident = &field.ident;
134 let mut value_internal = false;
135 field.attr.args.iter().for_each(|arg| match arg {
136 FieldAttributeArg::Id(value, _) => {
137 value_id = Some(value);
138 }
139 FieldAttributeArg::Internal(internal) => {
140 value_internal = *internal;
141 }
142 });
143
144 let value_id = if let Some(value_id) = value_id {
145 value_id
146 } else {
147 &ident.to_string()
148 };
149
150 quote! {
151 klass.bind_template_child_with_offset(
152 &#value_id,
153 #value_internal,
154 #crate_ident::offset_of!(Self => #ident),
155 );
156 }
157 }
158 });
159
160 quote! {
161 #(#recurse)*
162 }
163}
164
165fn gen_template_child_type_checks(fields: &[AttributedField]) -> TokenStream {
166 let crate_ident = crate_ident_new();
167
168 let recurse = fields.iter().map(|field| match field.attr.ty {
169 FieldAttributeType::TemplateChild => {
170 let ty = &field.ty;
171 let ident = &field.ident;
172 let type_err = format!("Template child with id `{}` has incompatible type. XML has {{:?}}, struct has {{:?}}", field.id());
173 quote! {
174 let ty = <<#ty as ::std::ops::Deref>::Target as #crate_ident::glib::prelude::StaticType>::static_type();
175 let child_ty = #crate_ident::glib::prelude::ObjectExt::type_(::std::ops::Deref::deref(&imp.#ident));
176 if !child_ty.is_a(ty) {
177 panic!(#type_err, child_ty, ty);
178 }
179 }
180 }
181 });
182
183 quote! {
184 #(#recurse)*
185 }
186}
187
188pub fn impl_composite_template(input: &syn::DeriveInput) -> Result<TokenStream> {
189 let name = &input.ident;
190 let crate_ident = crate_ident_new();
191
192 let template = match parse_template_source(input) {
193 Ok(v) => Some(v),
194 Err(e) => {
195 return Err(Error::new(
196 Span::call_site(),
197 format!(
198 "{e}: derive(CompositeTemplate) requires #[template(...)] to specify 'file', 'resource', or 'string'"
199 ),
200 ));
201 }
202 };
203
204 let allow_without_attribute = template
205 .as_ref()
206 .map(|t| t.allow_template_child_without_attribute)
207 .unwrap_or(false);
208 let source = template.as_ref().map(|t| &t.source);
209
210 let set_template = source.map(|s| gen_set_template(s, &crate_ident));
211
212 let fields = match input.data {
213 Data::Struct(ref s) => Some(&s.fields),
214 _ => {
215 return Err(Error::new(
216 Span::call_site(),
217 "derive(CompositeTemplate) only supports structs",
218 ));
219 }
220 };
221
222 let attributed_fields = match fields.map(|f| parse_fields(f, allow_without_attribute)) {
223 Some(fields) => fields?,
224 None => vec![],
225 };
226
227 #[cfg(feature = "xml_validation")]
228 {
229 if let Some(source) = source {
230 check_template_fields(source, &attributed_fields)?;
231 }
232 }
233 let template_children = gen_template_child_bindings(&attributed_fields);
234 let checks = gen_template_child_type_checks(&attributed_fields);
235
236 Ok(quote! {
237 impl #crate_ident::subclass::widget::CompositeTemplate for #name {
238 fn bind_template(klass: &mut Self::Class) {
239 #set_template
240
241 unsafe {
242 #template_children
243 }
244 }
245 fn check_template_children(widget: &<Self as #crate_ident::glib::subclass::prelude::ObjectSubclass>::Type) {
246 let imp = #crate_ident::subclass::prelude::ObjectSubclassIsExt::imp(widget);
247 #checks
248 }
249 }
250 })
251}