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