1 个不稳定版本
0.1.0 | 2022年9月4日 |
---|
#572 在 过程宏
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 ...
块内调用,并将实现三个方法:properties
、property
和set_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
。
动机
手动实现 properties
、property
和 set_property
有一些缺点
- 非常冗长,有很多样板代码
- 属性名需要多次重复
- 添加属性需要修改三个不同的地方(不算向结构体添加任何字段)
- 难以验证属性标志是否与实现一致(例如,属性被标记为可读写,但只实现了getter)
详细信息
一般结构
在 properties!
块中,预期有一个属性声明的列表。
每个声明由以下内容组成
- 一个类型声明属性(下面将描述),例如
#[int)(minimum = 3, maximum = 27)]
- 零个或多个形式为
#[doc = "..."]
的属性(编译器将这些文档注释转换为这些)。这些文档注释都被连接起来并存储在参数规范的blurb
中。 - 一个属性名,以及包含实现的块:
"property-name" => { /* implementation block */ }
属性类型声明
类型声明以属性的形式出现。它以一个“类型标签”开头,后面跟着一个(可选的)括号列表的标志和键/值对。
示例
#[int(construct, nick = "Great Integer", explicit_notify)
- 在这里
int
是类型标签,它确定要使用的ParamSpec*Builder
类型(参见下表中的支持的类型标签) explicit_notify
和construct
是标志,它们将被传递给参数规范构建器: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::READABLE
或ParamFlags::WRITABLE
标志。如果两个都实现了,则隐式设置ParamFlags::READWRITE
。
每个块分别成为fn property
和fn 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