内置属性和 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()
}