不依赖 "DSL" 使用 Ribir
或许是为了更直观的调试,或许是为了让代码更具 Rust 风格,有些人会更倾向于避免使用过多的宏和引入新的语法,因此也就不愿意使用 Ribir 的 "DSL"。
这并无问题,得益于 Ribir 在设计初期就将 "DSL" 定位为一个轻量级的语法转换层,你完全可以直接使用 Ribir 的 API 来构建 UI。甚至在一个代码片段中,你可以选择部分使用 API,部分使用宏,两者交织在一起使用。一切都将简单而自然。
核心概念
在 Ribir 中:
- 视图是由 widget 作为基本单位构建的。
- widget 之间通过纯组合方式组成新的 widget。
因此,通过 API 构建 UI 主要涉及两个关键点:
- 如何创建 widget
- 如何组合子 widget
通过 API 创建 widget
以 Radio
widget 为例,其定义如下:
use ribir::prelude::*;
pub struct Radio {
pub selected: bool,
pub value: Box<dyn Any>
}
这与常规的 Rust 结构体无异,你可以直接创建一个对象:
use ribir::prelude::*;
let radio = Radio { selected: true, value: Box::new(1.) };
这样,我们就得到了一个选中的 Radio
.
通过 FatObj
扩展 widget 的能力
我们已经创建了一个 Radio
,但是 它并没有提供任何响应事件的 API。
这是因为在 Ribir 中,事件响应是由独立的 widget 负责实现。任何 widget 都可以通过与它组合来获取事件响应的能力。
并且,对于内建 widget 如事件响应,我们可以无需通过组合方式即可获取。Ribir 提供了一个 FatObj<T>
的泛型,它提供了所有内建 widget 的初始化 API。只需用它包裹我们的 widget,即可让 widget 获得所有内建 widget 的能力。
use ribir::prelude::*;
let radio = Radio { selected: true, value: Box::new(1.) };
let radio = FatObj::new(radio)
.on_tap(|_| println!("Radio tapped"));
但在实际使用中,我们通常不直接这样写,而是通过 Declare
这个 trait 来创建 widget。
use ribir::prelude::*;
let mut btn = Radio::declarer();
btn.with_selected(true).on_tap(|_| println!("Radio clicked"));
let btn: FatObj<State<Radio>> = btn.finish();
为何我们应使用 Declare
创建 widget?
在上述示例中,我们通过类似 Builder 模式来创建 widget,这使得过程看起来更复杂。然而,这种方式实际上带来了更多的优势。
完整的初始化 API
要注意的是,我们最终创建的是 FatObj<State<Radio>>
,而不是 Radio
。这是因为通过 Declare
,我们不仅可以使用同名方法配置属性,还可以利用 FatObj
扩展内建 widget 的能力。至于为什么要使用 State
,这是因为 State
可以让你的 widget 状态被监听和修改。
use ribir::prelude::*;
let mut radio = Radio::declarer();
// 我们可以使用内建能力
radio.on_tap(|_| println!("taped!"));
let radio: FatObj<State<Radio>> = radio.finish();
watch!($read(radio).selected)
.subscribe(|selected| println!("The radio state change to {selected}"));
当然,无论是 FatObj
还是 State
,只有在你用到它们提供的能力时,才会影响到最终构建的视图的开销。