raui-core 0.28.1

Renderer Agnostic User Interface
Documentation
use crate::{
    props::Props,
    unpack_named_slots, widget,
    widget::{
        component::{
            containers::content_box::content_box,
            image_box::{image_box, ImageBoxProps},
            interactive::{
                button::{button, ButtonNotifyMessage, ButtonNotifyProps},
                navigation::{
                    use_nav_container_active, use_nav_item, use_nav_item_active,
                    use_nav_scroll_view_content, NavButtonTrackingActive, NavItemActive, NavJump,
                    NavScroll, NavSignal,
                },
                scroll_view::{use_scroll_view, ScrollViewState},
            },
            use_resize_listener, ResizeListenerSignal,
        },
        unit::{area::AreaBoxNode, content::ContentBoxItemLayout, image::ImageBoxMaterial},
        utils::{Rect, Vec2},
        WidgetId,
    },
    widget_component, widget_hook, Scalar,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ScrollBoxOwner(
    #[serde(default)]
    #[serde(skip_serializing_if = "WidgetId::is_none")]
    pub WidgetId,
);
implement_props_data!(ScrollBoxOwner);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SideScrollbarsProps {
    #[serde(default)]
    pub size: Scalar,
    #[serde(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub back_material: Option<ImageBoxMaterial>,
    #[serde(default)]
    pub front_material: ImageBoxMaterial,
}
implement_props_data!(SideScrollbarsProps);

impl Default for SideScrollbarsProps {
    fn default() -> Self {
        Self {
            size: 10.0,
            back_material: None,
            front_material: Default::default(),
        }
    }
}

#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
pub struct SideScrollbarsState {
    pub horizontal: Scalar,
    pub vertical: Scalar,
}
implement_props_data!(SideScrollbarsState);

widget_hook! {
    use_nav_scroll_box_content(life_cycle) {
        life_cycle.change(|context| {
            for msg in context.messenger.messages {
                if let Some(ResizeListenerSignal::Change) = msg.as_any().downcast_ref() {
                    if let Ok(data) = context.props.read::<ScrollBoxOwner>() {
                        context.messenger.write(data.0.to_owned(), ResizeListenerSignal::Change);
                    }
                }
            }
        });
    }
}

widget_component! {
    nav_scroll_box_content(id, named_slots) [
        use_resize_listener,
        use_nav_item_active,
        use_nav_container_active,
        use_nav_scroll_view_content,
        use_nav_scroll_box_content,
    ] {
        unpack_named_slots!(named_slots => content);

        widget! {{{
            AreaBoxNode {
                id: id.to_owned(),
                slot: Box::new(content),
            }
        }}}
    }
}

widget_hook! {
    use_nav_scroll_box(life_cycle) {
        life_cycle.change(|context| {
            for msg in context.messenger.messages {
                if let Some(ResizeListenerSignal::Change) = msg.as_any().downcast_ref() {
                    if let Ok(data) = context.state.read::<ScrollViewState>() {
                        context.signals.write(NavSignal::Jump(
                            NavJump::Scroll(NavScroll::Factor(data.value, false)),
                        ));
                    }
                }
            }
        });
    }
}

widget_component! {
    pub nav_scroll_box(id, key, props, state, named_slots) [
        use_resize_listener,
        use_nav_item,
        use_nav_container_active,
        use_scroll_view,
        use_nav_scroll_box,
    ] {
        unpack_named_slots!(named_slots => {content, scrollbars});

        let scroll_props = state.read_cloned_or_default::<ScrollViewState>();
        let content_props = Props::new(ContentBoxItemLayout {
            align: scroll_props.value,
            ..Default::default()
        })
        .with(ScrollBoxOwner(id.to_owned()));
        if let Some(props) = scrollbars.props_mut() {
            props.write(ScrollBoxOwner(id.to_owned()));
            props.write(scroll_props);
        }

        widget! {
            (#{key} content_box: {props.clone()} [
                (#{"content"} nav_scroll_box_content: {content_props} {
                    content = {content}
                })
                {scrollbars}
            ])
        }
    }
}

