diff --git a/crates/tek_api/src/arrange.rs b/crates/tek_api/src/arrange.rs index 65e89f85..a6edc930 100644 --- a/crates/tek_api/src/arrange.rs +++ b/crates/tek_api/src/arrange.rs @@ -230,3 +230,150 @@ impl ArrangerScene { //}) //} } + +#[derive(Clone, Debug)] +pub enum ArrangerCommand { + Clear, + Export, + Import, + StopAll, + Scene(ArrangerSceneCommand), + Track(ArrangerTrackCommand), + Clip(ArrangerClipCommand), +} + +#[derive(Clone, Debug)] +pub enum ArrangerSceneCommand { + Add, + Delete(usize), + RandomColor, + Play(usize), + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), +} + +#[derive(Clone, Debug)] +pub enum ArrangerTrackCommand { + Add, + Delete(usize), + RandomColor, + Stop, + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), +} + +#[derive(Clone, Debug)] +pub enum ArrangerClipCommand { + Play, + Get(usize, usize), + Set(usize, usize, Option>>), + Edit(Option>>), + SetLoop(bool), + RandomColor, +} + +impl Command for ArrangerCommand { + fn execute (self, state: &mut ArrangerModel) -> Perhaps { + match self { + Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) }, + Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) }, + Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) }, + _ => todo!() + } + Ok(None) + } +} + +impl Command for ArrangerSceneCommand { + fn execute (self, state: &mut ArrangerModel) -> Perhaps { + match self { + Self::Delete(index) => { state.scene_del(index); }, + _ => todo!() + } + Ok(None) + } +} + +impl Command for ArrangerTrackCommand { + fn execute (self, state: &mut ArrangerModel) -> Perhaps { + match self { + Self::Delete(index) => { state.track_del(index); }, + _ => todo!() + } + Ok(None) + } +} + +impl Command for ArrangerClipCommand { + fn execute (self, state: &mut ArrangerModel) -> Perhaps { + match self { + _ => todo!() + } + Ok(None) + } +} + +//impl Command for ArrangerSceneCommand { +//} + //Edit(phrase) => { state.state.phrase = phrase.clone() }, + //ToggleViewMode => { state.state.mode.to_next(); }, + //Delete => { state.state.delete(); }, + //Activate => { state.state.activate(); }, + //ZoomIn => { state.state.zoom_in(); }, + //ZoomOut => { state.state.zoom_out(); }, + //MoveBack => { state.state.move_back(); }, + //MoveForward => { state.state.move_forward(); }, + //RandomColor => { state.state.randomize_color(); }, + //Put => { state.state.phrase_put(); }, + //Get => { state.state.phrase_get(); }, + //AddScene => { state.state.scene_add(None, None)?; }, + //AddTrack => { state.state.track_add(None, None)?; }, + //ToggleLoop => { state.state.toggle_loop() }, + //pub fn zoom_in (&mut self) { + //if let ArrangerEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangerEditorMode::Vertical(factor + 1) + //} + //} + //pub fn zoom_out (&mut self) { + //if let ArrangerEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1)) + //} + //} + //pub fn move_back (&mut self) { + //match self.selected { + //ArrangerEditorFocus::Scene(s) => { + //if s > 0 { + //self.scenes.swap(s, s - 1); + //self.selected = ArrangerEditorFocus::Scene(s - 1); + //} + //}, + //ArrangerEditorFocus::Track(t) => { + //if t > 0 { + //self.tracks.swap(t, t - 1); + //self.selected = ArrangerEditorFocus::Track(t - 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} + //pub fn move_forward (&mut self) { + //match self.selected { + //ArrangerEditorFocus::Scene(s) => { + //if s < self.scenes.len().saturating_sub(1) { + //self.scenes.swap(s, s + 1); + //self.selected = ArrangerEditorFocus::Scene(s + 1); + //} + //}, + //ArrangerEditorFocus::Track(t) => { + //if t < self.tracks.len().saturating_sub(1) { + //self.tracks.swap(t, t + 1); + //self.selected = ArrangerEditorFocus::Track(t + 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} diff --git a/crates/tek_api/src/arrange_cmd.rs b/crates/tek_api/src/arrange_cmd.rs deleted file mode 100644 index aacf0fd1..00000000 --- a/crates/tek_api/src/arrange_cmd.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::*; - -#[derive(Clone)] -pub enum ArrangerCommand { - Clear, - Export, - Import, - StopAll, - Scene(ArrangerSceneCommand), - Track(ArrangerTrackCommand), - Clip(ArrangerClipCommand), -} - -#[derive(Clone)] -pub enum ArrangerSceneCommand { - Add, - Delete(usize), - RandomColor, - Play(usize), - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), -} - -#[derive(Clone)] -pub enum ArrangerTrackCommand { - Add, - Delete(usize), - RandomColor, - Stop, - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), -} - -#[derive(Clone)] -pub enum ArrangerClipCommand { - Play, - Get(usize, usize), - Set(usize, usize, Option>>), - Edit(Option>>), - SetLoop(bool), - RandomColor, -} - -impl Command for ArrangerCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) }, - Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) }, - Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) }, - _ => todo!() - } - Ok(None) - } -} - -impl Command for ArrangerSceneCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - Self::Delete(index) => { state.scene_del(index); }, - _ => todo!() - } - Ok(None) - } -} - -impl Command for ArrangerTrackCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - Self::Delete(index) => { state.track_del(index); }, - _ => todo!() - } - Ok(None) - } -} - -impl Command for ArrangerClipCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - _ => todo!() - } - Ok(None) - } -} - -//impl Command for ArrangerSceneCommand { -//} - //Edit(phrase) => { state.state.phrase = phrase.clone() }, - //ToggleViewMode => { state.state.mode.to_next(); }, - //Delete => { state.state.delete(); }, - //Activate => { state.state.activate(); }, - //ZoomIn => { state.state.zoom_in(); }, - //ZoomOut => { state.state.zoom_out(); }, - //MoveBack => { state.state.move_back(); }, - //MoveForward => { state.state.move_forward(); }, - //RandomColor => { state.state.randomize_color(); }, - //Put => { state.state.phrase_put(); }, - //Get => { state.state.phrase_get(); }, - //AddScene => { state.state.scene_add(None, None)?; }, - //AddTrack => { state.state.track_add(None, None)?; }, - //ToggleLoop => { state.state.toggle_loop() }, - //pub fn zoom_in (&mut self) { - //if let ArrangerEditorMode::Vertical(factor) = self.mode { - //self.mode = ArrangerEditorMode::Vertical(factor + 1) - //} - //} - //pub fn zoom_out (&mut self) { - //if let ArrangerEditorMode::Vertical(factor) = self.mode { - //self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1)) - //} - //} - //pub fn move_back (&mut self) { - //match self.selected { - //ArrangerEditorFocus::Scene(s) => { - //if s > 0 { - //self.scenes.swap(s, s - 1); - //self.selected = ArrangerEditorFocus::Scene(s - 1); - //} - //}, - //ArrangerEditorFocus::Track(t) => { - //if t > 0 { - //self.tracks.swap(t, t - 1); - //self.selected = ArrangerEditorFocus::Track(t - 1); - //// FIXME: also swap clip order in scenes - //} - //}, - //_ => todo!("arrangement: move forward") - //} - //} - //pub fn move_forward (&mut self) { - //match self.selected { - //ArrangerEditorFocus::Scene(s) => { - //if s < self.scenes.len().saturating_sub(1) { - //self.scenes.swap(s, s + 1); - //self.selected = ArrangerEditorFocus::Scene(s + 1); - //} - //}, - //ArrangerEditorFocus::Track(t) => { - //if t < self.tracks.len().saturating_sub(1) { - //self.tracks.swap(t, t + 1); - //self.selected = ArrangerEditorFocus::Track(t + 1); - //// FIXME: also swap clip order in scenes - //} - //}, - //_ => todo!("arrangement: move forward") - //} - //} diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index d7437d6c..d6e9eace 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -12,7 +12,6 @@ submod! { //api_jack arrange - arrange_cmd clock @@ -36,7 +35,6 @@ submod! { status transport - transport_cmd } pub trait JackModelApi { diff --git a/crates/tek_api/src/transport.rs b/crates/tek_api/src/transport.rs index 49e9c7d5..10fbb0e9 100644 --- a/crates/tek_api/src/transport.rs +++ b/crates/tek_api/src/transport.rs @@ -65,3 +65,33 @@ impl TransportModel { Ok(()) } } + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum TransportCommand { + Play(Option), + Pause(Option), + SeekUsec(f64), + SeekSample(f64), + SeekPulse(f64), + SetBpm(f64), + SetQuant(f64), + SetSync(f64), +} + +impl Command for TransportCommand { + fn execute (self, state: &mut T) -> Perhaps { + use TransportCommand::*; + match self { + Play(start) => {todo!()}, + Pause(start) => {todo!()}, + SeekUsec(usec) => {state.clock().current.update_from_usec(usec);}, + SeekSample(sample) => {state.clock().current.update_from_sample(sample);}, + SeekPulse(pulse) => {state.clock().current.update_from_pulse(pulse);}, + SetBpm(bpm) => {return Ok(Some(Self::SetBpm(state.clock().timebase().bpm.set(bpm))))}, + SetQuant(quant) => {return Ok(Some(Self::SetQuant(state.clock().quant.set(quant))))}, + SetSync(sync) => {return Ok(Some(Self::SetSync(state.clock().sync.set(sync))))}, + _ => { unreachable!() } + } + Ok(None) + } +} diff --git a/crates/tek_api/src/transport_cmd.rs b/crates/tek_api/src/transport_cmd.rs deleted file mode 100644 index d4b1bdc7..00000000 --- a/crates/tek_api/src/transport_cmd.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::*; - -#[derive(Copy, Clone, PartialEq)] -pub enum TransportCommand { - Play(Option), - Pause(Option), - SeekUsec(f64), - SeekSample(f64), - SeekPulse(f64), - SetBpm(f64), - SetQuant(f64), - SetSync(f64), -} - -impl Command for TransportCommand { - fn execute (self, state: &mut T) -> Perhaps { - use TransportCommand::*; - match self { - Play(start) => {todo!()}, - Pause(start) => {todo!()}, - SeekUsec(usec) => {state.clock().current.update_from_usec(usec);}, - SeekSample(sample) => {state.clock().current.update_from_sample(sample);}, - SeekPulse(pulse) => {state.clock().current.update_from_pulse(pulse);}, - SetBpm(bpm) => {return Ok(Some(Self::SetBpm(state.clock().timebase().bpm.set(bpm))))}, - SetQuant(quant) => {return Ok(Some(Self::SetQuant(state.clock().quant.set(quant))))}, - SetSync(sync) => {return Ok(Some(Self::SetSync(state.clock().sync.set(sync))))}, - _ => { unreachable!() } - } - Ok(None) - } -} diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 664f5641..90b3c22c 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -35,21 +35,21 @@ submod! { pub struct AppView where E: Engine, - A: Widget + Handle + Audio, - C: Command, + A: Widget + Audio, + C: Command, S: StatusBar, { pub app: A, pub cursor: (usize, usize), pub entered: bool, - pub menu_bar: Option>, + pub menu_bar: Option>, pub status_bar: Option, pub history: Vec, pub size: Measure, } -#[derive(Debug, Copy, Clone)] -pub enum AppViewCommand { +#[derive(Debug, Clone)] +pub enum AppViewCommand { Focus(FocusCommand), Undo, Redo, @@ -65,13 +65,13 @@ pub enum AppViewFocus { impl AppView where E: Engine, - A: Widget + Handle + Audio, - C: Command, + A: Widget + Audio, + C: Command, S: StatusBar { pub fn new ( app: A, - menu_bar: Option>, + menu_bar: Option>, status_bar: Option, ) -> Self { Self { @@ -88,8 +88,8 @@ where impl Content for AppView where - A: Widget + Handle + Audio, - C: Command, + A: Widget + Audio, + C: Command, S: StatusBar, { type Engine = Tui; diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 2440ae84..b2ef67b9 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -1,13 +1,246 @@ use crate::*; +impl TryFrom<&Arc>> for ArrangerApp { + type Error = Box; + fn try_from (jack: &Arc>) -> Usually { + Ok(Self::new(ArrangerModel { + name: Arc::new(RwLock::new(String::new())), + phrases: vec![], + scenes: vec![], + tracks: vec![], + transport: TransportModel { + metronome: false, + transport: jack.read().unwrap().transport(), + clock: Arc::new(Clock::from(Instant::default())), + jack: jack.clone(), + }, + }.into(), None, None)) + } +} + pub type ArrangerApp = AppView< E, ArrangerView, - ArrangerViewCommand, + ArrangerAppCommand, ArrangerStatusBar >; -/// Root level object for standalone `tek_arranger` +/// Handle top-level events in standalone arranger. +impl Handle for ArrangerApp { + fn handle (&mut self, i: &TuiInput) -> Perhaps { + ArrangerAppCommand::execute_with_state(self, i) + } +} + +pub type ArrangerAppCommand = AppViewCommand; + +#[derive(Clone, Debug)] +pub enum ArrangerViewCommand { + Edit(ArrangerCommand), + Select(ArrangerSelection), + Zoom(usize), + Transport(TransportCommand), + Phrases(PhrasePoolViewCommand), + Editor(PhraseEditorCommand), + EditPhrase(Option>>), +} + +impl InputToCommand> for ArrangerAppCommand { + fn input_to_command (view: &ArrangerApp, input: &TuiInput) -> Option { + use AppViewFocus::*; + use FocusCommand::*; + use ArrangerViewCommand::*; + Some(match input.event() { + 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), + key!(KeyCode::Up) => Self::Focus(Up), + key!(KeyCode::Down) => Self::Focus(Down), + key!(KeyCode::Left) => Self::Focus(Left), + key!(KeyCode::Right) => Self::Focus(Right), + key!(KeyCode::Enter) => Self::Focus(Enter), + key!(KeyCode::Esc) => Self::Focus(Exit), + key!(KeyCode::Char(' ')) => { + Self::App(Transport(TransportViewCommand::Transport(TransportCommand::Play(None)))) + }, + _ => match view.focused() { + Content(ArrangerViewFocus::Transport) => Transport( + TransportViewCommand::input_to_command(&view.sequencer.transport, input)? + ), + Content(ArrangerViewFocus::PhraseEditor) => Editor( + PhraseEditorCommand::input_to_command(&view.sequencer.editor, input)? + ), + Content(ArrangerViewFocus::PhrasePool) => match input.event() { + key!(KeyCode::Char('e')) => EditPhrase( + Some(view.sequencer.phrases.phrase().clone()) + ), + _ => Phrases( + PhrasePoolViewCommand::input_to_command(&view.sequencer.phrases, input)? + ) + }, + Content(ArrangerViewFocus::Arranger) => { + use ArrangerSelection as Focus; + use ArrangerCommand as Model; + use ArrangerTrackCommand as Track; + use ArrangerClipCommand as Clip; + use ArrangerSceneCommand as Scene; + match input.event() { + key!(KeyCode::Char('e')) => EditPhrase( + view.selected_phrase() + ), + _ => match input.event() { + // FIXME: boundary conditions + + key!(KeyCode::Up) => match view.selected { + ArrangerSelection::Mix => return None, + ArrangerSelection::Track(t) => return None, + ArrangerSelection::Scene(s) => Select(Focus::Scene(s - 1)), + ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)), + }, + + key!(KeyCode::Down) => match view.selected { + ArrangerSelection::Mix => Select(Focus::Scene(0)), + ArrangerSelection::Track(t) => Select(Focus::Clip(t, 0)), + ArrangerSelection::Scene(s) => Select(Focus::Scene(s + 1)), + ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s + 1)), + }, + + key!(KeyCode::Left) => match view.selected { + ArrangerSelection::Mix => return None, + ArrangerSelection::Track(t) => Select(Focus::Track(t - 1)), + ArrangerSelection::Scene(s) => return None, + ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t - 1, s)), + }, + + key!(KeyCode::Right) => match view.selected { + ArrangerSelection::Mix => return None, + ArrangerSelection::Track(t) => Select(Focus::Track(t + 1)), + ArrangerSelection::Scene(s) => Select(Focus::Clip(0, s)), + ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)), + }, + + key!(KeyCode::Char('+')) => Zoom(0), + + key!(KeyCode::Char('=')) => Zoom(0), + + key!(KeyCode::Char('_')) => Zoom(0), + + key!(KeyCode::Char('-')) => Zoom(0), + + key!(KeyCode::Char('`')) => { todo!("toggle view mode") }, + + key!(KeyCode::Char(',')) => match view.selected { + ArrangerSelection::Mix => Zoom(0), + ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))), + ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))), + ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('.')) => match view.selected { + ArrangerSelection::Mix => Zoom(0), + ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))), + ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))), + ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('<')) => match view.selected { + ArrangerSelection::Mix => Zoom(0), + ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))), + ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))), + ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('>')) => match view.selected { + ArrangerSelection::Mix => Zoom(0), + ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))), + ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))), + ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Enter) => match view.selected { + ArrangerSelection::Mix => return None, + ArrangerSelection::Track(t) => return None, + ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Play(s))), + ArrangerSelection::Clip(t, s) => return None, + }, + + key!(KeyCode::Delete) => match view.selected { + ArrangerSelection::Mix => Edit(Model::Clear), + ArrangerSelection::Track(t) => Edit(Model::Track(Track::Delete(t))), + ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Delete(s))), + ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('c')) => Edit(Model::Clip(Clip::RandomColor)), + + key!(KeyCode::Char('s')) => match view.selected { + ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + _ => return None, + }, + + key!(KeyCode::Char('g')) => match view.selected { + ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Get(t, s))), + _ => return None, + }, + + key!(Ctrl-KeyCode::Char('a')) => Edit(Model::Scene(Scene::Add)), + + key!(Ctrl-KeyCode::Char('t')) => Edit(Model::Track(Track::Add)), + + key!(KeyCode::Char('l')) => Edit(Model::Clip(Clip::SetLoop(false))), + + _ => return None + } + } + } + } + }) + } +} + +impl Command> for ArrangerAppCommand { + fn execute (self, state: &mut ArrangerApp) -> Perhaps { + let undo = match self { + Self::Focus(cmd) => { + delegate(cmd, Self::Focus, state) + }, + Self::App(cmd) => match cmd { + ArrangerViewCommand::Phrases(cmd) => { + delegate(cmd, |x|Self::App(ArrangerViewCommand::Phrases(x)), &mut state.app) + }, + ArrangerViewCommand::Editor(cmd) => { + delegate(cmd, |x|Self::App(ArrangerViewCommand::Editor(x)), &mut state.app) + }, + ArrangerViewCommand::Transport(cmd) => { + delegate(cmd, |x|Self::App(ArrangerViewCommand::Transport(x)), &mut state.app) + }, + ArrangerViewCommand::Zoom(zoom) => { + todo!(); + }, + ArrangerViewCommand::Select(selected) => { + state.selected = selected; + Ok(None) + }, + ArrangerViewCommand::Edit(command) => { + return Ok(command.execute(&mut state.model)?.map(ArrangerViewCommand::Edit)) + }, + ArrangerViewCommand::EditPhrase(phrase) => { + app.sequencer.editor.phrase = phrase.clone(); + state.focus(ArrangerViewFocus::PhraseEditor); + state.focus_enter(); + Ok(None) + } + }, + _ => {todo!()} + }?; + state.show_phrase(); + state.update_status(); + return Ok(undo); + } +} + +/// Root view for standalone `tek_arranger` pub struct ArrangerView { pub model: ArrangerModel, /// Sequencer component @@ -37,18 +270,6 @@ pub enum ArrangerMode { Vertical(usize), } -#[derive(Clone)] -pub enum ArrangerViewCommand { - Focus(FocusCommand), - Edit(ArrangerCommand), - Select(ArrangerSelection), - Zoom(usize), - Transport(TransportViewCommand), - Phrases(PhrasePoolViewCommand), - Editor(PhraseEditorCommand), - EditPhrase(Option>>), -} - /// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum ArrangerViewFocus { @@ -62,7 +283,7 @@ pub enum ArrangerViewFocus { PhraseEditor, } -#[derive(PartialEq, Clone, Copy)] +#[derive(PartialEq, Clone, Copy, Debug)] /// Represents the current user selection in the arranger pub enum ArrangerSelection { /// The whole mix is selected @@ -76,7 +297,7 @@ pub enum ArrangerSelection { } /// Status bar for arranger app -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum ArrangerStatusBar { Transport, ArrangerMix, @@ -88,24 +309,6 @@ pub enum ArrangerStatusBar { PhraseEdit, } -impl TryFrom<&Arc>> for ArrangerApp { - type Error = Box; - fn try_from (jack: &Arc>) -> Usually { - Ok(Self::new(ArrangerModel { - name: Arc::new(RwLock::new(String::new())), - phrases: vec![], - scenes: vec![], - tracks: vec![], - transport: TransportModel { - metronome: false, - transport: jack.read().unwrap().transport(), - clock: Arc::new(Clock::from(Instant::default())), - jack: jack.clone(), - }, - }.into(), None, None)) - } -} - impl From for ArrangerView { fn from (model: ArrangerModel) -> Self { let mut view = Self { @@ -262,7 +465,7 @@ impl ArrangerView { } } -impl Audio for ArrangerView { +impl Audio for ArrangerView { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { if self.model.process(client, scope) == Control::Quit { return Control::Quit @@ -290,200 +493,6 @@ impl Audio for ArrangerView { } } -/// Handle top-level events in standalone arranger. -impl Handle for ArrangerView { - fn handle (&mut self, i: &TuiInput) -> Perhaps { - ArrangerViewCommand::execute_with_state(self, i) - } -} - -impl InputToCommand> for ArrangerViewCommand { - fn input_to_command (view: &ArrangerView, input: &TuiInput) -> Option { - use FocusCommand::*; - use ArrangerViewCommand::*; - Some(match input.event() { - key!(KeyCode::Tab) => Focus(Next), - key!(Shift-KeyCode::Tab) => Focus(Prev), - key!(KeyCode::BackTab) => Focus(Prev), - key!(Shift-KeyCode::BackTab) => Focus(Prev), - key!(KeyCode::Up) => Focus(Up), - key!(KeyCode::Down) => Focus(Down), - key!(KeyCode::Left) => Focus(Left), - key!(KeyCode::Right) => Focus(Right), - key!(KeyCode::Enter) => Focus(Enter), - key!(KeyCode::Esc) => Focus(Exit), - key!(KeyCode::Char(' ')) => { - Transport(TransportViewCommand::Transport(TransportCommand::Play(None))) - }, - _ => match view.focused() { - ArrangerViewFocus::Transport => Transport( - TransportViewCommand::input_to_command(&view.sequencer.transport, input)? - ), - ArrangerViewFocus::PhraseEditor => Editor( - PhraseEditorCommand::input_to_command(&view.sequencer.editor, input)? - ), - ArrangerViewFocus::PhrasePool => match input.event() { - key!(KeyCode::Char('e')) => EditPhrase( - Some(view.sequencer.phrases.phrase().clone()) - ), - _ => Phrases( - PhrasePoolViewCommand::input_to_command(&view.sequencer.phrases, input)? - ) - }, - ArrangerViewFocus::Arranger => { - use ArrangerSelection as Focus; - use ArrangerCommand as Model; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; - use ArrangerSceneCommand as Scene; - match input.event() { - key!(KeyCode::Char('e')) => EditPhrase( - view.selected_phrase() - ), - _ => match input.event() { - // FIXME: boundary conditions - - key!(KeyCode::Up) => match view.selected { - ArrangerSelection::Mix => return None, - ArrangerSelection::Track(t) => return None, - ArrangerSelection::Scene(s) => Select(Focus::Scene(s - 1)), - ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)), - }, - - key!(KeyCode::Down) => match view.selected { - ArrangerSelection::Mix => Select(Focus::Scene(0)), - ArrangerSelection::Track(t) => Select(Focus::Clip(t, 0)), - ArrangerSelection::Scene(s) => Select(Focus::Scene(s + 1)), - ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s + 1)), - }, - - key!(KeyCode::Left) => match view.selected { - ArrangerSelection::Mix => return None, - ArrangerSelection::Track(t) => Select(Focus::Track(t - 1)), - ArrangerSelection::Scene(s) => return None, - ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t - 1, s)), - }, - - key!(KeyCode::Right) => match view.selected { - ArrangerSelection::Mix => return None, - ArrangerSelection::Track(t) => Select(Focus::Track(t + 1)), - ArrangerSelection::Scene(s) => Select(Focus::Clip(0, s)), - ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)), - }, - - key!(KeyCode::Char('+')) => Zoom(0), - - key!(KeyCode::Char('=')) => Zoom(0), - - key!(KeyCode::Char('_')) => Zoom(0), - - key!(KeyCode::Char('-')) => Zoom(0), - - key!(KeyCode::Char('`')) => { todo!("toggle view mode") }, - - key!(KeyCode::Char(',')) => match view.selected { - ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('.')) => match view.selected { - ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('<')) => match view.selected { - ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('>')) => match view.selected { - ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Enter) => match view.selected { - ArrangerSelection::Mix => return None, - ArrangerSelection::Track(t) => return None, - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Play(s))), - ArrangerSelection::Clip(t, s) => return None, - }, - - key!(KeyCode::Delete) => match view.selected { - ArrangerSelection::Mix => Edit(Model::Clear), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Delete(t))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Delete(s))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('c')) => Edit(Model::Clip(Clip::RandomColor)), - - key!(KeyCode::Char('s')) => match view.selected { - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), - _ => return None, - }, - - key!(KeyCode::Char('g')) => match view.selected { - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Get(t, s))), - _ => return None, - }, - - key!(Ctrl-KeyCode::Char('a')) => Edit(Model::Scene(Scene::Add)), - - key!(Ctrl-KeyCode::Char('t')) => Edit(Model::Track(Track::Add)), - - key!(KeyCode::Char('l')) => Edit(Model::Clip(Clip::SetLoop(false))), - - _ => return None - } - } - } - } - }) - } -} - -impl Command> for ArrangerViewCommand { - fn execute (self, view: &mut ArrangerView) -> Perhaps { - let undo = match self { - Self::Focus(cmd) => - delegate(cmd, Self::Focus, view), - Self::Phrases(cmd) => - delegate(cmd, Self::Phrases, &mut view.sequencer.phrases), - Self::Editor(cmd) => - delegate(cmd, Self::Editor, &mut view.sequencer.editor), - Self::Transport(cmd) => - delegate(cmd, Self::Transport, &mut view.sequencer.transport), - Self::Zoom(zoom) => { - todo!(); - }, - Self::Select(selected) => { - view.selected = selected; - Ok(None) - }, - Self::Edit(command) => { - return Ok(command.execute(&mut view.model)?.map(Self::Edit)) - }, - Self::EditPhrase(phrase) => { - view.sequencer.editor.phrase = phrase.clone(); - view.focus(ArrangerViewFocus::PhraseEditor); - view.focus_enter(); - Ok(None) - } - }?; - view.show_phrase(); - view.update_status(); - return Ok(undo); - } -} - //pub fn phrase_next (&mut self) { //if let ArrangerSelection::Clip(track, scene) = self.selected { //if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { @@ -617,7 +626,7 @@ impl FocusGrid for ArrangerApp { self.app.sequencer.transport.focused = focused == Content(Transport); self.app.sequencer.phrases.focused = focused == Content(PhrasePool); self.app.sequencer.editor.focused = focused == Content(PhraseEditor); - if let Some(status_bar) = self.status_bar { + if let Some(mut status_bar) = self.status_bar { status_bar.update(&( self.focused(), self.app.selected, diff --git a/crates/tek_tui/src/tui_pool.rs b/crates/tek_tui/src/tui_pool.rs index 602dbc11..7c8f8e90 100644 --- a/crates/tek_tui/src/tui_pool.rs +++ b/crates/tek_tui/src/tui_pool.rs @@ -133,7 +133,7 @@ impl Content for PhrasePoolView { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub enum PhrasePoolViewCommand { Select(usize), Edit(PhrasePoolCommand), diff --git a/crates/tek_tui/src/tui_pool_length.rs b/crates/tek_tui/src/tui_pool_length.rs index 0aaff85e..969017ea 100644 --- a/crates/tek_tui/src/tui_pool_length.rs +++ b/crates/tek_tui/src/tui_pool_length.rs @@ -1,6 +1,6 @@ use crate::*; -#[derive(Clone, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum PhraseLengthCommand { Begin, Next, diff --git a/crates/tek_tui/src/tui_pool_rename.rs b/crates/tek_tui/src/tui_pool_rename.rs index 33902b21..4711eb1d 100644 --- a/crates/tek_tui/src/tui_pool_rename.rs +++ b/crates/tek_tui/src/tui_pool_rename.rs @@ -1,6 +1,6 @@ use crate::*; -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum PhraseRenameCommand { Begin, Set(String), diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 79984359..0f7f806c 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -7,6 +7,45 @@ pub type SequencerApp = AppView< SequencerStatusBar, >; +#[derive(Clone, PartialEq)] +pub enum SequencerViewCommand { + Transport(TransportCommand), + Phrases(PhrasePoolViewCommand), + Editor(PhraseEditorCommand), +} + +/// Root view for standalone `tek_sequencer`. +pub struct SequencerView { + pub model: SequencerModel, + /// Displays the JACK transport. + pub transport: TransportView, + /// Displays the phrase pool + pub phrases: PhrasePoolView, + /// Displays the phrase editor + pub editor: PhraseEditor, + /// Width of phrase pool + pub split: u16, +} + +/// Sections in the sequencer app that may be focused +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum SequencerFocus { + /// The transport (toolbar) is focused + Transport, + /// The phrase list (pool) is focused + PhrasePool, + /// The phrase editor (sequencer) is focused + PhraseEditor, +} + +/// Status bar for sequencer app +#[derive(Copy, Clone)] +pub enum SequencerStatusBar { + Transport, + PhrasePool, + PhraseEditor, +} + impl TryFrom<&Arc>> for SequencerApp { type Error = Box; fn try_from (jack: &Arc>) -> Usually { @@ -28,54 +67,14 @@ impl From for SequencerView { fn from (model: SequencerModel) -> Self { Self { split: 20, - transport: TransportView::from(&model.transport), - phrases: PhrasePoolView::from(&model.phrases), + transport: TransportView::from(&model.transport()), + phrases: PhrasePoolView::from(&model.phrases()), editor: PhraseEditor::new(), model, } } } -/// Root level object for standalone `tek_sequencer`. -pub struct SequencerView { - pub model: SequencerModel, - /// Displays the JACK transport. - pub transport: TransportView, - /// Displays the phrase pool - pub phrases: PhrasePoolView, - /// Displays the phrase editor - pub editor: PhraseEditor, - /// Width of phrase pool - pub split: u16, -} - -/// Status bar for sequencer app -#[derive(Copy, Clone)] -pub enum SequencerStatusBar { - Transport, - PhrasePool, - PhraseEditor, -} - -#[derive(Clone, PartialEq)] -pub enum SequencerViewCommand { - Focus(FocusCommand), - Transport(TransportViewCommand), - Phrases(PhrasePoolViewCommand), - Editor(PhraseEditorCommand), -} - -/// Sections in the sequencer app that may be focused -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum SequencerFocus { - /// The transport (toolbar) is focused - Transport, - /// The phrase list (pool) is focused - PhrasePool, - /// The phrase editor (sequencer) is focused - PhraseEditor, -} - impl Content for SequencerView { type Engine = Tui; fn content (&self) -> impl Widget { @@ -113,14 +112,14 @@ impl Content for SequencerStatusBar { } } -impl Handle for SequencerView { +impl Handle for SequencerApp { fn handle (&mut self, i: &TuiInput) -> Perhaps { SequencerViewCommand::execute_with_state(self, i) } } -impl Command> for SequencerViewCommand { - fn execute (self, state: &mut SequencerView) -> Perhaps { +impl Command> for SequencerViewCommand { + fn execute (self, state: &mut SequencerApp) -> Perhaps { match self { Self::Focus(cmd) => delegate(cmd, Self::Focus, state), Self::Phrases(cmd) => delegate(cmd, Self::Phrases, &mut state.phrases), @@ -130,8 +129,9 @@ impl Command> for SequencerViewCommand { } } -impl InputToCommand> for SequencerViewCommand { - fn input_to_command (state: &SequencerView, input: &TuiInput) -> Option { +impl InputToCommand> for SequencerViewCommand { + fn input_to_command (state: &SequencerApp, input: &TuiInput) -> Option { + use AppViewFocus::*; use FocusCommand::*; match input.event() { key!(KeyCode::Tab) => Some(Self::Focus(Next)), @@ -143,16 +143,16 @@ impl InputToCommand> for SequencerViewCommand { key!(KeyCode::Left) => Some(Self::Focus(Left)), key!(KeyCode::Right) => Some(Self::Focus(Right)), _ => match state.focused() { - SequencerFocus::Transport => TransportViewCommand::input_to_command( - &state.transport, input - ).map(Self::Transport), - SequencerFocus::PhrasePool => PhrasePoolViewCommand::input_to_command( - &state.phrases, input - ).map(Self::Phrases), - SequencerFocus::PhraseEditor => PhraseEditorCommand::input_to_command( - &state.editor, input - ).map(Self::Editor), - + Content(SequencerFocus::Transport) => + TransportViewCommand::input_to_command(&state.transport, input) + .map(Self::Transport), + Content(SequencerFocus::PhrasePool) => + PhrasePoolViewCommand::input_to_command(&state.phrases, input) + .map(Self::Phrases), + Content(SequencerFocus::PhraseEditor) => + PhraseEditorCommand::input_to_command(&state.editor, input) + .map(Self::Editor), + _ => None, } } } diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index f1d50727..2476b6a2 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -1,12 +1,6 @@ use crate::*; -pub type TransportApp = AppView< - E, - TransportView, - TransportViewCommand, - TransportStatusBar ->; - +/// Create app state from JACK handle. impl TryFrom<&Arc>> for TransportApp { type Error = Box; fn try_from (jack: &Arc>) -> Usually { @@ -19,14 +13,98 @@ impl TryFrom<&Arc>> for TransportApp { } } -/// Stores and displays time-related info. -#[derive(Debug)] -pub struct TransportView { - _engine: PhantomData, - pub model: TransportModel, - pub focus: TransportViewFocus, - pub focused: bool, - pub size: Measure, +/// Root type of application. +pub type TransportApp = AppView< + E, + TransportView, + TransportAppCommand, + TransportStatusBar +>; + +/// Handle input. +impl Handle for TransportApp { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + TransportAppCommand::execute_with_state(self, from) + } +} + +pub type TransportAppCommand = AppViewCommand; + +impl InputToCommand> for TransportAppCommand { + fn input_to_command (app: &TransportApp, input: &TuiInput) -> Option { + use TransportViewFocus as Focus; + use FocusCommand as FocusCmd; + use TransportCommand as Cmd; + let clock = app.app.model.clock(); + Some(match input.event() { + + key!(KeyCode::Left) => Self::Focus(FocusCmd::Prev), + key!(KeyCode::Right) => Self::Focus(FocusCmd::Next), + + key!(KeyCode::Char('.')) => Self::App(match app.focused() { + AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() + 1.0), + AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64), + AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), + AppViewFocus::Content(Focus::PlayPause) => {todo!()}, + AppViewFocus::Content(Focus::Clock) => {todo!()}, + _ => {todo!()} + }), + key!(KeyCode::Char(',')) => Self::App(match app.focused() { + AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() - 1.0), + AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64), + AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), + AppViewFocus::Content(Focus::PlayPause) => {todo!()}, + AppViewFocus::Content(Focus::Clock) => {todo!()} + _ => {todo!()} + }), + key!(KeyCode::Char('>')) => Self::App(match app.focused() { + AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() + 0.001), + AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64), + AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), + AppViewFocus::Content(Focus::PlayPause) => {todo!()}, + AppViewFocus::Content(Focus::Clock) => {todo!()} + _ => {todo!()} + }), + key!(KeyCode::Char('<')) => Self::App(match app.focused() { + AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() - 0.001), + AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64), + AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), + AppViewFocus::Content(Focus::PlayPause) => {todo!()}, + AppViewFocus::Content(Focus::Clock) => {todo!()} + _ => {todo!()} + }), + + _ => return None + }) + } +} + +impl Command> for TransportAppCommand { + fn execute (self, state: &mut TransportApp) -> Perhaps { + let clock = state.app.model.clock(); + Ok(Some(match self { + Self::Focus(command) => Self::Focus({ + use FocusCommand::*; + match command { + Next => { todo!() }, + Prev => { todo!() }, + _ => { todo!() } + } + }), + Self::App(command) => Self::App({ + use TransportCommand::*; + match command { + SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)), + SetQuant(quant) => SetQuant(clock.quant.set(quant)), + SetSync(sync) => SetSync(clock.sync.set(sync)), + _ => { + todo!() + } + } + }), + _ => todo!() + })) + } } impl From for TransportView { @@ -41,6 +119,58 @@ impl From for TransportView { } } +/// Stores and displays time-related info. +#[derive(Debug)] +pub struct TransportView { + _engine: PhantomData, + pub model: TransportModel, + pub focus: TransportViewFocus, + pub focused: bool, + pub size: Measure, +} + +/// Which item of the transport toolbar is focused +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TransportViewFocus { + Bpm, + Sync, + PlayPause, + Clock, + Quant, +} + +impl TransportViewFocus { + 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, + } + } + pub fn wrap <'a, W: Widget> ( + self, parent_focus: bool, focus: Self, widget: &'a W + ) -> impl Widget + 'a { + let focused = parent_focus && focus == self; + let corners = focused.then_some(CORNERS); + let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50))); + lay!(corners, highlight, *widget) + } +} + +#[derive(Copy, Clone)] +pub struct TransportStatusBar; + impl Content for TransportView { type Engine = Tui; fn content (&self) -> impl Widget { @@ -84,9 +214,6 @@ impl Audio for TransportView { } } -#[derive(Copy, Clone)] -pub struct TransportStatusBar; - impl StatusBar for TransportStatusBar { type State = (); fn hotkey_fg () -> Color { @@ -105,129 +232,6 @@ impl Content for TransportStatusBar { } } -#[derive(Copy, Clone, PartialEq)] -pub enum TransportViewCommand { - Focus(FocusCommand), - Transport(TransportCommand), -} - -impl Handle for TransportView { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - TransportViewCommand::execute_with_state(self, from) - } -} - -impl InputToCommand> for TransportViewCommand { - fn input_to_command (view: &TransportView, input: &TuiInput) -> Option { - use TransportViewFocus as Focus; - use FocusCommand as FocusCmd; - use TransportCommand as Cmd; - let clock = view.model.clock(); - Some(match input.event() { - - key!(KeyCode::Left) => Self::Focus(FocusCmd::Prev), - key!(KeyCode::Right) => Self::Focus(FocusCmd::Next), - - key!(KeyCode::Char('.')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() + 1.0), - Focus::Quant => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), - Focus::PlayPause => {todo!()}, - Focus::Clock => {todo!()} - }), - key!(KeyCode::Char(',')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() - 1.0), - Focus::Quant => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), - Focus::PlayPause => {todo!()}, - Focus::Clock => {todo!()} - }), - key!(KeyCode::Char('>')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() + 0.001), - Focus::Quant => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), - Focus::PlayPause => {todo!()}, - Focus::Clock => {todo!()} - }), - key!(KeyCode::Char('<')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() - 0.001), - Focus::Quant => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), - Focus::PlayPause => {todo!()}, - Focus::Clock => {todo!()} - }), - - _ => return None - }) - } -} - -impl Command> for TransportViewCommand { - fn execute (self, view: &mut TransportView) -> Perhaps { - let clock = view.model.clock(); - Ok(Some(match self { - Self::Focus(command) => Self::Focus({ - use FocusCommand::*; - match command { - Next => { todo!() }, - Prev => { todo!() }, - _ => { todo!() } - } - }), - Self::Transport(command) => Self::Transport({ - use TransportCommand::*; - match command { - SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)), - SetQuant(quant) => SetQuant(clock.quant.set(quant)), - SetSync(sync) => SetSync(clock.sync.set(sync)), - _ => { - todo!() - } - } - }), - })) - } -} - -impl TransportViewFocus { - 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, - } - } - pub fn wrap <'a, W: Widget> ( - self, parent_focus: bool, focus: Self, widget: &'a W - ) -> impl Widget + 'a { - let focused = parent_focus && focus == self; - let corners = focused.then_some(CORNERS); - let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50))); - lay!(corners, highlight, *widget) - } -} - -/// Which item of the transport toolbar is focused -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum TransportViewFocus { - Bpm, - Sync, - PlayPause, - Clock, - Quant, -} - impl FocusGrid for TransportApp { type Item = AppViewFocus; fn cursor (&self) -> (usize, usize) {