文章目录
之前一篇博客Rust 开发的一些GUI库提到了floem
库,今天我决定试用了一下,根据官网文档,食用使用方式如下:
开始使用
让我们通过一个简单的项目来快速了解 Floem 的基础知识。
创建一个新的 Rust 项目
cargo new floem-app
添加 Floem 依赖
cd floem-app
cargo add floem
Hello World
在你的 main.rs 文件中,写入以下内容:
fn main() {
floem::launch(|| "Hello, World!");
}
然后运行 cargo run
,你将在 Floem 窗口中看到你的第一个 hello world。
看起来很简单,对吧?如果你查看 floem::launch
的定义,会发现它接收一个返回 IntoView
的闭包。如果你熟悉 Rust,IntoView
是一个 trait,包含方法 into_view
,可以将实现该 trait 的类型转换为 Floem View。Floem View 是 Floem 的核心,可以是文本标签、按钮、文本输入框或滚动视图等。Floem 为 &str
类型内部实现了 IntoView
,所以我们可以直接返回 “Hello, World!” 作为 IntoView
。
现在让我们深入一个更复杂的例子:计数器应用。
计数器应用
我们将创建一个非常简单的计数器,显示当前计数值,并有一个递增按钮和一个递减按钮。
在 main.rs 中写入以下内容:
use floem::{views::button, IntoView};
fn app_view() -> impl IntoView {
(
"Value: ",
button("Increment"),
button("Decrement"),
)
}
fn main() {
floem::launch(app_view);
}
运行后,你应该能看到 "Value: " 和两个巨大的 “Increment” 和 “Decrement” 按钮。
让我们通过添加一些“样式”来改进它:
use floem::{
views::{button, Decorators},
IntoView,
};
fn app_view() -> impl IntoView {
(
"Value: ",
(button("Increment"), button("Decrement")).style(|s| s.flex_row().gap(6)),
).style(|s| s.flex_col().gap(6).items_center())
}
fn main() {
floem::launch(app_view);
}
再次运行后,你会发现布局变得更合理了。在 Floem 中,视图通过 style
方法进行样式设置,该方法由 floem::views::Decorators
提供给所有实现了 IntoView
的类型。所以记得先导入该 trait,否则 Rust 编译器会报错。
Floem 的样式是通过 Taffy crate 实现的 Rust 版 Flexbox。如果你熟悉 Flexbox,会发现很多类似的地方。如果不熟悉,网上有很多关于 Flexbox 的教程,这些知识也适用于 Floem 样式。
响应式
但现在点击按钮还没有任何效果,因为还没有可变的值被显示或改变。那如何在 Floem 中实现响应式呢?
Signal
(信号)是 Floem 响应式的基础。如果你熟悉 Solidjs 或 Leptos,这里的 signal 与那些框架类似。简单来说,如果你更新了一个 Signal
的值,所有“监听”该 signal 的内容都会收到通知,并根据更新触发相应操作。在我们的计数器例子中,我们希望计数值在 UI 中被更新。让我们创建一个信号 signal。
use floem::{
prelude::create_rw_signal,
views::{button, dyn_view, Decorators},
IntoView,
};
fn app_view() -> impl IntoView {
let counter = create_rw_signal(0);
(
dyn_view(move || format!("Value: {}", counter)),
(button("Increment"), button("Decrement")).style(|s| s.flex_row().gap(6)),
)
.style(|s| s.flex_col().gap(6).items_center())
}
fn main() {
floem::launch(app_view);
}
你会注意到我们创建了一个计数器信号 signal,并用闭包包裹了值的显示,通过 dyn_view
格式化计数器的值。你可能会问,为什么不用 format!("Value: {}", counter)
,毕竟 String 也实现了 IntoView
。这样写虽然能编译通过,但会失去响应式。
解释如下:
与 Reactjs 等框架不同,视图创建函数只会被调用一次。因此 format!("Value: {}", counter)
也只会被调用一次,并且只会获取计数器的初始值 0。即使之后更新了计数器 signal 的值,Value: 0
也不会更新,因为不会再次运行。
所以 Floem 的响应式依赖于闭包,每当 signal 更新时,闭包会被反复执行。这带来了 Floem 的“细粒度响应式”特性,UI 不需要与之前的状态做 diff,只会更新需要更新的部分,无论视图树多深。即使计数值显示在很深的嵌套中,也能“直接”更新 UI,仅仅是那个文本标签部分,无需从根遍历视图树。
这也形成了 Floem 响应式的基本规则:如果你希望某些内容被更新,需要使用闭包。如果你在使用 Floem 时遇到某些内容没有更新,首先要检查“是不是用了闭包”。
动作
但按钮依然没有任何作用,因为我们还没有让按钮点击去更新计数器 signal。如果你这样写:
use floem::{
prelude::create_rw_signal,
views::{button, dyn_view, Decorators},
IntoView,
};
fn app_view() -> impl IntoView {
let mut counter = create_rw_signal(0);
(
dyn_view(move || format!("Value: {}", counter)),
(
button("Increment").action(move || counter += 1),
button("Decrement").action(move || counter -= 1),
)
.style(|s| s.flex_row().gap(6)),
)
.style(|s| s.flex_col().gap(6).items_center())
}
fn main() {
floem::launch(app_view);
}
我们只需在按钮上调用 action
方法,递增或递减计数器 signal 的值。现在你就有了一个可用的计数器示例。
本教程到此结束。想了解更多 Floem,可以查看我们仓库中的 示例,或阅读 API 文档。
后记
在Windows系统中,默认生成的GUI界面在运行的时候会弹出命令行窗口(控制台),要去掉这个窗口,可以通过以下cargo
命令实现:
- 对于debug版:
cargo rustc -- -Clink-args="/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
- 对于release版:
cargo rustc --release -- -Clink-args="/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"