separate PhraseSelectorView from PhraseListView

This commit is contained in:
🪞👃🪞 2024-11-25 23:12:09 +01:00
parent bbf9ec0afd
commit 3273c85630
8 changed files with 258 additions and 185 deletions

View file

@ -40,6 +40,7 @@ submod! {
tui_view_phrase_editor
tui_view_phrase_length
tui_view_phrase_list
tui_view_phrase_selector
tui_view_sequencer
tui_view_transport
}

View file

@ -79,7 +79,9 @@ impl_focus!(SequencerTui SequencerFocus [
/// Status bar for sequencer app
#[derive(Clone)]
pub struct SequencerStatusBar {
pub(crate) cpu: Option<String>
pub(crate) cpu: Option<String>,
pub(crate) size: String,
pub(crate) sr: String,
}
impl StatusBar for SequencerStatusBar {
@ -95,7 +97,9 @@ impl StatusBar for SequencerStatusBar {
impl From<&SequencerTui> for SequencerStatusBar {
fn from (state: &SequencerTui) -> Self {
Self {
cpu: state.perf.percentage().map(|cpu|format!("{cpu:.03}%"))
cpu: state.perf.percentage().map(|cpu|format!(" {cpu:.01}% ")),
size: format!(" {}x{} ", state.size.w(), state.size.h()),
sr: format!(" {}Hz ", state.clock.current.timebase.sr.get())
}
}
}

View file

@ -110,7 +110,7 @@ impl<T: PhraseEditorControl + HasEnter> Command<T> for PhraseCommand {
}
}
pub trait PhraseEditorControl: HasFocus {
pub trait PhraseEditorControl {
fn edit_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>);
fn phrase_to_edit (&self) -> Option<Arc<RwLock<Phrase>>>;
fn phrase_editing (&self) -> &Option<Arc<RwLock<Phrase>>>;

View file

@ -1,6 +1,7 @@
use crate::*;
pub struct PhraseListView<'a> {
pub(crate) title: &'static str,
pub(crate) focused: bool,
pub(crate) entered: bool,
pub(crate) phrases: &'a Vec<Arc<RwLock<Phrase>>>,
@ -11,6 +12,7 @@ pub struct PhraseListView<'a> {
impl<'a, T: HasPhraseList> From<&'a T> for PhraseListView<'a> {
fn from (state: &'a T) -> Self {
Self {
title: "Phrases",
focused: state.phrases_focused(),
entered: state.phrases_entered(),
phrases: state.phrases(),
@ -24,7 +26,7 @@ impl<'a, T: HasPhraseList> From<&'a T> for PhraseListView<'a> {
impl<'a> Content for PhraseListView<'a> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { focused, entered, phrases, index, mode } = self;
let Self { title, focused, entered, phrases, index, mode } = self;
let content = Stack::down(move|add|match mode {
Some(PhrasesMode::Import(_, ref browser)) => {
add(browser)
@ -66,7 +68,7 @@ impl<'a> Content for PhraseListView<'a> {
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_left = format!("[{}] {title}", if *entered {""} else {" "});
let upper_right = format!("({})", phrases.len());
lay!(
content,

View file

@ -0,0 +1,56 @@
use crate::*;
pub struct PhraseSelector<'a> {
pub(crate) title: &'static str,
pub(crate) phrase: &'a Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
pub(crate) focused: bool,
pub(crate) entered: bool,
}
impl<'a> PhraseSelector<'a> {
pub fn play_phrase <T: HasPhrase> (state: &'a T) -> Self {
Self {
title: "Now:",
phrase: state.play_phrase(),
focused: false,
entered: false,
}
}
pub fn next_phrase <T: HasPhrase> (state: &'a T) -> Self {
Self {
title: "Next:",
phrase: state.next_phrase(),
focused: false,
entered: false,
}
}
}
// TODO: Display phrases always in order of appearance
impl<'a> Content for PhraseSelector<'a> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { title, phrase, focused, entered } = self;
let content = Layers::new(move|add|{
if let Some((instant, Some(phrase))) = phrase {
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let length = PhraseLength::new(length, None);
let length = length.align_e().fill_x();
let row1 = lay!(format!(" ").align_w().fill_x(), length).fill_x();
let row2 = format!(" {name}");
let row2 = TuiStyle::bold(row2, true);
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?;
}
Ok(())
});
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!("[{}] {title}", if *entered {""} else {" "});
lay!(
content,
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
)
}
}

View file

@ -3,19 +3,27 @@ use crate::*;
impl Content for SequencerTui {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
SequencerStatusBar::with(self, col!(
lay!(self.size, SequencerStatusBar::with(self, col!(
TransportView::from(self),
Split::right(20,
PhraseListView::from(self),
col_up!(
PhraseSelector::play_phrase(&self.player).fixed_y(4),
PhraseSelector::next_phrase(&self.player).fixed_y(4),
PhraseListView::from(self),
),
PhraseView::from(self),
).min_y(20)
))
)))
}
}
impl Content for SequencerStatusBar {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
widget(&self.cpu).fg(Color::Rgb(255,128,0)).align_se().fill_x()
row!(
widget(&self.cpu).fg(Color::Rgb(255,128,0)),
widget(&self.sr).fg(Color::Rgb(255,128,0)),
widget(&self.size).fg(Color::Rgb(255,128,0)),
).align_se().fill_x()
}
}