widget_hook! {
    use_nav_scroll_box_side_scrollbars(life_cycle) {
        life_cycle.mount(|context| {
            drop(context.state.write_with(SideScrollbarsState::default()));
        });

        life_cycle.change(|context| {
            for msg in context.messenger.messages {
                if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {
                    if msg.state.selected && (msg.state.trigger || msg.state.context) {
                        let mut dirty = false;
                        let mut data = context.state.read_cloned_or_default::<SideScrollbarsState>();
                        if msg.sender.key() == "hbar" {
                            data.horizontal = msg.state.pointer.x;
                            dirty = true;
                        } else if msg.sender.key() == "vbar" {
                            data.vertical = msg.state.pointer.y;
                            dirty = true;
                        }
                        if dirty {
                            let view = context.props
                                .read_cloned_or_default::<ScrollBoxOwner>().0;
                            let pos = Vec2 {
                                x: data.horizontal,
                                y: data.vertical,
                            };
                            context.signals.write(NavSignal::Jump(
                                NavJump::Scroll(NavScroll::DirectFactor(view.into(), pos, false)),
                            ));
                            drop(context.state.write_with(data));
                        }
                    }
                }
            }
        });
    }
}

widget_component! {
    pub nav_scroll_box_side_scrollbars(id, key, props) [
        use_nav_item_active,
        use_nav_container_active,
        use_nav_scroll_box_side_scrollbars,
    ] {
        let view_props = props.read_cloned_or_default::<ScrollViewState>();
        let SideScrollbarsProps {
            size,
            back_material,
            front_material,
        } = props.read_cloned_or_default();
        let hbar = if view_props.size_factor.x > 1.0 {
            let props = Props::new(ButtonNotifyProps(id.to_owned().into()))
            .with(NavItemActive)
            .with(NavButtonTrackingActive)
            .with(ContentBoxItemLayout {
                anchors: Rect {
                    left: 0.0,
                    right: 1.0,
                    top: 1.0,
                    bottom: 1.0,
                },
                margin: Rect {
                    left: 0.0,
                    right: size,
                    top: -size,
                    bottom: 0.0,
                },
                align: Vec2 { x: 0.0, y: 1.0 },
                ..Default::default()
            });
            let front_props = ImageBoxProps {
                material: front_material.clone(),
                ..Default::default()
            };
            let back = if let Some(material) = back_material.clone() {
                let props = ImageBoxProps {
                    material,
                    ..Default::default()
                };

                widget!{ (#{"back"} image_box: {props}) }
            } else {
                widget!{()}
            };

            widget! {
                (#{"hbar"} button: {props} {
                    content = (#{"container"} content_box [
                        {back}
                        (#{"front"} image_box: {front_props})
                    ])
                })
            }
        } else {
            widget!{()}
        };
        let vbar = if view_props.size_factor.y > 1.0 {
            let props = Props::new(ButtonNotifyProps(id.to_owned().into()))
            .with(NavItemActive)
            .with(NavButtonTrackingActive)
            .with(ContentBoxItemLayout {
                anchors: Rect {
                    left: 1.0,
                    right: 1.0,
                    top: 0.0,
                    bottom: 1.0,
                },
                margin: Rect {
                    left: -size,
                    right: 0.0,
                    top: 0.0,
                    bottom: size,
                },
                align: Vec2 { x: 1.0, y: 0.0 },
                ..Default::default()
            });
            let back = if let Some(material) = back_material {
                let props = ImageBoxProps {
                    material,
                    ..Default::default()
                };

                widget!{ (#{"back"} image_box: {props}) }
            } else {
                widget!{()}
            };
            let front_props = ImageBoxProps {
                material: front_material,
                ..Default::default()
            };

            widget! {
                (#{"vbar"} button: {props} {
                    content = (#{"container"} content_box [
                        {back}
                        (#{"front"} image_box: {front_props})
                    ])
                })
            }
        } else {
            widget!{()}
        };

        widget! {
            (#{key} content_box [
                {hbar}
                {vbar}
            ])
        }
    }
}