1use std::any::Any;
5use std::hash::{DefaultHasher, Hash, Hasher};
6use std::panic::Location;
7
8use crate::cx::{Cx, location_key};
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11
12use crate::state::{Handle, State};
13
14#[track_caller]
19pub fn use_state<'s, T: 'static>(cx: &mut Cx<'s, '_>, init: impl FnOnce() -> T) -> State<'s, T> {
20 let location = Location::caller();
21 let store = cx.store;
22 let id = cx.scope_id().with(location_key(location));
23 let slot = store.slot(id, location, || Box::new(init()) as Box<dyn Any>);
24 State::new(store, slot, location)
25}
26
27#[track_caller]
29pub fn use_handle<'s, T: 'static>(cx: &mut Cx<'s, '_>, init: impl FnOnce() -> T) -> Handle<'s, T> {
30 let location = Location::caller();
31 let store = cx.store;
32 let id = cx.scope_id().with(location_key(location));
33 let slot = store.slot(id, location, || Box::new(init()) as Box<dyn Any>);
34 Handle::new(store, slot)
35}
36
37pub(crate) fn deps_hash<D: Hash>(deps: &D) -> u64 {
42 let mut hasher = DefaultHasher::new();
43 deps.hash(&mut hasher);
44 hasher.finish()
45}
46
47#[track_caller]
54pub fn use_memo<'s, D: Hash, T: 'static>(
55 cx: &mut Cx<'s, '_>,
56 deps: D,
57 f: impl FnOnce() -> T,
58) -> &'s T {
59 let location = Location::caller();
60 let store = cx.store;
61 let id = cx.scope_id().with(location_key(location));
62 let slot = store.slot(id, location, || Box::new(()) as Box<dyn Any>);
63
64 let hash = deps_hash(&deps);
65 let cached = match slot.memo_last() {
66 Some(value) if slot.deps_hash() == Some(hash) => Some(value),
67 _ => None,
68 };
69 let value = match cached {
70 Some(value) => value,
71 None => {
72 let value = slot.memo_push(Box::new(f()) as Box<dyn Any>);
73 slot.set_deps_hash(hash);
74 value
75 }
76 };
77 value.downcast_ref::<T>().expect("memo slot type mismatch")
78}
79
80#[track_caller]
91pub fn use_persisted<'s, T: Serialize + DeserializeOwned + 'static>(
92 cx: &mut Cx<'s, '_>,
93 key: &str,
94 init: impl FnOnce() -> T,
95) -> State<'s, T> {
96 fn to_json<T: Serialize + 'static>(value: &dyn Any) -> Option<String> {
97 serde_json::to_string(value.downcast_ref::<T>()?).ok()
98 }
99
100 let location = Location::caller();
101 let store = cx.store;
102 let slot = store.persisted_slot(key, location, to_json::<T>, || {
103 let restored =
104 store
105 .persisted_json(key)
106 .and_then(|json| match serde_json::from_str::<T>(&json) {
107 Ok(value) => Some(value),
108 Err(err) => {
109 log::warn!(
110 "egui-reactor: persisted value {key:?} could not be read: {err}"
111 );
112 None
113 }
114 });
115 Box::new(restored.unwrap_or_else(init)) as Box<dyn Any>
116 });
117 State::new(store, slot, location)
118}
119
120pub struct NoCleanup;
122
123pub struct FnCleanup;
125
126pub trait IntoCleanup<Marker> {
131 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>>;
133}
134
135impl IntoCleanup<NoCleanup> for () {
136 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
137 None
138 }
139}
140
141impl<F: FnOnce() + 'static> IntoCleanup<FnCleanup> for F {
142 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
143 Some(Box::new(self))
144 }
145}
146
147#[track_caller]
153pub fn use_effect<D, C, M>(cx: &mut Cx<'_, '_>, deps: D, f: impl FnOnce() -> C)
154where
155 D: Hash,
156 C: IntoCleanup<M>,
157{
158 let location = Location::caller();
159 let store = cx.store;
160 let id = cx.scope_id().with(location_key(location));
161 let slot = store.slot(id, location, || Box::new(()) as Box<dyn Any>);
162
163 let hash = deps_hash(&deps);
164 if slot.deps_hash() == Some(hash) {
165 return;
166 }
167 if let Some(cleanup) = slot.take_cleanup() {
168 cleanup();
169 }
170 let cleanup = f().into_cleanup();
171 slot.set_cleanup(cleanup);
172 slot.set_deps_hash(hash);
173}
174
175#[track_caller]
185pub fn use_animate(cx: &mut Cx<'_, '_>, on: bool, time: f32) -> f32 {
186 use_animate_with(cx, on, time, egui::emath::easing::cubic_out)
187}
188
189#[track_caller]
191pub fn use_animate_with(cx: &mut Cx<'_, '_>, on: bool, time: f32, easing: fn(f32) -> f32) -> f32 {
192 let id = cx.scope_id().with(location_key(Location::caller()));
193 cx.ctx()
194 .animate_bool_with_time_and_easing(id, on, time, easing)
195}