vizia_core/views/
tabview.rs1use 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}