From 98d2107e4ef20b7c83056509e12e38bb620951d3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 4 Jan 2025 11:19:37 +0100 Subject: [PATCH] wip: compiles and runs (not enabled yet) --- Cargo.lock | 50 +++++++ edn/src/edn_context.rs | 45 ++++++ edn/src/edn_item.rs | 60 ++++++++ edn/src/edn_layout.rs | 52 +------ edn/src/edn_lib.rs | 116 --------------- edn/src/edn_token.rs | 58 ++++++++ edn/src/lib.rs | 10 +- engine/src/output.rs | 45 +++--- layout/src/align.rs | 2 +- layout/src/transform_xy.rs | 4 +- layout/src/transform_xy_unit.rs | 2 +- src/clock/clock_tui.rs | 70 +++++---- src/groovebox/groovebox_tui.rs | 251 +++++++++++++++++++------------- src/lib.rs | 2 +- src/style.rs | 30 ++-- 15 files changed, 440 insertions(+), 357 deletions(-) create mode 100644 edn/src/edn_context.rs create mode 100644 edn/src/edn_item.rs create mode 100644 edn/src/edn_token.rs diff --git a/Cargo.lock b/Cargo.lock index 50887011..aa24f594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const_panic" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -557,6 +563,33 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "konst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +dependencies = [ + "const_panic", + "konst_kernel", + "konst_proc_macros", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" +dependencies = [ + "typewit", +] + +[[package]] +name = "konst_proc_macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1389,6 +1422,8 @@ name = "tek_edn" version = "0.1.0" dependencies = [ "clojure-reader", + "konst", + "tek_layout", ] [[package]] @@ -1461,6 +1496,21 @@ dependencies = [ "winnow", ] +[[package]] +name = "typewit" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/edn/src/edn_context.rs b/edn/src/edn_context.rs new file mode 100644 index 00000000..c051d08d --- /dev/null +++ b/edn/src/edn_context.rs @@ -0,0 +1,45 @@ +use crate::*; + +#[macro_export] macro_rules! edn_context { + ($Struct:ident |$l:lifetime, $state:ident| { + $($key:literal = $field:ident: $Type:ty => $expr:expr,)* + }) => { + + #[derive(Default)] + pub struct EdnView<$l> { $($field: Option<$Type>),* } + + impl<$l> EdnView<$l> { + pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { + let imports = Self::imports_all(edn); + move |state| { + let mut context = EdnView::default(); + for import in imports.iter() { + context.import(state, import) + } + } + } + fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { + let mut imports = vec![]; + for edn in edn.iter() { + for import in Self::imports_one(edn) { + imports.push(import); + } + } + imports + } + fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { + match edn { + Edn::Symbol(import) => vec![import], + Edn::List(edn) => Self::imports_all(edn.as_slice()), + _ => vec![], + } + } + pub fn import (&mut self, $state: &$l$Struct, key: &str) { + match key { + $($key => self.$field = Some($expr),)* + _ => {} + } + } + } + } +} diff --git a/edn/src/edn_item.rs b/edn/src/edn_item.rs new file mode 100644 index 00000000..6423af47 --- /dev/null +++ b/edn/src/edn_item.rs @@ -0,0 +1,60 @@ +use crate::*; + +fn number (digits: &str) -> usize { + let mut value = 0; + for c in digits.chars() { + value = 10 * value + digit(c); + } + value +} + +const fn digit (c: char) -> usize { + match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, _ => unreachable!() } +} + +#[derive(Debug)] +pub enum ParseError { + Empty, + Unexpected(char), + Incomplete +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub enum Item> { + #[default] Nil, + Num(usize), + Sym(T), + Key(T), + Exp(Vec>), +} + +impl Item { + pub fn read_all <'a> (mut source: &'a str) -> Result, ParseError> { + let mut items = vec![]; + loop { + if source.len() == 0 { + break + } + let (remaining, token) = Token::chomp(source)?; + match Item::read(token)? { Item::Nil => {}, item => items.push(item) }; + source = remaining + } + Ok(items) + } + pub fn read <'a> (token: Token<'a>) -> Result { + use Token::*; + Ok(match token { + Nil => Item::Nil, + Num(chars, index, length) => + Self::Num(number(&chars[index..index+length])), + Sym(chars, index, length) => + Self::Sym(chars[index..index+length].to_string()), + Key(chars, index, length) => + Self::Key(chars[index..index+length].to_string()), + Exp(chars, index, length, 0) => + Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?), + _ => panic!("unclosed delimiter") + }) + } +} diff --git a/edn/src/edn_layout.rs b/edn/src/edn_layout.rs index 1e4c7d3e..46deea52 100644 --- a/edn/src/edn_layout.rs +++ b/edn/src/edn_layout.rs @@ -18,10 +18,10 @@ impl<'a, E, T: AsRef> From<&'a [Item]> for EdnContent<'a, E, T> { //pub struct EdnContent<'a, T>(T, &'a [Item]); pub trait EdnLayout { - fn get_bool (&self, item: &Item<&str>) -> bool { todo!() } - fn get_unit (&self, item: &Item<&str>) -> E::Unit { todo!() } - fn get_usize (&self, key: &str) -> usize { todo!() } - fn get_content (&self, item: &Item<&str>) -> &dyn Render { todo!() } + fn get_bool (&self, item: &Item<&str>) -> bool { false } + fn get_unit (&self, item: &Item<&str>) -> E::Unit { 0.into() } + fn get_usize (&self, key: &str) -> usize { 0 } + fn get_content (&self, item: &Item<&str>) -> Box + '_> { Box::new(()) } fn parse <'a: 'static> (&'a self, items: &[Item<&str>]) -> Box + 'a> { match items { [Key("when"), c, a, ..] => @@ -71,50 +71,6 @@ pub trait EdnLayout { } } -macro_rules! edn_context { - ($Struct:ident |$l:lifetime, $state:ident| { - $($key:literal = $field:ident: $Type:ty => $expr:expr,)* - }) => { - - #[derive(Default)] - pub struct EdnView<$l> { $($field: Option<$Type>),* } - - impl<$l> EdnView<$l> { - pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { - let imports = Self::imports_all(edn); - move |state| { - let mut context = EdnView::default(); - for import in imports.iter() { - context.import(state, import) - } - } - } - fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { - let mut imports = vec![]; - for edn in edn.iter() { - for import in Self::imports_one(edn) { - imports.push(import); - } - } - imports - } - fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { - match edn { - Edn::Symbol(import) => vec![import], - Edn::List(edn) => Self::imports_all(edn.as_slice()), - _ => vec![], - } - } - pub fn import (&mut self, $state: &$l$Struct, key: &str) { - match key { - $($key => self.$field = Some($expr),)* - _ => {} - } - } - } - } -} - //edn_ns! { EdnLayout |context, item| { //[Key("when"), Sym(condition), Sym(template)] => When( diff --git a/edn/src/edn_lib.rs b/edn/src/edn_lib.rs index d8ec815f..7233d310 100644 --- a/edn/src/edn_lib.rs +++ b/edn/src/edn_lib.rs @@ -2,122 +2,6 @@ use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; pub use clojure_reader::edn::Edn; -fn number (digits: &str) -> usize { - let mut value = 0; - for c in digits.chars() { - value = 10 * value + digit(c); - } - value -} - -const fn digit (c: char) -> usize { - match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, _ => unreachable!() } -} - -#[derive(Debug)] -pub enum ParseError { - Empty, - Unexpected(char), - Incomplete -} - -#[derive(Debug, Clone, Default, PartialEq)] -pub enum Item> { - #[default] Nil, - Num(usize), - Sym(T), - Key(T), - Exp(Vec>), -} - -impl Item { - pub fn read_all <'a> (mut source: &'a str) -> Result, ParseError> { - let mut items = vec![]; - loop { - if source.len() == 0 { - break - } - let (remaining, token) = Token::chomp(source)?; - match Item::read(token)? { Item::Nil => {}, item => items.push(item) }; - source = remaining - } - Ok(items) - } - pub fn read <'a> (token: Token<'a>) -> Result { - use Token::*; - Ok(match token { - Nil => Item::Nil, - Num(chars, index, length) => - Self::Num(number(&chars[index..index+length])), - Sym(chars, index, length) => - Self::Sym(chars[index..index+length].to_string()), - Key(chars, index, length) => - Self::Key(chars[index..index+length].to_string()), - Exp(chars, index, length, 0) => - Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?), - _ => panic!("unclosed delimiter") - }) - } -} - - -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub enum Token<'a> { - #[default] Nil, - Num(&'a str, usize, usize), - Sym(&'a str, usize, usize), - Key(&'a str, usize, usize), - Exp(&'a str, usize, usize, usize), -} - -impl<'a> Token<'a> { - fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> { - use Token::*; - let mut state = Self::default(); - for (index, c) in source.char_indices() { - state = match state { - // must begin expression - Nil => match c { - ' '|'\n'|'\r'|'\t' => Nil, - '(' => Exp(source, index, 1, 1), - ':' => Sym(source, index, 1), - '0'..='9' => Num(source, index, 1), - 'a'..='z' => Key(source, index, 1), - _ => return Err(ParseError::Unexpected(c)) - }, - Num(_, _, 0) => unreachable!(), - Sym(_, _, 0) => unreachable!(), - Key(_, _, 0) => unreachable!(), - Num(source, index, length) => match c { - '0'..='9' => Num(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))), - _ => return Err(ParseError::Unexpected(c)) - }, - Sym(source, index, length) => match c { - 'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))), - _ => return Err(ParseError::Unexpected(c)) - }, - Key(source, index, length) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))), - _ => return Err(ParseError::Unexpected(c)) - }, - Exp(source, index, length, 0) => match c { - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))), - _ => return Err(ParseError::Unexpected(c)) - }, - Exp(source, index, length, depth) => match c { - ')' => Exp(source, index, length + 1, depth - 1), - '(' => Exp(source, index, length + 1, depth + 1), - _ => Exp(source, index, length + 1, depth) - }, - } - } - Ok(("", state)) - } -} //#[derive(Debug, Copy, Clone, Default, PartialEq)] //pub struct Items<'a>(&'a [Item<'a>]); diff --git a/edn/src/edn_token.rs b/edn/src/edn_token.rs new file mode 100644 index 00000000..791e9657 --- /dev/null +++ b/edn/src/edn_token.rs @@ -0,0 +1,58 @@ +use crate::*; + +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub enum Token<'a> { + #[default] Nil, + Num(&'a str, usize, usize), + Sym(&'a str, usize, usize), + Key(&'a str, usize, usize), + Exp(&'a str, usize, usize, usize), +} + +impl<'a> Token<'a> { + pub fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> { + use Token::*; + let mut state = Self::default(); + for (index, c) in source.char_indices() { + state = match state { + // must begin expression + Nil => match c { + ' '|'\n'|'\r'|'\t' => Nil, + '(' => Exp(source, index, 1, 1), + ':' => Sym(source, index, 1), + '0'..='9' => Num(source, index, 1), + 'a'..='z' => Key(source, index, 1), + _ => return Err(ParseError::Unexpected(c)) + }, + Num(_, _, 0) => unreachable!(), + Sym(_, _, 0) => unreachable!(), + Key(_, _, 0) => unreachable!(), + Num(source, index, length) => match c { + '0'..='9' => Num(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Sym(source, index, length) => match c { + 'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Key(source, index, length) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Exp(source, index, length, 0) => match c { + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))), + _ => return Err(ParseError::Unexpected(c)) + }, + Exp(source, index, length, depth) => match c { + ')' => Exp(source, index, length + 1, depth - 1), + '(' => Exp(source, index, length + 1, depth + 1), + _ => Exp(source, index, length + 1, depth) + }, + } + } + Ok(("", state)) + } +} diff --git a/edn/src/lib.rs b/edn/src/lib.rs index f6f2337e..5fe7808e 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -1,8 +1,10 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] -mod edn_lib; pub use self::edn_lib::*; -mod edn_layout; pub use self::edn_layout::*; +mod edn_context; pub use self::edn_context::*; +mod edn_item; pub use self::edn_item::*; +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::*; @@ -28,7 +30,7 @@ mod edn_layout; pub use self::edn_layout::*; #[cfg(test)] #[test] fn test_edn_layout () -> Result<(), ParseError> { let source = include_str!("example.edn"); let layout = Item::read_all(source)?; - panic!("{layout:?}"); - let content = EdnLayout::from(&layout); + //panic!("{layout:?}"); + //let content = >::from(&layout); Ok(()) } diff --git a/engine/src/output.rs b/engine/src/output.rs index 1edb44f2..1926b52a 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -1,7 +1,5 @@ use crate::*; use std::marker::PhantomData; -//use std::sync::{Arc, Mutex, RwLock}; - /// Rendering target pub trait Output { /// Current output area @@ -10,31 +8,37 @@ pub trait Output { fn area_mut (&mut self) -> &mut E::Area; /// Render widget in area fn place (&mut self, area: E::Area, content: &impl Render); - #[inline] fn x (&self) -> E::Unit { self.area().x() } #[inline] fn y (&self) -> E::Unit { self.area().y() } #[inline] fn w (&self) -> E::Unit { self.area().w() } #[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 { area } - fn render (&self, output: &mut E::Output) {} + fn layout (&self, area: E::Area) -> E::Area; + fn render (&self, output: &mut E::Output); +} +pub trait Content: Send + Sync + Sized { + fn content (&self) -> impl Render { () } + fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) } + fn render (&self, output: &mut E::Output) { self.content().render(output) } } 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> { - fn content (&self) -> impl Content { self } + fn content (&self) -> impl Render { self } } impl Content for &dyn Render { - fn content (&self) -> impl Content { self } -} -pub trait Content: Send + Sync + Sized { - fn content (&self) -> impl Content { () } - fn layout (&self, area: E::Area) -> E::Area { area } - fn render (&self, output: &mut E::Output) {} + fn content (&self) -> impl Render { self } } /// The platonic ideal unit of [Content]: total emptiness at dead center. impl Content for () { @@ -46,7 +50,7 @@ impl Content for () { } impl> Content for &T { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { (*self).content() } fn layout (&self, area: E::Area) -> E::Area { @@ -58,9 +62,8 @@ impl> Content for &T { } impl> Content for Option { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { self.as_ref() - .map(|content|content.content()) } fn layout (&self, area: E::Area) -> E::Area { self.as_ref() @@ -73,18 +76,6 @@ impl> Content for Option { } } -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)() - } -} #[macro_export] macro_rules! render { (($self:ident:$Struct:ty) => $content:expr) => { diff --git a/layout/src/align.rs b/layout/src/align.rs index 80dd44f5..419d5aff 100644 --- a/layout/src/align.rs +++ b/layout/src/align.rs @@ -20,7 +20,7 @@ impl Align { } impl> Content for Align { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { &self.1 } fn layout (&self, on: E::Area) -> E::Area { diff --git a/layout/src/transform_xy.rs b/layout/src/transform_xy.rs index 78b12918..f7f6b805 100644 --- a/layout/src/transform_xy.rs +++ b/layout/src/transform_xy.rs @@ -14,7 +14,7 @@ macro_rules! transform_xy { pub fn xy (item: T) -> Self { Self::XY(item) } } impl> Content for $Enum { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { match self { Self::X(item) => item, Self::Y(item) => item, @@ -31,7 +31,7 @@ macro_rules! transform_xy { transform_xy!(self: Fill |to|{ let [x0, y0, wmax, hmax] = to.xywh(); - let [x, y, w, h] = Content::layout(&self.content(), to).xywh(); + let [x, y, w, h] = self.content().layout(to).xywh(); match self { X(_) => [x0, y, wmax, h], Y(_) => [x, y0, w, hmax], diff --git a/layout/src/transform_xy_unit.rs b/layout/src/transform_xy_unit.rs index 3475782e..a43c786f 100644 --- a/layout/src/transform_xy_unit.rs +++ b/layout/src/transform_xy_unit.rs @@ -27,7 +27,7 @@ macro_rules! transform_xy_unit { } } impl> Content for $Enum { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { Some(match self { Self::X(_, content) => content, Self::Y(_, content) => content, diff --git a/src/clock/clock_tui.rs b/src/clock/clock_tui.rs index 80d9c0c4..e7078cd5 100644 --- a/src/clock/clock_tui.rs +++ b/src/clock/clock_tui.rs @@ -2,7 +2,6 @@ use crate::*; use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync}; use FocusCommand::{Next, Prev}; use KeyCode::{Enter, Left, Right, Char}; - /// Transport clock app. pub struct TransportTui { pub jack: Arc>, @@ -10,48 +9,13 @@ pub struct TransportTui { } has_clock!(|self: TransportTui|&self.clock); audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); -handle!(|self: TransportTui, input|ClockCommand::execute_with_state(self, input.event())); -keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand { - key(Char(' ')) => - if state.clock().is_stopped() { Play(None) } else { Pause(None) }, - shift(key(Char(' '))) => - if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } -}); -// TODO: -//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { - //key(Char(',')) => SetBpm(state.bpm().get() - 1.0), - //key(Char('.')) => SetBpm(state.bpm().get() + 1.0), - //key(Char('<')) => SetBpm(state.bpm().get() - 0.001), - //key(Char('>')) => SetBpm(state.bpm().get() + 0.001), -//}); -//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand { - //key(Char(',')) => SetQuant(state.quant.prev()), - //key(Char('.')) => SetQuant(state.quant.next()), - //key(Char('<')) => SetQuant(state.quant.prev()), - //key(Char('>')) => SetQuant(state.quant.next()), -//}); -//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand { - //key(Char(',')) => SetSync(state.sync.prev()), - //key(Char('.')) => SetSync(state.sync.next()), - //key(Char('<')) => SetSync(state.sync.prev()), - //key(Char('>')) => SetSync(state.sync.next()), -//}); -//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand { - //key(Char(',')) => todo!("transport seek bar"), - //key(Char('.')) => todo!("transport seek bar"), - //key(Char('<')) => todo!("transport seek beat"), - //key(Char('>')) => todo!("transport seek beat"), -//}); render!(Tui: (self: TransportTui) => TransportView { compact: false, clock: &self.clock }); impl TransportTui { pub fn new (jack: &Arc>) -> Usually { - Ok(Self { - jack: jack.clone(), - clock: Clock::from(jack), - }) + Ok(Self { jack: jack.clone(), clock: Clock::from(jack) }) } } @@ -136,3 +100,35 @@ render!(Tui: (self: OutputStats) => Either(self.compact, Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"), ))); +handle!(|self: TransportTui, input|ClockCommand::execute_with_state(self, input.event())); +keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand { + key(Char(' ')) => + if state.clock().is_stopped() { Play(None) } else { Pause(None) }, + shift(key(Char(' '))) => + if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } +}); +// TODO: +//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => SetBpm(state.bpm().get() - 1.0), + //key(Char('.')) => SetBpm(state.bpm().get() + 1.0), + //key(Char('<')) => SetBpm(state.bpm().get() - 0.001), + //key(Char('>')) => SetBpm(state.bpm().get() + 0.001), +//}); +//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => SetQuant(state.quant.prev()), + //key(Char('.')) => SetQuant(state.quant.next()), + //key(Char('<')) => SetQuant(state.quant.prev()), + //key(Char('>')) => SetQuant(state.quant.next()), +//}); +//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand { + //key(Char(',')) => SetSync(state.sync.prev()), + //key(Char('.')) => SetSync(state.sync.next()), + //key(Char('<')) => SetSync(state.sync.prev()), + //key(Char('>')) => SetSync(state.sync.next()), +//}); +//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => todo!("transport seek bar"), + //key(Char('.')) => todo!("transport seek bar"), + //key(Char('<')) => todo!("transport seek beat"), + //key(Char('>')) => todo!("transport seek beat"), +//}); diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index b8505009..3aa1ac33 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -1,6 +1,48 @@ use crate::*; use super::*; use std::marker::ConstParamTy; +use tek_engine::Render; +use Item::*; + +impl EdnLayout for Groovebox { + fn get_bool (&self, item: &Item<&str>) -> bool { todo!() } + fn get_unit (&self, item: &Item<&str>) -> u16 { + match item { + Sym(":sample-h") => if self.compact { 0 } else { 5 }, + Sym(":samples-w") => if self.compact { 4 } else { 11 }, + Sym(":samples-y") => if self.compact { 1 } else { 0 }, + Sym(":pool-w") => if self.compact { 5 } else { + let w = self.size.w(); + if w > 60 { 20 } else if w > 40 { 15 } else { 10 } + }, + _ => 0 + } + } + fn get_content (&self, item: &Item<&str>) -> Box + '_> { + 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])), + + Sym(":transport") => Box::new(TransportView::new(true, &self.player.clock)), + Sym(":clip-play") => Box::new(ClipSelected::play_phrase(&self.player)), + Sym(":clip-next") => Box::new(ClipSelected::next_phrase(&self.player)), + Sym(":clip-edit") => Box::new(MidiEditClip(&self.editor)), + Sym(":edit-stat") => Box::new(MidiEditStatus(&self.editor)), + Sym(":pool-view") => Box::new(PoolView(self.compact, &self.pool)), + Sym(":midi-view") => Box::new(&self.editor), + + Sym(":sample-view") => Box::new(SampleViewer::from_sampler( + &self.sampler, self.editor.note_point())), + Sym(":sample-stat") => Box::new(SamplerStatus( + &self.sampler, self.editor.note_point())), + Sym(":samples-view") => Box::new(SampleList::new( + self.compact, &self.sampler, &self.editor)), + + _ => Box::new(()) + } + } +} + render!(Tui: (self: Groovebox) => self.size.of( Bsp::s(self.toolbar_view(), Bsp::n(self.selector_view(), @@ -8,109 +50,120 @@ render!(Tui: (self: Groovebox) => self.size.of( Bsp::n(self.status_view(), Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); -const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn"); - -impl Content for Groovebox { - fn content (&self) -> impl Content { - EdnView::parse(self.edn.as_slice()) +impl Groovebox { + fn toolbar_view (&self) -> impl Content + use<'_> { + Fill::x(Fixed::y(2, lay!( + Align::w(Meter("L/", self.sampler.input_meter[0])), + Align::e(Meter("R/", self.sampler.input_meter[1])), + Align::x(TransportView::new(true, &self.player.clock)), + ))) + } + fn selector_view (&self) -> impl Content + use<'_> { + row!( + ClipSelected::play_phrase(&self.player), + ClipSelected::next_phrase(&self.player), + MidiEditClip(&self.editor), + MidiEditStatus(&self.editor), + ) + } + fn sample_view (&self) -> impl Content + use<'_> { + let note_pt = self.editor.note_point(); + let sample_h = if self.compact { 0 } else { 5 }; + Max::y(sample_h, Fill::xy( + SampleViewer::from_sampler(&self.sampler, note_pt))) + } + fn status_view (&self) -> impl Content + use<'_> { + let note_pt = self.editor.note_point(); + Align::w(Fixed::y(1, SamplerStatus(&self.sampler, note_pt))) + } + fn pool_view (&self) -> impl Content + use<'_> { + let w = self.size.w(); + let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + Fixed::x(if self.compact { 5 } else { pool_w }, + PoolView(self.compact, &self.pool)) + } + fn sampler_view (&self) -> impl Content + use<'_> { + let sampler_w = if self.compact { 4 } else { 11 }; + let sampler_y = if self.compact { 1 } else { 0 }; + Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( + SampleList::new(self.compact, &self.sampler, &self.editor)))) } } -macro_rules! edn_context { - ($Struct:ident |$l:lifetime, $state:ident| { - $($key:literal = $field:ident: $Type:ty => $expr:expr,)* - }) => { +//render!(Tui: (self: Groovebox) => 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))))))))); - #[derive(Default)] - pub struct EdnView<$l> { $($field: Option<$Type>),* } +//const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn"); - impl<$l> EdnView<$l> { - pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { - let imports = Self::imports_all(edn); - move |state| { - let mut context = EdnView::default(); - for import in imports.iter() { - context.import(state, import) - } - } - } - fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { - let mut imports = vec![]; - for edn in edn.iter() { - for import in Self::imports_one(edn) { - imports.push(import); - } - } - imports - } - fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { - match edn { - Edn::Symbol(import) => vec![import], - Edn::List(edn) => Self::imports_all(edn.as_slice()), - _ => vec![], - } - } - pub fn import (&mut self, $state: &$l$Struct, key: &str) { - match key { - $($key => self.$field = Some($expr),)* - _ => {} - } - } - } - } -} - -edn_context!(Groovebox |'a, state| { - ":input-meter-l" = input_meter_l: Meter<'a> => - Meter("L/", state.sampler.input_meter[0]), - ":input-meter-r" = input_meter_r: Meter<'a> => - Meter("R/", state.sampler.input_meter[1]), - ":transport" = transport: TransportView<'a> => - TransportView::new(true, &state.player.clock), - ":clip-play" = clip_play: ClipSelected => - ClipSelected::play_phrase(&state.player), - ":clip-next" = clip_next: ClipSelected => - ClipSelected::next_phrase(&state.player), - ":clip-edit" = clip_edit: MidiEditClip<'a> => - MidiEditClip(&state.editor), - ":edit-stat" = edit_stat: MidiEditStatus<'a> => - MidiEditStatus(&state.editor), - ":sample-h" = sample_h: u16 => - if state.compact { 0 } else { 5 }, - ":sample-view" = sample_view: SampleViewer => - SampleViewer::from_sampler(&state.sampler, state.editor.note_point()), - ":sample-stat" = sample_stat: SamplerStatus<'a> => - SamplerStatus(&state.sampler, state.editor.note_point()), - ":pool-w" = pool_w: u16 => if state.compact { 5 } else { - let w = state.size.w(); - if w > 60 { 20 } else if w > 40 { 15 } else { 10 } }, - ":pool-view" = pool_view: PoolView<'a> => - PoolView(state.compact, &state.pool), - ":samples-w" = samples_w: u16 => - if state.compact { 4 } else { 11 }, - ":samples-y" = samples_y: u16 => - if state.compact { 1 } else { 0 }, - ":samples-view" = samples_view: SampleList<'a> => SampleList::new( - state.compact, &state.sampler, &state.editor), - ":midi-view" = midi_view: &'a MidiEditor => - &state.editor, -}); - -//impl Groovebox { - //fn status_view (&self) -> impl Content + use<'_> { - //let note_pt = self.editor.note_point(); - //Align::w(Fixed::y(1, )) - //} - //fn pool_view (&self) -> impl Content + use<'_> { - //let w = self.size.w(); - //let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - //Fixed::x(if self.compact { 5 } else { pool_w }, - //) - //} - //fn sampler_view (&self) -> impl Content + use<'_> { - //let sampler_w = if self.compact { 4 } else { 11 }; - //let sampler_y = if self.compact { 1 } else { 0 }; - //Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( - //SampleList::new(self.compact, &self.sampler, &self.editor)))) +//impl Content for Groovebox { + //fn content (&self) -> impl Content { + //EdnView::parse(self.edn.as_slice()) //} //} + +//macro_rules! edn_context { + //($Struct:ident |$l:lifetime, $state:ident| { + //$($key:literal = $field:ident: $Type:ty => $expr:expr,)* + //}) => { + + //#[derive(Default)] + //pub struct EdnView<$l> { $($field: Option<$Type>),* } + + //impl<$l> EdnView<$l> { + //pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { + //let imports = Self::imports_all(edn); + //move |state| { + //let mut context = EdnView::default(); + //for import in imports.iter() { + //context.import(state, import) + //} + //} + //} + //fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { + //let mut imports = vec![]; + //for edn in edn.iter() { + //for import in Self::imports_one(edn) { + //imports.push(import); + //} + //} + //imports + //} + //fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { + //match edn { + //Edn::Symbol(import) => vec![import], + //Edn::List(edn) => Self::imports_all(edn.as_slice()), + //_ => vec![], + //} + //} + //pub fn import (&mut self, $state: &$l$Struct, key: &str) { + //match key { + //$($key => self.$field = Some($expr),)* + //_ => {} + //} + //} + //} + //} +//} + +////impl Groovebox { + ////fn status_view (&self) -> impl Content + use<'_> { + ////let note_pt = self.editor.note_point(); + ////Align::w(Fixed::y(1, )) + ////} + ////fn pool_view (&self) -> impl Content + use<'_> { + ////let w = self.size.w(); + ////let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + ////Fixed::x(if self.compact { 5 } else { pool_w }, + ////) + ////} + ////fn sampler_view (&self) -> impl Content + use<'_> { + ////let sampler_w = if self.compact { 4 } else { 11 }; + ////let sampler_y = if self.compact { 1 } else { 0 }; + ////Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( + ////SampleList::new(self.compact, &self.sampler, &self.editor)))) + ////} +////} diff --git a/src/lib.rs b/src/lib.rs index a88f93d0..9f109b7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ pub(crate) use ::tek_layout::{ tek_engine::{ Usually, Perhaps, Engine, Size, Area, - Output, Content, Thunk, render, + Output, Content, Render, Thunk, render, Input, Handle, handle, kexp, kpat, tui::{ diff --git a/src/style.rs b/src/style.rs index 46ecaf8c..cc9252e8 100644 --- a/src/style.rs +++ b/src/style.rs @@ -21,11 +21,8 @@ pub trait TuiStyle { impl TuiStyle for Tui {} pub struct Bold>(pub bool, W); - impl> Content for Bold { - fn content (&self) -> impl Content { - Some(&self.1) - } + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { to.fill_bold(to.area(), self.0); self.1.render(to) @@ -33,11 +30,8 @@ impl> Content for Bold { } pub struct Foreground>(pub Color, W); - impl> Content for Foreground { - fn content (&self) -> impl Content { - Some(&self.1) - } + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { to.fill_fg(to.area(), self.0); self.1.render(to) @@ -45,11 +39,8 @@ impl> Content for Foreground { } pub struct Background>(pub Color, W); - impl> Content for Background { - fn content (&self) -> impl Content { - Some(&self.1) - } + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { to.fill_bg(to.area(), self.0); self.1.render(to) @@ -57,11 +48,8 @@ impl> Content for Background { } pub struct Styled>(pub Option