magical markdown slides
1use ratatui::layout::{Constraint, Direction, Layout, Rect};
2
3/// Layout manager for slide presentation
4///
5/// Calculates screen layout with main slide area, optional notes panel, and status bar.
6pub struct SlideLayout {
7 show_notes: bool,
8}
9
10impl SlideLayout {
11 pub fn new(show_notes: bool) -> Self {
12 Self { show_notes }
13 }
14
15 /// Calculate layout areas for the slide viewer
16 ///
17 /// Returns (main_area, notes_area, status_area) where notes_area is None if notes are hidden.
18 pub fn calculate(&self, area: Rect) -> (Rect, Option<Rect>, Rect) {
19 let vertical_chunks = Layout::default()
20 .direction(Direction::Vertical)
21 .constraints([Constraint::Min(3), Constraint::Length(1)])
22 .split(area);
23
24 let content_area = vertical_chunks[0];
25 let status_area = vertical_chunks[1];
26
27 if self.show_notes {
28 let horizontal_chunks = Layout::default()
29 .direction(Direction::Horizontal)
30 .constraints([Constraint::Percentage(60), Constraint::Percentage(40)])
31 .split(content_area);
32
33 (horizontal_chunks[0], Some(horizontal_chunks[1]), status_area)
34 } else {
35 (content_area, None, status_area)
36 }
37 }
38
39 /// Update notes visibility
40 pub fn set_show_notes(&mut self, show: bool) {
41 self.show_notes = show;
42 }
43
44 /// Check if notes are visible
45 pub fn is_showing_notes(&self) -> bool {
46 self.show_notes
47 }
48}
49
50impl Default for SlideLayout {
51 fn default() -> Self {
52 Self { show_notes: false }
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn layout_without_notes() {
62 let layout = SlideLayout::new(false);
63 let area = Rect::new(0, 0, 100, 50);
64 let (main, notes, status) = layout.calculate(area);
65
66 assert!(notes.is_none());
67 assert_eq!(status.height, 1);
68 assert!(main.height > status.height);
69 }
70
71 #[test]
72 fn layout_with_notes() {
73 let layout = SlideLayout::new(true);
74 let area = Rect::new(0, 0, 100, 50);
75 let (main, notes, status) = layout.calculate(area);
76
77 assert!(notes.is_some());
78 let notes_area = notes.unwrap();
79 assert!(main.width > notes_area.width);
80 assert_eq!(main.height, notes_area.height);
81 assert_eq!(status.height, 1);
82 }
83
84 #[test]
85 fn layout_toggle_notes() {
86 let mut layout = SlideLayout::default();
87 assert!(!layout.is_showing_notes());
88
89 layout.set_show_notes(true);
90 assert!(layout.is_showing_notes());
91
92 layout.set_show_notes(false);
93 assert!(!layout.is_showing_notes());
94 }
95
96 #[test]
97 fn layout_small_terminal() {
98 let layout = SlideLayout::new(false);
99 let area = Rect::new(0, 0, 20, 10);
100 let (main, _notes, status) = layout.calculate(area);
101
102 assert_eq!(status.height, 1);
103 assert!(main.height >= 3);
104 }
105
106 #[test]
107 fn layout_proportions_with_notes() {
108 let layout = SlideLayout::new(true);
109 let area = Rect::new(0, 0, 100, 50);
110 let (main, notes, _status) = layout.calculate(area);
111
112 let notes_area = notes.unwrap();
113 let main_percentage = (main.width as f32 / area.width as f32) * 100.0;
114 let notes_percentage = (notes_area.width as f32 / area.width as f32) * 100.0;
115
116 assert!(main_percentage >= 55.0 && main_percentage <= 65.0);
117 assert!(notes_percentage >= 35.0 && notes_percentage <= 45.0);
118 }
119}