🏡 index : ~doyle/shalom.git

use iced::{
    advanced::graphics::core::Element,
    alignment::Vertical,
    font::{Stretch, Weight},
    theme,
    widget::{
        column, component, container, horizontal_rule, mouse_area, rule, svg, text, Component,
    },
    Alignment, Background, Color, ContentFit, Font, Length, Renderer, Theme,
};

use crate::theme::{
    colours::{SKY_500, SLATE_200},
    Icon,
};

pub struct RoomNavigation<M> {
    _phantom: std::marker::PhantomData<M>,
    width: Length,
    current: Page,
    on_change: Option<fn(Page) -> M>,
    on_exit: Option<M>,
}

impl<M> RoomNavigation<M> {
    pub fn new(current: Page) -> Self {
        Self {
            _phantom: std::marker::PhantomData,
            width: Length::Fill,
            current,
            on_change: None,
            on_exit: None,
        }
    }

    pub fn width(mut self, width: Length) -> Self {
        self.width = width;
        self
    }

    pub fn on_change(mut self, on_change: fn(Page) -> M) -> Self {
        self.on_change = Some(on_change);
        self
    }

    pub fn on_exit(mut self, event: M) -> Self {
        self.on_exit = Some(event);
        self
    }
}

impl<M: Clone> Component<M, Renderer> for RoomNavigation<M> {
    type State = ();
    type Event = Event;

    fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option<M> {
        match event {
            Event::Change(page) => self.on_change.map(|v| v(page)),
            Event::Exit => self.on_exit.clone(),
        }
    }

    fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, Renderer> {
        let section = |icon: Icon, t: &'static str, state: Style, page| {
            mouse_area(
                container(
                    column![
                        svg(icon)
                            .height(Length::Fixed(64.))
                            .width(Length::Fixed(64.))
                            .style(theme::Svg::Custom(Box::new(state))),
                        text(t).size(18.).font(Font {
                            weight: Weight::Bold,
                            stretch: Stretch::Condensed,
                            ..Font::with_name("Helvetica Neue")
                        }),
                    ]
                    .width(Length::Fill)
                    .align_items(Alignment::Center)
                    .padding(12.),
                )
                .style(theme::Container::Custom(Box::new(state)))
                .width(Length::Fill),
            )
            .on_press(Event::Change(page))
        };

        let s = |p: &[Page]| {
            if p.contains(&self.current) {
                Style::Active
            } else {
                Style::Inactive
            }
        };

        let exit = container(
            mouse_area(
                svg(Icon::Back)
                    .height(32)
                    .width(32)
                    .content_fit(ContentFit::None),
            )
            .on_press(Event::Exit),
        )
        .height(Length::Fill)
        .width(Length::Fill)
        .align_y(Vertical::Bottom)
        .padding(40);

        column![
            section(Icon::Speaker, "Listen", s(&[Page::Listen]), Page::Listen),
            horizontal_rule(1).style(theme::Rule::Custom(Box::new(s(&[
                Page::Listen,
                Page::Climate
            ])))),
            section(Icon::Hvac, "Climate", s(&[Page::Climate]), Page::Climate),
            horizontal_rule(1).style(theme::Rule::Custom(Box::new(s(&[
                Page::Climate,
                Page::Lights
            ])))),
            section(Icon::Bulb, "Lights", s(&[Page::Lights]), Page::Lights),
            exit,
        ]
        .width(self.width)
        .height(Length::Fill)
        .into()
    }
}

#[derive(Copy, Clone)]
pub enum Event {
    Change(Page),
    Exit,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Page {
    Listen,
    Climate,
    Lights,
}

impl<'a, M> From<RoomNavigation<M>> for Element<'a, M, Renderer>
where
    M: 'a + Clone,
{
    fn from(card: RoomNavigation<M>) -> Self {
        component(card)
    }
}

#[derive(Copy, Clone)]
pub enum Style {
    Active,
    Inactive,
}

impl container::StyleSheet for Style {
    type Style = Theme;

    fn appearance(&self, _style: &Self::Style) -> container::Appearance {
        match self {
            Self::Active => container::Appearance {
                text_color: Some(Color::WHITE),
                background: Some(Background::Color(SKY_500)),
                border_radius: 0.0.into(),
                border_width: 0.0,
                border_color: Color::default(),
            },
            Self::Inactive => container::Appearance {
                text_color: Some(Color::BLACK),
                background: Some(Background::Color(Color::WHITE)),
                border_radius: 0.0.into(),
                border_width: 0.0,
                border_color: Color::default(),
            },
        }
    }
}

impl svg::StyleSheet for Style {
    type Style = Theme;

    fn appearance(&self, _style: &Self::Style) -> svg::Appearance {
        match self {
            Self::Active => svg::Appearance {
                color: Some(Color::WHITE),
            },
            Self::Inactive => svg::Appearance {
                color: Some(Color::BLACK),
            },
        }
    }
}

impl rule::StyleSheet for Style {
    type Style = Theme;

    fn appearance(&self, _style: &Self::Style) -> rule::Appearance {
        match self {
            Self::Active => rule::Appearance {
                color: Color::WHITE,
                width: 1,
                radius: 0.0.into(),
                fill_mode: rule::FillMode::Full,
            },
            Self::Inactive => rule::Appearance {
                color: SLATE_200,
                width: 1,
                radius: 0.0.into(),
                fill_mode: rule::FillMode::Full,
            },
        }
    }
}