1use crossterm::event::{self, Event, KeyCode, KeyModifiers};
2use ratatui::{
3 backend::Backend,
4 layout::{Constraint, Rect},
5 style::{Modifier, Style},
6 symbols::Marker,
7 widgets::Chart,
8 widgets::{Cell, Row, Table},
9 Terminal,
10};
11use std::{
12 io,
13 time::{Duration, Instant},
14};
15
16use crate::{
17 display::{
18 oscilloscope::Oscilloscope, spectroscope::Spectroscope, update_value_f, update_value_i,
19 vectorscope::Vectorscope, Dimension, DisplayMode, GraphConfig,
20 },
21 input::{DataSource, Matrix},
22};
23
24pub enum CurrentDisplayMode {
25 Oscilloscope,
26 Vectorscope,
27 Spectroscope,
28}
29
30pub struct App {
31 #[allow(unused)]
32 channels: u8,
33 graph: GraphConfig,
34 oscilloscope: Oscilloscope,
35 vectorscope: Vectorscope,
36 spectroscope: Spectroscope,
37 mode: CurrentDisplayMode,
38}
39
40impl App {
42 pub fn new(ui: &crate::cfg::UiOptions, source: &crate::cfg::SourceOptions) -> Self {
43 let graph = GraphConfig {
44 axis_color: ui.axis_color,
45 labels_color: ui.labels_color,
46 palette: ui.palette_color.clone(),
47 scale: ui.scale as f64,
48 width: source.buffer, samples: source.buffer,
50 sampling_rate: source.sample_rate,
51 references: !ui.no_reference,
52 show_ui: !ui.no_ui,
53 scatter: ui.scatter,
54 pause: false,
55 marker_type: if ui.no_braille {
56 Marker::Dot
57 } else {
58 Marker::Braille
59 },
60 };
61
62 let oscilloscope = Oscilloscope::default();
63 let vectorscope = Vectorscope::default();
64 let spectroscope = Spectroscope::from(source);
65
66 App {
67 graph,
68 oscilloscope,
69 vectorscope,
70 spectroscope,
71 mode: CurrentDisplayMode::Oscilloscope,
72 channels: source.channels as u8,
73 }
74 }
75
76 pub fn run<T: Backend>(
77 &mut self,
78 mut source: Box<dyn DataSource<f64>>,
79 mut terminal: Terminal<T>,
80 ) -> Result<(), io::Error> {
81 let mut fps = 0;
82 let mut framerate = 0;
83 let mut last_poll = Instant::now();
84 let mut channels = Matrix::default();
85
86 loop {
87 let data = source.recv().ok_or(io::Error::new(
88 io::ErrorKind::BrokenPipe,
89 "data source returned null",
90 ))?;
91
92 if !self.graph.pause {
93 channels = data;
94 }
95
96 fps += 1;
97
98 if last_poll.elapsed().as_secs() >= 1 {
99 framerate = fps;
100 fps = 0;
101 last_poll = Instant::now();
102 }
103
104 {
105 let mut datasets = Vec::new();
106 let graph = self.graph.clone(); if self.graph.references {
108 datasets.append(&mut self.current_display_mut().references(&graph));
109 }
110 datasets.append(&mut self.current_display_mut().process(&graph, &channels));
111 terminal.draw(|f| {
112 let mut size = f.area();
113 if self.graph.show_ui {
114 f.render_widget(
115 make_header(
116 &self.graph,
117 &self.current_display().header(&self.graph),
118 self.current_display().mode_str(),
119 framerate,
120 self.graph.pause,
121 ),
122 Rect {
123 x: size.x,
124 y: size.y,
125 width: size.width,
126 height: 1,
127 }, );
129 size.height -= 1;
130 size.y += 1;
131 }
132 let chart = Chart::new(datasets.iter().map(|x| x.into()).collect())
133 .x_axis(self.current_display().axis(&self.graph, Dimension::X)) .y_axis(self.current_display().axis(&self.graph, Dimension::Y));
135 f.render_widget(chart, size)
136 })?;
137 }
138
139 while event::poll(Duration::from_millis(0))? {
140 let event = event::read()?;
142
143 if self.process_events(event.clone())? {
144 return Ok(());
145 }
146 self.current_display_mut().handle(event);
147 }
148 }
149 }
150
151 fn current_display_mut(&mut self) -> &mut dyn DisplayMode {
152 match self.mode {
153 CurrentDisplayMode::Oscilloscope => &mut self.oscilloscope as &mut dyn DisplayMode,
154 CurrentDisplayMode::Vectorscope => &mut self.vectorscope as &mut dyn DisplayMode,
155 CurrentDisplayMode::Spectroscope => &mut self.spectroscope as &mut dyn DisplayMode,
156 }
157 }
158
159 fn current_display(&self) -> &dyn DisplayMode {
160 match self.mode {
161 CurrentDisplayMode::Oscilloscope => &self.oscilloscope as &dyn DisplayMode,
162 CurrentDisplayMode::Vectorscope => &self.vectorscope as &dyn DisplayMode,
163 CurrentDisplayMode::Spectroscope => &self.spectroscope as &dyn DisplayMode,
164 }
165 }
166
167 fn process_events(&mut self, event: Event) -> Result<bool, io::Error> {
168 let mut quit = false;
169 if let Event::Key(key) = event {
170 if let KeyModifiers::CONTROL = key.modifiers {
171 match key.code {
172 KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('w') => quit = true,
174 _ => {}
175 }
176 }
177 let magnitude = match key.modifiers {
178 KeyModifiers::SHIFT => 10.0,
179 KeyModifiers::CONTROL => 5.0,
180 KeyModifiers::ALT => 0.2,
181 _ => 1.0,
182 };
183 match key.code {
184 KeyCode::Up => update_value_f(&mut self.graph.scale, 0.01, magnitude, 0.0..10.0), KeyCode::Down => update_value_f(&mut self.graph.scale, -0.01, magnitude, 0.0..10.0), KeyCode::Right => update_value_i(
187 &mut self.graph.samples,
188 true,
189 25,
190 magnitude,
191 0..self.graph.width * 2,
192 ),
193 KeyCode::Left => update_value_i(
194 &mut self.graph.samples,
195 false,
196 25,
197 magnitude,
198 0..self.graph.width * 2,
199 ),
200 KeyCode::Char('q') => quit = true,
201 KeyCode::Char(' ') => self.graph.pause = !self.graph.pause,
202 KeyCode::Char('s') => self.graph.scatter = !self.graph.scatter,
203 KeyCode::Char('h') => self.graph.show_ui = !self.graph.show_ui,
204 KeyCode::Char('r') => self.graph.references = !self.graph.references,
205 KeyCode::Tab => {
206 match self.mode {
208 CurrentDisplayMode::Oscilloscope => {
209 self.mode = CurrentDisplayMode::Vectorscope
210 }
211 CurrentDisplayMode::Vectorscope => {
212 self.mode = CurrentDisplayMode::Spectroscope
213 }
214 CurrentDisplayMode::Spectroscope => {
215 self.mode = CurrentDisplayMode::Oscilloscope
216 }
217 }
218 }
219 KeyCode::Esc => {
220 self.graph.samples = self.graph.width;
221 self.graph.scale = 1.;
222 }
223 _ => {}
224 }
225 };
226
227 Ok(quit)
228 }
229}
230
231fn make_header<'a>(
234 cfg: &GraphConfig,
235 module_header: &'a str,
236 kind_o_scope: &'static str,
237 fps: usize,
238 pause: bool,
239) -> Table<'a> {
240 Table::new(
241 vec![Row::new(vec![
242 Cell::from(format!("{}::scope-tui", kind_o_scope)).style(
243 Style::default()
244 .fg(*cfg.palette.first().expect("empty palette?"))
245 .add_modifier(Modifier::BOLD),
246 ),
247 Cell::from(module_header),
248 Cell::from(format!("-{:.2}x+", cfg.scale)),
249 Cell::from(format!("{}/{} spf", cfg.samples, cfg.width)),
250 Cell::from(format!("{}fps", fps)),
251 Cell::from(if cfg.scatter { "***" } else { "---" }),
252 Cell::from(if pause { "||" } else { "|>" }),
253 ])],
254 vec![
255 Constraint::Percentage(35),
256 Constraint::Percentage(25),
257 Constraint::Percentage(7),
258 Constraint::Percentage(13),
259 Constraint::Percentage(6),
260 Constraint::Percentage(6),
261 Constraint::Percentage(6),
262 ],
263 )
264 .style(Style::default().fg(cfg.labels_color))
265}