From 7dd5f7f4885f41abc01684b15f058296612dd51d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 29 May 2024 11:32:26 +0300 Subject: [PATCH] use main loop with input everywhere --- src/looper.rs | 17 ++- src/looper/handle.rs | 4 + src/looper/render.rs | 2 - src/main.rs | 294 +++++++++++++++++++++++++--------------- src/mixer.rs | 15 +- src/mixer/handle.rs | 14 +- src/mixer/render.rs | 2 - src/prelude.rs | 7 +- src/render.rs | 20 ++- src/sampler.rs | 15 +- src/sampler/handle.rs | 2 +- src/sequencer.rs | 21 ++- src/sequencer/handle.rs | 2 +- src/transport.rs | 26 ++-- src/transport/handle.rs | 5 + src/transport/render.rs | 23 ++-- 16 files changed, 301 insertions(+), 168 deletions(-) diff --git a/src/looper.rs b/src/looper.rs index c99ffcc1..f6c807c3 100644 --- a/src/looper.rs +++ b/src/looper.rs @@ -6,14 +6,25 @@ pub use self::jack::*; pub use self::render::*; use crate::prelude::*; -pub struct Looper; +pub struct Looper { + exited: bool +} -const ACTIONS: [(&'static str, &'static str);1] = [ +pub const ACTIONS: [(&'static str, &'static str);1] = [ ("Ins/Del", "Add/remove loop"), ]; impl Looper { pub fn new () -> Result> { - Ok(Self) + Ok(Self { exited: false }) + } +} + +impl Exitable for Looper { + fn exit (&mut self) { + self.exited = true + } + fn exited (&self) -> bool { + self.exited } } diff --git a/src/looper/handle.rs b/src/looper/handle.rs index 3ca1c525..f573801b 100644 --- a/src/looper/handle.rs +++ b/src/looper/handle.rs @@ -1,2 +1,6 @@ use crate::prelude::*; use super::Looper; + +pub fn handle (state: &mut Looper, event: &Event) -> Result<(), Box> { + Ok(()) +} diff --git a/src/looper/render.rs b/src/looper/render.rs index 4f5f3de9..4be2892d 100644 --- a/src/looper/render.rs +++ b/src/looper/render.rs @@ -6,8 +6,6 @@ pub fn render ( stdout: &mut std::io::Stdout, mut offset: (u16, u16), ) -> Result<(), Box> { - let (w, h) = crate::render::render_toolbar_vertical(stdout, offset, &ACTIONS)?; - offset.0 = offset.0 + w + 2; let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); stdout .queue(move_to(0, 0))?.queue(Print(" Name Input Length Route"))? diff --git a/src/main.rs b/src/main.rs index d1467dbe..e5c75ea5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,131 +20,199 @@ use crate::prelude::*; fn main () -> Result<(), Box> { let cli = cli::Cli::parse(); if let Some(command) = cli.command { - match command { - cli::Command::Transport => main_loop( - &mut transport::Transport::new()?, - transport::render - ), - cli::Command::Mixer => main_loop( - &mut mixer::Mixer::new()?, - mixer::render - ), - cli::Command::Looper => main_loop( - &mut looper::Looper::new()?, - looper::render - ), - cli::Command::Sampler => main_loop( - &mut sampler::Sampler::new()?, - sampler::render - ), - cli::Command::Sequencer => main_loop( - &mut sequencer::Sequencer::new()?, - sequencer::render - ), - } + run_one(&command) } else { - main_loop(&mut ( - transport::Transport::new()?, - mixer::Mixer::new()?, - looper::Looper::new()?, - sampler::Sampler::new()?, - sequencer::Sequencer::new()?, - ), |(transport, mixer, looper, sampler, sequencer), stdout, offset| { - transport::render(transport, stdout, (1, 1))?; - render::render_box(stdout, 28, 0, 60, 6, false)?; - mixer::render(mixer, stdout, (1, 10))?; - render::render_box(stdout, 18, 9, 62, 9, false)?; - looper::render(looper, stdout, (1, 20))?; - render::render_box(stdout, 17, 19, 50, 6, false)?; + run_all() + } +} + +fn run_one (command: &cli::Command) -> Result<(), Box> { + match command { + cli::Command::Transport => main_loop( + &mut transport::Transport::new()?, + |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(()) + ), + cli::Command::Mixer => main_loop( + &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(()) + ), + cli::Command::Looper => main_loop( + &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(()) + ), + cli::Command::Sampler => main_loop( + &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(()) + ), + cli::Command::Sequencer => main_loop( + &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(()) + ), + } +} + +fn run_all () -> Result<(), Box> { + let mut actions = vec![]; + actions.extend_from_slice(&transport::ACTIONS); + actions.extend_from_slice(&mixer::ACTIONS); + actions.extend_from_slice(&looper::ACTIONS); + main_loop( + &mut App { + exited: false, + mode: Mode::Transport, + transport: transport::Transport::new()?, + mixer: mixer::Mixer::new()?, + looper: looper::Looper::new()?, + sampler: sampler::Sampler::new()?, + sequencer: sequencer::Sequencer::new()?, + }, + |state, stdout, mut offset| { + let (w, h) = render::render_toolbar_vertical(stdout, (offset.0, offset.1 + 1), &actions)?; + offset.0 = offset.0 + w + 1; + + transport::render(&mut state.transport, stdout, (offset.0 + 1, 1))?; + render::render_box(stdout, offset.0, 0, 64, 4, + state.mode == Mode::Transport)?; + + mixer::render(&mut state.mixer, stdout, (offset.0 + 1, 6))?; + render::render_box(stdout, offset.0, 5, 64, 9, + state.mode == Mode::Mixer)?; + + looper::render(&mut state.looper, stdout, (offset.0 + 1, 16))?; + render::render_box(stdout, offset.0, 15, 64, 6, + state.mode == Mode::Looper)?; + //sampler::render(sampler, stdout, (1, 46))?; //sequencer::render(sequencer, stdout, (1, 66))?; Ok(()) - }) + }, + |state, event| { + if let Event::Key(key) = event { + match key.code { + KeyCode::Char('c') => { + if key.modifiers == KeyModifiers::CONTROL { + state.exit(); + } + }, + KeyCode::Tab => match state.mode { + Mode::Transport => state.mode = Mode::Mixer, + Mode::Mixer => state.mode = Mode::Looper, + Mode::Looper => state.mode = Mode::Transport, + _ => {} + }, + KeyCode::BackTab => match state.mode { + Mode::Transport => state.mode = Mode::Looper, + Mode::Looper => state.mode = Mode::Mixer, + Mode::Mixer => state.mode = Mode::Transport, + _ => {} + }, + _ => 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 + )?, + _ => {}, + } + } + } + Ok(()) + } + ) +} + +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 } } -pub fn main_loop ( +pub fn main_loop ( state: &mut T, - mut render: impl FnMut(&mut T, &mut Stdout, (u16, u16)) -> Result<(), Box> + mut render: impl FnMut(&mut T, &mut Stdout, (u16, u16)) -> Result<(), Box>, + mut handle: impl FnMut(&mut T, &Event) -> Result<(), Box>, ) -> Result<(), Box> { + let mut stdout = stdout(); let sleep = std::time::Duration::from_millis(16); - let mut stdout = std::io::stdout(); + crossterm::terminal::enable_raw_mode()?; + let (tx, input) = channel::(); + let exited = Arc::new(AtomicBool::new(false)); + let exit_input_thread = exited.clone(); + spawn(move || { + loop { + // Exit if flag is set + if exit_input_thread.fetch_and(true, Ordering::Relaxed) { + break + } + // Listen for events and send them to the main thread + if crossterm::event::poll(Duration::from_millis(100)).is_ok() { + if tx.send(crossterm::event::read().unwrap()).is_err() { + break + } + } + } + }); + stdout.queue(Clear(ClearType::All))?.queue(Hide)?.flush()?; loop { - stdout.queue(Clear(ClearType::All))?.queue(Hide)?; render(state, &mut stdout, (0, 0))?; stdout.flush()?; - std::thread::sleep(sleep); + handle(state, &input.recv()?)?; + if state.exited() { + crossterm::terminal::disable_raw_mode()?; + stdout.queue(Clear(ClearType::All))?.queue(Show)?.flush()?; + break + } } + Ok(()) } - -//pub fn run_tui () -> Result<(), Box> { - //let mut stdout = stdout(); - //let mut app = Mixer::new()?; - //let sleep = std::time::Duration::from_millis(16); - //crossterm::terminal::enable_raw_mode()?; - //let (tx, input) = channel::(); - //let exited = Arc::new(AtomicBool::new(false)); - //let exit_input_thread = exited.clone(); - //spawn(move || { - //loop { - //// Exit if flag is set - //if exit_input_thread.fetch_and(true, Ordering::Relaxed) { - //break - //} - //// Listen for events and send them to the main thread - //if crossterm::event::poll(Duration::from_millis(100)).is_ok() { - //if tx.send(crossterm::event::read().unwrap()).is_err() { - //break - //} - //} - //} - //}); - //loop { - //render(&mut app, &mut stdout, (0, 0))?; - //handle(&mut app, input.recv()?)?; - //if app.exit { - //app.stdout.queue(cursor::Show)?.flush()?; - //crossterm::terminal::disable_raw_mode()?; - //break - //} - //} - //Ok(()) -//} - -//pub fn run_tui () -> Result<(), Box> { - //let mut app = Self::new()?; - //let mut stdout = std::io::stdout(); - //let sleep = std::time::Duration::from_millis(16); - //crossterm::terminal::enable_raw_mode()?; - //let (tx, input) = channel::(); - //let exited = Arc::new(AtomicBool::new(false)); - //// Spawn the input thread - //let exit_input_thread = exited.clone(); - //spawn(move || { - //loop { - //// Exit if flag is set - //if exit_input_thread.fetch_and(true, Ordering::Relaxed) { - //break - //} - //// Listen for events and send them to the main thread - //if crossterm::event::poll(Duration::from_millis(100)).is_ok() { - //if tx.send(crossterm::event::read().unwrap()).is_err() { - //break - //} - //} - //} - //}); - //loop { - //use crossterm::{*, terminal::{Clear, ClearType}, cursor::Hide}; - //stdout.queue(Clear(ClearType::All))?.queue(Hide)?; - //render(&mut app, &mut stdout, (0, 0))?; - //stdout.flush()?; - //handle(&mut app, input.recv()?)?; - //if app.exit { - //stdout.queue(cursor::Hide)?.flush()?; - //crossterm::terminal::disable_raw_mode()?; - //break - //} - //} - //Ok(()) -//} diff --git a/src/mixer.rs b/src/mixer.rs index 7d3f777b..9cc22985 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -11,14 +11,14 @@ use crate::prelude::*; // - If one stage clips, all stages after it are marked red // - If one track clips, all tracks that feed from it are marked red? -const ACTIONS: [(&'static str, &'static str);2] = [ +pub const ACTIONS: [(&'static str, &'static str);2] = [ ("+/-", "Adjust"), ("Ins/Del", "Add/remove track"), ]; pub struct Mixer { + exited: bool, jack: Jack, - exit: bool, tracks: Vec, selected_track: usize, selected_column: usize, @@ -57,7 +57,7 @@ impl Mixer { ) )?; Ok(Self { - exit: false, + exited: false, selected_column: 0, selected_track: 1, tracks: vec![ @@ -108,3 +108,12 @@ impl Track { }) } } + +impl Exitable for Mixer { + fn exit (&mut self) { + self.exited = true + } + fn exited (&self) -> bool { + self.exited + } +} diff --git a/src/mixer/handle.rs b/src/mixer/handle.rs index 806ee9fd..a7027d43 100644 --- a/src/mixer/handle.rs +++ b/src/mixer/handle.rs @@ -1,16 +1,14 @@ use crate::prelude::*; use super::Mixer; -pub fn handle ( - state: &mut Mixer, - event: crossterm::event::Event -) -> Result<(), Box> { - use crossterm::event::{Event, KeyCode, KeyModifiers}; +pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box> { + if let Event::Key(event) = event { + match event.code { KeyCode::Char('c') => { if event.modifiers == KeyModifiers::CONTROL { - state.exit = true; + state.exit(); } }, KeyCode::Down => { @@ -40,9 +38,11 @@ pub fn handle ( } }, _ => { - println!("{event:?}"); + println!("\n{event:?}"); } } + } + Ok(()) } diff --git a/src/mixer/render.rs b/src/mixer/render.rs index f5491086..f674e2bd 100644 --- a/src/mixer/render.rs +++ b/src/mixer/render.rs @@ -6,8 +6,6 @@ pub fn render ( stdout: &mut Stdout, mut offset: (u16, u16) ) -> Result<(), Box> { - let (w, h) = crate::render::render_toolbar_vertical(stdout, offset, &ACTIONS)?; - offset.0 = offset.0 + w + 2; render_table(state, stdout, offset)?; render_meters(state, stdout, offset)?; stdout.flush()?; diff --git a/src/prelude.rs b/src/prelude.rs index ce433571..631a0ac3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,7 +9,8 @@ pub use std::sync::{ }; pub use crossterm::{ QueueableCommand, - cursor::{self, MoveTo, Hide}, + event::{Event, KeyCode, KeyModifiers}, + cursor::{self, MoveTo, Show, Hide}, terminal::{self, Clear, ClearType}, style::*, }; @@ -32,3 +33,7 @@ pub type Jack = AsyncClient< N, ClosureProcessHandler Control + Send>> >; +pub trait Exitable { + fn exit (&mut self); + fn exited (&self) -> bool; +} diff --git a/src/render.rs b/src/render.rs index 59fb80fe..f1cd53c0 100644 --- a/src/render.rs +++ b/src/render.rs @@ -5,7 +5,6 @@ pub fn render_toolbar_vertical ( offset: (u16, u16), actions: &[(&str, &str)], ) -> Result<(u16, u16), Box> { - use crossterm::{*, style::{Stylize, PrintStyledContent}, cursor::MoveTo}; let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); let mut x: u16 = 1; let mut y: u16 = 0; @@ -31,11 +30,20 @@ pub fn render_box ( ) -> Result<(), Box> { let edge: String = std::iter::repeat("─").take(w.saturating_sub(2) as usize).collect(); let back: String = std::iter::repeat(" ").take(w.saturating_sub(2) as usize).collect(); - stdout.queue(MoveTo(x, y))?.queue(Print(&format!("┌{edge}┐")))?; - for row in y+1..y+h { - stdout.queue(MoveTo(x, row))?.queue(Print("│"))?; - stdout.queue(MoveTo(x+w-1, row))?.queue(Print("│"))?; + if active { + stdout.queue(MoveTo(x, y))?.queue(PrintStyledContent(format!("┌{edge}┐").bold().yellow()))?; + for row in y+1..y+h { + stdout.queue(MoveTo(x, row))?.queue(PrintStyledContent("│".bold().yellow()))?; + stdout.queue(MoveTo(x+w-1, row))?.queue(PrintStyledContent("│".bold().yellow()))?; + } + stdout.queue(MoveTo(x, y+h))?.queue(PrintStyledContent(format!("└{edge}┘").bold().yellow()))?; + } else { + stdout.queue(MoveTo(x, y))?.queue(Print(&format!("┌{edge}┐")))?; + for row in y+1..y+h { + stdout.queue(MoveTo(x, row))?.queue(Print("│"))?; + stdout.queue(MoveTo(x+w-1, row))?.queue(Print("│"))?; + } + stdout.queue(MoveTo(x, y+h))?.queue(Print(&format!("└{edge}┘")))?; } - stdout.queue(MoveTo(x, y+h))?.queue(Print(&format!("└{edge}┘")))?; Ok(()) } diff --git a/src/sampler.rs b/src/sampler.rs index b98b6ac5..0c630e4f 100644 --- a/src/sampler.rs +++ b/src/sampler.rs @@ -6,9 +6,11 @@ pub use self::jack::*; pub use self::render::*; use crate::prelude::*; +pub const ACTIONS: [(&'static str, &'static str);0] = []; + pub struct Sampler { + exited: bool, jack: Jack, - exit: bool, samples: Vec, selected_sample: usize, selected_column: usize, @@ -29,7 +31,7 @@ impl Sampler { ) )?; Ok(Self { - exit: false, + exited: false, selected_sample: 0, selected_column: 0, samples: vec![ @@ -62,3 +64,12 @@ impl Sample { } } + +impl Exitable for Sampler { + fn exit (&mut self) { + self.exited = true + } + fn exited (&self) -> bool { + self.exited + } +} diff --git a/src/sampler/handle.rs b/src/sampler/handle.rs index 3b6ffe83..33c1b5ae 100644 --- a/src/sampler/handle.rs +++ b/src/sampler/handle.rs @@ -10,7 +10,7 @@ pub fn handle ( match event.code { KeyCode::Char('c') => { if event.modifiers == KeyModifiers::CONTROL { - state.exit = true; + state.exit(); } }, KeyCode::Down => { diff --git a/src/sequencer.rs b/src/sequencer.rs index b338ddb8..bd0b0248 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -1,15 +1,16 @@ mod handle; -mod render; mod jack; +mod render; pub use self::handle::*; -pub use self::render::*; pub use self::jack::*; +pub use self::render::*; use crate::prelude::*; +pub const ACTIONS: [(&'static str, &'static str);0] = []; + pub struct Sequencer { + exited: bool, jack: Jack, - exit: bool, - stdout: Stdout, cursor: (u16, u16), duration: u16, } @@ -29,11 +30,19 @@ impl Sequencer { ) )?; Ok(Self { - exit: false, - stdout: std::io::stdout(), + exited: false, cursor: (0, 0), duration: 0, jack, }) } } + +impl Exitable for Sequencer { + fn exit (&mut self) { + self.exited = true + } + fn exited (&self) -> bool { + self.exited + } +} diff --git a/src/sequencer/handle.rs b/src/sequencer/handle.rs index 5265647c..6f22be8f 100644 --- a/src/sequencer/handle.rs +++ b/src/sequencer/handle.rs @@ -10,7 +10,7 @@ fn handle ( match event.code { KeyCode::Char('c') => { if event.modifiers == KeyModifiers::CONTROL { - state.exit = true; + state.exit(); } }, KeyCode::Char('[') => { diff --git a/src/transport.rs b/src/transport.rs index 95533c4c..41203c62 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -6,17 +6,27 @@ pub use self::jack::*; pub use self::render::*; use crate::prelude::*; -pub struct Transport; +pub const ACTIONS: [(&'static str, &'static str);2] = [ + ("(Shift-)Tab, Arrows ", "Navigate"), + //("Home", "⏹ Stop and rewind"), + ("(Shift-)Space", "⏮ Play/pause"), +]; + +pub struct Transport { + exited: bool +} impl Transport { pub fn new () -> Result> { - Ok(Self) + Ok(Self { exited: false }) } } -const ACTIONS: [(&'static str, &'static str);3] = [ - ("Tab/Shift-Tab, Arrows", "Navigate"), - //("Home", "⏹ Stop and rewind"), - ("Space", "⏮ Play from cursor/pause"), - ("Shift-Space", "⯈ Play from start/rewind"), -]; +impl Exitable for Transport { + fn exit (&mut self) { + self.exited = true + } + fn exited (&self) -> bool { + self.exited + } +} diff --git a/src/transport/handle.rs b/src/transport/handle.rs index 3616db21..f1c88fff 100644 --- a/src/transport/handle.rs +++ b/src/transport/handle.rs @@ -1 +1,6 @@ use crate::prelude::*; +use super::*; + +pub fn handle (state: &mut Transport, event: &Event) -> Result<(), Box> { + Ok(()) +} diff --git a/src/transport/render.rs b/src/transport/render.rs index 8377ff04..5f6e3b02 100644 --- a/src/transport/render.rs +++ b/src/transport/render.rs @@ -6,33 +6,30 @@ pub fn render ( stdout: &mut Stdout, mut offset: (u16, u16) ) -> Result<(), Box> { - use crossterm::{*, cursor::{Hide, MoveTo}, style::{*, Stylize}}; - let (w, h) = crate::render::render_toolbar_vertical(stdout, offset, &ACTIONS)?; - offset.0 = offset.0 + w + 2; let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); stdout.queue(move_to( 1, 0))?.queue( Print("Project: ") )?.queue(move_to(10, 0))?.queue( PrintStyledContent("The Quick Brown Fox - Jumping Over Lazy Dogs".white().bold()) - )?.queue(move_to( 1, 2))?.queue( + )?.queue(move_to( 1, 1))?.queue( Print("Rate: ") - )?.queue(move_to( 7, 2))?.queue( + )?.queue(move_to( 7, 1))?.queue( PrintStyledContent("48000Hz".white().bold()) - )?.queue(move_to(20, 2))?.queue( + )?.queue(move_to(20, 1))?.queue( Print("BPM: ") - )?.queue(move_to(25, 2))?.queue( + )?.queue(move_to(25, 1))?.queue( PrintStyledContent("120.34".white().bold()) - )?.queue(move_to(35, 2))?.queue( + )?.queue(move_to(35, 1))?.queue( Print("Signature: ") - )?.queue(move_to(46, 2))?.queue( + )?.queue(move_to(46, 1))?.queue( PrintStyledContent("4 / 4".white().bold()) - )?.queue(move_to( 1, 4))?.queue( + )?.queue(move_to( 1, 2))?.queue( Print("Time: ") - )?.queue(move_to( 7, 4))?.queue( + )?.queue(move_to( 7, 2))?.queue( PrintStyledContent("1m 23.456s".white().bold()) - )?.queue(move_to(20, 4))?.queue( + )?.queue(move_to(20, 2))?.queue( Print("Beat: ") - )?.queue(move_to(26, 4))?.queue( + )?.queue(move_to(26, 2))?.queue( PrintStyledContent("30x 3/4".white().bold()) )?; Ok(())