wip: implementing pool command dispatch

This commit is contained in:
🪞👃🪞 2025-01-14 18:11:50 +01:00
parent 50b7d8a23d
commit d393cab2d8
8 changed files with 270 additions and 243 deletions

View file

@ -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::*;

View file

@ -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()),
}
});

View file

@ -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)
}
}

View file

View file

View file

View file

View 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()),
}
});