From 623fce73a40e7f2fd0415ce9be9eea78f6e3833b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 18 Dec 2024 13:46:07 +0100 Subject: [PATCH] remove HasPhraseList; 8470LOC --- crates/tek/src/tui/app_arranger.rs | 56 +++----------- crates/tek/src/tui/app_groovebox.rs | 8 +- crates/tek/src/tui/app_sampler.rs | 4 +- crates/tek/src/tui/app_sequencer.rs | 14 ---- crates/tek/src/tui/file_browser.rs | 116 +++------------------------- crates/tek/src/tui/phrase_length.rs | 78 ++++++++++--------- crates/tek/src/tui/phrase_list.rs | 90 +++++++++++++++------ 7 files changed, 133 insertions(+), 233 deletions(-) diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index 287b659c..06e725ba 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -1,7 +1,6 @@ use crate::*; use crate::api::ArrangerTrackCommand; use crate::api::ArrangerSceneCommand; - /// Root view for standalone `tek_arranger` pub struct ArrangerTui { pub jack: Arc>, @@ -55,10 +54,8 @@ has_editor!(|self:ArrangerTui|self.editor); handle!(|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input)); render!(|self: ArrangerTui|{ let arranger_focused = self.arranger_focused(); - let transport_focused = if let ArrangerFocus::Transport(_) = self.focus.inner() { - true - } else { - false + let transport_focused = match self.focus.inner() { + ArrangerFocus::Transport(_) => true, _ => false }; let transport = TransportView::from((self, None, transport_focused)); let with_transport = move|x|col!([transport, x]); @@ -72,20 +69,8 @@ render!(|self: ArrangerTui|{ }; add(&self.size) }))); - with_transport(col!([ - Fixed::h(self.splits[0], lay!([ - arranger(), - Tui::push_x(1, Tui::fg( - TuiTheme::title_fg(arranger_focused), - format!("[{}] Arranger", if self.entered { - "■" - } else { - " " - }) - )) - ])), - Split::right(false, self.splits[1], PhraseListView(&self.phrases), &self.editor), - ])) + let with_pool = |x|Split::right(false, self.splits[1], PhraseListView(&self.phrases), x); + with_transport(col!([Fixed::h(self.splits[0], arranger()), with_pool(&self.editor),])) }); audio!(|self: ArrangerTui, client, scope|{ // Start profiling cycle @@ -124,23 +109,7 @@ audio!(|self: ArrangerTui, client, scope|{ self.perf.update(t0, scope); return Control::Continue }); - -impl HasPhraseList for ArrangerTui { - fn phrases_focused (&self) -> bool { - self.focused() == ArrangerFocus::Phrases - } - fn phrases_entered (&self) -> bool { - self.entered() && self.phrases_focused() - } - fn phrases_mode (&self) -> &Option { - &self.phrases.mode - } - fn phrase_index (&self) -> usize { - self.phrases.phrase.load(Ordering::Relaxed) - } -} -#[derive(Clone, Debug)] -pub enum ArrangerCommand { +#[derive(Clone, Debug)] pub enum ArrangerCommand { Focus(FocusCommand), Undo, Redo, @@ -384,8 +353,6 @@ impl TransportControl for ArrangerTui { } } } -has_clock!(|self:ArrangerTrack|self.player.clock()); -has_player!(|self:ArrangerTrack|self.player); /// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -809,8 +776,7 @@ impl HasScenes for ArrangerTui { } } -#[derive(Default, Debug, Clone)] -pub struct ArrangerScene { +#[derive(Default, Debug, Clone)] pub struct ArrangerScene { /// Name of scene pub(crate) name: Arc>, /// Clips in scene, one per track @@ -818,7 +784,6 @@ pub struct ArrangerScene { /// Identifying color of scene pub(crate) color: ItemColor, } - impl ArrangerSceneApi for ArrangerScene { fn name (&self) -> &Arc> { &self.name @@ -830,7 +795,6 @@ impl ArrangerSceneApi for ArrangerScene { self.color } } - impl HasTracks for ArrangerTui { fn tracks (&self) -> &Vec { &self.tracks @@ -839,7 +803,6 @@ impl HasTracks for ArrangerTui { &mut self.tracks } } - impl ArrangerTracksApi for ArrangerTui { fn track_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerTrack> @@ -863,8 +826,7 @@ impl ArrangerTracksApi for ArrangerTui { } } -#[derive(Debug)] -pub struct ArrangerTrack { +#[derive(Debug)] pub struct ArrangerTrack { /// Name of track pub(crate) name: Arc>, /// Preferred width of track column @@ -874,7 +836,8 @@ pub struct ArrangerTrack { /// MIDI player state pub(crate) player: PhrasePlayerModel, } - +has_clock!(|self:ArrangerTrack|self.player.clock()); +has_player!(|self:ArrangerTrack|self.player); impl ArrangerTrackApi for ArrangerTrack { /// Name of track fn name (&self) -> &Arc> { @@ -893,7 +856,6 @@ impl ArrangerTrackApi for ArrangerTrack { self.color } } - #[derive(PartialEq, Clone, Copy, Debug)] /// Represents the current user selection in the arranger pub enum ArrangerSelection { diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index fb9cd270..b07691ef 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -33,10 +33,10 @@ pub enum GrooveboxCommand { handle!(|self:GrooveboxTui,input|GrooveboxCommand::execute_with_state(self, input)); input_to_command!(GrooveboxCommand: |state:GrooveboxTui,input|match input.event() { _ => match state.focus { - GrooveboxFocus::Sequencer => - GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?), - GrooveboxFocus::Sampler => - GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?), + GrooveboxFocus::Sequencer => GrooveboxCommand::Sequencer( + SequencerCommand::input_to_command(&state.sequencer, input)?), + GrooveboxFocus::Sampler => GrooveboxCommand::Sampler( + SamplerCommand::input_to_command(&state.sampler, input)?), } }); command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 9306db47..8753739e 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -54,7 +54,9 @@ pub enum SamplerCommand { NoteOff(u7) } input_to_command!(SamplerCommand:|state:SamplerTui,input|match state.mode { - Some(SamplerMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?), + Some(SamplerMode::Import(..)) => Self::Import( + FileBrowserCommand::input_to_command(state, input)? + ), _ => match input.event() { // load sample key_pat!(Char('l')) => Self::Import(FileBrowserCommand::Begin), diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index aefd456a..cf2a2fdb 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -76,20 +76,6 @@ audio!(|self:SequencerTui, client, scope|{ self.perf.update(t0, scope); Control::Continue }); -impl HasPhraseList for SequencerTui { - fn phrases_focused (&self) -> bool { - true - } - fn phrases_entered (&self) -> bool { - true - } - fn phrases_mode (&self) -> &Option { - &self.phrases.mode - } - fn phrase_index (&self) -> usize { - self.phrases.phrase.load(Ordering::Relaxed) - } -} has_size!(|self:SequencerTui|&self.size); has_clock!(|self:SequencerTui|&self.clock); has_phrases!(|self:SequencerTui|self.phrases.phrases); diff --git a/crates/tek/src/tui/file_browser.rs b/crates/tek/src/tui/file_browser.rs index 9d265a70..2cd49187 100644 --- a/crates/tek/src/tui/file_browser.rs +++ b/crates/tek/src/tui/file_browser.rs @@ -2,7 +2,6 @@ use crate::*; use KeyCode::{Up, Down, Right, Left, Enter, Esc, Char, Backspace}; use FileBrowserCommand::*; use super::phrase_list::PhraseListMode::{Import, Export}; - /// Browses for phrase to import/export #[derive(Debug, Clone)] pub struct FileBrowser { @@ -14,7 +13,16 @@ pub struct FileBrowser { pub scroll: usize, pub size: Measure } - +/// Commands supported by [FileBrowser] +#[derive(Debug, Clone, PartialEq)] +pub enum FileBrowserCommand { + Begin, + Cancel, + Confirm, + Select(usize), + Chdir(PathBuf), + Filter(String), +} render!(|self: FileBrowser|{ Stack::down(|add|{ let mut i = 0; @@ -34,7 +42,6 @@ render!(|self: FileBrowser|{ Ok(()) }) }); - impl FileBrowser { pub fn new (cwd: Option) -> Usually { let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; @@ -83,106 +90,3 @@ impl FileBrowser { Self::new(Some(self.path())) } } - -/// Commands supported by [FileBrowser] -#[derive(Debug, Clone, PartialEq)] -pub enum FileBrowserCommand { - Begin, - Cancel, - Confirm, - Select(usize), - Chdir(PathBuf), - Filter(String), -} - -command!(|self: FileBrowserCommand, state: PhraseListModel|{ - let mode = state.phrases_mode_mut(); - match mode { - Some(Import(index, ref mut browser)) => match self { - Cancel => { - *mode = None; - }, - Chdir(cwd) => { - *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); - }, - Select(index) => { - browser.index = index; - }, - Confirm => { - if browser.is_file() { - let index = *index; - let path = browser.path(); - *mode = None; - PhrasePoolCommand::Import(index, path).execute(state)?; - } else if browser.is_dir() { - *mode = Some(Import(*index, browser.chdir()?)); - } - }, - _ => todo!(), - }, - Some(PhraseListMode::Export(index, ref mut browser)) => match self { - Cancel => { - *mode = None; - }, - Chdir(cwd) => { - *mode = Some(PhraseListMode::Export(*index, FileBrowser::new(Some(cwd))?)); - }, - Select(index) => { - browser.index = index; - }, - _ => unreachable!() - }, - _ => unreachable!(), - }; - None -}); - -input_to_command!(FileBrowserCommand:|state:PhraseListModel,from|{ - if let Some(PhraseListMode::Import(_index, browser)) = state.phrases_mode() { - match from.event() { - key_pat!(Up) => Select( - browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1)) - ), - key_pat!(Down) => Select( - browser.index.saturating_add(1) % browser.len() - ), - key_pat!(Right) => Chdir(browser.cwd.clone()), - key_pat!(Left) => Chdir(browser.cwd.clone()), - key_pat!(Enter) => Confirm, - key_pat!(Char(_)) => { todo!() }, - key_pat!(Backspace) => { todo!() }, - key_pat!(Esc) => Self::Cancel, - _ => return None - } - } else if let Some(PhraseListMode::Export(_index, browser)) = state.phrases_mode() { - match from.event() { - key_pat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())), - key_pat!(Down) => Select(browser.index.saturating_add(1) % browser.len()), - key_pat!(Right) => Chdir(browser.cwd.clone()), - key_pat!(Left) => Chdir(browser.cwd.clone()), - key_pat!(Enter) => Confirm, - key_pat!(Char(_)) => { todo!() }, - key_pat!(Backspace) => { todo!() }, - key_pat!(Esc) => Self::Cancel, - _ => return None - } - } else { - unreachable!() - } -}); - -input_to_command!(PhraseLengthCommand:|state:PhraseListModel,from|{ - if let Some(PhraseListMode::Length(_, length, _)) = state.phrases_mode() { - match from.event() { - key_pat!(Up) => Self::Inc, - key_pat!(Down) => Self::Dec, - key_pat!(Right) => Self::Next, - key_pat!(Left) => Self::Prev, - key_pat!(Enter) => Self::Set(*length), - key_pat!(Esc) => Self::Cancel, - _ => return None - } - } else { - unreachable!() - } -}); diff --git a/crates/tek/src/tui/phrase_length.rs b/crates/tek/src/tui/phrase_length.rs index aa862aa1..e6d0129f 100644 --- a/crates/tek/src/tui/phrase_length.rs +++ b/crates/tek/src/tui/phrase_length.rs @@ -2,7 +2,6 @@ use crate::*; use super::phrase_list::{PhraseListModel, PhraseListMode}; use PhraseLengthFocus::*; use PhraseLengthCommand::*; - /// Displays and edits phrase length. #[derive(Clone)] pub struct PhraseLength { @@ -15,7 +14,6 @@ pub struct PhraseLength { /// Selected subdivision pub focus: Option, } - impl PhraseLength { pub fn new (pulses: usize, focus: Option) -> Self { Self { ppq: PPQ, bpb: 4, pulses, focus } @@ -39,7 +37,6 @@ impl PhraseLength { format!("{:>02}", self.ticks()) } } - /// Focused field of `PhraseLength` #[derive(Copy, Clone, Debug)] pub enum PhraseLengthFocus { @@ -50,7 +47,6 @@ pub enum PhraseLengthFocus { /// Editing the number of ticks Tick, } - impl PhraseLengthFocus { pub fn next (&mut self) { *self = match self { @@ -67,7 +63,6 @@ impl PhraseLengthFocus { } } } - render!(|self: PhraseLength|{ let bars = ||self.bars_string(); let beats = ||self.beats_string(); @@ -83,7 +78,6 @@ render!(|self: PhraseLength|{ add(&row!([" ", bars(), ".", beats(), "[", ticks()])), }) }); - #[derive(Copy, Clone, Debug, PartialEq)] pub enum PhraseLengthCommand { Begin, @@ -94,36 +88,48 @@ pub enum PhraseLengthCommand { Inc, Dec, } - -impl Command for PhraseLengthCommand { - fn execute (self, state: &mut PhraseListModel) -> Perhaps { - match state.phrases_mode_mut().clone() { - Some(PhraseListMode::Length(phrase, ref mut length, ref mut focus)) => match self { - Cancel => { *state.phrases_mode_mut() = None; }, - Prev => { focus.prev() }, - Next => { focus.next() }, - Inc => match focus { - Bar => { *length += 4 * PPQ }, - Beat => { *length += PPQ }, - Tick => { *length += 1 }, - }, - Dec => match focus { - Bar => { *length = length.saturating_sub(4 * PPQ) }, - Beat => { *length = length.saturating_sub(PPQ) }, - Tick => { *length = length.saturating_sub(1) }, - }, - Set(length) => { - let mut phrase = state.phrases()[phrase].write().unwrap(); - let old_length = phrase.length; - phrase.length = length; - std::mem::drop(phrase); - *state.phrases_mode_mut() = None; - return Ok(Some(Self::Set(old_length))) - }, - _ => unreachable!() +command!(|self:PhraseLengthCommand,state:PhraseListModel|{ + match state.phrases_mode_mut().clone() { + Some(PhraseListMode::Length(phrase, ref mut length, ref mut focus)) => match self { + Cancel => { *state.phrases_mode_mut() = None; }, + Prev => { focus.prev() }, + Next => { focus.next() }, + Inc => match focus { + Bar => { *length += 4 * PPQ }, + Beat => { *length += PPQ }, + Tick => { *length += 1 }, + }, + Dec => match focus { + Bar => { *length = length.saturating_sub(4 * PPQ) }, + Beat => { *length = length.saturating_sub(PPQ) }, + Tick => { *length = length.saturating_sub(1) }, + }, + Set(length) => { + let mut phrase = state.phrases()[phrase].write().unwrap(); + let old_length = phrase.length; + phrase.length = length; + std::mem::drop(phrase); + *state.phrases_mode_mut() = None; + return Ok(Some(Self::Set(old_length))) }, _ => unreachable!() - }; - Ok(None) + }, + _ => unreachable!() + }; + None +}); +input_to_command!(PhraseLengthCommand:|state:PhraseListModel,from|{ + if let Some(PhraseListMode::Length(_, length, _)) = state.phrases_mode() { + match from.event() { + key_pat!(Up) => Self::Inc, + key_pat!(Down) => Self::Dec, + key_pat!(Right) => Self::Next, + key_pat!(Left) => Self::Prev, + key_pat!(Enter) => Self::Set(*length), + key_pat!(Esc) => Self::Cancel, + _ => return None + } + } else { + unreachable!() } -} +}); diff --git a/crates/tek/src/tui/phrase_list.rs b/crates/tek/src/tui/phrase_list.rs index 8130336b..a7e38ad2 100644 --- a/crates/tek/src/tui/phrase_list.rs +++ b/crates/tek/src/tui/phrase_list.rs @@ -156,7 +156,6 @@ fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option return None }) } - impl Default for PhraseListModel { fn default () -> Self { Self { @@ -168,7 +167,6 @@ impl Default for PhraseListModel { } } } - impl From<&Arc>> for PhraseListModel { fn from (phrase: &Arc>) -> Self { let mut model = Self::default(); @@ -177,10 +175,8 @@ impl From<&Arc>> for PhraseListModel { model } } - has_phrases!(|self:PhraseListModel|self.phrases); has_phrase!(|self:PhraseListModel|self.phrases[self.phrase_index()]); - impl PhraseListModel { pub(crate) fn phrase_index (&self) -> usize { self.phrase.load(Relaxed) @@ -195,16 +191,7 @@ impl PhraseListModel { &mut self.mode } } - -pub trait HasPhraseList: HasPhrases { - fn phrases_focused (&self) -> bool; - fn phrases_entered (&self) -> bool; - fn phrases_mode (&self) -> &Option; - fn phrase_index (&self) -> usize; -} - pub struct PhraseListView<'a>(pub(crate) &'a PhraseListModel); - // TODO: Display phrases always in order of appearance render!(|self: PhraseListView<'a>|{ let PhraseListModel { phrases, mode, .. } = self.0; @@ -254,14 +241,12 @@ render!(|self: PhraseListView<'a>|{ add(&self.0.size) })) }); - pub struct PhraseSelector { pub(crate) title: &'static str, pub(crate) name: String, pub(crate) color: ItemPalette, pub(crate) time: String, } - // TODO: Display phrases always in order of appearance render!(|self: PhraseSelector|Fixed::wh(24, 1, row!([ Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)), @@ -270,7 +255,6 @@ render!(|self: PhraseSelector|Fixed::wh(24, 1, row!([ Tui::bg(self.color.dark.rgb, &self.time), ])), ]))); - impl PhraseSelector { // beats elapsed pub fn play_phrase (state: &T) -> Self { @@ -314,13 +298,69 @@ impl PhraseSelector { }; Self { title: " Next|", time, name, color, } } - pub fn edit_phrase (phrase: &Option>>) -> Self { - let (time, name, color) = if let Some(phrase) = phrase { - let phrase = phrase.read().unwrap(); - (format!("{}", phrase.length), phrase.name.clone(), phrase.color) - } else { - ("".to_string(), " ".to_string(), ItemPalette::from(TuiTheme::g(64))) - }; - Self { title: "Editing:", time, name, color } - } } +command!(|self: FileBrowserCommand, state: PhraseListModel|{ + use PhraseListMode::*; + use FileBrowserCommand::*; + let mode = &mut state.mode; + match mode { + Some(Import(index, ref mut browser)) => match self { + Cancel => { *mode = None; }, + Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); }, + Select(index) => { browser.index = index; }, + Confirm => if browser.is_file() { + let index = *index; + let path = browser.path(); + *mode = None; + PhrasePoolCommand::Import(index, path).execute(state)?; + } else if browser.is_dir() { + *mode = Some(Import(*index, browser.chdir()?)); + }, + _ => todo!(), + }, + Some(Export(index, ref mut browser)) => match self { + Cancel => { *mode = None; }, + Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); }, + Select(index) => { browser.index = index; }, + _ => unreachable!() + }, + _ => unreachable!(), + }; + None +}); +input_to_command!(FileBrowserCommand:|state:PhraseListModel,from|{ + use PhraseListMode::*; + use FileBrowserCommand::*; + use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char}; + if let Some(PhraseListMode::Import(_index, browser)) = &state.mode { + match from.event() { + key_pat!(Up) => Select(browser.index.overflowing_sub(1).0 + .min(browser.len().saturating_sub(1))), + key_pat!(Down) => Select(browser.index.saturating_add(1) + % browser.len()), + key_pat!(Right) => Chdir(browser.cwd.clone()), + key_pat!(Left) => Chdir(browser.cwd.clone()), + key_pat!(Enter) => Confirm, + key_pat!(Char(_)) => { todo!() }, + key_pat!(Backspace) => { todo!() }, + key_pat!(Esc) => Cancel, + _ => return None + } + } else if let Some(PhraseListMode::Export(_index, browser)) = &state.mode { + match from.event() { + key_pat!(Up) => Select(browser.index.overflowing_sub(1).0 + .min(browser.len())), + key_pat!(Down) => Select(browser.index.saturating_add(1) + % browser.len()), + key_pat!(Right) => Chdir(browser.cwd.clone()), + key_pat!(Left) => Chdir(browser.cwd.clone()), + key_pat!(Enter) => Confirm, + key_pat!(Char(_)) => { todo!() }, + key_pat!(Backspace) => { todo!() }, + key_pat!(Esc) => Cancel, + _ => return None + } + } else { + unreachable!() + } +});