remove HasPhraseList; 8470LOC

This commit is contained in:
🪞👃🪞 2024-12-18 13:46:07 +01:00
parent efda18293d
commit 623fce73a4
7 changed files with 133 additions and 233 deletions

View file

@ -1,7 +1,6 @@
use crate::*;
use crate::api::ArrangerTrackCommand;
use crate::api::ArrangerSceneCommand;
/// Root view for standalone `tek_arranger`
pub struct ArrangerTui {
pub jack: Arc<RwLock<JackClient>>,
@ -55,10 +54,8 @@ has_editor!(|self:ArrangerTui|self.editor);
handle!(<Tui>|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));
render!(<Tui>|self: ArrangerTui|{
let arranger_focused = self.arranger_focused();
let transport_focused = if let ArrangerFocus::Transport(_) = self.focus.inner() {
true
} else {
false
let transport_focused = match self.focus.inner() {
ArrangerFocus::Transport(_) => true, _ => false
};
let transport = TransportView::from((self, None, transport_focused));
let with_transport = move|x|col!([transport, x]);
@ -72,20 +69,8 @@ render!(<Tui>|self: ArrangerTui|{
};
add(&self.size)
})));
with_transport(col!([
Fixed::h(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),
]))
let with_pool = |x|Split::right(false, self.splits[1], PhraseListView(&self.phrases), x);
with_transport(col!([Fixed::h(self.splits[0], arranger()), with_pool(&self.editor),]))
});
audio!(|self: ArrangerTui, client, scope|{
// Start profiling cycle
@ -124,23 +109,7 @@ audio!(|self: ArrangerTui, client, scope|{
self.perf.update(t0, scope);
return Control::Continue
});
impl HasPhraseList for ArrangerTui {
fn phrases_focused (&self) -> bool {
self.focused() == ArrangerFocus::Phrases
}
fn phrases_entered (&self) -> bool {
self.entered() && self.phrases_focused()
}
fn phrases_mode (&self) -> &Option<PhraseListMode> {
&self.phrases.mode
}
fn phrase_index (&self) -> usize {
self.phrases.phrase.load(Ordering::Relaxed)
}
}
#[derive(Clone, Debug)]
pub enum ArrangerCommand {
#[derive(Clone, Debug)] pub enum ArrangerCommand {
Focus(FocusCommand<ArrangerFocus>),
Undo,
Redo,
@ -384,8 +353,6 @@ impl TransportControl<ArrangerFocus> for ArrangerTui {
}
}
}
has_clock!(|self:ArrangerTrack|self.player.clock());
has_player!(|self:ArrangerTrack|self.player);
/// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -809,8 +776,7 @@ impl HasScenes<ArrangerScene> for ArrangerTui {
}
}
#[derive(Default, Debug, Clone)]
pub struct ArrangerScene {
#[derive(Default, Debug, Clone)] pub struct ArrangerScene {
/// Name of scene
pub(crate) name: Arc<RwLock<String>>,
/// Clips in scene, one per track
@ -818,7 +784,6 @@ pub struct ArrangerScene {
/// Identifying color of scene
pub(crate) color: ItemColor,
}
impl ArrangerSceneApi for ArrangerScene {
fn name (&self) -> &Arc<RwLock<String>> {
&self.name
@ -830,7 +795,6 @@ impl ArrangerSceneApi for ArrangerScene {
self.color
}
}
impl HasTracks<ArrangerTrack> for ArrangerTui {
fn tracks (&self) -> &Vec<ArrangerTrack> {
&self.tracks
@ -839,7 +803,6 @@ impl HasTracks<ArrangerTrack> for ArrangerTui {
&mut self.tracks
}
}
impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
-> Usually<&mut ArrangerTrack>
@ -863,8 +826,7 @@ impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
}
}
#[derive(Debug)]
pub struct ArrangerTrack {
#[derive(Debug)] pub struct ArrangerTrack {
/// Name of track
pub(crate) name: Arc<RwLock<String>>,
/// Preferred width of track column
@ -874,7 +836,8 @@ pub struct ArrangerTrack {
/// MIDI player state
pub(crate) player: PhrasePlayerModel,
}
has_clock!(|self:ArrangerTrack|self.player.clock());
has_player!(|self:ArrangerTrack|self.player);
impl ArrangerTrackApi for ArrangerTrack {
/// Name of track
fn name (&self) -> &Arc<RwLock<String>> {
@ -893,7 +856,6 @@ impl ArrangerTrackApi for ArrangerTrack {
self.color
}
}
#[derive(PartialEq, Clone, Copy, Debug)]
/// Represents the current user selection in the arranger
pub enum ArrangerSelection {

View file

@ -33,10 +33,10 @@ pub enum GrooveboxCommand {
handle!(<Tui>|self:GrooveboxTui,input|GrooveboxCommand::execute_with_state(self, input));
input_to_command!(GrooveboxCommand: <Tui>|state:GrooveboxTui,input|match input.event() {
_ => match state.focus {
GrooveboxFocus::Sequencer =>
GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?),
GrooveboxFocus::Sampler =>
GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?),
GrooveboxFocus::Sequencer => GrooveboxCommand::Sequencer(
SequencerCommand::input_to_command(&state.sequencer, input)?),
GrooveboxFocus::Sampler => GrooveboxCommand::Sampler(
SamplerCommand::input_to_command(&state.sampler, input)?),
}
});
command!(|self:GrooveboxCommand,state:GrooveboxTui|match self {

View file

@ -54,7 +54,9 @@ pub enum SamplerCommand {
NoteOff(u7)
}
input_to_command!(SamplerCommand:<Tui>|state:SamplerTui,input|match state.mode {
Some(SamplerMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
Some(SamplerMode::Import(..)) => Self::Import(
FileBrowserCommand::input_to_command(state, input)?
),
_ => match input.event() {
// load sample
key_pat!(Char('l')) => Self::Import(FileBrowserCommand::Begin),

View file

@ -76,20 +76,6 @@ audio!(|self:SequencerTui, client, scope|{
self.perf.update(t0, scope);
Control::Continue
});
impl HasPhraseList for SequencerTui {
fn phrases_focused (&self) -> bool {
true
}
fn phrases_entered (&self) -> bool {
true
}
fn phrases_mode (&self) -> &Option<PhraseListMode> {
&self.phrases.mode
}
fn phrase_index (&self) -> usize {
self.phrases.phrase.load(Ordering::Relaxed)
}
}
has_size!(<Tui>|self:SequencerTui|&self.size);
has_clock!(|self:SequencerTui|&self.clock);
has_phrases!(|self:SequencerTui|self.phrases.phrases);

View file

@ -2,7 +2,6 @@ use crate::*;
use KeyCode::{Up, Down, Right, Left, Enter, Esc, Char, Backspace};
use FileBrowserCommand::*;
use super::phrase_list::PhraseListMode::{Import, Export};
/// Browses for phrase to import/export
#[derive(Debug, Clone)]
pub struct FileBrowser {
@ -14,7 +13,16 @@ pub struct FileBrowser {
pub scroll: usize,
pub size: Measure<Tui>
}
/// Commands supported by [FileBrowser]
#[derive(Debug, Clone, PartialEq)]
pub enum FileBrowserCommand {
Begin,
Cancel,
Confirm,
Select(usize),
Chdir(PathBuf),
Filter(String),
}
render!(<Tui>|self: FileBrowser|{
Stack::down(|add|{
let mut i = 0;
@ -34,7 +42,6 @@ render!(<Tui>|self: FileBrowser|{
Ok(())
})
});
impl FileBrowser {
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
@ -83,106 +90,3 @@ impl FileBrowser {
Self::new(Some(self.path()))
}
}
/// Commands supported by [FileBrowser]
#[derive(Debug, Clone, PartialEq)]
pub enum FileBrowserCommand {
Begin,
Cancel,
Confirm,
Select(usize),
Chdir(PathBuf),
Filter(String),
}
command!(|self: FileBrowserCommand, state: PhraseListModel|{
let mode = state.phrases_mode_mut();
match mode {
Some(Import(index, ref mut browser)) => match self {
Cancel => {
*mode = None;
},
Chdir(cwd) => {
*mode = Some(Import(*index, FileBrowser::new(Some(cwd))?));
},
Select(index) => {
browser.index = index;
},
Confirm => {
if browser.is_file() {
let index = *index;
let path = browser.path();
*mode = None;
PhrasePoolCommand::Import(index, path).execute(state)?;
} else if browser.is_dir() {
*mode = Some(Import(*index, browser.chdir()?));
}
},
_ => todo!(),
},
Some(PhraseListMode::Export(index, ref mut browser)) => match self {
Cancel => {
*mode = None;
},
Chdir(cwd) => {
*mode = Some(PhraseListMode::Export(*index, FileBrowser::new(Some(cwd))?));
},
Select(index) => {
browser.index = index;
},
_ => unreachable!()
},
_ => unreachable!(),
};
None
});
input_to_command!(FileBrowserCommand:<Tui>|state:PhraseListModel,from|{
if let Some(PhraseListMode::Import(_index, browser)) = state.phrases_mode() {
match from.event() {
key_pat!(Up) => Select(
browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1))
),
key_pat!(Down) => Select(
browser.index.saturating_add(1) % browser.len()
),
key_pat!(Right) => Chdir(browser.cwd.clone()),
key_pat!(Left) => Chdir(browser.cwd.clone()),
key_pat!(Enter) => Confirm,
key_pat!(Char(_)) => { todo!() },
key_pat!(Backspace) => { todo!() },
key_pat!(Esc) => Self::Cancel,
_ => return None
}
} else if let Some(PhraseListMode::Export(_index, browser)) = state.phrases_mode() {
match from.event() {
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())),
key_pat!(Down) => Select(browser.index.saturating_add(1) % browser.len()),
key_pat!(Right) => Chdir(browser.cwd.clone()),
key_pat!(Left) => Chdir(browser.cwd.clone()),
key_pat!(Enter) => Confirm,
key_pat!(Char(_)) => { todo!() },
key_pat!(Backspace) => { todo!() },
key_pat!(Esc) => Self::Cancel,
_ => return None
}
} else {
unreachable!()
}
});
input_to_command!(PhraseLengthCommand:<Tui>|state:PhraseListModel,from|{
if let Some(PhraseListMode::Length(_, length, _)) = state.phrases_mode() {
match from.event() {
key_pat!(Up) => Self::Inc,
key_pat!(Down) => Self::Dec,
key_pat!(Right) => Self::Next,
key_pat!(Left) => Self::Prev,
key_pat!(Enter) => Self::Set(*length),
key_pat!(Esc) => Self::Cancel,
_ => return None
}
} else {
unreachable!()
}
});

View file

@ -2,7 +2,6 @@ use crate::*;
use super::phrase_list::{PhraseListModel, PhraseListMode};
use PhraseLengthFocus::*;
use PhraseLengthCommand::*;
/// Displays and edits phrase length.
#[derive(Clone)]
pub struct PhraseLength {
@ -15,7 +14,6 @@ pub struct PhraseLength {
/// Selected subdivision
pub focus: Option<PhraseLengthFocus>,
}
impl PhraseLength {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus }
@ -39,7 +37,6 @@ impl PhraseLength {
format!("{:>02}", self.ticks())
}
}
/// Focused field of `PhraseLength`
#[derive(Copy, Clone, Debug)]
pub enum PhraseLengthFocus {
@ -50,7 +47,6 @@ pub enum PhraseLengthFocus {
/// Editing the number of ticks
Tick,
}
impl PhraseLengthFocus {
pub fn next (&mut self) {
*self = match self {
@ -67,7 +63,6 @@ impl PhraseLengthFocus {
}
}
}
render!(<Tui>|self: PhraseLength|{
let bars = ||self.bars_string();
let beats = ||self.beats_string();
@ -83,7 +78,6 @@ render!(<Tui>|self: PhraseLength|{
add(&row!([" ", bars(), ".", beats(), "[", ticks()])),
})
});
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PhraseLengthCommand {
Begin,
@ -94,36 +88,48 @@ pub enum PhraseLengthCommand {
Inc,
Dec,
}
impl Command<PhraseListModel> for PhraseLengthCommand {
fn execute (self, state: &mut PhraseListModel) -> Perhaps<Self> {
match state.phrases_mode_mut().clone() {
Some(PhraseListMode::Length(phrase, ref mut length, ref mut focus)) => match self {
Cancel => { *state.phrases_mode_mut() = None; },
Prev => { focus.prev() },
Next => { focus.next() },
Inc => match focus {
Bar => { *length += 4 * PPQ },
Beat => { *length += PPQ },
Tick => { *length += 1 },
},
Dec => match focus {
Bar => { *length = length.saturating_sub(4 * PPQ) },
Beat => { *length = length.saturating_sub(PPQ) },
Tick => { *length = length.saturating_sub(1) },
},
Set(length) => {
let mut phrase = state.phrases()[phrase].write().unwrap();
let old_length = phrase.length;
phrase.length = length;
std::mem::drop(phrase);
*state.phrases_mode_mut() = None;
return Ok(Some(Self::Set(old_length)))
},
_ => unreachable!()
command!(|self:PhraseLengthCommand,state:PhraseListModel|{
match state.phrases_mode_mut().clone() {
Some(PhraseListMode::Length(phrase, ref mut length, ref mut focus)) => match self {
Cancel => { *state.phrases_mode_mut() = None; },
Prev => { focus.prev() },
Next => { focus.next() },
Inc => match focus {
Bar => { *length += 4 * PPQ },
Beat => { *length += PPQ },
Tick => { *length += 1 },
},
Dec => match focus {
Bar => { *length = length.saturating_sub(4 * PPQ) },
Beat => { *length = length.saturating_sub(PPQ) },
Tick => { *length = length.saturating_sub(1) },
},
Set(length) => {
let mut phrase = state.phrases()[phrase].write().unwrap();
let old_length = phrase.length;
phrase.length = length;
std::mem::drop(phrase);
*state.phrases_mode_mut() = None;
return Ok(Some(Self::Set(old_length)))
},
_ => unreachable!()
};
Ok(None)
},
_ => unreachable!()
};
None
});
input_to_command!(PhraseLengthCommand:<Tui>|state:PhraseListModel,from|{
if let Some(PhraseListMode::Length(_, length, _)) = state.phrases_mode() {
match from.event() {
key_pat!(Up) => Self::Inc,
key_pat!(Down) => Self::Dec,
key_pat!(Right) => Self::Next,
key_pat!(Left) => Self::Prev,
key_pat!(Enter) => Self::Set(*length),
key_pat!(Esc) => Self::Cancel,
_ => return None
}
} else {
unreachable!()
}
}
});

View file

@ -156,7 +156,6 @@ fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<Phra
_ => return None
})
}
impl Default for PhraseListModel {
fn default () -> Self {
Self {
@ -168,7 +167,6 @@ impl Default for PhraseListModel {
}
}
}
impl From<&Arc<RwLock<Phrase>>> for PhraseListModel {
fn from (phrase: &Arc<RwLock<Phrase>>) -> Self {
let mut model = Self::default();
@ -177,10 +175,8 @@ impl From<&Arc<RwLock<Phrase>>> for PhraseListModel {
model
}
}
has_phrases!(|self:PhraseListModel|self.phrases);
has_phrase!(|self:PhraseListModel|self.phrases[self.phrase_index()]);
impl PhraseListModel {
pub(crate) fn phrase_index (&self) -> usize {
self.phrase.load(Relaxed)
@ -195,16 +191,7 @@ impl PhraseListModel {
&mut self.mode
}
}
pub trait HasPhraseList: HasPhrases {
fn phrases_focused (&self) -> bool;
fn phrases_entered (&self) -> bool;
fn phrases_mode (&self) -> &Option<PhraseListMode>;
fn phrase_index (&self) -> usize;
}
pub struct PhraseListView<'a>(pub(crate) &'a PhraseListModel);
// TODO: Display phrases always in order of appearance
render!(<Tui>|self: PhraseListView<'a>|{
let PhraseListModel { phrases, mode, .. } = self.0;
@ -254,14 +241,12 @@ render!(<Tui>|self: PhraseListView<'a>|{
add(&self.0.size)
}))
});
pub struct PhraseSelector {
pub(crate) title: &'static str,
pub(crate) name: String,
pub(crate) color: ItemPalette,
pub(crate) time: String,
}
// TODO: Display phrases always in order of appearance
render!(<Tui>|self: PhraseSelector|Fixed::wh(24, 1, row!([
Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)),
@ -270,7 +255,6 @@ render!(<Tui>|self: PhraseSelector|Fixed::wh(24, 1, row!([
Tui::bg(self.color.dark.rgb, &self.time),
])),
])));
impl PhraseSelector {
// beats elapsed
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
@ -314,13 +298,69 @@ impl PhraseSelector {
};
Self { title: " Next|", time, name, color, }
}
pub fn edit_phrase (phrase: &Option<Arc<RwLock<Phrase>>>) -> Self {
let (time, name, color) = if let Some(phrase) = phrase {
let phrase = phrase.read().unwrap();
(format!("{}", phrase.length), phrase.name.clone(), phrase.color)
} else {
("".to_string(), " ".to_string(), ItemPalette::from(TuiTheme::g(64)))
};
Self { title: "Editing:", time, name, color }
}
}
command!(|self: FileBrowserCommand, state: PhraseListModel|{
use PhraseListMode::*;
use FileBrowserCommand::*;
let mode = &mut state.mode;
match mode {
Some(Import(index, ref mut browser)) => match self {
Cancel => { *mode = None; },
Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); },
Select(index) => { browser.index = index; },
Confirm => if browser.is_file() {
let index = *index;
let path = browser.path();
*mode = None;
PhrasePoolCommand::Import(index, path).execute(state)?;
} else if browser.is_dir() {
*mode = Some(Import(*index, browser.chdir()?));
},
_ => todo!(),
},
Some(Export(index, ref mut browser)) => match self {
Cancel => { *mode = None; },
Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); },
Select(index) => { browser.index = index; },
_ => unreachable!()
},
_ => unreachable!(),
};
None
});
input_to_command!(FileBrowserCommand:<Tui>|state:PhraseListModel,from|{
use PhraseListMode::*;
use FileBrowserCommand::*;
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
if let Some(PhraseListMode::Import(_index, browser)) = &state.mode {
match from.event() {
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0
.min(browser.len().saturating_sub(1))),
key_pat!(Down) => Select(browser.index.saturating_add(1)
% browser.len()),
key_pat!(Right) => Chdir(browser.cwd.clone()),
key_pat!(Left) => Chdir(browser.cwd.clone()),
key_pat!(Enter) => Confirm,
key_pat!(Char(_)) => { todo!() },
key_pat!(Backspace) => { todo!() },
key_pat!(Esc) => Cancel,
_ => return None
}
} else if let Some(PhraseListMode::Export(_index, browser)) = &state.mode {
match from.event() {
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0
.min(browser.len())),
key_pat!(Down) => Select(browser.index.saturating_add(1)
% browser.len()),
key_pat!(Right) => Chdir(browser.cwd.clone()),
key_pat!(Left) => Chdir(browser.cwd.clone()),
key_pat!(Enter) => Confirm,
key_pat!(Char(_)) => { todo!() },
key_pat!(Backspace) => { todo!() },
key_pat!(Esc) => Cancel,
_ => return None
}
} else {
unreachable!()
}
});