diff --git a/edn/src/edn_item.rs b/edn/src/edn_item.rs index 176b9da5..ffa8fabf 100644 --- a/edn/src/edn_item.rs +++ b/edn/src/edn_item.rs @@ -15,6 +15,7 @@ const fn digit (c: char) -> usize { #[derive(Debug)] pub enum ParseError { + Unknown(u8), Empty, Incomplete, Unexpected(char), @@ -25,27 +26,90 @@ impl std::fmt::Display for ParseError { Self::Empty => write!(f, "empty"), Self::Incomplete => write!(f, "incomplete"), Self::Unexpected(c) => write!(f, "unexpected '{c}'"), + Self::Unknown(i) => write!(f, "unknown #{i}"), } } } impl std::error::Error for ParseError {} -#[derive(Debug, Clone, Default, PartialEq)] -pub enum Item { - #[default] Nil, +pub enum EdnItem { + Nil, Num(usize), Sym(T), Key(T), - Exp(Vec>), + Exp(Vec>), +} +impl Default for EdnItem { + fn default () -> Self { + Self::Nil + } } -impl Item { +impl + PartialEq + Default + Clone + std::fmt::Debug> EdnItem { + pub fn to_ref (&self) -> EdnItem<&str> { + match self { + Self::Key(x) => EdnItem::Key(x.as_ref()), + _ => todo!() + } + } + pub fn to_str (&self) -> &str { + match self { + Self::Key(x) => x.as_ref(), + _ => todo!() + } + } } -impl<'a> Item<&'a str> { +impl EdnItem { + fn from (other: EdnItem>) -> EdnItem { + use EdnItem::*; + match other { + Nil => Nil, + Key(t) => Key(t.as_ref().to_string()), + _ => todo!() + } + } } -impl<'a, T: std::fmt::Debug + Clone + Default + PartialEq + From<&'a str>> Item { +impl<'a> TryFrom> for EdnItem { + type Error = ParseError; + fn try_from (token: Token<'a>) -> Result { + use Token::*; + Ok(match token { + Nil => Self::Nil, + Num(chars, index, length) => + Self::Num(number(&chars[index..index+length])), + Sym(chars, index, length) => + Self::Sym(String::from(&chars[index..index+length])), + Key(chars, index, length) => + Self::Key(String::from(&chars[index..index+length])), + Exp(chars, index, length, 0) => + Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?), + _ => panic!("unclosed delimiter") + }) + } +} + +impl<'a> TryFrom> for EdnItem<&'a str> { + type Error = ParseError; + fn try_from (token: Token<'a>) -> Result { + use Token::*; + Ok(match token { + Nil => EdnItem::Nil, + Num(chars, index, length) => + Self::Num(number(&chars[index..index+length])), + Sym(chars, index, length) => + Self::Sym(&chars[index..index+length]), + Key(chars, index, length) => + Self::Key(&chars[index..index+length]), + Exp(chars, index, length, 0) => + Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?), + _ => panic!("unclosed delimiter") + }) + } +} + +impl<'a, T: std::fmt::Debug + Clone + Default + PartialEq + From<&'a str>> EdnItem { pub fn read_all (mut source: &'a str) -> Result, ParseError> { let mut items = vec![]; loop { @@ -53,15 +117,24 @@ impl<'a, T: std::fmt::Debug + Clone + Default + PartialEq + From<&'a str>> Item< break } let (remaining, token) = Token::chomp(source)?; - match Item::read(token)? { Item::Nil => {}, item => items.push(item) }; + match Self::read(token)? { Self::Nil => {}, item => items.push(item) }; source = remaining } Ok(items) } + pub fn read_one (source: &'a str) -> Result<(Self, &'a str), ParseError> { + Ok({ + if source.len() == 0 { + return Err(ParseError::Unknown(5)) + } + let (remaining, token) = Token::chomp(source)?; + (Self::read(token)?, remaining) + }) + } pub fn read (token: Token<'a>) -> Result { use Token::*; Ok(match token { - Nil => Item::Nil, + Nil => EdnItem::Nil, Num(chars, index, length) => Self::Num(number(&chars[index..index+length])), Sym(chars, index, length) => @@ -73,8 +146,8 @@ impl<'a, T: std::fmt::Debug + Clone + Default + PartialEq + From<&'a str>> Item< _ => panic!("unclosed delimiter") }) } - //pub fn to_str <'a> (&'a self) -> Item<&'a str> { - //use Item::*; + //pub fn to_str <'a> (&'a self) -> EdnItem<&'a str> { + //use EdnItem::*; //match self { //Nil => Nil, //Num(n) => Num(*n), diff --git a/edn/src/edn_layout.rs b/edn/src/edn_layout.rs index fe00b06a..83217dfe 100644 --- a/edn/src/edn_layout.rs +++ b/edn/src/edn_layout.rs @@ -1,68 +1,69 @@ use crate::*; use std::marker::PhantomData; use ::tek_layout::{*, tek_engine::{Usually, Content, Render, Engine, Thunk}}; -use Item::*; - -//pub struct EdnContent<'a, E, T: AsRef>(PhantomData, &'a [Item]); - -//impl<'a, E, T: AsRef> From<&'a [Item]> for EdnContent<'a, E, T> { - //fn from (items: &'a [Item]) -> Self { - //Self(Default::default(), items) - //} -//} +use EdnItem::*; +/// Compiles view from EDN form to Thunk pub struct EdnView<'a, E: Engine + 'a, T: EdnLayout<'a, E> + 'a>( - PhantomData<&'a ()>, - BoxBox + Send + Sync> + Send + Sync> + PhantomData<&'a (E, T)>, + EdnItem, + //BoxBox + Send + Sync + 'a> + Send + Sync + 'a> ); impl<'a, E: Engine + 'a, T: EdnLayout<'a, E> + 'a> EdnView<'a, E, T> { - fn from (source: &'a str) -> Usually { - let items: Vec> = Item::read_all(source)?; - let layout = T::parse(items.iter().map(|x|x.to_str()).collect::>().as_slice()); - Ok(Self(Default::default(), Box::new(move|_|{ - let _ = layout; - Box::new(()) - }))) + pub fn new (source: &'a str) -> Usually { + let (item, _): (EdnItem, _) = EdnItem::read_one(&source)?; + Ok(Self(Default::default(), item)) } } -macro_rules! edn_ns { - (|$state:ident, $items:ident| { $($match:pat => $handle:expr),* $(,)? }) => { - match $items { - $($match => |$state|Box::new($handle),)* - _ => |$state|Box::new(()) +pub type EdnRender<'a, Engine> = + dyn Render + Send + Sync + 'a; + +pub type EdnCallback<'a, Engine, State> = + dyn Fn(&'a State)->Box> + Send + Sync + 'a; + +pub type EdnRenderCallback<'a, Engine, State> = + Box>; + +pub trait EdnLayout<'a, E: Engine + 'a> where Self: 'a { + fn get_bool (&self, _: &EdnItem<&str>) -> bool { false } + fn get_unit (&self, _: &EdnItem<&str>) -> E::Unit { 0.into() } + fn get_usize (&self, _: &EdnItem<&str>) -> usize { 0 } + fn get_content (&'a self, _: &EdnItem<&str>) -> Box> { Box::new(()) } + fn parse (items: &'a [EdnItem]) -> EdnRenderCallback<'a, E, Self> { + if let (Some(first), rest) = (items.get(0).map(EdnItem::to_str), &items[1..]) { + match (first, rest) { + ("when", [c, a, ..]) => Box::new(move|state|Box::new( + When(state.get_bool(&c.to_ref()), state.get_content(&a.to_ref())))), + _ => Box::new(|_|Box::new(())) + } + } else { + Box::new(|_|Box::new(())) } } -} - -pub trait EdnLayout<'a, E: Engine> where Self: 'a { - fn get_bool (&self, _: &Item<&str>) -> bool { false } - fn get_unit (&self, _: &Item<&str>) -> E::Unit { 0.into() } - fn get_usize (&self, _: &Item<&str>) -> usize { 0 } - fn get_content (&self, _: &Item<&str>) -> Box> { Box::new(()) } - fn parse (items: &'a [Item<&'a str>]) -> impl Fn(&'a Self)->Box + 'a> + 'a { - edn_ns!(|state, items|{ - [Key("when"), c, a, ..] => When(state.get_bool(c), state.get_content(a)), - [Key("either"), c, a, b, ..] => Either(state.get_bool(c), state.get_content(a), state.get_content(b)), - [Key("fill"), a, ..] => Fill::xy(state.get_content(a)), - [Key("fill/x"), a, ..] => Fill::x(state.get_content(a)), - [Key("fill/y"), a, ..] => Fill::y(state.get_content(a)), - [Key("fixed"), x, y, a, ..] => Fixed::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), - [Key("fixed/x"), x, a, ..] => Fixed::x(state.get_unit(x), state.get_content(a)), - [Key("fixed/y"), y, a, ..] => Fixed::y(state.get_unit(y), state.get_content(a)), - [Key("shrink"), x, y, a, ..] => Shrink::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), - [Key("shrink/x"), x, a, ..] => Shrink::x(state.get_unit(x), state.get_content(a)), - [Key("shrink/y"), y, a, ..] => Shrink::y(state.get_unit(y), state.get_content(a)), - [Key("expand"), x, y, a, ..] => Expand::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), - [Key("expand/x"), x, a, ..] => Expand::x(state.get_unit(x), state.get_content(a)), - [Key("expand/y"), y, a, ..] => Expand::y(state.get_unit(y), state.get_content(a)), - [Key("push"), x, y, a, ..] => Push::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), - [Key("push/x"), x, a, ..] => Push::x(state.get_unit(x), state.get_content(a)), - [Key("push/y"), y, a, ..] => Push::y(state.get_unit(y), state.get_content(a)), - [Key("pull"), x, y, a, ..] => Pull::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), - [Key("pull/x"), x, a, ..] => Pull::x(state.get_unit(x), state.get_content(a)), - [Key("pull/y"), y, a, ..] => Pull::y(state.get_unit(y), state.get_content(a)), - }) - } + //Box::new(match [items.get(0).map(|x|x.to_str()), items[1..]] { + //["when", [c, a, ..]] => When(state.get_bool(c), state.get_content(a)), + //["either", [c, a, b, ..]] => Either(state.get_bool(c), state.get_content(a), state.get_content(b)), + //["fill", [a, ..]] => Fill::xy(state.get_content(a)), + //["fill/x", [a, ..]] => Fill::x(state.get_content(a)), + //["fill/y", [a, ..]] => Fill::y(state.get_content(a)), + //["fixed", [x, y, a, ..]] => Fixed::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), + //["fixed/x", [x, a, ..]] => Fixed::x(state.get_unit(x), state.get_content(a)), + //["fixed/y", [y, a, ..]] => Fixed::y(state.get_unit(y), state.get_content(a)), + //["shrink", [x, y, a, ..]] => Shrink::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), + //["shrink/x", [x, a, ..]] => Shrink::x(state.get_unit(x), state.get_content(a)), + //["shrink/y", [y, a, ..]] => Shrink::y(state.get_unit(y), state.get_content(a)), + //["expand", [x, y, a, ..]] => Expand::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), + //["expand/x", [x, a, ..]] => Expand::x(state.get_unit(x), state.get_content(a)), + //["expand/y", [y, a, ..]] => Expand::y(state.get_unit(y), state.get_content(a)), + //["push", [x, y, a, ..]] => Push::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), + //["push/x", [x, a, ..]] => Push::x(state.get_unit(x), state.get_content(a)), + //["push/y", [y, a, ..]] => Push::y(state.get_unit(y), state.get_content(a)), + //["pull", [x, y, a, ..]] => Pull::xy(state.get_unit(x), state.get_unit(y), state.get_content(a)), + //["pull/x", [x, a, ..]] => Pull::x(state.get_unit(x), state.get_content(a)), + //["pull/y", [y, a, ..]] => Pull::y(state.get_unit(y), state.get_content(a)), + //_ => Box:: + //}) + //} } diff --git a/edn/src/lib.rs b/edn/src/lib.rs index 5fe7808e..8540d308 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -7,29 +7,29 @@ mod edn_layout; pub use self::edn_layout::*; mod edn_token; pub use self::edn_token::*; #[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> { - use Item::*; - assert_eq!(Item::read_all("")?, + use EdnItem::*; + assert_eq!(EdnItem::read_all("")?, vec![]); - assert_eq!(Item::read_all(" ")?, + assert_eq!(EdnItem::read_all(" ")?, vec![]); - assert_eq!(Item::read_all("1234")?, + assert_eq!(EdnItem::read_all("1234")?, vec![Num(1234)]); - assert_eq!(Item::read_all("1234 5 67")?, + assert_eq!(EdnItem::read_all("1234 5 67")?, vec![Num(1234), Num(5), Num(67)]); - assert_eq!(Item::read_all("foo/bar")?, + assert_eq!(EdnItem::read_all("foo/bar")?, vec![Key("foo/bar".into())]); - assert_eq!(Item::read_all(":symbol")?, + assert_eq!(EdnItem::read_all(":symbol")?, vec![Sym(":symbol".into())]); - assert_eq!(Item::read_all(" foo/bar :baz 456")?, + assert_eq!(EdnItem::read_all(" foo/bar :baz 456")?, vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)]); - assert_eq!(Item::read_all(" (foo/bar :baz 456) ")?, + assert_eq!(EdnItem::read_all(" (foo/bar :baz 456) ")?, vec![Exp(vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)])]); Ok(()) } #[cfg(test)] #[test] fn test_edn_layout () -> Result<(), ParseError> { let source = include_str!("example.edn"); - let layout = Item::read_all(source)?; + let layout = EdnItem::read_all(source)?; //panic!("{layout:?}"); //let content = >::from(&layout); Ok(()) diff --git a/engine/src/output.rs b/engine/src/output.rs index 1926b52a..7fa9a823 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -14,13 +14,6 @@ pub trait Output { #[inline] fn h (&self) -> E::Unit { self.area().h() } #[inline] fn wh (&self) -> E::Size { self.area().wh().into() } } -pub struct Thunk, F: Fn()->T + Send + Sync>(F, PhantomData); -impl, F: Fn()->T + Send + Sync> Thunk { - pub fn new (thunk: F) -> Self { Self(thunk, Default::default()) } -} -impl, F: Fn()->T + Send + Sync> Content for Thunk { - fn content (&self) -> impl Content { (self.0)() } -} pub trait Render: Send + Sync { fn layout (&self, area: E::Area) -> E::Area; fn render (&self, output: &mut E::Output); @@ -34,21 +27,19 @@ impl> Render for C { fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) } fn render (&self, output: &mut E::Output) { Content::render(self, output) } } -impl Content for Box> { +impl<'a, E: Engine> Content for Box + Send + Sync + 'a> { fn content (&self) -> impl Render { self } } -impl Content for &dyn Render { +impl Content for &(dyn Render + '_) { fn content (&self) -> impl Render { self } } -/// The platonic ideal unit of [Content]: total emptiness at dead center. -impl Content for () { - fn layout (&self, area: E::Area) -> E::Area { - let [x, y] = area.center(); - [x, y, 0.into(), 0.into()].into() - } - fn render (&self, _: &mut E::Output) {} +pub struct Thunk, F: Fn()->T + Send + Sync>(F, PhantomData); +impl, F: Fn()->T + Send + Sync> Thunk { + pub fn new (thunk: F) -> Self { Self(thunk, Default::default()) } +} +impl, F: Fn()->T + Send + Sync> Content for Thunk { + fn content (&self) -> impl Render { (self.0)() } } - impl> Content for &T { fn content (&self) -> impl Render { (*self).content() @@ -60,7 +51,14 @@ impl> Content for &T { (*self).render(output) } } - +/// The platonic ideal unit of [Content]: total emptiness at dead center. +impl Content for () { + fn layout (&self, area: E::Area) -> E::Area { + let [x, y] = area.center(); + [x, y, 0.into(), 0.into()].into() + } + fn render (&self, _: &mut E::Output) {} +} impl> Content for Option { fn content (&self) -> impl Render { self.as_ref() @@ -76,11 +74,10 @@ impl> Content for Option { } } - #[macro_export] macro_rules! render { (($self:ident:$Struct:ty) => $content:expr) => { impl Content for $Struct { - fn content (&$self) -> impl Content { Some($content) } + fn content (&$self) -> impl Render { Some($content) } } }; (|$self:ident:$Struct:ident $(< @@ -98,7 +95,7 @@ impl> Content for Option { ) => { impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Engine> for $Struct $(<$($($L)? $($T)?),+>)? { - fn content (&$self) -> impl Content<$Engine> { $content } + fn content (&$self) -> impl Render<$Engine> { $content } } }; diff --git a/layout/src/transform_xy.rs b/layout/src/transform_xy.rs index f7f6b805..654df17d 100644 --- a/layout/src/transform_xy.rs +++ b/layout/src/transform_xy.rs @@ -9,16 +9,16 @@ macro_rules! transform_xy { ($self:ident : $Enum:ident |$to:ident|$area:expr) => { pub enum $Enum { X(T), Y(T), XY(T) } impl $Enum { - pub fn x (item: T) -> Self { Self::X(item) } - pub fn y (item: T) -> Self { Self::Y(item) } + pub fn x (item: T) -> Self { Self::X(item) } + pub fn y (item: T) -> Self { Self::Y(item) } pub fn xy (item: T) -> Self { Self::XY(item) } } impl> Content for $Enum { fn content (&self) -> impl Render { match self { - Self::X(item) => item, - Self::Y(item) => item, - Self::XY(item) => item, + Self::X(item) => item, + Self::Y(item) => item, + Self::XY(item) => item, } } fn layout (&$self, $to: ::Area) -> ::Area { diff --git a/src/arranger/arranger_command.rs b/src/arranger/arranger_command.rs index 523eb9d2..283373b5 100644 --- a/src/arranger/arranger_command.rs +++ b/src/arranger/arranger_command.rs @@ -1,5 +1,6 @@ use crate::*; use ClockCommand::{Play, Pause}; +use ArrangerCommand as Cmd; #[derive(Clone, Debug)] pub enum ArrangerCommand { History(isize), @@ -15,6 +16,7 @@ use ClockCommand::{Play, Pause}; StopAll, Clear, } + #[derive(Clone, Debug)] pub enum ArrangerTrackCommand { Add, @@ -25,6 +27,7 @@ pub enum ArrangerTrackCommand { SetZoom(usize), SetColor(usize, ItemPalette), } + #[derive(Clone, Debug)] pub enum ArrangerSceneCommand { Enqueue(usize), @@ -35,6 +38,7 @@ pub enum ArrangerSceneCommand { SetZoom(usize), SetColor(usize, ItemPalette), } + #[derive(Clone, Debug)] pub enum ArrangerClipCommand { Get(usize, usize), @@ -45,7 +49,9 @@ pub enum ArrangerClipCommand { SetColor(usize, usize, ItemPalette), } -use ArrangerCommand as Cmd; +//handle!(|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input.event())); +//input_to_command!(ArrangerCommand: |state: ArrangerTui, input: Event|{KEYS_ARRANGER.handle(state, input)?}); + keymap!(KEYS_ARRANGER = |state: ArrangerTui, input: Event| ArrangerCommand { key(Char('u')) => Cmd::History(-1), key(Char('U')) => Cmd::History(1), diff --git a/src/arranger/arranger_keys.edn b/src/arranger/arranger_keys.edn new file mode 100644 index 00000000..0a8a50f4 --- /dev/null +++ b/src/arranger/arranger_keys.edn @@ -0,0 +1,40 @@ +(def keys + (u :undo 1) + (shift-u :redo 1) + (ctrl-k :todo "keyboard") + (space :play-toggle) + (shift-space :play-start-toggle) + (e :editor-show :pool-phrase) + (ctrl-a :scene-add) + (ctrl-t :track-add) + (tab :pool-toggle)) + +(def keys-clip + (q :clip-launch) + (c :clip-color) + (g :clip-get) + (p :clip-put) + (del :clip-del) + (, :clip-prev) + (. :clip-next) + (< :clip-swap-prev) + (> :clip-swap-next) + (l :clip-loop-toggle)) + +(def keys-scene + (q :scene-launch) + (c :scene-color) + (, :scene-prev) + (. :scene-next) + (< :scene-swap-prev) + (> :scene-swap-next) + (del :scene-delete)) + +(def keys-track + (q :track-launch) + (c :track-color) + (, :track-prev) + (. :track-next) + (< :track-swap-prev) + (> :track-swap-next) + (del :track-delete)) diff --git a/src/arranger/arranger_v_clips.rs b/src/arranger/arranger_v_clips.rs index 6dcc0d86..5b0776b2 100644 --- a/src/arranger/arranger_v_clips.rs +++ b/src/arranger/arranger_v_clips.rs @@ -18,7 +18,7 @@ impl<'a> ArrangerVClips<'a> { } } impl<'a> Content for ArrangerVClips<'a> { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { let iter = ||self.scenes.iter().zip(self.rows.iter().map(|row|row.0)); let col = Map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses)); Fill::xy(col) diff --git a/src/command.rs b/src/command.rs index 5a99fa9f..03f415d9 100644 --- a/src/command.rs +++ b/src/command.rs @@ -2,25 +2,24 @@ use crate::*; #[macro_export] macro_rules! keymap { ( - $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty + $(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty { $($key:expr => $handler:expr),* $(,)? } $(,)? ) => { pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { fallback: None, bindings: &[ $(($key, &|$state|Some($handler)),)* ] }; - input_to_command!($Command: |state: $State, input: $Input|$KEYS.handle(state, input)?); + input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?); }; ( - $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty - { $($key:expr => $handler:expr),* $(,)? }, - $default:expr + $(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty + { $($key:expr => $handler:expr),* $(,)? }, $default:expr ) => { pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { fallback: Some(&|$state, $input|Some($default)), bindings: &[ $(($key, &|$state|Some($handler)),)* ] }; - input_to_command!($Command: |state: $State, input: $Input|$KEYS.handle(state, input)?); + input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?); }; } @@ -28,7 +27,6 @@ pub struct EventMap<'a, S, I: PartialEq, C> { pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option)], pub fallback: Option<&'a dyn Fn(&S, &I) -> Option> } - impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> { pub fn handle (&self, state: &S, input: &I) -> Option { for (binding, handler) in self.bindings.iter() { @@ -43,15 +41,20 @@ impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> { } } } - pub trait Command: Send + Sync + Sized { fn execute (self, state: &mut S) -> Perhaps; fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps { Ok(self.execute(state)?.map(wrap)) } } - #[macro_export] macro_rules! input_to_command { + (<$($l:lifetime),+> $Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => { + impl<$($l),+> InputToCommand<$Input, $State> for $Command { + fn input_to_command ($state: &$State, $input: &$Input) -> Option { + Some($handler) + } + } + }; ($Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => { impl InputToCommand<$Input, $State> for $Command { fn input_to_command ($state: &$State, $input: &$Input) -> Option { @@ -74,11 +77,11 @@ pub trait InputToCommand: Command + Sized { } #[macro_export] macro_rules! command { - (|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { - impl Command<$State> for $Command { + ($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { + impl$(<$($l),+>)? Command<$State> for $Command { fn execute ($self, $state: &mut $State) -> Perhaps { Ok($handler) } } - } + }; } diff --git a/src/field.rs b/src/field.rs index 1f80e02b..7d6ba6e4 100644 --- a/src/field.rs +++ b/src/field.rs @@ -6,7 +6,7 @@ pub struct Field(pub ItemPalette, pub T, pub U) impl Content for Field where T: AsRef + Send + Sync, U: AsRef + Send + Sync { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0; row!( Tui::fg_bg(dark.rgb, darkest.rgb, "▐"), @@ -23,7 +23,7 @@ pub struct FieldV(pub ItemPalette, pub T, pub U) impl Content for FieldV where T: AsRef + Send + Sync, U: AsRef + Send + Sync { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0; let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")); let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")); diff --git a/src/groovebox.rs b/src/groovebox.rs index 741adee4..8ba0987e 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -58,16 +58,17 @@ impl<'a> Groovebox<'a> { note_buf: vec![], perf: PerfModel::default(), - view: EdnView::from(include_str!("groovebox/groovebox.edn")), + view: EdnView::new(include_str!("groovebox/groovebox.edn"))?, }) } } -has_clock!(|self: Groovebox|self.player.clock()); +has_clock!(|self: Groovebox<'a>|self.player.clock()); -impl<'a> EdnLayout<'a, Tui> for Groovebox { - fn get_bool (&self, item: &Item<&str>) -> bool { todo!() } - fn get_unit (&self, item: &Item<&str>) -> u16 { +impl<'a> EdnLayout<'a, Tui> for Groovebox<'a> { + fn get_bool (&self, item: &EdnItem<&str>) -> bool { todo!() } + fn get_unit (&self, item: &EdnItem<&str>) -> u16 { + use EdnItem::*; match item { Sym(":sample-h") => if self.compact { 0 } else { 5 }, Sym(":samples-w") => if self.compact { 4 } else { 11 }, @@ -79,7 +80,8 @@ impl<'a> EdnLayout<'a, Tui> for Groovebox { _ => 0 } } - fn get_content (&self, item: &Item<&str>) -> Box + '_> { + fn get_content (&'a self, item: &EdnItem<&str>) -> Box> { + use EdnItem::*; match item { Sym(":input-meter-l") => Box::new(Meter("L/", self.sampler.input_meter[0])), Sym(":input-meter-r") => Box::new(Meter("R/", self.sampler.input_meter[1])), @@ -103,3 +105,52 @@ impl<'a> EdnLayout<'a, Tui> for Groovebox { } } } + +/// Status bar for sequencer app +#[derive(Clone)] +pub struct GrooveboxStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) playing: bool, +} +from!(|state: &Groovebox<'_>|GrooveboxStatus = { + let samples = state.clock().chunk.load(Relaxed); + let rate = state.clock().timebase.sr.get(); + let buffer = samples as f64 / rate; + let width = state.size.w(); + Self { + width, + playing: state.clock().is_rolling(), + cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), + size: format!("{}x{}│", width, state.size.h()), + } +}); +render!(Tui: (self: GrooveboxStatus) => Fixed::y(2, lay!( + Self::help(), + Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +))); +impl GrooveboxStatus { + fn help () -> impl Content { + let single = |binding, command|row!(" ", col!( + Tui::fg(TuiTheme::yellow(), binding), + command + )); + let double = |(b1, c1), (b2, c2)|col!( + row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,), + row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,), + ); + Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!( + single("SPACE", "play/pause"), + double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), + double(("a", "append"), ("s", "set note"),), + double((",.", "length"), ("<>", "triplet"), ), + double(("[]", "phrase"), ("{}", "order"), ), + double(("q", "enqueue"), ("e", "edit"), ), + double(("c", "color"), ("", ""),), + )) + } + fn stats (&self) -> impl Content + use<'_> { + row!(&self.cpu, &self.size) + } +} diff --git a/src/groovebox/groovebox_audio.rs b/src/groovebox/groovebox_audio.rs index 812e0fb6..fed0b16c 100644 --- a/src/groovebox/groovebox_audio.rs +++ b/src/groovebox/groovebox_audio.rs @@ -1,8 +1,7 @@ use crate::*; use super::*; - -audio!(|self: Groovebox, client, scope|{ +audio!(|self: Groovebox<'a>, client, scope|{ let t0 = self.perf.get_t0(); if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { return Control::Quit diff --git a/src/groovebox/groovebox_command.rs b/src/groovebox/groovebox_command.rs index 5ab59b3b..cda5775a 100644 --- a/src/groovebox/groovebox_command.rs +++ b/src/groovebox/groovebox_command.rs @@ -14,7 +14,7 @@ pub enum GrooveboxCommand { Sampler(SamplerCommand), } -command!(|self: GrooveboxCommand, state: Groovebox|match self { +command!(<'a>|self: GrooveboxCommand, state: Groovebox<'a>|match self { Self::Enqueue(phrase) => { state.player.enqueue_next(phrase.as_ref()); None @@ -46,8 +46,10 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self { }, }); -handle!(|self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event())); -keymap!(KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { +handle!(|self: Groovebox<'static>, input| + GrooveboxCommand::execute_with_state(self, input.event())); + +keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox<'static>, input: Event| GrooveboxCommand { // Tab: Toggle compact mode key(Tab) => Cmd::Compact(!state.compact), // q: Enqueue currently edited phrase diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 0c51f2be..dba38596 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -2,16 +2,16 @@ use crate::*; use super::*; use std::marker::ConstParamTy; use tek_engine::Render; -use Item::*; +use EdnItem::*; -render!(Tui: (self: Groovebox) => self.size.of( +render!(Tui: (self: Groovebox<'a>) => self.size.of( Bsp::s(self.toolbar_view(), Bsp::n(self.selector_view(), Bsp::n(self.sample_view(), Bsp::n(self.status_view(), Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); -impl Groovebox { +impl<'a> Groovebox<'a> { fn toolbar_view (&self) -> impl Content + use<'_> { Fill::x(Fixed::y(2, lay!( Align::w(Meter("L/", self.sampler.input_meter[0])), diff --git a/src/status.rs b/src/status.rs index c3559e88..d0b3cdbe 100644 --- a/src/status.rs +++ b/src/status.rs @@ -49,55 +49,6 @@ impl SequencerStatus { } } -/// Status bar for sequencer app -#[derive(Clone)] -pub struct GrooveboxStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) playing: bool, -} -from!(|state: &Groovebox|GrooveboxStatus = { - let samples = state.clock().chunk.load(Relaxed); - let rate = state.clock().timebase.sr.get(); - let buffer = samples as f64 / rate; - let width = state.size.w(); - Self { - width, - playing: state.clock().is_rolling(), - cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), - size: format!("{}x{}│", width, state.size.h()), - } -}); -render!(Tui: (self: GrooveboxStatus) => Fixed::y(2, lay!( - Self::help(), - Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -))); -impl GrooveboxStatus { - fn help () -> impl Content { - let single = |binding, command|row!(" ", col!( - Tui::fg(TuiTheme::yellow(), binding), - command - )); - let double = |(b1, c1), (b2, c2)|col!( - row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,), - row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,), - ); - Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!( - single("SPACE", "play/pause"), - double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), - double(("a", "append"), ("s", "set note"),), - double((",.", "length"), ("<>", "triplet"), ), - double(("[]", "phrase"), ("{}", "order"), ), - double(("q", "enqueue"), ("e", "edit"), ), - double(("c", "color"), ("", ""),), - )) - } - fn stats (&self) -> impl Content + use<'_> { - row!(&self.cpu, &self.size) - } -} - /// Status bar for arranger app #[derive(Clone)] pub struct ArrangerStatus {