vizia_core 0.4.0

Core components of vizia
use std::ops::Deref;

use crate::prelude::*;

pub enum TabEvent {
    SetSelected(usize),
}

pub struct TabView {
    selected_index: Signal<usize>,
    is_vertical: Signal<bool>,
    on_select: Option<Box<dyn Fn(&mut EventContext, usize)>>,
}

impl TabView {
    pub fn new<S, V, T, F>(cx: &mut Context, list: S, content: F) -> Handle<Self>
    where
        S: Res<V> + 'static,
        V: Deref<Target = [T]> + Clone + 'static,
        T: Clone + 'static,
        F: 'static + Clone + Fn(&mut Context, usize, T) -> TabPair,
    {
        let selected_index = Signal::new(0usize);
        let is_vertical = Signal::new(false);
        let list = list.to_signal(cx);

        Self { selected_index, is_vertical, on_select: None }
            .build(cx, move |cx| {
                let content_for_headers = content.clone();

                ScrollView::new(cx, move |cx| {
                    Binding::new(cx, list, move |cx| {
                        let list_values = list.get();

                        for (index, item) in list_values.iter().cloned().enumerate() {
                            let builder = (content_for_headers)(cx, index, item).header;
                            let is_selected =
                                selected_index.map(move |selected_index| *selected_index == index);

                            TabHeader::new(cx, index, builder)
                                .checked(is_selected)
                                .toggle_class("vertical", is_vertical);
                        }
                    });
                })
                .class("tabview-header")
                .z_index(1)
                .toggle_class("vertical", is_vertical);

                Divider::new(cx).toggle_class("vertical", is_vertical);

                VStack::new(cx, move |cx| {
                    Binding::new(cx, list, move |cx| {
                        let list_values = list.get();
                        let content = content.clone();
                        Binding::new(cx, selected_index, move |cx| {
                            let selected = selected_index.get();
                            if let Some(item) = list_values.get(selected).cloned() {
                                ((content)(cx, selected, item).content)(cx);
                            }
                        });
                    });
                })
                .overflow(Overflow::Hidden)
                .class("tabview-content-wrapper");
            })
            .toggle_class("vertical", is_vertical)
    }
}

impl View for TabView {
    fn element(&self) -> Option<&'static str> {
        Some("tabview")
    }

    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
        event.map(|tab_event, meta| match tab_event {
            TabEvent::SetSelected(index) => {
                if self.selected_index.get() != *index {
                    self.selected_index.set(*index);
                    if let Some(callback) = &self.on_select {
                        (callback)(cx, *index);
                    }
                }
                meta.consume();
            }
        });
    }
}

impl Handle<'_, TabView> {
    pub fn vertical(self) -> Self {
        self.modify(|tabview: &mut TabView| {
            tabview.is_vertical.set(true);
        })
    }

    pub fn on_select(self, callback: impl Fn(&mut EventContext, usize) + 'static) -> Self {
        self.modify(|tabview: &mut TabView| tabview.on_select = Some(Box::new(callback)))
    }

    pub fn with_selected<U: Into<usize>>(mut self, selected: impl Res<U>) -> Self {
        let _entity = self.entity();
        selected.set_or_bind(self.context(), |cx, selected| {
            let index = selected.get_value(cx).into();
            cx.emit(TabEvent::SetSelected(index));
        });

        self
    }
}

pub struct TabPair {
    pub header: Box<dyn Fn(&mut Context)>,
    pub content: Box<dyn Fn(&mut Context)>,
}

impl TabPair {
    pub fn new<H, C>(header: H, content: C) -> Self
    where
        H: 'static + Fn(&mut Context),
        C: 'static + Fn(&mut Context),
    {
        Self { header: Box::new(header), content: Box::new(content) }
    }
}

pub struct TabHeader {
    index: usize,
}

impl TabHeader {
    pub fn new<F>(cx: &mut Context, index: usize, content: F) -> Handle<Self>
    where
        F: 'static + Fn(&mut Context),
    {
        Self { index }.build(cx, |cx| (content)(cx))
    }
}

impl View for TabHeader {
    fn element(&self) -> Option<&'static str> {
        Some("tabheader")
    }

    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
        event.map(|window_event, _meta| match window_event {
            WindowEvent::PressDown { mouse: _ } => {
                cx.emit(TabEvent::SetSelected(self.index));
            }

            _ => {}
        });
    }
}