raui_immediate_widgets/
lib.rs

1use raui_immediate::*;
2
3#[allow(ambiguous_glob_reexports)]
4pub mod prelude {
5    pub use crate::{
6        core::*,
7        core::{containers::*, interactive::*},
8        material::*,
9        material::{containers::*, interactive::*},
10    };
11}
12
13macro_rules! impl_list_components {
14    ($($name:ident),+ $(,)?) => {
15        $(
16            pub fn $name<R>(
17                props: impl Into<raui_core::props::Props>,
18                f: impl FnMut() -> R,
19            ) -> R {
20                use raui_core::prelude::*;
21                #[allow(unused_imports)]
22                use raui_material::prelude::*;
23                crate::list_component(make_widget!($name), props, f)
24            }
25        )+
26    };
27}
28
29macro_rules! impl_content_components {
30    ($content:literal : $($name:ident),+ $(,)?) => {
31        $(
32            pub fn $name<R>(
33                props: impl Into<raui_core::props::Props>,
34                f: impl FnMut() -> R,
35            ) -> R {
36                use raui_core::prelude::*;
37                #[allow(unused_imports)]
38                use raui_material::prelude::*;
39                crate::content_component(make_widget!($name), $content, props, f)
40            }
41        )+
42    };
43}
44
45macro_rules! impl_components {
46    ($($name:ident),+ $(,)?) => {
47        $(
48            pub fn $name(
49                props: impl Into<raui_core::props::Props>,
50            ) {
51                use raui_core::prelude::*;
52                #[allow(unused_imports)]
53                use raui_material::prelude::*;
54                crate::component(make_widget!($name), props)
55            }
56        )+
57    };
58}
59
60pub mod core {
61    pub use raui_core::widget::{
62        component::{image_box::ImageBoxProps, space_box::SpaceBoxProps, text_box::TextBoxProps},
63        unit::{
64            content::{ContentBoxItem, ContentBoxItemLayout},
65            flex::{FlexBoxItem, FlexBoxItemLayout},
66            grid::{GridBoxItem, GridBoxItemLayout},
67            image::{
68                ImageBoxAspectRatio, ImageBoxColor, ImageBoxFrame, ImageBoxImage,
69                ImageBoxImageScaling, ImageBoxMaterial, ImageBoxProcedural, ImageBoxSizeValue,
70            },
71            size::SizeBoxSizeValue,
72            text::{
73                TextBoxDirection, TextBoxFont, TextBoxHorizontalAlign, TextBoxSizeValue,
74                TextBoxVerticalAlign,
75            },
76        },
77        utils::*,
78    };
79
80    impl_components! {
81        image_box,
82        text_box,
83        space_box,
84    }
85
86    pub mod containers {
87        pub use raui_core::widget::component::containers::{
88            anchor_box::{AnchorNotifyProps, AnchorProps, PivotBoxProps},
89            content_box::ContentBoxProps,
90            context_box::ContextBoxProps,
91            flex_box::FlexBoxProps,
92            grid_box::GridBoxProps,
93            hidden_box::HiddenBoxProps,
94            horizontal_box::HorizontalBoxProps,
95            portal_box::PortalsContainer,
96            scroll_box::ScrollBoxOwner,
97            size_box::SizeBoxProps,
98            switch_box::SwitchBoxProps,
99            tabs_box::{TabPlateProps, TabsBoxProps, TabsBoxTabsLocation},
100            variant_box::VariantBoxProps,
101            vertical_box::VerticalBoxProps,
102            wrap_box::WrapBoxProps,
103        };
104
105        impl_content_components! {
106            "content":
107            anchor_box,
108            pivot_box,
109            context_box,
110            portals_context_box,
111            hidden_box,
112            portal_box,
113            size_box,
114            tooltip_box,
115            portals_tooltip_box,
116            wrap_box,
117        }
118
119        impl_list_components! {
120            content_box,
121            nav_content_box,
122            flex_box,
123            nav_flex_box,
124            grid_box,
125            nav_grid_box,
126            horizontal_box,
127            nav_horizontal_box,
128            nav_scroll_box,
129            nav_scroll_box_content,
130            nav_scroll_box_side_scrollbars,
131            switch_box,
132            nav_switch_box,
133            nav_tabs_box,
134            variant_box,
135            nav_vertical_box,
136            vertical_box,
137        }
138    }
139
140    pub mod interactive {
141        use raui_core::{
142            make_widget,
143            props::Props,
144            widget::component::interactive::{
145                options_view::OptionsViewProxy, slider_view::SliderViewProxy,
146            },
147        };
148        use raui_immediate::{begin, end, pop, push, use_state};
149        use std::str::FromStr;
150
151        pub use raui_core::widget::component::interactive::{
152            button::{ButtonNotifyProps, ButtonProps},
153            input_field::{
154                TextInput, TextInputControlNotifyProps, TextInputMode, TextInputNotifyProps,
155                TextInputProps, TextInputState,
156            },
157            navigation::{
158                NavContainerActive, NavDirection, NavItemActive, NavJump, NavJumpActive,
159                NavJumpLooped, NavJumpMapProps, NavJumpMode, NavScroll, NavTextChange,
160                NavTrackingActive, NavType,
161            },
162            scroll_view::{ScrollViewNotifyProps, ScrollViewRange, ScrollViewState},
163        };
164
165        #[derive(Debug, Default, Copy, Clone)]
166        pub struct ImmediateButton {
167            pub state: ButtonProps,
168            pub prev: ButtonProps,
169        }
170
171        impl ImmediateButton {
172            pub fn select_start(&self) -> bool {
173                !self.prev.selected && self.state.selected
174            }
175
176            pub fn select_stop(&self) -> bool {
177                self.prev.selected && !self.state.selected
178            }
179
180            pub fn select_changed(&self) -> bool {
181                self.prev.selected != self.state.selected
182            }
183
184            pub fn trigger_start(&self) -> bool {
185                !self.prev.trigger && self.state.trigger
186            }
187
188            pub fn trigger_stop(&self) -> bool {
189                self.prev.trigger && !self.state.trigger
190            }
191
192            pub fn trigger_changed(&self) -> bool {
193                self.prev.trigger != self.state.trigger
194            }
195
196            pub fn context_start(&self) -> bool {
197                !self.prev.context && self.state.context
198            }
199
200            pub fn context_stop(&self) -> bool {
201                self.prev.context && !self.state.context
202            }
203
204            pub fn context_changed(&self) -> bool {
205                self.prev.context != self.state.context
206            }
207        }
208
209        impl_content_components! {
210            "content":
211            navigation_barrier,
212        }
213
214        pub fn button(
215            props: impl Into<Props>,
216            mut f: impl FnMut(ImmediateButton),
217        ) -> ImmediateButton {
218            use crate::internal::*;
219            use raui_core::prelude::*;
220            let state = use_state(ImmediateButton::default);
221            let result = state.read().unwrap().to_owned();
222            begin();
223            f(result);
224            let node = end().pop().unwrap_or_default();
225            push(
226                make_widget!(immediate_button)
227                    .with_props(ImmediateButtonProps { state: Some(state) })
228                    .merge_props(props.into())
229                    .named_slot("content", node),
230            );
231            result
232        }
233
234        pub fn text_input<T: ToString + FromStr + Send + Sync>(
235            value: &T,
236            props: impl Into<Props>,
237            mut f: impl FnMut(&str, TextInputState),
238        ) -> (Option<T>, TextInputState) {
239            use crate::internal::*;
240            use raui_core::prelude::*;
241            let content = use_state(|| value.to_string());
242            let props = props.into();
243            let TextInputProps { allow_new_line, .. } = props.read_cloned_or_default();
244            let text_state = use_state(TextInputState::default);
245            let text_result = text_state.read().unwrap().to_owned();
246            if !text_result.focused {
247                *content.write().unwrap() = value.to_string();
248            }
249            let result = content.read().unwrap().to_string();
250            begin();
251            f(&result, text_result);
252            let node = end().pop().unwrap_or_default();
253            push(
254                make_widget!(immediate_text_input)
255                    .with_props(ImmediateTextInputProps {
256                        state: Some(text_state),
257                    })
258                    .merge_props(props)
259                    .with_props(TextInputProps {
260                        allow_new_line,
261                        text: Some(content.into()),
262                    })
263                    .named_slot("content", node),
264            );
265            (result.parse().ok(), text_result)
266        }
267
268        pub fn input_field<T: ToString + FromStr + Send + Sync>(
269            value: &T,
270            props: impl Into<Props>,
271            mut f: impl FnMut(&str, TextInputState, ImmediateButton),
272        ) -> (Option<T>, TextInputState, ImmediateButton) {
273            use crate::internal::*;
274            use raui_core::prelude::*;
275            let content = use_state(|| value.to_string());
276            let props = props.into();
277            let TextInputProps { allow_new_line, .. } = props.read_cloned_or_default();
278            let text_state = use_state(TextInputState::default);
279            let text_result = text_state.read().unwrap().to_owned();
280            let button_state = use_state(ImmediateButton::default);
281            let button_result = button_state.read().unwrap().to_owned();
282            if !text_result.focused {
283                *content.write().unwrap() = value.to_string();
284            }
285            let result = content.read().unwrap().to_string();
286            begin();
287            f(&result, text_result, button_result);
288            let node = end().pop().unwrap_or_default();
289            push(
290                make_widget!(immediate_input_field)
291                    .with_props(ImmediateTextInputProps {
292                        state: Some(text_state),
293                    })
294                    .with_props(ImmediateButtonProps {
295                        state: Some(button_state),
296                    })
297                    .merge_props(props)
298                    .with_props(TextInputProps {
299                        allow_new_line,
300                        text: Some(content.into()),
301                    })
302                    .named_slot("content", node),
303            );
304            (result.parse().ok(), text_result, button_result)
305        }
306
307        pub fn slider_view<T: SliderViewProxy + Clone + 'static>(
308            value: T,
309            props: impl Into<Props>,
310            mut f: impl FnMut(&T, ImmediateButton),
311        ) -> (T, ImmediateButton) {
312            use crate::internal::*;
313            use raui_core::prelude::*;
314            let content = use_state(|| value.to_owned());
315            let props = props.into();
316            let SliderViewProps {
317                from,
318                to,
319                direction,
320                ..
321            } = props.read_cloned_or_default();
322            let button_state = use_state(ImmediateButton::default);
323            let button_result = button_state.read().unwrap().to_owned();
324            let result = content.read().unwrap().to_owned();
325            begin();
326            f(&result, button_result);
327            let node = end().pop().unwrap_or_default();
328            push(
329                make_widget!(immediate_slider_view)
330                    .with_props(ImmediateButtonProps {
331                        state: Some(button_state),
332                    })
333                    .merge_props(props)
334                    .with_props(SliderViewProps {
335                        input: Some(content.into()),
336                        from,
337                        to,
338                        direction,
339                    })
340                    .named_slot("content", node),
341            );
342            (result, button_result)
343        }
344
345        pub fn options_view<T: OptionsViewProxy + Clone + 'static>(
346            value: T,
347            props: impl Into<Props>,
348            mut f_items: impl FnMut(&T),
349            mut f_content: impl FnMut(),
350        ) -> T {
351            use raui_core::prelude::*;
352            let content = use_state(|| value.to_owned());
353            let props = props.into();
354            let result = content.read().unwrap().to_owned();
355            begin();
356            f_items(&result);
357            let nodes = end();
358            begin();
359            f_content();
360            let node = pop();
361            push(
362                make_widget!(raui_core::widget::component::interactive::options_view::options_view)
363                    .merge_props(props)
364                    .with_props(OptionsViewProps {
365                        input: Some(content.into()),
366                    })
367                    .named_slot("content", node)
368                    .listed_slots(nodes),
369            );
370            result
371        }
372    }
373}
374
375pub mod material {
376    pub use raui_material::theme;
377
378    pub use raui_material::component::{
379        icon_paper::{IconImage, IconPaperProps},
380        switch_paper::SwitchPaperProps,
381        text_paper::TextPaperProps,
382    };
383
384    impl_components! {
385        icon_paper,
386        switch_paper,
387        text_paper,
388    }
389
390    pub mod containers {
391        pub use raui_material::component::containers::{
392            context_paper::ContextPaperProps,
393            modal_paper::ModalPaperProps,
394            paper::{PaperContentLayoutProps, PaperProps},
395            scroll_paper::SideScrollbarsPaperProps,
396            tooltip_paper::TooltipPaperProps,
397        };
398
399        impl_list_components! {
400            context_paper,
401            flex_paper,
402            nav_flex_paper,
403            grid_paper,
404            nav_grid_paper,
405            horizontal_paper,
406            nav_horizontal_paper,
407            modal_paper,
408            paper,
409            scroll_paper,
410            scroll_paper_side_scrollbars,
411            text_tooltip_paper,
412            tooltip_paper,
413            vertical_paper,
414            nav_vertical_paper,
415            wrap_paper,
416        }
417    }
418
419    pub mod interactive {
420        use crate::core::interactive::ImmediateButton;
421        use raui_core::{
422            props::Props,
423            widget::component::interactive::{
424                input_field::TextInputState, slider_view::SliderViewProxy,
425            },
426        };
427        use raui_immediate::{begin, end, push, use_state};
428        use std::str::FromStr;
429
430        pub use raui_material::component::interactive::{
431            button_paper::ButtonPaperOverrideStyle, text_field_paper::TextFieldPaperProps,
432        };
433
434        pub fn button_paper(
435            props: impl Into<Props>,
436            mut f: impl FnMut(ImmediateButton),
437        ) -> ImmediateButton {
438            use crate::internal::*;
439            use raui_core::prelude::*;
440            let state = use_state(ImmediateButton::default);
441            let result = state.read().unwrap().to_owned();
442            begin();
443            f(result);
444            let node = end().pop().unwrap_or_default();
445            push(
446                make_widget!(immediate_button_paper)
447                    .with_props(ImmediateButtonProps { state: Some(state) })
448                    .merge_props(props.into())
449                    .named_slot("content", node),
450            );
451            result
452        }
453
454        pub fn icon_button_paper(props: impl Into<Props>) -> ImmediateButton {
455            use crate::internal::*;
456            use raui_core::prelude::*;
457            let state = use_state(ImmediateButton::default);
458            let result = state.read().unwrap().to_owned();
459            push(
460                make_widget!(immediate_icon_button_paper)
461                    .with_props(ImmediateButtonProps { state: Some(state) })
462                    .merge_props(props.into()),
463            );
464            result
465        }
466
467        pub fn switch_button_paper(props: impl Into<Props>) -> ImmediateButton {
468            use crate::internal::*;
469            use raui_core::prelude::*;
470            let state = use_state(ImmediateButton::default);
471            let result = state.read().unwrap().to_owned();
472            push(
473                make_widget!(immediate_switch_button_paper)
474                    .with_props(ImmediateButtonProps { state: Some(state) })
475                    .merge_props(props.into()),
476            );
477            result
478        }
479
480        pub fn text_button_paper(props: impl Into<Props>) -> ImmediateButton {
481            use crate::internal::*;
482            use raui_core::prelude::*;
483            let state = use_state(ImmediateButton::default);
484            let result = state.read().unwrap().to_owned();
485            push(
486                make_widget!(immediate_text_button_paper)
487                    .with_props(ImmediateButtonProps { state: Some(state) })
488                    .merge_props(props.into()),
489            );
490            result
491        }
492
493        pub fn text_field_paper<T: ToString + FromStr + Send + Sync>(
494            value: &T,
495            props: impl Into<Props>,
496        ) -> (Option<T>, TextInputState, ImmediateButton) {
497            use crate::internal::*;
498            use raui_core::prelude::*;
499            let content = use_state(|| value.to_string());
500            let props = props.into();
501            let TextInputProps { allow_new_line, .. } =
502                props.read_cloned_or_default::<TextInputProps>();
503            let text_state = use_state(TextInputState::default);
504            let text_result = text_state.read().unwrap().to_owned();
505            let button_state = use_state(ImmediateButton::default);
506            let button_result = button_state.read().unwrap().to_owned();
507            if !text_result.focused {
508                *content.write().unwrap() = value.to_string();
509            }
510            let result = content.read().unwrap().to_string();
511            push(
512                make_widget!(immediate_text_field_paper)
513                    .with_props(ImmediateTextInputProps {
514                        state: Some(text_state),
515                    })
516                    .with_props(ImmediateButtonProps {
517                        state: Some(button_state),
518                    })
519                    .merge_props(props)
520                    .with_props(TextInputProps {
521                        allow_new_line,
522                        text: Some(content.into()),
523                    }),
524            );
525            (result.parse().ok(), text_result, button_result)
526        }
527
528        pub fn slider_paper<T: SliderViewProxy + Clone + 'static>(
529            value: T,
530            props: impl Into<Props>,
531            mut f: impl FnMut(&T, ImmediateButton),
532        ) -> (T, ImmediateButton) {
533            use crate::internal::*;
534            use raui_core::prelude::*;
535            let content = use_state(|| value.to_owned());
536            let props = props.into();
537            let SliderViewProps {
538                from,
539                to,
540                direction,
541                ..
542            } = props.read_cloned_or_default();
543            let button_state = use_state(ImmediateButton::default);
544            let button_result = button_state.read().unwrap().to_owned();
545            let result = content.read().unwrap().to_owned();
546            begin();
547            f(&result, button_result);
548            let node = end().pop().unwrap_or_default();
549            push(
550                make_widget!(immediate_slider_paper)
551                    .with_props(ImmediateButtonProps {
552                        state: Some(button_state),
553                    })
554                    .merge_props(props)
555                    .with_props(SliderViewProps {
556                        input: Some(content.into()),
557                        from,
558                        to,
559                        direction,
560                    })
561                    .named_slot("content", node),
562            );
563            (result, button_result)
564        }
565
566        pub fn numeric_slider_paper<T: SliderViewProxy + Clone + 'static>(
567            value: T,
568            props: impl Into<Props>,
569        ) -> (T, ImmediateButton) {
570            use crate::internal::*;
571            use raui_core::prelude::*;
572            let content = use_state(|| value.to_owned());
573            let props = props.into();
574            let SliderViewProps {
575                from,
576                to,
577                direction,
578                ..
579            } = props.read_cloned_or_default();
580            let button_state = use_state(ImmediateButton::default);
581            let button_result = button_state.read().unwrap().to_owned();
582            let result = content.read().unwrap().to_owned();
583            push(
584                make_widget!(immediate_numeric_slider_paper)
585                    .with_props(ImmediateButtonProps {
586                        state: Some(button_state),
587                    })
588                    .merge_props(props)
589                    .with_props(SliderViewProps {
590                        input: Some(content.into()),
591                        from,
592                        to,
593                        direction,
594                    }),
595            );
596            (result, button_result)
597        }
598    }
599}
600
601mod internal {
602    use super::core::interactive::ImmediateButton;
603    use raui_core::prelude::*;
604    use raui_material::prelude::*;
605    use serde::{Deserialize, Serialize};
606
607    #[derive(PropsData, Default, Clone, Serialize, Deserialize)]
608    #[props_data(raui_core::props::PropsData)]
609    pub struct ImmediateButtonProps {
610        #[serde(default, skip)]
611        pub state: Option<ManagedLazy<ImmediateButton>>,
612    }
613
614    impl std::fmt::Debug for ImmediateButtonProps {
615        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
616            f.debug_struct("ImmediateButtonProps")
617                .field(
618                    "state",
619                    &self
620                        .state
621                        .as_ref()
622                        .and_then(|state| state.read())
623                        .map(|state| *state),
624                )
625                .finish()
626        }
627    }
628
629    #[derive(PropsData, Default, Clone, Serialize, Deserialize)]
630    #[props_data(raui_core::props::PropsData)]
631    pub struct ImmediateTextInputProps {
632        #[serde(default, skip)]
633        pub state: Option<ManagedLazy<TextInputState>>,
634    }
635
636    impl std::fmt::Debug for ImmediateTextInputProps {
637        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638            f.debug_struct("ImmediateTextInputProps")
639                .finish_non_exhaustive()
640        }
641    }
642
643    fn use_immediate_button(ctx: &mut WidgetContext) {
644        ctx.props.write(ButtonNotifyProps(ctx.id.to_owned().into()));
645
646        if let Ok(props) = ctx.props.read::<ImmediateButtonProps>() {
647            let state = props.state.as_ref().unwrap();
648            let mut state = state.write().unwrap();
649            state.prev = state.state;
650        }
651
652        ctx.life_cycle.change(|ctx| {
653            if let Ok(props) = ctx.props.read::<ImmediateButtonProps>() {
654                if let Some(state) = props.state.as_ref() {
655                    if let Some(mut state) = state.write() {
656                        for msg in ctx.messenger.messages {
657                            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {
658                                state.state = msg.state;
659                            }
660                        }
661                    }
662                }
663            }
664        });
665    }
666
667    fn use_immediate_text_input(ctx: &mut WidgetContext) {
668        if let Ok(data) = ctx.state.read_cloned::<TextInputState>() {
669            if let Ok(props) = ctx.props.read::<ImmediateTextInputProps>() {
670                let state = props.state.as_ref().unwrap();
671                let mut state = state.write().unwrap();
672                *state = data;
673            }
674        }
675    }
676
677    #[pre_hooks(use_immediate_button)]
678    pub(crate) fn immediate_button(mut ctx: WidgetContext) -> WidgetNode {
679        button(ctx)
680    }
681
682    #[pre_hooks(use_immediate_text_input)]
683    pub(crate) fn immediate_text_input(mut ctx: WidgetContext) -> WidgetNode {
684        text_input(ctx)
685    }
686
687    #[pre_hooks(use_immediate_text_input, use_immediate_button)]
688    pub(crate) fn immediate_input_field(mut ctx: WidgetContext) -> WidgetNode {
689        input_field(ctx)
690    }
691
692    #[pre_hooks(use_immediate_button)]
693    pub(crate) fn immediate_slider_view(mut ctx: WidgetContext) -> WidgetNode {
694        slider_view(ctx)
695    }
696
697    pub(crate) fn immediate_button_paper(ctx: WidgetContext) -> WidgetNode {
698        button_paper_impl(make_widget!(immediate_button), ctx)
699    }
700
701    pub(crate) fn immediate_icon_button_paper(ctx: WidgetContext) -> WidgetNode {
702        icon_button_paper_impl(make_widget!(immediate_button_paper), ctx)
703    }
704
705    pub(crate) fn immediate_switch_button_paper(ctx: WidgetContext) -> WidgetNode {
706        switch_button_paper_impl(make_widget!(immediate_button_paper), ctx)
707    }
708
709    pub(crate) fn immediate_text_button_paper(ctx: WidgetContext) -> WidgetNode {
710        text_button_paper_impl(make_widget!(immediate_button_paper), ctx)
711    }
712
713    pub(crate) fn immediate_text_field_paper(ctx: WidgetContext) -> WidgetNode {
714        text_field_paper_impl(make_widget!(immediate_input_field), ctx)
715    }
716
717    pub(crate) fn immediate_slider_paper(ctx: WidgetContext) -> WidgetNode {
718        slider_paper_impl(make_widget!(immediate_slider_view), ctx)
719    }
720
721    pub(crate) fn immediate_numeric_slider_paper(ctx: WidgetContext) -> WidgetNode {
722        numeric_slider_paper_impl(make_widget!(immediate_slider_paper), ctx)
723    }
724}