#gtk #macro

nightly macro gtk-properties-macro

为gtk-rs提供的实验性属性声明宏

1 个不稳定版本

0.1.0 2022年9月4日

#572过程宏

MIT 许可证

27KB
463

⚠ 实验性项目 ⚠

gtk-properties-macro

此包包含一个宏,使用gtk-rs时可以更轻松地声明对象属性。

有关Rust中GTK属性的一般介绍,请参阅gtk-rs书籍中的“属性”章节

用法

将以下内容添加到您的 Cargo.toml

gtk-properties-macro = "0.1"

⚠ 需要 Rust nightly

示例

这是一个最小示例,在功能上等同于本书中的这个示例

有关更完整的(工作)示例,请参阅examples/目录。如果您对宏生成的代码感兴趣,请查看tests/expands/目录中的文件。

让我们开始吧

use gtk_properties_macro::properties;

impl ObjectImpl for CustomButton {
    properties! {
        #[int]
        "number" => {
            get {
                self.number.get().to_value()
            }
            set {
                let input_number =
                    value.get().expect("The value needs to be of type `i32`.");
                self.number.replace(input_number);
            }
        }
    }

    fn constructed(&self, boj: &Self::Type) {
        ...
    }
}

让我们逐个分析它

properties! { ... }

这里我们调用properties宏。它必须在impl ObjectImpl for ...块内调用,并将实现三个方法:propertiespropertyset_property

#[int]
"number" => { ... }

这是一个属性声明。在这种情况下,它声明了一个名为"number"的属性,其类型为"int"(这里的"int"对应于ParamSpecInt,稍后会更详细地介绍)。

get {
    self.number.get().to_value()
}

这指定了如何"获取"属性的值。该块成为fn property实现的组成部分。在'get'块内,我们可以访问以下内容:

  • self:我们的对象的"内部"结构体
  • object:外部对象
  • id:此属性的ID(usize
  • pspec:此属性的ParamSpec

该块必须评估为glib::Value

set {
    let input_number =
        value.get().expect("The value needs to be of type `i32`.");
    self.number.replace(input_number);
}

属性的相应“set”块。它成为 fn set_property 实现的一部分。在 'set' 块中,我们可以访问

  • 'get' 块中的所有内容
  • value:一个包含正在设置的值的 glib::Value

动机

手动实现 propertiespropertyset_property 有一些缺点

  • 非常冗长,有很多样板代码
  • 属性名需要多次重复
  • 添加属性需要修改三个不同的地方(不算向结构体添加任何字段)
  • 难以验证属性标志是否与实现一致(例如,属性被标记为可读写,但只实现了getter)

详细信息

一般结构

properties! 块中,预期有一个属性声明的列表。

每个声明由以下内容组成

  1. 一个类型声明属性(下面将描述),例如 #[int)(minimum = 3, maximum = 27)]
  2. 零个或多个形式为 #[doc = "..."] 的属性(编译器将这些文档注释转换为这些)。这些文档注释都被连接起来并存储在参数规范的 blurb 中。
  3. 一个属性名,以及包含实现的块:"property-name" => { /* implementation block */ }

属性类型声明

类型声明以属性的形式出现。它以一个“类型标签”开头,后面跟着一个(可选的)括号列表的标志和键/值对。

示例

#[int(construct, nick = "Great Integer", explicit_notify)
  • 在这里 int 是类型标签,它确定要使用的 ParamSpec*Builder 类型(参见下表中的支持的类型标签)
  • explicit_notifyconstruct 是标志,它们将被传递给参数规范构建器:builder.flags(ParamFlags::CONSTRUCT | ParamFlags::EXPLICIT_NOTIFY)
  • nick = "Great Integer" 是一个键/值对,它将成为构建器上的一个方法调用:builder.nick("My Number")

目前对这些参数的解析有一个例外:如果类型标签是 object,则第一个参数 必须 是一个 gobject 类型。示例

#[object(gtk::Button, more, flags, here, ...)]

支持的类型

由于这是一个实验,目前只支持几种类型

ParamSpec 类型 类型标签
ParamSpecBoolean 布尔值
ParamSpecBoxed -
ParamSpecChar 字符
ParamSpecDouble double
ParamSpecEnum -
ParamSpecFlags -
ParamSpecFloat float
ParamSpecGType -
ParamSpecInt int
ParamSpecInt64 int64
ParamSpecLong long
ParamSpecObject object(some::glib::Object)
ParamSpecOverride -
ParamSpecParam -
ParamSpecPointer -
ParamSpecString string
ParamSpecUChar -
ParamSpecUInt -
ParamSpecUInt64 -
ParamSpecULong -
ParamSpecUnichar -
ParamSpecValueArray -
ParamSpecVariant -

实现块

属性定义必须实现至少一个'get'或'set'。如果只实现一个,则相应地隐式设置ParamFlags::READABLEParamFlags::WRITABLE标志。如果两个都实现了,则隐式设置ParamFlags::READWRITE

每个块分别成为fn propertyfn set_property方法的组成部分。

示例

impl ObjectImpl for MyObject {
    properties! {
        #[int]
        "my-number" => {
            get { self.my_number.get() } // assuming `my_number` is `Cell<Value>` here for simplicity
            set { self.my_number.replace(value); }
        }
        #[string]
        "my-string" => {
            get { self.my_string.get() }
            set { self.my_string.replace(value); }
        }
    }
}

生成一个类似这样的property函数

fn property(&self, object: &Self::Type, id: usize, pspec: ParamSpec) -> Value {
    match id {
        1 => self.my_number().get(),
        2 => self.my_string().get(),
        _ => unimplemented!()
    }
}

以及相应的set_property函数,如下所示

fn set_property(&self, object: &Self::Type, id: usize, value: Value, pspec: ParamSpec) {
    match id {
        1 => {
            self.my_number().replace(value);
        }
        2 => {
            self.my_string().replace(value);
        }
        _ => unimplemented!()
    }
}

未来想法

  • 允许自定义ParamSpec声明,而无需扩展DSL
    properties! {
        ...
        // a custom property ('spec' block required), denoted by `_`:
        _ => {
            spec { ParamSpecSomething::builder("my-custom-prop").build() }
            get { ... }
            set { ... }
        }
        ...
    }
    
  • 通过将嵌套声明作为第一个参数解析,支持ParamSpecValueArray
    properties! {
        ...
        // sth like ParamSpecValueArray::builder("my-string-array", ParamSpecString::builder("my-string").build()).flags(...).build()
        #[array(string(name = "my-string"), explicit_notify)]
        "my-string-array" => {
            ...
        }
        ...
    }
    
  • 如果许多属性对应于内嵌对象结构体的简单字段,get/set块可能会重复。可能的缩写
    struct MyObject {
      x: Cell<i32>,
      y: Cell<i32>,
      z: Cell<i32>,
    }
    
    impl ObjectImpl for MyObject {
        properties! {
            #[int] "x" => cell(x),
            #[int] "y" => cell(y),
            #[int] "z" => cell(z),
        }
    }
    
    其中cell(x)等价于
    get { self.x.get().to_value() }
    set { self.x.replace(value.get().unwrap()) }
    

依赖项

~1.5MB
~35K SLoC