1use crate::component::{Component, EventCx, MeasureCx};
2use crate::event::Event;
3use crate::geom::{Rect, Size};
4use crate::layout::Constraint;
5use crate::node::Node;
6use crate::render::RenderCx;
7use crate::style::Style;
8use crate::text::Text;
9
10pub struct Tabs {
15 tabs: Vec<(Text, Node)>,
17 selected: usize,
19 rect: Rect,
21 style: Style,
23 header_style: Style,
25 selected_style: Style,
27}
28
29impl Tabs {
30 pub fn new() -> Self {
32 Self {
33 tabs: Vec::new(),
34 selected: 0,
35 rect: Rect::default(),
36 style: Style::default(),
37 header_style: Style::default().bold(),
38 selected_style: Style::default(),
39 }
40 }
41
42 pub fn tab(mut self, title: impl Into<Text>, component: impl Component + 'static) -> Self {
44 self.tabs.push((title.into(), Node::new(component)));
45 self
46 }
47
48 pub fn style(mut self, style: Style) -> Self {
50 self.style = style;
51 self
52 }
53
54 pub fn header_style(mut self, style: Style) -> Self {
56 self.header_style = style;
57 self
58 }
59
60 pub fn selected_style(mut self, style: Style) -> Self {
62 self.selected_style = style;
63 self
64 }
65
66 pub fn selected_index(&self) -> usize {
68 self.selected
69 }
70
71 pub fn set_selected(&mut self, index: usize, cx: &mut EventCx) {
73 if index < self.tabs.len() {
74 self.selected = index;
75 cx.invalidate_paint();
76 }
77 }
78
79 fn content_rect(&self) -> Rect {
81 let y = self.rect.y.saturating_add(2); Rect {
83 x: self.rect.x,
84 y,
85 width: self.rect.width,
86 height: self.rect.height.saturating_sub(2),
87 }
88 }
89}
90
91impl Component for Tabs {
92 fn render(&self, cx: &mut RenderCx) {
93 if self.tabs.is_empty() {
94 return;
95 }
96
97 for (i, (title, _)) in self.tabs.iter().enumerate() {
99 if i == self.selected {
100 cx.set_style(self.selected_style.clone());
101 } else {
102 cx.set_style(self.header_style.clone());
103 }
104 cx.text(format!(" {} ", title.first_text()));
105 if i < self.tabs.len() - 1 {
106 cx.set_style(self.header_style.clone());
107 cx.text("│");
108 }
109 }
110 cx.line("");
111
112 cx.set_style(self.style.clone());
114 let total_width: u16 = self.tabs.iter()
115 .map(|(t, _)| t.max_width() + 2)
116 .sum::<u16>()
117 + (self.tabs.len().saturating_sub(1)) as u16; cx.text("─".repeat(total_width as usize));
119 cx.line("");
120
121 if let Some((_, child)) = self.tabs.get(self.selected) {
123 child.render_with_parent(cx.buffer, cx.focused_id, cx.clip_rect, cx.wrap, cx.truncate, cx.align, Some(&cx.style));
124 }
125 }
126
127 fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
128 if self.tabs.is_empty() {
129 return Size { width: 0, height: 0 };
130 }
131
132 let child_size = self.tabs.get(self.selected)
133 .map(|(_, child)| child.measure(constraint))
134 .unwrap_or_default();
135
136 Size {
137 width: constraint.max.width,
138 height: 2u16.saturating_add(child_size.height), }
140 }
141
142 fn event(&mut self, event: &Event, cx: &mut EventCx) {
143 if self.tabs.is_empty() {
144 return;
145 }
146
147 if matches!(event, Event::Focus | Event::Blur | Event::Tick) {
148 return;
149 }
150
151 if cx.phase() == crate::event::EventPhase::Capture {
154 return;
155 }
156 if let Event::Key(key_event) = event {
157 match &key_event.key {
158 crate::event::Key::Left => {
159 if self.selected > 0 {
160 self.selected -= 1;
161 } else {
162 self.selected = self.tabs.len() - 1;
163 }
164 cx.invalidate_paint();
165 cx.dispatch(crate::event::Command::FocusPrev);
166 return;
167 }
168 crate::event::Key::Right => {
169 if self.selected + 1 < self.tabs.len() {
170 self.selected += 1;
171 } else {
172 self.selected = 0;
173 }
174 cx.invalidate_paint();
175 cx.dispatch(crate::event::Command::FocusNext);
176 return;
177 }
178 crate::event::Key::Char('q') => {
179 cx.quit();
180 return;
181 }
182 _ => {}
183 }
184 }
185 if let Event::Key(k) = event {
187 if k.key == crate::event::Key::Char('c') && k.modifiers.ctrl {
188 cx.quit();
189 }
190 }
191 }
192
193 fn layout(&mut self, rect: Rect, _cx: &mut crate::component::LayoutCx) {
194 self.rect = rect;
195 let content = self.content_rect();
196 if let Some((_, child)) = self.tabs.get_mut(self.selected) {
197 child.layout(content);
198 }
199 }
200
201 fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
202 if let Some((_, child)) = self.tabs.get(self.selected) {
203 f(child);
204 }
205 }
206
207 fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
208 if let Some((_, child)) = self.tabs.get_mut(self.selected) {
209 f(child);
210 }
211 }
212
213 fn focusable(&self) -> bool {
214 false
215 }
216
217 fn style(&self) -> Style {
218 self.style.clone()
219 }
220}