vizia_core/views/
tabview.rs

1use std::ops::Deref;
2
3use crate::{icons::ICON_PLUS, prelude::*};
4
5pub enum TabEvent {
6    SetSelected(usize),
7}
8
9#[derive(Lens)]
10pub struct TabView {
11    selected_index: usize,
12    is_vertical: bool,
13
14    #[lens(ignore)]
15    on_select: Option<Box<dyn Fn(&mut EventContext, usize)>>,
16}
17
18impl TabView {
19    pub fn new<L, T, F>(cx: &mut Context, lens: L, content: F) -> Handle<Self>
20    where
21        L: Lens<Target: std::ops::Deref<Target = [T]>>,
22        T: Clone + 'static,
23        F: 'static + Clone + Fn(&mut Context, Index<L, T>) -> TabPair,
24    {
25        Self { selected_index: 0, is_vertical: false, on_select: None }
26            .build(cx, move |cx| {
27                let content2 = content.clone();
28                // Tab headers
29                ScrollView::new(cx, move |cx| {
30                    //VStack::new(cx, move |cx| {
31                    Binding::new(cx, lens.map(|list| list.len()), move |cx, list_length| {
32                        let list_length = list_length.get(cx);
33                        for index in 0..list_length {
34                            let l = lens.idx(index);
35                            let builder = (content2)(cx, l).header;
36                            TabHeader::new(cx, index, builder)
37                                .bind(TabView::selected_index, move |handle, selected_index| {
38                                    let selected_index = selected_index.get(handle.cx);
39                                    handle.checked(selected_index == index);
40                                })
41                                .toggle_class("vertical", TabView::is_vertical);
42                        }
43                    })
44                    //})
45                    //.toggle_class("vertical", TabView::is_vertical)
46                    //.class("tabview-tabheader-wrapper");
47                })
48                .class("tabview-header")
49                .z_index(1)
50                .toggle_class("vertical", TabView::is_vertical);
51
52                Divider::new(cx).toggle_class("vertical", TabView::is_vertical);
53
54                // Tab content
55                VStack::new(cx, |cx| {
56                    Binding::new(cx, TabView::selected_index, move |cx, selected| {
57                        let selected = selected.get(cx);
58                        let l = lens.idx(selected);
59                        ((content)(cx, l).content)(cx);
60                    });
61                })
62                .overflow(Overflow::Hidden)
63                .class("tabview-content-wrapper");
64            })
65            .toggle_class("vertical", TabView::is_vertical)
66    }
67}
68
69impl View for TabView {
70    fn element(&self) -> Option<&'static str> {
71        Some("tabview")
72    }
73
74    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
75        event.map(|tab_event, meta| match tab_event {
76            TabEvent::SetSelected(index) => {
77                self.selected_index = *index;
78                if let Some(callback) = &self.on_select {
79                    (callback)(cx, self.selected_index);
80                }
81                meta.consume();
82            }
83        });
84    }
85}
86
87impl Handle<'_, TabView> {
88    pub fn vertical(self) -> Self {
89        self.modify(|tabview: &mut TabView| tabview.is_vertical = true)
90    }
91
92    pub fn on_select(self, callback: impl Fn(&mut EventContext, usize) + 'static) -> Self {
93        self.modify(|tabview: &mut TabView| tabview.on_select = Some(Box::new(callback)))
94    }
95
96    pub fn with_selected<U: Into<usize>>(mut self, selected: impl Res<U>) -> Self {
97        let entity = self.entity();
98        selected.set_or_bind(self.context(), entity, |cx, selected| {
99            let index = selected.get(cx).into();
100            cx.emit(TabEvent::SetSelected(index));
101        });
102
103        self
104    }
105}
106
107pub struct TabPair {
108    pub header: Box<dyn Fn(&mut Context)>,
109    pub content: Box<dyn Fn(&mut Context)>,
110}
111
112impl TabPair {
113    pub fn new<H, C>(header: H, content: C) -> Self
114    where
115        H: 'static + Fn(&mut Context),
116        C: 'static + Fn(&mut Context),
117    {
118        Self { header: Box::new(header), content: Box::new(content) }
119    }
120}
121
122pub struct TabHeader {
123    index: usize,
124}
125
126impl TabHeader {
127    pub fn new<F>(cx: &mut Context, index: usize, content: F) -> Handle<Self>
128    where
129        F: 'static + Fn(&mut Context),
130    {
131        Self { index }.build(cx, |cx| (content)(cx))
132    }
133}
134
135impl View for TabHeader {
136    fn element(&self) -> Option<&'static str> {
137        Some("tabheader")
138    }
139
140    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
141        event.map(|window_event, _meta| match window_event {
142            WindowEvent::PressDown { mouse: _ } => {
143                cx.emit(TabEvent::SetSelected(self.index));
144            }
145
146            _ => {}
147        });
148    }
149}
150
151pub struct TabBar {}
152
153impl TabBar {
154    pub fn new<L: Lens, T: 'static>(
155        cx: &mut Context,
156        list: L,
157        item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
158    ) -> Handle<Self>
159    where
160        L::Target: Deref<Target = [T]> + Data,
161    {
162        Self {}
163            .build(cx, |cx| {
164                List::new(cx, list, item_content)
165                    .selectable(Selectable::Single)
166                    .layout_type(LayoutType::Row);
167                Button::new(cx, |cx| Svg::new(cx, ICON_PLUS).size(Stretch(1.0)))
168                    .variant(ButtonVariant::Text)
169                    .padding(Pixels(0.0))
170                    .size(Pixels(16.0));
171            })
172            .alignment(Alignment::Left)
173            .layout_type(LayoutType::Row)
174    }
175}
176
177impl View for TabBar {
178    fn element(&self) -> Option<&'static str> {
179        Some("tabbar")
180    }
181}