6 个版本 (3 个重大变更)
0.4.0 | 2024年5月26日 |
---|---|
0.3.0 | 2024年5月26日 |
0.2.0 | 2024年5月26日 |
0.1.2 | 2024年1月8日 |
#135 in GUI
67KB
1.5K SLoC
egui_hooks
类似 React Hooks 的 API,用于增强 egui::Memory
的易用性
egui 版本 | egui_hooks 版本 |
---|---|
[email protected] | 0.1.2 |
[email protected] | 0.2.0 |
[email protected] | 0.3.0 |
[email protected] | 0.4.0 |
概述
此 crate 提供了类似 React Hooks 的 API,用于 egui。
尽管这个项目最初是一个玩具项目,但我最终发现它非常有用,它可以成为部件开发的基石,也可以用于应用开发。
查看示例,运行 cargo run --example <example-name>
以查看演示。
特性
- 无资源泄露:与直接使用
egui::Memory
相反,当部件不再显示时,状态会自动从 HashMap 中释放。这是基于此 crate 中定义的TwoFrameMap
(2f kv)。 - 无锁定或回调:您可以在不使用
ui.data(|| { ... })
的情况下管理状态。这是因为 hooks 封装了底层的 RwLock 操作。 - 依赖跟踪:Hooks 有依赖项,如
use_state(|| user_id.clone(), user_id)
或use_effect(|| log(input), input)
,因此您可以精确跟踪依赖关系,而无需手动编写状态更改时的if
语句。 - 可组合:Hooks 是可组合的,您可以在自定义 Hooks 中调用现有 Hooks。
- 熟悉API:钩子设计得与React Hooks API相似,因此您可以轻松学习如何使用它们。在最近的UI开发场景中,管理UI状态是关键,但内置的
egui::Memory
是一个相对底层的API,对应用程序开发不够友好,而egui_hooks提供了一个更高级的API,但控制更加精确。
工作原理
如果您在一个小部件中使用use_state(|| 0usize, dep).into_var()
,以下事情会发生
- 在第一次调用
use_state
时,它会在egui::Memory
中创建一个具有默认值的Arc<ArcSwap<usize>>
。 - 如果自上一帧以来
dep
已更改,则将默认值存储到现有的ArcSwap
中。 - 返回一个
Var<usize>
给调用者。 - 调用者可以在其小部件代码中
Deref
或DerefMut
Var
。 - 当
Var
被丢弃时,它将更新后的值存储到ArcSwap
中。 - 当小部件不再显示时,从
egui::Memory
中删除ArcSwap
。
这是egui_hooks中钩子的典型生命周期。
此外,还有一个持久版本的use_state
,称为use_persisted_state
。它执行类似的事情,但它使用持久化方法将状态的副本存储到egui::Memory
中。与未持久化的一样,当小部件不再显示时,持久化状态会被释放。您需要persistence
功能才能使用持久化钩子。
预期用例
use_state
用于特定小部件的状态(例如动画状态、滚动位置)use_state
与into_var()
一起使用,将变量就地提供给Window::open
或TextEdit::singleline
use_memo
、use_cache
用于缓存昂贵的计算use_effect
、use_future
用于副作用(例如日志记录、网络请求)use_global
用于全局设置(例如主题、区域设置)use_kv
用于在多个小部件之间共享状态(例如获取特定小部件的位置)use_ephemeral_kv
用于在当前帧中存储事件(例如在自定义小部件上提供自定义响应)use_previous_measurement
用于使用上一帧的结果进行布局use_measurement
用于计算和缓存小部件的大小以进行布局
状态
-
use_memo
-
use_effect
-
use_effect_with_cleanup
-
use_state
、use_persisted_state
-
state.into_var()
将状态用作变量 -
use_kv
,use_persisted_kv
-
use_2f_kv
,use_persisted_2f_kv
-
使用临时kv
-
use_global
,use_persisted_global
,和use_ephemeral_global
-
use_cache
(egui::Memory 中缓存的轻量级包装器) -
使用之前的测量
-
use_measurement
(无需担心2^N 问题计算小部件的大小。) -
use_future
(需要tokio
功能) -
use_throttle
和use_debounce
-
使用拖拽起始点
-
use_two_path
(这是一个玩笑,但真的想实现这个功能)
用法
- 使用状态
// You can reset the initial state by changing the dependency part.
let count = ui.use_state(|| 0usize, ());
ui.label(format!("Count: {}", count));
if ui.button("Increment").clicked() {
count.set_next(*count + 1);
}
- use_memo
let count = ui.use_state(|| 0usize, ());
let memo = ui.use_memo(
|| {
println!("Calculating memoized value");
count.pow(2)
},
count.clone(),
);
ui.label(format!("Memo: {}", memo));
if ui.button("Increment").clicked() {
count.set_next(*count + 1);
}
自定义钩子
您可以通过两种方式创建自己的钩子。
- 为钩子创建一个函数
这是创建自定义钩子的最简单和推荐的方式。
fn use_search(ui: &mut Ui, backend: Backend) -> Option<SearchResults> {
let text = ui.use_state(|| String::default(), ()).into_var();
ui.text_edit_singleline(&mut *name);
ui.use_future(async {
backend.search(name.get()).await
}, name.state())
}
- 实现
Hook
特性
所有内置钩子都是通过这种方式实现的。这允许您创建一个具有完全控制的钩子,但这种方式有点冗长。
impl<D> Hook<D> for MyHook {
type Backend = ()
type Output = usize;
fn init(
&mut self,
_index: usize,
_deps: &D,
_backend: Option<Self::Backend>,
_ui: &mut egui::Ui,
) -> Self::Backend {
}
fn hook(self, backend: &mut Self::Backend, ui: &mut egui::Ui) -> Self::Output {
let count = ui.use_state(0usize, ());
ui.label(format!("Count: {}", count));
if ui.button("Increment").clicked() {
count.set_next(*count + 1);
}
count
}
}
依赖关系
~4.5–10MB
~87K SLoC