mirror of
https://github.com/alemidev/scope-tui.git
synced 2024-11-23 22:24:48 +01:00
chore: moved run_app in app file
This commit is contained in:
parent
72a7b55c7f
commit
32a9819106
2 changed files with 226 additions and 237 deletions
219
src/app.rs
219
src/app.rs
|
@ -124,3 +124,222 @@ impl From::<&crate::Args> for App {
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io::Error> {
|
||||||
|
// prepare globals
|
||||||
|
let mut buffer : Vec<u8> = vec![0; args.buffer as usize];
|
||||||
|
let mut app = App::from(&args);
|
||||||
|
let fmt = Signed16PCM{}; // TODO some way to choose this?
|
||||||
|
|
||||||
|
let mut pause = false;
|
||||||
|
|
||||||
|
// setup audio capture
|
||||||
|
let spec = Spec {
|
||||||
|
format: Format::S16NE,
|
||||||
|
channels: 2,
|
||||||
|
rate: args.sample_rate,
|
||||||
|
};
|
||||||
|
assert!(spec.is_valid());
|
||||||
|
|
||||||
|
let dev = match &args.device {
|
||||||
|
Some(d) => Some(d.as_str()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let s = match Simple::new(
|
||||||
|
None, // Use the default server
|
||||||
|
"ScopeTUI", // Our application’s name
|
||||||
|
Direction::Record, // We want a record stream
|
||||||
|
dev, // Use requested device, or default
|
||||||
|
"Music", // Description of our stream
|
||||||
|
&spec, // Our sample format
|
||||||
|
None, // Use default channel map
|
||||||
|
Some(&BufferAttr {
|
||||||
|
maxlength: args.server_buffer * args.buffer,
|
||||||
|
fragsize: args.buffer,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
println!("[!] Could not connect to pulseaudio : {:?}", e);
|
||||||
|
return Err(io::Error::new(ErrorKind::Other, "could not connect to pulseaudio"));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fps = 0;
|
||||||
|
let mut framerate = 0;
|
||||||
|
let mut last_poll = Instant::now();
|
||||||
|
let mut channels = vec![];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match s.read(&mut buffer) {
|
||||||
|
Ok(()) => {},
|
||||||
|
Err(e) => {
|
||||||
|
println!("[!] Could not read data from pulseaudio : {:?}", e);
|
||||||
|
return Err(io::Error::new(ErrorKind::Other, "could not read from pulseaudio"));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pause {
|
||||||
|
channels = fmt.oscilloscope(&mut buffer, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.cfg.triggering {
|
||||||
|
// TODO allow to customize channel to use for triggering and threshold
|
||||||
|
if let Some(ch) = channels.get(0) {
|
||||||
|
let mut discard = 0;
|
||||||
|
for i in 0..ch.len() { // seek to first sample rising through threshold
|
||||||
|
if i + 1 < ch.len() && ch[i] <= 0.0 && ch[i+1] > 0.0 { // triggered
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
discard += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ch in channels.iter_mut() {
|
||||||
|
*ch = ch[discard..].to_vec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut measures;
|
||||||
|
|
||||||
|
if app.cfg.vectorscope {
|
||||||
|
measures = vec![];
|
||||||
|
for chunk in channels.chunks(2) {
|
||||||
|
let mut tmp = vec![];
|
||||||
|
for i in 0..chunk[0].len() {
|
||||||
|
tmp.push((chunk[0][i] as f64, chunk[1][i] as f64));
|
||||||
|
}
|
||||||
|
// split it in two so the math downwards still works the same
|
||||||
|
let pivot = tmp.len() / 2;
|
||||||
|
measures.push(tmp[pivot..].to_vec()); // put more recent first
|
||||||
|
measures.push(tmp[..pivot].to_vec());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
measures = vec![vec![]; channels.len()];
|
||||||
|
for i in 0..channels[0].len() {
|
||||||
|
for j in 0..channels.len() {
|
||||||
|
measures[j].push((i as f64, channels[j][i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut datasets = vec![];
|
||||||
|
|
||||||
|
if app.cfg.references {
|
||||||
|
datasets.push(data_set("", &app.references.x, app.cfg.marker_type, GraphType::Line, app.cfg.axis_color));
|
||||||
|
datasets.push(data_set("", &app.references.y, app.cfg.marker_type, GraphType::Line, app.cfg.axis_color));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ds_names = if app.cfg.vectorscope { vec!["1", "2"] } else { vec!["R", "L"] };
|
||||||
|
let palette : Vec<Color> = app.cfg.palette.iter().rev().map(|x| x.clone()).collect();
|
||||||
|
|
||||||
|
for (i, ds) in measures.iter().rev().enumerate() {
|
||||||
|
datasets.push(data_set(ds_names[i], ds, app.cfg.marker_type, app.cfg.graph_type, palette[i % palette.len()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
fps += 1;
|
||||||
|
|
||||||
|
if last_poll.elapsed().as_secs() >= 1 {
|
||||||
|
framerate = fps;
|
||||||
|
fps = 0;
|
||||||
|
last_poll = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.draw(|f| {
|
||||||
|
let size = f.size();
|
||||||
|
let chart = Chart::new(datasets)
|
||||||
|
.block(block(&app, args.sample_rate as f32, framerate))
|
||||||
|
.x_axis(axis(&app, Dimension::X)) // TODO allow to have axis sometimes?
|
||||||
|
.y_axis(axis(&app, Dimension::Y));
|
||||||
|
f.render_widget(chart, size)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(Event::Key(key)) = poll_event()? {
|
||||||
|
match key.modifiers {
|
||||||
|
KeyModifiers::CONTROL => {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('w') => break,
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') => break,
|
||||||
|
KeyCode::Char(' ') => pause = !pause,
|
||||||
|
KeyCode::Char('=') => app.update_scale(-1000),
|
||||||
|
KeyCode::Char('-') => app.update_scale(1000),
|
||||||
|
KeyCode::Char('+') => app.update_scale(-100),
|
||||||
|
KeyCode::Char('_') => app.update_scale(100),
|
||||||
|
KeyCode::Char('v') => app.cfg.vectorscope = !app.cfg.vectorscope,
|
||||||
|
KeyCode::Char('s') => app.set_scatter(!app.scatter()), // TODO no funcs
|
||||||
|
KeyCode::Char('h') => app.cfg.references = !app.cfg.references,
|
||||||
|
KeyCode::Char('t') => app.cfg.triggering = !app.cfg.triggering,
|
||||||
|
KeyCode::Up => {},
|
||||||
|
KeyCode::Down => {},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.update_values();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO these functions probably shouldn't be here
|
||||||
|
|
||||||
|
fn poll_event() -> Result<Option<Event>, std::io::Error> {
|
||||||
|
if event::poll(Duration::from_millis(0))? {
|
||||||
|
Ok(Some(event::read()?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_set<'a>(
|
||||||
|
name: &'a str,
|
||||||
|
data: &'a [(f64, f64)],
|
||||||
|
marker_type: symbols::Marker,
|
||||||
|
graph_type: GraphType,
|
||||||
|
axis_color: Color
|
||||||
|
) -> Dataset<'a> {
|
||||||
|
Dataset::default()
|
||||||
|
.name(name)
|
||||||
|
.marker(marker_type)
|
||||||
|
.graph_type(graph_type)
|
||||||
|
.style(Style::default().fg(axis_color))
|
||||||
|
.data(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(app: &App, dim: Dimension) -> Axis {
|
||||||
|
let mut a = Axis::default();
|
||||||
|
if app.cfg.references {
|
||||||
|
a = a.title(Span::styled(app.name(&dim), Style::default().fg(Color::Cyan)));
|
||||||
|
}
|
||||||
|
a.style(Style::default().fg(app.cfg.axis_color))
|
||||||
|
.bounds(app.bounds(&dim))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block(app: &App, sample_rate: f32, framerate: u32) -> Block {
|
||||||
|
let mut b = Block::default();
|
||||||
|
|
||||||
|
if app.cfg.references {
|
||||||
|
b = b.title(
|
||||||
|
Span::styled(
|
||||||
|
format!(
|
||||||
|
"TUI {} -- {}{} mode -- range {} -- {} samples -- {:.1} kHz -- {} fps",
|
||||||
|
if app.cfg.vectorscope { "Vectorscope" } else { "Oscilloscope" },
|
||||||
|
if app.cfg.triggering { "triggered " } else { "" },
|
||||||
|
if app.scatter() { "scatter" } else { "line" },
|
||||||
|
app.cfg.scale, app.cfg.width, sample_rate / 1000.0, framerate,
|
||||||
|
),
|
||||||
|
Style::default().add_modifier(Modifier::BOLD).fg(Color::Yellow))
|
||||||
|
).title_alignment(Alignment::Center);
|
||||||
|
}
|
||||||
|
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
244
src/main.rs
244
src/main.rs
|
@ -3,35 +3,24 @@ mod app;
|
||||||
mod config;
|
mod config;
|
||||||
mod music;
|
mod music;
|
||||||
|
|
||||||
use std::{io::{self, ErrorKind}, time::{Duration, Instant}};
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::{CrosstermBackend, Backend},
|
backend::CrosstermBackend,
|
||||||
widgets::{Block, Chart, Axis, Dataset, GraphType},
|
Terminal,
|
||||||
// layout::{Layout, Constraint, Direction},
|
|
||||||
Terminal, text::Span, style::{Style, Color, Modifier}, symbols, layout::Alignment
|
|
||||||
};
|
};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, DisableMouseCapture, Event, KeyCode, KeyModifiers},
|
event::DisableMouseCapture, execute,
|
||||||
execute,
|
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
|
|
||||||
use libpulse_simple_binding::Simple;
|
|
||||||
use libpulse_binding::{stream::Direction, def::BufferAttr};
|
|
||||||
use libpulse_binding::sample::{Spec, Format};
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use parser::{SampleParser, Signed16PCM};
|
|
||||||
|
|
||||||
use crate::app::App;
|
|
||||||
use crate::music::Note;
|
use crate::music::Note;
|
||||||
use crate::config::Dimension;
|
use crate::app::run_app;
|
||||||
|
|
||||||
/// A simple oscilloscope/vectorscope for your terminal
|
/// A simple oscilloscope/vectorscope for your terminal
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
pub struct Args {
|
||||||
/// Audio device to attach to
|
/// Audio device to attach to
|
||||||
device: Option<String>,
|
device: Option<String>,
|
||||||
|
|
||||||
|
@ -76,7 +65,7 @@ struct Args {
|
||||||
no_braille: bool,
|
no_braille: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), io::Error> {
|
fn main() -> Result<(), std::io::Error> {
|
||||||
let mut args = Args::parse();
|
let mut args = Args::parse();
|
||||||
|
|
||||||
if let Some(txt) = &args.tune { // TODO make it less jank
|
if let Some(txt) = &args.tune { // TODO make it less jank
|
||||||
|
@ -92,7 +81,7 @@ fn main() -> Result<(), io::Error> {
|
||||||
|
|
||||||
// setup terminal
|
// setup terminal
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = std::io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen)?;
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
@ -116,222 +105,3 @@ fn main() -> Result<(), io::Error> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io::Error> {
|
|
||||||
// prepare globals
|
|
||||||
let mut buffer : Vec<u8> = vec![0; args.buffer as usize];
|
|
||||||
let mut app = App::from(&args);
|
|
||||||
let fmt = Signed16PCM{}; // TODO some way to choose this?
|
|
||||||
|
|
||||||
let mut pause = false;
|
|
||||||
|
|
||||||
// setup audio capture
|
|
||||||
let spec = Spec {
|
|
||||||
format: Format::S16NE,
|
|
||||||
channels: 2,
|
|
||||||
rate: args.sample_rate,
|
|
||||||
};
|
|
||||||
assert!(spec.is_valid());
|
|
||||||
|
|
||||||
let dev = match &args.device {
|
|
||||||
Some(d) => Some(d.as_str()),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let s = match Simple::new(
|
|
||||||
None, // Use the default server
|
|
||||||
"ScopeTUI", // Our application’s name
|
|
||||||
Direction::Record, // We want a record stream
|
|
||||||
dev, // Use requested device, or default
|
|
||||||
"Music", // Description of our stream
|
|
||||||
&spec, // Our sample format
|
|
||||||
None, // Use default channel map
|
|
||||||
Some(&BufferAttr {
|
|
||||||
maxlength: args.server_buffer * args.buffer,
|
|
||||||
fragsize: args.buffer,
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
println!("[!] Could not connect to pulseaudio : {:?}", e);
|
|
||||||
return Err(io::Error::new(ErrorKind::Other, "could not connect to pulseaudio"));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut fps = 0;
|
|
||||||
let mut framerate = 0;
|
|
||||||
let mut last_poll = Instant::now();
|
|
||||||
let mut channels = vec![];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match s.read(&mut buffer) {
|
|
||||||
Ok(()) => {},
|
|
||||||
Err(e) => {
|
|
||||||
println!("[!] Could not read data from pulseaudio : {:?}", e);
|
|
||||||
return Err(io::Error::new(ErrorKind::Other, "could not read from pulseaudio"));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pause {
|
|
||||||
channels = fmt.oscilloscope(&mut buffer, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if app.cfg.triggering {
|
|
||||||
// TODO allow to customize channel to use for triggering and threshold
|
|
||||||
if let Some(ch) = channels.get(0) {
|
|
||||||
let mut discard = 0;
|
|
||||||
for i in 0..ch.len() { // seek to first sample rising through threshold
|
|
||||||
if i + 1 < ch.len() && ch[i] <= 0.0 && ch[i+1] > 0.0 { // triggered
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
discard += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ch in channels.iter_mut() {
|
|
||||||
*ch = ch[discard..].to_vec();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut measures;
|
|
||||||
|
|
||||||
if app.cfg.vectorscope {
|
|
||||||
measures = vec![];
|
|
||||||
for chunk in channels.chunks(2) {
|
|
||||||
let mut tmp = vec![];
|
|
||||||
for i in 0..chunk[0].len() {
|
|
||||||
tmp.push((chunk[0][i] as f64, chunk[1][i] as f64));
|
|
||||||
}
|
|
||||||
// split it in two so the math downwards still works the same
|
|
||||||
let pivot = tmp.len() / 2;
|
|
||||||
measures.push(tmp[pivot..].to_vec()); // put more recent first
|
|
||||||
measures.push(tmp[..pivot].to_vec());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
measures = vec![vec![]; channels.len()];
|
|
||||||
for i in 0..channels[0].len() {
|
|
||||||
for j in 0..channels.len() {
|
|
||||||
measures[j].push((i as f64, channels[j][i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut datasets = vec![];
|
|
||||||
|
|
||||||
if app.cfg.references {
|
|
||||||
datasets.push(data_set("", &app.references.x, app.cfg.marker_type, GraphType::Line, app.cfg.axis_color));
|
|
||||||
datasets.push(data_set("", &app.references.y, app.cfg.marker_type, GraphType::Line, app.cfg.axis_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
let ds_names = if app.cfg.vectorscope { vec!["1", "2"] } else { vec!["R", "L"] };
|
|
||||||
let palette : Vec<Color> = app.cfg.palette.iter().rev().map(|x| x.clone()).collect();
|
|
||||||
|
|
||||||
for (i, ds) in measures.iter().rev().enumerate() {
|
|
||||||
datasets.push(data_set(ds_names[i], ds, app.cfg.marker_type, app.cfg.graph_type, palette[i % palette.len()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
fps += 1;
|
|
||||||
|
|
||||||
if last_poll.elapsed().as_secs() >= 1 {
|
|
||||||
framerate = fps;
|
|
||||||
fps = 0;
|
|
||||||
last_poll = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.draw(|f| {
|
|
||||||
let size = f.size();
|
|
||||||
let chart = Chart::new(datasets)
|
|
||||||
.block(block(&app, args.sample_rate as f32, framerate))
|
|
||||||
.x_axis(axis(&app, Dimension::X)) // TODO allow to have axis sometimes?
|
|
||||||
.y_axis(axis(&app, Dimension::Y));
|
|
||||||
f.render_widget(chart, size)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(Event::Key(key)) = poll_event()? {
|
|
||||||
match key.modifiers {
|
|
||||||
KeyModifiers::CONTROL => {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('w') => break,
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Char('q') => break,
|
|
||||||
KeyCode::Char(' ') => pause = !pause,
|
|
||||||
KeyCode::Char('=') => app.update_scale(-1000),
|
|
||||||
KeyCode::Char('-') => app.update_scale(1000),
|
|
||||||
KeyCode::Char('+') => app.update_scale(-100),
|
|
||||||
KeyCode::Char('_') => app.update_scale(100),
|
|
||||||
KeyCode::Char('v') => app.cfg.vectorscope = !app.cfg.vectorscope,
|
|
||||||
KeyCode::Char('s') => app.set_scatter(!app.scatter()), // TODO no funcs
|
|
||||||
KeyCode::Char('h') => app.cfg.references = !app.cfg.references,
|
|
||||||
KeyCode::Char('t') => app.cfg.triggering = !app.cfg.triggering,
|
|
||||||
KeyCode::Up => {},
|
|
||||||
KeyCode::Down => {},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
app.update_values();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO these functions probably shouldn't be here
|
|
||||||
|
|
||||||
fn poll_event() -> Result<Option<Event>, std::io::Error> {
|
|
||||||
if event::poll(Duration::from_millis(0))? {
|
|
||||||
Ok(Some(event::read()?))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_set<'a>(
|
|
||||||
name: &'a str,
|
|
||||||
data: &'a [(f64, f64)],
|
|
||||||
marker_type: symbols::Marker,
|
|
||||||
graph_type: GraphType,
|
|
||||||
axis_color: Color
|
|
||||||
) -> Dataset<'a> {
|
|
||||||
Dataset::default()
|
|
||||||
.name(name)
|
|
||||||
.marker(marker_type)
|
|
||||||
.graph_type(graph_type)
|
|
||||||
.style(Style::default().fg(axis_color))
|
|
||||||
.data(&data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn axis(app: &App, dim: Dimension) -> Axis {
|
|
||||||
let mut a = Axis::default();
|
|
||||||
if app.cfg.references {
|
|
||||||
a = a.title(Span::styled(app.name(&dim), Style::default().fg(Color::Cyan)));
|
|
||||||
}
|
|
||||||
a.style(Style::default().fg(app.cfg.axis_color))
|
|
||||||
.bounds(app.bounds(&dim))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block(app: &App, sample_rate: f32, framerate: u32) -> Block {
|
|
||||||
let mut b = Block::default();
|
|
||||||
|
|
||||||
if app.cfg.references {
|
|
||||||
b = b.title(
|
|
||||||
Span::styled(
|
|
||||||
format!(
|
|
||||||
"TUI {} -- {}{} mode -- range {} -- {} samples -- {:.1} kHz -- {} fps",
|
|
||||||
if app.cfg.vectorscope { "Vectorscope" } else { "Oscilloscope" },
|
|
||||||
if app.cfg.triggering { "triggered " } else { "" },
|
|
||||||
if app.scatter() { "scatter" } else { "line" },
|
|
||||||
app.cfg.scale, app.cfg.width, sample_rate / 1000.0, framerate,
|
|
||||||
),
|
|
||||||
Style::default().add_modifier(Modifier::BOLD).fg(Color::Yellow))
|
|
||||||
).title_alignment(Alignment::Center);
|
|
||||||
}
|
|
||||||
|
|
||||||
b
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue