tek/crates/app/src/api.rs

748 lines
26 KiB
Rust

use crate::*;
use std::path::PathBuf;
type MaybeClip = Option<Arc<RwLock<MidiClip>>>;
macro_rules! ns { ($C:ty, $s:expr, $a:expr, $W:expr) => { <$C>::try_from_expr($s, $a).map($W) } }
macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; }
macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.command(self, input) {
let undo = command.execute(self)?;
if let Some(undo) = undo {
self.history.push(undo);
}
Some(true)
} else {
None
}));
#[tengri_proc::expose] impl App {
fn _todo_isize_stub (&self) -> isize {
todo!()
}
fn _todo_item_theme_stub (&self) -> ItemTheme {
todo!()
}
fn focus_editor (&self) -> bool {
self.is_editing()
}
fn focus_message (&self) -> bool {
matches!(self.dialog, Some(Dialog::Message(..)))
}
fn focus_device_add (&self) -> bool {
matches!(self.dialog, Some(Dialog::Device(..)))
}
fn focus_clip (&self) -> bool {
!self.is_editing() && self.selected.is_clip()
}
fn focus_track (&self) -> bool {
!self.is_editing() && self.selected.is_track()
}
fn focus_scene (&self) -> bool {
!self.is_editing() && self.selected.is_scene()
}
fn focus_mix (&self) -> bool {
!self.is_editing() && self.selected.is_mix()
}
fn focus_pool_import (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Import(..)))
}
fn focus_pool_export (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Export(..)))
}
fn focus_pool_rename (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Rename(..)))
}
fn focus_pool_length (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Length(..)))
}
fn editor_pitch (&self) -> Option<u7> {
Some((self.editor().map(|e|e.get_note_pos()).unwrap() as u8).into())
}
/// Width of display
pub(crate) fn w (&self) -> u16 {
self.size.w() as u16
}
/// Width allocated for sidebar.
pub(crate) fn w_sidebar (&self) -> u16 {
self.w() / if self.is_editing() { 16 } else { 8 } as u16
}
/// Width taken by all tracks.
pub(crate) fn w_tracks (&self) -> u16 {
self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
}
/// Width available to display tracks.
pub(crate) fn w_tracks_area (&self) -> u16 {
self.w().saturating_sub(2 * self.w_sidebar())
}
/// Height of display
pub(crate) fn h (&self) -> u16 {
self.size.h() as u16
}
/// Height available to display track headers.
pub(crate) fn h_tracks_area (&self) -> u16 {
5 // FIXME
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
}
/// Height available to display tracks.
pub(crate) fn h_scenes_area (&self) -> u16 {
//15
self.h().saturating_sub(
self.h_inputs() +
self.h_outputs() +
self.h_devices() +
13 // FIXME
)
}
/// Height taken by all scenes.
pub(crate) fn h_scenes (&self) -> u16 {
self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last()
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all inputs.
pub(crate) fn h_inputs (&self) -> u16 {
self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all outputs.
pub(crate) fn h_outputs (&self) -> u16 {
self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by visible device slots.
pub(crate) fn h_devices (&self) -> u16 {
2
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
fn scene_count (&self) -> usize {
self.scenes.len()
}
fn scene_selected (&self) -> Option<usize> {
self.selected.scene()
}
fn track_count (&self) -> usize {
self.tracks.len()
}
fn track_selected (&self) -> Option<usize> {
self.selected.track()
}
fn select_scene_next (&self) -> Selection {
self.selected.scene_next(self.scenes.len())
}
fn select_scene_prev (&self) -> Selection {
self.selected.scene_prev()
}
fn select_track_header (&self) -> Selection {
self.selected.track_header(self.tracks.len())
}
fn select_track_next (&self) -> Selection {
self.selected.track_next(self.tracks.len())
}
fn select_track_prev (&self) -> Selection {
self.selected.track_prev()
}
fn clip_selected (&self) -> Option<Arc<RwLock<MidiClip>>> {
match self.selected {
Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(),
_ => None
}
}
fn device_kind (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
index
} else {
0
}
}
fn device_kind_prev (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1))
} else {
0
}
}
fn device_kind_next (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
(index + 1) % self.device_kinds().len()
} else {
0
}
}
}
#[tengri_proc::expose] impl MidiPool {
fn _todo_bool_stub (&self) -> bool {
todo!()
}
fn _todo_path_buf_stub (&self) -> PathBuf {
todo!()
}
fn _todo_arc_str_stub (&self) -> Arc<str> {
todo!()
}
fn clip_new (&self) -> MidiClip {
self.new_clip()
}
fn clip_cloned (&self) -> MidiClip {
self.cloned_clip()
}
fn clip_index_current (&self) -> usize {
0
}
fn clip_index_after (&self) -> usize {
0
}
fn clip_index_previous (&self) -> usize {
0
}
fn clip_index_next (&self) -> usize {
0
}
fn color_random (&self) -> ItemColor {
ItemColor::random()
}
}
#[tengri_proc::command(App)] impl AppCommand {
fn toggle_help (app: &mut App, value: bool) -> Perhaps<Self> {
app.toggle_dialog(Some(Dialog::Help));
Ok(None)
}
fn toggle_menu (app: &mut App, value: bool) -> Perhaps<Self> {
app.toggle_dialog(Some(Dialog::Menu));
Ok(None)
}
fn toggle_edit (app: &mut App, value: bool) -> Perhaps<Self> {
app.toggle_editor(Some(value));
Ok(None)
}
fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
}
fn enqueue (app: &mut App, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
todo!()
}
fn history (app: &mut App, delta: isize) -> Perhaps<Self> {
todo!()
}
fn zoom (app: &mut App, zoom: usize) -> Perhaps<Self> {
todo!()
}
fn launch (app: &mut App) -> Perhaps<Self> {
app.launch();
Ok(None)
}
fn select (app: &mut App, selection: Selection) -> Perhaps<Self> {
app.select(selection);
Ok(None)
//("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
//(0, 0) => Self::Select(Selection::Mix),
//(t, 0) => Self::Select(Selection::Track(t)),
//(0, s) => Self::Select(Selection::Scene(s)),
//(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) })))
}
fn stop_all (app: &mut App) -> Perhaps<Self> {
app.stop_all();
Ok(None)
}
fn sampler (app: &mut App, command: SamplerCommand) -> Perhaps<Self> {
Ok(app.sampler_mut()
.map(|s|command.delegate(s, |command|Self::Sampler{command}))
.transpose()?
.flatten())
}
fn scene (app: &mut App, command: SceneCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Scene{command})?)
}
fn track (app: &mut App, command: TrackCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Track{command})?)
}
fn input (app: &mut App, command: InputCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Input{command})?)
}
fn output (app: &mut App, command: OutputCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Output{command})?)
}
fn clip (app: &mut App, command: ClipCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Clip{command})?)
}
fn clock (app: &mut App, command: ClockCommand) -> Perhaps<Self> {
Ok(command.execute(&mut app.clock)?.map(|command|Self::Clock{command}))
}
fn device (app: &mut App, command: DeviceCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Device{command})?)
}
fn message (app: &mut App, command: MessageCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Message{command})?)
}
fn editor (app: &mut App, command: MidiEditCommand) -> Perhaps<Self> {
Ok(if let Some(editor) = app.editor.as_mut() {
let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?;
// update linked sampler after editor action
app.sampler_mut().map(|sampler|match command {
// autoselect: automatically select sample in sampler
MidiEditCommand::NotePos { pos } => { sampler.set_note_pos(pos); },
_ => {}
});
undo
} else {
None
})
}
fn pool (app: &mut App, command: PoolCommand) -> Perhaps<Self> {
Ok(if let Some(pool) = app.pool.as_mut() {
let undo = command.clone().delegate(pool, |command|AppCommand::Pool{command})?;
// update linked editor after pool action
app.editor.as_mut().map(|editor|match command {
// autoselect: automatically load selected clip in editor
PoolCommand::Select { .. } |
// autocolor: update color in all places simultaneously
PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } =>
editor.set_clip(pool.clip().as_ref()),
_ => {}
});
undo
} else {
None
})
}
}
impl<'state> Context<'state, ClockCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<ClockCommand> {
Context::get(&self.clock, iter)
}
}
impl<'state> Context<'state, MidiEditCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<MidiEditCommand> {
self.editor().map(|e|Context::get(e, iter)).flatten()
}
}
impl<'state> Context<'state, PoolCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<PoolCommand> {
self.pool().map(|p|Context::get(p, iter)).flatten()
}
}
impl<'state> Context<'state, SamplerCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<SamplerCommand> {
self.sampler().map(|p|Context::get(p, iter)).flatten()
}
}
#[tengri_proc::command(App)] impl InputCommand {
fn add (app: &mut App) -> Perhaps<Self> {
app.midi_in_add()?;
Ok(None)
}
}
#[tengri_proc::command(App)] impl OutputCommand {
fn add (app: &mut App) -> Perhaps<Self> {
app.midi_out_add()?;
Ok(None)
}
}
#[tengri_proc::command(App)] impl DeviceCommand {
fn picker (app: &mut App) -> Perhaps<Self> {
app.device_picker_show();
Ok(None)
}
fn pick (app: &mut App, i: usize) -> Perhaps<Self> {
app.device_pick(i);
Ok(None)
}
fn add (app: &mut App, i: usize) -> Perhaps<Self> {
app.device_add(i);
Ok(None)
}
}
#[tengri_proc::command(App)] impl MessageCommand {
fn dismiss (app: &mut App) -> Perhaps<Self> {
app.message_dismiss();
Ok(None)
}
}
#[tengri_proc::command(App)] impl TrackCommand {
fn toggle_play (app: &mut App) -> Perhaps<Self> {
todo!()
}
fn toggle_solo (app: &mut App) -> Perhaps<Self> {
todo!()
}
fn toggle_rec (app: &mut App) -> Perhaps<Self> {
app.track_toggle_record();
Ok(Some(Self::ToggleRec))
}
fn toggle_mon (app: &mut App) -> Perhaps<Self> {
app.track_toggle_monitor();
Ok(Some(Self::ToggleMon))
}
fn set_size (app: &mut App, size: usize) -> Perhaps<Self> {
todo!()
}
fn set_zoom (app: &mut App, zoom: usize) -> Perhaps<Self> {
todo!()
}
fn swap (app: &mut App, index: usize, other: usize) -> Perhaps<Self> {
todo!();
Ok(Some(Self::Swap { index, other }))
}
fn del (app: &mut App, index: usize) -> Perhaps<Self> {
app.track_del(index);
Ok(None)
}
fn stop (app: &mut App, index: usize) -> Perhaps<Self> {
app.tracks[index].sequencer.enqueue_next(None);
Ok(None)
}
fn add (app: &mut App) -> Perhaps<Self> {
Ok(Some(Self::Del { index: app.track_add_focus()? }))
}
fn set_color (app: &mut App, index: usize, color: ItemTheme) -> Perhaps<Self> {
Ok(Some(Self::SetColor { index, color: app.track_set_color(index, color) }))
}
}
#[tengri_proc::command(App)] impl SceneCommand {
fn add (app: &mut App) -> Perhaps<Self> {
todo!()
}
fn del (app: &mut App, index: usize) -> Perhaps<Self> {
app.scene_del(index);
Ok(None)
}
fn enqueue (app: &mut App, index: usize) -> Perhaps<Self> {
app.scene_enqueue(index);
Ok(None)
}
fn set_color (app: &mut App, index: usize, color: ItemTheme) -> Perhaps<Self> {
Ok(Some(Self::SetColor { index, color: app.scene_set_color(index, color) }))
}
fn set_size (app: &mut App, index: usize, size: usize) -> Perhaps<Self> {
todo!()
}
fn set_zoom (app: &mut App, index: usize, zoom: usize) -> Perhaps<Self> {
todo!()
}
fn swap (app: &mut App, index: usize, other: usize) -> Perhaps<Self> {
todo!();
Ok(Some(Self::Swap { index, other }))
}
}
#[tengri_proc::command(App)] impl ClipCommand {
fn get (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}"))
//("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
todo!()
}
fn edit (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}"))
//("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
todo!()
}
fn set_loop (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
todo!()
}
fn put (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//(Put [t: usize, s: usize, c: MaybeClip]
//Some(Self::Put(t, s, app.clip_put(t, s, c))))
//("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
todo!()
}
fn del (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None))))
todo!()
}
fn enqueue (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//(Enqueue [t: usize, s: usize]
//cmd!(app.tracks[t].sequencer.enqueue_next(app.scenes[s].clips[t].as_ref())))
//("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
todo!()
}
fn set_color (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//(SetColor [t: usize, s: usize, c: ItemTheme]
//app.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
todo!()
}
}
#[tengri_proc::command(MidiPool)] impl PoolCommand {
/// Toggle visibility of pool
fn show (pool: &mut MidiPool, visible: bool) -> Perhaps<Self> {
pool.visible = visible;
Ok(Some(Self::Show { visible: !visible }))
}
/// Select a clip from the clip pool
fn select (pool: &mut MidiPool, index: usize) -> Perhaps<Self> {
pool.set_clip_index(index);
Ok(None)
}
/// Rename a clip
fn rename (pool: &mut MidiPool, command: ClipRenameCommand) -> Perhaps<Self> {
Ok(match command {
ClipRenameCommand::Begin => {
pool.begin_clip_rename();
None
},
_ => command.delegate(pool, |command|Self::Rename{command})?
})
}
/// Change the length of a clip
fn length (pool: &mut MidiPool, command: ClipLengthCommand) -> Perhaps<Self> {
Ok(match command {
ClipLengthCommand::Begin => {
pool.begin_clip_length();
None
},
_ => command.delegate(pool, |command|Self::Length{command})?
})
}
/// Import from file
fn import (pool: &mut MidiPool, command: FileBrowserCommand) -> Perhaps<Self> {
Ok(match command {
FileBrowserCommand::Begin => {
pool.begin_import();
None
},
_ => command.delegate(pool, |command|Self::Import{command})?
})
}
/// Export to file
fn export (pool: &mut MidiPool, command: FileBrowserCommand) -> Perhaps<Self> {
Ok(match command {
FileBrowserCommand::Begin => {
pool.begin_export();
None
},
_ => command.delegate(pool, |command|Self::Export{command})?
})
}
/// Update the contents of the clip pool
fn clip (pool: &mut MidiPool, command: PoolClipCommand) -> Perhaps<Self> {
Ok(command.execute(pool)?.map(|command|Self::Clip{command}))
}
}
#[tengri_proc::command(MidiPool)] impl PoolClipCommand {
fn add (pool: &mut MidiPool, index: usize, clip: MidiClip) -> Perhaps<Self> {
let mut index = index;
let clip = Arc::new(RwLock::new(clip));
let mut clips = pool.clips_mut();
if index >= clips.len() {
index = clips.len();
clips.push(clip)
} else {
clips.insert(index, clip);
}
Ok(Some(Self::Delete { index }))
}
fn delete (pool: &mut MidiPool, index: usize) -> Perhaps<Self> {
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
Ok(Some(Self::Add { index, clip }))
}
fn swap (pool: &mut MidiPool, index: usize, other: usize) -> Perhaps<Self> {
pool.clips_mut().swap(index, other);
Ok(Some(Self::Swap { index, other }))
}
fn import (pool: &mut MidiPool, index: usize, path: PathBuf) -> Perhaps<Self> {
let bytes = std::fs::read(&path)?;
let smf = Smf::parse(bytes.as_slice())?;
let mut t = 0u32;
let mut events = vec![];
for track in smf.tracks.iter() {
for event in track.iter() {
t += event.delta.as_int();
if let TrackEventKind::Midi { channel, message } = event.kind {
events.push((t, channel.as_int(), message));
}
}
}
let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None);
for event in events.iter() {
clip.notes[event.0 as usize].push(event.2);
}
Ok(Self::Add { index, clip }.execute(pool)?)
}
fn export (pool: &mut MidiPool, index: usize, path: PathBuf) -> Perhaps<Self> {
todo!("export clip to midi file");
}
fn set_name (pool: &mut MidiPool, index: usize, name: Arc<str>) -> Perhaps<Self> {
let clip = &mut pool.clips_mut()[index];
let old_name = clip.read().unwrap().name.clone();
clip.write().unwrap().name = name;
Ok(Some(Self::SetName { index, name: old_name }))
}
fn set_length (pool: &mut MidiPool, index: usize, length: usize) -> Perhaps<Self> {
let clip = &mut pool.clips_mut()[index];
let old_len = clip.read().unwrap().length;
clip.write().unwrap().length = length;
Ok(Some(Self::SetLength { index, length: old_len }))
}
fn set_color (pool: &mut MidiPool, index: usize, color: ItemColor) -> Perhaps<Self> {
let mut color = ItemTheme::from(color);
std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color);
Ok(Some(Self::SetColor { index, color: color.base }))
}
}
#[tengri_proc::command(MidiPool)] impl ClipRenameCommand {
fn begin (pool: &mut MidiPool) -> Perhaps<Self> {
unreachable!();
}
fn cancel (pool: &mut MidiPool) -> Perhaps<Self> {
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
pool.clips()[clip].write().unwrap().name = old_name.clone().into();
}
return Ok(None)
}
fn confirm (pool: &mut MidiPool) -> Perhaps<Self> {
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
let old_name = old_name.clone();
*pool.mode_mut() = None;
return Ok(Some(Self::Set { value: old_name }))
}
return Ok(None)
}
fn set (pool: &mut MidiPool, value: Arc<str>) -> Perhaps<Self> {
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
pool.clips()[clip].write().unwrap().name = value;
}
return Ok(None)
}
}
#[tengri_proc::command(MidiPool)] impl ClipLengthCommand {
fn begin (pool: &mut MidiPool) -> Perhaps<Self> {
unreachable!()
}
fn cancel (pool: &mut MidiPool) -> Perhaps<Self> {
if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() {
*pool.mode_mut() = None;
}
Ok(None)
}
fn set (pool: &mut MidiPool, length: usize) -> Perhaps<Self> {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
let old_length;
{
let clip = pool.clips()[clip].clone();//.write().unwrap();
old_length = Some(clip.read().unwrap().length);
clip.write().unwrap().length = *length;
}
*pool.mode_mut() = None;
return Ok(old_length.map(|length|Self::Set { length }))
}
Ok(None)
}
fn next (pool: &mut MidiPool) -> Perhaps<Self> {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
focus.next()
}
Ok(None)
}
fn prev (pool: &mut MidiPool) -> Perhaps<Self> {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
focus.prev()
}
Ok(None)
}
fn inc (pool: &mut MidiPool) -> Perhaps<Self> {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
match focus {
ClipLengthFocus::Bar => { *length += 4 * PPQ },
ClipLengthFocus::Beat => { *length += PPQ },
ClipLengthFocus::Tick => { *length += 1 },
}
}
Ok(None)
}
fn dec (pool: &mut MidiPool) -> Perhaps<Self> {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
match focus {
ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) },
ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) },
ClipLengthFocus::Tick => { *length = length.saturating_sub(1) },
}
}
Ok(None)
}
}
#[tengri_proc::command(MidiPool)] impl FileBrowserCommand {
fn begin (pool: &mut MidiPool) -> Perhaps<Self> {
unreachable!();
}
fn cancel (pool: &mut MidiPool) -> Perhaps<Self> {
pool.mode = None;
Ok(None)
}
fn confirm (pool: &mut MidiPool) -> Perhaps<Self> {
Ok(match pool.mode {
Some(PoolMode::Import(index, ref mut browser)) => {
if browser.is_file() {
let path = browser.path();
pool.mode = None;
let _undo = PoolClipCommand::import(pool, index, path)?;
None
} else if browser.is_dir() {
pool.mode = Some(PoolMode::Import(index, browser.chdir()?));
None
} else {
None
}
},
Some(PoolMode::Export(index, ref mut browser)) => {
todo!()
},
_ => unreachable!(),
})
}
fn select (pool: &mut MidiPool, index: usize) -> Perhaps<Self> {
Ok(match pool.mode {
Some(PoolMode::Import(index, ref mut browser)) => {
browser.index = index;
None
},
Some(PoolMode::Export(index, ref mut browser)) => {
browser.index = index;
None
},
_ => unreachable!(),
})
}
fn chdir (pool: &mut MidiPool, dir: PathBuf) -> Perhaps<Self> {
Ok(match pool.mode {
Some(PoolMode::Import(index, ref mut browser)) => {
pool.mode = Some(PoolMode::Import(index, FileBrowser::new(Some(dir))?));
None
},
Some(PoolMode::Export(index, ref mut browser)) => {
pool.mode = Some(PoolMode::Export(index, FileBrowser::new(Some(dir))?));
None
},
_ => unreachable!(),
})
}
fn filter (pool: &mut MidiPool, filter: Arc<str>) -> Perhaps<Self> {
todo!()
}
}