Skip to main content

vizia_core/views/
tabview.rs

1use std::ops::Deref;
2
3use crate::prelude::*;
4
5pub enum TabEvent {
6    SetSelected(usize),
7}
8
9pub struct TabView {
10    selected_index: Signal<usize>,
11    is_vertical: Signal<bool>,
12    on_select: Option<Box<dyn Fn(&mut EventContext, usize)>>,
13}
14
15impl TabView {
16    pub fn new<S, V, T, F>(cx: &mut Context, list: S, content: F) -> Handle<Self>
17    where
18        S: Res<V> + 'static,
19        V: Deref<Target = [T]> + Clone + 'static,
20        T: Clone + 'static,
21        F: 'static + Clone + Fn(&mut Context, usize, T) -> TabPair,
22    {
23        let selected_index = Signal::new(0usize);
24        let is_vertical = Signal::new(false);
25        let list = list.to_signal(cx);
26
27        Self { selected_index, is_vertical, on_select: None }
28            .build(cx, move |cx| {
29                let content_for_headers = content.clone();
30
31                ScrollView::new(cx, move |cx| {
32                    Binding::new(cx, list, move |cx| {
33                        let list_values = list.get();
34
35                        for (index, item) in list_values.iter().cloned().enumerate() {
36                            let builder = (content_for_headers)(cx, index, item).header;
37                            let is_selected =
38                                selected_index.map(move |selected_index| *selected_index == index);
39
40                            TabHeader::new(cx, index, builder)
41                                .checked(is_selected)
42                                .toggle_class("vertical", is_vertical);
43                        }
44                    });
45                })
46                .class("tabview-header")
47                .z_index(1)
48                .toggle_class("vertical", is_vertical);
49
50                Divider::new(cx).toggle_class("vertical", is_vertical);
51
52                VStack::new(cx, move |cx| {
53                    Binding::new(cx, list, move |cx| {
54                        let list_values = list.get();
55                        let content = content.clone();
56                        Binding::new(cx, selected_index, move |cx| {
57                            let selected = selected_index.get();
58                            if let Some(item) = list_values.get(selected).cloned() {
59                                ((content)(cx, selected, item).content)(cx);
60                            }
61                        });
62                    });
63                })
64                .overflow(Overflow::Hidden)
65                .class("tabview-content-wrapper");
66            })
67            .toggle_class("vertical", is_vertical)
68    }
69}
70
71impl View for TabView {
72    fn element(&self) -> Option<&'static str> {
73        Some("tabview")
74    }
75
76    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
77        event.map(|tab_event, meta| match tab_event {
78            TabEvent::SetSelected(index) => {
79                if self.selected_index.get() != *index {
80                    self.selected_index.set(*index);
81                    if let Some(callback) = &self.on_select {
82                        (callback)(cx, *index);
83                    }
84                }
85                meta.consume();
86            }
87        });
88    }
89}
90
91impl Handle<'_, TabView> {
92    pub fn vertical(self) -> Self {
93        self.modify(|tabview: &mut TabView| {
94            tabview.is_vertical.set(true);
95        })
96    }
97
98    pub fn on_select(self, callback: impl Fn(&mut EventContext, usize) + 'static) -> Self {
99        self.modify(|tabview: &mut TabView| tabview.on_select = Some(Box::new(callback)))
100    }
101
102    pub fn with_selected<U: Into<usize>>(mut self, selected: impl Res<U>) -> Self {
103        let _entity = self.entity();
104        selected.set_or_bind(self.context(), |cx, selected| {
105            let index = selected.get_value(cx).into();
106            cx.emit(TabEvent::SetSelected(index));
107        });
108
109        self
110    }
111}
112
113pub struct TabPair {
114    pub header: Box<dyn Fn(&mut Context)>,
115    pub content: Box<dyn Fn(&mut Context)>,
116}
117
118impl TabPair {
119    pub fn new<H, C>(header: H, content: C) -> Self
120    where
121        H: 'static + Fn(&mut Context),
122        C: 'static + Fn(&mut Context),
123    {
124        Self { header: Box::new(header), content: Box::new(content) }
125    }
126}
127
128pub struct TabHeader {
129    index: usize,
130}
131
132impl TabHeader {
133    pub fn new<F>(cx: &mut Context, index: usize, content: F) -> Handle<Self>
134    where
135        F: 'static + Fn(&mut Context),
136    {
137        Self { index }.build(cx, |cx| (content)(cx))
138    }
139}
140
141impl View for TabHeader {
142    fn element(&self) -> Option<&'static str> {
143        Some("tabheader")
144    }
145
146    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
147        event.map(|window_event, _meta| match window_event {
148            WindowEvent::PressDown { mouse: _ } => {
149                cx.emit(TabEvent::SetSelected(self.index));
150            }
151
152            _ => {}
153        });
154    }
155}