gtk4-macros-0.3.1/.cargo_vcs_info.json0000644000000001120000000000100131620ustar { "git": { "sha1": "55b67a928dad6cfca8e0caaa360953833bd3a0d7" } } gtk4-macros-0.3.1/COPYRIGHT000064400000000000000000000012130072674642500133000ustar 00000000000000The gtk-rs Project is licensed under the MIT license, see the LICENSE file or . Copyrights in the gtk-rs Project project are retained by their contributors. No copyright assignment is required to contribute to the gtk-rs Project project. For full authorship information, see the version control history. This project provides interoperability with various GNOME libraries but doesn't distribute any parts of them. Distributing compiled libraries and executables that link to those libraries may be subject to terms of the GNU LGPL or other licenses. For more information check the license of each GNOME library. gtk4-macros-0.3.1/Cargo.toml0000644000000025350000000000100111730ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "gtk4-macros" version = "0.3.1" authors = ["The gtk-rs Project Developers"] description = "Macros helpers for GTK 4 bindings" homepage = "https://gtk-rs.org/" documentation = "https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros" keywords = ["gtk", "gtk4", "gtk-rs", "gnome", "GUI"] categories = ["api-bindings", "gui"] license = "MIT" repository = "https://github.com/gtk-rs/gtk4-rs" [lib] proc-macro = true [dependencies.anyhow] version = "1.0" [dependencies.heck] version = "0.3" [dependencies.itertools] version = "0.10" [dependencies.proc-macro-crate] version = "1.0" [dependencies.proc-macro-error] version = "1.0" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "1.0" features = ["full"] default-features = false [dev-dependencies] gtk4-macros-0.3.1/Cargo.toml.orig000064400000000000000000000013340072674642500147000ustar 00000000000000[package] authors = ["The gtk-rs Project Developers"] categories = ["api-bindings", "gui"] description = "Macros helpers for GTK 4 bindings" documentation = "https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros" edition = "2018" homepage = "https://gtk-rs.org/" keywords = ["gtk", "gtk4", "gtk-rs", "gnome", "GUI"] license = "MIT" name = "gtk4-macros" repository = "https://github.com/gtk-rs/gtk4-rs" version = "0.3.1" [lib] proc-macro = true [dependencies] anyhow = "1.0" heck = "0.3" itertools = "0.10" proc-macro-crate = "1.0" proc-macro-error = "1.0" proc-macro2 = "1.0" quote = "1.0" syn = {version = "1.0", default-features = false, features = ["full"]} [dev-dependencies] gtk = { path = "../gtk4", package = "gtk4" } gtk4-macros-0.3.1/LICENSE000064400000000000000000000020000072674642500130050ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gtk4-macros-0.3.1/README.md000064400000000000000000000013760072674642500132760ustar 00000000000000# gtk4-macros [Project site](https://gtk-rs.org/) Macro helpers for GTK 4 bindings, part of [gtk4-rs](https://github.com/gtk-rs/gtk4-rs/). ## Minimum supported Rust version Currently, the minimum supported Rust version is `1.54.0`. ## Documentation - The Rust API [Stable](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros)/[Development](https://gtk-rs.org/gtk4-rs/git/docs/gtk4_macros/) ## Available Macros - `CompositeTemplate` ### See Also - [glib](https://crates.io/crates/glib) - [gio](https://crates.io/crates/gio) - [gtk4](https://crates.io/crates/gdk4) - [gdk4](https://crates.io/crates/gdk4) - [gtk4](https://crates.io/crates/gtk4) ## License The Rust bindings of __gtk4-macros__ are available under the MIT License, please refer to it. gtk4-macros-0.3.1/src/attribute_parser.rs000064400000000000000000000157060072674642500165350ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use anyhow::{bail, Result}; use proc_macro2::Span; use syn::parse::Error; use syn::spanned::Spanned; use syn::{ Attribute, DeriveInput, Field, Fields, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Type, }; pub enum TemplateSource { File(String), Resource(String), String(String), } pub fn parse_template_source(input: &DeriveInput) -> Result { let meta = match find_attribute_meta(&input.attrs, "template")? { Some(meta) => meta, _ => bail!("Missing 'template' attribute"), }; let meta = match meta.nested.iter().find(|n| match n { NestedMeta::Meta(m) => { let p = m.path(); p.is_ident("file") || p.is_ident("resource") || p.is_ident("string") } _ => false, }) { Some(meta) => meta, _ => bail!("Invalid meta, specify one of 'file', 'resource', or 'string'"), }; let (ident, v) = parse_attribute(meta)?; match ident.as_ref() { "file" => Ok(TemplateSource::File(v)), "resource" => Ok(TemplateSource::Resource(v)), "string" => Ok(TemplateSource::String(v)), s => bail!("Unknown enum meta {}", s), } } // find the #[@attr_name] attribute in @attrs fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result> { let meta = match attrs.iter().find(|a| a.path.is_ident(attr_name)) { Some(a) => a.parse_meta(), _ => return Ok(None), }; match meta? { Meta::List(n) => Ok(Some(n)), _ => bail!("wrong meta type"), } } // parse a single meta like: ident = "value" fn parse_attribute(meta: &NestedMeta) -> Result<(String, String)> { let meta = match &meta { NestedMeta::Meta(m) => m, _ => bail!("wrong meta type"), }; let meta = match meta { Meta::NameValue(n) => n, _ => bail!("wrong meta type"), }; let value = match &meta.lit { Lit::Str(s) => s.value(), _ => bail!("wrong meta type"), }; let ident = match meta.path.get_ident() { None => bail!("missing ident"), Some(ident) => ident, }; Ok((ident.to_string(), value)) } pub enum FieldAttributeArg { Id(String), } #[derive(Debug)] pub enum FieldAttributeType { TemplateChild, } pub struct FieldAttribute { pub ty: FieldAttributeType, pub args: Vec, pub path_span: Span, pub span: Span, } pub struct AttributedField { pub ident: Ident, pub ty: Type, pub attr: FieldAttribute, } fn parse_field_attr_value_str(name_value: &MetaNameValue) -> Result { match &name_value.lit { Lit::Str(s) => Ok(s.value()), _ => Err(Error::new( name_value.lit.span(), "invalid value type: Expected str literal", )), } } fn parse_field_attr_meta( ty: &FieldAttributeType, meta: &NestedMeta, ) -> Result { let meta = match &meta { NestedMeta::Meta(m) => m, _ => { return Err(Error::new( meta.span(), "invalid type - expected a name-value pair like id = \"widget\"", )) } }; let name_value = match meta { Meta::NameValue(n) => n, _ => { return Err(Error::new( meta.span(), "invalid type - expected a name-value pair like id = \"widget\"", )) } }; let ident = match name_value.path.get_ident() { None => { return Err(Error::new( name_value.path.span(), "invalid name type - expected identifier", )) } Some(ident) => ident, }; let ident_str = ident.to_string(); let unknown_err = Err(Error::new( ident.span(), &format!("unknown attribute argument: `{}`", ident_str), )); let value = match ty { FieldAttributeType::TemplateChild => match ident_str.as_str() { "id" => FieldAttributeArg::Id(parse_field_attr_value_str(name_value)?), _ => return unknown_err, }, }; Ok(value) } fn parse_field_attr_args( ty: &FieldAttributeType, attr: &Attribute, ) -> Result, Error> { let mut field_attribute_args = Vec::new(); match attr.parse_meta()? { Meta::List(list) => { for meta in &list.nested { let new_arg = parse_field_attr_meta(ty, meta)?; for arg in &field_attribute_args { // Comparison of enum variants, not data if std::mem::discriminant(arg) == std::mem::discriminant(&new_arg) { return Err(Error::new( meta.span(), "two instances of the same attribute \ argument, each argument must be specified only once", )); } } field_attribute_args.push(new_arg); } } Meta::Path(_) => (), meta => { return Err(Error::new( meta.span(), "invalid attribute argument type, expected `name = value` list or nothing", )) } } Ok(field_attribute_args) } fn parse_field(field: &Field) -> Result, Error> { let field_attrs = &field.attrs; let ident = match &field.ident { Some(ident) => ident, None => return Err(Error::new(field.span(), "expected identifier")), }; let ty = &field.ty; let mut attr = None; for field_attr in field_attrs { let span = field_attr.span(); let path_span = field_attr.path.span(); let ty = if field_attr.path.is_ident("template_child") { Some(FieldAttributeType::TemplateChild) } else { None }; if let Some(ty) = ty { let args = parse_field_attr_args(&ty, field_attr)?; if attr.is_none() { attr = Some(FieldAttribute { ty, args, path_span, span, }) } else { return Err(Error::new( span, "multiple attributes on the same field are not supported", )); } } } if let Some(attr) = attr { Ok(Some(AttributedField { ident: ident.clone(), ty: ty.clone(), attr, })) } else { Ok(None) } } pub fn parse_fields(fields: &Fields) -> Result, Error> { let mut attributed_fields = Vec::new(); for field in fields { if !field.attrs.is_empty() { if let Some(attributed_field) = parse_field(field)? { attributed_fields.push(attributed_field) } } } Ok(attributed_fields) } gtk4-macros-0.3.1/src/composite_template_derive.rs000064400000000000000000000051160072674642500204030ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use proc_macro2::TokenStream; use proc_macro_error::{abort, abort_call_site}; use quote::quote; use syn::Data; use std::string::ToString; use crate::attribute_parser::*; use crate::util::*; fn gen_set_template(source: TemplateSource) -> TokenStream { match source { TemplateSource::File(file) => quote! { let t = include_bytes!(#file); klass.set_template(t); }, TemplateSource::Resource(resource) => quote! { klass.set_template_from_resource(&#resource); }, TemplateSource::String(template) => quote! { klass.set_template(&#template); }, } } fn gen_template_child_bindings(fields: &syn::Fields) -> TokenStream { let crate_ident = crate_ident_new(); let attributed_fields = match parse_fields(fields) { Ok(fields) => fields, Err(err) => abort!(err.span(), err.to_string()), }; let recurse = attributed_fields.iter().map(|field| match field.attr.ty { FieldAttributeType::TemplateChild => { let mut value_id = &field.ident.to_string(); let ident = &field.ident; field.attr.args.iter().for_each(|arg| match arg { FieldAttributeArg::Id(value) => { value_id = value; } }); quote! { klass.bind_template_child_with_offset( &#value_id, #crate_ident::offset_of!(Self => #ident), ); } } }); quote! { #(#recurse)* } } pub fn impl_composite_template(input: &syn::DeriveInput) -> TokenStream { let name = &input.ident; let crate_ident = crate_ident_new(); let source = match parse_template_source(input) { Ok(v) => v, Err(e) => abort_call_site!( "{}: derive(CompositeTemplate) requires #[template(...)] to specify 'file', 'resource', or 'string'", e ), }; let set_template = gen_set_template(source); let fields = match input.data { Data::Struct(ref s) => &s.fields, _ => abort_call_site!("derive(CompositeTemplate) only supports structs"), }; let template_children = gen_template_child_bindings(fields); quote! { impl #crate_ident::subclass::widget::CompositeTemplate for #name { fn bind_template(klass: &mut Self::Class) { #set_template unsafe { #template_children } } } } } gtk4-macros-0.3.1/src/lib.rs000064400000000000000000000052470072674642500137230ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. //! # GTK 4 Macros //! //! The crate aims to provide useful macros to use with the GTK 4 Rust bindings. mod attribute_parser; mod composite_template_derive; mod util; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; use syn::{parse_macro_input, DeriveInput}; /// Derive macro for using a composite template in a widget. /// /// The `template` attribute specifies where the template should be loaded /// from; it can be a `file`, a `resource`, or a `string`. /// /// The `template_child` attribute is used to mark all internal widgets /// we need to have programmatic access to. /// /// # Example /// /// Specify that `MyWidget` is using a composite template and load the /// template file the `composite_template.ui` file. /// /// Then, in the `ObjectSubclass` implementation you will need to call /// `bind_template` in the `class_init` function, and `init_template` in /// `instance_init` function. /// /// /// ```no_run /// # fn main() {} /// use gtk::prelude::*; /// use gtk::glib; /// use gtk::CompositeTemplate; /// use gtk::subclass::prelude::*; /// /// mod imp { /// use super::*; /// /// #[derive(Debug, Default, CompositeTemplate)] /// #[template(file = "test/template.ui")] /// pub struct MyWidget { /// #[template_child] /// pub label: TemplateChild, /// #[template_child(id = "my_button_id")] /// pub button: TemplateChild, /// } /// /// #[glib::object_subclass] /// impl ObjectSubclass for MyWidget { /// const NAME: &'static str = "MyWidget"; /// type Type = super::MyWidget; /// type ParentType = gtk::Box; /// /// fn class_init(klass: &mut Self::Class) { /// Self::bind_template(klass); /// } /// /// fn instance_init(obj: &glib::subclass::InitializingObject) { /// obj.init_template(); /// } /// } /// /// impl ObjectImpl for MyWidget {} /// impl WidgetImpl for MyWidget {} /// impl BoxImpl for MyWidget {} /// } /// /// glib::wrapper! { /// pub struct MyWidget(ObjectSubclass) @extends gtk::Widget, gtk::Box; /// } /// /// impl MyWidget { /// pub fn new() -> Self { /// glib::Object::new(&[]).expect("Failed to create an instance of MyWidget") /// } /// } /// ``` #[proc_macro_derive(CompositeTemplate, attributes(template, template_child))] #[proc_macro_error] pub fn composite_template_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let gen = composite_template_derive::impl_composite_template(&input); gen.into() } gtk4-macros-0.3.1/src/test/template.ui000064400000000000000000000006440072674642500157340ustar 00000000000000 gtk4-macros-0.3.1/src/util.rs000064400000000000000000000007230072674642500141240ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use proc_macro2::{Ident, Span}; use proc_macro_crate::crate_name; pub fn crate_ident_new() -> Ident { use proc_macro_crate::FoundCrate; let crate_name = match crate_name("gtk4").expect("missing gtk4 dependency in `Cargo.toml`") { FoundCrate::Name(name) => name, FoundCrate::Itself => "gtk4".to_owned(), }; Ident::new(&crate_name, Span::call_site()) }