diff --git a/input/src/edn_command.rs b/input/src/edn_command.rs index 2c735ff3..f26e6dfd 100644 --- a/input/src/edn_command.rs +++ b/input/src/edn_command.rs @@ -2,21 +2,51 @@ use crate::*; use EdnItem::*; #[macro_export] macro_rules! edn_command { - ($Command:ty : |$state:ident:$State:ty| { - $(($key:literal [$($arg:ident : $type:ty),*] $command:expr))* - }) => { + ($Command:ty : |$state:ident:$State:ty| { $(( + // identifier + $key:literal [ + // named parameters + $( + // argument name + $arg:ident + // if type is not provided defaults to EdnItem + $( + // type:name separator + : + // argument type + $type:ty + )? + ),* + // rest of parameters + $(, ..$rest:ident)? + ] + // bound command: + $command:expr + ))* }) => { impl EdnCommand<$State> for $Command { fn from_edn <'a> ($state: &$State, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { - match (head, tail) { - $((EdnItem::Key($key), [$($arg),*]) => { - $(let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg);)* - $command - },)* - _ => panic!("no such command") - } + $(if let (EdnItem::Key($key), [ // if the identifier matches + // bind argument ids + $($arg),* + // bind rest parameters + $(, $rest @ ..)? + ]) = (head, tail) { + $( + $(let $arg: Option<$type> = EdnProvide::<$type>::get($state, $arg);)? + )* + //$(edn_command!(@bind $state => $arg $(?)? : $type);)* + return $command + })* + panic!("no such command") } } - } + }; + (@bind $state:ident =>$arg:ident ? : $type:ty) => { + let $arg: Option<$type> = EdnProvide::<$type>::get($state, $arg); + }; + (@bind $state:ident => $arg:ident : $type:ty) => { + let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg); + }; } /// Turns an EDN symbol sequence into a command enum variant. diff --git a/input/src/edn_provide.rs b/input/src/edn_provide.rs index 7af0d397..cf4b6963 100644 --- a/input/src/edn_provide.rs +++ b/input/src/edn_provide.rs @@ -23,3 +23,12 @@ pub trait EdnProvide { self.get(edn).expect("no value") } } + +impl, U> EdnProvide for &T { + fn get > (&self, edn: &EdnItem) -> Option { + (*self).get(edn) + } + fn get_or_fail > (&self, edn: &EdnItem) -> U { + (*self).get_or_fail(edn) + } +} diff --git a/midi/src/midi_edit_cmd.rs b/midi/src/midi_edit_cmd.rs index 39628e3d..9d023ee2 100644 --- a/midi/src/midi_edit_cmd.rs +++ b/midi/src/midi_edit_cmd.rs @@ -12,9 +12,13 @@ edn_provide!(usize: |self: MidiEditor|{ ":time-zoom" => self.time_zoom().get(), }); edn_command!(MidiEditCommand: |state: MidiEditor| { - ("note/put" [a: bool] Self::PutNote) - ("note/del" [a: bool] Self::PutNote) - ("note/dur" [a: usize] Self::SetNoteCursor(a)) + ("note/put" [a: bool] Self::PutNote) + ("note/del" [a: bool] Self::PutNote) + ("note/pos" [a: usize] Self::SetNoteCursor(a.expect("no note cursor"))) + ("note/len" [a: usize] Self::SetNoteLength(a.expect("no note length"))) + ("time/pos" [a: usize] Self::SetTimeCursor(a.expect("no time cursor"))) + ("time/zoom" [a: usize] Self::SetTimeZoom(a.expect("no time zoom"))) + ("time/lock" [a: bool] Self::SetTimeLock(a.expect("no time lock"))) }); //impl EdnCommand for MidiEditCommand { //fn from_edn <'a> (state: &MidiEditor, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { diff --git a/output/src/edn_view.rs b/output/src/edn_view.rs index bc173a9d..cabc65eb 100644 --- a/output/src/edn_view.rs +++ b/output/src/edn_view.rs @@ -78,10 +78,12 @@ fn match_exp <'a, E: Output + 'a, State: EdnViewData> ( ) -> Box + 'a> { match (head, tail) { - (Key("when"), [c, a]) => When(s.get_bool(c.to_ref()), + (Key("when"), [c, a]) => When( + s.get_bool(c.to_ref()), s.get_content(a.to_ref())).boxed(), - (Key("either"),[c, a, b]) => Either(s.get_bool(c.to_ref()), + (Key("either"),[c, a, b]) => Either( + s.get_bool(c.to_ref()), s.get_content(a.to_ref()), s.get_content(b.to_ref())).boxed(), diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index 410aed32..b83736a6 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -1,7 +1,6 @@ use crate::*; use EdnItem::*; use ClockCommand::{Play, Pause}; -use self::ArrangerCommand as Cmd; pub const TRACK_MIN_WIDTH: usize = 9; command!(|self: ClipCommand, state: App|match self { _ => todo!("clip command") }); command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") }); diff --git a/tek/src/control.rs b/tek/src/control.rs index 10f077c8..73ab4a4c 100644 --- a/tek/src/control.rs +++ b/tek/src/control.rs @@ -2,24 +2,16 @@ use crate::*; use EdnItem::*; use ClockCommand::{Play, Pause}; use KeyCode::{Tab, Char}; -use SequencerCommand as SeqCmd; -use GrooveboxCommand as GrvCmd; -use ArrangerCommand as ArrCmd; use SamplerCommand as SmplCmd; use MidiEditCommand as EditCmd; use PoolClipCommand as PoolCmd; - handle!(TuiIn: |self: App, input| Ok(None)); -//handle!(TuiIn: |self: Sequencer, input|SequencerCommand::execute_with_state(self, input.event())); -//handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event())); -//handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); - #[derive(Clone, Debug)] pub enum AppCommand { Clear, Clip(ClipCommand), Clock(ClockCommand), Color(ItemPalette), - Compact(bool), + Compact(Option), Editor(MidiEditCommand), Enqueue(Option>>), History(isize), @@ -29,46 +21,29 @@ handle!(TuiIn: |self: App, input| Ok(None)); Select(ArrangerSelection), StopAll, Track(TrackCommand), - Zoom(usize), -} -impl EdnCommand for AppCommand { - fn from_edn <'a> (state: &App, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { - match (head, tail) { - (Key("clear"), []) => Self::Clear, - (Key("stop-all"), []) => Self::StopAll, - - (Key("color"), [a]) => Self::Color(ItemPalette::default()), - (Key("compact"), [a]) => Self::Compact(true), - (Key("enqueue"), [a]) => Self::Enqueue(None), - (Key("history"), [a]) => Self::History(0), - (Key("select"), [a]) => Self::Select(ArrangerSelection::Mix), - (Key("zoom"), [a]) => Self::Zoom(0), - - (Key("clock"), [a, b @ ..]) => Self::Clock( - ClockCommand::from_edn(state, &a.to_ref(), b)), - (Key("track"), [a, b @ ..]) => Self::Track( - TrackCommand::from_edn(state, &a.to_ref(), b)), - (Key("scene"), [a, b @ ..]) => Self::Scene( - SceneCommand::from_edn(state, &a.to_ref(), b)), - (Key("clip"), [a, b @ ..]) => Self::Clip( - ClipCommand::from_edn(state, &a.to_ref(), b)), - - (Key("pool"), [a, b @ ..]) if let Some(pool) = state.pool.as_ref() => - Self::Pool(PoolCommand::from_edn(pool, &a.to_ref(), b)), - (Key("editor"), [a, b @ ..]) if let Some(editor) = state.editor.as_ref() => - Self::Editor(MidiEditCommand::from_edn(editor, &a.to_ref(), b)), - (Key("sampler"), [a, b @ ..]) if let Some(sampler) = state.sampler.as_ref() => - Self::Sampler(SamplerCommand::from_edn(sampler, &a.to_ref(), b)), - - _ => panic!(), - } - } + Zoom(Option), } +edn_command!(AppCommand: |state: App| { + ("clear" [] Self::Clear) + ("stop-all" [] Self::StopAll) + ("compact" [c: bool ] Self::Compact(c)) + ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) + ("history" [d: isize] Self::History(d.unwrap_or(0))) + ("zoom" [z: usize] Self::Zoom(z)) + ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b))) + ("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b))) + ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b))) + ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b))) + ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b))) + ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b))) + ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b))) + ("select" [s: ArrangerSelection] Self::Select(s.expect("no selection"))) + ("enqueue" [c: Arc>] Self::Enqueue(c)) +}); command!(|self: AppCommand, state: App|match self { Self::Clear => { todo!() }, Self::Zoom(_) => { todo!(); }, Self::History(delta) => { todo!("undo/redo") }, - Self::Select(s) => { state.selected = s; None }, Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, Self::Scene(cmd) => match cmd { @@ -111,7 +86,6 @@ command!(|self: AppCommand, state: App|match self { }, _ => None }.map(Self::Clip), - Self::Editor(cmd) => state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), Self::Sampler(cmd) => @@ -127,7 +101,6 @@ command!(|self: AppCommand, state: App|match self { state.color = palette; Some(Self::Color(old)) }, - Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() { let undo = cmd.clone().delegate(pool, Self::Pool)?; if let Some(editor) = state.editor.as_mut() { @@ -143,45 +116,60 @@ command!(|self: AppCommand, state: App|match self { } else { None }, - - Self::Compact(compact) => if state.compact != compact { - state.compact = compact; - Some(Self::Compact(!compact)) - } else { - None - }, + Self::Compact(compact) => match compact { + Some(compact) => { + if state.compact != compact { + state.compact = compact; + Some(Self::Compact(Some(!compact))) + } else { + None + } + }, + None => { + state.compact = !state.compact; + Some(Self::Compact(Some(!state.compact))) + } + } }); -#[derive(Clone, Debug)] pub enum SequencerCommand { - Compact(bool), - History(isize), - Clock(ClockCommand), - Pool(PoolCommand), - Editor(MidiEditCommand), - Enqueue(Option>>), -} -#[derive(Clone, Debug)] pub enum GrooveboxCommand { - Compact(bool), - History(isize), - Clock(ClockCommand), - Pool(PoolCommand), - Editor(MidiEditCommand), - Enqueue(Option>>), - Sampler(SamplerCommand), -} -#[derive(Clone, Debug)] pub enum ArrangerCommand { - History(isize), - Color(ItemPalette), - Clock(ClockCommand), - Scene(SceneCommand), - Track(TrackCommand), - Clip(ClipCommand), - Select(ArrangerSelection), - Zoom(usize), - Pool(PoolCommand), - Editor(MidiEditCommand), - StopAll, - Clear, -} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +//handle!(TuiIn: |self: Sequencer, input|SequencerCommand::execute_with_state(self, input.event())); +//handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event())); +//handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); +//use SequencerCommand as SeqCmd; +//use GrooveboxCommand as GrvCmd; +//use ArrangerCommand as ArrCmd; +//#[derive(Clone, Debug)] pub enum SequencerCommand { + //Compact(bool), + //History(isize), + //Clock(ClockCommand), + //Pool(PoolCommand), + //Editor(MidiEditCommand), + //Enqueue(Option>>), +//} +//#[derive(Clone, Debug)] pub enum GrooveboxCommand { + //Compact(bool), + //History(isize), + //Clock(ClockCommand), + //Pool(PoolCommand), + //Editor(MidiEditCommand), + //Enqueue(Option>>), + //Sampler(SamplerCommand), +//} +//#[derive(Clone, Debug)] pub enum ArrangerCommand { + //History(isize), + //Color(ItemPalette), + //Clock(ClockCommand), + //Scene(SceneCommand), + //Track(TrackCommand), + //Clip(ClipCommand), + //Select(ArrangerSelection), + //Zoom(usize), + //Pool(PoolCommand), + //Editor(MidiEditCommand), + //StopAll, + //Clear, +//} //command!(|self: SequencerCommand, state: Sequencer|match self { //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, diff --git a/tek/src/model.rs b/tek/src/model.rs index f594c6e4..b30e41c7 100644 --- a/tek/src/model.rs +++ b/tek/src/model.rs @@ -94,6 +94,21 @@ has_size!(|self: App|&self.size); has_clock!(|self: App|&self.clock); has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); has_editor!(|self: App|self.editor.as_ref().expect("no editor")); +edn_provide!(u16: |self: App|{ + ":sample-h" => if self.compact() { 0 } else { 5 }, + ":samples-w" => if self.compact() { 4 } else { 11 }, + ":samples-y" => if self.compact() { 1 } else { 0 }, + ":pool-w" => if self.compact() { 5 } else { + let w = self.size.w(); + if w > 60 { 20 } else if w > 40 { 15 } else { 10 } + } +}); +edn_provide!(Color: |self: App| { _ => return None }); +edn_provide!(usize: |self: App| { _ => return None }); +edn_provide!(isize: |self: App| { _ => return None }); +edn_provide!(bool: |self: App| { _ => return None }); +edn_provide!(ArrangerSelection: |self: App| { _ => return None }); +edn_provide!(Arc>: |self: App| { _ => return None }); //#[derive(Default)] pub struct Sequencer { //pub jack: Arc>, diff --git a/tek/src/view.rs b/tek/src/view.rs index 7b003821..c54837ec 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -21,21 +21,11 @@ impl EdnViewData for &App { } } fn get_unit (&self, item: EdnItem<&str>) -> u16 { - use EdnItem::*; - match item.to_str() { - ":sample-h" => if self.compact() { 0 } else { 5 }, - ":samples-w" => if self.compact() { 4 } else { 11 }, - ":samples-y" => if self.compact() { 1 } else { 0 }, - ":pool-w" => if self.compact() { 5 } else { - let w = self.size.w(); - if w > 60 { 20 } else if w > 40 { 15 } else { 10 } - }, - _ => 0 - } + EdnProvide::::get_or_fail(self, &item) } } impl App { - fn compact (&self) -> bool { false } + pub fn compact (&self) -> bool { false } fn toolbar (&self) -> impl Content + use<'_> { Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))) } @@ -337,6 +327,7 @@ fn cell > (color: ItemPalette, field: T) -> impl Content self.size.of(EdnView::from_source(self, Self::EDN))); //impl EdnViewData for &Sequencer { //fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {