diff --git a/edn/src/context.rs b/edn/src/context.rs index da366c4c..921cefbb 100644 --- a/edn/src/context.rs +++ b/edn/src/context.rs @@ -1,10 +1,10 @@ use crate::*; -pub trait TryFromAtom: Sized { - fn try_from_atom (state: &T, value: Value) -> Option { +pub trait TryFromAtom<'a, T>: Sized { + fn try_from_atom (state: &'a T, value: Value<'a>) -> Option { if let Value::Exp(0, iter) = value { return Self::try_from_expr(state, iter) } None } - fn try_from_expr (_state: &T, _iter: TokenIter) -> Option { + fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option { None } } diff --git a/input/src/keymap.rs b/input/src/keymap.rs index 2c95d1c5..e9695c29 100644 --- a/input/src/keymap.rs +++ b/input/src/keymap.rs @@ -3,33 +3,40 @@ use crate::*; pub trait AtomInput: Input { fn matches_atom (&self, token: &str) -> bool; } -pub trait KeyMap { +pub trait KeyMap<'a> { /// Try to find a command that matches the current input event. - fn command , I: AtomInput> (&self, state: &S, input: &I) -> Option; + fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) + -> Option; } -impl KeyMap for TokenIter<'_> { - fn command , I: AtomInput> (&self, state: &S, input: &I) -> Option { - for token in *self { - if let Value::Exp(0, iter) = token.value() { - if let Some((Token { value: Value::Sym(binding), .. }, _)) = iter.next() { - if input.matches_atom(binding) { - if let Some(command) = C::try_from_expr(state, iter.clone()) { - return Some(command) - } +impl<'a> KeyMap<'a> for TokenIter<'a> { + fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) + -> Option + { + let iter = self.clone(); + while let Some(next) = iter.next() { + match next { + (Token { value: Value::Exp(0, iter), .. }, _) => { + let next = iter.next(); + match next { + Some((Token { value: Value::Sym(binding), .. }, _)) => { + if input.matches_atom(binding) { + if let Some(command) = C::try_from_expr(state, iter.clone()) { + return Some(command) + } + } + }, + _ => panic!("invalid config (expected symbol)") } - } else { - panic!("invalid config: {token:?}") - } - } else { - panic!("invalid config: {token:?}") + }, + _ => panic!("invalid config (expected expression)") } } None } } /// A [Command] that can be constructed from a [Token]. -pub trait AtomCommand: TryFromAtom + Command {} -impl + Command> AtomCommand for T {} +pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command {} +impl<'a, C, T: TryFromAtom<'a, C> + Command> AtomCommand<'a, C> for T {} /** Implement `AtomCommand` for given `State` and `Command` */ #[macro_export] macro_rules! atom_command { ($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $(( @@ -53,7 +60,7 @@ impl + Command> AtomCommand for T {} // bound command: $command:expr ))* }) => { - impl<$State: $Trait> TryFromAtom<$State> for $Command { + impl<'a, $State: $Trait> TryFromAtom<'a, $State> for $Command { fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { match $iter.next() { $(Some((Token { value: Value::Key($key), .. }, _)) => { @@ -94,7 +101,7 @@ impl + Command> AtomCommand for T {} // bound command: $command:expr ))* }) => { - impl TryFromAtom<$State> for $Command { + impl<'a> TryFromAtom<'a, $State> for $Command { fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { match iter.next() { $(Some((Token { value: Value::Key($key), .. }, _)) => { diff --git a/output/src/op_cond.rs b/output/src/op_cond.rs index f8b8f321..bbec8dad 100644 --- a/output/src/op_cond.rs +++ b/output/src/op_cond.rs @@ -10,7 +10,7 @@ try_from_expr!(<'a, E>: When>: |state, iter| { let iter = iter.clone(); let condition = iter.next(); if let Some((ref condition, _)) = condition { - let condition = state.get_bool(&condition.value).expect("no condition"); + let condition = state.get(&condition.value).expect("no condition"); if let Some((ref content, _)) = iter.next() { let content = state.get_content(&content.value).expect("no atom"); return Some(Self(condition, content)) @@ -23,7 +23,7 @@ try_from_expr!(<'a, E>: Either, RenderBox<'a, E>>: |state, iter let iter = iter.clone(); let condition = iter.next(); if let Some((ref condition, _)) = condition { - let condition = state.get_bool(&condition.value).expect("no condition"); + let condition = state.get(&condition.value).expect("no condition"); if let Some((ref content1, _)) = iter.next() { let content1 = state.get_content(&content1.value).expect("no content1"); if let Some((ref content2, _)) = iter.next() { diff --git a/output/src/op_transform.rs b/output/src/op_transform.rs index 23edc9be..121d5111 100644 --- a/output/src/op_transform.rs +++ b/output/src/op_transform.rs @@ -12,9 +12,9 @@ macro_rules! transform_xy { pub fn y (item: T) -> Self { Self::Y(item) } pub fn xy (item: T) -> Self { Self::XY(item) } } - impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom + impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> for $Enum> { - fn try_from_expr (state: &T, iter: TokenIter) -> Option { + fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option { Some(if let Some((Token { value: Value::Key($x), .. }, _)) = iter.next() { Self::x(state.get_content(&iter.next().expect("no content").0.value).expect("no content")) } else if let Some((Token { value: Value::Key($y), .. }, _)) = iter.next() { @@ -51,20 +51,20 @@ macro_rules! transform_xy_unit { pub fn y (y: U, item: T) -> Self { Self::Y(y, item) } pub fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) } } - impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom + impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> for $Enum> { - fn try_from_expr (state: &T, iter: TokenIter) -> Option { + fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option { Some(if let Some((Token { value: Value::Key($x), .. }, _)) = iter.next() { - let x = state.get_unit(&iter.next().expect("no x").0.value).expect("no x"); + let x = state.get(&iter.next().expect("no x").0.value).expect("no x"); let c = state.get_content(&iter.next().expect("no content").0.value).expect("no content"); Self::x(x, c) } else if let Some((Token { value: Value::Key($y), .. }, _)) = iter.next() { - let y = state.get_unit(&iter.next().expect("no y").0.value).expect("no y"); + let y = state.get(&iter.next().expect("no y").0.value).expect("no y"); let c = state.get_content(&iter.next().expect("no content").0.value).expect("no content"); Self::y(y, c) } else if let Some((Token { value: Value::Key($xy), .. }, _)) = iter.next() { - let x = state.get_unit(&iter.next().expect("no x").0.value).expect("no x"); - let y = state.get_unit(&iter.next().expect("no y").0.value).expect("no y"); + let x = state.get(&iter.next().expect("no x").0.value).expect("no x"); + let y = state.get(&iter.next().expect("no y").0.value).expect("no y"); let c = state.get_content(&iter.next().expect("no content").0.value).expect("no content"); Self::xy(x, y, c) } else { diff --git a/output/src/view.rs b/output/src/view.rs index b12d6af4..04e3629a 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -1,28 +1,25 @@ use crate::*; -use std::{sync::Arc, fmt::Debug}; -use Value::*; -/// Define an EDN-backed view. -/// -/// This consists of: -/// * render callback (implementation of [Content]) -/// * value providers (implementations of [Context]) #[macro_export] macro_rules! view { - ($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 } + ($Output:ty: |$self:ident: $State:ty| $expr:expr; { + $($sym:literal => $body:expr),* $(,)? + }) => { + impl Content<$Output> for $State { + fn content (&$self) -> impl Render<$Output> { $expr } } - $($( - impl Context<$type> for $App { - fn get (&$self, atom: &Value) -> Option<$type> { - Some(match atom.to_ref() { $(Atom::Sym($sym) => $value,)* _ => return None }) + impl<'a> ViewContext<'a, $Output> for $State { + fn get_content_custom (&'a $self, value: &Value<'a>) -> Option> { + if let Value::Sym(s) = value { + match *s { $($sym => Some($body),)* _ => None } + } else { + panic!("expected symbol") } } - )*)? + } } } +// An ephemeral wrapper around view state and view description, +// that is meant to be constructed and returned from [Content::content]. 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 { @@ -35,97 +32,36 @@ impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content for View<'a, T> { return None } } - -/// Renders from EDN source and context. -/// -/// Generic over: -/// -/// * `O` - Output target. -/// * `S` - State provider. -/// * `A` - Atom storage type. -#[derive(Default, Clone)] pub enum AtomView<'a, O, S, A> { - _Unused(std::marker::PhantomData<&'a O>), - #[default] Inert, - Src(S, &'a str), - Ref(S, &'a A), - Own(S, A), - Err(Arc) -} -impl<'a, O, S, A> Debug for AtomView<'a, O, S, A> -where S: Debug, A: Debug { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - Self::Inert => - write!(f, "AtomView::Inert"), - Self::Src(state, view) => - write!(f, "AtomView::Src(state={state:?} view={view:?}"), - Self::Ref(state, view) => - write!(f, "AtomView::Ref(state={state:?} view={view:?}"), - Self::Own(state, view) => - write!(f, "AtomView::Arc(state={state:?} view={view:?}"), - Self::Err(error) => - write!(f, "AtomView::Err({error})"), - _ => unreachable!() - } - } -} -impl<'a, O, S, A> Content for AtomView<'a, O, S, A> -where O: Output, S: ViewContext<'a, O>, A: Send + Sync { - fn content (&self) -> impl Render { - match self { - Self::Inert => { panic!("inert rendered") }, - Self::Err(e) => { panic!("render error: {e}") }, - Self::Src(state, source) => {}, - Self::Ref(state, atom) => {}, - Self::Own(state, atom) => {}, - _ => unreachable!() - } - } -} -impl<'a, E: Output, T> ViewContext<'a, E> for T where T: Sized + Send + Sync +// Provides components to the view. +pub trait ViewContext<'a, E: Output + 'a>: Send + Sync + Context + Context + Context - + Context + 'a>> {} -/// Provides values to the template -pub trait ViewContext<'a, E: Output>: Sized + Send + Sync - + Context - + Context - + Context - + Context + 'a>> { - fn get_bool (&self, atom: &Value) -> Option { - Some(match atom { - Num(n) => match *n { 0 => false, _ => true }, - Sym(x) => match *x { - ":false" | ":f" => false, ":true" | ":t" => true, - _ => return Context::get(self, atom) - }, - _ => return Context::get(self, atom) - }) + fn get_content (&'a self, value: &Value<'a>) -> Option> { + if let Some(builtin) = self.get_content_builtin(value) { + Some(builtin) + } else { + self.get_content_custom(value) + } } - fn get_usize (&self, atom: &Value) -> Option { - Some(match atom {Num(n) => *n, _ => return Context::get(self, atom)}) - } - fn get_unit (&self, atom: &Value) -> Option { - Some(match atom {Num(n) => E::Unit::from(*n as u16), _ => return Context::get(self, atom)}) - } - fn get_content (&self, atom: &Value) -> Option + 'a>> where E: 'a { - try_delegate!(self, *atom, When::>); - try_delegate!(self, *atom, Either::, RenderBox<'a, E>>); - try_delegate!(self, *atom, Align::>); - try_delegate!(self, *atom, Bsp::, RenderBox<'a, E>>); - try_delegate!(self, *atom, Fill::>); - try_delegate!(self, *atom, Fixed::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Min::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Max::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Shrink::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Expand::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Push::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Pull::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Margin::<_, RenderBox<'a, E>>); - try_delegate!(self, *atom, Padding::<_, RenderBox<'a, E>>); - Some(Context::get_or_fail(self, atom)) + fn get_content_custom (&'a self, value: &Value<'a>) -> Option>; + fn get_content_builtin (&'a self, value: &Value<'a>) -> Option> { + try_delegate!(self, *value, When::>); + try_delegate!(self, *value, Either::, RenderBox<'a, E>>); + try_delegate!(self, *value, Align::>); + try_delegate!(self, *value, Bsp::, RenderBox<'a, E>>); + try_delegate!(self, *value, Fill::>); + try_delegate!(self, *value, Fixed::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Min::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Max::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Shrink::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Expand::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Push::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Pull::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Margin::<_, RenderBox<'a, E>>); + try_delegate!(self, *value, Padding::<_, RenderBox<'a, E>>); + None } } #[macro_export] macro_rules! try_delegate { @@ -135,17 +71,10 @@ pub trait ViewContext<'a, E: Output>: Sized + Send + Sync } } } -// A function that returns a `RenderBox. -pub type AtomCallback<'a, O, State> = - dyn Fn(&'a State)-> RenderBox<'a, O> + Send + Sync + 'a; -// A box containing a function that returns a `RenderBox. -pub type AtomRenderCallback<'a, O, State> = - Box>; - #[macro_export] macro_rules! try_from_expr { (<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $atoms:ident|$body:expr) => { - impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromAtom for $Struct { - fn try_from_atom ($state: &T, atom: Value) -> Option { + impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromAtom<$l, T> for $Struct { + fn try_from_atom ($state: &$l T, atom: Value<$l>) -> Option { if let Value::Exp(0, $atoms) = atom { $body; } @@ -154,28 +83,3 @@ 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 1fcfda54..e320c922 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -175,7 +175,21 @@ 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(View(self, self.view))); +view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { + ":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() +}); provide_bool!(bool: |self: Tek| {}); provide_num!(isize: |self: Tek| {}); provide!(Color: |self: Tek| {}); @@ -191,20 +205,6 @@ provide_num!(u16: |self: Tek| { 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>,