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