diff --git a/edn/src/context.rs b/edn/src/context.rs index b0a0b0be..da366c4c 100644 --- a/edn/src/context.rs +++ b/edn/src/context.rs @@ -92,28 +92,36 @@ impl, U> Context for Option { } }; } -/// Implement `Context` for a context and content type. +/// Implement `Context` for a context and the boolean type. /// -/// This enables support for layout expressions. -#[macro_export] macro_rules! provide_content { - (|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<'a, E: Output> Context<'a, Box + 'a>> for $State { - fn get (&'a $self, atom: &'a Value) -> Option + 'a>> { - Some(match (atom.kind(), atom.text()) { +/// This enables support for boolean literals. +#[macro_export] macro_rules! provide_bool { + // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. + ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl<$T: $Trait> Context<$type> for $T { + fn get (&$self, atom: &Value) -> Option<$type> { + Some(match atom { + Value::Num(n) => match *n { 0 => false, _ => true }, + Value::Sym(":false") | Value::Sym(":f") => false, + Value::Sym(":true") | Value::Sym(":t") => true, + $(Value::Sym($pat) => $expr,)* + _ => return Context::get(self, atom) + }) + } + } + }; + // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. + ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl Context<$type> for $State { + fn get (&$self, atom: &Value) -> Option<$type> { + Some(match atom { + Value::Num(n) => match *n { 0 => false, _ => true }, + Value::Sym(":false") | Value::Sym(":f") => false, + Value::Sym(":true") | Value::Sym(":t") => true, $(Value::Sym($pat) => $expr,)* _ => return None }) } } }; - ($Output:ty: |$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<'a> Context<'a, Box + 'a>> for $State { - fn get (&'a $self, atom: &'a Value) -> Option + 'a>> { - Some(match (atom.kind(), atom.text()) { - $(Value::Sym($pat) => $expr,)* - _ => return None - }) - } - } - } } diff --git a/edn/src/iter.rs b/edn/src/iter.rs index 4e9b74d3..e87bc21c 100644 --- a/edn/src/iter.rs +++ b/edn/src/iter.rs @@ -1,5 +1,5 @@ use crate::*; -#[derive(Copy, Clone, Debug, PartialEq)] pub struct TokenIter<'a>(pub &'a str); +#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct TokenIter<'a>(pub &'a str); const_iter!(<'a>|self: TokenIter<'a>| => Token<'a> => self.next_mut().map(|(result, _)|result)); impl<'a> From<&'a str> for TokenIter<'a> {fn from (source: &'a str) -> Self{Self::new(source)}} impl<'a> TokenIter<'a> { diff --git a/output/src/view.rs b/output/src/view.rs index 1362f3d0..b12d6af4 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -7,44 +7,35 @@ use Value::*; /// * render callback (implementation of [Content]) /// * value providers (implementations of [Context]) #[macro_export] macro_rules! view { - ($Output:ty: |$self:ident: $App:ty| $content:expr; { + ($Output:ty: |$self:ident: $App:ty| $content:expr $(;{ $( $type:ty { $($sym:literal => $value:expr),* } );* - }) => { + })?) => { impl Content<$Output> for $App { fn content(&$self) -> impl Render<$Output> { $content } } - $( + $($( impl Context<$type> for $App { fn get (&$self, atom: &Value) -> Option<$type> { Some(match atom.to_ref() { $(Atom::Sym($sym) => $value,)* _ => return None }) } } - )* + )*)? } } -/// Implements `Context` for content components and expressions -#[macro_export] macro_rules! provide_content { - (|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<'a, E: Output> Context<'a, Box + 'a>> for $State { - fn get (&$self, atom: &Value) -> Option + 'a>> { - Some(match atom.to_ref() { - $(Atom::Sym($pat) => $expr),*, - _ => return None - }) - } - } - }; - ($Output:ty: |$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<'a> Context<'a, Box + 'a>> for $State { - fn get (&$self, atom: &Value) -> Option + 'a>> { - Some(match atom.to_ref() { - $(Atom::Sym($pat) => $expr),*, - _ => return None - }) + +pub struct View<'a, T>(pub &'a T, pub TokenIter<'a>); +impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content for View<'a, T> { + fn content (&self) -> impl Render { + let iter = self.1.clone(); + while let Some((Token { value, .. }, _)) = iter.next() { + if let Some(content) = self.0.get_content(&value) { + return Some(content) } } + return None } } + /// Renders from EDN source and context. /// /// Generic over: @@ -163,3 +154,28 @@ pub type AtomRenderCallback<'a, O, State> = } } } +/// Implement `Context` for a context and content type. +/// +/// This enables support for layout expressions. +#[macro_export] macro_rules! provide_content { + (|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl Context + '_>> for $State { + fn get (&$self, atom: &Value) -> Option + '_>> { + Some(match atom { + $(Value::Sym($pat) => $expr,)* + _ => return None + }) + } + } + }; + ($Output:ty: |$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl Context + '_>> for $State { + fn get (&$self, atom: &Value) -> Option + '_>> { + Some(match atom { + $(Value::Sym($pat) => $expr,)* + _ => return None + }) + } + } + } +} diff --git a/tek/src/lib.rs b/tek/src/lib.rs index d33cac7e..1fcfda54 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -118,8 +118,6 @@ impl TekCli { #[derive(Default, Debug)] struct Tek { /// Must not be dropped for the duration of the process pub jack: Arc>, - /// View definition - pub atom: String, /// Source of time pub clock: Clock, /// Theme @@ -143,11 +141,14 @@ impl TekCli { pub editing: AtomicBool, pub history: Vec, - keys: TokenIter<'static>, - keys_clip: TokenIter<'static>, - keys_track: TokenIter<'static>, - keys_scene: TokenIter<'static>, - keys_mix: TokenIter<'static>, + /// View definition + pub view: TokenIter<'static>, + // Input definitions + pub keys: TokenIter<'static>, + pub keys_clip: TokenIter<'static>, + pub keys_track: TokenIter<'static>, + pub keys_scene: TokenIter<'static>, + pub keys_mix: TokenIter<'static>, } has_size!(|self: Tek|&self.size); has_clock!(|self: Tek|self.clock); @@ -174,37 +175,36 @@ provide_num!(usize: |self: Tek| { ":track" => self.selected.track().unwrap_or(0), ":track-next" => (self.selected.track().unwrap_or(0) + 1).min(self.tracks.len()), ":track-prev" => self.selected.track().unwrap_or(0).saturating_sub(1) }); -view!(TuiOut: |self: Tek| self.size.of(AtomView::from_source(self, self.atom.as_ref())); { - bool {}; - isize {}; - Color {}; - Selection {}; - Arc> {}; - Option>> {}; - // Provide sizes: - u16 { - ":sidebar-w" => self.sidebar_w(), - ":sample-h" => if self.is_editing() { 0 } else { 5 }, - ":samples-w" => if self.is_editing() { 4 } else { 11 }, - ":samples-y" => if self.is_editing() { 1 } else { 0 }, - ":pool-w" => if self.is_editing() { 5 } else { - let w = self.size.w(); - if w > 60 { 20 } else if w > 40 { 15 } else { 10 } } }; - // Provide components: - Box + 'a> { - ":editor" => (&self.editor).boxed(), - ":pool" => self.view_pool().boxed(), - ":sample" => self.view_sample(self.is_editing()).boxed(), - ":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(), - ":status" => self.view_editor().boxed(), - ":toolbar" => self.view_clock().boxed(), - ":tracks" => self.view_row(self.w(), 3, self.track_header(), self.track_cells()).boxed(), - ":inputs" => self.view_row(self.w(), 3, self.input_header(), self.input_cells()).boxed(), - ":outputs" => self.view_row(self.w(), 3, self.output_header(), self.output_cells()).boxed(), - ":scenes" => Outer(false, Style::default().fg(TuiTheme::g(0))).enclose_bg(self.view_row( - self.w(), self.size.h().saturating_sub(12) as u16, - self.scene_header(), self.clip_columns() - )).boxed() }}); +view!(TuiOut: |self: Tek| self.size.of(View(self, self.view))); +provide_bool!(bool: |self: Tek| {}); +provide_num!(isize: |self: Tek| {}); +provide!(Color: |self: Tek| {}); +provide!(Selection: |self: Tek| {}); +provide!(Arc>: |self: Tek| {}); +provide!(Option>>: |self: Tek| {}); +provide_num!(u16: |self: Tek| { + ":sidebar-w" => self.sidebar_w(), + ":sample-h" => if self.is_editing() { 0 } else { 5 }, + ":samples-w" => if self.is_editing() { 4 } else { 11 }, + ":samples-y" => if self.is_editing() { 1 } else { 0 }, + ":pool-w" => if self.is_editing() { 5 } else { + let w = self.size.w(); + if w > 60 { 20 } else if w > 40 { 15 } else { 10 } } +}); +provide_content!(TuiOut: |self: Tek| { + ":editor" => (&self.editor).boxed(), + ":pool" => self.view_pool().boxed(), + ":sample" => self.view_sample(self.is_editing()).boxed(), + ":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(), + ":status" => self.view_editor().boxed(), + ":toolbar" => self.view_clock().boxed(), + ":tracks" => self.view_row(self.w(), 3, self.track_header(), self.track_cells()).boxed(), + ":inputs" => self.view_row(self.w(), 3, self.input_header(), self.input_cells()).boxed(), + ":outputs" => self.view_row(self.w(), 3, self.output_header(), self.output_cells()).boxed(), + ":scenes" => Outer(false, Style::default().fg(TuiTheme::g(0))).enclose_bg(self.view_row( + self.w(), self.size.h().saturating_sub(12) as u16, + self.scene_header(), self.clip_columns() + )).boxed() }); impl Tek { fn new_clock ( jack: &Arc>, @@ -212,12 +212,17 @@ impl Tek { midi_froms: &[PortConnection], midi_tos: &[PortConnection], ) -> Usually { let tek = Self { - atom: include_str!("./view_transport.edn").to_string(), - jack: jack.clone(), - color: ItemPalette::random(), - clock: Clock::new(jack, bpm), - midi_ins: vec![JackPort::::new(jack, "GlobalI", midi_froms)?], - midi_outs: vec![JackPort::::new(jack, "GlobalO", midi_tos)?], + view: TokenIter(include_str!("./view_transport.edn")), + jack: jack.clone(), + color: ItemPalette::random(), + clock: Clock::new(jack, bpm), + midi_ins: vec![JackPort::::new(jack, "GlobalI", midi_froms)?], + midi_outs: vec![JackPort::::new(jack, "GlobalO", midi_tos)?], + keys: TokenIter(KEYS_APP), + keys_clip: TokenIter(KEYS_CLIP), + keys_track: TokenIter(KEYS_TRACK), + keys_scene: TokenIter(KEYS_SCENE), + keys_mix: TokenIter(KEYS_MIX), ..Default::default() }; tek.sync_lead(sync_lead); @@ -232,7 +237,7 @@ impl Tek { let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); let clip = Arc::new(RwLock::new(clip)); Ok(Self { - atom: include_str!("./view_sequencer.edn").to_string(), + view: TokenIter(include_str!("./view_sequencer.edn")), pool: Some((&clip).into()), editor: Some((&clip).into()), editing: false.into(), @@ -248,7 +253,7 @@ impl Tek { audio_froms: &[&[PortConnection];2], audio_tos: &[&[PortConnection];2], ) -> Usually { let app = Self { - atom: include_str!("./view_groovebox.edn").to_string(), + view: TokenIter(include_str!("./view_groovebox.edn")), sampler: Some(Sampler::new(jack, &"sampler", midi_froms, audio_froms, audio_tos)?), ..Self::new_sequencer(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? }; @@ -265,7 +270,7 @@ impl Tek { scenes: usize, tracks: usize, track_width: usize, ) -> Usually { let mut arranger = Self { - atom: include_str!("./view_arranger.edn").to_string(), + view: TokenIter(include_str!("./view_arranger.edn")), ..Self::new_groovebox( jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos, audio_froms, audio_tos, @@ -600,19 +605,17 @@ handle!(TuiIn: |self: Tek, input|Ok({ } } // Handle from root keymap - if let Some(command) = KeyMap::new(KEYS_APP)?.command::<_, TekCommand>(self, input) { + if let Some(command) = self.keys.command::<_, TekCommand, _>(self, input) { if let Some(undo) = command.execute(self)? { self.history.push(undo); } return Ok(Some(true)) } // Handle from selection-dependent keymaps - if let Some(command) = KeyMap::new(match self.selected() { - Selection::Clip(t, s) => KEYS_CLIP, - Selection::Track(t) => KEYS_TRACK, - Selection::Scene(s) => KEYS_SCENE, - Selection::Mix => KEYS_MIX, - })?.command::<_, TekCommand>( - self, input - ) { + if let Some(command) = match self.selected() { + Selection::Clip(_, _) => self.keys_clip, + Selection::Track(_) => self.keys_track, + Selection::Scene(_) => self.keys_scene, + Selection::Mix => self.keys_mix, + }.command::<_, TekCommand, _>(self, input) { if let Some(undo) = command.execute(self)? { self.history.push(undo); } return Ok(Some(true)) }