raui_immediate/
lib.rs

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}