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_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_edit; pub use midi_edit::*;
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ impl MidiPool {
|
|||
pub(crate) fn set_clip_index (&self, value: usize) {
|
||||
self.clip.store(value, Relaxed);
|
||||
}
|
||||
pub(crate) fn clips_mode (&self) -> &Option<PoolMode> {
|
||||
pub(crate) fn mode (&self) -> &Option<PoolMode> {
|
||||
&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
|
||||
}
|
||||
pub fn file_picker (&self) -> Option<&FileBrowser> {
|
||||
|
|
@ -81,6 +81,35 @@ impl MidiPool {
|
|||
_ => 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.
|
||||
#[derive(Clone)]
|
||||
|
|
@ -135,3 +164,43 @@ impl ClipLengthFocus {
|
|||
*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 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!(usize: |self: MidiPool| {});
|
||||
edn_provide!(MidiClip: |self: MidiPool| {});
|
||||
|
|
@ -7,9 +34,8 @@ edn_provide!(PathBuf: |self: MidiPool| {});
|
|||
edn_provide!(Arc<str>: |self: MidiPool| {});
|
||||
edn_provide!(ItemColor: |self: MidiPool| {});
|
||||
#[derive(Clone, PartialEq, Debug)] pub enum PoolCommand {
|
||||
/// Toggle visibility of pool
|
||||
Show(bool),
|
||||
/// Update the contents of the clip pool
|
||||
Clip(PoolClipCommand),
|
||||
/// Select a clip from the clip pool
|
||||
Select(usize),
|
||||
/// Rename a clip
|
||||
|
|
@ -20,15 +46,33 @@ edn_provide!(ItemColor: |self: MidiPool| {});
|
|||
Import(FileBrowserCommand),
|
||||
/// Export to file
|
||||
Export(FileBrowserCommand),
|
||||
/// Update the contents of the clip pool
|
||||
Clip(PoolClipCommand),
|
||||
}
|
||||
edn_command!(PoolCommand: |state: MidiPool| {
|
||||
("show" [a: bool] Self::Show(a.expect("no flag")))
|
||||
("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)))
|
||||
("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)))
|
||||
("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 {
|
||||
Add(usize, MidiClip),
|
||||
|
|
@ -50,44 +94,6 @@ edn_command!(PoolClipCommand: |state: MidiPool| {
|
|||
("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")))
|
||||
});
|
||||
#[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 {
|
||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||
use PoolClipCommand::*;
|
||||
|
|
@ -153,65 +159,144 @@ impl<T: HasClips> Command<T> for PoolClipCommand {
|
|||
})
|
||||
}
|
||||
}
|
||||
command!(|self: PoolCommand, state: MidiPool|{
|
||||
use PoolCommand::*;
|
||||
#[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")))
|
||||
});
|
||||
command!(|self: ClipRenameCommand, state: MidiPool|{
|
||||
use ClipRenameCommand::*;
|
||||
if let Some(
|
||||
PoolMode::Rename(clip, ref mut old_name)
|
||||
) = state.mode_mut().clone() {
|
||||
match self {
|
||||
Show(visible) => {
|
||||
state.visible = visible;
|
||||
Some(Self::Show(!visible))
|
||||
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 {
|
||||
ClipRenameCommand::Begin => {
|
||||
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),
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
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::Length(..)) => Self::Length(ClipLengthCommand::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)?),
|
||||
_ => to_clips_command(state, input)?
|
||||
});
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
fn to_clips_command (state: &MidiPool, input: &Event) -> Option<PoolCommand> {
|
||||
use KeyCode::{Up, Down, Delete, Char};
|
||||
use PoolCommand as Cmd;
|
||||
|
|
@ -261,46 +346,17 @@ fn to_clips_command (state: &MidiPool, input: &Event) -> Option<PoolCommand> {
|
|||
_ => 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|{
|
||||
use FileBrowserCommand::*;
|
||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
||||
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
||||
match input {
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||
.min(browser.len().saturating_sub(1))),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||
% browser.len()),
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1))),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1)% browser.len()),
|
||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||
|
||||
kpat!(Enter) => Confirm,
|
||||
kpat!(Char(_)) => { 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 {
|
||||
match input {
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||
.min(browser.len())),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||
% browser.len()),
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1) % browser.len()),
|
||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||
|
||||
kpat!(Enter) => Confirm,
|
||||
kpat!(Char(_)) => { todo!() },
|
||||
kpat!(Backspace) => { todo!() },
|
||||
|
|
@ -325,42 +380,9 @@ input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{
|
|||
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|{
|
||||
if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() {
|
||||
if let Some(PoolMode::Length(_, length, _)) = state.mode() {
|
||||
match input {
|
||||
kpat!(Up) => Self::Inc,
|
||||
kpat!(Down) => Self::Dec,
|
||||
|
|
@ -374,10 +396,11 @@ input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{
|
|||
unreachable!()
|
||||
}
|
||||
});
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
impl InputToCommand<Event, MidiPool> for ClipRenameCommand {
|
||||
fn input_to_command (state: &MidiPool, input: &Event) -> Option<Self> {
|
||||
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 {
|
||||
kpat!(Char(c)) => {
|
||||
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