1use internal::immediate_effects_box;
2use raui_core::{
3 DynamicManaged, DynamicManagedLazy, Lifetime, ManagedLazy, Prefab, PropsData, TypeHash,
4 make_widget,
5 props::{Props, PropsData},
6 widget::{
7 component::WidgetComponent, context::WidgetContext, node::WidgetNode, unit::WidgetUnitNode,
8 },
9};
10use serde::{Deserialize, Serialize};
11use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
12
13thread_local! {
14 pub(crate) static STACK: RefCell<Vec<Vec<WidgetNode>>> = Default::default();
15 pub(crate) static STATES: RefCell<Option<Rc<RefCell<ImmediateStates>>>> = Default::default();
16 pub(crate) static ACCESS_POINTS: RefCell<Option<Rc<RefCell<ImmediateAccessPoints>>>> = Default::default();
17 pub(crate) static PROPS_STACK: RefCell<Option<Rc<RefCell<Vec<Props>>>>> = Default::default();
18}
19
20#[derive(Default)]
21pub struct ImmediateContext {
22 states: Rc<RefCell<ImmediateStates>>,
23 access_points: Rc<RefCell<ImmediateAccessPoints>>,
24 props_stack: Rc<RefCell<Vec<Props>>>,
25}
26
27impl ImmediateContext {
28 pub fn activate(context: &Self) {
29 STATES.with(|states| {
30 context.states.borrow_mut().reset();
31 *states.borrow_mut() = Some(context.states.clone());
32 });
33 ACCESS_POINTS.with(|access_points| {
34 *access_points.borrow_mut() = Some(context.access_points.clone());
35 });
36 PROPS_STACK.with(|props_stack| {
37 *props_stack.borrow_mut() = Some(context.props_stack.clone());
38 });
39 }
40
41 pub fn deactivate() {
42 STATES.with(|states| {
43 *states.borrow_mut() = None;
44 });
45 ACCESS_POINTS.with(|access_points| {
46 if let Some(access_points) = access_points.borrow_mut().as_mut() {
47 access_points.borrow_mut().reset();
48 }
49 *access_points.borrow_mut() = None;
50 });
51 PROPS_STACK.with(|props_stack| {
52 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
53 props_stack.borrow_mut().clear();
54 }
55 *props_stack.borrow_mut() = None;
56 });
57 }
58}
59
60#[derive(Default)]
61struct ImmediateStates {
62 data: Vec<DynamicManaged>,
63 position: usize,
64}
65
66impl ImmediateStates {
67 fn reset(&mut self) {
68 self.data.truncate(self.position);
69 self.position = 0;
70 }
71
72 fn alloc<T>(&mut self, mut init: impl FnMut() -> T) -> ManagedLazy<T> {
73 let index = self.position;
74 self.position += 1;
75 if let Some(managed) = self.data.get_mut(index) {
76 if managed.type_hash() != &TypeHash::of::<T>() {
77 *managed = DynamicManaged::new(init()).ok().unwrap();
78 }
79 } else {
80 self.data.push(DynamicManaged::new(init()).ok().unwrap());
81 }
82 self.data
83 .get(index)
84 .unwrap()
85 .lazy()
86 .into_typed()
87 .ok()
88 .unwrap()
89 }
90}
91
92#[derive(Default)]
93struct ImmediateAccessPoints {
94 data: HashMap<String, DynamicManagedLazy>,
95}
96
97impl ImmediateAccessPoints {
98 fn register<T>(&mut self, id: impl ToString, data: &mut T) -> Lifetime {
99 let result = Lifetime::default();
100 self.data
101 .insert(id.to_string(), DynamicManagedLazy::new(data, result.lazy()));
102 result
103 }
104
105 fn reset(&mut self) {
106 self.data.clear();
107 }
108
109 fn access<T>(&self, id: &str) -> ManagedLazy<T> {
110 self.data
111 .get(id)
112 .unwrap()
113 .clone()
114 .into_typed()
115 .ok()
116 .unwrap()
117 }
118}
119
120#[derive(PropsData, Default, Clone, Serialize, Deserialize)]
121#[props_data(raui_core::props::PropsData)]
122pub struct ImmediateHooks {
123 #[serde(default, skip)]
124 hooks: Vec<fn(&mut WidgetContext)>,
125}
126
127impl ImmediateHooks {
128 pub fn with(mut self, pointer: fn(&mut WidgetContext)) -> Self {
129 self.hooks.push(pointer);
130 self
131 }
132}
133
134impl std::fmt::Debug for ImmediateHooks {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 f.debug_struct(stringify!(ImmediateHooks))
137 .finish_non_exhaustive()
138 }
139}
140
141macro_rules! impl_lifecycle_props {
142 ($($id:ident),+ $(,)?) => {
143 $(
144 #[derive(PropsData, Default, Clone, Serialize, Deserialize)]
145 #[props_data(raui_core::props::PropsData)]
146 pub struct $id {
147 #[serde(default, skip)]
148 callback: Option<Arc<dyn Fn() + Send + Sync>>,
149 }
150
151 impl $id {
152 pub fn new(callback: impl Fn() + Send + Sync + 'static) -> Self {
153 Self {
154 callback: Some(Arc::new(callback)),
155 }
156 }
157 }
158
159 impl std::fmt::Debug for $id {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct(stringify!($id)).finish_non_exhaustive()
162 }
163 }
164 )+
165 };
166}
167
168impl_lifecycle_props! {
169 ImmediateOnMount,
170 ImmediateOnChange,
171 ImmediateOnUnmount
172}
173
174pub fn use_state<T>(init: impl FnMut() -> T) -> ManagedLazy<T> {
175 STATES.with(|states| {
176 let states = states.borrow();
177 let mut states = states
178 .as_ref()
179 .unwrap_or_else(|| panic!("You must activate context first for `use_state` to work!"))
180 .borrow_mut();
181 states.alloc(init)
182 })
183}
184
185pub fn use_access<T>(id: &str) -> ManagedLazy<T> {
186 ACCESS_POINTS.with(|access_points| {
187 let access_points = access_points.borrow();
188 let access_points = access_points
189 .as_ref()
190 .unwrap_or_else(|| panic!("You must activate context first for `use_access` to work!"))
191 .borrow();
192 access_points.access(id)
193 })
194}
195
196pub fn use_stack_props<T: PropsData + Clone + 'static>() -> Option<T> {
197 PROPS_STACK.with(|props_stack| {
198 if let Some(props_stack) = props_stack.borrow().as_ref() {
199 for props in props_stack.borrow().iter().rev() {
200 if let Ok(props) = props.read_cloned::<T>() {
201 return Some(props);
202 }
203 }
204 }
205 None
206 })
207}
208
209pub fn use_effects<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
210 begin();
211 let result = f();
212 let node = end().pop().unwrap_or_default();
213 push(
214 make_widget!(immediate_effects_box)
215 .merge_props(props.into())
216 .named_slot("content", node),
217 );
218 result
219}
220
221pub fn register_access<T>(id: &str, data: &mut T) -> Lifetime {
222 ACCESS_POINTS.with(|access_points| {
223 let access_points = access_points.borrow();
224 let mut access_points = access_points
225 .as_ref()
226 .unwrap_or_else(|| panic!("You must activate context first for `use_access` to work!"))
227 .borrow_mut();
228 access_points.register(id, data)
229 })
230}
231
232pub fn begin() {
233 STACK.with(|stack| stack.borrow_mut().push(Default::default()));
234}
235
236pub fn end() -> Vec<WidgetNode> {
237 STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default())
238}
239
240pub fn push(widget: impl Into<WidgetNode>) {
241 STACK.with(|stack| {
242 if let Some(widgets) = stack.borrow_mut().last_mut() {
243 widgets.push(widget.into());
244 }
245 });
246}
247
248pub fn extend(iter: impl IntoIterator<Item = WidgetNode>) {
249 STACK.with(|stack| {
250 if let Some(widgets) = stack.borrow_mut().last_mut() {
251 widgets.extend(iter);
252 }
253 });
254}
255
256pub fn pop() -> WidgetNode {
257 STACK.with(|stack| {
258 stack
259 .borrow_mut()
260 .last_mut()
261 .and_then(|widgets| widgets.pop())
262 .unwrap_or_default()
263 })
264}
265
266pub fn reset() {
267 STACK.with(|stack| {
268 stack.borrow_mut().clear();
269 });
270 PROPS_STACK.with(|props_stack| {
271 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
272 props_stack.borrow_mut().clear();
273 }
274 });
275}
276
277pub fn apply_key<R>(key: impl ToString, mut f: impl FnMut() -> R) -> R {
278 let key = key.to_string();
279 begin();
280 let result = f();
281 let mut widgets = end();
282 match widgets.len() {
283 0 => {}
284 1 => {
285 let mut widget = widgets.pop().unwrap();
286 if let WidgetNode::Component(widget) = &mut widget {
287 widget.key = Some(key);
288 }
289 push(widget);
290 }
291 _ => {
292 for (index, widget) in widgets.iter_mut().enumerate() {
293 if let WidgetNode::Component(widget) = widget {
294 widget.key = Some(format!("{key}-{index}"));
295 }
296 }
297 extend(widgets);
298 }
299 }
300 result
301}
302
303pub fn apply_props<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
304 let props = props.into();
305 begin();
306 let result = f();
307 let mut widgets = end();
308 for widget in &mut widgets {
309 if let Some(widget) = widget.props_mut() {
310 widget.merge_from(props.clone());
311 }
312 }
313 extend(widgets);
314 result
315}
316
317pub fn apply_shared_props<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
318 let props = props.into();
319 begin();
320 let result = f();
321 let mut widgets = end();
322 for widget in &mut widgets {
323 if let Some(widget) = widget.shared_props_mut() {
324 widget.merge_from(props.clone());
325 }
326 }
327 extend(widgets);
328 result
329}
330
331pub fn stack_props<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
332 PROPS_STACK.with(|props_stack| {
333 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
334 props_stack.borrow_mut().push(props.into());
335 }
336 });
337 begin();
338 let result = f();
339 extend(end());
340 PROPS_STACK.with(|props_stack| {
341 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
342 props_stack.borrow_mut().pop();
343 }
344 });
345 result
346}
347
348pub fn list_component<R>(
349 widget: impl Into<WidgetComponent>,
350 props: impl Into<Props>,
351 mut f: impl FnMut() -> R,
352) -> R {
353 begin();
354 let result = f();
355 let widgets = end();
356 push(
357 widget
358 .into()
359 .merge_props(props.into())
360 .listed_slots(widgets),
361 );
362 result
363}
364
365pub fn content_component<R>(
366 widget: impl Into<WidgetComponent>,
367 content_name: &str,
368 props: impl Into<Props>,
369 mut f: impl FnMut() -> R,
370) -> R {
371 begin();
372 let result = f();
373 let node = end().pop().unwrap_or_default();
374 push(
375 widget
376 .into()
377 .merge_props(props.into())
378 .named_slot(content_name, node),
379 );
380 result
381}
382
383pub fn tuple<R>(mut f: impl FnMut() -> R) -> R {
384 begin();
385 let result = f();
386 let widgets = end();
387 push(WidgetNode::Tuple(widgets));
388 result
389}
390
391pub fn component(widget: impl Into<WidgetComponent>, props: impl Into<Props>) {
392 push(widget.into().merge_props(props.into()));
393}
394
395pub fn unit(widget: impl Into<WidgetUnitNode>) {
396 push(widget.into());
397}
398
399pub fn make_widgets(context: &ImmediateContext, mut f: impl FnMut()) -> Vec<WidgetNode> {
400 ImmediateContext::activate(context);
401 begin();
402 f();
403 let result = end();
404 ImmediateContext::deactivate();
405 result
406}
407
408mod internal {
409 use super::*;
410 use raui_core::{unpack_named_slots, widget::unit::area::AreaBoxNode};
411
412 pub(crate) fn immediate_effects_box(mut ctx: WidgetContext) -> WidgetNode {
413 for hook in ctx.props.read_cloned_or_default::<ImmediateHooks>().hooks {
414 hook(&mut ctx);
415 }
416
417 if let Ok(event) = ctx.props.read::<ImmediateOnMount>()
418 && let Some(callback) = event.callback.as_ref()
419 {
420 let callback = callback.clone();
421 ctx.life_cycle.mount(move |_| {
422 callback();
423 });
424 }
425 if let Ok(event) = ctx.props.read::<ImmediateOnChange>()
426 && let Some(callback) = event.callback.as_ref()
427 {
428 let callback = callback.clone();
429 ctx.life_cycle.change(move |_| {
430 callback();
431 });
432 }
433 if let Ok(event) = ctx.props.read::<ImmediateOnUnmount>()
434 && let Some(callback) = event.callback.as_ref()
435 {
436 let callback = callback.clone();
437 ctx.life_cycle.unmount(move |_| {
438 callback();
439 });
440 }
441
442 unpack_named_slots!(ctx.named_slots => content);
443
444 AreaBoxNode {
445 id: ctx.id.to_owned(),
446 slot: Box::new(content),
447 }
448 .into()
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 fn run(frame: usize) {
457 let show_slider = use_state(|| false);
458 let mut show_slider = show_slider.write().unwrap();
459
460 let show_text_field = use_state(|| false);
461 let mut show_text_field = show_text_field.write().unwrap();
462
463 if frame == 1 {
464 *show_slider = true;
465 } else if frame == 3 {
466 *show_text_field = true;
467 } else if frame == 5 {
468 *show_slider = false;
469 } else if frame == 7 {
470 *show_text_field = false;
471 } else if frame == 9 {
472 *show_slider = true;
473 *show_text_field = true;
474 }
475
476 println!(
477 "* #{} | HOVERED: {} | CLICKED: {}",
478 frame, *show_slider, *show_text_field
479 );
480
481 if *show_slider {
482 slider();
483 }
484 if *show_text_field {
485 text_field();
486 }
487 }
488
489 fn slider() {
490 let value = use_state(|| 0.0);
491 let mut state = value.write().unwrap();
492
493 *state += 0.1;
494 println!("* SLIDER VALUE: {}", *state);
495 }
496
497 fn text_field() {
498 let text = use_state(String::default);
499 let mut text = text.write().unwrap();
500
501 text.push('z');
502
503 println!("* TEXT FIELD: {}", text.as_str());
504 }
505
506 #[test]
507 fn test_use_state() {
508 let context = ImmediateContext::default();
509 for frame in 0..12 {
510 ImmediateContext::activate(&context);
511 run(frame);
512 ImmediateContext::deactivate();
513 }
514 }
515}