ratatui_toolkit/master_layout/
tab.rs1use super::{Footer, InteractionMode, Pane, PaneContainer, PaneLayout};
4use ratatui::layout::{Constraint, Direction, Layout, Rect};
5
6pub struct Tab {
8 name: String,
9 pane_container: PaneContainer,
10 footer: Footer,
11}
12
13impl Tab {
14 pub fn new(name: impl Into<String>) -> Self {
16 Self {
17 name: name.into(),
18 pane_container: PaneContainer::default(),
19 footer: Footer::with_mode(),
20 }
21 }
22
23 pub fn with_layout(name: impl Into<String>, layout: PaneLayout) -> Self {
25 Self {
26 name: name.into(),
27 pane_container: PaneContainer::new(layout),
28 footer: Footer::with_mode(),
29 }
30 }
31
32 pub fn name(&self) -> &str {
34 &self.name
35 }
36
37 pub fn set_name(&mut self, name: impl Into<String>) {
39 self.name = name.into();
40 }
41
42 pub fn pane_container(&self) -> &PaneContainer {
44 &self.pane_container
45 }
46
47 pub fn pane_container_mut(&mut self) -> &mut PaneContainer {
49 &mut self.pane_container
50 }
51
52 pub fn footer(&self) -> &Footer {
54 &self.footer
55 }
56
57 pub fn footer_mut(&mut self) -> &mut Footer {
59 &mut self.footer
60 }
61
62 pub fn add_pane(&mut self, pane: Pane) {
64 self.pane_container.add_pane(pane);
65 }
66
67 pub fn set_layout(&mut self, layout: PaneLayout) {
69 let new_container = PaneContainer::new(layout);
71
72 self.pane_container = new_container;
76 }
77
78 pub fn pane_count(&self) -> usize {
80 self.pane_container.pane_count()
81 }
82
83 pub fn render(&mut self, frame: &mut ratatui::Frame, area: Rect, mode: &InteractionMode) {
85 let chunks = Layout::default()
87 .direction(Direction::Vertical)
88 .constraints([
89 Constraint::Min(3), Constraint::Length(3), ])
92 .split(area);
93
94 let pane_area = chunks[0];
95 let footer_area = chunks[1];
96
97 self.pane_container.update_layout(pane_area);
99
100 self.pane_container.render(frame, mode);
102
103 self.footer
105 .render_with_mode(footer_area, frame.buffer_mut(), mode);
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::super::PaneId;
112 use super::*;
113 use crossterm::event::{KeyEvent, MouseEvent};
114 use ratatui::{buffer::Buffer, widgets::Widget};
115
116 struct MockContent {
118 title: String,
119 }
120
121 impl MockContent {
122 fn new(title: &str) -> Self {
123 Self {
124 title: title.to_string(),
125 }
126 }
127 }
128
129 impl Widget for MockContent {
130 fn render(self, _area: Rect, _buf: &mut Buffer) {}
131 }
132
133 impl super::super::pane::PaneContent for MockContent {
134 fn handle_key(&mut self, _key: KeyEvent) -> bool {
135 true
136 }
137 fn handle_mouse(&mut self, _mouse: MouseEvent) -> bool {
138 true
139 }
140 fn title(&self) -> String {
141 self.title.clone()
142 }
143 fn render_content(&mut self, _area: Rect, _frame: &mut ratatui::Frame) {
144 }
146 }
147
148 #[test]
149 fn test_tab_creation() {
150 let tab = Tab::new("Test Tab");
151 assert_eq!(tab.name(), "Test Tab");
152 assert_eq!(tab.pane_count(), 0);
153 }
154
155 #[test]
156 fn test_tab_with_layout() {
157 let layout = PaneLayout::Horizontal(vec![50, 50]);
158 let tab = Tab::with_layout("Test Tab", layout);
159 assert_eq!(tab.name(), "Test Tab");
160 assert_eq!(tab.pane_count(), 0);
161 }
162
163 #[test]
164 fn test_set_name() {
165 let mut tab = Tab::new("Original");
166 assert_eq!(tab.name(), "Original");
167
168 tab.set_name("Updated");
169 assert_eq!(tab.name(), "Updated");
170 }
171
172 #[test]
173 fn test_add_pane() {
174 let mut tab = Tab::new("Test Tab");
175 let pane_id = PaneId::new("pane1");
176 let pane = Pane::new(pane_id, Box::new(MockContent::new("Pane 1")));
177
178 tab.add_pane(pane);
179 assert_eq!(tab.pane_count(), 1);
180 }
181
182 #[test]
183 fn test_add_multiple_panes() {
184 let mut tab = Tab::new("Test Tab");
185
186 tab.add_pane(Pane::new(
187 PaneId::new("p1"),
188 Box::new(MockContent::new("Pane 1")),
189 ));
190 tab.add_pane(Pane::new(
191 PaneId::new("p2"),
192 Box::new(MockContent::new("Pane 2")),
193 ));
194 tab.add_pane(Pane::new(
195 PaneId::new("p3"),
196 Box::new(MockContent::new("Pane 3")),
197 ));
198
199 assert_eq!(tab.pane_count(), 3);
200 }
201
202 #[test]
203 fn test_pane_container_access() {
204 let mut tab = Tab::new("Test Tab");
205 let pane_id = PaneId::new("pane1");
206
207 tab.add_pane(Pane::new(pane_id, Box::new(MockContent::new("Pane 1"))));
208
209 assert_eq!(tab.pane_container().pane_count(), 1);
211
212 let pane_id2 = PaneId::new("pane2");
214 tab.pane_container_mut()
215 .add_pane(Pane::new(pane_id2, Box::new(MockContent::new("Pane 2"))));
216 assert_eq!(tab.pane_count(), 2);
217 }
218
219 #[test]
220 fn test_footer_access() {
221 let mut tab = Tab::new("Test Tab");
222
223 assert!(!tab.footer().items.is_empty()); tab.footer_mut().add_static("Test item");
228 assert!(tab.footer().items.len() > 1);
229 }
230
231 #[test]
232 fn test_set_layout() {
233 let mut tab = Tab::new("Test Tab");
234
235 tab.add_pane(Pane::new(
237 PaneId::new("p1"),
238 Box::new(MockContent::new("Pane 1")),
239 ));
240 assert_eq!(tab.pane_count(), 1);
241
242 tab.set_layout(PaneLayout::Vertical(vec![30, 70]));
244
245 assert_eq!(tab.pane_count(), 0);
247 }
248
249 #[test]
250 fn test_render_does_not_panic() {
251 use ratatui::backend::TestBackend;
252 use ratatui::Terminal;
253
254 let mut tab = Tab::new("Test Tab");
255 tab.add_pane(Pane::new(
256 PaneId::new("p1"),
257 Box::new(MockContent::new("Pane 1")),
258 ));
259
260 let backend = TestBackend::new(80, 24);
261 let mut terminal = Terminal::new(backend).unwrap();
262
263 terminal
264 .draw(|frame| {
265 let area = frame.area();
266 tab.render(frame, area, &InteractionMode::default());
267 })
268 .unwrap();
269 }
270
271 #[test]
272 fn test_empty_tab_renders() {
273 use ratatui::backend::TestBackend;
274 use ratatui::Terminal;
275
276 let mut tab = Tab::new("Empty Tab");
277
278 let backend = TestBackend::new(80, 24);
279 let mut terminal = Terminal::new(backend).unwrap();
280
281 terminal
282 .draw(|frame| {
283 let area = frame.area();
284 tab.render(frame, area, &InteractionMode::default());
285 })
286 .unwrap();
287 }
288}