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 WidgetRef, component::WidgetComponent, context::WidgetContext, node::WidgetNode,
8 unit::WidgetUnitNode,
9 },
10};
11use serde::{Deserialize, Serialize};
12use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
13
14thread_local! {
15 pub(crate) static STACK: RefCell<Vec<Vec<WidgetNode>>> = Default::default();
16 pub(crate) static STATES: RefCell<Option<Rc<RefCell<ImmediateStates>>>> = Default::default();
17 pub(crate) static ACCESS_POINTS: RefCell<Option<Rc<RefCell<ImmediateAccessPoints>>>> = Default::default();
18 pub(crate) static PROPS_STACK: RefCell<Option<Rc<RefCell<Vec<Props>>>>> = Default::default();
19}
20
21#[derive(Default)]
22pub struct ImmediateContext {
23 states: Rc<RefCell<ImmediateStates>>,
24 access_points: Rc<RefCell<ImmediateAccessPoints>>,
25 props_stack: Rc<RefCell<Vec<Props>>>,
26}
27
28impl ImmediateContext {
29 pub fn activate(context: &Self) {
30 STATES.with(|states| {
31 context.states.borrow_mut().reset();
32 *states.borrow_mut() = Some(context.states.clone());
33 });
34 ACCESS_POINTS.with(|access_points| {
35 *access_points.borrow_mut() = Some(context.access_points.clone());
36 });
37 PROPS_STACK.with(|props_stack| {
38 *props_stack.borrow_mut() = Some(context.props_stack.clone());
39 });
40 }
41
42 pub fn deactivate() {
43 STATES.with(|states| {
44 *states.borrow_mut() = None;
45 });
46 ACCESS_POINTS.with(|access_points| {
47 if let Some(access_points) = access_points.borrow_mut().as_mut() {
48 access_points.borrow_mut().reset();
49 }
50 *access_points.borrow_mut() = None;
51 });
52 PROPS_STACK.with(|props_stack| {
53 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
54 props_stack.borrow_mut().clear();
55 }
56 *props_stack.borrow_mut() = None;
57 });
58 }
59}
60
61#[derive(Default)]
62struct ImmediateStates {
63 data: Vec<DynamicManaged>,
64 position: usize,
65}
66
67impl ImmediateStates {
68 fn reset(&mut self) {
69 self.data.truncate(self.position);
70 self.position = 0;
71 }
72
73 fn alloc<T>(&mut self, mut init: impl FnMut() -> T) -> ManagedLazy<T> {
74 let index = self.position;
75 self.position += 1;
76 if let Some(managed) = self.data.get_mut(index) {
77 if managed.type_hash() != &TypeHash::of::<T>() {
78 *managed = DynamicManaged::new(init()).ok().unwrap();
79 }
80 } else {
81 self.data.push(DynamicManaged::new(init()).ok().unwrap());
82 }
83 self.data
84 .get(index)
85 .unwrap()
86 .lazy()
87 .into_typed()
88 .ok()
89 .unwrap()
90 }
91}
92
93#[derive(Default)]
94struct ImmediateAccessPoints {
95 data: HashMap<String, DynamicManagedLazy>,
96}
97
98impl ImmediateAccessPoints {
99 fn register<T>(&mut self, id: impl ToString, data: &mut T) -> Lifetime {
100 let result = Lifetime::default();
101 self.data
102 .insert(id.to_string(), DynamicManagedLazy::new(data, result.lazy()));
103 result
104 }
105
106 fn reset(&mut self) {
107 self.data.clear();
108 }
109
110 fn access<T>(&self, id: &str) -> ManagedLazy<T> {
111 self.data
112 .get(id)
113 .unwrap()
114 .clone()
115 .into_typed()
116 .ok()
117 .unwrap()
118 }
119}
120
121#[derive(PropsData, Default, Clone, Serialize, Deserialize)]
122#[props_data(raui_core::props::PropsData)]
123pub struct ImmediateHooks {
124 #[serde(default, skip)]
125 pre_hooks: Vec<fn(&mut WidgetContext)>,
126 #[serde(default, skip)]
127 post_hooks: Vec<fn(&mut WidgetContext)>,
128}
129
130impl ImmediateHooks {
131 pub fn with(mut self, pointer: fn(&mut WidgetContext)) -> Self {
132 self.pre_hooks.push(pointer);
133 self
134 }
135
136 pub fn with_post(mut self, pointer: fn(&mut WidgetContext)) -> Self {
137 self.post_hooks.push(pointer);
138 self
139 }
140}
141
142impl std::fmt::Debug for ImmediateHooks {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.debug_struct(stringify!(ImmediateHooks))
145 .finish_non_exhaustive()
146 }
147}
148
149macro_rules! impl_lifecycle_props {
150 ($($id:ident),+ $(,)?) => {
151 $(
152 #[derive(PropsData, Default, Clone, Serialize, Deserialize)]
153 #[props_data(raui_core::props::PropsData)]
154 pub struct $id {
155 #[serde(default, skip)]
156 callback: Option<Arc<dyn Fn() + Send + Sync>>,
157 }
158
159 impl $id {
160 pub fn new(callback: impl Fn() + Send + Sync + 'static) -> Self {
161 Self {
162 callback: Some(Arc::new(callback)),
163 }
164 }
165 }
166
167 impl std::fmt::Debug for $id {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct(stringify!($id)).finish_non_exhaustive()
170 }
171 }
172 )+
173 };
174}
175
176impl_lifecycle_props! {
177 ImmediateOnMount,
178 ImmediateOnChange,
179 ImmediateOnUnmount
180}
181
182pub fn use_state<T>(init: impl FnMut() -> T) -> ManagedLazy<T> {
183 STATES.with(|states| {
184 let states = states.borrow();
185 let mut states = states
186 .as_ref()
187 .unwrap_or_else(|| panic!("You must activate context first for `use_state` to work!"))
188 .borrow_mut();
189 states.alloc(init)
190 })
191}
192
193pub fn use_access<T>(id: &str) -> ManagedLazy<T> {
194 ACCESS_POINTS.with(|access_points| {
195 let access_points = access_points.borrow();
196 let access_points = access_points
197 .as_ref()
198 .unwrap_or_else(|| panic!("You must activate context first for `use_access` to work!"))
199 .borrow();
200 access_points.access(id)
201 })
202}
203
204pub fn use_stack_props<T: PropsData + Clone + 'static>() -> Option<T> {
205 PROPS_STACK.with(|props_stack| {
206 if let Some(props_stack) = props_stack.borrow().as_ref() {
207 for props in props_stack.borrow().iter().rev() {
208 if let Ok(props) = props.read_cloned::<T>() {
209 return Some(props);
210 }
211 }
212 }
213 None
214 })
215}
216
217pub fn use_effects<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
218 begin();
219 let result = f();
220 let node = end().pop().unwrap_or_default();
221 push(
222 make_widget!(immediate_effects_box)
223 .merge_props(props.into())
224 .named_slot("content", node),
225 );
226 result
227}
228
229pub fn register_access<T>(id: &str, data: &mut T) -> Lifetime {
230 ACCESS_POINTS.with(|access_points| {
231 let access_points = access_points.borrow();
232 let mut access_points = access_points
233 .as_ref()
234 .unwrap_or_else(|| panic!("You must activate context first for `use_access` to work!"))
235 .borrow_mut();
236 access_points.register(id, data)
237 })
238}
239
240pub fn begin() {
241 STACK.with(|stack| stack.borrow_mut().push(Default::default()));
242}
243
244pub fn end() -> Vec<WidgetNode> {
245 STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default())
246}
247
248pub fn push(widget: impl Into<WidgetNode>) {
249 STACK.with(|stack| {
250 if let Some(widgets) = stack.borrow_mut().last_mut() {
251 widgets.push(widget.into());
252 }
253 });
254}
255
256pub fn extend(iter: impl IntoIterator<Item = WidgetNode>) {
257 STACK.with(|stack| {
258 if let Some(widgets) = stack.borrow_mut().last_mut() {
259 widgets.extend(iter);
260 }
261 });
262}
263
264pub fn pop() -> WidgetNode {
265 STACK.with(|stack| {
266 stack
267 .borrow_mut()
268 .last_mut()
269 .and_then(|widgets| widgets.pop())
270 .unwrap_or_default()
271 })
272}
273
274pub fn reset() {
275 STACK.with(|stack| {
276 stack.borrow_mut().clear();
277 });
278 PROPS_STACK.with(|props_stack| {
279 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
280 props_stack.borrow_mut().clear();
281 }
282 });
283}
284
285pub fn list_component<R>(
286 widget: impl Into<WidgetComponent>,
287 props: impl Into<Props>,
288 mut f: impl FnMut() -> R,
289) -> R {
290 begin();
291 let result = f();
292 let widgets = end();
293 push(
294 widget
295 .into()
296 .merge_props(props.into())
297 .listed_slots(widgets),
298 );
299 result
300}
301
302pub fn slot_component<R>(
303 widget: impl Into<WidgetComponent>,
304 props: impl Into<Props>,
305 mut f: impl FnMut() -> R,
306) -> R {
307 begin();
308 let result = f();
309 let widgets = end();
310 let mut list_widgets = Vec::new();
311 let mut slot_widgets = Vec::new();
312 for widget in widgets {
313 if let Some(w) = widget.as_component() {
314 if let Some(name) = w.key.as_deref() {
315 slot_widgets.push((name.to_owned(), widget));
316 } else {
317 list_widgets.push(widget);
318 }
319 }
320 }
321 push(
322 widget
323 .into()
324 .merge_props(props.into())
325 .listed_slots(list_widgets)
326 .named_slots(slot_widgets),
327 );
328 result
329}
330
331pub fn content_component<R>(
332 widget: impl Into<WidgetComponent>,
333 content_name: &str,
334 props: impl Into<Props>,
335 mut f: impl FnMut() -> R,
336) -> R {
337 begin();
338 let result = f();
339 let node = end().pop().unwrap_or_default();
340 push(
341 widget
342 .into()
343 .merge_props(props.into())
344 .named_slot(content_name, node),
345 );
346 result
347}
348
349pub fn tuple<R>(mut f: impl FnMut() -> R) -> R {
350 begin();
351 let result = f();
352 let widgets = end();
353 push(WidgetNode::Tuple(widgets));
354 result
355}
356
357pub fn component(widget: impl Into<WidgetComponent>, props: impl Into<Props>) {
358 push(widget.into().merge_props(props.into()));
359}
360
361pub fn unit(widget: impl Into<WidgetUnitNode>) {
362 push(widget.into());
363}
364
365pub fn make_widgets(context: &ImmediateContext, mut f: impl FnMut()) -> Vec<WidgetNode> {
366 ImmediateContext::activate(context);
367 begin();
368 f();
369 let result = end();
370 ImmediateContext::deactivate();
371 result
372}
373
374pub trait ImmediateApply: Sized {
375 fn before(self) -> Self {
376 self
377 }
378
379 fn after(self) -> Self {
380 self
381 }
382
383 fn process(self, widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {
384 widgets
385 }
386}
387
388macro_rules! impl_tuple_immediate_apply {
389 ($($id:ident),+ $(,)?) => {
390 #[allow(non_snake_case)]
391 impl<$($id: $crate::ImmediateApply),+> $crate::ImmediateApply for ($($id,)+) {
392 fn before(self) -> Self {
393 let ($($id,)+) = self;
394 (
395 $(
396 $id.before(),
397 )+
398 )
399 }
400
401 fn after(self) -> Self {
402 let ($($id,)+) = self;
403 (
404 $(
405 $id.after(),
406 )+
407 )
408 }
409
410 fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {
411 let ($($id,)+) = self;
412 $(
413 widgets = $id.process(widgets);
414 )+
415 widgets
416 }
417 }
418 };
419}
420
421impl_tuple_immediate_apply!(A);
422impl_tuple_immediate_apply!(A, B);
423impl_tuple_immediate_apply!(A, B, C);
424impl_tuple_immediate_apply!(A, B, C, D);
425impl_tuple_immediate_apply!(A, B, C, D, E);
426impl_tuple_immediate_apply!(A, B, C, D, E, F);
427impl_tuple_immediate_apply!(A, B, C, D, E, F, G);
428impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H);
429impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I);
430impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J);
431impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K);
432impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L);
433impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M);
434impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
435impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
436impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
437impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
438impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
439impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
440impl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
441impl_tuple_immediate_apply!(
442 A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U
443);
444impl_tuple_immediate_apply!(
445 A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V
446);
447impl_tuple_immediate_apply!(
448 A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X
449);
450impl_tuple_immediate_apply!(
451 A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y
452);
453impl_tuple_immediate_apply!(
454 A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y, Z
455);
456
457pub struct ImKey<T: ToString>(pub T);
458
459impl<T: ToString> ImmediateApply for ImKey<T> {
460 fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {
461 let key = self.0.to_string();
462 match widgets.len() {
463 0 => {}
464 1 => {
465 if let WidgetNode::Component(widget) = &mut widgets[0] {
466 widget.key = Some(key);
467 }
468 }
469 _ => {
470 for (index, widget) in widgets.iter_mut().enumerate() {
471 if let WidgetNode::Component(widget) = widget {
472 widget.key = Some(format!("{key}-{index}"));
473 }
474 }
475 }
476 }
477 widgets
478 }
479}
480
481pub struct ImIdRef<T: Into<WidgetRef>>(pub T);
482
483impl<T: Into<WidgetRef>> ImmediateApply for ImIdRef<T> {
484 fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {
485 let idref = self.0.into();
486 for widget in &mut widgets {
487 if let WidgetNode::Component(widget) = widget {
488 widget.idref = Some(idref.clone());
489 }
490 }
491 widgets
492 }
493}
494
495pub struct ImProps<T: Into<Props>>(pub T);
496
497impl<T: Into<Props>> ImmediateApply for ImProps<T> {
498 fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {
499 let props = self.0.into();
500 for widget in &mut widgets {
501 if let Some(widget) = widget.props_mut() {
502 widget.merge_from(props.clone());
503 }
504 }
505 widgets
506 }
507}
508
509pub struct ImSharedProps<T: Into<Props>>(pub T);
510
511impl<T: Into<Props>> ImmediateApply for ImSharedProps<T> {
512 fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {
513 let props = self.0.into();
514 for widget in &mut widgets {
515 if let Some(widget) = widget.shared_props_mut() {
516 widget.merge_from(props.clone());
517 }
518 }
519 widgets
520 }
521}
522
523pub enum ImStackProps<T: Into<Props>> {
524 Props(T),
525 Done,
526}
527
528impl<T: Into<Props>> ImStackProps<T> {
529 pub fn new(props: T) -> Self {
530 Self::Props(props)
531 }
532}
533
534impl<T: Into<Props>> ImmediateApply for ImStackProps<T> {
535 fn before(self) -> Self {
536 if let Self::Props(props) = self {
537 let props = props.into();
538 PROPS_STACK.with(|props_stack| {
539 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
540 props_stack.borrow_mut().push(props.clone());
541 }
542 });
543 }
544 Self::Done
545 }
546
547 fn after(self) -> Self {
548 if let Self::Done = self {
549 PROPS_STACK.with(|props_stack| {
550 if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
551 props_stack.borrow_mut().pop();
552 }
553 });
554 }
555 self
556 }
557}
558
559pub fn apply<R>(items: impl ImmediateApply, mut f: impl FnMut() -> R) -> R {
560 begin();
561 let items = items.before();
562 let result = f();
563 let items = items.after();
564 let widgets = end();
565 let widgets = items.process(widgets);
566 extend(widgets);
567 result
568}
569
570#[deprecated(note = "Use `apply` with `ImKey` instead")]
571pub fn apply_key<R>(key: impl ToString, f: impl FnMut() -> R) -> R {
572 apply(ImKey(key), f)
573}
574
575#[deprecated(note = "Use `apply` with `ImIdRef` instead")]
576pub fn apply_idref<R>(key: impl Into<WidgetRef>, f: impl FnMut() -> R) -> R {
577 apply(ImIdRef(key), f)
578}
579
580#[deprecated(note = "Use `apply` with `ImProps` instead")]
581pub fn apply_props<R>(props: impl Into<Props>, f: impl FnMut() -> R) -> R {
582 apply(ImProps(props), f)
583}
584
585#[deprecated(note = "Use `apply` with `ImSharedProps` instead")]
586pub fn apply_shared_props<R>(props: impl Into<Props>, f: impl FnMut() -> R) -> R {
587 apply(ImSharedProps(props), f)
588}
589
590#[deprecated(note = "Use `apply` with `ImStackProps` instead")]
591pub fn stack_props<R>(props: impl Into<Props>, f: impl FnMut() -> R) -> R {
592 apply(ImStackProps::new(props), f)
593}
594
595mod internal {
596 use super::*;
597 use raui_core::widget::unit::area::AreaBoxNode;
598
599 pub(crate) fn immediate_effects_box(mut ctx: WidgetContext) -> WidgetNode {
600 let hooks = ctx.props.read_cloned_or_default::<ImmediateHooks>();
601 for hook in &hooks.pre_hooks {
602 hook(&mut ctx);
603 }
604
605 if let Ok(event) = ctx.props.read::<ImmediateOnMount>()
606 && let Some(callback) = event.callback.as_ref()
607 {
608 let callback = callback.clone();
609 ctx.life_cycle.mount(move |_| {
610 callback();
611 });
612 }
613 if let Ok(event) = ctx.props.read::<ImmediateOnChange>()
614 && let Some(callback) = event.callback.as_ref()
615 {
616 let callback = callback.clone();
617 ctx.life_cycle.change(move |_| {
618 callback();
619 });
620 }
621 if let Ok(event) = ctx.props.read::<ImmediateOnUnmount>()
622 && let Some(callback) = event.callback.as_ref()
623 {
624 let callback = callback.clone();
625 ctx.life_cycle.unmount(move |_| {
626 callback();
627 });
628 }
629
630 let content = ctx.named_slots.remove("content").unwrap_or_default();
631
632 let result = AreaBoxNode {
633 id: ctx.id.to_owned(),
634 slot: Box::new(content),
635 };
636 for hook in &hooks.post_hooks {
637 hook(&mut ctx);
638 }
639 result.into()
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use raui_core::widget::component::image_box::{ImageBoxProps, image_box};
646
647 use super::*;
648
649 fn run(frame: usize) {
650 let show_slider = use_state(|| false);
651 let mut show_slider = show_slider.write().unwrap();
652
653 let show_text_field = use_state(|| false);
654 let mut show_text_field = show_text_field.write().unwrap();
655
656 if frame == 1 {
657 *show_slider = true;
658 } else if frame == 3 {
659 *show_text_field = true;
660 } else if frame == 5 {
661 *show_slider = false;
662 } else if frame == 7 {
663 *show_text_field = false;
664 } else if frame == 9 {
665 *show_slider = true;
666 *show_text_field = true;
667 }
668
669 println!(
670 "* #{} | HOVERED: {} | CLICKED: {}",
671 frame, *show_slider, *show_text_field
672 );
673
674 if *show_slider {
675 slider();
676 }
677 if *show_text_field {
678 text_field();
679 }
680 }
681
682 fn slider() {
683 let value = use_state(|| 0.0);
684 let mut state = value.write().unwrap();
685
686 *state += 0.1;
687 println!("* SLIDER VALUE: {}", *state);
688 }
689
690 fn text_field() {
691 let text = use_state(String::default);
692 let mut text = text.write().unwrap();
693
694 text.push('z');
695
696 println!("* TEXT FIELD: {}", text.as_str());
697 }
698
699 #[test]
700 fn test_use_state() {
701 let context = ImmediateContext::default();
702 for frame in 0..12 {
703 ImmediateContext::activate(&context);
704 run(frame);
705 ImmediateContext::deactivate();
706 }
707 }
708
709 #[test]
710 fn test_apply() {
711 let context = ImmediateContext::default();
712 ImmediateContext::activate(&context);
713 begin();
714
715 apply(
716 (
717 ImKey("image"),
718 ImProps(ImageBoxProps::colored(Default::default())),
719 ),
720 || {
721 component(make_widget!(image_box), ());
722 },
723 );
724
725 let widgets = end();
726 ImmediateContext::deactivate();
727
728 assert_eq!(widgets.len(), 1);
729 if let WidgetNode::Component(component) = &widgets[0] {
730 assert_eq!(component.key.as_deref(), Some("image"));
731 assert!(component.props.has::<ImageBoxProps>());
732 } else {
733 panic!("Expected a component node");
734 }
735 }
736}