diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index e0feca43..ff689c59 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -196,9 +196,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option Cmd::Editor(PhraseCommand::Show(Some( - state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone() - ))), + key_pat!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some(state.phrases.phrase().clone()))), // WSAD navigation, Q launches, E edits, PgUp/Down pool, Arrows editor _ => match state.focused() { ArrangerFocus::Transport(_) => { @@ -327,46 +325,39 @@ has_clock!(|self:ArrangerTrack|self.player.clock()); has_phrases!(|self:ArrangerTui|self.phrases.phrases); has_editor!(|self:ArrangerTui|self.editor); has_player!(|self:ArrangerTrack|self.player); -// Layout for standalone arranger app. render!(|self: ArrangerTui|{ let arranger_focused = self.arranger_focused(); - let border = Lozenge(Style::default().bg(TuiTheme::border_bg()).fg(TuiTheme::border_fg(arranger_focused))); let transport_focused = if let ArrangerFocus::Transport(_) = self.focus.inner() { true } else { false }; - col!([ - TransportView::from((self, None, transport_focused)), - col!([ - Tui::fixed_y(self.splits[0], lay!([ - border.wrap(Tui::grow_y(1, Layers::new(move |add|{ - match self.mode { - ArrangerMode::Horizontal => - add(&arranger_content_horizontal(self))?, - ArrangerMode::Vertical(factor) => - add(&arranger_content_vertical(self, factor))? - }; - add(&self.size) - }))), - self.size, - Tui::push_x(1, Tui::fg( - TuiTheme::title_fg(arranger_focused), - format!("[{}] Arranger", if self.entered { - "■" - } else { - " " - }) - )) - ])), - Split::right( - false, - self.splits[1], - PhraseListView::from(self), - &self.editor, - ) - ]) - ]) + let transport = TransportView::from((self, None, transport_focused)); + let with_transport = move|x|col!([transport, x]); + let border = Lozenge(Style::default() + .bg(TuiTheme::border_bg()) + .fg(TuiTheme::border_fg(arranger_focused))); + let arranger = move||border.wrap(Tui::grow_y(1, lay!(|add|{ + match self.mode { + ArrangerMode::Horizontal => add(&arranger_content_horizontal(self))?, + ArrangerMode::Vertical(factor) => add(&arranger_content_vertical(self, factor))? + }; + add(&self.size) + }))); + with_transport(col!([ + Tui::fixed_y(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), + ])) }); audio!(|self: ArrangerTui, client, scope|{ // Start profiling cycle diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 49530e8c..f0625a0a 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -88,13 +88,9 @@ impl InputToCommand for SequencerCommand { // Toggle visibility of phrase pool column key_pat!(Tab) => ShowPool(!state.show_pool), // Enqueue currently edited phrase - key_pat!(Char('q')) => Enqueue(Some( - state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone() - )), + key_pat!(Char('q')) => Enqueue(Some(state.phrases.phrase().clone())), // 0: Enqueue phrase 0 (stop all) - key_pat!(Char('0')) => Enqueue(Some( - state.phrases.phrases[0].clone() - )), + key_pat!(Char('0')) => Enqueue(Some(state.phrases.phrases()[0].clone())), // E: Toggle between editing currently playing or other phrase key_pat!(Char('e')) => if let Some((_, Some(playing_phrase))) = state.player.play_phrase() { let editing_phrase = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); @@ -153,7 +149,7 @@ render!(|self: SequencerTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.show_pool { phrase_w } else { 0 }; - let pool = Tui::fill_y(Tui::at_e(PhraseListView::from(self))); + let pool = Tui::fill_y(Tui::at_e(PhraseListView(&self.phrases))); let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); let with_status = |x|Tui::split_n(false, 2, SequencerStatusBar::from(self), x); let with_bar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); @@ -168,6 +164,10 @@ render!(|self: SequencerTui|{ with_size(with_status(col!([ toolbar, editor, ]))) }); +has_clock!(|self:SequencerTui|&self.clock); +has_phrases!(|self:SequencerTui|self.phrases.phrases); +has_editor!(|self:SequencerTui|self.editor); + pub struct PhraseSelector { pub(crate) title: &'static str, pub(crate) name: String, @@ -235,10 +235,6 @@ impl PhraseSelector { } } -has_clock!(|self:SequencerTui|&self.clock); -has_phrases!(|self:SequencerTui|self.phrases.phrases); -has_editor!(|self:SequencerTui|self.editor); - impl HasPhraseList for SequencerTui { fn phrases_focused (&self) -> bool { true diff --git a/crates/tek/src/tui/phrase_list.rs b/crates/tek/src/tui/phrase_list.rs index f6e7fe16..2ee0a514 100644 --- a/crates/tek/src/tui/phrase_list.rs +++ b/crates/tek/src/tui/phrase_list.rs @@ -5,17 +5,20 @@ use crate::{ tui::file_browser::FileBrowserCommand as Browse, api::PhrasePoolCommand as Pool, }; +use Ordering::Relaxed; #[derive(Debug)] pub struct PhraseListModel { /// Collection of phrases pub(crate) phrases: Vec>>, /// Selected phrase - pub(crate) phrase: AtomicUsize, - /// Scroll offset - pub scroll: usize, + pub(crate) phrase: AtomicUsize, /// Mode switch - pub(crate) mode: Option, + pub(crate) mode: Option, + /// Rendered size + size: Measure, + /// Scroll offset + scroll: usize, } /// Modes for phrase pool @@ -167,6 +170,7 @@ impl Default for PhraseListModel { phrase: 0.into(), scroll: 0, mode: None, + size: Measure::new(), } } } @@ -175,7 +179,7 @@ impl From<&Arc>> for PhraseListModel { fn from (phrase: &Arc>) -> Self { let mut model = Self::default(); model.phrases.push(phrase.clone()); - model.phrase.store(1, Ordering::Relaxed); + model.phrase.store(1, Relaxed); model } } @@ -185,10 +189,10 @@ has_phrase!(|self:PhraseListModel|self.phrases[self.phrase_index()]); impl PhraseListModel { pub(crate) fn phrase_index (&self) -> usize { - self.phrase.load(Ordering::Relaxed) + self.phrase.load(Relaxed) } pub(crate) fn set_phrase_index (&self, value: usize) { - self.phrase.store(value, Ordering::Relaxed); + self.phrase.store(value, Relaxed); } pub(crate) fn phrases_mode (&self) -> &Option { &self.mode @@ -205,35 +209,15 @@ pub trait HasPhraseList: HasPhrases { fn phrase_index (&self) -> usize; } -pub struct PhraseListView<'a> { - pub(crate) title: &'static str, - pub(crate) focused: bool, - pub(crate) entered: bool, - pub(crate) phrases: &'a Vec>>, - pub(crate) index: usize, - pub(crate) mode: &'a Option -} - -impl<'a, T: HasPhraseList> From<&'a T> for PhraseListView<'a> { - fn from (state: &'a T) -> Self { - Self { - title: "Pool:", - focused: state.phrases_focused(), - entered: state.phrases_entered(), - phrases: state.phrases(), - index: state.phrase_index(), - mode: state.phrases_mode(), - } - } -} +pub struct PhraseListView<'a>(pub(crate) &'a PhraseListModel); // TODO: Display phrases always in order of appearance render!(|self: PhraseListView<'a>|{ - let Self { title, focused, entered, phrases, index, mode } = self; - let bg = TuiTheme::g(32); - let title_color = TuiTheme::ti1(); - let upper_left = format!("{title}"); - let upper_right = format!("({})", phrases.len()); + let PhraseListModel { phrases, mode, .. } = self.0; + let bg = TuiTheme::g(32); + let title_color = TuiTheme::ti1(); + let upper_left = "Pool:"; + let upper_right = format!("({})", phrases.len()); Tui::bg(bg, lay!(move|add|{ //add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; add(&Tui::inset_xy(0, 1, Tui::fill_xy(col!(move|add|match mode { @@ -244,7 +228,7 @@ render!(|self: PhraseListView<'a>|{ let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); let mut length = PhraseLength::new(length, None); if let Some(PhraseListMode::Length(phrase, new_length, focus)) = mode { - if *focused && i == *phrase { + if i == *phrase { length.pulses = *new_length; length.focus = Some(*focus); } @@ -257,14 +241,14 @@ render!(|self: PhraseListView<'a>|{ Tui::bold(true, { let mut row2 = format!(" {name}"); if let Some(PhraseListMode::Rename(phrase, _)) = mode { - if *focused && i == *phrase { + if i == *phrase { row2 = format!("{row2}▄"); } }; row2 }), ]))))?; - if *entered && i == *index { + if i == self.0.phrase_index() { add(&CORNERS)?; } Ok(()) @@ -272,6 +256,7 @@ render!(|self: PhraseListView<'a>|{ }) }))))?; add(&Tui::fill_x(Tui::at_nw(Tui::push_x(1, Tui::fg(title_color, upper_left.to_string())))))?; - add(&Tui::fill_x(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string()))))) + add(&Tui::fill_x(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))?; + add(&self.0.size) })) });