diff --git a/crates/tek_tui/src/tui_focus.rs b/crates/tek_tui/src/tui_focus.rs index d8d9ebdf..8a29a328 100644 --- a/crates/tek_tui/src/tui_focus.rs +++ b/crates/tek_tui/src/tui_focus.rs @@ -17,6 +17,7 @@ pub enum TransportFocus { Clock, Quant, } + /// Sections in the sequencer app that may be focused #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum SequencerFocus { @@ -27,6 +28,7 @@ pub enum SequencerFocus { /// The phrase editor (sequencer) is focused PhraseEditor, } + /// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum ArrangerFocus { @@ -67,27 +69,6 @@ impl FocusWrap for Option { } } -impl TransportFocus { - pub fn next (&mut self) { - *self = match self { - Self::PlayPause => Self::Bpm, - Self::Bpm => Self::Quant, - Self::Quant => Self::Sync, - Self::Sync => Self::Clock, - Self::Clock => Self::PlayPause, - } - } - pub fn prev (&mut self) { - *self = match self { - Self::PlayPause => Self::Clock, - Self::Bpm => Self::PlayPause, - Self::Quant => Self::Bpm, - Self::Sync => Self::Quant, - Self::Clock => Self::Sync, - } - } -} - macro_rules! impl_focus { ($Struct:ident $Focus:ident $Grid:expr) => { impl HasFocus for $Struct { @@ -132,24 +113,26 @@ macro_rules! impl_focus { } impl_focus!(TransportTui TransportFocus [ - &[Menu], + //&[Menu], &[Content(Bpm), Content(Sync), Content(PlayPause), Content(Clock), Content(Quant)], ]); impl_focus!(SequencerTui SequencerFocus [ + //&[ + //Menu, + //Menu, + //Menu, + //Menu, + //Menu, + //], &[ - Menu, - Menu, - Menu, - Menu, - Menu, - ], &[ Content(Transport(TransportFocus::Bpm)), Content(Transport(TransportFocus::Sync)), Content(Transport(TransportFocus::PlayPause)), Content(Transport(TransportFocus::Clock)), Content(Transport(TransportFocus::Quant)) - ], &[ + ], + &[ Content(Phrases), Content(Phrases), Content(PhraseEditor), @@ -159,13 +142,14 @@ impl_focus!(SequencerTui SequencerFocus [ ]); impl_focus!(ArrangerTui ArrangerFocus [ + //&[ + //Menu, + //Menu, + //Menu, + //Menu, + //Menu, + //], &[ - Menu, - Menu, - Menu, - Menu, - Menu, - ], &[ Content(Transport(TransportFocus::Bpm)), Content(Transport(TransportFocus::Sync)), Content(Transport(TransportFocus::PlayPause)), diff --git a/crates/tek_tui/src/tui_init.rs b/crates/tek_tui/src/tui_init.rs index fed142cf..d6073291 100644 --- a/crates/tek_tui/src/tui_init.rs +++ b/crates/tek_tui/src/tui_init.rs @@ -9,7 +9,7 @@ impl TryFrom<&Arc>> for TransportTui { clock: ClockModel::from(&Arc::new(jack.read().unwrap().transport())), size: Measure::new(), cursor: (0, 0), - focus: FocusState::Entered(AppFocus::Content(TransportFocus::PlayPause)) + focus: FocusState::Entered(AppFocus::Content(TransportFocus::Bpm)) }) } } @@ -30,7 +30,7 @@ impl TryFrom<&Arc>> for SequencerTui { midi_buf: vec![vec![];65536], note_buf: vec![], clock, - focus: FocusState::Entered(AppFocus::Content(SequencerFocus::Transport(TransportFocus::PlayPause))) + focus: FocusState::Entered(AppFocus::Content(SequencerFocus::Transport(TransportFocus::Bpm))) }) } } @@ -58,7 +58,7 @@ impl TryFrom<&Arc>> for ArrangerTui { status_bar: None, midi_buf: vec![vec![];65536], note_buf: vec![], - focus: FocusState::Entered(AppFocus::Content(ArrangerFocus::Transport(TransportFocus::PlayPause))) + focus: FocusState::Entered(AppFocus::Content(ArrangerFocus::Transport(TransportFocus::Bpm))) }) } } diff --git a/crates/tek_tui/src/tui_input.rs b/crates/tek_tui/src/tui_input.rs index 43554492..f27f26e9 100644 --- a/crates/tek_tui/src/tui_input.rs +++ b/crates/tek_tui/src/tui_input.rs @@ -1,5 +1,26 @@ use crate::*; +impl InputToCommand for TransportCommand { + fn input_to_command (state: &T, input: &TuiInput) -> Option { + to_transport_command(state, input) + .or_else(||to_focus_command(input).map(TransportCommand::Focus)) + } +} + +impl InputToCommand for SequencerCommand { + fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option { + to_sequencer_command(state, input) + .or_else(||to_focus_command(input).map(SequencerCommand::Focus)) + } +} + +impl InputToCommand for ArrangerCommand { + fn input_to_command (state: &ArrangerTui, input: &TuiInput) -> Option { + to_arranger_command(state, input) + .or_else(||to_focus_command(input).map(ArrangerCommand::Focus)) + } +} + fn to_focus_command (input: &TuiInput) -> Option { use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc}; Some(match input.event() { @@ -17,182 +38,177 @@ fn to_focus_command (input: &TuiInput) -> Option { }) } -impl InputToCommand for TransportCommand { - fn input_to_command (state: &T, input: &TuiInput) -> Option { - use ClockCommand::{SetBpm, SetQuant, SetSync}; - use TransportCommand::{Focus, Clock}; - use KeyCode::{Enter, Left, Right, Char}; - Some(match input.event() { - key!(Left) => Focus(FocusCommand::Prev), - key!(Right) => Focus(FocusCommand::Next), - key!(Char(' ')) => todo!("toolbar space"), - key!(Shift-Char(' ')) => todo!("toolbar shift-space"), - _ => match state.transport_focused().unwrap() { - TransportFocus::Bpm => match input.event() { - key!(Char(',')) => Clock(SetBpm(state.bpm().get() - 1.0)), - key!(Char('.')) => Clock(SetBpm(state.bpm().get() + 1.0)), - key!(Char('<')) => Clock(SetBpm(state.bpm().get() - 0.001)), - key!(Char('>')) => Clock(SetBpm(state.bpm().get() + 0.001)), - _ => return None, - }, - TransportFocus::Quant => match input.event() { - key!(Char(',')) => Clock(SetQuant(state.prev_quant())), - key!(Char('.')) => Clock(SetQuant(state.next_quant())), - key!(Char('<')) => Clock(SetQuant(state.prev_quant())), - key!(Char('>')) => Clock(SetQuant(state.next_quant())), - _ => return None, - }, - TransportFocus::Sync => match input.event() { - key!(Char(',')) => Clock(SetSync(state.prev_sync())), - key!(Char('.')) => Clock(SetSync(state.next_sync())), - key!(Char('<')) => Clock(SetSync(state.prev_sync())), - key!(Char('>')) => Clock(SetSync(state.next_sync())), - _ => return None, - }, - TransportFocus::Clock => match input.event() { - key!(Char(',')) => todo!("transport seek bar"), - key!(Char('.')) => todo!("transport seek bar"), - key!(Char('<')) => todo!("transport seek beat"), - key!(Char('>')) => todo!("transport seek beat"), - _ => return None, - }, - TransportFocus::PlayPause => match input.event() { - key!(Enter) => todo!("transport play toggle"), - key!(Shift-Enter) => todo!("transport shift-play toggle"), - _ => return None, - }, - } - }) - } +fn to_transport_command (state: &T, input: &TuiInput) -> Option +where + T: TransportControl +{ + use ClockCommand::{SetBpm, SetQuant, SetSync}; + use TransportCommand::{Focus, Clock}; + use KeyCode::{Enter, Left, Right, Char}; + Some(match input.event() { + key!(Left) => Focus(FocusCommand::Prev), + key!(Right) => Focus(FocusCommand::Next), + key!(Char(' ')) => todo!("toolbar space"), + key!(Shift-Char(' ')) => todo!("toolbar shift-space"), + _ => match state.transport_focused().unwrap() { + TransportFocus::Bpm => match input.event() { + key!(Char(',')) => Clock(SetBpm(state.bpm().get() - 1.0)), + key!(Char('.')) => Clock(SetBpm(state.bpm().get() + 1.0)), + key!(Char('<')) => Clock(SetBpm(state.bpm().get() - 0.001)), + key!(Char('>')) => Clock(SetBpm(state.bpm().get() + 0.001)), + _ => return None, + }, + TransportFocus::Quant => match input.event() { + key!(Char(',')) => Clock(SetQuant(state.prev_quant())), + key!(Char('.')) => Clock(SetQuant(state.next_quant())), + key!(Char('<')) => Clock(SetQuant(state.prev_quant())), + key!(Char('>')) => Clock(SetQuant(state.next_quant())), + _ => return None, + }, + TransportFocus::Sync => match input.event() { + key!(Char(',')) => Clock(SetSync(state.prev_sync())), + key!(Char('.')) => Clock(SetSync(state.next_sync())), + key!(Char('<')) => Clock(SetSync(state.prev_sync())), + key!(Char('>')) => Clock(SetSync(state.next_sync())), + _ => return None, + }, + TransportFocus::Clock => match input.event() { + key!(Char(',')) => todo!("transport seek bar"), + key!(Char('.')) => todo!("transport seek bar"), + key!(Char('<')) => todo!("transport seek beat"), + key!(Char('>')) => todo!("transport seek beat"), + _ => return None, + }, + TransportFocus::PlayPause => match input.event() { + key!(Enter) => todo!("transport play toggle"), + key!(Shift-Enter) => todo!("transport shift-play toggle"), + _ => return None, + }, + } + }) } -impl InputToCommand for SequencerCommand { - fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option { - use SequencerCommand::*; - Some(if state.entered() { - match state.focused() { - AppFocus::Menu => todo!(), - AppFocus::Content(SequencerFocus::Transport(_)) => { - use TransportCommand::{Clock, Focus}; - match TransportCommand::input_to_command(state, input)? { - Clock(_) => { todo!() }, - Focus(_) => { todo!() }, - } - }, - AppFocus::Content(SequencerFocus::Phrases) => - Phrases(PhrasesCommand::input_to_command(state, input)?), - AppFocus::Content(SequencerFocus::PhraseEditor) => - Editor(PhraseCommand::input_to_command(state, input)?), - } - } else if let Some(command) = to_focus_command(input) { - Self::Focus(command) - } else { - return None - }) +fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option { + use SequencerCommand as Cmd; + if !state.entered() { + return None } + Some(match state.focused() { + AppFocus::Menu => { + todo!() + }, + AppFocus::Content(SequencerFocus::Transport(_)) => { + match TransportCommand::input_to_command(state, input)? { + TransportCommand::Clock(_) => { todo!() }, + TransportCommand::Focus(command) => Cmd::Focus(command), + } + }, + AppFocus::Content(SequencerFocus::Phrases) => { + Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?) + }, + AppFocus::Content(SequencerFocus::PhraseEditor) => { + Cmd::Editor(PhraseCommand::input_to_command(state, input)?) + }, + }) } -impl InputToCommand for ArrangerCommand { - fn input_to_command (state: &ArrangerTui, input: &TuiInput) -> Option { - Some(if state.entered() { - match state.focused() { - AppFocus::Menu => todo!(), - AppFocus::Content(ArrangerFocus::Transport(_)) => { - use TransportCommand::{Clock, Focus}; - match TransportCommand::input_to_command(state, input)? { - Clock(_) => { todo!() }, - Focus(_) => { todo!() } - } - }, - AppFocus::Content(ArrangerFocus::PhraseEditor) => { - Self::Editor(PhraseCommand::input_to_command(state, input)?) - }, - AppFocus::Content(ArrangerFocus::Phrases) => match input.event() { - key!(KeyCode::Char('e')) => { - Self::EditPhrase(state.phrase_editing().clone()) +fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option { + use ArrangerCommand as Cmd; + if !state.entered() { + return None + } + Some(match state.focused() { + AppFocus::Menu => todo!(), + AppFocus::Content(ArrangerFocus::Transport(_)) => { + use TransportCommand::{Clock, Focus}; + match TransportCommand::input_to_command(state, input)? { + Clock(_) => { todo!() }, + Focus(command) => Cmd::Focus(command) + } + }, + AppFocus::Content(ArrangerFocus::PhraseEditor) => { + Cmd::Editor(PhraseCommand::input_to_command(state, input)?) + }, + AppFocus::Content(ArrangerFocus::Phrases) => match input.event() { + key!(KeyCode::Char('e')) => { + Cmd::EditPhrase(state.phrase_editing().clone()) + }, + _ => { + Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?) + } + }, + AppFocus::Content(ArrangerFocus::Arranger) => { + use ArrangerSelection as Select; + use ArrangerTrackCommand as Track; + use ArrangerClipCommand as Clip; + use ArrangerSceneCommand as Scene; + use KeyCode::{Char, Up, Down, Left, Right, Enter, Delete}; + match input.event() { + key!(Char('e')) => Cmd::EditPhrase(state.phrase_editing().clone()), + key!(Char('l')) => Cmd::Clip(Clip::SetLoop(false)), + key!(Char('+')) => Cmd::Zoom(0), // TODO + key!(Char('=')) => Cmd::Zoom(0), // TODO + key!(Char('_')) => Cmd::Zoom(0), // TODO + key!(Char('-')) => Cmd::Zoom(0), // TODO + key!(Char('`')) => { todo!("toggle state mode") }, + key!(Ctrl-Char('a')) => Cmd::Scene(Scene::Add), + key!(Ctrl-Char('t')) => Cmd::Track(Track::Add), + _ => match state.selected() { + Select::Mix => match input.event() { + key!(Down) => Cmd::Select(Select::Scene(0)), + key!(Right) => Cmd::Select(Select::Track(0)), + key!(Char(',')) => Cmd::Zoom(0), + key!(Char('.')) => Cmd::Zoom(0), + key!(Char('<')) => Cmd::Zoom(0), + key!(Char('>')) => Cmd::Zoom(0), + key!(Delete) => Cmd::Clear, + key!(Char('c')) => Cmd::Color(ItemColor::random()), + _ => return None + }, + Select::Track(t) => match input.event() { + key!(Down) => Cmd::Select(Select::Clip(t, 0)), + key!(Left) => Cmd::Select(if t > 0 { Select::Track(t - 1) } else { Select::Mix }), + key!(Right) => Cmd::Select(Select::Track(t + 1)), + key!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)), + key!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)), + key!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)), + key!(Char('>')) => Cmd::Track(Track::Swap(t, t + 1)), + key!(Delete) => Cmd::Track(Track::Delete(t)), + //key!(Char('c')) => Cmd::Track(Track::Color(t, ItemColor::random())), + _ => return None + }, + Select::Scene(s) => match input.event() { + key!(Up) => Cmd::Select(if s > 0 { Select::Scene(s - 1) } else { Select::Mix }), + key!(Down) => Cmd::Select(Select::Scene(s + 1)), + key!(Right) => Cmd::Select(Select::Clip(0, s)), + key!(Char(',')) => Cmd::Scene(Scene::Swap(s, s - 1)), + key!(Char('.')) => Cmd::Scene(Scene::Swap(s, s + 1)), + key!(Char('<')) => Cmd::Scene(Scene::Swap(s, s - 1)), + key!(Char('>')) => Cmd::Scene(Scene::Swap(s, s + 1)), + key!(Enter) => Cmd::Scene(Scene::Play(s)), + key!(Delete) => Cmd::Scene(Scene::Delete(s)), + //key!(Char('c')) => Cmd::Track(Scene::Color(s, ItemColor::random())), + _ => return None + }, + Select::Clip(t, s) => match input.event() { + key!(Up) => Cmd::Select(if s > 0 { Select::Clip(t, s - 1) } else { Select::Track(t) }), + key!(Down) => Cmd::Select(Select::Clip(t, s + 1)), + key!(Left) => Cmd::Select(if t > 0 { Select::Clip(t - 1, s) } else { Select::Scene(s) }), + key!(Right) => Cmd::Select(Select::Clip(t + 1, s)), + key!(Char(',')) => Cmd::Clip(Clip::Set(t, s, None)), + key!(Char('.')) => Cmd::Clip(Clip::Set(t, s, None)), + key!(Char('<')) => Cmd::Clip(Clip::Set(t, s, None)), + key!(Char('>')) => Cmd::Clip(Clip::Set(t, s, None)), + key!(Delete) => Cmd::Clip(Clip::Set(t, s, None)), + //key!(Char('c')) => Cmd::Clip(Clip::Color(t, s, ItemColor::random())), + //key!(Char('g')) => Cmd::Clip(Clip(Clip::Get(t, s))), + //key!(Char('s')) => Cmd::Clip(Clip(Clip::Set(t, s))), + _ => return None }, - _ => { - Self::Phrases(PhrasesCommand::input_to_command(state, input)?) - } - }, - AppFocus::Content(ArrangerFocus::Arranger) => { - use ArrangerSelection as Select; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; - use ArrangerSceneCommand as Scene; - use KeyCode::{Char, Up, Down, Left, Right, Enter, Delete}; - match input.event() { - key!(Char('e')) => Self::EditPhrase(state.phrase_editing().clone()), - key!(Char('l')) => Self::Clip(Clip::SetLoop(false)), - key!(Char('+')) => Self::Zoom(0), // TODO - key!(Char('=')) => Self::Zoom(0), // TODO - key!(Char('_')) => Self::Zoom(0), // TODO - key!(Char('-')) => Self::Zoom(0), // TODO - key!(Char('`')) => { todo!("toggle state mode") }, - key!(Ctrl-Char('a')) => Self::Scene(Scene::Add), - key!(Ctrl-Char('t')) => Self::Track(Track::Add), - _ => match state.selected() { - Select::Mix => match input.event() { - key!(Down) => Self::Select(Select::Scene(0)), - key!(Right) => Self::Select(Select::Track(0)), - key!(Char(',')) => Self::Zoom(0), - key!(Char('.')) => Self::Zoom(0), - key!(Char('<')) => Self::Zoom(0), - key!(Char('>')) => Self::Zoom(0), - key!(Delete) => Self::Clear, - key!(Char('c')) => Self::Color(ItemColor::random()), - _ => return None - }, - Select::Track(t) => match input.event() { - key!(Down) => Self::Select(Select::Clip(t, 0)), - key!(Left) => Self::Select(if t > 0 { Select::Track(t - 1) } else { Select::Mix }), - key!(Right) => Self::Select(Select::Track(t + 1)), - key!(Char(',')) => Self::Track(Track::Swap(t, t - 1)), - key!(Char('.')) => Self::Track(Track::Swap(t, t + 1)), - key!(Char('<')) => Self::Track(Track::Swap(t, t - 1)), - key!(Char('>')) => Self::Track(Track::Swap(t, t + 1)), - key!(Delete) => Self::Track(Track::Delete(t)), - //key!(Char('c')) => Self::Track(Track::Color(t, ItemColor::random())), - _ => return None - }, - Select::Scene(s) => match input.event() { - key!(Up) => Self::Select(if s > 0 { Select::Scene(s - 1) } else { Select::Mix }), - key!(Down) => Self::Select(Select::Scene(s + 1)), - key!(Right) => Self::Select(Select::Clip(0, s)), - key!(Char(',')) => Self::Scene(Scene::Swap(s, s - 1)), - key!(Char('.')) => Self::Scene(Scene::Swap(s, s + 1)), - key!(Char('<')) => Self::Scene(Scene::Swap(s, s - 1)), - key!(Char('>')) => Self::Scene(Scene::Swap(s, s + 1)), - key!(Enter) => Self::Scene(Scene::Play(s)), - key!(Delete) => Self::Scene(Scene::Delete(s)), - //key!(Char('c')) => Self::Track(Scene::Color(s, ItemColor::random())), - _ => return None - }, - Select::Clip(t, s) => match input.event() { - key!(Up) => Self::Select(if s > 0 { Select::Clip(t, s - 1) } else { Select::Track(t) }), - key!(Down) => Self::Select(Select::Clip(t, s + 1)), - key!(Left) => Self::Select(if t > 0 { Select::Clip(t - 1, s) } else { Select::Scene(s) }), - key!(Right) => Self::Select(Select::Clip(t + 1, s)), - key!(Char(',')) => Self::Clip(Clip::Set(t, s, None)), - key!(Char('.')) => Self::Clip(Clip::Set(t, s, None)), - key!(Char('<')) => Self::Clip(Clip::Set(t, s, None)), - key!(Char('>')) => Self::Clip(Clip::Set(t, s, None)), - key!(Delete) => Self::Clip(Clip::Set(t, s, None)), - //key!(Char('c')) => Self::Clip(Clip::Color(t, s, ItemColor::random())), - //key!(Char('g')) => Self::Clip(Clip(Clip::Get(t, s))), - //key!(Char('s')) => Self::Clip(Clip(Clip::Set(t, s))), - _ => return None - }, - } - } } } - } else if let Some(command) = to_focus_command(input) { - Self::Focus(command) - } else { - return None - }) - } + } + }) } impl InputToCommand for PhrasesCommand {