diff --git a/crates/tek_tui/src/tui_model_phrase_list.rs b/crates/tek_tui/src/tui_model_phrase_list.rs index 46b31cc8..1434a338 100644 --- a/crates/tek_tui/src/tui_model_phrase_list.rs +++ b/crates/tek_tui/src/tui_model_phrase_list.rs @@ -35,3 +35,40 @@ pub enum PhrasesMode { /// Save phrase to disk Export(usize, FileBrowser), } + +pub trait HasPhraseList: HasPhrases { + fn phrases_focused (&self) -> bool; + fn phrases_entered (&self) -> bool; + fn phrases_mode (&self) -> &Option; + fn phrase_index (&self) -> usize; +} + +impl HasPhraseList for SequencerTui { + fn phrases_focused (&self) -> bool { + self.focused() == AppFocus::Content(SequencerFocus::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) + } +} + +impl HasPhraseList for ArrangerTui { + fn phrases_focused (&self) -> bool { + self.focused() == AppFocus::Content(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) + } +} diff --git a/crates/tek_tui/src/tui_view_arranger.rs b/crates/tek_tui/src/tui_view_arranger.rs index 5fc2cc6f..7dbdaa0a 100644 --- a/crates/tek_tui/src/tui_view_arranger.rs +++ b/crates/tek_tui/src/tui_view_arranger.rs @@ -35,7 +35,7 @@ impl Content for ArrangerTui { ), Split::right( self.splits[1], - PhraseListView(self), + PhraseListView::from(self), PhraseView::from(self), ) ) diff --git a/crates/tek_tui/src/tui_view_phrase_list.rs b/crates/tek_tui/src/tui_view_phrase_list.rs index 740e047f..1e5e59d0 100644 --- a/crates/tek_tui/src/tui_view_phrase_list.rs +++ b/crates/tek_tui/src/tui_view_phrase_list.rs @@ -1,24 +1,30 @@ use crate::*; -impl Widget for PhrasesModel { - type Engine = Tui; - fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - PhraseListView(self).layout(to) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - PhraseListView(self).render(to) +pub struct PhraseListView<'a> { + 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 { + focused: state.phrases_focused(), + entered: state.phrases_entered(), + phrases: state.phrases(), + index: state.phrase_index(), + mode: state.phrases_mode(), + } } } -pub struct PhraseListView<'a, T: PhraseListViewState>(pub &'a T); - // TODO: Display phrases always in order of appearance -impl<'a, T: PhraseListViewState> Content for PhraseListView<'a, T> { +impl<'a> Content for PhraseListView<'a> { type Engine = Tui; fn content (&self) -> impl Widget { - let focused = self.0.phrases_focused(); - let entered = self.0.phrases_entered(); - let mode = self.0.phrases_mode(); + let Self { focused, entered, phrases, index, mode } = self; let content = Stack::down(move|add|match mode { Some(PhrasesMode::Import(_, ref browser)) => { add(browser) @@ -27,14 +33,12 @@ impl<'a, T: PhraseListViewState> Content for PhraseListView<'a, T> { add(browser) }, _ => { - let phrases = self.0.phrases(); - let selected = self.0.phrase_index(); for (i, phrase) in phrases.iter().enumerate() { add(&Layers::new(|add|{ let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); let mut length = PhraseLength::new(length, None); if let Some(PhrasesMode::Length(phrase, new_length, focus)) = mode { - if focused && i == *phrase { + if *focused && i == *phrase { length.pulses = *new_length; length.focus = Some(*focus); } @@ -43,13 +47,13 @@ impl<'a, T: PhraseListViewState> Content for PhraseListView<'a, T> { let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x(); let mut row2 = format!(" {name}"); if let Some(PhrasesMode::Rename(phrase, _)) = mode { - if focused && i == *phrase { + if *focused && i == *phrase { row2 = format!("{row2}▄"); } }; let row2 = TuiStyle::bold(row2, true); add(&col!(row1, row2).fill_x().bg(color.base.rgb))?; - if focused && i == selected { + if *focused && i == *index { add(&CORNERS)?; } Ok(()) @@ -58,12 +62,12 @@ impl<'a, T: PhraseListViewState> Content for PhraseListView<'a, T> { Ok(()) } }); - let border_color = if focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)}; + let border_color = if *focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)}; let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color)); let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border); - let title_color = if focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; - let upper_left = format!("[{}] Phrases", if entered {"■"} else {" "}); - let upper_right = format!("({})", self.0.phrases().len()); + let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; + let upper_left = format!("[{}] Phrases", if *entered {"■"} else {" "}); + let upper_right = format!("({})", phrases.len()); lay!( content, TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(), @@ -71,46 +75,3 @@ impl<'a, T: PhraseListViewState> Content for PhraseListView<'a, T> { ) } } - -pub trait PhraseListViewState: Send + Sync { - fn phrases_focused (&self) -> bool; - fn phrases_entered (&self) -> bool; - fn phrases (&self) -> &Vec>>; - fn phrase_index (&self) -> usize; - fn phrases_mode (&self) -> &Option; -} - -macro_rules! impl_phrases_view_state { - ($Struct:ident $(:: $field:ident)* [$self1:ident: $focus:expr] [$self2:ident: $enter:expr]) => { - impl PhraseListViewState for $Struct { - fn phrases_focused (&$self1) -> bool { - $focus - } - fn phrases_entered (&$self2) -> bool { - $enter - } - fn phrases (&self) -> &Vec>> { - &self$(.$field)*.phrases - } - fn phrase_index (&self) -> usize { - self$(.$field)*.phrase.load(Ordering::Relaxed) - } - fn phrases_mode (&self) -> &Option { - &self$(.$field)*.mode - } - } - } -} - -impl_phrases_view_state!(PhrasesModel - [self: false] - [self: false] -); -impl_phrases_view_state!(SequencerTui::phrases - [self: self.focused() == AppFocus::Content(SequencerFocus::Phrases)] - [self: self.focused() == AppFocus::Content(SequencerFocus::Phrases)] -); -impl_phrases_view_state!(ArrangerTui::phrases - [self: self.focused() == AppFocus::Content(ArrangerFocus::Phrases)] - [self: self.focused() == AppFocus::Content(ArrangerFocus::Phrases)] -); diff --git a/crates/tek_tui/src/tui_view_sequencer.rs b/crates/tek_tui/src/tui_view_sequencer.rs index 2e86a998..5473217e 100644 --- a/crates/tek_tui/src/tui_view_sequencer.rs +++ b/crates/tek_tui/src/tui_view_sequencer.rs @@ -6,7 +6,7 @@ impl Content for SequencerTui { SequencerStatusBar::with(self, col!( TransportView::from(self), Split::right(20, - PhraseListView(self), + PhraseListView::from(self), PhraseView::from(self), ).min_y(20) ))