mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
remove HasPhraseList; 8470LOC
This commit is contained in:
parent
efda18293d
commit
623fce73a4
7 changed files with 133 additions and 233 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue