diff --git a/src/app.rs b/src/app.rs index ae9bcf87..77158692 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,12 @@ use crate::*; -pub mod audio; pub use self::audio::*; +pub mod audio; #[allow(unused)] pub use self::audio::*; pub mod bind; pub use self::bind::*; pub mod cli; pub use self::cli::*; pub mod config; pub use self::config::*; pub mod view; pub use self::view::*; pub mod draw; pub use self::draw::*; pub mod mode; pub use self::mode::*; +pub mod size; pub use self::size::*; /// Total application state. /// @@ -81,26 +82,6 @@ impl App { } } - pub fn new_shared ( - jack: &Jack<'static>, project: Arrangement, config: Config, mode: impl AsRef - ) -> Arc> { - Arc::new(RwLock::new(App::new(jack, project, config, ":menu"))) - } - - pub fn new_shared_run ( - exit: &Exit, - output: W, - jack: &Jack<'static>, - project: Arrangement, - config: Config, - mode: impl AsRef - ) -> Usually<(Arc>, Task, Task)> { - let state = Self::new_shared(&jack, project, config, mode); - let keyboard = tui_input(exit.as_ref(), &state, Duration::from_millis(100))?; - let terminal = tui_output(exit.as_ref(), &state, Duration::from_millis(10), output)?; - Ok((state, keyboard, terminal)) - } - pub fn confirm (&mut self) -> Perhaps { Ok(match &self.dialog { Dialog::Menu(index, items) => { @@ -250,65 +231,24 @@ impl App { } } -/// The [Draw] implementation for [App] handles the loaded view, -/// which is defined in terms of [dizzle] DSL. -/// -/// If there is an error, the error is displayed. FIXME: overlay it -/// Then, every top-level form of the DSL description is rendered. -impl Draw for App { - fn draw (self, to: &mut Tui) -> Usually> { - let xywh = to.area().into(); - if let Some(e) = self.error.read().unwrap().as_ref() { - to.show(xywh, e.as_ref()); - } - for (index, dsl) in self.mode.view.iter().enumerate() { - if let Err(e) = self.interpret(to, dsl) { - *self.error.write().unwrap() = Some(format!("view #{index}: {e}").into()); - break; - } - } - Ok(xywh) - } -} - -impl HasClipsSize for App { - fn clips_size (&self) -> &Size { &self.project.size_inner } -} - -impl HasJack<'static> for App { - fn jack (&self) -> &Jack<'static> { &self.jack } -} - -def_command!(AppCommand: |app: App| { - Nop => Ok(None), - Cancel => todo!(), // TODO delegate: - Confirm => app.confirm(), - Inc { axis: ControlAxis } => app.inc(axis), - Dec { axis: ControlAxis } => app.dec(axis), - SetDialog { dialog: Dialog } => { - swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }) - }, -}); - -impl_default!(AppCommand: Self::Nop); - impl_has!(Clock: |self: App|self.project.clock); impl_has!(Vec: |self: App|self.project.midi_ins); impl_has!(Vec: |self: App|self.project.midi_outs); impl_has!(Dialog: |self: App|self.dialog); impl_has!(Jack<'static>: |self: App|self.jack); -impl_has!(Size: |self: App|self.size); impl_has!(Pool: |self: App|self.pool); impl_has!(Selection: |self: App|self.project.selection); - impl_as_ref!(Vec: |self: App|self.project.as_ref()); impl_as_mut!(Vec: |self: App|self.project.as_mut()); - impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt()); impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt()); - impl_has_clips!( |self: App|self.pool.clips); +primitive!(u8: try_to_u8); +primitive!(u16: try_to_u16); +primitive!(usize: try_to_usize); +primitive!(isize: try_to_isize); + namespace!(App: Arc { literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); }); namespace!(App: u8 { literal = |dsl|try_to_u8(dsl); }); @@ -374,83 +314,52 @@ namespace!(App: Option { symbol = |app| { ":selected/track" => app.selection().track(), }; }); -namespace!(App: Option>> { - symbol = |app| { - ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { - app.scenes()[*scene].clips[*track].clone() - } else { - None - } - }; -}); +namespace!(App: Option>> { symbol = |app| { + ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { + app.scenes()[*scene].clips[*track].clone() + } else { + None + } +}; }); -impl<'a> Namespace<'a, AppCommand> for App { - symbols!('a |app| -> AppCommand { - "x/inc" => AppCommand::Inc { axis: ControlAxis::X }, - "x/dec" => AppCommand::Dec { axis: ControlAxis::X }, - "y/inc" => AppCommand::Inc { axis: ControlAxis::Y }, - "y/dec" => AppCommand::Dec { axis: ControlAxis::Y }, - "confirm" => AppCommand::Confirm, - "cancel" => AppCommand::Cancel, - }); -} - -impl HasTrackScroll for App { - fn track_scroll (&self) -> usize { - self.project.track_scroll() +pub fn swap_value ( + target: &mut T, value: &T, returned: impl Fn(T)->U +) -> Perhaps { + if *target == *value { + Ok(None) + } else { + let mut value = value.clone(); + std::mem::swap(target, &mut value); + Ok(Some(returned(value))) } } -impl HasSceneScroll for App { - fn scene_scroll (&self) -> usize { - self.project.scene_scroll() +pub fn toggle_bool ( + target: &mut bool, value: &Option, returned: impl Fn(Option)->U +) -> Perhaps { + let mut value = value.unwrap_or(!*target); + if value == *target { + Ok(None) + } else { + std::mem::swap(target, &mut value); + Ok(Some(returned(Some(value)))) } } -impl ScenesView for App { - fn w_mid (&self) -> u16 { - (self.size.w() as u16).saturating_sub(self.w_side()) - } - fn w_side (&self) -> u16 { - 20 - } - fn h_scenes (&self) -> u16 { - (self.size.h() as u16).saturating_sub(20) - } +pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { + let (mut subdirs, mut files) = std::fs::read_dir(dir)? + .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ + let entry = entry.expect("failed to read drectory entry"); + let meta = entry.metadata().expect("failed to read entry metadata"); + if meta.is_file() { + files.push(entry.file_name()); + } else if meta.is_dir() { + subdirs.push(entry.file_name()); + } + (subdirs, files) + }); + subdirs.sort(); + files.sort(); + Ok((subdirs, files)) } -//impl_handle!(TuiIn: |self: App, input|{ - //let commands = tek_collect_commands(self, input)?; - //let history = tek_execute_commands(self, commands)?; - //self.history.extend(history.into_iter()); - //Ok(None) -//}); - -//fn tek_collect_commands (app: &App, input: &TuiIn) -> Usually> { - //let mut commands = vec![]; - //for id in app.mode.keys.iter() { - //if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) - //&& let Some(bindings) = event_map.query(input.event()) { - //for binding in bindings { - //for command in binding.commands.iter() { - //if let Some(command) = app.namespace(command)? as Option { - //commands.push(command) - //} - //} - //} - //} - //} - //Ok(commands) -//} - -//fn tek_execute_commands ( - //app: &mut App, commands: Vec -//) -> Usually)>> { - //let mut history = vec![]; - //for command in commands.into_iter() { - //let result = command.act(app); - //match result { Err(err) => { history.push((command, None)); return Err(err) } - //Ok(undo) => { history.push((command, undo)); } }; - //} - //Ok(history) -//} diff --git a/src/app/audio.rs b/src/app/audio.rs index c5eee668..e224068e 100644 --- a/src/app/audio.rs +++ b/src/app/audio.rs @@ -1,5 +1,7 @@ use crate::*; +impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } } + impl_audio!(App: tek_jack_process, tek_jack_event); fn tek_jack_process (state: &mut App, client: &Client, scope: &ProcessScope) -> Control { diff --git a/src/app/bind.rs b/src/app/bind.rs index b8ae47f3..2f683fcc 100644 --- a/src/app/bind.rs +++ b/src/app/bind.rs @@ -1,4 +1,9 @@ use crate::*; + +tui_keys!(|self: App, input| { + todo!() +}); + /// A control axis. /// /// ``` @@ -128,3 +133,63 @@ impl Bind { } impl_debug!(Condition |self, w| { write!(w, "*") }); + +impl_default!(AppCommand: Self::Nop); + +def_command!(AppCommand: |app: App| { + Nop => Ok(None), + Cancel => todo!(), // TODO delegate: + Confirm => app.confirm(), + Inc { axis: ControlAxis } => app.inc(axis), + Dec { axis: ControlAxis } => app.dec(axis), + SetDialog { dialog: Dialog } => { + swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }) + }, +}); + +impl<'a> Namespace<'a, AppCommand> for App { + symbols!('a |app| -> AppCommand { + "x/inc" => AppCommand::Inc { axis: ControlAxis::X }, + "x/dec" => AppCommand::Dec { axis: ControlAxis::X }, + "y/inc" => AppCommand::Inc { axis: ControlAxis::Y }, + "y/dec" => AppCommand::Dec { axis: ControlAxis::Y }, + "confirm" => AppCommand::Confirm, + "cancel" => AppCommand::Cancel, + }); +} + +//impl_handle!(TuiIn: |self: App, input|{ + //let commands = tek_collect_commands(self, input)?; + //let history = tek_execute_commands(self, commands)?; + //self.history.extend(history.into_iter()); + //Ok(None) +//}); + +//fn tek_collect_commands (app: &App, input: &TuiIn) -> Usually> { + //let mut commands = vec![]; + //for id in app.mode.keys.iter() { + //if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) + //&& let Some(bindings) = event_map.query(input.event()) { + //for binding in bindings { + //for command in binding.commands.iter() { + //if let Some(command) = app.namespace(command)? as Option { + //commands.push(command) + //} + //} + //} + //} + //} + //Ok(commands) +//} + +//fn tek_execute_commands ( + //app: &mut App, commands: Vec +//) -> Usually)>> { + //let mut history = vec![]; + //for command in commands.into_iter() { + //let result = command.act(app); + //match result { Err(err) => { history.push((command, None)); return Err(err) } + //Ok(undo) => { history.push((command, undo)); } }; + //} + //Ok(history) +//} diff --git a/src/app/cli.rs b/src/app/cli.rs index 28c75e27..b0c0c0c7 100644 --- a/src/app/cli.rs +++ b/src/app/cli.rs @@ -109,7 +109,7 @@ impl Action { //return Ok(()) //} // Initialize the app state - let app = App::new_shared(&jack, proj, config, ":menu"); + let app = Arc::new(RwLock::new(App::new(&jack, proj, config, ":menu"))); //if matches!(self, Action::Headless) { //// TODO: Headless mode (daemon + client over IPC, then over network...) //println!("todo headless"); diff --git a/src/app/draw.rs b/src/app/draw.rs index dbe9d2b5..4fcb06d1 100644 --- a/src/app/draw.rs +++ b/src/app/draw.rs @@ -1,5 +1,26 @@ use crate::*; +/// The [Draw] implementation for [App] handles the loaded view, +/// which is defined in terms of [dizzle] DSL. +/// +/// If there is an error, the error is displayed. FIXME: overlay it +/// Then, every top-level form of the DSL description is rendered. +impl Draw for App { + fn draw (self, to: &mut Tui) -> Usually> { + let xywh = to.area().into(); + if let Some(e) = self.error.read().unwrap().as_ref() { + to.show(xywh, e.as_ref())?; + } + for (index, dsl) in self.mode.view.iter().enumerate() { + if let Err(e) = self.interpret(to, dsl) { + *self.error.write().unwrap() = Some(format!("view #{index}: {e}").into()); + break; + } + } + Ok(xywh) + } +} + impl Interpret> for App { fn interpret_expr <'a> (&'a self, to: &mut Tui, lang: &'a impl Expression) -> Usually> diff --git a/src/app/size.rs b/src/app/size.rs new file mode 100644 index 00000000..843328c8 --- /dev/null +++ b/src/app/size.rs @@ -0,0 +1,57 @@ +use crate::*; + +impl_has!(Size: |self: App|self.size); + +/// Define a type alias for iterators of sized items (columns). +macro_rules! def_sizes_iter { + ($Type:ident => $($Item:ty),+) => { + pub trait $Type<'a> = + Iterator + Send + Sync + 'a; + } +} + +def_sizes_iter!(InputsSizes => MidiInput); +def_sizes_iter!(OutputsSizes => MidiOutput); +def_sizes_iter!(PortsSizes => Arc, [Connect]); +def_sizes_iter!(ScenesSizes => Scene); +def_sizes_iter!(TracksSizes => Track); + +pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { + track.width as u16 +} + +pub trait HasWidth { + const MIN_WIDTH: usize; + /// Increment track width. + fn width_inc (&mut self); + /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. + fn width_dec (&mut self); +} + +impl HasClipsSize for App { + fn clips_size (&self) -> &Size { &self.project.size_inner } +} + +impl HasTrackScroll for App { + fn track_scroll (&self) -> usize { + self.project.track_scroll() + } +} + +impl HasSceneScroll for App { + fn scene_scroll (&self) -> usize { + self.project.scene_scroll() + } +} + +impl ScenesView for App { + fn w_mid (&self) -> u16 { + (self.size.w() as u16).saturating_sub(self.w_side()) + } + fn w_side (&self) -> u16 { + 20 + } + fn h_scenes (&self) -> u16 { + (self.size.h() as u16).saturating_sub(20) + } +} diff --git a/src/app/view.rs b/src/app/view.rs index 6b2690bb..27b9313a 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -1,5 +1,9 @@ use crate::*; +tui_view!(|self: App| { + "" +}); + /// Collection of custom view definitions. pub type Views = Arc, Arc>>>; diff --git a/src/device/audio.rs b/src/device/audio.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tek.rs b/src/tek.rs index 53336e9c..8535b8cc 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -98,87 +98,8 @@ pub fn main () -> Usually<()> { }).map(|_|()) } -tui_keys!(|self: App, input| { - todo!() -}); - -tui_view!(|self: App| { - "" -}); - -pub fn swap_value ( - target: &mut T, value: &T, returned: impl Fn(T)->U -) -> Perhaps { - if *target == *value { - Ok(None) - } else { - let mut value = value.clone(); - std::mem::swap(target, &mut value); - Ok(Some(returned(value))) - } -} - -pub fn toggle_bool ( - target: &mut bool, value: &Option, returned: impl Fn(Option)->U -) -> Perhaps { - let mut value = value.unwrap_or(!*target); - if value == *target { - Ok(None) - } else { - std::mem::swap(target, &mut value); - Ok(Some(returned(Some(value)))) - } -} - //take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { - let (mut subdirs, mut files) = std::fs::read_dir(dir)? - .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ - let entry = entry.expect("failed to read drectory entry"); - let meta = entry.metadata().expect("failed to read entry metadata"); - if meta.is_file() { - files.push(entry.file_name()); - } else if meta.is_dir() { - subdirs.push(entry.file_name()); - } - (subdirs, files) - }); - subdirs.sort(); - files.sort(); - Ok((subdirs, files)) -} - -pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { - track.width as u16 -} - -/// Define a type alias for iterators of sized items (columns). -macro_rules! def_sizes_iter { - ($Type:ident => $($Item:ty),+) => { - pub trait $Type<'a> = - Iterator + Send + Sync + 'a; - } -} - -primitive!(u8: try_to_u8); -primitive!(u16: try_to_u16); -primitive!(usize: try_to_usize); -primitive!(isize: try_to_isize); -pub trait HasWidth { - const MIN_WIDTH: usize; - /// Increment track width. - fn width_inc (&mut self); - /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. - fn width_dec (&mut self); -} - pub mod device; pub use self::device::*; pub mod app; pub use self::app::*; - -def_sizes_iter!(InputsSizes => MidiInput); -def_sizes_iter!(OutputsSizes => MidiOutput); -def_sizes_iter!(PortsSizes => Arc, [Connect]); -def_sizes_iter!(ScenesSizes => Scene); -def_sizes_iter!(TracksSizes => Track);