mirror of
https://github.com/alemidev/scope-tui.git
synced 2024-11-23 22:24:48 +01:00
alemi
771987063d
now splitting stream in channels and parsing stream format are separate but handled by the source, so that cpal source can skip format parsing. added some nicer types, and also range now is +-1 because way easier than 32k sorry this is a huge commit, ive been messing with it for a while and changed a lot across whole project, at this point i'm just committing it because it can only get worse ehe
232 lines
7.4 KiB
Rust
232 lines
7.4 KiB
Rust
|
|
use std::{io, ops::Range, time::{Duration, Instant}};
|
|
use ratatui::{
|
|
style::Color, widgets::{Table, Row, Cell}, symbols::Marker,
|
|
backend::Backend,
|
|
widgets::Chart,
|
|
Terminal, style::{Style, Modifier}, layout::{Rect, Constraint}
|
|
};
|
|
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
|
|
|
|
use crate::{display::{oscilloscope::Oscilloscope, spectroscope::Spectroscope, vectorscope::Vectorscope, Dimension, DisplayMode, GraphConfig}, input::{Matrix, DataSource}};
|
|
|
|
pub enum CurrentDisplayMode {
|
|
Oscilloscope,
|
|
Vectorscope,
|
|
Spectroscope,
|
|
}
|
|
|
|
pub struct App {
|
|
#[allow(unused)] channels: u8,
|
|
graph: GraphConfig,
|
|
oscilloscope: Oscilloscope,
|
|
vectorscope: Vectorscope,
|
|
spectroscope: Spectroscope,
|
|
mode: CurrentDisplayMode,
|
|
}
|
|
|
|
// TODO another way to build this that doesn't require getting cli args directly!!!
|
|
impl From::<&crate::ScopeArgs> for App {
|
|
fn from(args: &crate::ScopeArgs) -> Self {
|
|
let graph = GraphConfig {
|
|
axis_color: Color::DarkGray,
|
|
labels_color: Color::Cyan,
|
|
palette: vec![Color::Red, Color::Yellow, Color::Green, Color::Magenta],
|
|
scale: args.scale as f64,
|
|
width: args.buffer, // TODO also make bit depth customizable
|
|
samples: args.buffer,
|
|
sampling_rate: args.sample_rate,
|
|
references: !args.no_reference,
|
|
show_ui: !args.no_ui,
|
|
scatter: args.scatter,
|
|
pause: false,
|
|
marker_type: if args.no_braille {
|
|
Marker::Dot
|
|
} else {
|
|
Marker::Braille
|
|
},
|
|
};
|
|
|
|
let oscilloscope = Oscilloscope::from_args(args);
|
|
let vectorscope = Vectorscope::from_args(args);
|
|
let spectroscope = Spectroscope::from_args(args);
|
|
|
|
App {
|
|
graph, oscilloscope, vectorscope, spectroscope,
|
|
mode: CurrentDisplayMode::Oscilloscope,
|
|
channels: args.channels as u8,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl App {
|
|
pub fn run<T : Backend>(&mut self, mut source: Box<dyn DataSource<f64>>, terminal: &mut Terminal<T>) -> Result<(), io::Error> {
|
|
let mut fps = 0;
|
|
let mut framerate = 0;
|
|
let mut last_poll = Instant::now();
|
|
let mut channels = Matrix::default();
|
|
|
|
loop {
|
|
let data = source.recv()
|
|
.ok_or(io::Error::new(io::ErrorKind::BrokenPipe, "data source returned null"))?;
|
|
|
|
if !self.graph.pause {
|
|
channels = data;
|
|
}
|
|
|
|
fps += 1;
|
|
|
|
if last_poll.elapsed().as_secs() >= 1 {
|
|
framerate = fps;
|
|
fps = 0;
|
|
last_poll = Instant::now();
|
|
}
|
|
|
|
{
|
|
let mut datasets = Vec::new();
|
|
let graph = self.graph.clone(); // TODO cheap fix...
|
|
if self.graph.references {
|
|
datasets.append(&mut self.current_display_mut().references(&graph));
|
|
}
|
|
datasets.append(&mut self.current_display_mut().process(&graph, &channels));
|
|
terminal.draw(|f| {
|
|
let mut size = f.size();
|
|
if self.graph.show_ui {
|
|
f.render_widget(
|
|
make_header(&self.graph, &self.current_display().header(&self.graph), self.current_display().mode_str(), framerate, self.graph.pause),
|
|
Rect { x: size.x, y: size.y, width: size.width, height:1 } // a 1px line at the top
|
|
);
|
|
size.height -= 1;
|
|
size.y += 1;
|
|
}
|
|
let chart = Chart::new(datasets.iter().map(|x| x.into()).collect())
|
|
.x_axis(self.current_display().axis(&self.graph, Dimension::X)) // TODO allow to have axis sometimes?
|
|
.y_axis(self.current_display().axis(&self.graph, Dimension::Y));
|
|
f.render_widget(chart, size)
|
|
})?;
|
|
}
|
|
|
|
while event::poll(Duration::from_millis(0))? { // process all enqueued events
|
|
let event = event::read()?;
|
|
|
|
if self.process_events(event.clone())? { return Ok(()); }
|
|
self.current_display_mut().handle(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn current_display_mut(&mut self) -> &mut dyn DisplayMode {
|
|
match self.mode {
|
|
CurrentDisplayMode::Oscilloscope => &mut self.oscilloscope as &mut dyn DisplayMode,
|
|
CurrentDisplayMode::Vectorscope => &mut self.vectorscope as &mut dyn DisplayMode,
|
|
CurrentDisplayMode::Spectroscope => &mut self.spectroscope as &mut dyn DisplayMode,
|
|
}
|
|
}
|
|
|
|
fn current_display(&self) -> &dyn DisplayMode {
|
|
match self.mode {
|
|
CurrentDisplayMode::Oscilloscope => &self.oscilloscope as &dyn DisplayMode,
|
|
CurrentDisplayMode::Vectorscope => &self.vectorscope as &dyn DisplayMode,
|
|
CurrentDisplayMode::Spectroscope => &self.spectroscope as &dyn DisplayMode,
|
|
}
|
|
}
|
|
|
|
fn process_events(&mut self, event: Event) -> Result<bool, io::Error> {
|
|
let mut quit = false;
|
|
if let Event::Key(key) = event {
|
|
if let KeyModifiers::CONTROL = key.modifiers {
|
|
match key.code { // mimic other programs shortcuts to quit, for user friendlyness
|
|
KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('w') => quit = true,
|
|
_ => {},
|
|
}
|
|
}
|
|
let magnitude = match key.modifiers {
|
|
KeyModifiers::SHIFT => 10.0,
|
|
KeyModifiers::CONTROL => 5.0,
|
|
KeyModifiers::ALT => 0.2,
|
|
_ => 1.0,
|
|
};
|
|
match key.code {
|
|
KeyCode::Up => update_value_f(&mut self.graph.scale, 0.01, magnitude, 0.0..1.5), // inverted to act as zoom
|
|
KeyCode::Down => update_value_f(&mut self.graph.scale, -0.01, magnitude, 0.0..1.5), // inverted to act as zoom
|
|
KeyCode::Right => update_value_i(&mut self.graph.samples, true, 25, magnitude, 0..self.graph.width*2),
|
|
KeyCode::Left => update_value_i(&mut self.graph.samples, false, 25, magnitude, 0..self.graph.width*2),
|
|
KeyCode::Char('q') => quit = true,
|
|
KeyCode::Char(' ') => self.graph.pause = !self.graph.pause,
|
|
KeyCode::Char('s') => self.graph.scatter = !self.graph.scatter,
|
|
KeyCode::Char('h') => self.graph.show_ui = !self.graph.show_ui,
|
|
KeyCode::Char('r') => self.graph.references = !self.graph.references,
|
|
KeyCode::Tab => { // switch modes
|
|
match self.mode {
|
|
CurrentDisplayMode::Oscilloscope => self.mode = CurrentDisplayMode::Vectorscope,
|
|
CurrentDisplayMode::Vectorscope => self.mode = CurrentDisplayMode::Spectroscope,
|
|
CurrentDisplayMode::Spectroscope => self.mode = CurrentDisplayMode::Oscilloscope,
|
|
}
|
|
},
|
|
KeyCode::Esc => {
|
|
self.graph.samples = self.graph.width;
|
|
self.graph.scale = 1.;
|
|
},
|
|
_ => {},
|
|
}
|
|
};
|
|
|
|
Ok(quit)
|
|
}
|
|
}
|
|
|
|
// TODO can these be removed or merged somewhere else?
|
|
|
|
pub fn update_value_f(val: &mut f64, base: f64, magnitude: f64, range: Range<f64>) {
|
|
let delta = base * magnitude;
|
|
if *val + delta > range.end {
|
|
*val = range.end
|
|
} else if *val + delta < range.start {
|
|
*val = range.start
|
|
} else {
|
|
*val += delta;
|
|
}
|
|
}
|
|
|
|
pub fn update_value_i(val: &mut u32, inc: bool, base: u32, magnitude: f64, range: Range<u32>) {
|
|
let delta = (base as f64 * magnitude) as u32;
|
|
if inc {
|
|
if range.end - delta < *val {
|
|
*val = range.end
|
|
} else {
|
|
*val += delta
|
|
}
|
|
} else if range.start + delta > *val {
|
|
*val = range.start
|
|
} else {
|
|
*val -= delta
|
|
}
|
|
}
|
|
|
|
fn make_header<'a>(cfg: &GraphConfig, module_header: &'a str, kind_o_scope: &'static str, fps: usize, pause: bool) -> Table<'a> {
|
|
Table::new(
|
|
vec![
|
|
Row::new(
|
|
vec![
|
|
Cell::from(format!("{}::scope-tui", kind_o_scope)).style(Style::default().fg(*cfg.palette.first().expect("empty palette?")).add_modifier(Modifier::BOLD)),
|
|
Cell::from(module_header),
|
|
Cell::from(format!("-{:.2}x+", cfg.scale)),
|
|
Cell::from(format!("{}/{} spf", cfg.samples, cfg.width)),
|
|
Cell::from(format!("{}fps", fps)),
|
|
Cell::from(if cfg.scatter { "***" } else { "---" }),
|
|
Cell::from(if pause { "||" } else { "|>" }),
|
|
]
|
|
)
|
|
],
|
|
vec![
|
|
Constraint::Percentage(35),
|
|
Constraint::Percentage(25),
|
|
Constraint::Percentage(7),
|
|
Constraint::Percentage(13),
|
|
Constraint::Percentage(6),
|
|
Constraint::Percentage(6),
|
|
Constraint::Percentage(6)
|
|
]
|
|
)
|
|
.style(Style::default().fg(cfg.labels_color))
|
|
}
|