raui-core 0.70.17

RAUI application layer
Documentation
use crate::{
    PropsData, Scalar, make_widget, pre_hooks,
    props::Props,
    widget::{
        component::{
            containers::{
                flex_box::{FlexBoxProps, flex_box},
                switch_box::{SwitchBoxProps, switch_box},
            },
            interactive::{
                button::{ButtonNotifyMessage, ButtonNotifyProps, button},
                navigation::{NavItemActive, use_nav_container_active, use_nav_item},
            },
        },
        context::WidgetContext,
        node::WidgetNode,
        unit::flex::{FlexBoxDirection, FlexBoxItemLayout},
        utils::Transform,
    },
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum TabsBoxTabsLocation {
    #[default]
    Top,
    Bottom,
    Left,
    Right,
}

#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub struct TabsBoxProps {
    #[serde(default)]
    pub tabs_location: TabsBoxTabsLocation,
    #[serde(default)]
    pub tabs_and_content_separation: Scalar,
    #[serde(default)]
    pub tabs_basis: Option<Scalar>,
    #[serde(default)]
    pub contents_clipping: bool,
    #[serde(default)]
    pub start_index: usize,
    #[serde(default)]
    pub transform: Transform,
}

#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub struct TabsState {
    #[serde(default)]
    pub active_index: usize,
}

#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub struct TabPlateProps {
    #[serde(default)]
    pub active: bool,
    #[serde(default)]
    pub index: usize,
}

impl TabsBoxProps {
    fn to_main_props(&self) -> FlexBoxProps {
        FlexBoxProps {
            direction: match self.tabs_location {
                TabsBoxTabsLocation::Top => FlexBoxDirection::VerticalTopToBottom,
                TabsBoxTabsLocation::Bottom => FlexBoxDirection::VerticalBottomToTop,
                TabsBoxTabsLocation::Left => FlexBoxDirection::HorizontalLeftToRight,
                TabsBoxTabsLocation::Right => FlexBoxDirection::HorizontalRightToLeft,
            },
            separation: self.tabs_and_content_separation,
            wrap: false,
            transform: self.transform.to_owned(),
        }
    }

    fn to_tabs_props(&self) -> FlexBoxProps {
        FlexBoxProps {
            direction: match self.tabs_location {
                TabsBoxTabsLocation::Top => FlexBoxDirection::HorizontalLeftToRight,
                TabsBoxTabsLocation::Bottom => FlexBoxDirection::HorizontalLeftToRight,
                TabsBoxTabsLocation::Left => FlexBoxDirection::VerticalTopToBottom,
                TabsBoxTabsLocation::Right => FlexBoxDirection::VerticalTopToBottom,
            },
            ..Default::default()
        }
    }
}

pub fn use_nav_tabs_box(context: &mut WidgetContext) {
    context.life_cycle.mount(|context| {
        let _ = context.state.write(TabsState {
            active_index: context
                .props
                .map_or_default::<TabsBoxProps, _, _>(|p| p.start_index),
        });
    });

    context.life_cycle.change(|context| {
        for msg in context.messenger.messages {
            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()
                && msg.trigger_start()
                && let Ok(index) = msg.sender.key().parse::<usize>()
            {
                let _ = context.state.write(TabsState {
                    active_index: index,
                });
            }
        }
    })
}

#[pre_hooks(use_nav_container_active, use_nav_item, use_nav_tabs_box)]
pub fn nav_tabs_box(mut context: WidgetContext) -> WidgetNode {
    let WidgetContext {
        id,
        key,
        props,
        state,
        listed_slots,
        ..
    } = context;

    let main_props = props.read_cloned_or_default::<TabsBoxProps>();
    let props = props.clone().with(main_props.to_main_props());
    let tabs_props = Props::new(main_props.to_tabs_props()).with(FlexBoxItemLayout {
        basis: main_props.tabs_basis,
        grow: 0.0,
        shrink: 0.0,
        ..Default::default()
    });
    let TabsState { active_index } = state.read_cloned_or_default();
    let switch_props = SwitchBoxProps {
        active_index: if active_index < listed_slots.len() {
            Some(active_index)
        } else {
            None
        },
        clipping: main_props.contents_clipping,
        ..Default::default()
    };
    let mut tabs = Vec::<WidgetNode>::with_capacity(listed_slots.len());
    let mut contents = Vec::with_capacity(listed_slots.len());

    for (index, item) in listed_slots.into_iter().enumerate() {
        let [mut tab, content] = item.unpack_tuple();
        tab.remap_props(|props| {
            props.with(TabPlateProps {
                active: active_index == index,
                index,
            })
        });
        let props = Props::new(NavItemActive).with(ButtonNotifyProps(id.to_owned().into()));
        tabs.push(
            make_widget!(button)
                .key(index)
                .merge_props(props)
                .named_slot("content", tab)
                .into(),
        );
        contents.push(content);
    }

    make_widget!(flex_box)
        .key(key)
        .merge_props(props)
        .listed_slot(
            make_widget!(flex_box)
                .key("tabs")
                .merge_props(tabs_props)
                .listed_slots(tabs),
        )
        .listed_slot(
            make_widget!(switch_box)
                .key("contents")
                .with_props(switch_props)
                .listed_slots(contents),
        )
        .into()
}