diff --git a/crates/tek/src/core/focus.rs b/crates/tek/src/core/focus.rs index b153cfde..fe16eeae 100644 --- a/crates/tek/src/core/focus.rs +++ b/crates/tek/src/core/focus.rs @@ -34,7 +34,7 @@ impl FocusState { } #[derive(Copy, Clone, PartialEq, Debug)] -pub enum FocusCommand { +pub enum FocusCommand { Up, Down, Left, @@ -43,10 +43,11 @@ pub enum FocusCommand { Prev, Enter, Exit, + Set(T) } -impl Command for FocusCommand { - fn execute (self, state: &mut F) -> Perhaps { +impl Command for FocusCommand { + fn execute (self, state: &mut F) -> Perhaps> { use FocusCommand::*; match self { Next => { state.focus_next(); }, @@ -57,6 +58,7 @@ impl Command for FocusComman Right => { state.focus_right(); }, Enter => { state.focus_enter(); }, Exit => { state.focus_exit(); }, + Set(to) => { state.set_focused(to); }, } Ok(None) } @@ -64,7 +66,7 @@ impl Command for FocusComman /// Trait for things that have focusable subparts. pub trait HasFocus { - type Item: Copy + PartialEq + Debug; + type Item: Copy + PartialEq + Debug + Send + Sync; /// Get the currently focused item. fn focused (&self) -> Self::Item; /// Get the currently focused item. @@ -249,55 +251,67 @@ impl FocusOrder for T { } } -#[cfg(test)] -mod test { - use super::*; +pub trait FocusWrap { + fn wrap <'a, W: Render> (self, focus: T, content: &'a W) + -> impl Render + 'a; +} - #[test] - fn test_focus () { +pub fn to_focus_command (input: &TuiInput) -> Option> { + use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc}; + Some(match input.event() { + key!(Tab) => FocusCommand::Next, + key!(Shift-Tab) => FocusCommand::Prev, + key!(BackTab) => FocusCommand::Prev, + key!(Shift-BackTab) => FocusCommand::Prev, + key!(Up) => FocusCommand::Up, + key!(Down) => FocusCommand::Down, + key!(Left) => FocusCommand::Left, + key!(Right) => FocusCommand::Right, + key!(Enter) => FocusCommand::Enter, + key!(Esc) => FocusCommand::Exit, + _ => return None + }) +} - struct FocusTest { - focused: char, - cursor: (usize, usize) - } - - impl HasFocus for FocusTest { - type Item = char; +#[macro_export] macro_rules! impl_focus { + ($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => { + impl HasFocus for $Struct { + type Item = $Focus; + /// Get the currently focused item. fn focused (&self) -> Self::Item { - self.focused + self.focus.inner() } + /// Get the currently focused item. fn set_focused (&mut self, to: Self::Item) { - self.focused = to + self.focus.set_inner(to) + } + $(fn focus_updated (&mut $self) { $update_focus })? + } + impl HasEnter for $Struct { + /// Get the currently focused item. + fn entered (&self) -> bool { + self.focus.is_entered() + } + /// Get the currently focused item. + fn set_entered (&mut self, entered: bool) { + if entered { + self.focus.to_entered() + } else { + self.focus.to_focused() + } } } - - impl FocusGrid for FocusTest { + impl FocusGrid for $Struct { fn focus_cursor (&self) -> (usize, usize) { self.cursor } fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.cursor } - fn focus_layout (&self) -> &[&[Self::Item]] { - &[ - &['a', 'a', 'a', 'b', 'b', 'd'], - &['a', 'a', 'a', 'b', 'b', 'd'], - &['a', 'a', 'a', 'c', 'c', 'd'], - &['a', 'a', 'a', 'c', 'c', 'd'], - &['e', 'e', 'e', 'e', 'e', 'e'], - ] + fn focus_layout (&self) -> &[&[$Focus]] { + use $Focus::*; + &$Grid } } - - let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; - - tester.focus_right(); - assert_eq!(tester.cursor.0, 3); - assert_eq!(tester.focused, 'b'); - - tester.focus_down(); - assert_eq!(tester.cursor.1, 2); - assert_eq!(tester.focused, 'c'); - } } diff --git a/crates/tek/src/core/test.rs b/crates/tek/src/core/test.rs new file mode 100644 index 00000000..88699914 --- /dev/null +++ b/crates/tek/src/core/test.rs @@ -0,0 +1,49 @@ +#[cfg(test)] mod test_focus { + use super::focus::*; + #[test] fn test_focus () { + + struct FocusTest { + focused: char, + cursor: (usize, usize) + } + + impl HasFocus for FocusTest { + type Item = char; + fn focused (&self) -> Self::Item { + self.focused + } + fn set_focused (&mut self, to: Self::Item) { + self.focused = to + } + } + + impl FocusGrid for FocusTest { + fn focus_cursor (&self) -> (usize, usize) { + self.cursor + } + fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { + &mut self.cursor + } + fn focus_layout (&self) -> &[&[Self::Item]] { + &[ + &['a', 'a', 'a', 'b', 'b', 'd'], + &['a', 'a', 'a', 'b', 'b', 'd'], + &['a', 'a', 'a', 'c', 'c', 'd'], + &['a', 'a', 'a', 'c', 'c', 'd'], + &['e', 'e', 'e', 'e', 'e', 'e'], + ] + } + } + + let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; + + tester.focus_right(); + assert_eq!(tester.cursor.0, 3); + assert_eq!(tester.focused, 'b'); + + tester.focus_down(); + assert_eq!(tester.cursor.1, 2); + assert_eq!(tester.focused, 'c'); + + } +} diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 90c10630..6011ae72 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -1,6 +1,5 @@ use crate::*; -mod engine_focus; pub(crate) use engine_focus::*; mod engine_input; pub(crate) use engine_input::*; mod engine_style; pub(crate) use engine_style::*; mod engine_theme; pub(crate) use engine_theme::*; diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index f3e1026e..e94a5716 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -7,7 +7,6 @@ use crate::{ } }; - impl TryFrom<&Arc>> for ArrangerTui { type Error = Box; fn try_from (jack: &Arc>) -> Usually { @@ -200,6 +199,16 @@ pub enum ArrangerFocus { PhraseEditor, } +impl Into> for ArrangerFocus { + fn into (self) -> Option { + if let Self::Transport(transport) = self { + Some(transport) + } else { + None + } + } +} + impl From<&ArrangerTui> for Option { fn from (state: &ArrangerTui) -> Self { match state.focus.inner() { @@ -1075,7 +1084,7 @@ impl Handle for ArrangerTui { #[derive(Clone, Debug)] pub enum ArrangerCommand { - Focus(FocusCommand), + Focus(FocusCommand), Undo, Redo, Clear, @@ -1132,7 +1141,7 @@ impl Command for ArrangerClipCommand { } } -pub trait ArrangerControl: TransportControl { +pub trait ArrangerControl: TransportControl { fn selected (&self) -> ArrangerSelection; fn selected_mut (&mut self) -> &mut ArrangerSelection; fn activate (&mut self) -> Usually<()>; @@ -1212,7 +1221,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option match state.focused() { ArrangerFocus::Transport(_) => { - match TransportCommand::input_to_command(state, input)? { + match to_transport_command(state, input)? { TransportCommand::Clock(command) => Cmd::Clock(command), _ => return None, } @@ -1323,3 +1332,12 @@ fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option return None }) } + +impl TransportControl for ArrangerTui { + fn transport_focused (&self) -> Option { + match self.focus.inner() { + ArrangerFocus::Transport(focus) => Some(focus), + _ => None + } + } +} diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index bfd1b849..beece12f 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -1,9 +1,8 @@ use crate::{*, api::ClockCommand::{Play, Pause}}; -use super::phrase_editor::PhraseCommand::Show; -use super::app_transport::TransportCommand; -use KeyCode::{Char, Enter}; +use KeyCode::{Tab, BackTab, Char, Enter}; use SequencerCommand::*; use SequencerFocus::*; +use PhraseCommand::*; /// Create app state from JACK handle. impl TryFrom<&Arc>> for SequencerTui { @@ -41,7 +40,7 @@ impl TryFrom<&Arc>> for SequencerTui { midi_buf: vec![vec![];65536], note_buf: vec![], perf: PerfModel::default(), - focus: FocusState::Entered(SequencerFocus::PhraseEditor) + focus: SequencerFocus::PhraseEditor }) } @@ -60,7 +59,7 @@ pub struct SequencerTui { pub entered: bool, pub note_buf: Vec, pub midi_buf: Vec>>, - pub focus: FocusState, + pub focus: SequencerFocus, pub perf: PerfModel, } @@ -109,32 +108,26 @@ impl Audio for SequencerTui { render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1, Tui::fill_xy(SequencerStatusBar::from(self)), - Tui::split_down(false, 2, - TransportView::from(( - self, - self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(), - if let SequencerFocus::Transport(_) = self.focus.inner() { - true - } else { - false - } - )), - Tui::split_left(false, 20, - Tui::fixed_x(20, Tui::split_down(false, 4, col!([ - PhraseSelector::play_phrase( - &self.player, self.focused() == SequencerFocus::PhrasePlay, self.entered() - ), - PhraseSelector::next_phrase( - &self.player, self.focused() == SequencerFocus::PhraseNext, self.entered() - ), - ]), Tui::split_up(false, 2, - PhraseSelector::edit_phrase( - &self.editor.phrase, self.focused() == SequencerFocus::PhraseEditor, self.entered() - ), - PhraseListView::from(self), + Tui::split_left(false, 20, + Tui::fixed_x(20, Tui::split_down(false, 4, col!([ + PhraseSelector::play_phrase(&self.player, false, true), + PhraseSelector::next_phrase(&self.player, false, true), + ]), Tui::split_up(false, 2, + PhraseSelector::edit_phrase(&self.editor.phrase, self.focused() == SequencerFocus::PhraseEditor, true), + PhraseListView::from(self), + ))), + col!([ + Tui::fixed_y(2, TransportView::from(( + self, + self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(), + if let SequencerFocus::Transport(_) = self.focus { + true + } else { + false + } ))), PhraseView::from(self) - ) + ]), ) )])); @@ -161,7 +154,19 @@ impl HasEditor for SequencerTui { self.focused() == SequencerFocus::PhraseEditor } fn editor_entered (&self) -> bool { - self.entered() && self.editor_focused() + true + } +} + +impl HasFocus for SequencerTui { + type Item = SequencerFocus; + /// Get the currently focused item. + fn focused (&self) -> Self::Item { + self.focus + } + /// Get the currently focused item. + fn set_focused (&mut self, to: Self::Item) { + self.focus = to } } @@ -174,64 +179,27 @@ pub enum SequencerFocus { PhraseList, /// The phrase editor (sequencer) is focused PhraseEditor, +} - PhrasePlay, - PhraseNext, +impl Into> for SequencerFocus { + fn into (self) -> Option { + if let Self::Transport(transport) = self { + Some(transport) + } else { + None + } + } } impl From<&SequencerTui> for Option { fn from (state: &SequencerTui) -> Self { - match state.focus.inner() { + match state.focus { Transport(focus) => Some(focus), _ => None } } } -impl_focus!(SequencerTui SequencerFocus [ - //&[ - //Menu, - //Menu, - //Menu, - //Menu, - //Menu, - //], - &[ - Transport(TransportFocus::PlayPause), - Transport(TransportFocus::Bpm), - Transport(TransportFocus::Sync), - Transport(TransportFocus::Quant), - Transport(TransportFocus::Clock), - ], - //&[ - //PhrasePlay, - //PhrasePlay, - //PhraseEditor, - //PhraseEditor, - //PhraseEditor, - //], - //&[ - //PhraseNext, - //PhraseNext, - //PhraseEditor, - //PhraseEditor, - //PhraseEditor, - //], - &[ - PhraseList, - PhraseList, - PhraseEditor, - PhraseEditor, - PhraseEditor, - ], -] => [self: { - if self.focus.is_entered() && self.focus.inner() == PhraseEditor { - self.editor.edit_mode = PhraseEditMode::Note - } else { - self.editor.edit_mode = PhraseEditMode::Scroll - } -}]); - /// Status bar for sequencer app #[derive(Clone)] pub struct SequencerStatusBar { @@ -272,8 +240,8 @@ impl From<&SequencerTui> for SequencerStatusBar { Transport(Sync) => " LAUNCH SYNC ", Transport(Quant) => " REC QUANT ", Transport(Clock) => " SEEK ", - PhrasePlay => " TO PLAY ", - PhraseNext => " UP NEXT ", + //PhrasePlay => " TO PLAY ", + //PhraseNext => " UP NEXT ", PhraseList => " PHRASES ", PhraseEditor => match state.editor.edit_mode { PhraseEditMode::Note => " EDIT MIDI ", @@ -299,16 +267,12 @@ impl From<&SequencerTui> for SequencerStatusBar { ("", ".,", " by beat"), ("", "<>", " by time"), ], - PhraseList => if state.entered() { - &[ - ("", "↕", " pick"), - ("", ".,", " move"), - ("", "⏎", " play"), - ("", "e", " edit"), - ] - } else { - default_help - }, + PhraseList => &[ + ("", "↕", " pick"), + ("", ".,", " move"), + ("", "⏎", " play"), + ("", "e", " edit"), + ], PhraseEditor => match state.editor.edit_mode { PhraseEditMode::Note => &[ ("", "✣", " cursor"), @@ -316,14 +280,8 @@ impl From<&SequencerTui> for SequencerStatusBar { PhraseEditMode::Scroll => &[ ("", "✣", " scroll"), ], - } - _ => if state.entered() { - &[ - ("", "Esc", " exit") - ] - } else { - default_help - } + }, + _ => default_help, } } } @@ -331,15 +289,15 @@ impl From<&SequencerTui> for SequencerStatusBar { render!(|self: SequencerStatusBar|{ lay!(|add|if self.width > 60 { - add(&row!(![ - SequencerMode::from(self), - SequencerStats::from(self), - ])) + add(&Tui::fill_x(Tui::fixed_y(1, lay!([ + Tui::fill_x(Tui::at_w(SequencerMode::from(self))), + Tui::fill_x(Tui::at_e(SequencerStats::from(self))), + ])))) } else { - add(&col!(![ - SequencerMode::from(self), - SequencerStats::from(self), - ])) + add(&Tui::fill_x(col!(![ + Tui::fill_x(Tui::center_x(SequencerMode::from(self))), + Tui::fill_x(Tui::center_x(SequencerStats::from(self))), + ]))) }) }); @@ -404,14 +362,11 @@ impl Handle for SequencerTui { #[derive(Clone, Debug)] pub enum SequencerCommand { - Focus(FocusCommand), + Focus(FocusCommand), Clock(ClockCommand), Phrases(PhrasesCommand), Editor(PhraseCommand), Enqueue(Option>>), - //Clear, - //Undo, - //Redo, } impl Command for SequencerCommand { @@ -425,76 +380,74 @@ impl Command for SequencerCommand { state.player.enqueue_next(phrase.as_ref()); None }, - //Self::Undo => { todo!() }, - //Self::Redo => { todo!() }, - //Self::Clear => { todo!() }, }) } } +impl Command for FocusCommand { + fn execute (self, state: &mut SequencerTui) -> Perhaps> { + if let FocusCommand::Set(to) = self { + state.set_focused(to); + } + Ok(None) + } +} + impl InputToCommand for SequencerCommand { fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option { - if state.entered() { - to_sequencer_command(state, input) - .or_else(||to_focus_command(input).map(SequencerCommand::Focus)) - } else { - to_focus_command(input).map(SequencerCommand::Focus) - .or_else(||to_sequencer_command(state, input)) - }.or_else(||Some({ - let time_zoom = state.editor.view_mode.time_zoom(); - let next_zoom = next_note_length(time_zoom); - let prev_zoom = prev_note_length(time_zoom); - match input.event() { - key!(Char('-')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(next_zoom)), - key!(Char('_')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(next_zoom)), - key!(Char('=')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(prev_zoom)), - key!(Char('+')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(prev_zoom)), - key!(Char('c')) => SequencerCommand::Phrases( - PhrasesCommand::input_to_command(&state.phrases, input)?, - ), - _ => return None - } - })) + to_sequencer_command(state, input) + .or_else(||to_focus_command(input).map(Focus)) } } pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option { - let stopped = state.clock().is_stopped(); + use super::app_transport::TransportCommand; Some(match input.event() { - // Play/pause - key!(Char(' ')) => Clock(if stopped { Play(None) } else { Pause(None) }), - // Play from start/rewind to start - key!(Shift-Char(' ')) => Clock(if stopped { Play(Some(0)) } else { Pause(Some(0)) }), - // Edit phrase - key!(Char('e')) => match state.focused() { - PhrasePlay => Editor(Show( - state.player.play_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) - )), - PhraseNext => Editor(Show( - state.player.next_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) - )), - PhraseList => Editor(Show( - Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()) - )), - _ => return None, - }, - _ => match state.focused() { - Transport(_) => match TransportCommand::input_to_command(state, input)? { + + // Transport: Play/pause + key!(Char(' ')) => + Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), + + // Transport: Play from start or rewind to start + key!(Shift-Char(' ')) => + Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), + + // Editor: zoom + key!(Char('z')) | key!(Char('-')) | key!(Char('_'))| key!(Char('=')) | key!(Char('+')) => + Editor(PhraseCommand::input_to_command(&state.editor, input)?), + + // List: select phrase to edit, change color + key!(Char('[')) | key!(Char(']')) | key!(Char('c')) => + Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?), + + // Enqueue currently edited phrase + key!(Enter) => + Enqueue(Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone())), + + // Switch between editor and list + key!(Tab) | key!(BackTab) | key!(Shift-Tab) | key!(Shift-BackTab) => match state.focus { + PhraseEditor => SequencerCommand::Focus(FocusCommand::Set(PhraseList)), + _ => SequencerCommand::Focus(FocusCommand::Set(PhraseList)), + } + + // Delegate to focused control: + _ => match state.focus { + PhraseEditor => Editor(PhraseCommand::input_to_command(&state.editor, input)?), + PhraseList => Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?), + Transport(_) => match to_transport_command(state, input)? { TransportCommand::Clock(command) => Clock(command), _ => return None, }, - PhraseEditor => Editor( - PhraseCommand::input_to_command(&state.editor, input)? - ), - PhraseList => match input.event() { - key!(Enter) => Enqueue(Some( - state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone() - )), - _ => Phrases( - PhrasesCommand::input_to_command(&state.phrases, input)? - ), - } - _ => return None } + }) } + +impl TransportControl for SequencerTui { + fn transport_focused (&self) -> Option { + match self.focus { + SequencerFocus::Transport(focus) => Some(focus), + _ => None + } + } +} diff --git a/crates/tek/src/tui/app_transport.rs b/crates/tek/src/tui/app_transport.rs index d4ca3d59..a3c11beb 100644 --- a/crates/tek/src/tui/app_transport.rs +++ b/crates/tek/src/tui/app_transport.rs @@ -207,16 +207,28 @@ impl FocusWrap for Option { } } -impl_focus!(TransportTui TransportFocus [ - //&[Menu], - &[ - PlayPause, - Bpm, - Sync, - Quant, - Clock, - ], -]); +impl HasFocus for TransportTui { + type Item = TransportFocus; + /// Get the currently focused item. + fn focused (&self) -> Self::Item { + self.focus.inner() + } + /// Get the currently focused item. + fn set_focused (&mut self, to: Self::Item) { + self.focus.set_inner(to) + } +} + +//impl_focus!(TransportTui TransportFocus [ + ////&[Menu], + //&[ + //PlayPause, + //Bpm, + //Sync, + //Quant, + //Clock, + //], +//]); #[derive(Copy, Clone)] pub struct TransportStatusBar; @@ -239,14 +251,24 @@ impl Handle for TransportTui { } } +pub trait TransportControl: HasClock + HasFocus { + fn transport_focused (&self) -> Option; +} + +impl TransportControl for TransportTui { + fn transport_focused (&self) -> Option { + Some(self.focus.inner()) + } +} + #[derive(Clone, Debug, PartialEq)] pub enum TransportCommand { - Focus(FocusCommand), + Focus(FocusCommand), Clock(ClockCommand), } -impl Command for TransportCommand { - fn execute (self, state: &mut T) -> Perhaps { +impl Command for TransportCommand { + fn execute (self, state: &mut TransportTui) -> Perhaps { Ok(match self { Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus), Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock), @@ -254,44 +276,26 @@ impl Command for TransportCommand { } } -pub trait TransportControl: HasClock + FocusGrid + HasEnter { - fn transport_focused (&self) -> Option; -} - -impl TransportControl for TransportTui { - fn transport_focused (&self) -> Option { - Some(self.focus.inner()) - } -} - -impl TransportControl for SequencerTui { - fn transport_focused (&self) -> Option { - match self.focus.inner() { - SequencerFocus::Transport(focus) => Some(focus), - _ => None +impl Command for FocusCommand { + fn execute (self, state: &mut TransportTui) -> Perhaps> { + if let FocusCommand::Set(to) = self { + state.set_focused(to); } + Ok(None) } } -impl TransportControl for ArrangerTui { - fn transport_focused (&self) -> Option { - match self.focus.inner() { - ArrangerFocus::Transport(focus) => Some(focus), - _ => None - } - } -} - -impl InputToCommand for TransportCommand { - fn input_to_command (state: &T, input: &TuiInput) -> Option { +impl InputToCommand for TransportCommand { + fn input_to_command (state: &TransportTui, input: &TuiInput) -> Option { to_transport_command(state, input) .or_else(||to_focus_command(input).map(TransportCommand::Focus)) } } -pub fn to_transport_command (state: &T, input: &TuiInput) -> Option +pub fn to_transport_command (state: &T, input: &TuiInput) -> Option where - T: TransportControl + T: TransportControl, + U: Into>, { Some(match input.event() { key!(Left) => Focus(Prev), diff --git a/crates/tek/src/tui/engine_focus.rs b/crates/tek/src/tui/engine_focus.rs deleted file mode 100644 index 4b0c417a..00000000 --- a/crates/tek/src/tui/engine_focus.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::*; - -pub trait FocusWrap { - fn wrap <'a, W: Render> (self, focus: T, content: &'a W) - -> impl Render + 'a; -} - -pub fn to_focus_command (input: &TuiInput) -> Option { - use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc}; - Some(match input.event() { - key!(Tab) => FocusCommand::Next, - key!(Shift-Tab) => FocusCommand::Prev, - key!(BackTab) => FocusCommand::Prev, - key!(Shift-BackTab) => FocusCommand::Prev, - key!(Up) => FocusCommand::Up, - key!(Down) => FocusCommand::Down, - key!(Left) => FocusCommand::Left, - key!(Right) => FocusCommand::Right, - key!(Enter) => FocusCommand::Enter, - key!(Esc) => FocusCommand::Exit, - _ => return None - }) -} - -#[macro_export] macro_rules! impl_focus { - ($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => { - impl HasFocus for $Struct { - type Item = $Focus; - /// Get the currently focused item. - fn focused (&self) -> Self::Item { - self.focus.inner() - } - /// Get the currently focused item. - fn set_focused (&mut self, to: Self::Item) { - self.focus.set_inner(to) - } - $(fn focus_updated (&mut $self) { $update_focus })? - } - impl HasEnter for $Struct { - /// Get the currently focused item. - fn entered (&self) -> bool { - self.focus.is_entered() - } - /// Get the currently focused item. - fn set_entered (&mut self, entered: bool) { - if entered { - self.focus.to_entered() - } else { - self.focus.to_focused() - } - } - } - impl FocusGrid for $Struct { - fn focus_cursor (&self) -> (usize, usize) { - self.cursor - } - fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } - fn focus_layout (&self) -> &[&[$Focus]] { - use $Focus::*; - &$Grid - } - } - } -} diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 669a43f1..5ae39a71 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -62,7 +62,7 @@ impl Default for PhraseEditorModel { time_point: 0.into(), view_mode: Box::new(PianoHorizontal { buffer: Default::default(), - time_zoom: 24, + time_zoom: Some(24), note_zoom: PhraseViewNoteZoom::N(1) }), } @@ -190,7 +190,7 @@ render!(|self: PhraseViewStats<'a>|{ "Time: {}/{} {} {upper_right}", self.0.time_point, phrase.read().unwrap().length, - pulses_to_name(self.0.view_mode.time_zoom()), + pulses_to_name(self.0.view_mode.time_zoom().unwrap()), ) }; Tui::pull_x(1, Tui::bg(title_color, Tui::fg(TuiTheme::g(224), upper_right))) @@ -218,7 +218,6 @@ render!(|self: PhraseViewCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok( to, self.0.time_point, self.0.time_start, - self.0.view_mode.time_zoom(), self.0.note_point, self.0.note_len, self.0.note_range.1, @@ -234,8 +233,8 @@ pub enum PhraseEditMode { pub trait PhraseViewMode { fn show (&mut self, phrase: Option<&Phrase>, note_len: usize); - fn time_zoom (&self) -> usize; - fn set_time_zoom (&mut self, time_zoom: usize); + fn time_zoom (&self) -> Option; + fn set_time_zoom (&mut self, time_zoom: Option); fn buffer_width (&self, phrase: &Phrase) -> usize; fn buffer_height (&self, phrase: &Phrase) -> usize; fn render_keys (&self, @@ -247,7 +246,6 @@ pub trait PhraseViewMode { to: &mut TuiOutput, time_point: usize, time_start: usize, - time_zoom: usize, note_point: usize, note_len: usize, note_hi: usize, @@ -256,7 +254,7 @@ pub trait PhraseViewMode { } pub struct PianoHorizontal { - time_zoom: usize, + time_zoom: Option, note_zoom: PhraseViewNoteZoom, buffer: BigBuffer, } @@ -269,22 +267,23 @@ pub enum PhraseViewNoteZoom { } impl PhraseViewMode for PianoHorizontal { - fn time_zoom (&self) -> usize { + fn time_zoom (&self) -> Option { self.time_zoom } - fn set_time_zoom (&mut self, time_zoom: usize) { + fn set_time_zoom (&mut self, time_zoom: Option) { self.time_zoom = time_zoom } fn show (&mut self, phrase: Option<&Phrase>, note_len: usize) { if let Some(phrase) = phrase { self.buffer = BigBuffer::new(self.buffer_width(phrase), self.buffer_height(phrase)); - draw_piano_horizontal(&mut self.buffer, phrase, self.time_zoom, note_len); + draw_piano_horizontal_bg(&mut self.buffer, phrase, self.time_zoom.unwrap(), note_len); + draw_piano_horizontal_fg(&mut self.buffer, phrase, self.time_zoom.unwrap()); } else { self.buffer = Default::default(); } } fn buffer_width (&self, phrase: &Phrase) -> usize { - phrase.length / self.time_zoom + phrase.length / self.time_zoom.unwrap() } /// Determine the required height to render the phrase. fn buffer_height (&self, phrase: &Phrase) -> usize { @@ -369,12 +368,12 @@ impl PhraseViewMode for PianoHorizontal { to: &mut TuiOutput, time_point: usize, time_start: usize, - time_zoom: usize, note_point: usize, note_len: usize, note_hi: usize, note_lo: usize, ) { + let time_zoom = self.time_zoom.unwrap(); let [x0, y0, w, _] = to.area().xywh(); let style = Some(Style::default().fg(Color::Rgb(0,255,0))); for (y, note) in (note_lo..=note_hi).rev().enumerate() { @@ -386,7 +385,7 @@ impl PhraseViewMode for PianoHorizontal { to.blit(&"█", x0 + x as u16, y0 + y as u16, style); let tail = note_len as u16 / time_zoom as u16; for x_tail in (x0 + x + 1)..(x0 + x + tail) { - to.blit(&"▄", x_tail, y0 + y as u16, style); + to.blit(&"▂", x_tail, y0 + y as u16, style); } break } @@ -417,7 +416,6 @@ fn draw_piano_horizontal_bg ( for (y, note) in (0..127).rev().enumerate() { for (x, time) in (0..target.width).map(|x|(x, x*time_zoom)) { let cell = target.get_mut(x, y).unwrap(); - //cell.set_fg(Color::Rgb(48, 55, 45)); cell.set_bg(phrase.color.darkest.rgb); cell.set_fg(phrase.color.darker.rgb); cell.set_char(if time % 384 == 0 { @@ -447,7 +445,7 @@ fn draw_piano_horizontal_fg ( for (y, note) in (0..127).rev().enumerate() { let cell = target.get_mut(x, note).unwrap(); if notes_on[note] { - cell.set_char('▄'); + cell.set_char('▂'); cell.set_style(style); } } @@ -484,7 +482,7 @@ pub enum PhraseCommand { SetNoteScroll(usize), SetTimeCursor(usize), SetTimeScroll(usize), - SetTimeZoom(usize), + SetTimeZoom(Option), Show(Option>>), SetEditMode(PhraseEditMode), ToggleDirection, @@ -504,10 +502,11 @@ impl InputToCommand for PhraseCommand { Some(match from.event() { key!(Char('`')) => ToggleDirection, key!(Esc) => SetEditMode(PhraseEditMode::Scroll), - key!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)), - key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)), - key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)), - key!(Char('+')) => SetTimeZoom(prev_note_length(time_zoom)), + key!(Char('z')) => SetTimeZoom(None), + key!(Char('-')) => SetTimeZoom(time_zoom.map(next_note_length)), + key!(Char('_')) => SetTimeZoom(time_zoom.map(next_note_length)), + key!(Char('=')) => SetTimeZoom(time_zoom.map(prev_note_length)), + key!(Char('+')) => SetTimeZoom(time_zoom.map(prev_note_length)), key!(Char('a')) => AppendNote, key!(Char('s')) => PutNote, // TODO: no triplet/dotted @@ -523,8 +522,8 @@ impl InputToCommand for PhraseCommand { key!(Down) => SetNoteScroll(note_lo.saturating_sub(1)), key!(PageUp) => SetNoteScroll(note_lo + 3), key!(PageDown) => SetNoteScroll(note_lo.saturating_sub(3)), - key!(Left) => SetTimeScroll(time_start.saturating_sub(time_zoom)), - key!(Right) => SetTimeScroll(time_start + time_zoom), + key!(Left) => SetTimeScroll(time_start.saturating_sub(time_zoom.unwrap())), + key!(Right) => SetTimeScroll(time_start + time_zoom.unwrap()), _ => return None }, PhraseEditMode::Note => match from.event() { @@ -535,8 +534,8 @@ impl InputToCommand for PhraseCommand { key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)), key!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)), key!(Right) => SetTimeCursor((time_point + note_len) % length), - key!(Shift-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)), - key!(Shift-Right) => SetTimeCursor((time_point + time_zoom) % length), + key!(Shift-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom.unwrap())), + key!(Shift-Right) => SetTimeCursor((time_point + time_zoom.unwrap()) % length), _ => return None }, } diff --git a/crates/tek/src/tui/phrase_list.rs b/crates/tek/src/tui/phrase_list.rs index d0c2f8b7..eed3fbe0 100644 --- a/crates/tek/src/tui/phrase_list.rs +++ b/crates/tek/src/tui/phrase_list.rs @@ -72,7 +72,7 @@ impl HasPhraseList for SequencerTui { self.focused() == SequencerFocus::PhraseList } fn phrases_entered (&self) -> bool { - self.entered() && self.phrases_focused() + true && self.phrases_focused() } fn phrases_mode (&self) -> &Option { &self.phrases.mode