fast_rich/live.rs
1use crate::console::{Console, RenderContext};
2use crate::renderable::Renderable;
3
4/// Live display context manager.
5///
6/// Manages a live-updating display in the terminal.
7pub struct Live<'a> {
8 console: &'a Console,
9 renderable: Box<dyn Renderable>,
10 refresh_rate: u64, // Refresh rate in Hz (not currently used for auto-refresh thread in this version)
11 transient: bool,
12 height: usize,
13 started: bool,
14}
15
16impl<'a> Live<'a> {
17 /// Create a new Live display.
18 pub fn new(renderable: impl Renderable + 'static, console: &'a Console) -> Self {
19 Live {
20 console,
21 renderable: Box::new(renderable),
22 refresh_rate: 4,
23 transient: false,
24 height: 0,
25 started: false,
26 }
27 }
28
29 /// Set whether the display should be cleared on exit.
30 pub fn transient(mut self, transient: bool) -> Self {
31 self.transient = transient;
32 self
33 }
34
35 /// Set the refresh rate (currently unused in manual refresh mode).
36 pub fn refresh_rate(mut self, rate: u64) -> Self {
37 self.refresh_rate = rate;
38 self
39 }
40
41 /// Start the live display.
42 pub fn start(&mut self) {
43 if self.started {
44 return;
45 }
46 self.console.show_cursor(false);
47 self.started = true;
48 self.refresh();
49 }
50
51 /// Stop the live display.
52 pub fn stop(&mut self) {
53 if !self.started {
54 return;
55 }
56
57 // Clean up
58 if self.height > 0 {
59 self.console.move_cursor_up(self.height as u16);
60 // We clear lines downwards to ensure clean exit
61 for _ in 0..self.height {
62 self.console.clear_line();
63 self.console.move_cursor_down(1);
64 }
65 self.console.move_cursor_up(self.height as u16);
66 }
67
68 if !self.transient {
69 // If not transient, we want to leave the last frame visible
70 // So we reprint it one last time, but this time we don't track height
71 // to "lock" it in place (as standard output)
72 self.console.print_renderable(self.renderable.as_ref());
73 self.console.newline(); // Ensure final newline
74 }
75
76 self.console.show_cursor(true);
77 self.started = false;
78 self.height = 0;
79 }
80
81 /// Update the renderable and refresh the display.
82 pub fn update(&mut self, renderable: impl Renderable + 'static) {
83 self.renderable = Box::new(renderable);
84 self.refresh();
85 }
86
87 /// Refresh the display with the current renderable.
88 pub fn refresh(&mut self) {
89 if !self.started {
90 return;
91 }
92
93 // 1. Move cursor up to overwrite previous output
94 if self.height > 0 {
95 self.console.move_cursor_up(self.height as u16);
96 }
97
98 // 2. Render content
99 let width = self.console.get_width();
100 let context = RenderContext {
101 width,
102 height: None,
103 };
104
105 let segments = self.renderable.render(&context);
106
107 // 3. Calculate new height
108 // We need to know how many lines this render took.
109 // Similar to console.print, but we track lines.
110 let mut lines = 0;
111 for segment in &segments {
112 // Write to console
113 // We can't use console.print because that might add a newline we don't control
114 // cleanly or we want to count lines.
115 // Actually console.print adds a newline at the end if we use println.
116 // Let's use internal write helpers or just count newlines in segments.
117 if segment.newline {
118 lines += 1;
119 }
120 }
121
122 // If the last segment didn't have a newline, it's still occupying a line?
123 // Usually renderables ensure newlines or we treat them as flow.
124 // For now let's assume one newline per line.
125
126 // Wait, Console::print_renderable just writes segments. It behaves linearly.
127 // We want to verify if the output ended with a newline or not.
128 // Simplest strategy:
129 // We write the segments.
130 // Then we ensure we end with a newline so the cursor is on the next line?
131 // Or we keep the cursor at the end of the last line?
132
133 // rich.live typically clears the area and rewrites.
134 // To avoid flicker:
135 // Move up N lines.
136 // Clear line? Or just overwrite? Overwriting is better (less flicker).
137 // If new content is shorter than old content, we must clear the remainder.
138
139 // Strategy:
140 // 1. Buffer the output (optional, but good for flicker).
141 // 2. Write output.
142 // 3. Count lines written.
143 // 4. If new_lines < old_lines, clear remaining old lines.
144
145 // Let's rely on Console to write but we need to buffer to count lines?
146 // Or we can just count segments newlines?
147
148 // Let's buffer it for now to be safe and to support "clearing".
149 // Actually, Console has a capture mode but we are using the live console.
150 // Let's just write and track.
151
152 // NOTE: This simple implementation assumes the renderable *is* the lines.
153 self.console.write_segments(&segments);
154
155 // If the segments didn't end with a newline, we add one to separate from potential next output?
156 // Rich usually ensures block display.
157 if !segments.is_empty() && !segments.last().unwrap().newline {
158 self.console.newline();
159 lines += 1;
160 }
161
162 let new_height = lines;
163
164 // If we shrank, clear the lines below (that were part of old height)
165 if self.height > new_height {
166 let diff = self.height - new_height;
167 // Cursor is currently at end of new content.
168 for _ in 0..diff {
169 self.console.clear_line(); // Clear this line
170 self.console.newline(); // Move down
171 }
172 // Move back up
173 self.console.move_cursor_up(diff as u16);
174 }
175
176 self.height = new_height;
177 }
178}
179
180impl<'a> Drop for Live<'a> {
181 fn drop(&mut self) {
182 self.stop();
183 }
184}