mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
simplify PhraseListView and arranger layout
This commit is contained in:
parent
9dd1d62de3
commit
dcd6bc24a7
3 changed files with 56 additions and 84 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue