simplify PhraseListView and arranger layout

This commit is contained in:
🪞👃🪞 2024-12-15 20:07:52 +01:00
parent 9dd1d62de3
commit dcd6bc24a7
3 changed files with 56 additions and 84 deletions

View file

@ -196,9 +196,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
return None return None
} }
Some(match input.event() { Some(match input.event() {
key_pat!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some( key_pat!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some(state.phrases.phrase().clone()))),
state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()
))),
// WSAD navigation, Q launches, E edits, PgUp/Down pool, Arrows editor // WSAD navigation, Q launches, E edits, PgUp/Down pool, Arrows editor
_ => match state.focused() { _ => match state.focused() {
ArrangerFocus::Transport(_) => { ArrangerFocus::Transport(_) => {
@ -327,46 +325,39 @@ has_clock!(|self:ArrangerTrack|self.player.clock());
has_phrases!(|self:ArrangerTui|self.phrases.phrases); has_phrases!(|self:ArrangerTui|self.phrases.phrases);
has_editor!(|self:ArrangerTui|self.editor); has_editor!(|self:ArrangerTui|self.editor);
has_player!(|self:ArrangerTrack|self.player); has_player!(|self:ArrangerTrack|self.player);
// Layout for standalone arranger app.
render!(|self: ArrangerTui|{ render!(|self: ArrangerTui|{
let arranger_focused = self.arranger_focused(); 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() { let transport_focused = if let ArrangerFocus::Transport(_) = self.focus.inner() {
true true
} else { } else {
false false
}; };
col!([ let transport = TransportView::from((self, None, transport_focused));
TransportView::from((self, None, transport_focused)), let with_transport = move|x|col!([transport, x]);
col!([ let border = Lozenge(Style::default()
Tui::fixed_y(self.splits[0], lay!([ .bg(TuiTheme::border_bg())
border.wrap(Tui::grow_y(1, Layers::new(move |add|{ .fg(TuiTheme::border_fg(arranger_focused)));
match self.mode { let arranger = move||border.wrap(Tui::grow_y(1, lay!(|add|{
ArrangerMode::Horizontal => match self.mode {
add(&arranger_content_horizontal(self))?, ArrangerMode::Horizontal => add(&arranger_content_horizontal(self))?,
ArrangerMode::Vertical(factor) => ArrangerMode::Vertical(factor) => add(&arranger_content_vertical(self, factor))?
add(&arranger_content_vertical(self, factor))? };
}; add(&self.size)
add(&self.size) })));
}))), with_transport(col!([
self.size, Tui::fixed_y(self.splits[0], lay!([
Tui::push_x(1, Tui::fg( arranger(),
TuiTheme::title_fg(arranger_focused), Tui::push_x(1, Tui::fg(
format!("[{}] Arranger", if self.entered { TuiTheme::title_fg(arranger_focused),
"" format!("[{}] Arranger", if self.entered {
} else { ""
" " } else {
}) " "
)) })
])), ))
Split::right( ])),
false, Split::right(false, self.splits[1], PhraseListView(&self.phrases), &self.editor),
self.splits[1], ]))
PhraseListView::from(self),
&self.editor,
)
])
])
}); });
audio!(|self: ArrangerTui, client, scope|{ audio!(|self: ArrangerTui, client, scope|{
// Start profiling cycle // Start profiling cycle

View file

@ -88,13 +88,9 @@ impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
// Toggle visibility of phrase pool column // Toggle visibility of phrase pool column
key_pat!(Tab) => ShowPool(!state.show_pool), key_pat!(Tab) => ShowPool(!state.show_pool),
// Enqueue currently edited phrase // Enqueue currently edited phrase
key_pat!(Char('q')) => Enqueue(Some( key_pat!(Char('q')) => Enqueue(Some(state.phrases.phrase().clone())),
state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()
)),
// 0: Enqueue phrase 0 (stop all) // 0: Enqueue phrase 0 (stop all)
key_pat!(Char('0')) => Enqueue(Some( key_pat!(Char('0')) => Enqueue(Some(state.phrases.phrases()[0].clone())),
state.phrases.phrases[0].clone()
)),
// E: Toggle between editing currently playing or other phrase // E: Toggle between editing currently playing or other phrase
key_pat!(Char('e')) => if let Some((_, Some(playing_phrase))) = state.player.play_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()); 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 w = self.size.w();
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; 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_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_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_status = |x|Tui::split_n(false, 2, SequencerStatusBar::from(self), x);
let with_bar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), 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, ]))) 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 struct PhraseSelector {
pub(crate) title: &'static str, pub(crate) title: &'static str,
pub(crate) name: String, 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 { impl HasPhraseList for SequencerTui {
fn phrases_focused (&self) -> bool { fn phrases_focused (&self) -> bool {
true true

View file

@ -5,17 +5,20 @@ use crate::{
tui::file_browser::FileBrowserCommand as Browse, tui::file_browser::FileBrowserCommand as Browse,
api::PhrasePoolCommand as Pool, api::PhrasePoolCommand as Pool,
}; };
use Ordering::Relaxed;
#[derive(Debug)] #[derive(Debug)]
pub struct PhraseListModel { pub struct PhraseListModel {
/// Collection of phrases /// Collection of phrases
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>, pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
/// Selected phrase /// Selected phrase
pub(crate) phrase: AtomicUsize, pub(crate) phrase: AtomicUsize,
/// Scroll offset
pub scroll: usize,
/// Mode switch /// Mode switch
pub(crate) mode: Option<PhraseListMode>, pub(crate) mode: Option<PhraseListMode>,
/// Rendered size
size: Measure<Tui>,
/// Scroll offset
scroll: usize,
} }
/// Modes for phrase pool /// Modes for phrase pool
@ -167,6 +170,7 @@ impl Default for PhraseListModel {
phrase: 0.into(), phrase: 0.into(),
scroll: 0, scroll: 0,
mode: None, mode: None,
size: Measure::new(),
} }
} }
} }
@ -175,7 +179,7 @@ impl From<&Arc<RwLock<Phrase>>> for PhraseListModel {
fn from (phrase: &Arc<RwLock<Phrase>>) -> Self { fn from (phrase: &Arc<RwLock<Phrase>>) -> Self {
let mut model = Self::default(); let mut model = Self::default();
model.phrases.push(phrase.clone()); model.phrases.push(phrase.clone());
model.phrase.store(1, Ordering::Relaxed); model.phrase.store(1, Relaxed);
model model
} }
} }
@ -185,10 +189,10 @@ has_phrase!(|self:PhraseListModel|self.phrases[self.phrase_index()]);
impl PhraseListModel { impl PhraseListModel {
pub(crate) fn phrase_index (&self) -> usize { 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) { 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<PhraseListMode> { pub(crate) fn phrases_mode (&self) -> &Option<PhraseListMode> {
&self.mode &self.mode
@ -205,35 +209,15 @@ pub trait HasPhraseList: HasPhrases {
fn phrase_index (&self) -> usize; fn phrase_index (&self) -> usize;
} }
pub struct PhraseListView<'a> { pub struct PhraseListView<'a>(pub(crate) &'a PhraseListModel);
pub(crate) title: &'static str,
pub(crate) focused: bool,
pub(crate) entered: bool,
pub(crate) phrases: &'a Vec<Arc<RwLock<Phrase>>>,
pub(crate) index: usize,
pub(crate) mode: &'a Option<PhraseListMode>
}
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(),
}
}
}
// TODO: Display phrases always in order of appearance // TODO: Display phrases always in order of appearance
render!(|self: PhraseListView<'a>|{ render!(|self: PhraseListView<'a>|{
let Self { title, focused, entered, phrases, index, mode } = self; let PhraseListModel { phrases, mode, .. } = self.0;
let bg = TuiTheme::g(32); let bg = TuiTheme::g(32);
let title_color = TuiTheme::ti1(); let title_color = TuiTheme::ti1();
let upper_left = format!("{title}"); let upper_left = "Pool:";
let upper_right = format!("({})", phrases.len()); let upper_right = format!("({})", phrases.len());
Tui::bg(bg, lay!(move|add|{ Tui::bg(bg, lay!(move|add|{
//add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; //add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
add(&Tui::inset_xy(0, 1, Tui::fill_xy(col!(move|add|match mode { 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 Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let mut length = PhraseLength::new(length, None); let mut length = PhraseLength::new(length, None);
if let Some(PhraseListMode::Length(phrase, new_length, focus)) = mode { if let Some(PhraseListMode::Length(phrase, new_length, focus)) = mode {
if *focused && i == *phrase { if i == *phrase {
length.pulses = *new_length; length.pulses = *new_length;
length.focus = Some(*focus); length.focus = Some(*focus);
} }
@ -257,14 +241,14 @@ render!(|self: PhraseListView<'a>|{
Tui::bold(true, { Tui::bold(true, {
let mut row2 = format!(" {name}"); let mut row2 = format!(" {name}");
if let Some(PhraseListMode::Rename(phrase, _)) = mode { if let Some(PhraseListMode::Rename(phrase, _)) = mode {
if *focused && i == *phrase { if i == *phrase {
row2 = format!("{row2}"); row2 = format!("{row2}");
} }
}; };
row2 row2
}), }),
]))))?; ]))))?;
if *entered && i == *index { if i == self.0.phrase_index() {
add(&CORNERS)?; add(&CORNERS)?;
} }
Ok(()) 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_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)
})) }))
}); });