pelican_ui 0.3.1

Standard UI tools and components built off Pelican UI of the RAMP stack.
Documentation
use roost::{Component, Context};
use roost::drawable::Drawable;
use roost::events::{Event, OnEvent};
use roost::layouts::{Column, Row, Padding, Offset, Size, Opt, Stack};

use crate::components::Rectangle;
use crate::components::interface::general::InterfaceTrait;
use crate::components::interface::system::MobileKeyboard;
use crate::components::interface::navigation::{AppPage, NavigateEvent, RootInfo, NavigatorEvent, NavigatorSelectable};
use crate::plugin::PelicanUI;

#[derive(Component, Debug)]
pub struct MobileInterface(Column, Option<Box<dyn AppPage>>, Option<MobileKeyboard>, Option<Opt<Box<dyn Drawable>>>);

impl InterfaceTrait for MobileInterface {
    fn app_page(&mut self) -> &mut Option<Box<dyn AppPage>> {&mut self.1}
    fn navigator(&mut self) -> &mut Option<Opt<Box<dyn Drawable>>> {&mut self.3}
}

impl MobileInterface {
    pub fn new(
        ctx: &mut Context, 
        start_page: Box<dyn AppPage>,
        navigation: Option<(Vec<RootInfo>, Option<Vec<RootInfo>>)>
    ) -> Self {
        let navigator = navigation.map(|n| Opt::new(Box::new(MobileNavigator::new(ctx, n)) as Box<dyn Drawable>, true));
        let insets = ctx.hardware.safe_area_insets();
        let padding = Padding(insets.0, insets.2, insets.1, insets.3);
        let layout = Column::new(0.0, Offset::Center, Size::Fit, padding);
        MobileInterface(layout, Some(start_page), None, navigator)
    }
}

impl OnEvent for MobileInterface {
    fn on_event(&mut self, ctx: &mut Context, mut event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
        if event.downcast_mut::<NavigateEvent>().is_some() {
            self.2 = None;
        } else if let Some(ShowKeyboard(b)) = event.downcast_ref::<ShowKeyboard>() {
            self.2 = b.then_some(MobileKeyboard::new(ctx, true));
        }
        vec![event]
    }
}

#[derive(Debug, Component)]
pub struct MobileNavigator(Stack, Rectangle, MobileNavigatorContent);
impl OnEvent for MobileNavigator {}

impl MobileNavigator {
    pub fn new(ctx: &mut Context, navigation: (Vec<RootInfo>, Option<Vec<RootInfo>>)) -> Self {
        let height = Size::custom(move |heights: Vec<(f32, f32)>|(heights[1].0, heights[1].1));
        let background = ctx.get::<PelicanUI>().get().0.theme().colors.background.primary;

        MobileNavigator(
            Stack(Offset::Center, Offset::Start, Size::Fill, height, Padding::default()), 
            Rectangle::new(background, 0.0, None),
            MobileNavigatorContent::new(ctx, navigation)
        )
    }
}

#[derive(Debug, Component)]
struct MobileNavigatorContent(Row, Vec<NavigatorSelectable>);
impl OnEvent for MobileNavigatorContent {}

impl MobileNavigatorContent {
    fn new(ctx: &mut Context, mut navigation: (Vec<RootInfo>, Option<Vec<RootInfo>>)) -> Self {
        let group_id = uuid::Uuid::new_v4();
        let mut tabs = Vec::new();
        if let Some(n) = navigation.1 { navigation.0.extend(n); }
        for (i, info) in navigation.0.into_iter().enumerate() {
            let closure = move |ctx: &mut Context| ctx.trigger_event(NavigatorEvent(i));
            tabs.push(NavigatorSelectable::mobile(ctx, info.icon, closure, 0 == i, group_id));
        }

        let layout = Row::new(48.0, Offset::Center, Size::Fit, Padding(0.0, 8.0, 0.0, 8.0));
        MobileNavigatorContent(layout, tabs)
    }
}

/// Event used to open or close keyboard.
#[derive(Debug, Clone)]
pub struct ShowKeyboard(pub bool);

impl Event for ShowKeyboard {
    fn pass(self: Box<Self>, _ctx: &mut Context, children: &Vec<((f32, f32), (f32, f32))>) -> Vec<Option<Box<dyn Event>>> {
        children.iter().map(|_| Some(self.clone() as Box<dyn Event>)).collect()
    }
}