From fc7f6f540761104e624d3712364f38a12d5c6cde Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 5 Jun 2024 15:40:26 +0300 Subject: [PATCH] wip: ratatui --- Cargo.toml | 2 +- src/engine.rs | 44 +++--- src/looper/handle.rs | 6 + src/looper/render.rs | 5 + src/main.rs | 315 ++++++++++++++++++++++------------------ src/mixer/handle.rs | 6 + src/mixer/render.rs | 5 + src/prelude.rs | 18 ++- src/render.rs | 27 ++++ src/sampler/handle.rs | 6 + src/sampler/render.rs | 5 + src/sequencer/handle.rs | 12 +- src/sequencer/mod.rs | 3 +- src/sequencer/render.rs | 227 ++++++++++++++++++++--------- src/transport/handle.rs | 6 +- src/transport/render.rs | 41 ++++++ 16 files changed, 485 insertions(+), 243 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 68b99266..62daaaad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" jack = "0.10" clap = { version = "4.5.4", features = [ "derive" ] } crossterm = "0.27" -ratatui = "0.26.3" +ratatui = { version = "0.26.3", features = [ "unstable-widget-ref" ] } diff --git a/src/engine.rs b/src/engine.rs index dc8aff7e..52547bd7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -10,6 +10,10 @@ pub trait Exitable { fn exited (&self) -> bool; } +pub trait HandleInput { + fn handle (&mut self, event: &Event) -> Result<(), Box>; +} + #[derive(Debug)] pub enum Event { /// An input event that must be handled. @@ -87,36 +91,32 @@ impl Engine { }) } - pub fn run ( - &mut self, - state: &mut T, - mut render: impl FnMut(&mut T, &mut Stdout, (u16, u16)) -> Result<(), Box>, - mut handle: impl FnMut(&mut T, &Event) -> Result<(), Box>, + pub fn run ( + &mut self, mut state: T, ) -> Result<(), Box> { - // JACK thread: - // Input thread: - // Render (main) thread: - crossterm::terminal::enable_raw_mode()?; - let stdout = &mut self.stdout; - stdout - .queue(crossterm::terminal::EnterAlternateScreen)? - .flush()?; + stdout().queue(EnterAlternateScreen)?.flush(); + enable_raw_mode()?; + let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; let sleep = std::time::Duration::from_millis(20); loop { - stdout - .queue(crossterm::terminal::BeginSynchronizedUpdate)? - .queue(Clear(ClearType::All))?; - render(state, stdout, (0, 0))?; - stdout - .queue(crossterm::terminal::EndSynchronizedUpdate)? - .flush()?; + //stdout() + //.queue(crossterm::terminal::BeginSynchronizedUpdate)? + //.queue(Clear(ClearType::All))?; + terminal.draw(|frame|{ + let area = frame.size(); + frame.render_widget(&state, area); + }); + //render(state, stdout, (0, 0))?; + //stdout() + //.queue(crossterm::terminal::EndSynchronizedUpdate)? + //.flush()?; // Handle event if present (`None` redraws) if let event = self.receiver.recv()? { - handle(state, &event)?; + state.handle(&event)?; } if state.exited() { self.exited.store(true, Ordering::Relaxed); - stdout + stdout() .queue(crossterm::terminal::LeaveAlternateScreen)? .flush()?; crossterm::terminal::disable_raw_mode()?; diff --git a/src/looper/handle.rs b/src/looper/handle.rs index f573801b..4d323c1c 100644 --- a/src/looper/handle.rs +++ b/src/looper/handle.rs @@ -1,6 +1,12 @@ use crate::prelude::*; use super::Looper; +impl HandleInput for Looper { + fn handle (&mut self, event: &Event) -> Result<(), Box> { + handle(self, event) + } +} + pub fn handle (state: &mut Looper, event: &Event) -> Result<(), Box> { Ok(()) } diff --git a/src/looper/render.rs b/src/looper/render.rs index 9f21c29a..541d54c3 100644 --- a/src/looper/render.rs +++ b/src/looper/render.rs @@ -1,6 +1,11 @@ use crate::prelude::*; use super::{Looper, ACTIONS}; +impl WidgetRef for Looper { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + } +} + pub fn render ( state: &mut Looper, stdout: &mut std::io::Stdout, diff --git a/src/main.rs b/src/main.rs index 03908ebd..ac69b43f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ pub mod sequencer; pub mod render; use crate::prelude::*; +use crate::render::ActionBar; fn main () -> Result<(), Box> { let cli = cli::Cli::parse(); @@ -28,97 +29,28 @@ fn main () -> Result<(), Box> { fn run_one (command: &cli::Command) -> Result<(), Box> { let mut engine = crate::engine::Engine::new(None)?; - match command { - cli::Command::Transport => engine.run( - &mut transport::Transport::new(engine.jack_client.as_client())?, - |state, stdout, mut offset| { - let (w, h) = render::render_toolbar_vertical(stdout, offset, &transport::ACTIONS)?; - offset.0 = offset.0 + w + 2; - transport::render(state, stdout, offset) - }, - |_, _|Ok(()) + transport::Transport::new(engine.jack_client.as_client())?, ), - cli::Command::Mixer => engine.run( - &mut mixer::Mixer::new()?, - |state, stdout, mut offset| { - let (w, h) = render::render_toolbar_vertical(stdout, offset, &mixer::ACTIONS)?; - offset.0 = offset.0 + w + 2; - mixer::render(state, stdout, offset) - }, - |_, _|Ok(()) + mixer::Mixer::new()?, ), - cli::Command::Looper => engine.run( - &mut looper::Looper::new()?, - |state, stdout, mut offset| { - let (w, h) = render::render_toolbar_vertical(stdout, offset, &looper::ACTIONS)?; - offset.0 = offset.0 + w + 2; - looper::render(state, stdout, offset) - }, - |_, _|Ok(()) + looper::Looper::new()?, ), - cli::Command::Sampler => engine.run( - &mut sampler::Sampler::new()?, - |state, stdout, mut offset| { - let (w, h) = render::render_toolbar_vertical(stdout, offset, &sampler::ACTIONS)?; - offset.0 = offset.0 + w + 2; - sampler::render(state, stdout, offset) - }, - |_, _|Ok(()) + sampler::Sampler::new()?, ), - cli::Command::Sequencer => engine.run( - &mut sequencer::Sequencer::new()?, - |state, stdout, mut offset| { - let (w, h) = render::render_toolbar_vertical(stdout, offset, &sequencer::ACTIONS)?; - offset.0 = offset.0 + w + 2; - sequencer::render(state, stdout, offset) - }, - |_, _|Ok(()) + sequencer::Sequencer::new()?, ), - } } fn run_all () -> Result<(), Box> { - let mut engine = crate::engine::Engine::new(None)?; - - struct App { - exited: bool, - mode: Mode, - transport: crate::transport::Transport, - mixer: crate::mixer::Mixer, - looper: crate::looper::Looper, - sampler: crate::sampler::Sampler, - sequencer: crate::sequencer::Sequencer, - } - - #[derive(PartialEq)] - enum Mode { - Transport, - Mixer, - Looper, - Sampler, - Sequencer - } - - impl Exitable for App { - fn exit (&mut self) { - self.exited = true - } - fn exited (&self) -> bool { - self.exited - } - } - - let mut actions = vec![]; - - let mut state = App { + engine.run(App { exited: false, mode: Mode::Sequencer, transport: transport::Transport::new(engine.jack_client.as_client())?, @@ -126,99 +58,196 @@ fn run_all () -> Result<(), Box> { mixer: mixer::Mixer::new()?, looper: looper::Looper::new()?, sampler: sampler::Sampler::new()?, - }; + actions: vec![], + }) +} - let render = |state: &mut App, stdout: &mut Stdout, mut offset: (u16, u16)| { - actions.clear(); - actions.extend_from_slice(&transport::ACTIONS); - match state.mode { - Mode::Transport => {}, - Mode::Mixer => actions.extend_from_slice(&mixer::ACTIONS), - Mode::Looper => actions.extend_from_slice(&looper::ACTIONS), - Mode::Sampler => actions.extend_from_slice(&sampler::ACTIONS), - Mode::Sequencer => actions.extend_from_slice(&sequencer::ACTIONS), - } +struct App { + exited: bool, + mode: Mode, + transport: crate::transport::Transport, + mixer: crate::mixer::Mixer, + looper: crate::looper::Looper, + sampler: crate::sampler::Sampler, + sequencer: crate::sequencer::Sequencer, + actions: Vec<(&'static str, &'static str)> +} - let (w, h) = render::render_toolbar_vertical(stdout, (offset.0, offset.1 + 1), &actions)?; - offset.0 = offset.0 + 20; +#[derive(PartialEq)] +enum Mode { + Transport, + Mixer, + Looper, + Sampler, + Sequencer +} - transport::render(&mut state.transport, stdout, (offset.0 + 1, 1))?; - render::render_box(stdout, Some("Transport"), offset.0, 0, 70, 4, - state.mode == Mode::Transport)?; +impl Exitable for App { + fn exit (&mut self) { + self.exited = true + } + fn exited (&self) -> bool { + self.exited + } +} - sequencer::render(&mut state.sequencer, stdout, (offset.0 + 1, 3))?; - render::render_box(stdout, Some("Sequencer"), offset.0, 5, 70, 6, - state.mode == Mode::Sequencer)?; +impl WidgetRef for App { + fn render_ref (&self, area: Rect, buffer: &mut Buffer) { + use ratatui::{widgets::*, style::Stylize}; + let column_constraints = vec![ + Constraint::Percentage(28), + Constraint::Percentage(72), + ]; + let row_constraints = vec![ + Constraint::Length(4), + Constraint::Length(14), + Constraint::Length(10), + Constraint::Length(10), + Constraint::Length(10), + ]; + let cols = Layout::default() + .direction(Direction::Horizontal) + .constraints(column_constraints) + .split(area); + let side = Layout::default() + .direction(Direction::Vertical) + .constraints(row_constraints.clone()) + .split(cols[0]); + let main = Layout::default() + .direction(Direction::Vertical) + .constraints(row_constraints) + .split(cols[1]); - sampler::render(&mut state.sampler, stdout, (offset.0 + 1, 10))?; - render::render_box(stdout, Some("Sampler"), offset.0, 12, 70, 4, - state.mode == Mode::Sampler)?; + Line::from(" Transport").render(side[0], buffer); + self.transport.render(main[0].inner(&Margin { + vertical: 0, + horizontal: 1, + }), buffer); + buffer.set_string( + area.x, main[1].y-1, "─".repeat(area.width as usize), Style::default().black().dim() + ); + Line::from(" Piano roll").render(side[1], buffer); + self.sequencer.render(main[1].inner(&Margin { + vertical: 0, + horizontal: 1, + }), buffer); + buffer.set_string( + area.x, main[2].y+1, "─".repeat(area.width as usize), Style::default().black().dim() + ); - mixer::render(&mut state.mixer, stdout, (offset.0 + 1, 18))?; - render::render_box(stdout, Some("Mixer"), offset.0, 17, 70, 9, - state.mode == Mode::Mixer)?; + //Block::default() + //.title("Sampler") + //.borders(Borders::ALL) + //.render(rows[2], buffer); - looper::render(&mut state.looper, stdout, (offset.0 + 1, 28))?; - render::render_box(stdout, Some("Looper"), offset.0, 27, 70, 6, - state.mode == Mode::Looper)?; + //Block::default() + //.title("Mixer") + //.borders(Borders::ALL) + //.render(rows[3], buffer); - Ok(()) - }; + //Block::default() + //.title("Looper") + //.borders(Borders::ALL) + //.render(rows[4], buffer); - let handle = |state: &mut App, event: &Event| { + + //let mut offset = (0, 0); + //let (w, h) = render::render_toolbar_vertical(stdout, (offset.0, offset.1 + 1), &actions)?; + //offset.0 = offset.0 + 20; + + //transport::render(&mut state.transport, stdout, (offset.0 + 1, 1))?; + //render::render_box(stdout, Some("Transport"), offset.0, 0, 70, 4, + //state.mode == Mode::Transport)?; + + //sequencer::render(&mut state.sequencer, stdout, (offset.0 + 1, 3))?; + //render::render_box(stdout, Some("Sequencer"), offset.0, 5, 70, 6, + //state.mode == Mode::Sequencer)?; + + //sampler::render(&mut state.sampler, stdout, (offset.0 + 1, 10))?; + //render::render_box(stdout, Some("Sampler"), offset.0, 12, 70, 4, + //state.mode == Mode::Sampler)?; + + //mixer::render(&mut state.mixer, stdout, (offset.0 + 1, 18))?; + //render::render_box(stdout, Some("Mixer"), offset.0, 17, 70, 9, + //state.mode == Mode::Mixer)?; + + //looper::render(&mut state.looper, stdout, (offset.0 + 1, 28))?; + //render::render_box(stdout, Some("Looper"), offset.0, 27, 70, 6, + //state.mode == Mode::Looper)?; + + //Ok(()) + } +} + +impl HandleInput for App { + fn handle (&mut self, event: &Event) -> Result<(), Box> { //println!("{event:?}"); if let Event::Input(crossterm::event::Event::Key(key)) = event { match key.code { KeyCode::Char('c') => { if key.modifiers == KeyModifiers::CONTROL { - state.exit(); + self.exit(); } }, KeyCode::Char(' ') => { if key.modifiers == KeyModifiers::SHIFT { - state.transport.play_from_start_or_stop_and_rewind(); + self.transport.play_from_start_or_stop_and_rewind(); } else { - state.transport.play_or_pause().unwrap(); + self.transport.play_or_pause().unwrap(); } }, - KeyCode::Tab => match state.mode { - Mode::Transport => state.mode = Mode::Sequencer, - Mode::Sequencer => state.mode = Mode::Sampler, - Mode::Sampler => state.mode = Mode::Mixer, - Mode::Mixer => state.mode = Mode::Looper, - Mode::Looper => state.mode = Mode::Transport, - _ => {} + KeyCode::Tab => { + match self.mode { + Mode::Transport => self.mode = Mode::Sequencer, + Mode::Sequencer => self.mode = Mode::Sampler, + Mode::Sampler => self.mode = Mode::Mixer, + Mode::Mixer => self.mode = Mode::Looper, + Mode::Looper => self.mode = Mode::Transport, + _ => {} + }; + self.actions.clear(); + self.actions.extend_from_slice(&transport::ACTIONS); + match self.mode { + Mode::Transport => {}, + Mode::Mixer => self.actions.extend_from_slice(&mixer::ACTIONS), + Mode::Looper => self.actions.extend_from_slice(&looper::ACTIONS), + Mode::Sampler => self.actions.extend_from_slice(&sampler::ACTIONS), + Mode::Sequencer => self.actions.extend_from_slice(&sequencer::ACTIONS), + } }, - KeyCode::BackTab => match state.mode { - Mode::Transport => state.mode = Mode::Looper, - Mode::Sequencer => state.mode = Mode::Transport, - Mode::Sampler => state.mode = Mode::Sequencer, - Mode::Mixer => state.mode = Mode::Sampler, - Mode::Looper => state.mode = Mode::Mixer, - _ => {} + KeyCode::BackTab => { + match self.mode { + Mode::Transport => self.mode = Mode::Looper, + Mode::Sequencer => self.mode = Mode::Transport, + Mode::Sampler => self.mode = Mode::Sequencer, + Mode::Mixer => self.mode = Mode::Sampler, + Mode::Looper => self.mode = Mode::Mixer, + _ => {} + }; + self.actions.clear(); + self.actions.extend_from_slice(&transport::ACTIONS); + match self.mode { + Mode::Transport => {}, + Mode::Mixer => self.actions.extend_from_slice(&mixer::ACTIONS), + Mode::Looper => self.actions.extend_from_slice(&looper::ACTIONS), + Mode::Sampler => self.actions.extend_from_slice(&sampler::ACTIONS), + Mode::Sequencer => self.actions.extend_from_slice(&sequencer::ACTIONS), + } }, - _ => match state.mode { - Mode::Transport => transport::handle( - &mut state.transport, &event - )?, - Mode::Mixer => mixer::handle( - &mut state.mixer, &event - )?, - Mode::Looper => looper::handle( - &mut state.looper, &event - )?, - Mode::Sampler => sampler::handle( - &mut state.sampler, &event - )?, - Mode::Sequencer => sequencer::handle( - &mut state.sequencer, &event - )?, + _ => match self.mode { + Mode::Transport => self.transport + .handle(&event)?, + Mode::Mixer => self.mixer + .handle(&event)?, + Mode::Looper => self.looper + .handle(&event)?, + Mode::Sampler => self.sampler + .handle(&event)?, + Mode::Sequencer => self.sequencer + .handle(&event)?, } } } Ok(()) - }; - - engine.run(&mut state, render, handle) - + } } diff --git a/src/mixer/handle.rs b/src/mixer/handle.rs index 57337e0a..31d1a742 100644 --- a/src/mixer/handle.rs +++ b/src/mixer/handle.rs @@ -1,6 +1,12 @@ use crate::prelude::*; use super::Mixer; +impl HandleInput for Mixer { + fn handle (&mut self, event: &Event) -> Result<(), Box> { + handle(self, event) + } +} + pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box> { if let Event::Input(crossterm::event::Event::Key(event)) = event { diff --git a/src/mixer/render.rs b/src/mixer/render.rs index 946e1b83..798b441d 100644 --- a/src/mixer/render.rs +++ b/src/mixer/render.rs @@ -1,6 +1,11 @@ use crate::prelude::*; use super::{Mixer, ACTIONS}; +impl WidgetRef for Mixer { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + } +} + pub fn render ( state: &mut Mixer, stdout: &mut Stdout, diff --git a/src/prelude.rs b/src/prelude.rs index c59d884d..2412ff2a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -19,9 +19,19 @@ pub use crossterm::{ QueueableCommand, event::{self, KeyCode, KeyModifiers}, cursor::{self, MoveTo, Show, Hide}, - terminal::{self, Clear, ClearType}, + terminal::{ + self, + Clear, ClearType, + EnterAlternateScreen, LeaveAlternateScreen, + enable_raw_mode, disable_raw_mode + }, style::*, }; +pub use ratatui::{ + prelude::*, + widgets::WidgetRef, + //style::Stylize, +}; pub use jack::{ AsyncClient, AudioIn, @@ -45,4 +55,8 @@ pub use jack::{ }; pub type BoxedProcessHandler = Box Control + Send>; pub type Jack = AsyncClient>; -pub use crate::engine::{Exitable, Event}; +pub use crate::engine::{ + Exitable, + HandleInput, + Event +}; diff --git a/src/render.rs b/src/render.rs index c27a73d7..1171d1ff 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,5 +1,32 @@ use crate::prelude::*; + //let render_with_actions = |actions| |state, frame: &mut Frame| { + //let layout = Layout::default() + //.direction(Direction::Horizontal) + //.constraints(vec![ + //Constraint::Percentage(30), + //Constraint::Percentage(70), + //]) + //.split(frame.size()); + //frame.render_widget_ref(ActionBar(actions), layout[0]); + //frame.render_widget_ref(state, layout[1]); + //Ok(()) + //}; + +pub struct ActionBar<'a>(pub &'a [(&'static str, &'static str)]); + +impl<'a> WidgetRef for ActionBar<'a> { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(self.0.iter().map(|_|Constraint::Length(3)).collect::>()) + .split(area); + for (index, action) in self.0.iter().enumerate() { + Line::raw(action.0).render(layout[index], buf); + } + } +} + pub fn render_toolbar_vertical ( stdout: &mut std::io::Stdout, offset: (u16, u16), diff --git a/src/sampler/handle.rs b/src/sampler/handle.rs index c4f5cf95..7a130ad0 100644 --- a/src/sampler/handle.rs +++ b/src/sampler/handle.rs @@ -1,6 +1,12 @@ use crate::prelude::*; use super::Sampler; +impl HandleInput for Sampler { + fn handle (&mut self, event: &Event) -> Result<(), Box> { + handle(self, event) + } +} + pub fn handle ( state: &mut Sampler, event: &Event diff --git a/src/sampler/render.rs b/src/sampler/render.rs index dff0fa3d..fbe8d81f 100644 --- a/src/sampler/render.rs +++ b/src/sampler/render.rs @@ -1,6 +1,11 @@ use crate::prelude::*; use super::Sampler; +impl WidgetRef for Sampler { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + } +} + pub fn render ( state: &mut Sampler, stdout: &mut Stdout, diff --git a/src/sequencer/handle.rs b/src/sequencer/handle.rs index 40d05dd1..460288e8 100644 --- a/src/sequencer/handle.rs +++ b/src/sequencer/handle.rs @@ -1,7 +1,13 @@ use crate::prelude::*; use super::Sequencer; -pub fn handle (state: &mut Sequencer, event: &Event) -> Result<(), Box> { +impl HandleInput for Sequencer { + fn handle (&mut self, event: &Event) -> Result<(), Box> { + handle(self, event) + } +} + +fn handle (state: &mut Sequencer, event: &Event) -> Result<(), Box> { if let Event::Input(crossterm::event::Event::Key(event)) = event { @@ -12,7 +18,7 @@ pub fn handle (state: &mut Sequencer, event: &Event) -> Result<(), Box { - state.cursor.0 = if state.cursor.0 >= 3 { + state.cursor.0 = if state.cursor.0 >= 23 { 0 } else { state.cursor.0 + 1 @@ -20,7 +26,7 @@ pub fn handle (state: &mut Sequencer, event: &Event) -> Result<(), Box { state.cursor.0 = if state.cursor.0 == 0 { - 3 + 23 } else { state.cursor.0 - 1 } diff --git a/src/sequencer/mod.rs b/src/sequencer/mod.rs index 2997267f..ab89b9fd 100644 --- a/src/sequencer/mod.rs +++ b/src/sequencer/mod.rs @@ -33,7 +33,8 @@ impl Sequencer { pub fn new () -> Result> { let exited = Arc::new(AtomicBool::new(false)); - let sequence: Arc>>>> = Arc::new(Mutex::new(vec![vec![None;64];4])); + let sequence: Arc>>>> = + Arc::new(Mutex::new(vec![vec![None;64];128])); let (client, _status) = Client::new( "blinkenlive-sequencer", ClientOptions::NO_START_SERVER diff --git a/src/sequencer/render.rs b/src/sequencer/render.rs index 367f4b3e..7c070488 100644 --- a/src/sequencer/render.rs +++ b/src/sequencer/render.rs @@ -1,81 +1,170 @@ use crate::prelude::*; use super::Sequencer; +use ratatui::style::Stylize; -pub fn render ( - state: &mut Sequencer, - stdout: &mut Stdout, - offset: (u16, u16) -) -> Result<(), Box> { - //render_toolbar(state, stdout, offset)?; - render_grid(state, stdout, offset)?; - render_events(state, stdout, offset)?; - Ok(()) +const NOTE_NAMES: [&'static str;12] = [ + "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B ", +]; + +const KEYS: [&'static str; 6] = [ + "▀", + "▀", + "▀", + "█", + "▄", + "▄", +]; + +impl WidgetRef for Sequencer { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + render_sequence_header(area, buf); + render_sequence_keys(area, buf, &self.sequence); + render_sequence_cursor(area, buf, self.cursor); + let cursor = self.cursor; + } } -fn render_grid ( - state: &mut Sequencer, - stdout: &mut Stdout, - offset: (u16, u16) -) -> Result<(), Box> { - let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); - let cursor: String = if state.cursor.2 == 0 { - "❱".into() - } else { - std::iter::repeat("·") - .take(state.cursor.2 as usize) - .collect() - }; - let transport = state.transport.query()?; - let frame = transport.pos.frame(); - let rate = transport.pos.frame_rate().unwrap(); - let second = (frame as f64) / (rate as f64); - let minute = second / 60f64; - let bpm = 120f64; - let div = 4; - let beats = minute * bpm; - let bars = beats as u32 / div as u32; - let beat = beats as u32 % div as u32 + 1; - let beat_sub = beats % 1.0; - stdout - .queue(move_to(1, 3))?.queue(Print("1.1"))? - .queue(move_to(17, 3))?.queue(Print("1.2"))? - .queue(move_to(33, 3))?.queue(Print("1.3"))? - .queue(move_to(49, 3))?.queue(Print("1.4"))?; - let sequence = state.sequence.lock().unwrap(); - for (index, row) in sequence.iter().enumerate() { - let y = index as u16 + 4; - for x in 0u16..64 { - let bg = if x as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 { - crossterm::style::Color::Black - } else { - crossterm::style::Color::Reset - }; - if let Some(step) = &row[x as usize] { - stdout.queue(move_to(1 + x, y))?.queue( - PrintStyledContent("X".white().bold().on(bg)) - )?; - } else if x % 16 == 0 { - stdout.queue(move_to(1 + x, y))?.queue( - PrintStyledContent("┊".grey().on(bg)) - )?; - } else { - stdout.queue(move_to(1 + x, y))?.queue( - PrintStyledContent("·".grey().on(bg)) - )?; +fn render_sequence_header ( + area: Rect, buf: &mut Buffer +) { + buf.set_string(area.x, area.y, "1.1.", Style::default().dim()); + buf.set_string(area.x + 16, area.y, "1.2.", Style::default().dim()); + buf.set_string(area.x + 32, area.y, "1.3.", Style::default().dim()); + buf.set_string(area.x + 48, area.y, "1.4.", Style::default().dim()); +} + +fn render_sequence_keys ( + area: Rect, buf: &mut Buffer, sequence: &Arc>>>> +) { + let sequence = sequence.lock().unwrap(); + for key in 0..12 { + buf.set_string(area.x - 3, area.y + 1 + key, KEYS[(key % 6) as usize], + Style::default().dim()); + buf.set_string(area.x - 2, area.y + 1 + key, "█", + Style::default().dim()); + buf.set_string(area.x, area.y + 1 + key, "·".repeat(64), + Style::default().black().dim()); + buf.set_string(area.x, area.y + 1 + key, "┊", + Style::default().black().dim()); + buf.set_string(area.x + 16, area.y + 1 + key, "┊", + Style::default().black().dim()); + buf.set_string(area.x + 32, area.y + 1 + key, "┊", + Style::default().black().dim()); + buf.set_string(area.x + 48, area.y + 1 + key, "┊", + Style::default().black().dim()); + for step in 0..64 { + let top = sequence[(key * 2) as usize][step].is_some(); + let bottom = sequence[(key * 2 + 1) as usize][step].is_some(); + match (top, bottom) { + (true, true) => { + buf.set_string(area.x + step as u16, area.y + 1 + key, "█", + Style::default().yellow().bold()); + }, + (true, false) => { + buf.set_string(area.x + step as u16, area.y + 1 + key, "▀", + Style::default().yellow().bold()); + }, + (false, true) => { + buf.set_string(area.x + step as u16, area.y + 1 + key, "▄", + Style::default().yellow().bold()); + }, + (false, false) => {}, } } } - stdout - .queue(move_to(1 + state.cursor.1, 4 + state.cursor.0))? - .queue(PrintStyledContent(cursor.reverse()))?; - Ok(()) } -fn render_events ( - state: &mut Sequencer, - stdout: &mut Stdout, - offset: (u16, u16) -) -> Result<(), Box> { - Ok(()) +fn render_sequence_cursor ( + area: Rect, buf: &mut Buffer, cursor: (u16, u16, u16) +) { + let cursor_character = match cursor.0 % 2 { + 0 => "▀", + 1 => "▄", + _ => unreachable!() + }; + let cursor_y = cursor.0 / 2; + buf.set_string(area.x + cursor.1, area.y + 1 + cursor_y, if cursor.2 == 0 { + cursor_character.into() + } else { + cursor_character.repeat(cursor.2 as usize) + }, Style::default().yellow()); } +//pub fn render ( + //state: &mut Sequencer, + //stdout: &mut Stdout, + //offset: (u16, u16) +//) -> Result<(), Box> { + ////render_toolbar(state, stdout, offset)?; + //render_grid(state, stdout, offset)?; + //render_events(state, stdout, offset)?; + //Ok(()) +//} + +//fn render_grid ( + //state: &mut Sequencer, + //stdout: &mut Stdout, + //offset: (u16, u16) +//) -> Result<(), Box> { + //let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); + //let cursor: String = if state.cursor.2 == 0 { + //"❱".into() + //} else { + //std::iter::repeat("·") + //.take(state.cursor.2 as usize) + //.collect() + //}; + //let transport = state.transport.query()?; + //let frame = transport.pos.frame(); + //let rate = transport.pos.frame_rate().unwrap(); + //let second = (frame as f64) / (rate as f64); + //let minute = second / 60f64; + //let bpm = 120f64; + //let div = 4; + //let beats = minute * bpm; + //let bars = beats as u32 / div as u32; + //let beat = beats as u32 % div as u32 + 1; + //let beat_sub = beats % 1.0; + //stdout + //.queue(move_to(1, 3))?.queue(Print("1.1"))? + //.queue(move_to(17, 3))?.queue(Print("1.2"))? + //.queue(move_to(33, 3))?.queue(Print("1.3"))? + //.queue(move_to(49, 3))?.queue(Print("1.4"))?; + //let sequence = state.sequence.lock().unwrap(); + //for (index, row) in sequence.iter().enumerate() { + //let y = index as u16 + 4; + //for x in 0u16..64 { + //let bg = if x as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 { + //crossterm::style::Color::Black + //} else { + //crossterm::style::Color::Reset + //}; + //if let Some(step) = &row[x as usize] { + //stdout.queue(move_to(1 + x, y))?.queue( + //PrintStyledContent("X".white().bold().on(bg)) + //)?; + //} else if x % 16 == 0 { + //stdout.queue(move_to(1 + x, y))?.queue( + //PrintStyledContent("┊".grey().on(bg)) + //)?; + //} else { + //stdout.queue(move_to(1 + x, y))?.queue( + //PrintStyledContent("·".grey().on(bg)) + //)?; + //} + //} + //} + //stdout + //.queue(move_to(1 + state.cursor.1, 4 + state.cursor.0))? + //.queue(PrintStyledContent(cursor.reverse()))?; + //Ok(()) +//} + +//fn render_events ( + //state: &mut Sequencer, + //stdout: &mut Stdout, + //offset: (u16, u16) +//) -> Result<(), Box> { + //Ok(()) +//} + diff --git a/src/transport/handle.rs b/src/transport/handle.rs index e5e1baec..463fbe81 100644 --- a/src/transport/handle.rs +++ b/src/transport/handle.rs @@ -1,6 +1,8 @@ use crate::prelude::*; use super::*; -pub fn handle (state: &mut super::Transport, event: &Event) -> Result<(), Box> { - Ok(()) +impl HandleInput for super::Transport { + fn handle (&mut self, event: &Event) -> Result<(), Box> { + Ok(()) + } } diff --git a/src/transport/render.rs b/src/transport/render.rs index 052fd2bd..4e209a4b 100644 --- a/src/transport/render.rs +++ b/src/transport/render.rs @@ -1,6 +1,47 @@ use crate::prelude::*; use super::{Transport, ACTIONS}; +impl WidgetRef for Transport { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + use ratatui::{layout::*, widgets::*, style::Stylize}; + Line::from("Project:").render(area, buf); + if let Ok(position) = self.transport.query() { + let frame = position.pos.frame(); + let rate = position.pos.frame_rate(); + let bbt = position.pos.bbt().map(|mut bbt|*bbt + .with_bpm(self.bpm) + .with_timesig(self.timesig.0, self.timesig.1)); + Line::from("Frame:").render(area.clone().offset(Offset { x: 0, y: 1 }), buf); + Line::from(format!("{frame}")).render(area.clone().offset(Offset { x: 0, y: 2 }), buf); + Line::from("Rate:").render(area.clone().offset(Offset { x: 10, y: 1 }), buf); + Line::from(match rate { + Some(rate) => format!("{rate}Hz"), + None => String::from("(none)"), + }).render(area.clone().offset(Offset { x: 10, y: 2 }), buf); + Line::from("Time:").render(area.clone().offset(Offset { x: 20, y: 1 }), buf); + Line::from(match rate { + Some(rate) => format!("{:.03}", frame as f64 / rate as f64), + None => String::from("(none)") + }).render(area.clone().offset(Offset { x: 20, y: 2 }), buf); + Line::from("BPM:").render(area.clone().offset(Offset { x: 30, y: 1 }), buf); + Line::from(match bbt { + Some(bbt) => format!("{:.01}", bbt.bpm), + None => String::from("(none)") + }).render(area.clone().offset(Offset { x: 30, y: 2 }), buf); + Line::from("TimeSig:").render(area.clone().offset(Offset { x: 40, y: 1 }), buf); + Line::from(match bbt { + Some(bbt) => format!("{}/{}", bbt.sig_num, bbt.sig_denom), + None => String::from("(none)") + }).render(area.clone().offset(Offset { x: 40, y: 2 }), buf); + Line::from("Beat:").render(area.clone().offset(Offset { x: 50, y: 1 }), buf); + Line::from(match bbt { + Some(bbt) => format!("{}.{}.{}", bbt.bar, bbt.beat, bbt.tick), + None => String::from("(none)") + }).render(area.clone().offset(Offset { x: 50, y: 2 }), buf); + } + } +} + pub fn render ( state: &mut Transport, stdout: &mut Stdout,