diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index 42da8ce5..014e3ff2 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -1,5 +1,70 @@ use crate::*; +pub trait FocusGrid { + fn layout (&self) -> &[&[T]]; + fn cursor (&self) -> (usize, usize); + fn cursor_mut (&mut self) -> &mut (usize, usize); + fn focused (&self) -> &T { + let (x, y) = self.cursor(); + &self.layout()[y][x] + } + fn focus_up (&mut self) { + let layout = self.layout(); + let (x, y) = self.cursor(); + let next_y = if y == 0 { layout.len() - 1 } else { y - 1 }; + let next_x = if layout[y].len() == layout[next_y].len() { x } else { + ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize + }; + *self.cursor_mut() = (next_x, next_y); + } + fn focus_down (&mut self) { + let layout = self.layout(); + let (x, y) = self.cursor(); + let next_y = if y >= layout.len() - 1 { 0 } else { y + 1 }; + let next_x = if layout[y].len() == layout[next_y].len() { x } else { + ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize + }; + *self.cursor_mut() = (next_x, next_y); + } + fn focus_left (&mut self) { + let layout = self.layout(); + let (x, y) = self.cursor(); + let next_x = if x == 0 { layout[y].len() - 1 } else { x - 1 }; + *self.cursor_mut() = (next_x, y); + } + fn focus_right (&mut self) { + let layout = self.layout(); + let (x, y) = self.cursor(); + let next_x = if x >= layout[y].len() - 1 { 0 } else { x - 1 }; + *self.cursor_mut() = (next_x, y); + } + fn focus_next (&mut self) { + todo!(); + } + fn focus_prev (&mut self) { + todo!(); + } +} + +//pub struct FocusFrame<'a, E: Engine>(usize, Vec>); + +//pub struct FocusCell<'a, E: Engine> { + //item: &'a dyn Handle, + //next: Option<&'a FocusCell<'a, E>>, + //prev: Option<&'a FocusCell<'a, E>>, + //up: Option<&'a FocusCell<'a, E>>, + //down: Option<&'a FocusCell<'a, E>>, + //left: Option<&'a FocusCell<'a, E>>, + //right: Option<&'a FocusCell<'a, E>>, +//} + +pub trait FocusContainer { + fn focused (&mut self) -> &mut dyn Handle; + fn handle_focus (&mut self, _: &E::Input) -> Perhaps { + Ok(None) + } +} + /// A component that may contain [Focusable] components. pub trait Focus : Widget + Handle { fn focus (&self) -> usize; diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index f29dc0af..6ac70c4f 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -219,6 +219,14 @@ pub type KeyMap = [KeyBinding]; kind: crossterm::event::KeyEventKind::Press, state: crossterm::event::KeyEventState::NONE })) + }; + (Shift-$code:pat) => { + TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent { + code: $code, + modifiers: crossterm::event::KeyModifiers::SHIFT, + kind: crossterm::event::KeyEventKind::Press, + state: crossterm::event::KeyEventState::NONE + })) } } pub struct TuiOutput { diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index 0725325b..07d9f2a6 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 { + /// Index of currently focused component + pub focus: ArrangerFocus, /// Controls the JACK transport. pub transport: Option>>>, /// Contains all the sequencers. @@ -12,10 +14,25 @@ pub struct Arranger { pub editor: PhraseEditor, /// This allows the sequencer view to be moved or hidden. pub show_sequencer: Option, - /// Index of currently focused component - pub focus: usize, /// 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 +#[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::PhrasePool, ArrangerFocus::PhraseEditor], + ] + } } /// Represents the tracks and scenes of the composition. pub struct Arrangement { diff --git a/crates/tek_sequencer/src/arranger_cli.rs b/crates/tek_sequencer/src/arranger_cli.rs index 33f8c4cd..7ef8e928 100644 --- a/crates/tek_sequencer/src/arranger_cli.rs +++ b/crates/tek_sequencer/src/arranger_cli.rs @@ -58,9 +58,10 @@ impl ArrangerCli { )? ); Tui::run(Arc::new(RwLock::new(Arranger { + focus_cursor: (0, 1), transport: self.transport.then_some(transport), show_sequencer: Some(tek_core::Direction::Down), - focus: 0, + focus: ArrangerFocus::Arrangement, modal: None, editor: PhraseEditor::new(), arrangement, diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 0b129214..a95eec8b 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -25,66 +25,34 @@ impl Content for Arranger { }) } } -/// Focusable items in standalone arranger. -impl Focus<3, Tui> for Arranger { - 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.arrangement, self.editor) - } - fn focusable_mut (&mut self) -> [&mut dyn Focusable;3] { - focusables_mut!(self.transport, self.arrangement, self.editor) - } -} /// Handle top-level events in standalone arranger. impl Handle for Arranger { fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(modal) = self.modal.as_mut() { - let result = modal.handle(from)?; - if from.is_done() { - self.modal = None; + if !match self.focused() { + ArrangerFocus::Transport => self.transport.handle(from)?, + ArrangerFocus::Arrangement => self.arrangement.handle(from)?, + ArrangerFocus::PhrasePool => self.phrases.handle(from)?, + ArrangerFocus::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) } + }, + _ => {} } - return Ok(result) - } - let focus = self.focus; - let is_first_row = self.arrangement.is_first_row(); - let is_last_row = self.arrangement.is_last_row(); - 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) - } + }; Ok(Some(true)) } }