1use std::io::{self, Write};
10use std::thread;
11use std::time::Duration;
12
13use eye_declare::{Component, Elements, Hooks, InlineRenderer, Spinner, TextBlock, VStack};
14use ratatui_core::{
15 buffer::Buffer,
16 layout::Rect,
17 style::{Color, Modifier, Style},
18 text::{Line, Span},
19 widgets::Widget,
20};
21use ratatui_widgets::paragraph::Paragraph;
22
23struct StatusLog {
29 name: String,
30}
31
32impl StatusLog {
33 fn new(name: impl Into<String>) -> Self {
34 Self { name: name.into() }
35 }
36}
37
38#[derive(Default)]
39struct StatusLogState {
40 entries: Vec<(String, Style)>,
41}
42
43impl StatusLogState {
44 fn log(&mut self, msg: impl Into<String>, style: Style) {
45 self.entries.push((msg.into(), style));
46 }
47}
48
49impl Component for StatusLog {
50 type State = StatusLogState;
51
52 fn render(&self, area: Rect, buf: &mut Buffer, state: &Self::State) {
53 let lines: Vec<Line> = state
54 .entries
55 .iter()
56 .map(|(text, style)| Line::from(Span::styled(text.as_str(), *style)))
57 .collect();
58 Paragraph::new(lines).render(area, buf);
59 }
60
61 fn desired_height(&self, _width: u16, state: &Self::State) -> u16 {
62 state.entries.len() as u16
63 }
64
65 fn initial_state(&self) -> Option<StatusLogState> {
66 let mut state = StatusLogState {
67 entries: Vec::new(),
68 };
69 if !self.name.is_empty() {
70 state.log(
71 format!(" {} created", self.name),
72 Style::default().fg(Color::DarkGray),
73 );
74 }
75 Some(state)
76 }
77
78 fn lifecycle(&self, hooks: &mut Hooks<StatusLogState>, _state: &StatusLogState) {
79 if !self.name.is_empty() {
80 let mount_name = self.name.clone();
81 hooks.use_mount(move |state| {
82 state.log(
83 format!(" {} mounted", mount_name),
84 Style::default()
85 .fg(Color::Green)
86 .add_modifier(Modifier::ITALIC),
87 );
88 });
89
90 let unmount_name = self.name.clone();
91 hooks.use_unmount(move |state| {
92 state.log(
93 format!(" {} unmounted", unmount_name),
94 Style::default()
95 .fg(Color::Red)
96 .add_modifier(Modifier::ITALIC),
97 );
98 });
99 }
100 }
101}
102
103struct AppState {
108 tasks: Vec<String>,
109 processing: bool,
110}
111
112fn task_view(state: &AppState) -> Elements {
113 let mut els = Elements::new();
114
115 els.add(
116 TextBlock::new().line(
117 format!("Tasks ({})", state.tasks.len()),
118 Style::default()
119 .fg(Color::White)
120 .add_modifier(Modifier::BOLD),
121 ),
122 );
123
124 for task in &state.tasks {
125 els.add(StatusLog::new(task)).key(task.clone());
126 }
127
128 if state.processing {
129 els.add(Spinner::new("Processing...")).key("spinner");
130 }
131
132 els.add(TextBlock::new().line("---", Style::default().fg(Color::DarkGray)));
133
134 els
135}
136
137fn main() -> io::Result<()> {
142 let (width, _) = crossterm::terminal::size()?;
143 let mut r = InlineRenderer::new(width);
144 let mut stdout = io::stdout();
145
146 let container = r.push(VStack);
147 let mut state = AppState {
148 tasks: vec!["Alpha".into(), "Beta".into(), "Gamma".into()],
149 processing: false,
150 };
151
152 r.rebuild(container, task_view(&state));
154 flush(&mut r, &mut stdout)?;
155 thread::sleep(Duration::from_millis(1000));
156
157 state.tasks.retain(|t| t != "Beta");
159 r.rebuild(container, task_view(&state));
160 flush(&mut r, &mut stdout)?;
161 thread::sleep(Duration::from_millis(1000));
162
163 state.tasks.push("Delta".into());
165 r.rebuild(container, task_view(&state));
166 flush(&mut r, &mut stdout)?;
167 thread::sleep(Duration::from_millis(1000));
168
169 state.processing = true;
171 r.rebuild(container, task_view(&state));
172 let start = std::time::Instant::now();
174 while start.elapsed() < Duration::from_millis(1500) && r.has_active() {
175 r.tick();
176 flush(&mut r, &mut stdout)?;
177 thread::sleep(Duration::from_millis(50));
178 }
179
180 state.tasks.clear();
182 state.processing = false;
183 r.rebuild(container, task_view(&state));
184 flush(&mut r, &mut stdout)?;
185
186 println!();
187 Ok(())
188}
189
190fn flush(r: &mut InlineRenderer, stdout: &mut impl Write) -> io::Result<()> {
191 let output = r.render();
192 if !output.is_empty() {
193 stdout.write_all(&output)?;
194 stdout.flush()?;
195 }
196 Ok(())
197}