diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index c52f6a25..f5581aaa 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -4,6 +4,7 @@ pub trait FocusGrid { fn layout (&self) -> &[&[T]]; fn cursor (&self) -> (usize, usize); fn cursor_mut (&mut self) -> &mut (usize, usize); + fn update_focus (&mut self) {} fn focused (&self) -> &T { let (x, y) = self.cursor(); &self.layout()[y][x] @@ -16,6 +17,7 @@ pub trait FocusGrid { ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize }; *self.cursor_mut() = (next_x, next_y); + self.update_focus(); } fn focus_down (&mut self) { let layout = self.layout(); @@ -25,18 +27,21 @@ pub trait FocusGrid { ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize }; *self.cursor_mut() = (next_x, next_y); + self.update_focus(); } fn focus_left (&mut self) { let layout = self.layout(); let (x, y) = self.cursor(); let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 }; *self.cursor_mut() = (next_x, y); + self.update_focus(); } fn focus_right (&mut self) { let layout = self.layout(); let (x, y) = self.cursor(); let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 }; *self.cursor_mut() = (next_x, y); + self.update_focus(); } fn focus_next (&mut self) { let current = *self.focused(); @@ -50,6 +55,7 @@ pub trait FocusGrid { if *self.focused() == current { // FIXME: prevent infinite loop self.focus_next() } + self.update_focus(); } fn focus_prev (&mut self) { let current = *self.focused(); @@ -65,6 +71,7 @@ pub trait FocusGrid { if *self.focused() == current { // FIXME: prevent infinite loop self.focus_prev() } + self.update_focus(); } } diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index ee4811c9..13458716 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -2,6 +2,8 @@ use crate::*; /// Root level object for standalone `tek_arranger` pub struct Arranger { + /// Which view is focused + pub focus_cursor: (usize, usize), /// Controls the JACK transport. pub transport: Option>>>, /// Contains all the sequencers. @@ -14,24 +16,10 @@ pub struct Arranger { pub show_sequencer: Option, /// Slot for modal dialog displayed on top of app. pub modal: Option>>, - /// Focus cursor - pub focus_cursor: (usize, usize) } -/// Sections in the arranger that may be focused +/// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq)] pub enum ArrangerFocus { Transport, Arrangement, PhrasePool, PhraseEditor } -/// Focus layout of arranger. -impl FocusGrid for Arranger { - fn cursor (&self) -> (usize, usize) { self.focus_cursor } - fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } - fn layout (&self) -> &[&[ArrangerFocus]] { - &[ - &[ArrangerFocus::Transport], - &[ArrangerFocus::Arrangement, ArrangerFocus::Arrangement], - &[ArrangerFocus::PhrasePool, ArrangerFocus::PhraseEditor], - ] - } -} /// Represents the tracks and scenes of the composition. pub struct Arrangement { /// Name of arranger @@ -104,6 +92,29 @@ pub struct ArrangerRenameModal { pub result: Arc>, pub cursor: usize } +/// Focus layout of arranger app +impl FocusGrid for Arranger { + fn cursor (&self) -> (usize, usize) { self.focus_cursor } + fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } + fn layout (&self) -> &[&[ArrangerFocus]] { &[ + &[ArrangerFocus::Transport], + &[ArrangerFocus::Arrangement, ArrangerFocus::Arrangement], + &[ArrangerFocus::PhrasePool, ArrangerFocus::PhraseEditor], + ] } + fn update_focus (&mut self) { + let focused = *self.focused(); + if let Some(transport) = self.transport.as_ref() { + transport.write().unwrap().focused = + focused == ArrangerFocus::Transport + } + self.arrangement.focused = + focused == ArrangerFocus::Arrangement; + self.phrases.write().unwrap().focused = + focused == ArrangerFocus::PhrasePool; + self.editor.focused = + focused == ArrangerFocus::PhraseEditor; + } +} /// General methods for arrangement impl Arrangement { pub fn new (name: &str, phrases: &Arc>>) -> Self { @@ -182,7 +193,7 @@ impl Arrangement { for track_index in 0..self.tracks.len() { if let Some(phrase) = scene.clip(track_index) { let len = phrase.read().unwrap().name.read().unwrap().len(); - lens[track_index] = lens[track_index].max(len + 16); + lens[track_index] = lens[track_index].max(len); } } } diff --git a/crates/tek_sequencer/src/arranger_cli.rs b/crates/tek_sequencer/src/arranger_cli.rs index 99dae7b3..cca6f0f7 100644 --- a/crates/tek_sequencer/src/arranger_cli.rs +++ b/crates/tek_sequencer/src/arranger_cli.rs @@ -7,30 +7,22 @@ pub fn main () -> Usually<()> { ArrangerCli::parse().run() } #[command(version, about, long_about = None)] pub struct ArrangerCli { /// Name of JACK client - #[arg(short, long)] - name: Option, + #[arg(short, long)] name: Option, /// Whether to include a transport toolbar (default: true) - #[arg(short, long, default_value_t = true)] - transport: bool, + #[arg(short, long, default_value_t = true)] transport: bool, /// Number of tracks - #[arg(short = 'x', long, default_value_t = 8)] - tracks: usize, + #[arg(short = 'x', long, default_value_t = 8)] tracks: usize, /// Number of scenes - #[arg(short, long, default_value_t = 8)] - scenes: usize, + #[arg(short, long, default_value_t = 8)] scenes: usize, } impl ArrangerCli { /// Run the arranger TUI from CLI arguments. fn run (&self) -> Usually<()> { - let jack = JackClient::Inactive( - Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0 - ); - let jack_transport = jack.transport(); - let mut transport = TransportToolbar::new(Some(jack_transport)); - transport.set_focused(true); - let transport = Arc::new(RwLock::new(transport)); - let phrases = Arc::new(RwLock::new(PhrasePool::new())); + let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0; + let jack = JackClient::Inactive(jack); + let transport = Arc::new(RwLock::new(TransportToolbar::new(Some(jack.transport())))); + let phrases = Arc::new(RwLock::new(PhrasePool::new())); let mut arrangement = Arrangement::new("", &phrases); if let Some(name) = self.name.as_ref() { *arrangement.name.write().unwrap() = name.clone(); @@ -57,7 +49,7 @@ impl ArrangerCli { } )? ); - Tui::run(Arc::new(RwLock::new(Arranger { + let mut app = Arranger { focus_cursor: (0, 1), transport: self.transport.then_some(transport), show_sequencer: Some(tek_core::Direction::Down), @@ -65,7 +57,9 @@ impl ArrangerCli { editor: PhraseEditor::new(), arrangement, phrases, - })))?; + }; + app.update_focus(); + Tui::run(Arc::new(RwLock::new(app)))?; Ok(()) } } diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 8431acad..89fa2c8c 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -28,15 +28,6 @@ impl Content for Arranger { /// Handle top-level events in standalone arranger. impl Handle for Arranger { fn handle (&mut self, from: &TuiInput) -> Perhaps { - let update_focus = |arranger: &mut Self| { - let focused = *arranger.focused(); - arranger.transport.as_ref().map(|transport|{ - transport.write().unwrap().focused = focused == ArrangerFocus::Transport - }); - arranger.arrangement.focused = focused == ArrangerFocus::Arrangement; - arranger.phrases.write().unwrap().focused = focused == ArrangerFocus::PhrasePool; - arranger.editor.focused = focused == ArrangerFocus::PhraseEditor; - }; if !match self.focused() { ArrangerFocus::Transport => self.transport.handle(from)?, ArrangerFocus::Arrangement => self.arrangement.handle(from)?, @@ -45,15 +36,15 @@ impl Handle for Arranger { }.unwrap_or(false) { match from.event() { // Tab navigation - key!(KeyCode::Tab) => { self.focus_next(); update_focus(self); }, - key!(Shift-KeyCode::Tab) => { self.focus_prev(); update_focus(self); }, - key!(KeyCode::BackTab) => { self.focus_prev(); update_focus(self); }, - key!(Shift-KeyCode::BackTab) => { self.focus_prev(); update_focus(self); }, + key!(KeyCode::Tab) => { self.focus_next(); }, + key!(Shift-KeyCode::Tab) => { self.focus_prev(); }, + key!(KeyCode::BackTab) => { self.focus_prev(); }, + key!(Shift-KeyCode::BackTab) => { self.focus_prev(); }, // Directional navigation - key!(KeyCode::Up) => { self.focus_up(); update_focus(self); }, - key!(KeyCode::Down) => { self.focus_down(); update_focus(self); }, - key!(KeyCode::Left) => { self.focus_left(); update_focus(self); }, - key!(KeyCode::Right) => { self.focus_right(); update_focus(self); }, + key!(KeyCode::Up) => { self.focus_up(); }, + key!(KeyCode::Down) => { self.focus_down(); }, + key!(KeyCode::Left) => { self.focus_left(); }, + key!(KeyCode::Right) => { self.focus_right(); }, // Global play/pause binding key!(KeyCode::Char(' ')) => match self.transport { Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, @@ -86,14 +77,6 @@ impl Arranger { }; } } -impl Focusable for Arrangement { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} impl Handle for Arrangement { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 5f5b720c..dd77beef 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -7,15 +7,18 @@ pub type PhraseMessage = Vec; pub type PhraseChunk = [Vec]; /// Root level object for standalone `tek_sequencer` pub struct Sequencer { - /// Controls the JACK transport. - pub transport: Option>>>, - /// Pool of all phrases available to the sequencer - pub phrases: Arc>>, - /// Phrase editor view - pub editor: PhraseEditor, /// Which view is focused - pub focus: usize + pub focus_cursor: (usize, usize), + /// Controls the JACK transport. + pub transport: Option>>>, + /// Pool of all phrases available to the sequencer + pub phrases: Arc>>, + /// Phrase editor view + pub editor: PhraseEditor, } +/// Sections in the sequencer app that may be focused +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum SequencerFocus { Transport, PhrasePool, PhraseEditor } /// Contains all phrases in a project pub struct PhrasePool { _engine: PhantomData, @@ -93,6 +96,26 @@ pub struct PhrasePlayer { /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) } +/// Focus layout of sequencer app +impl FocusGrid for Sequencer { + fn cursor (&self) -> (usize, usize) { self.focus_cursor } + fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } + fn layout (&self) -> &[&[SequencerFocus]] { &[ + &[SequencerFocus::Transport], + &[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor], + ] } + fn update_focus (&mut self) { + let focused = *self.focused(); + if let Some(transport) = self.transport.as_ref() { + transport.write().unwrap().focused = + focused == SequencerFocus::Transport + } + self.phrases.write().unwrap().focused = + focused == SequencerFocus::PhrasePool; + self.editor.focused = + focused == SequencerFocus::PhraseEditor; + } +} impl PhrasePool { pub fn new () -> Self { Self { diff --git a/crates/tek_sequencer/src/sequencer_cli.rs b/crates/tek_sequencer/src/sequencer_cli.rs index 87583979..95f68f3d 100644 --- a/crates/tek_sequencer/src/sequencer_cli.rs +++ b/crates/tek_sequencer/src/sequencer_cli.rs @@ -7,24 +7,20 @@ pub fn main () -> Usually<()> { SequencerCli::parse().run() } #[command(version, about, long_about = None)] pub struct SequencerCli { /// Name of JACK client - #[arg(short, long)] name: Option, + #[arg(short, long)] name: Option, /// Pulses per quarter note (sequencer resolution; default: 96) - #[arg(short, long)] ppq: Option, + #[arg(short, long)] ppq: Option, /// Default phrase duration (in pulses; default: 4 * PPQ = 1 bar) - #[arg(short, long)] length: Option, + #[arg(short, long)] length: Option, /// Whether to include a transport toolbar (default: true) - #[arg(short, long)] transport: Option + #[arg(short, long, default_value_t = true)] transport: bool } impl SequencerCli { fn run (&self) -> Usually<()> { - let jack = JackClient::Inactive( - Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0 - ); - let jack_transport = jack.transport(); - let mut transport = TransportToolbar::new(Some(jack_transport)); - transport.set_focused(true); - let transport = Arc::new(RwLock::new(transport)); + let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0; + let jack = JackClient::Inactive(jack); + let transport = Arc::new(RwLock::new(TransportToolbar::new(Some(jack.transport())))); transport.write().unwrap().jack = Some( jack.activate( &transport.clone(), @@ -34,10 +30,10 @@ impl SequencerCli { )? ); let seq = Sequencer { - focus: 0, - editor: PhraseEditor::new(), - phrases: Arc::new(RwLock::new(PhrasePool::new())), - transport: Some(transport), + focus_cursor: (1, 1), + transport: self.transport.then_some(transport), + editor: PhraseEditor::new(), + phrases: Arc::new(RwLock::new(PhrasePool::new())), }; if let Some(name) = self.name.as_ref() { // TODO diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index cd09157c..09867265 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -10,59 +10,33 @@ impl Content for Sequencer { }) } } -/// Focusable items in standalone arranger. -impl Focus<3, Tui> for Sequencer { - fn focus (&self) -> usize { - self.focus - } - fn focus_mut (&mut self) -> &mut usize { - &mut self.focus - } - fn focusable (&self) -> [&dyn Focusable;3] { - focusables!(self.transport, self.phrases, self.editor) - } - fn focusable_mut (&mut self) -> [&mut dyn Focusable;3] { - focusables_mut!(self.transport, self.phrases, self.editor) - } -} /// Handle top-level events in standalone arranger. impl Handle for Sequencer { fn handle (&mut self, from: &TuiInput) -> Perhaps { - let focus = self.focus; - let is_first_row = false; - let is_last_row = false; - match from.event() { - key!(KeyCode::Char(' ')) => { - if let Some(ref mut transport) = self.transport { - transport.write().unwrap().toggle_play()?; - } else { - return Ok(None) - } - }, - key!(KeyCode::Tab) => { - self.focus_next(); - }, - key!(KeyCode::BackTab) => { - self.focus_prev(); - }, - key!(KeyCode::Down) => { - if focus == 0 { - self.focus_next(); - } else if focus == 1 && is_last_row { - self.focus_next(); - } else { - return self.focused_mut().handle(from) - } - }, - key!(KeyCode::Up) => { - if focus == 1 && is_first_row { - self.focus_prev(); - } else { - return self.focused_mut().handle(from) - } - }, - _ => return self.focused_mut().handle(from) - } + if !match self.focused() { + SequencerFocus::Transport => self.transport.handle(from)?, + SequencerFocus::PhrasePool => self.phrases.handle(from)?, + SequencerFocus::PhraseEditor => self.editor.handle(from)? + }.unwrap_or(false) { + match from.event() { + // Tab navigation + key!(KeyCode::Tab) => { self.focus_next(); }, + key!(Shift-KeyCode::Tab) => { self.focus_prev(); }, + key!(KeyCode::BackTab) => { self.focus_prev(); }, + key!(Shift-KeyCode::BackTab) => { self.focus_prev(); }, + // Directional navigation + key!(KeyCode::Up) => { self.focus_up(); }, + key!(KeyCode::Down) => { self.focus_down(); }, + key!(KeyCode::Left) => { self.focus_left(); }, + key!(KeyCode::Right) => { self.focus_right(); }, + // Global play/pause binding + key!(KeyCode::Char(' ')) => match self.transport { + Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, + None => { return Ok(None) } + }, + _ => {} + } + }; Ok(Some(true)) } } @@ -81,14 +55,6 @@ impl Content for PhrasePool { .fg(Color::Rgb(70, 80, 50)))) } } -impl Focusable for PhrasePool { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} impl Handle for PhrasePool { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { @@ -225,14 +191,6 @@ impl Handle for PhraseEditor { return Ok(Some(true)) } } -impl Focusable for PhraseEditor { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} impl PhraseEditor { const H_KEYS_OFFSET: usize = 5; /// Select which pattern to display. This pre-renders it to the buffer at full resolution. diff --git a/crates/tek_sequencer/src/transport_tui.rs b/crates/tek_sequencer/src/transport_tui.rs index 03b7e5f4..6944c49f 100644 --- a/crates/tek_sequencer/src/transport_tui.rs +++ b/crates/tek_sequencer/src/transport_tui.rs @@ -1,5 +1,4 @@ use crate::*; - impl TransportToolbar { fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps { match from.event() { @@ -28,7 +27,6 @@ impl TransportToolbar { return Ok(Some(true)) } } - impl Handle for TransportToolbar { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { @@ -48,7 +46,6 @@ impl Handle for TransportToolbar { Ok(Some(true)) } } - impl Content for TransportToolbar { type Engine = Tui; fn content (&self) -> impl Widget { @@ -89,16 +86,6 @@ impl Content for TransportToolbar { ).fill_x().bg(Color::Rgb(40, 50, 30)) } } - -impl Focusable for TransportToolbar { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} - impl TransportToolbarFocus { pub fn wrap <'a, W: Widget> ( self, parent_focus: bool, focus: Self, widget: &'a W