diff --git a/edn/src/edn_item.rs b/edn/src/edn_item.rs index 831df995..293e7346 100644 --- a/edn/src/edn_item.rs +++ b/edn/src/edn_item.rs @@ -38,6 +38,18 @@ impl PartialEq for EdnItem { } } } +impl EdnItem<&str> { + pub fn clone (&self) -> EdnItem { + use EdnItem::*; + match self { + Nil => Nil, + Num(u) => Num(*u), + Sym(u) => Sym(u.to_string()), + Key(u) => Key(u.to_string()), + Exp(e) => Exp(e.iter().map(|i|i.clone()).collect()), + } + } +} impl + PartialEq + Default + Clone + std::fmt::Debug> EdnItem { pub fn to_ref (&self) -> EdnItem<&str> { match self { diff --git a/edn/src/edn_view.rs b/edn/src/edn_view.rs index c7ee5802..6be8f2df 100644 --- a/edn/src/edn_view.rs +++ b/edn/src/edn_view.rs @@ -26,12 +26,15 @@ pub enum EdnView> { } impl> EdnView { - pub fn new (state: T, source: &str) -> Self { + pub fn from_source (state: T, source: &str) -> Self { match EdnItem::read_one(&source) { Ok((layout, _)) => Self::Ok(state, layout), Err(error) => Self::Err(format!("{error}")) } } + pub fn from_items (state: T, items: &[EdnItem<&str>]) -> Self { + Self::Ok(state, EdnItem::Exp(items.iter().map(|i|(*i).clone()).collect())) + } } impl + Send + Sync> Content for EdnView { diff --git a/examples/edn.rs b/examples/edn.rs index 9334c357..62ef8bb2 100644 --- a/examples/edn.rs +++ b/examples/edn.rs @@ -31,7 +31,7 @@ impl Content for Example { fn content (&self) -> impl Render { Bsp::a( Push::xy(1, 1, Align::n(format!("Example {}/{}:", self.0 + 1, EDN.len()))), - EdnView::new(self, EDN[self.0]) + EdnView::from_source(self, EDN[self.0]) ) } } diff --git a/examples/edn03.edn b/examples/edn03.edn index cac47263..e69de29b 100644 --- a/examples/edn03.edn +++ b/examples/edn03.edn @@ -1,73 +0,0 @@ -(align/c (bg/behind :bg0 (margin/xy 1 1 (col - (bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1))) - (bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2))) - (bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3))))))) - - fn content (&self) -> dyn Render { - let border_style = Style::default().fg(Color::Rgb(0,0,0)); - Align::Center(Layers::new(move|add|{ - - add(&Background(Color::Rgb(0,128,128)))?; - - add(&Margin::XY(1, 1, Stack::down(|add|{ - - add(&Layers::new(|add|{ - add(&Background(Color::Rgb(128,96,0)))?; - add(&Border(Square(border_style)))?; - add(&Margin::XY(2, 1, "..."))?; - Ok(()) - }).debug())?; - - add(&Layers::new(|add|{ - add(&Background(Color::Rgb(128,64,0)))?; - add(&Border(Lozenge(border_style)))?; - add(&Margin::XY(4, 2, "---"))?; - Ok(()) - }).debug())?; - - add(&Layers::new(|add|{ - add(&Background(Color::Rgb(96,64,0)))?; - add(&Border(SquareBold(border_style)))?; - add(&Margin::XY(6, 3, "~~~"))?; - Ok(()) - }).debug())?; - - Ok(()) - })).debug())?; - - Ok(()) - - })) - //Align::Center(Margin::X(1, Layers::new(|add|{ - //add(&Background(Color::Rgb(128,0,0)))?; - //add(&Stack::down(|add|{ - //add(&Margin::Y(1, Layers::new(|add|{ - //add(&Background(Color::Rgb(0,128,0)))?; - //add(&Align::Center("12345"))?; - //add(&Align::Center("FOO")) - //})))?; - //add(&Margin::XY(1, 1, Layers::new(|add|{ - //add(&Align::Center("1234567"))?; - //add(&Align::Center("BAR"))?; - //add(&Background(Color::Rgb(0,0,128))) - //}))) - //})) - //}))) - - //Align::Y(Layers::new(|add|{ - //add(&Background(Color::Rgb(128,0,0)))?; - //add(&Margin::X(1, Align::Center(Stack::down(|add|{ - //add(&Align::X(Margin::Y(1, Layers::new(|add|{ - //add(&Background(Color::Rgb(0,128,0)))?; - //add(&Align::Center("12345"))?; - //add(&Align::Center("FOO")) - //})))?; - //add(&Margin::XY(1, 1, Layers::new(|add|{ - //add(&Align::Center("1234567"))?; - //add(&Align::Center("BAR"))?; - //add(&Background(Color::Rgb(0,0,128))) - //})))?; - //Ok(()) - //}))))) - //})) - } diff --git a/examples/edn99.edn b/examples/edn99.edn new file mode 100644 index 00000000..cac47263 --- /dev/null +++ b/examples/edn99.edn @@ -0,0 +1,73 @@ +(align/c (bg/behind :bg0 (margin/xy 1 1 (col + (bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1))) + (bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2))) + (bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3))))))) + + fn content (&self) -> dyn Render { + let border_style = Style::default().fg(Color::Rgb(0,0,0)); + Align::Center(Layers::new(move|add|{ + + add(&Background(Color::Rgb(0,128,128)))?; + + add(&Margin::XY(1, 1, Stack::down(|add|{ + + add(&Layers::new(|add|{ + add(&Background(Color::Rgb(128,96,0)))?; + add(&Border(Square(border_style)))?; + add(&Margin::XY(2, 1, "..."))?; + Ok(()) + }).debug())?; + + add(&Layers::new(|add|{ + add(&Background(Color::Rgb(128,64,0)))?; + add(&Border(Lozenge(border_style)))?; + add(&Margin::XY(4, 2, "---"))?; + Ok(()) + }).debug())?; + + add(&Layers::new(|add|{ + add(&Background(Color::Rgb(96,64,0)))?; + add(&Border(SquareBold(border_style)))?; + add(&Margin::XY(6, 3, "~~~"))?; + Ok(()) + }).debug())?; + + Ok(()) + })).debug())?; + + Ok(()) + + })) + //Align::Center(Margin::X(1, Layers::new(|add|{ + //add(&Background(Color::Rgb(128,0,0)))?; + //add(&Stack::down(|add|{ + //add(&Margin::Y(1, Layers::new(|add|{ + //add(&Background(Color::Rgb(0,128,0)))?; + //add(&Align::Center("12345"))?; + //add(&Align::Center("FOO")) + //})))?; + //add(&Margin::XY(1, 1, Layers::new(|add|{ + //add(&Align::Center("1234567"))?; + //add(&Align::Center("BAR"))?; + //add(&Background(Color::Rgb(0,0,128))) + //}))) + //})) + //}))) + + //Align::Y(Layers::new(|add|{ + //add(&Background(Color::Rgb(128,0,0)))?; + //add(&Margin::X(1, Align::Center(Stack::down(|add|{ + //add(&Align::X(Margin::Y(1, Layers::new(|add|{ + //add(&Background(Color::Rgb(0,128,0)))?; + //add(&Align::Center("12345"))?; + //add(&Align::Center("FOO")) + //})))?; + //add(&Margin::XY(1, 1, Layers::new(|add|{ + //add(&Align::Center("1234567"))?; + //add(&Align::Center("BAR"))?; + //add(&Background(Color::Rgb(0,0,128))) + //})))?; + //Ok(()) + //}))))) + //})) + } diff --git a/src/groovebox.rs b/src/groovebox.rs index a299edcb..133827c9 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -1,3 +1,8 @@ +mod groovebox_audio; pub use self::groovebox_audio::*; +mod groovebox_command; pub use self::groovebox_command::*; +mod groovebox_tui; pub use self::groovebox_tui::*; +mod groovebox_edn; pub use self::groovebox_edn::*; + use crate::*; use super::*; use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; @@ -6,10 +11,6 @@ use GrooveboxCommand as Cmd; use MidiEditCommand::*; use PhrasePoolCommand::*; -mod groovebox_audio; pub use self::groovebox_audio::*; -mod groovebox_command; pub use self::groovebox_command::*; -mod groovebox_tui; pub use self::groovebox_tui::*; - pub struct Groovebox { _jack: Arc>, pub player: MidiPlayer, @@ -24,7 +25,7 @@ pub struct Groovebox { pub midi_buf: Vec>>, pub perf: PerfModel, } - +has_clock!(|self: Groovebox|self.player.clock()); impl Groovebox { pub fn new ( jack: &Arc>, @@ -59,91 +60,3 @@ impl Groovebox { } } -has_clock!(|self: Groovebox|self.player.clock()); - -impl EdnViewData for &Groovebox { - fn get_bool (&self, item: EdnItem<&str>) -> bool { todo!() } - 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 - } - } - fn get_content <'a> (&'a self, item: EdnItem<&str>) -> RenderBox<'a, TuiOut> { - use EdnItem::*; - match item.to_str() { - ":input-meter-l" => Box::new(Meter("L/", self.sampler.input_meter[0])), - ":input-meter-r" => Box::new(Meter("R/", self.sampler.input_meter[1])), - - ":transport" => Box::new(TransportView::new(true, &self.player.clock)), - ":clip-play" => Box::new(ClipSelected::play_phrase(&self.player)), - ":clip-next" => Box::new(ClipSelected::next_phrase(&self.player)), - ":clip-edit" => Box::new(MidiEditClip(&self.editor)), - ":edit-stat" => Box::new(MidiEditStatus(&self.editor)), - ":pool-view" => Box::new(PoolView(self.compact, &self.pool)), - ":midi-view" => Box::new(&self.editor), - - ":sample-view" => Box::new(SampleViewer::from_sampler(&self.sampler, self.editor.note_point())), - ":sample-stat" => Box::new(SamplerStatus(&self.sampler, self.editor.note_point())), - ":samples-view" => Box::new(SampleList::new(self.compact, &self.sampler, &self.editor)), - - _ => panic!("{item:?}") - } - } -} - -/// 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!(TuiOut: (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_edn.rs b/src/groovebox/groovebox_edn.rs new file mode 100644 index 00000000..2e7bc79c --- /dev/null +++ b/src/groovebox/groovebox_edn.rs @@ -0,0 +1,44 @@ +use crate::*; + +impl EdnViewData for &Groovebox { + 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 + } + } + fn get_content <'a> (&'a self, item: EdnItem<&str>) -> RenderBox<'a, TuiOut> { + use EdnItem::*; + match item { + Nil => Box::new(()), + Sym(bol) => match bol { + ":input-meter-l" => Meter("L/", self.sampler.input_meter[0]).boxed(), + ":input-meter-r" => Box::new(Meter("R/", self.sampler.input_meter[1])), + + ":transport" => Box::new(TransportView::new(true, &self.player.clock)), + ":clip-play" => Box::new(ClipSelected::play_phrase(&self.player)), + ":clip-next" => Box::new(ClipSelected::next_phrase(&self.player)), + ":clip-edit" => Box::new(MidiEditClip(&self.editor)), + ":edit-stat" => Box::new(MidiEditStatus(&self.editor)), + ":pool-view" => Box::new(PoolView(self.compact, &self.pool)), + ":midi-view" => Box::new(&self.editor), + + ":sample-view" => Box::new(SampleViewer::from_sampler(&self.sampler, self.editor.note_point())), + ":sample-stat" => Box::new(SamplerStatus(&self.sampler, self.editor.note_point())), + ":samples-view" => Box::new(SampleList::new(self.compact, &self.sampler, &self.editor)), + + _ => panic!("unknown sym {bol:?}") + }, + Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), + _ => panic!("no content for {item:?}") + } + } +} + diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 966cdcf6..f8ddb10b 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -8,10 +8,11 @@ const EDN: &'static str = include_str!("groovebox.edn"); impl Content for Groovebox { fn content (&self) -> impl Render { - self.size.of(EdnView::new(self, EDN)) + self.size.of(EdnView::from_source(self, EDN)) } } +// this works: //render!(TuiOut: (self: Groovebox) => self.size.of( //Bsp::s(self.toolbar_view(), //Bsp::n(self.selector_view(), @@ -59,6 +60,54 @@ impl 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!(TuiOut: (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) + //} +//} //render!(TuiOut: (self: Groovebox) => self.size.of( //Bsp::s(self.toolbar_view(), //Bsp::n(self.selector_view(),