内置属性和 FatObj
Ribir 提供了一个强大的内置属性系统,让您可以为任何 Widget 添加常用功能,如布局控制(margin、alignment)、视觉效果(background、border、opacity、transform)和交互事件(on_tap、on_hover)。这些功能并非由每个 Widget 单独实现,而是通过一个称为 FatObj 的通用包装器统一提供。
@ 实例化过程
当您在 fn_widget! 中使用 @ 语 法通过类型来声明(例如 @Text { ... })时,Ribir 会执行以下步骤来构建 Widget:
- 获取 Builder: 调用
Declaretrait 的declarer()方法来获取该 Widget 的 Builder。 - 初始化字段: 对于
{ ... }块中指定的每个字段,调用 Builder 上相应的with_xxx()方法(例如with_text(...))。 - 完成构建: 最后,调用 Builder 的
finish()方法(Builder 实现了ObjDeclarertrait)来完成构建并返回已声明的 Widget。
#[declare] 选项
#[declare] 宏支持多项选项来自定义其行为:
- 默认: 生成一个完整的 Builder,返回
FatObj<Stateful<T>>,启用所有内置属性和响应式状态。 #[declare(stateless)]: 生成一个完整的 Builder,返回FatObj<T>。支持内置属性,但 Widget 本身不是有状态的。#[declare(simple)]: 生成一个简化的 Builder,返回Stateful<T>(如果 struct 没有字段则返回T)。这适用于不需要内置属性的 Widget。#[declare(simple, stateless)]: 与simple类似,但始终返回原始对象T。#[declare(eager)]: 生成一个 eager 模式的 Builder,它会立即构建 Widget(即在设置字段时立即修改 Widget 实例)。这允许部分初始化复杂字段(例如,可以分别设置Size字段的width和height)。它可以与simple和stateless结合使用。#[declare(validate)]: 为 Widget 启用declare_validate验证方法。
[!NOTE]
#[simple_declare]现在已被废弃,推荐使用#[declare(simple)]。
Eager 模式:复杂字段的部分初始化
#[declare(eager)] 支持复杂字段的部分初始化。在延迟模式下,Widget 直到 finish() 时才构建。而在 eager 模式下,Widget 会立即以默认值创建,允许自定义设置方法修改复杂字段的各个部分。
示例:为 Size 字段支持 width 和 height
use ribir::prelude::*;
#[derive(Default)]
#[declare(eager)]
struct SizedBox {
#[declare(default)]
size: Size,
}
impl Render for SizedBox {
fn measure(&self, clamp: BoxClamp, _: &mut MeasureCtx) -> Size { clamp.clamp(self.size) }
fn paint(&self, _: &mut PaintingCtx) {}
}
impl SizedBoxDeclarer {
/// 设置宽度,支持普通值和 pipe
fn with_width<K: ?Sized>(&mut self, width: impl RInto<PipeValue<f32>, K>) -> &mut Self {
let host = self.host().clone_writer();
let mix = self.mix_builtin_widget();
mix.init_sub_widget(width, &host, |w: &mut SizedBox, v| w.size.width = v);
self
}
/// 设置高度,支持普通值和 pipe
fn with_height<K: ?Sized>(&mut self, height: impl RInto<PipeValue<f32>, K>) -> &mut Self {
let host = self.host().clone_writer();
let mix = self.mix_builtin_widget();
mix.init_sub_widget(height, &host, |w: &mut SizedBox, v| w.size.height = v);
self
}
}
// 使用:
fn example() -> Widget<'static> {
let w = Stateful::new(100.0f32);
fn_widget! {
@SizedBox {
width: pipe!(*$read(w)), // 或:width: 100.0
height: 50.0,
}
}.into_widget()
}
关键点:
host()在声明期间提供对有状态 Widget 的访问RInto<PipeValue<T>, K>同时接受普通值和响应式 pipeinit_sub_widget自动处理 pipe 订阅和清理
什么是 FatObj?
FatObj<T> 是 Ribir 核心库中的一个泛型结构体,其作用是在构建阶段临时包装一个 Widget,并为其附加各种内置属性,例如 margin、background、on_tap 等。
工作原理
- 惰性初始化:
FatObj在内部维护所有内置属性(如margin、padding等)的状态,但它们默认为空。只有在您显式使用某个属性时,相关的 Widget 才会被初始化。这确保了未使用的功能不会产生额外的性能开销。 - 组合: 在 Widget 构建的最后阶段,
FatObj会将它包装的 Widget 与已启用的内置功能(如Padding、Container、MixBuiltin等)组合成最终的 Widget 树。
例如:Margin(MixBuiltin(Text))