Crates.io | egui_hooks |
lib.rs | egui_hooks |
version | |
source | src |
created_at | 2024-01-04 15:31:02.112665 |
updated_at | 2024-10-18 07:30:54.574781 |
description | React Hooks like API for egui |
homepage | |
repository | https://github.com/ryo33/egui_hooks |
max_upload_size | |
id | 1088743 |
Cargo.toml error: | TOML parse error at line 17, column 1 | 17 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
React Hooks like API for enhancing the ergonomics of egui::Memory
egui version | egui_hooks version |
---|---|
egui@0.24.0 | 0.1.2 |
egui@0.25.0 | 0.2.0 |
egui@0.26.0 | 0.3.0 |
egui@0.27.0 | 0.4.0 |
egui@0.28.0 | 0.5.0 |
egui@0.29.0 | 0.6.0 |
This crate provids React Hooks like API for egui.
Though this started as a toy project, eventually I found that it's definitely useful and that could be a core building block for widget development, and also for application development.
Look at the examples, run
cargo run --example <example-name>
to see the demo.
egui::Memory
directly, the states are
automatically freed from the HashMap when the widget will be no longer
displayed. This is based on TwoFrameMap
(2f kv) defined in this crate.ui.data(|| { ... })
.
This is because hooks encapsulate the underlying RwLock operation.use_state(|| user_id.clone(), user_id)
or
use_effect(|| log(input), input)
, so you can precisely track the
dependencies without manually writing if
statements on state change.egui::Memory
is relatively
low-level API and not friendly for applcation development, and egui_hooks
provides a higher level API but with more precise control.If you use use_state(|| 0usize, dep).into_var()
in a widget, the following
things happen:
use_state
, it creates a Arc<ArcSwap<usize>>
in the
egui::Memory
with the default value.dep
is changed since the last frame, it stores the default value to
the existing ArcSwap
.Var<usize>
to the caller.Deref
or DerefMut
the Var
in their widget code.Var
is dropped, it stores the updated value to the ArcSwap
.ArcSwap
is removed from the
egui::Memory
.This is the typical lifecycle of a hook in egui_hooks.
Also, there is a persistent version of use_state
called use_persisted_state
.
It does the similar thing, but it stores the copy of the state to the
egui::Memory
with persisted methods. The persisted state is freed when the
widget is no longer displayed as like the not-persisted one. You need persistence
feature to use persisted hooks.
use_state
for states in a specific widget (e.g. animation state, scroll
position)use_state
with into_var()
to feed a variable in-place to Window::open
or
TextEdit::singleline
use_memo
, use_cache
for caching expensive calculationuse_effect
, use_future
for side effects (e.g. logging, network request)use_global
for global settings (e.g. theme, locale)use_kv
for sharing states between widgets (e.g. getting a position of a
specific widget)use_ephemeral_kv
for storing events in the current frame (e.g. providing
custom response on a custom widget)use_previous_measurement
for using the previous frame result for layoutinguse_measurement
for calculating and memoizing the size of a widget for
layoutinguse_memo
use_effect
use_effect_with_cleanup
use_state
, use_persisted_state
state.into_var()
to use state as a variableuse_kv
, use_persisted_kv
use_2f_kv
, use_persisted_2f_kv
use_ephemeral_kv
use_global
, use_persisted_global
, and use_ephemeral_global
use_cache
(a thin wrapper of caches in egui::Memory
)use_previous_measurement
use_measurement
(calculate the size of the widget without fear of the
2^N problem.use_future
(needs tokio
feature)use_throttle
and use_debounce
use_drag_origin
use_two_path
(it's joke, but really want to implement this)// 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);
}
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);
}
You can create your own hooks by the two ways.
This is the simplest and recommended way to create a custom hook.
fn use_search(ui: &mut Ui, backend: Backend) -> Option<SearchResults> {
let text = ui.use_state(|| String::default(), ()).into_var().into_var();
ui.text_edit_singleline(&mut *text);
ui.use_future(async {
backend.search(&*text).await
}, text.clone())
}
Hook
traitAll built-in hooks are implemented in this way. This allow you to create a hook with full control, but it is a bit verbose.
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
}
}