use crate::*; impl Handle for ArrangerTui { fn handle (&mut self, i: &TuiInput) -> Perhaps { ArrangerCommand::execute_with_state(self, i) } } #[derive(Clone, Debug)] pub enum ArrangerCommand { Focus(FocusCommand), Undo, Redo, Clear, Color(ItemColor), Clock(ClockCommand), Scene(ArrangerSceneCommand), Track(ArrangerTrackCommand), Clip(ArrangerClipCommand), Select(ArrangerSelection), Zoom(usize), Phrases(PhrasesCommand), Editor(PhraseCommand), } impl Command for ArrangerCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { use ArrangerCommand::*; Ok(match self { Focus(cmd) => cmd.execute(state)?.map(Focus), Scene(cmd) => cmd.execute(state)?.map(Scene), Track(cmd) => cmd.execute(state)?.map(Track), Clip(cmd) => cmd.execute(state)?.map(Clip), Phrases(cmd) => cmd.execute(&mut state.phrases)?.map(Phrases), Editor(cmd) => cmd.execute(state)?.map(Editor), Clock(cmd) => cmd.execute(state)?.map(Clock), Zoom(zoom) => { todo!(); }, Select(selected) => { *state.selected_mut() = selected; None }, _ => { todo!() } }) } } impl Command for ArrangerSceneCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } impl Command for ArrangerTrackCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } impl Command for ArrangerClipCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } pub trait ArrangerControl: TransportControl { fn selected (&self) -> ArrangerSelection; fn selected_mut (&mut self) -> &mut ArrangerSelection; fn activate (&mut self) -> Usually<()>; fn selected_phrase (&self) -> Option>>; fn toggle_loop (&mut self); fn randomize_color (&mut self); } impl ArrangerControl for ArrangerTui { fn selected (&self) -> ArrangerSelection { self.selected } fn selected_mut (&mut self) -> &mut ArrangerSelection { &mut self.selected } fn activate (&mut self) -> Usually<()> { if let ArrangerSelection::Scene(s) = self.selected { for (t, track) in self.tracks.iter_mut().enumerate() { let phrase = self.scenes[s].clips[t].clone(); if track.player.play_phrase.is_some() || phrase.is_some() { track.enqueue_next(phrase.as_ref()); } } if self.is_stopped() { self.play_from(Some(0))?; } } else if let ArrangerSelection::Clip(t, s) = self.selected { let phrase = self.scenes()[s].clips[t].clone(); self.tracks_mut()[t].enqueue_next(phrase.as_ref()); }; Ok(()) } fn selected_phrase (&self) -> Option>> { self.selected_scene()?.clips.get(self.selected.track()?)?.clone() } fn toggle_loop (&mut self) { if let Some(phrase) = self.selected_phrase() { phrase.write().unwrap().toggle_loop() } } fn randomize_color (&mut self) { match self.selected { ArrangerSelection::Mix => { self.color = ItemColor::random_dark() }, ArrangerSelection::Track(t) => { self.tracks_mut()[t].color = ItemColor::random() }, ArrangerSelection::Scene(s) => { self.scenes_mut()[s].color = ItemColor::random() }, ArrangerSelection::Clip(t, s) => { if let Some(phrase) = &self.scenes_mut()[s].clips[t] { phrase.write().unwrap().color = ItemColorTriplet::random(); } } } } } 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_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option { use ArrangerCommand as Cmd; use KeyCode::Char; if !state.entered() { return None } Some(match input.event() { key!(Char('e')) => Cmd::Editor(PhraseCommand::Show(state.phrase_to_edit().clone())), _ => match state.focused() { ArrangerFocus::Transport(_) => { match TransportCommand::input_to_command(state, input)? { TransportCommand::Clock(command) => Cmd::Clock(command), _ => return None, } }, ArrangerFocus::PhraseEditor => { Cmd::Editor(PhraseCommand::input_to_command(state, input)?) }, ArrangerFocus::Phrases => { Cmd::Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?) }, ArrangerFocus::Arranger => { use ArrangerSelection::*; match input.event() { key!(Char('l')) => Cmd::Clip(ArrangerClipCommand::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(ArrangerSceneCommand::Add), key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add), _ => match state.selected() { Mix => to_arranger_mix_command(input)?, Track(t) => to_arranger_track_command(input, t)?, Scene(s) => to_arranger_scene_command(input, s)?, Clip(t, s) => to_arranger_clip_command(input, t, s)?, } } } } }) } fn to_arranger_mix_command (input: &TuiInput) -> Option { use KeyCode::{Char, Down, Right, Delete}; use ArrangerCommand as Cmd; use ArrangerSelection as Select; Some(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 }) } fn to_arranger_track_command (input: &TuiInput, t: usize) -> Option { use KeyCode::{Char, Down, Left, Right, Delete}; use ArrangerCommand as Cmd; use ArrangerSelection as Select; use ArrangerTrackCommand as Track; Some(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 }) } fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option { use KeyCode::{Char, Up, Down, Right, Enter, Delete}; use ArrangerCommand as Cmd; use ArrangerSelection as Select; use ArrangerSceneCommand as Scene; Some(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 }) } fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option { use KeyCode::{Char, Up, Down, Left, Right, Delete}; use ArrangerCommand as Cmd; use ArrangerSelection as Select; use ArrangerClipCommand as Clip; Some(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 }) }