mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: implementing pool command dispatch
This commit is contained in:
parent
50b7d8a23d
commit
d393cab2d8
8 changed files with 270 additions and 243 deletions
|
|
@ -10,7 +10,6 @@ mod midi_point; pub use midi_point::*;
|
||||||
mod midi_view; pub use midi_view::*;
|
mod midi_view; pub use midi_view::*;
|
||||||
|
|
||||||
mod midi_pool; pub use midi_pool::*;
|
mod midi_pool; pub use midi_pool::*;
|
||||||
mod midi_pool_tui; pub use midi_pool_tui::*;
|
|
||||||
mod midi_pool_cmd; pub use midi_pool_cmd::*;
|
mod midi_pool_cmd; pub use midi_pool_cmd::*;
|
||||||
|
|
||||||
mod midi_edit; pub use midi_edit::*;
|
mod midi_edit; pub use midi_edit::*;
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,10 @@ impl MidiPool {
|
||||||
pub(crate) fn set_clip_index (&self, value: usize) {
|
pub(crate) fn set_clip_index (&self, value: usize) {
|
||||||
self.clip.store(value, Relaxed);
|
self.clip.store(value, Relaxed);
|
||||||
}
|
}
|
||||||
pub(crate) fn clips_mode (&self) -> &Option<PoolMode> {
|
pub(crate) fn mode (&self) -> &Option<PoolMode> {
|
||||||
&self.mode
|
&self.mode
|
||||||
}
|
}
|
||||||
pub(crate) fn clips_mode_mut (&mut self) -> &mut Option<PoolMode> {
|
pub(crate) fn mode_mut (&mut self) -> &mut Option<PoolMode> {
|
||||||
&mut self.mode
|
&mut self.mode
|
||||||
}
|
}
|
||||||
pub fn file_picker (&self) -> Option<&FileBrowser> {
|
pub fn file_picker (&self) -> Option<&FileBrowser> {
|
||||||
|
|
@ -81,6 +81,35 @@ impl MidiPool {
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn begin_clip_length (&mut self) {
|
||||||
|
let length = self.clips()[self.clip_index()].read().unwrap().length;
|
||||||
|
*self.mode_mut() = Some(PoolMode::Length(
|
||||||
|
self.clip_index(),
|
||||||
|
length,
|
||||||
|
ClipLengthFocus::Bar
|
||||||
|
));
|
||||||
|
}
|
||||||
|
pub fn begin_clip_rename (&mut self) {
|
||||||
|
let name = self.clips()[self.clip_index()].read().unwrap().name.clone();
|
||||||
|
*self.mode_mut() = Some(PoolMode::Rename(
|
||||||
|
self.clip_index(),
|
||||||
|
name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
pub fn begin_import (&mut self) -> Usually<()> {
|
||||||
|
*self.mode_mut() = Some(PoolMode::Import(
|
||||||
|
self.clip_index(),
|
||||||
|
FileBrowser::new(None)?
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn begin_export (&mut self) -> Usually<()> {
|
||||||
|
*self.mode_mut() = Some(PoolMode::Export(
|
||||||
|
self.clip_index(),
|
||||||
|
FileBrowser::new(None)?
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Displays and edits clip length.
|
/// Displays and edits clip length.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -135,3 +164,43 @@ impl ClipLengthFocus {
|
||||||
*self = match self { Self::Bar => Self::Tick, Self::Beat => Self::Bar, Self::Tick => Self::Beat, }
|
*self = match self { Self::Bar => Self::Tick, Self::Beat => Self::Bar, Self::Tick => Self::Beat, }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub struct PoolView<'a>(pub bool, pub &'a MidiPool);
|
||||||
|
content!(TuiOut: |self: PoolView<'a>| {
|
||||||
|
let Self(compact, model) = self;
|
||||||
|
let MidiPool { clips, mode, .. } = self.1;
|
||||||
|
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
||||||
|
let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
|
||||||
|
let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
|
||||||
|
let iter = | |model.clips().clone().into_iter();
|
||||||
|
Tui::bg(Color::Reset, Fixed::y(clips.read().unwrap().len() as u16, on_bg(border(Map::new(iter, move|clip, i|{
|
||||||
|
let item_height = 1;
|
||||||
|
let item_offset = i as u16 * item_height;
|
||||||
|
let selected = i == model.clip_index();
|
||||||
|
let MidiClip { ref name, color, length, .. } = *clip.read().unwrap();
|
||||||
|
let bg = if selected { color.light.rgb } else { color.base.rgb };
|
||||||
|
let fg = color.lightest.rgb;
|
||||||
|
let name = if *compact { format!(" {i:>3}") } else { format!(" {i:>3} {name}") };
|
||||||
|
let length = if *compact { String::default() } else { format!("{length} ") };
|
||||||
|
Fixed::y(1, map_south(item_offset, item_height, Tui::bg(bg, lay!(
|
||||||
|
Fill::x(Align::w(Tui::fg(fg, Tui::bold(selected, name)))),
|
||||||
|
Fill::x(Align::e(Tui::fg(fg, Tui::bold(selected, length)))),
|
||||||
|
Fill::x(Align::w(When::new(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶"))))),
|
||||||
|
Fill::x(Align::e(When::new(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀"))))),
|
||||||
|
))))
|
||||||
|
})))))
|
||||||
|
});
|
||||||
|
content!(TuiOut: |self: ClipLength| {
|
||||||
|
let bars = ||self.bars_string();
|
||||||
|
let beats = ||self.beats_string();
|
||||||
|
let ticks = ||self.ticks_string();
|
||||||
|
match self.focus {
|
||||||
|
None =>
|
||||||
|
row!(" ", bars(), ".", beats(), ".", ticks()),
|
||||||
|
Some(ClipLengthFocus::Bar) =>
|
||||||
|
row!("[", bars(), "]", beats(), ".", ticks()),
|
||||||
|
Some(ClipLengthFocus::Beat) =>
|
||||||
|
row!(" ", bars(), "[", beats(), "]", ticks()),
|
||||||
|
Some(ClipLengthFocus::Tick) =>
|
||||||
|
row!(" ", bars(), ".", beats(), "[", ticks()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,32 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use KeyCode::*;
|
use KeyCode::*;
|
||||||
|
impl PoolCommand {
|
||||||
|
const KEYS_POOL: &str = include_str!("midi_pool_keys.edn");
|
||||||
|
const KEYS_CLIP: &str = include_str!("midi_pool_keys_clip.edn");
|
||||||
|
const KEYS_RENAME: &str = include_str!("midi_pool_keys_rename.edn");
|
||||||
|
const KEYS_LENGTH: &str = include_str!("midi_pool_keys_length.edn");
|
||||||
|
const KEYS_FILE: &str = include_str!("midi_pool_keys_file.edn");
|
||||||
|
pub fn from_tui_event (state: &MidiPool, input: &Event) -> Usually<Option<Self>> {
|
||||||
|
use EdnItem::*;
|
||||||
|
let edns: Vec<EdnItem<&str>> = EdnItem::read_all(match state.mode() {
|
||||||
|
Some(PoolMode::Rename(..)) => Self::KEYS_RENAME,
|
||||||
|
Some(PoolMode::Length(..)) => Self::KEYS_LENGTH,
|
||||||
|
Some(PoolMode::Import(..)) | Some(PoolMode::Export(..)) => Self::KEYS_FILE,
|
||||||
|
_ => Self::KEYS_CLIP
|
||||||
|
})?;
|
||||||
|
for item in edns {
|
||||||
|
match item {
|
||||||
|
Exp(e) => match e.as_slice() {
|
||||||
|
[Sym(key), Key(command), args @ ..] => {
|
||||||
|
}
|
||||||
|
_ => panic!("invalid config")
|
||||||
|
}
|
||||||
|
_ => panic!("invalid config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
edn_provide!(bool: |self: MidiPool| {});
|
edn_provide!(bool: |self: MidiPool| {});
|
||||||
edn_provide!(usize: |self: MidiPool| {});
|
edn_provide!(usize: |self: MidiPool| {});
|
||||||
edn_provide!(MidiClip: |self: MidiPool| {});
|
edn_provide!(MidiClip: |self: MidiPool| {});
|
||||||
|
|
@ -7,9 +34,8 @@ edn_provide!(PathBuf: |self: MidiPool| {});
|
||||||
edn_provide!(Arc<str>: |self: MidiPool| {});
|
edn_provide!(Arc<str>: |self: MidiPool| {});
|
||||||
edn_provide!(ItemColor: |self: MidiPool| {});
|
edn_provide!(ItemColor: |self: MidiPool| {});
|
||||||
#[derive(Clone, PartialEq, Debug)] pub enum PoolCommand {
|
#[derive(Clone, PartialEq, Debug)] pub enum PoolCommand {
|
||||||
|
/// Toggle visibility of pool
|
||||||
Show(bool),
|
Show(bool),
|
||||||
/// Update the contents of the clip pool
|
|
||||||
Clip(PoolClipCommand),
|
|
||||||
/// Select a clip from the clip pool
|
/// Select a clip from the clip pool
|
||||||
Select(usize),
|
Select(usize),
|
||||||
/// Rename a clip
|
/// Rename a clip
|
||||||
|
|
@ -20,15 +46,33 @@ edn_provide!(ItemColor: |self: MidiPool| {});
|
||||||
Import(FileBrowserCommand),
|
Import(FileBrowserCommand),
|
||||||
/// Export to file
|
/// Export to file
|
||||||
Export(FileBrowserCommand),
|
Export(FileBrowserCommand),
|
||||||
|
/// Update the contents of the clip pool
|
||||||
|
Clip(PoolClipCommand),
|
||||||
}
|
}
|
||||||
edn_command!(PoolCommand: |state: MidiPool| {
|
edn_command!(PoolCommand: |state: MidiPool| {
|
||||||
("show" [a: bool] Self::Show(a.expect("no flag")))
|
("show" [a: bool] Self::Show(a.expect("no flag")))
|
||||||
("select" [i: usize] Self::Select(i.expect("no index")))
|
("select" [i: usize] Self::Select(i.expect("no index")))
|
||||||
("clip" [a, ..b] Self::Clip(PoolClipCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("rename" [a, ..b] Self::Rename(ClipRenameCommand::from_edn(state, &a.to_ref(), b)))
|
("rename" [a, ..b] Self::Rename(ClipRenameCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
("length" [a, ..b] Self::Length(ClipLengthCommand::from_edn(state, &a.to_ref(), b)))
|
("length" [a, ..b] Self::Length(ClipLengthCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
("import" [a, ..b] Self::Import(FileBrowserCommand::from_edn(state, &a.to_ref(), b)))
|
("import" [a, ..b] Self::Import(FileBrowserCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
("export" [a, ..b] Self::Export(FileBrowserCommand::from_edn(state, &a.to_ref(), b)))
|
("export" [a, ..b] Self::Export(FileBrowserCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("clip" [a, ..b] Self::Clip(PoolClipCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
});
|
||||||
|
command!(|self: PoolCommand, state: MidiPool|{
|
||||||
|
use PoolCommand::*;
|
||||||
|
match self {
|
||||||
|
Rename(ClipRenameCommand::Begin) => { state.begin_clip_rename(); None }
|
||||||
|
Rename(command) => command.delegate(state, Rename)?,
|
||||||
|
Length(ClipLengthCommand::Begin) => { state.begin_clip_length(); None },
|
||||||
|
Length(command) => command.delegate(state, Length)?,
|
||||||
|
Import(FileBrowserCommand::Begin) => { state.begin_import()?; None },
|
||||||
|
Import(command) => command.delegate(state, Import)?,
|
||||||
|
Export(FileBrowserCommand::Begin) => { state.begin_export()?; None },
|
||||||
|
Export(command) => command.delegate(state, Export)?,
|
||||||
|
Clip(command) => command.execute(state)?.map(Clip),
|
||||||
|
Show(visible) => { state.visible = visible; Some(Self::Show(!visible)) },
|
||||||
|
Select(clip) => { state.set_clip_index(clip); None },
|
||||||
|
}
|
||||||
});
|
});
|
||||||
#[derive(Clone, Debug, PartialEq)] pub enum PoolClipCommand {
|
#[derive(Clone, Debug, PartialEq)] pub enum PoolClipCommand {
|
||||||
Add(usize, MidiClip),
|
Add(usize, MidiClip),
|
||||||
|
|
@ -41,53 +85,15 @@ edn_command!(PoolCommand: |state: MidiPool| {
|
||||||
SetColor(usize, ItemColor),
|
SetColor(usize, ItemColor),
|
||||||
}
|
}
|
||||||
edn_command!(PoolClipCommand: |state: MidiPool| {
|
edn_command!(PoolClipCommand: |state: MidiPool| {
|
||||||
("add" [i: usize, c: MidiClip] Self::Add(i.expect("no index"), c.expect("no clip")))
|
("add" [i: usize, c: MidiClip] Self::Add(i.expect("no index"), c.expect("no clip")))
|
||||||
("delete" [i: usize] Self::Delete(i.expect("no index")))
|
("delete" [i: usize] Self::Delete(i.expect("no index")))
|
||||||
("swap" [a: usize, b: usize] Self::Swap(a.expect("no index"), b.expect("no index")))
|
("swap" [a: usize, b: usize] Self::Swap(a.expect("no index"), b.expect("no index")))
|
||||||
("import" [i: usize, p: PathBuf] Self::Import(i.expect("no index"), p.expect("no path")))
|
("import" [i: usize, p: PathBuf] Self::Import(i.expect("no index"), p.expect("no path")))
|
||||||
("export" [i: usize, p: PathBuf] Self::Export(i.expect("no index"), p.expect("no path")))
|
("export" [i: usize, p: PathBuf] Self::Export(i.expect("no index"), p.expect("no path")))
|
||||||
("set-name" [i: usize, n: Arc<str>] Self::SetName(i.expect("no index"), n.expect("no name")))
|
("set-name" [i: usize, n: Arc<str>] Self::SetName(i.expect("no index"), n.expect("no name")))
|
||||||
("set-length" [i: usize, l: usize] Self::SetLength(i.expect("no index"), l.expect("no length")))
|
("set-length" [i: usize, l: usize] Self::SetLength(i.expect("no index"), l.expect("no length")))
|
||||||
("set-color" [i: usize, c: ItemColor] Self::SetColor(i.expect("no index"), c.expect("no color")))
|
("set-color" [i: usize, c: ItemColor] Self::SetColor(i.expect("no index"), c.expect("no color")))
|
||||||
});
|
});
|
||||||
#[derive(Clone, Debug, PartialEq)] pub enum ClipRenameCommand {
|
|
||||||
Begin,
|
|
||||||
Cancel,
|
|
||||||
Confirm,
|
|
||||||
Set(Arc<str>),
|
|
||||||
}
|
|
||||||
edn_command!(ClipRenameCommand: |state: MidiPool| {
|
|
||||||
("begin" [] Self::Begin)
|
|
||||||
("cancel" [] Self::Cancel)
|
|
||||||
("confirm" [] Self::Confirm)
|
|
||||||
("set" [n: Arc<str>] Self::Set(n.expect("no name")))
|
|
||||||
});
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)] pub enum ClipLengthCommand {
|
|
||||||
Begin,
|
|
||||||
Cancel,
|
|
||||||
Set(usize),
|
|
||||||
Next,
|
|
||||||
Prev,
|
|
||||||
Inc,
|
|
||||||
Dec,
|
|
||||||
}
|
|
||||||
edn_command!(ClipLengthCommand: |state: MidiPool| {
|
|
||||||
("begin" [] Self::Begin)
|
|
||||||
("cancel" [] Self::Cancel)
|
|
||||||
("next" [] Self::Next)
|
|
||||||
("prev" [] Self::Prev)
|
|
||||||
("inc" [] Self::Inc)
|
|
||||||
("dec" [] Self::Dec)
|
|
||||||
("set" [l: usize] Self::Set(l.expect("no length")))
|
|
||||||
});
|
|
||||||
edn_command!(FileBrowserCommand: |state: MidiPool| {
|
|
||||||
("begin" [] Self::Begin)
|
|
||||||
("cancel" [] Self::Cancel)
|
|
||||||
("confirm" [] Self::Confirm)
|
|
||||||
("select" [i: usize] Self::Select(i.expect("no index")))
|
|
||||||
("chdir" [p: PathBuf] Self::Chdir(p.expect("no path")))
|
|
||||||
("filter" [f: Arc<str>] Self::Filter(f.expect("no filter")))
|
|
||||||
});
|
|
||||||
impl<T: HasClips> Command<T> for PoolClipCommand {
|
impl<T: HasClips> Command<T> for PoolClipCommand {
|
||||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||||
use PoolClipCommand::*;
|
use PoolClipCommand::*;
|
||||||
|
|
@ -153,65 +159,144 @@ impl<T: HasClips> Command<T> for PoolClipCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command!(|self: PoolCommand, state: MidiPool|{
|
#[derive(Clone, Debug, PartialEq)] pub enum ClipRenameCommand {
|
||||||
use PoolCommand::*;
|
Begin,
|
||||||
match self {
|
Cancel,
|
||||||
Show(visible) => {
|
Confirm,
|
||||||
state.visible = visible;
|
Set(Arc<str>),
|
||||||
Some(Self::Show(!visible))
|
}
|
||||||
|
edn_command!(ClipRenameCommand: |state: MidiPool| {
|
||||||
|
("begin" [] Self::Begin)
|
||||||
|
("cancel" [] Self::Cancel)
|
||||||
|
("confirm" [] Self::Confirm)
|
||||||
|
("set" [n: Arc<str>] Self::Set(n.expect("no name")))
|
||||||
|
});
|
||||||
|
command!(|self: ClipRenameCommand, state: MidiPool|{
|
||||||
|
use ClipRenameCommand::*;
|
||||||
|
if let Some(
|
||||||
|
PoolMode::Rename(clip, ref mut old_name)
|
||||||
|
) = state.mode_mut().clone() {
|
||||||
|
match self {
|
||||||
|
Set(s) => {
|
||||||
|
state.clips()[clip].write().unwrap().name = s;
|
||||||
|
return Ok(Some(Self::Set(old_name.clone().into())))
|
||||||
|
},
|
||||||
|
Confirm => {
|
||||||
|
let old_name = old_name.clone();
|
||||||
|
*state.mode_mut() = None;
|
||||||
|
return Ok(Some(Self::Set(old_name)))
|
||||||
|
},
|
||||||
|
Cancel => {
|
||||||
|
state.clips()[clip].write().unwrap().name = old_name.clone().into();
|
||||||
|
return Ok(None)
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
Rename(command) => match command {
|
} else {
|
||||||
ClipRenameCommand::Begin => {
|
unreachable!()
|
||||||
let length = state.clips()[state.clip_index()].read().unwrap().length;
|
|
||||||
*state.clips_mode_mut() = Some(
|
|
||||||
PoolMode::Length(state.clip_index(), length, ClipLengthFocus::Bar)
|
|
||||||
);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => command.execute(state)?.map(Rename)
|
|
||||||
},
|
|
||||||
Length(command) => match command {
|
|
||||||
ClipLengthCommand::Begin => {
|
|
||||||
let name = state.clips()[state.clip_index()].read().unwrap().name.clone();
|
|
||||||
*state.clips_mode_mut() = Some(
|
|
||||||
PoolMode::Rename(state.clip_index(), name)
|
|
||||||
);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => command.execute(state)?.map(Length)
|
|
||||||
},
|
|
||||||
Import(command) => match command {
|
|
||||||
FileBrowserCommand::Begin => {
|
|
||||||
*state.clips_mode_mut() = Some(
|
|
||||||
PoolMode::Import(state.clip_index(), FileBrowser::new(None)?)
|
|
||||||
);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => command.execute(state)?.map(Import)
|
|
||||||
},
|
|
||||||
Export(command) => match command {
|
|
||||||
FileBrowserCommand::Begin => {
|
|
||||||
*state.clips_mode_mut() = Some(
|
|
||||||
PoolMode::Export(state.clip_index(), FileBrowser::new(None)?)
|
|
||||||
);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => command.execute(state)?.map(Export)
|
|
||||||
},
|
|
||||||
Select(clip) => {
|
|
||||||
state.set_clip_index(clip);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
Clip(command) => command.execute(state)?.map(Clip),
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
input_to_command!(PoolCommand: |state: MidiPool, input: Event|match state.clips_mode() {
|
#[derive(Copy, Clone, Debug, PartialEq)] pub enum ClipLengthCommand {
|
||||||
|
Begin,
|
||||||
|
Cancel,
|
||||||
|
Set(usize),
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
Inc,
|
||||||
|
Dec,
|
||||||
|
}
|
||||||
|
edn_command!(ClipLengthCommand: |state: MidiPool| {
|
||||||
|
("begin" [] Self::Begin)
|
||||||
|
("cancel" [] Self::Cancel)
|
||||||
|
("next" [] Self::Next)
|
||||||
|
("prev" [] Self::Prev)
|
||||||
|
("inc" [] Self::Inc)
|
||||||
|
("dec" [] Self::Dec)
|
||||||
|
("set" [l: usize] Self::Set(l.expect("no length")))
|
||||||
|
});
|
||||||
|
command!(|self: ClipLengthCommand, state: MidiPool|{
|
||||||
|
use ClipLengthCommand::*;
|
||||||
|
use ClipLengthFocus::*;
|
||||||
|
if let Some(
|
||||||
|
PoolMode::Length(clip, ref mut length, ref mut focus)
|
||||||
|
) = state.mode_mut().clone() {
|
||||||
|
match self {
|
||||||
|
Cancel => { *state.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 old_length = None;
|
||||||
|
{
|
||||||
|
let clip = state.clips()[clip].clone();//.write().unwrap();
|
||||||
|
old_length = Some(clip.read().unwrap().length);
|
||||||
|
clip.write().unwrap().length = length;
|
||||||
|
}
|
||||||
|
*state.mode_mut() = None;
|
||||||
|
return Ok(old_length.map(Self::Set))
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
edn_command!(FileBrowserCommand: |state: MidiPool| {
|
||||||
|
("begin" [] Self::Begin)
|
||||||
|
("cancel" [] Self::Cancel)
|
||||||
|
("confirm" [] Self::Confirm)
|
||||||
|
("select" [i: usize] Self::Select(i.expect("no index")))
|
||||||
|
("chdir" [p: PathBuf] Self::Chdir(p.expect("no path")))
|
||||||
|
("filter" [f: Arc<str>] Self::Filter(f.expect("no filter")))
|
||||||
|
});
|
||||||
|
command!(|self: FileBrowserCommand, state: MidiPool|{
|
||||||
|
use PoolMode::*;
|
||||||
|
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;
|
||||||
|
PoolClipCommand::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!(PoolCommand: |state: MidiPool, input: Event|match state.mode() {
|
||||||
Some(PoolMode::Rename(..)) => Self::Rename(ClipRenameCommand::input_to_command(state, input)?),
|
Some(PoolMode::Rename(..)) => Self::Rename(ClipRenameCommand::input_to_command(state, input)?),
|
||||||
Some(PoolMode::Length(..)) => Self::Length(ClipLengthCommand::input_to_command(state, input)?),
|
Some(PoolMode::Length(..)) => Self::Length(ClipLengthCommand::input_to_command(state, input)?),
|
||||||
Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
|
Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
|
||||||
Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?),
|
Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?),
|
||||||
_ => to_clips_command(state, input)?
|
_ => to_clips_command(state, input)?
|
||||||
});
|
});
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
fn to_clips_command (state: &MidiPool, input: &Event) -> Option<PoolCommand> {
|
fn to_clips_command (state: &MidiPool, input: &Event) -> Option<PoolCommand> {
|
||||||
use KeyCode::{Up, Down, Delete, Char};
|
use KeyCode::{Up, Down, Delete, Char};
|
||||||
use PoolCommand as Cmd;
|
use PoolCommand as Cmd;
|
||||||
|
|
@ -261,46 +346,17 @@ fn to_clips_command (state: &MidiPool, input: &Event) -> Option<PoolCommand> {
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
command!(|self: FileBrowserCommand, state: MidiPool|{
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
use PoolMode::*;
|
|
||||||
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;
|
|
||||||
PoolClipCommand::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: |state: MidiPool, input: Event|{
|
input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{
|
||||||
use FileBrowserCommand::*;
|
use FileBrowserCommand::*;
|
||||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
||||||
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
||||||
match input {
|
match input {
|
||||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1))),
|
||||||
.min(browser.len().saturating_sub(1))),
|
kpat!(Down) => Select(browser.index.saturating_add(1)% browser.len()),
|
||||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||||
% browser.len()),
|
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Enter) => Confirm,
|
kpat!(Enter) => Confirm,
|
||||||
kpat!(Char(_)) => { todo!() },
|
kpat!(Char(_)) => { todo!() },
|
||||||
kpat!(Backspace) => { todo!() },
|
kpat!(Backspace) => { todo!() },
|
||||||
|
|
@ -309,12 +365,11 @@ input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{
|
||||||
}
|
}
|
||||||
} else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
|
} else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
|
||||||
match input {
|
match input {
|
||||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())),
|
||||||
.min(browser.len())),
|
kpat!(Down) => Select(browser.index.saturating_add(1) % browser.len()),
|
||||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||||
% browser.len()),
|
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Enter) => Confirm,
|
kpat!(Enter) => Confirm,
|
||||||
kpat!(Char(_)) => { todo!() },
|
kpat!(Char(_)) => { todo!() },
|
||||||
kpat!(Backspace) => { todo!() },
|
kpat!(Backspace) => { todo!() },
|
||||||
|
|
@ -325,42 +380,9 @@ input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
command!(|self: ClipLengthCommand,state:MidiPool|{
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
use ClipLengthCommand::*;
|
|
||||||
use ClipLengthFocus::*;
|
|
||||||
match state.clips_mode_mut().clone() {
|
|
||||||
Some(PoolMode::Length(clip, ref mut length, ref mut focus)) => match self {
|
|
||||||
Cancel => { *state.clips_mode_mut() = None; },
|
|
||||||
Self::Prev => { focus.prev() },
|
|
||||||
Self::Next => { focus.next() },
|
|
||||||
Self::Inc => match focus {
|
|
||||||
Bar => { *length += 4 * PPQ },
|
|
||||||
Beat => { *length += PPQ },
|
|
||||||
Tick => { *length += 1 },
|
|
||||||
},
|
|
||||||
Self::Dec => match focus {
|
|
||||||
Bar => { *length = length.saturating_sub(4 * PPQ) },
|
|
||||||
Beat => { *length = length.saturating_sub(PPQ) },
|
|
||||||
Tick => { *length = length.saturating_sub(1) },
|
|
||||||
},
|
|
||||||
Self::Set(length) => {
|
|
||||||
let mut old_length = None;
|
|
||||||
{
|
|
||||||
let mut clip = state.clips()[clip].clone();//.write().unwrap();
|
|
||||||
old_length = Some(clip.read().unwrap().length);
|
|
||||||
clip.write().unwrap().length = length;
|
|
||||||
}
|
|
||||||
*state.clips_mode_mut() = None;
|
|
||||||
return Ok(old_length.map(Self::Set))
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
None
|
|
||||||
});
|
|
||||||
input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{
|
input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{
|
||||||
if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() {
|
if let Some(PoolMode::Length(_, length, _)) = state.mode() {
|
||||||
match input {
|
match input {
|
||||||
kpat!(Up) => Self::Inc,
|
kpat!(Up) => Self::Inc,
|
||||||
kpat!(Down) => Self::Dec,
|
kpat!(Down) => Self::Dec,
|
||||||
|
|
@ -374,10 +396,11 @@ input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
impl InputToCommand<Event, MidiPool> for ClipRenameCommand {
|
impl InputToCommand<Event, MidiPool> for ClipRenameCommand {
|
||||||
fn input_to_command (state: &MidiPool, input: &Event) -> Option<Self> {
|
fn input_to_command (state: &MidiPool, input: &Event) -> Option<Self> {
|
||||||
use KeyCode::{Char, Backspace, Enter, Esc};
|
use KeyCode::{Char, Backspace, Enter, Esc};
|
||||||
if let Some(PoolMode::Rename(_, ref old_name)) = state.clips_mode() {
|
if let Some(PoolMode::Rename(_, ref old_name)) = state.mode() {
|
||||||
Some(match input {
|
Some(match input {
|
||||||
kpat!(Char(c)) => {
|
kpat!(Char(c)) => {
|
||||||
let mut new_name = old_name.clone().to_string();
|
let mut new_name = old_name.clone().to_string();
|
||||||
|
|
@ -398,27 +421,4 @@ impl InputToCommand<Event, MidiPool> for ClipRenameCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Command<MidiPool> for ClipRenameCommand {
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
fn execute (self, state: &mut MidiPool) -> Perhaps<Self> {
|
|
||||||
use ClipRenameCommand::*;
|
|
||||||
match state.clips_mode_mut().clone() {
|
|
||||||
Some(PoolMode::Rename(clip, ref mut old_name)) => match self {
|
|
||||||
Set(s) => {
|
|
||||||
state.clips()[clip].write().unwrap().name = s;
|
|
||||||
return Ok(Some(Self::Set(old_name.clone().into())))
|
|
||||||
},
|
|
||||||
Confirm => {
|
|
||||||
let old_name = old_name.clone();
|
|
||||||
*state.clips_mode_mut() = None;
|
|
||||||
return Ok(Some(Self::Set(old_name)))
|
|
||||||
},
|
|
||||||
Cancel => {
|
|
||||||
state.clips()[clip].write().unwrap().name = old_name.clone().into();
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
0
midi/src/midi_pool_keys_clip.edn
Normal file
0
midi/src/midi_pool_keys_clip.edn
Normal file
0
midi/src/midi_pool_keys_file.edn
Normal file
0
midi/src/midi_pool_keys_file.edn
Normal file
0
midi/src/midi_pool_keys_length.edn
Normal file
0
midi/src/midi_pool_keys_length.edn
Normal file
0
midi/src/midi_pool_keys_rename.edn
Normal file
0
midi/src/midi_pool_keys_rename.edn
Normal file
|
|
@ -1,41 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
pub struct PoolView<'a>(pub bool, pub &'a MidiPool);
|
|
||||||
content!(TuiOut: |self: PoolView<'a>| {
|
|
||||||
let Self(compact, model) = self;
|
|
||||||
let MidiPool { clips, mode, .. } = self.1;
|
|
||||||
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
|
||||||
let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
|
|
||||||
let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
|
|
||||||
let iter = | |model.clips().clone().into_iter();
|
|
||||||
Tui::bg(Color::Reset, Fixed::y(clips.read().unwrap().len() as u16, on_bg(border(Map::new(iter, move|clip, i|{
|
|
||||||
let item_height = 1;
|
|
||||||
let item_offset = i as u16 * item_height;
|
|
||||||
let selected = i == model.clip_index();
|
|
||||||
let MidiClip { ref name, color, length, .. } = *clip.read().unwrap();
|
|
||||||
let bg = if selected { color.light.rgb } else { color.base.rgb };
|
|
||||||
let fg = color.lightest.rgb;
|
|
||||||
let name = if *compact { format!(" {i:>3}") } else { format!(" {i:>3} {name}") };
|
|
||||||
let length = if *compact { String::default() } else { format!("{length} ") };
|
|
||||||
Fixed::y(1, map_south(item_offset, item_height, Tui::bg(bg, lay!(
|
|
||||||
Fill::x(Align::w(Tui::fg(fg, Tui::bold(selected, name)))),
|
|
||||||
Fill::x(Align::e(Tui::fg(fg, Tui::bold(selected, length)))),
|
|
||||||
Fill::x(Align::w(When::new(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶"))))),
|
|
||||||
Fill::x(Align::e(When::new(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀"))))),
|
|
||||||
))))
|
|
||||||
})))))
|
|
||||||
});
|
|
||||||
content!(TuiOut: |self: ClipLength| {
|
|
||||||
let bars = ||self.bars_string();
|
|
||||||
let beats = ||self.beats_string();
|
|
||||||
let ticks = ||self.ticks_string();
|
|
||||||
match self.focus {
|
|
||||||
None =>
|
|
||||||
row!(" ", bars(), ".", beats(), ".", ticks()),
|
|
||||||
Some(ClipLengthFocus::Bar) =>
|
|
||||||
row!("[", bars(), "]", beats(), ".", ticks()),
|
|
||||||
Some(ClipLengthFocus::Beat) =>
|
|
||||||
row!(" ", bars(), "[", beats(), "]", ticks()),
|
|
||||||
Some(ClipLengthFocus::Tick) =>
|
|
||||||
row!(" ", bars(), ".", beats(), "[", ticks()),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue