/////////////////////////////////////////////////////////////////////////////////////////////////// //#[cfg(test)] mod test_focus { //use super::focus::*; //#[test] fn test_focus () { //struct FocusTest { //focused: char, //cursor: (usize, usize) //} //impl HasFocus for FocusTest { //type Item = char; //fn focused (&self) -> Self::Item { //self.focused //} //fn set_focused (&mut self, to: Self::Item) { //self.focused = to //} //} //impl FocusGrid for FocusTest { //fn focus_cursor (&self) -> (usize, usize) { //self.cursor //} //fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { //&mut self.cursor //} //fn focus_layout (&self) -> &[&[Self::Item]] { //&[ //&['a', 'a', 'a', 'b', 'b', 'd'], //&['a', 'a', 'a', 'b', 'b', 'd'], //&['a', 'a', 'a', 'c', 'c', 'd'], //&['a', 'a', 'a', 'c', 'c', 'd'], //&['e', 'e', 'e', 'e', 'e', 'e'], //] //} //} //let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; //tester.focus_right(); //assert_eq!(tester.cursor.0, 3); //assert_eq!(tester.focused, 'b'); //tester.focus_down(); //assert_eq!(tester.cursor.1, 2); //assert_eq!(tester.focused, 'c'); //} //} //use crate::*; //struct TestEngine([u16;4], Vec>); //impl Engine for TestEngine { //type Unit = u16; //type Size = [Self::Unit;2]; //type Area = [Self::Unit;4]; //type Input = Self; //type Handled = bool; //fn exited (&self) -> bool { //true //} //} //#[derive(Copy, Clone)] //struct TestArea(u16, u16); //impl Render for TestArea { //fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { //Ok(Some([to[0], to[1], self.0, self.1])) //} //fn render (&self, to: &mut TestEngine) -> Perhaps<[u16;4]> { //if let Some(layout) = self.layout(to.area())? { //for y in layout.y()..layout.y()+layout.h()-1 { //for x in layout.x()..layout.x()+layout.w()-1 { //to.1[y as usize][x as usize] = '*'; //} //} //Ok(Some(layout)) //} else { //Ok(None) //} //} //} //#[test] //fn test_plus_minus () -> Usually<()> { //let area = [0, 0, 10, 10]; //let engine = TestEngine(area, vec![vec![' ';10];10]); //let test = TestArea(4, 4); //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); //assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4])); //Ok(()) //} //#[test] //fn test_outset_align () -> Usually<()> { //let area = [0, 0, 10, 10]; //let engine = TestEngine(area, vec![vec![' ';10];10]); //let test = TestArea(4, 4); //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); //assert_eq!(Margin::X(1, test).layout(area)?, Some([0, 0, 6, 4])); //assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4])); //assert_eq!(Align::X(Margin::X(1, test)).layout(area)?, Some([2, 0, 6, 4])); //assert_eq!(Margin::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4])); //Ok(()) //} ////#[test] ////fn test_misc () -> Usually<()> { ////let area: [u16;4] = [0, 0, 10, 10]; ////let test = TestArea(4, 4); ////assert_eq!(test.layout(area)?, ////Some([0, 0, 4, 4])); ////assert_eq!(Align::Center(test).layout(area)?, ////Some([3, 3, 4, 4])); ////assert_eq!(Align::Center(Stack::down(|add|{ ////add(&test)?; ////add(&test) ////})).layout(area)?, ////Some([3, 1, 4, 8])); ////assert_eq!(Align::Center(Stack::down(|add|{ ////add(&Margin::XY(2, 2, test))?; ////add(&test) ////})).layout(area)?, ////Some([2, 0, 6, 10])); ////assert_eq!(Align::Center(Stack::down(|add|{ ////add(&Margin::XY(2, 2, test))?; ////add(&Padding::XY(2, 2, test)) ////})).layout(area)?, ////Some([2, 1, 6, 8])); ////assert_eq!(Stack::down(|add|{ ////add(&Margin::XY(2, 2, test))?; ////add(&Padding::XY(2, 2, test)) ////}).layout(area)?, ////Some([0, 0, 6, 8])); ////assert_eq!(Stack::right(|add|{ ////add(&Stack::down(|add|{ ////add(&Margin::XY(2, 2, test))?; ////add(&Padding::XY(2, 2, test)) ////}))?; ////add(&Align::Center(TestArea(2 ,2))) ////}).layout(area)?, ////Some([0, 0, 8, 8])); ////Ok(()) ////} ////#[test] ////fn test_offset () -> Usually<()> { ////let area: [u16;4] = [50, 50, 100, 100]; ////let test = TestArea(3, 3); ////assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3])); ////assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3])); ////assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3])); ////Ok(()) ////} ////#[test] ////fn test_outset () -> Usually<()> { ////let area: [u16;4] = [50, 50, 100, 100]; ////let test = TestArea(3, 3); ////assert_eq!(Margin::X(1, test).layout(area)?, Some([49, 50, 5, 3])); ////assert_eq!(Margin::Y(1, test).layout(area)?, Some([50, 49, 3, 5])); ////assert_eq!(Margin::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5])); ////Ok(()) ////} ////#[test] ////fn test_padding () -> Usually<()> { ////let area: [u16;4] = [50, 50, 100, 100]; ////let test = TestArea(3, 3); ////assert_eq!(Padding::X(1, test).layout(area)?, Some([51, 50, 1, 3])); ////assert_eq!(Padding::Y(1, test).layout(area)?, Some([50, 51, 3, 1])); ////assert_eq!(Padding::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1])); ////Ok(()) ////} ////#[test] ////fn test_stuff () -> Usually<()> { ////let area: [u16;4] = [0, 0, 100, 100]; ////assert_eq!("1".layout(area)?, ////Some([0, 0, 1, 1])); ////assert_eq!("333".layout(area)?, ////Some([0, 0, 3, 1])); ////assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?, ////Some([0, 0, 3, 1])); ////assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?, ////Some([0, 0, 3, 2])); ////assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?, ////Some([0, 0, 4, 1])); ////assert_eq!(Stack::down(|add|{ ////add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?; ////add(&"55555") ////}).layout(area)?, ////Some([0, 0, 5, 2])); ////let area: [u16;4] = [1, 1, 100, 100]; ////assert_eq!(Margin::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, ////Some([0, 1, 6, 1])); ////assert_eq!(Margin::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, ////Some([1, 0, 4, 3])); ////assert_eq!(Margin::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, ////Some([0, 0, 6, 3])); ////assert_eq!(Stack::down(|add|{ ////add(&Margin::XY(1, 1, "1"))?; ////add(&Margin::XY(1, 1, "333")) ////}).layout(area)?, ////Some([1, 1, 5, 6])); ////let area: [u16;4] = [1, 1, 95, 100]; ////assert_eq!(Align::Center(Stack::down(|add|{ ////add(&Margin::XY(1, 1, "1"))?; ////add(&Margin::XY(1, 1, "333")) ////})).layout(area)?, ////Some([46, 48, 5, 6])); ////assert_eq!(Align::Center(Stack::down(|add|{ ////add(&Layers::new(|add|{ //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,128,0))))?; ////add(&Margin::XY(1, 1, "1"))?; ////add(&Margin::XY(1, 1, "333"))?; //////add(&Background(Color::Rgb(0,128,0)))?; ////Ok(()) ////}))?; ////add(&Layers::new(|add|{ //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,0,128))))?; ////add(&Margin::XY(1, 1, "555"))?; ////add(&Margin::XY(1, 1, "777777"))?; //////add(&Background(Color::Rgb(0,0,128)))?; ////Ok(()) ////})) ////})).layout(area)?, ////Some([46, 48, 5, 6])); ////Ok(()) ////} //#[derive(Default)] pub struct Sequencer { //pub jack: Arc>, //pub compact: bool, //pub editor: MidiEditor, //pub midi_buf: Vec>>, //pub note_buf: Vec, //pub perf: PerfModel, //pub player: MidiPlayer, //pub pool: MidiPool, //pub selectors: bool, //pub size: Measure, //pub status: bool, //pub transport: bool, //} //has_size!(|self:Sequencer|&self.size); //has_clock!(|self:Sequencer|&self.player.clock); //has_clips!(|self:Sequencer|self.pool.clips); //has_editor!(|self:Sequencer|self.editor); //has_player!(|self:Sequencer|self.player); //#[derive(Default)] pub struct Groovebox { //pub jack: Arc>, //pub compact: bool, //pub editor: MidiEditor, //pub midi_buf: Vec>>, //pub note_buf: Vec, //pub perf: PerfModel, //pub player: MidiPlayer, //pub pool: MidiPool, //pub sampler: Sampler, //pub size: Measure, //pub status: bool, //} //has_clock!(|self: Groovebox|self.player.clock()); //#[derive(Default)] pub struct Arranger { //pub clock: Clock, //pub color: ItemPalette, //pub compact: bool, //pub editing: AtomicBool, //pub editor: MidiEditor, //pub jack: Arc>, //pub midi_buf: Vec>>, //pub midi_ins: Vec>, //pub midi_outs: Vec>, //pub note_buf: Vec, //pub perf: PerfModel, //pub pool: MidiPool, //pub scenes: Vec, //pub selected: ArrangerSelection, //pub size: Measure, //pub splits: [u16;2], //pub tracks: Vec, //} //has_clock!(|self: Arranger|&self.clock); //has_clips!(|self: Arranger|self.pool.clips); //has_editor!(|self: Arranger|self.editor); /////////////////////////////////////////////////////////////////////////////////////////////////// //render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN))); //impl EdnViewData for &Sequencer { //fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { //use EdnItem::*; //match item { //Nil => Box::new(()), //Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), //Sym(":editor") => (&self.editor).boxed(), //Sym(":pool") => self.pool_view().boxed(), //Sym(":status") => self.status_view().boxed(), //Sym(":toolbar") => self.toolbar_view().boxed(), //_ => panic!("no content for {item:?}") //} //} //} //impl Sequencer { //const EDN: &'static str = include_str!("../edn/sequencer.edn"); //fn toolbar_view (&self) -> impl Content + use<'_> { //Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.player.clock)))) //} //fn status_view (&self) -> impl Content + use<'_> { //Bsp::e( //When(self.selectors, Bsp::e( //self.player.play_status(), //self.player.next_status(), //)), //Bsp::e( //self.editor.clip_status(), //self.editor.edit_status(), //) //) //} //fn pool_view (&self) -> impl Content + use<'_> { //let w = self.size.w(); //let clip_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; //let pool_w = if self.pool.visible { clip_w } else { 0 }; //let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); //Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) //} //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(("[]", "clip"), ("{}", "order"), ), //double(("q", "enqueue"), ("e", "edit"), ), //double(("c", "color"), ("", ""),), //)) //} //} ///////////////////////////////////////////////////////////////////////////////////////////////////// //render!(TuiOut: (self: Groovebox) => self.size.of(EdnView::from_source(self, Self::EDN))); //impl EdnViewData for &Groovebox { //fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { //use EdnItem::*; //match item { //Nil => Box::new(()), //Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), //Sym(":editor") => (&self.editor).boxed(), //Sym(":pool") => self.pool().boxed(), //Sym(":status") => self.status().boxed(), //Sym(":toolbar") => self.toolbar().boxed(), //Sym(":sampler") => self.sampler().boxed(), //Sym(":sample") => self.sample().boxed(), //_ => panic!("no content for {item:?}") //} //} //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 //} //} //} //impl Groovebox { //const EDN: &'static str = include_str!("../edn/groovebox.edn"); //fn toolbar (&self) -> impl Content + use<'_> { //Fill::x(Fixed::y(2, lay!( //Fill::x(Align::w(Meter("L/", self.sampler.input_meter[0]))), //Fill::x(Align::e(Meter("R/", self.sampler.input_meter[1]))), //Align::x(ClockView::new(true, &self.player.clock)), //))) //} //fn status (&self) -> impl Content + use<'_> { //row!( //self.player.play_status(), //self.player.next_status(), //self.editor.clip_status(), //self.editor.edit_status(), //) //} //fn sample (&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( //Bsp::a( //Fill::x(Align::w(Fixed::y(1, self.sampler.status(note_pt)))), //self.sampler.viewer(note_pt)))) //} //fn pool (&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 (&self) -> impl Content + use<'_> { //let note_pt = self.editor.note_point(); //let sampler_w = if self.compact { 4 } else { 40 }; //let sampler_y = if self.compact { 1 } else { 0 }; //Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(self.sampler.list(self.compact, &self.editor)))) //} //} ///// 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) //} //} //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 (&self) -> impl Content + use<'_> { ////let note_pt = self.editor.note_point(); ////Align::w(Fixed::y(1, )) ////} ////fn pool (&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 (&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)))) ////} ////} /////////////////////////////////////////////////////////////////////////////////////////////////// //render!(TuiOut: (self: Arranger) => self.size.of(EdnView::from_source(self, Self::EDN))); //impl EdnViewData for &Arranger { //fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { //use EdnItem::*; //let tracks_w = self.tracks_with_sizes().last().unwrap().3 as u16; //match item { //Nil => Box::new(()), //Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), //Sym(":editor") => (&self.editor).boxed(), //Sym(":pool") => self.pool().boxed(), //Sym(":status") => self.status().boxed(), //Sym(":toolbar") => self.toolbar().boxed(), //Sym(":tracks") => self.track_row(tracks_w).boxed(), //Sym(":scenes") => self.scene_row(tracks_w).boxed(), //Sym(":inputs") => self.input_row(tracks_w).boxed(), //Sym(":outputs") => self.output_row(tracks_w).boxed(), //_ => panic!("no content for {item:?}") //} //} //} //impl Arranger { //const EDN: &'static str = include_str!("../edn/arranger.edn"); //pub const LEFT_SEP: char = '▎'; //fn toolbar (&self) -> impl Content + use<'_> { //Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))) //} //fn pool (&self) -> impl Content + use<'_> { //Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact, &self.pool))) //} //fn status (&self) -> impl Content + use<'_> { //Bsp::e(self.editor.clip_status(), self.editor.edit_status()) //} //fn is_editing (&self) -> bool { // !self.pool.visible //} //fn sidebar_w (&self) -> u16 { //let w = self.size.w(); //let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; //let w = if self.pool.visible { w } else { 8 }; //w //} //fn editor_w (&self) -> usize { ////self.editor.note_len() / self.editor.note_zoom().get() //(5 + (self.editor.time_len().get() / self.editor.time_zoom().get())) //.min(self.size.w().saturating_sub(20)) //.max(16) ////self.editor.time_axis().get().max(16) ////50 //} //pub fn scenes_with_sizes (&self, h: usize) //-> impl Iterator //{ //let mut y = 0; //let editing = self.is_editing(); //let (selected_track, selected_scene) = match self.selected { //ArrangerSelection::Clip(t, s) => (Some(t), Some(s)), //_ => (None, None) //}; //self.scenes.iter().enumerate().map(move|(s, scene)|{ //let active = editing && selected_track.is_some() && selected_scene == Some(s); //let height = if active { 15 } else { h }; //let data = (s, scene, y, y + height); //y += height; //data //}) //} //pub fn tracks_with_sizes (&self) //-> impl Iterator //{ //tracks_with_sizes(self.tracks.iter(), match self.selected { //ArrangerSelection::Track(t) if self.is_editing() => Some(t), //ArrangerSelection::Clip(t, _) if self.is_editing() => Some(t), //_ => None //}, self.editor_w()) //} //fn play_row (&self, tracks_w: u16) -> impl Content + '_ { //let h = 2; //Fixed::y(h, Bsp::e( //Fixed::xy(self.sidebar_w() as u16, h, self.play_header()), //Fill::x(Align::c(Fixed::xy(tracks_w, h, self.play_cells()))) //)) //} //fn play_header (&self) -> BoxThunk { //(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Playing")).boxed()).into() //} //fn play_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { ////let color = track.color; //let color: ItemPalette = track.color.dark.into(); //let timebase = self.clock().timebase(); //let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb, //if let Some((_, Some(clip))) = track.player.play_clip().as_ref() { //let length = clip.read().unwrap().length; //let elapsed = track.player.pulses_since_start().unwrap() as usize; //format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64)) //} else { //String::new() //}); //let cell = Bsp::s(value, phat_hi(color.dark.rgb, color.darker.rgb)); //Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell)) //})).boxed()).into() //} //fn next_row (&self, tracks_w: u16) -> impl Content + '_ { //let h = 2; //Fixed::y(h, Bsp::e( //Fixed::xy(self.sidebar_w() as u16, h, self.next_header()), //Fill::x(Align::c(Fixed::xy(tracks_w, h, self.next_cells()))) //)) //} //fn next_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Next")).boxed()).into() //} //fn next_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { //let color: ItemPalette = track.color; //let color: ItemPalette = track.color.dark.into(); //let current = &self.clock().playhead; //let timebase = ¤t.timebase; //let cell = Self::cell(color, Tui::bold(true, { //let mut result = String::new(); //if let Some((t, _)) = track.player.next_clip().as_ref() { //let target = t.pulse.get(); //let current = current.pulse.get(); //if target > current { //result = format!("-{:>}", timebase.format_beats_0_short(target - current)) //} //} //result //})); //let cell = Tui::fg_bg(color.lightest.rgb, color.base.rgb, cell); //let cell = Bsp::s(cell, phat_hi(color.dark.rgb, color.darker.rgb)); //Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell)) //})).boxed()).into() //} ///// beats until switchover //fn cell_until_next (track: &ArrangerTrack, current: &Arc) //-> Option> //{ //let timebase = ¤t.timebase; //let mut result = String::new(); //if let Some((t, _)) = track.player.next_clip().as_ref() { //let target = t.pulse.get(); //let current = current.pulse.get(); //if target > current { //result = format!("-{:>}", timebase.format_beats_0_short(target - current)) //} //} //Some(result) //} //fn track_row (&self, tracks_w: u16) -> impl Content + '_ { //let h = 3; //let border = |x|x;//Rugged(Style::default().fg(Color::Rgb(0,0,0)).bg(Color::Reset)).enclose2(x); //Fixed::y(h, Bsp::e( //Fixed::xy(self.sidebar_w() as u16, h, self.track_header()), //Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.track_cells())))) //)) //} //fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(||Tui::bold(true, Bsp::s( //row!( //Tui::fg(TuiTheme::g(128), "add "), //Tui::fg(TuiTheme::orange(), "t"), //Tui::fg(TuiTheme::g(128), "rack"), //), //row!( //Tui::fg(TuiTheme::orange(), "a"), //Tui::fg(TuiTheme::g(128), "dd scene"), //), //).boxed())).into() //} //fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //let iter = ||self.tracks_with_sizes(); //(move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { //let name = Push::x(1, &track.name); //let color = track.color; //let fg = color.lightest.rgb; //let bg = color.base.rgb; //let active = self.selected.track() == Some(i); //let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) }; //let border = Style::default().fg(bfg).bg(bg); //Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16, //Outer(border).enclose(Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::x(name))))) //)) //})).boxed()).into() //} //fn input_row (&self, tracks_w: u16) -> impl Content + '_ { //let h = 2 + self.midi_ins[0].connect.len() as u16; //Fixed::y(h, Bsp::e( //Fixed::xy(self.sidebar_w() as u16, h, self.input_header()), //Fill::x(Align::c(Fixed::xy(tracks_w, h, self.input_cells()))) //)) //} //fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(||Bsp::s( //Tui::bold(true, row!( //Tui::fg(TuiTheme::g(128), "midi "), //Tui::fg(TuiTheme::orange(), "I"), //Tui::fg(TuiTheme::g(128), "ns"), //)), //Bsp::s( //Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), //Align::w(&self.midi_ins[0].name)))), //self.midi_ins.get(0) //.and_then(|midi_in|midi_in.connect.get(0)) //.map(|connect|Fill::x(Align::w( //Tui::bold(false, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), connect.info()))))) //) //).boxed()).into() //} //fn input_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { //let w = (x2 - x1) as u16; //let color: ItemPalette = track.color.dark.into(); //map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, Bsp::n( //Self::rec_mon(color.base.rgb, false, false), //phat_hi(color.base.rgb, color.dark.rgb) //)))) //})).boxed()).into() //} //fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content { //row!( //Tui::fg_bg(if rec { Color::Red } else { bg }, bg, "▐"), //Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"), //Tui::fg_bg(if rec { Color::White } else { bg }, bg, "▐"), //Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"), //Tui::fg_bg(if mon { Color::White } else { bg }, bg, "▌"), //) //} //fn output_row (&self, tracks_w: u16) -> impl Content + '_ { //let h = 2 + self.midi_outs[0].connect.len() as u16; //Fixed::y(h, Bsp::e( //Fixed::xy(self.sidebar_w() as u16, h, self.output_header()), //Fill::x(Align::c(Fixed::xy(tracks_w, h, self.output_cells()))) //)) //} //fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(||Bsp::s( //Tui::bold(true, row!( //Tui::fg(TuiTheme::g(128), "midi "), //Tui::fg(TuiTheme::orange(), "O"), //Tui::fg(TuiTheme::g(128), "uts"), //)), //Bsp::s( //Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), //Align::w(&self.midi_outs[0].name)))), //self.midi_outs.get(0) //.and_then(|midi_out|midi_out.connect.get(0)) //.map(|connect|Fill::x(Align::w( //Tui::bold(false, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), connect.info()))))) //), //).boxed()).into() //} //fn output_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { //let w = (x2 - x1) as u16; //let color: ItemPalette = track.color.dark.into(); //map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, Bsp::n( //Self::mute_solo(color.base.rgb, false, false), //phat_hi(color.dark.rgb, color.darker.rgb) //)))) //})).boxed()).into() //} //fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content { //row!( //Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"), //Tui::fg_bg(if mute { Color::White } else { bg }, bg, "▐"), //Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"), //) //} //fn scene_row (&self, tracks_w: u16) -> impl Content + '_ { //let h = (self.size.h() as u16).saturating_sub(8).max(8); //let border = |x|x;//Skinny(Style::default().fg(Color::Rgb(0,0,0)).bg(Color::Reset)).enclose2(x); //Bsp::e( //Tui::bg(Color::Reset, Fixed::xy(self.sidebar_w() as u16, h, self.scene_headers())), //Tui::bg(Color::Reset, Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.scene_cells()))))) //) //} //fn scene_headers <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //(||{ //let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); //let selected = self.selected.scene(); //Fill::y(Align::c(Map::new(||self.scenes_with_sizes(2), move|(_, scene, y1, y2), i| { //let h = (y2 - y1) as u16; //let name = format!("🭬{}", &scene.name); //let color = scene.color; //let active = selected == Some(i); //let mid = if active { color.light } else { color.base }; //let top = Some(last_color.read().unwrap().base.rgb); //let cell = phat_sel_3( //active, //Tui::bold(true, name.clone()), //Tui::bold(true, name), //top, //mid.rgb, //Color::Rgb(0, 0, 0) //); //*last_color.write().unwrap() = color; //map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell)) //}))).boxed() //}).into() //} //fn scene_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { //let editing = self.is_editing(); //let tracks = move||self.tracks_with_sizes(); //let scenes = ||self.scenes_with_sizes(2); //let selected_track = self.selected.track(); //let selected_scene = self.selected.scene(); //(move||Fill::y(Align::c(Map::new(tracks, move|(_, track, x1, x2), t| { //let w = (x2 - x1) as u16; //let color: ItemPalette = track.color.dark.into(); //let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); //let cells = Map::new(scenes, move|(_, scene, y1, y2), s| { //let h = (y2 - y1) as u16; //let color = scene.color; //let (name, fg, bg) = if let Some(c) = &scene.clips[t] { //let c = c.read().unwrap(); //(c.name.to_string(), c.color.lightest.rgb, c.color.base.rgb) //} else { //("⏹ ".to_string(), TuiTheme::g(64), TuiTheme::g(32)) //}; //let last = last_color.read().unwrap().clone(); //let active = editing && selected_scene == Some(s) && selected_track == Some(t); //let editor = Thunk::new(||&self.editor); //let cell = Thunk::new(move||phat_sel_3( //selected_track == Some(t) && selected_scene == Some(s), //Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), //Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), //if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) { //None //} else { //Some(bg.into()) //}, //bg.into(), //bg.into(), //)); //let cell = Either(active, editor, cell); //*last_color.write().unwrap() = bg.into(); //map_south( //y1 as u16, //h + 1, //Fill::x(Fixed::y(h + 1, cell)) //) //}); //let column = Fixed::x(w, Tui::bg(Color::Reset, Align::y(cells)).boxed()); //Fixed::x(w, map_east(x1 as u16, w, column)) //}))).boxed()).into() //} //fn cell_clip <'a> ( //scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 //) -> impl Content + use<'a> { //scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{ //let clip = clip.read().unwrap(); //let mut bg = TuiTheme::border_bg(); //let name = clip.name.to_string(); //let max_w = name.len().min((w as usize).saturating_sub(2)); //let color = clip.color; //bg = color.dark.rgb; //if let Some((_, Some(ref playing))) = track.player.play_clip() { //if *playing.read().unwrap() == *clip { //bg = color.light.rgb //} //}; //Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w])))); //})) //} //fn track_column_separators <'a> (&'a self) -> impl Content + 'a { //let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16); //let fg = Color::Rgb(64,64,64); //Map::new(move||self.tracks_with_sizes(), move|(_n, _track, x1, x2), _i|{ //Push::x(scenes_w, map_east(x1 as u16, (x2 - x1) as u16, //Fixed::x((x2 - x1) as u16, Tui::fg(fg, RepeatV(&"·"))))) //}) //} //pub fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> { //let mut widths = vec![]; //let mut total = 0; //for track in tracks.iter() { //let width = track.width; //widths.push((width, total)); //total += width; //} //widths.push((0, total)); //widths //} //fn scene_row_sep <'a> (&'a self) -> impl Content + 'a { //let fg = Color::Rgb(255,255,255); //Map::new(move||self.scenes_with_sizes(1), |_, _|"") ////Map(||rows.iter(), |(_n, _scene, y1, _y2), _i| { ////let y = to.area().y() + (y / PPQ) as u16 + 1; ////if y >= to.buffer.area.height { break } ////for x in to.area().x()..to.area().x2().saturating_sub(2) { //////if x < to.buffer.area.x && y < to.buffer.area.y { ////if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { ////cell.modifier = Modifier::UNDERLINED; ////cell.underline_color = fg; ////} //////} ////} ////}) //} //pub fn scene_heights (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> { //let mut total = 0; //if factor == 0 { //scenes.iter().map(|scene|{ //let pulses = scene.pulses().max(PPQ); //total += pulses; //(pulses, total - pulses) //}).collect() //} else { //(0..=scenes.len()).map(|i|{ //(factor*PPQ, factor*PPQ*i) //}).collect() //} //} //fn cursor (&self) -> impl Content + '_ { //let color = self.color; //let bg = color.lighter.rgb;//Color::Rgb(0, 255, 0); //let selected = self.selected(); //let cols = Arranger::track_widths(&self.tracks); //let rows = Arranger::scene_heights(&self.scenes, 1); //let scenes_w = 16.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16); //let focused = true; //let reticle = Reticle(Style { //fg: Some(self.color.lighter.rgb), //bg: None, //underline_color: None, //add_modifier: Modifier::empty(), //sub_modifier: Modifier::DIM //}); //RenderThunk::new(move|to: &mut TuiOut|{ //let area = to.area(); //let [x, y, w, h] = area.xywh(); //let mut track_area: Option<[u16;4]> = match selected { //ArrangerSelection::Track(t) | ArrangerSelection::Clip(t, _) => Some([ //x + scenes_w + cols[t].1 as u16, y, //cols[t].0 as u16, h, //]), //_ => None //}; //let mut scene_area: Option<[u16;4]> = match selected { //ArrangerSelection::Scene(s) | ArrangerSelection::Clip(_, s) => Some([ //x, y + HEADER_H + (rows[s].1 / PPQ) as u16, //w, (rows[s].0 / PPQ) as u16 //]), //_ => None //}; //let mut clip_area: Option<[u16;4]> = match selected { //ArrangerSelection::Clip(t, s) => Some([ //(scenes_w + x + cols[t].1 as u16).saturating_sub(1), //HEADER_H + y + (rows[s].1/PPQ) as u16, //cols[t].0 as u16 + 2, //(rows[s].0 / PPQ) as u16 //]), //_ => None //}; //if let Some([x, y, width, height]) = track_area { //to.fill_fg([x, y, 1, height], bg); //to.fill_fg([x + width, y, 1, height], bg); //} //if let Some([_, y, _, height]) = scene_area { //to.fill_ul([x, y - 1, w, 1], bg); //to.fill_ul([x, y + height - 1, w, 1], bg); //} //if focused { //to.place(if let Some(clip_area) = clip_area { //clip_area //} else if let Some(track_area) = track_area { //track_area.clip_h(HEADER_H) //} else if let Some(scene_area) = scene_area { //scene_area.clip_w(scenes_w) //} else { //area.clip_w(scenes_w).clip_h(HEADER_H) //}, &reticle) //}; //}) //} ///// A 1-row cell. //fn cell > (color: ItemPalette, field: T) -> impl Content { //Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) //} //} /////////////////////////////////////////////////////////////////////////////////////////////////// //handle!(TuiIn: |self: Sequencer, input|SequencerCommand::execute_with_state(self, input.event())); //handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event())); //handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); //use SequencerCommand as SeqCmd; //use GrooveboxCommand as GrvCmd; //use ArrangerCommand as ArrCmd; //#[derive(Clone, Debug)] pub enum SequencerCommand { //Compact(bool), //History(isize), //Clock(ClockCommand), //Pool(PoolCommand), //Editor(MidiEditCommand), //Enqueue(Option>>), //} //#[derive(Clone, Debug)] pub enum GrooveboxCommand { //Compact(bool), //History(isize), //Clock(ClockCommand), //Pool(PoolCommand), //Editor(MidiEditCommand), //Enqueue(Option>>), //Sampler(SamplerCommand), //} //#[derive(Clone, Debug)] pub enum ArrangerCommand { //History(isize), //Color(ItemPalette), //Clock(ClockCommand), //Scene(SceneCommand), //Track(TrackCommand), //Clip(ClipCommand), //Select(ArrangerSelection), //Zoom(usize), //Pool(PoolCommand), //Editor(MidiEditCommand), //StopAll, //Clear, //} //command!(|self: SequencerCommand, state: Sequencer|match self { //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, //Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None }, //Self::History(delta) => { todo!("undo/redo") }, //Self::Pool(cmd) => match cmd { //// autoselect: automatically load selected clip in editor //PoolCommand::Select(_) => { //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; //state.editor.set_clip(state.pool.clip().as_ref()); //undo //}, //// update color in all places simultaneously //PoolCommand::Clip(PoolCmd::SetColor(index, _)) => { //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; //state.editor.set_clip(state.pool.clip().as_ref()); //undo //}, //_ => cmd.delegate(&mut state.pool, Self::Pool)? //}, //Self::Compact(compact) => if state.compact != compact { //state.compact = compact; //Some(Self::Compact(!compact)) //} else { //None //}, //}); //command!(|self: GrooveboxCommand, state: Groovebox|match self { //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, //Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None }, //Self::History(delta) => { todo!("undo/redo") }, //Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?, //Self::Pool(cmd) => match cmd { //// autoselect: automatically load selected clip in editor //PoolCommand::Select(_) => { //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; //state.editor.set_clip(state.pool.clip().as_ref()); //undo //}, //// update color in all places simultaneously //PoolCommand::Clip(PoolCmd::SetColor(index, _)) => { //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; //state.editor.set_clip(state.pool.clip().as_ref()); //undo //}, //_ => cmd.delegate(&mut state.pool, Self::Pool)? //}, //Self::Compact(compact) => if state.compact != compact { //state.compact = compact; //Some(Self::Compact(!compact)) //} else { //None //}, //}); //command!(|self: ArrangerCommand, state: Arranger|match self { //Self::Clear => { todo!() }, //Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, //Self::History(_) => { todo!() }, //Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, //Self::Select(s) => { state.selected = s; None }, //Self::Track(cmd) => cmd.delegate(state, Self::Track)?, //Self::Zoom(_) => { todo!(); }, //Self::StopAll => { //for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); } //None //}, //Self::Color(palette) => { //let old = state.color; //state.color = palette; //Some(Self::Color(old)) //}, //Self::Pool(cmd) => { //match cmd { //// autoselect: automatically load selected clip in editor //PoolCommand::Select(_) => { //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; //state.editor.set_clip(state.pool.clip().as_ref()); //undo //}, //// reload clip in editor to update color //PoolCommand::Clip(PoolClipCommand::SetColor(index, _)) => { //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; //state.editor.set_clip(state.pool.clip().as_ref()); //undo //}, //_ => cmd.delegate(&mut state.pool, Self::Pool)? //} //}, //}); //command!(|self: SceneCommand, state: Arranger|match self { //Self::Add => { state.scene_add(None, None)?; None } //Self::Del(index) => { state.scene_del(index); None }, //Self::SetColor(index, color) => { //let old = state.scenes[index].color; //state.scenes[index].color = color; //Some(Self::SetColor(index, old)) //}, //Self::Enqueue(scene) => { //for track in 0..state.tracks.len() { //state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); //} //None //}, //_ => None //}); //command!(|self: TrackCommand, state: Arranger|match self { //Self::Add => { state.track_add(None, None)?; None }, //Self::Del(index) => { state.track_del(index); None }, //Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, //Self::SetColor(index, color) => { //let old = state.tracks[index].color; //state.tracks[index].color = color; //Some(Self::SetColor(index, old)) //}, //_ => None //}); //command!(|self: ClipCommand, state: Arranger|match self { //Self::Get(track, scene) => { todo!() }, //Self::Put(track, scene, clip) => { //let old = state.scenes[scene].clips[track].clone(); //state.scenes[scene].clips[track] = clip; //Some(Self::Put(track, scene, old)) //}, //Self::Enqueue(track, scene) => { //state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); //None //}, //_ => None //}); //keymap!(KEYS_SEQUENCER = |state: Sequencer, input: Event| SequencerCommand { //// TODO: k: toggle on-screen keyboard //ctrl(key(Char('k'))) => { todo!("keyboard") }, //// Transport: Play/pause //key(Char(' ')) => SeqCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), //// Transport: Play from start or rewind to start //shift(key(Char(' '))) => SeqCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), //// u: undo //key(Char('u')) => SeqCmd::History(-1), //// Shift-U: redo //key(Char('U')) => SeqCmd::History( 1), //// Tab: Toggle compact mode //key(Tab) => SeqCmd::Compact(!state.compact), //// q: Enqueue currently edited clip //key(Char('q')) => SeqCmd::Enqueue(state.pool.clip().clone()), //// 0: Enqueue clip 0 (stop all) //key(Char('0')) => SeqCmd::Enqueue(Some(state.clips()[0].clone())), //// e: Toggle between editing currently playing or other clip ////key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_clip() { ////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); ////let selected = state.pool.clip().clone(); ////SeqCmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { ////selected ////} else { ////playing.clone() ////}))) ////} else { ////return None ////} //}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { //SeqCmd::Editor(command) //} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { //SeqCmd::Pool(command) //} else { //return None //}); //keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { //// Tab: Toggle compact mode //key(Tab) => GrvCmd::Compact(!state.compact), //// q: Enqueue currently edited clip //key(Char('q')) => GrvCmd::Enqueue(state.pool.clip().clone()), //// 0: Enqueue clip 0 (stop all) //key(Char('0')) => GrvCmd::Enqueue(Some(state.pool.clips()[0].clone())), //// TODO: k: toggle on-screen keyboard //ctrl(key(Char('k'))) => todo!("keyboard"), //// Transport: Play from start or rewind to start //ctrl(key(Char(' '))) => GrvCmd::Clock( //if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } //), //// Shift-R: toggle recording //shift(key(Char('R'))) => GrvCmd::Sampler(if state.sampler.recording.is_some() { //SmplCmd::RecordFinish //} else { //SmplCmd::RecordBegin(u7::from(state.editor.note_point() as u8)) //}), //// Shift-Del: delete sample //shift(key(Delete)) => GrvCmd::Sampler( //SmplCmd::SetSample(u7::from(state.editor.note_point() as u8), None) //), //// e: Toggle between editing currently playing or other clip ////shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_clip() { ////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); ////let selected = state.pool.clip().clone().map(|s|s.read().unwrap().clone()); ////GrvCmd::Editor(Show(if selected != editing { ////selected ////} else { ////Some(playing.clone()) ////})) ////} else { ////return None ////}, //}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { //GrvCmd::Editor(command) //} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { //GrvCmd::Pool(command) //} else { //return None //}); //keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { //key(Char('u')) => ArrCmd::History(-1), //key(Char('U')) => ArrCmd::History(1), //// TODO: k: toggle on-screen keyboard //ctrl(key(Char('k'))) => { todo!("keyboard") }, //// Transport: Play/pause //key(Char(' ')) => ArrCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), //// Transport: Play from start or rewind to start //shift(key(Char(' '))) => ArrCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), //key(Char('e')) => ArrCmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())), //ctrl(key(Char('a'))) => ArrCmd::Scene(SceneCommand::Add), //ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(SceneCommand::Add), //ctrl(key(Char('t'))) => ArrCmd::Track(TrackCommand::Add), //// Tab: Toggle visibility of clip pool column //key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)), //}, { //use ArrangerSelection as Selected; //use SceneCommand as Scene; //use TrackCommand as Track; //use ClipCommand as Clip; //let t_len = state.tracks.len(); //let s_len = state.scenes.len(); //match state.selected { //Selected::Clip(t, s) => clip_keymap(state, input, t, s), //Selected::Scene(s) => scene_keymap(state, input, s), //Selected::Track(t) => track_keymap(state, input, t), //Selected::Mix => match input { //kpat!(Delete) => Some(ArrCmd::Clear), //kpat!(Char('0')) => Some(ArrCmd::StopAll), //kpat!(Char('c')) => Some(ArrCmd::Color(ItemPalette::random())), //kpat!(Up) => return None, //kpat!(Down) => Some(ArrCmd::Select(Selected::Scene(0))), //kpat!(Left) => return None, //kpat!(Right) => Some(ArrCmd::Select(Selected::Track(0))), //_ => None //}, //} //}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { //Some(ArrCmd::Editor(command)) //} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { //Some(ArrCmd::Pool(command)) //} else { //None //})?); //fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option { //use ArrangerSelection as Selected; //use SceneCommand as Scene; //use TrackCommand as Track; //use ClipCommand as Clip; //let t_len = state.tracks.len(); //let s_len = state.scenes.len(); //Some(match input { //kpat!(Char('g')) => ArrCmd::Pool(PoolCommand::Select(0)), //kpat!(Char('q')) => ArrCmd::Clip(Clip::Enqueue(t, s)), //kpat!(Char('l')) => ArrCmd::Clip(Clip::SetLoop(t, s, false)), //kpat!(Enter) => if state.scenes[s].clips[t].is_none() { //// FIXME: get this clip from the pool (autoregister via intmut) //let (_, clip) = state.add_clip(); //ArrCmd::Clip(Clip::Put(t, s, Some(clip))) //} else { //return None //}, //kpat!(Delete) => ArrCmd::Clip(Clip::Put(t, s, None)), //kpat!(Char('p')) => ArrCmd::Clip(Clip::Put(t, s, state.pool.clip().clone())), //kpat!(Char(',')) => ArrCmd::Clip(Clip::Put(t, s, None)), //kpat!(Char('.')) => ArrCmd::Clip(Clip::Put(t, s, None)), //kpat!(Char('<')) => ArrCmd::Clip(Clip::Put(t, s, None)), //kpat!(Char('>')) => ArrCmd::Clip(Clip::Put(t, s, None)), //kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }), //kpat!(Down) => ArrCmd::Select(Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1)))), //kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) }), //kpat!(Right) => ArrCmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)), //_ => return None //}) //} //fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option { //use ArrangerSelection as Selected; //use SceneCommand as Scene; //use TrackCommand as Track; //use ClipCommand as Clip; //let t_len = state.tracks.len(); //let s_len = state.scenes.len(); //Some(match input { //kpat!(Char(',')) => ArrCmd::Scene(Scene::Swap(s, s - 1)), //kpat!(Char('.')) => ArrCmd::Scene(Scene::Swap(s, s + 1)), //kpat!(Char('<')) => ArrCmd::Scene(Scene::Swap(s, s - 1)), //kpat!(Char('>')) => ArrCmd::Scene(Scene::Swap(s, s + 1)), //kpat!(Char('q')) => ArrCmd::Scene(Scene::Enqueue(s)), //kpat!(Delete) => ArrCmd::Scene(Scene::Del(s)), //kpat!(Char('c')) => ArrCmd::Scene(Scene::SetColor(s, ItemPalette::random())), //kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }), //kpat!(Down) => ArrCmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1)))), //kpat!(Left) => return None, //kpat!(Right) => ArrCmd::Select(Selected::Clip(0, s)), //_ => return None //}) //} //fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option { //use ArrangerSelection as Selected; //use SceneCommand as Scene; //use TrackCommand as Track; //use ClipCommand as Clip; //let t_len = state.tracks.len(); //let s_len = state.scenes.len(); //Some(match input { //kpat!(Char(',')) => ArrCmd::Track(Track::Swap(t, t - 1)), //kpat!(Char('.')) => ArrCmd::Track(Track::Swap(t, t + 1)), //kpat!(Char('<')) => ArrCmd::Track(Track::Swap(t, t - 1)), //kpat!(Char('>')) => ArrCmd::Track(Track::Swap(t, t + 1)), //kpat!(Delete) => ArrCmd::Track(Track::Del(t)), //kpat!(Char('c')) => ArrCmd::Track(Track::SetColor(t, ItemPalette::random())), //kpat!(Up) => return None, //kpat!(Down) => ArrCmd::Select(Selected::Clip(t, 0)), //kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }), //kpat!(Right) => ArrCmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1)))), //_ => return None //}) //} //impl HasJack for Arranger { //fn jack (&self) -> &Arc> { &self.jack } //} //audio!(|self: Sequencer, client, scope|{ //// Start profiling cycle //let t0 = self.perf.get_t0(); //// Update transport clock //if Control::Quit == ClockAudio(self).process(client, scope) { //return Control::Quit //} //// Update MIDI sequencer //if Control::Quit == PlayerAudio( //&mut self.player, &mut self.note_buf, &mut self.midi_buf //).process(client, scope) { //return Control::Quit //} //// End profiling cycle //self.perf.update(t0, scope); //Control::Continue //}); //audio!(|self: Groovebox, client, scope|{ //// Start profiling cycle //let t0 = self.perf.get_t0(); //// Update transport clock //if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { //return Control::Quit //} //// Update MIDI sequencer //if Control::Quit == PlayerAudio( //&mut self.player, &mut self.note_buf, &mut self.midi_buf //).process(client, scope) { //return Control::Quit //} //// Update sampler //if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { //return Control::Quit //} //// TODO move these to editor and sampler: //for RawMidi { time, bytes } in self.player.midi_ins[0].port.iter(scope) { //if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { //match message { //MidiMessage::NoteOn { ref key, .. } => { //self.editor.set_note_point(key.as_int() as usize); //}, //MidiMessage::Controller { controller, value } => { //if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { //sample.write().unwrap().handle_cc(controller, value) //} //} //_ => {} //} //} //} //// End profiling cycle //self.perf.update(t0, scope); //Control::Continue //}); //audio!(|self: Arranger, client, scope|{ //// Start profiling cycle //let t0 = self.perf.get_t0(); //// Update transport clock //if Control::Quit == ClockAudio(self).process(client, scope) { //return Control::Quit //} ////// Update MIDI sequencers ////let tracks = &mut self.tracks; ////let note_buf = &mut self.note_buf; ////let midi_buf = &mut self.midi_buf; ////if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { ////return Control::Quit ////} //// FIXME: one of these per playing track ////self.now.set(0.); ////if let ArrangerSelection::Clip(t, s) = self.selected { ////let clip = self.scenes.get(s).map(|scene|scene.clips.get(t)); ////if let Some(Some(Some(clip))) = clip { ////if let Some(track) = self.tracks().get(t) { ////if let Some((ref started_at, Some(ref playing))) = track.player.play_clip { ////let clip = clip.read().unwrap(); ////if *playing.read().unwrap() == *clip { ////let pulse = self.current().pulse.get(); ////let start = started_at.pulse.get(); ////let now = (pulse - start) % clip.length as f64; ////self.now.set(now); ////} ////} ////} ////} ////} //// End profiling cycle //self.perf.update(t0, scope); //return Control::Continue //}); //fn get_device_mut (&self, i: usize) -> Option>>> { //self.devices.get(i).map(|d|d.state.write().unwrap()) //} //pub fn device_mut (&self) -> Option>>> { //self.get_device_mut(self.device) //} ///// Add a device to the end of the chain. //pub fn append_device (&mut self, device: JackDevice) -> Usually<&mut JackDevice> { //self.devices.push(device); //let index = self.devices.len() - 1; //Ok(&mut self.devices[index]) //} //pub fn add_device (&mut self, device: JackDevice) { //self.devices.push(device); //} //pub fn connect_first_device (&self) -> Usually<()> { //if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) { //device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?; //} //Ok(()) //} //pub fn connect_last_device (&self, app: &Track) -> Usually<()> { //Ok(match self.devices.get(self.devices.len().saturating_sub(1)) { //Some(device) => { //app.audio_out(0).map(|left|device.connect_audio_out(0, &left)).transpose()?; //app.audio_out(1).map(|right|device.connect_audio_out(1, &right)).transpose()?; //() //}, //None => () //}) //} use crate::*; impl MixerTrack { pub fn new (name: &str) -> Usually { Ok(Self { name: name.to_string().into(), audio_ins: vec![], audio_outs: vec![], devices: vec![], //ports: JackPorts::default(), //devices: vec![], //device: 0, }) } } pub struct TrackView<'a> { pub chain: Option<&'a MixerTrack>, pub direction: Direction, pub focused: bool, pub entered: bool, } impl<'a> Content for TrackView<'a> { fn render (&self, to: &mut TuiOut) { todo!(); //let mut area = to.area(); //if let Some(chain) = self.chain { //match self.direction { //Direction::Down => area.width = area.width.min(40), //Direction::Right => area.width = area.width.min(10), //_ => { unimplemented!() }, //} //to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered)); //let mut split = Stack::new(self.direction); //for device in chain.devices.as_slice().iter() { //split = split.add_ref(device); //} //let (area, areas) = split.render_areas(to)?; //if self.focused && self.entered && areas.len() > 0 { //Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?; //} //Ok(Some(area)) //} else { //let [x, y, width, height] = area; //let label = "No chain selected"; //let x = x + (width - label.len() as u16) / 2; //let y = y + height / 2; //to.blit(&label, x, y, Some(Style::default().dim().bold()))?; //Ok(Some(area)) //} } } //impl Content for Mixer { //fn content (&self) -> impl Content { //Stack::right(|add| { //for channel in self.tracks.iter() { //add(channel)?; //} //Ok(()) //}) //} //} //impl Content for Track { //fn content (&self) -> impl Content { //TrackView { //chain: Some(&self), //direction: tek_core::Direction::Right, //focused: true, //entered: true, ////pub channels: u8, ////pub input_ports: Vec>, ////pub pre_gain_meter: f64, ////pub gain: f64, ////pub insert_ports: Vec>, ////pub return_ports: Vec>, ////pub post_gain_meter: f64, ////pub post_insert_meter: f64, ////pub level: f64, ////pub pan: f64, ////pub output_ports: Vec>, ////pub post_fader_meter: f64, ////pub route: String, //} //} //} handle!(TuiIn: |self: Mixer, engine|{ if let crossterm::event::Event::Key(event) = engine.event() { match event.code { //KeyCode::Char('c') => { //if event.modifiers == KeyModifiers::CONTROL { //self.exit(); //} //}, KeyCode::Down => { self.selected_track = (self.selected_track + 1) % self.tracks.len(); println!("{}", self.selected_track); return Ok(Some(true)) }, KeyCode::Up => { if self.selected_track == 0 { self.selected_track = self.tracks.len() - 1; } else { self.selected_track -= 1; } println!("{}", self.selected_track); return Ok(Some(true)) }, KeyCode::Left => { if self.selected_column == 0 { self.selected_column = 6 } else { self.selected_column -= 1; } return Ok(Some(true)) }, KeyCode::Right => { if self.selected_column == 6 { self.selected_column = 0 } else { self.selected_column += 1; } return Ok(Some(true)) }, _ => { println!("\n{event:?}"); } } } Ok(None) }); handle!(TuiIn: |self:MixerTrack,from|{ match from.event() { //, NONE, "chain_cursor_up", "move cursor up", || { kpat!(KeyCode::Up) => { Ok(Some(true)) }, // , NONE, "chain_cursor_down", "move cursor down", || { kpat!(KeyCode::Down) => { Ok(Some(true)) }, // Left, NONE, "chain_cursor_left", "move cursor left", || { kpat!(KeyCode::Left) => { //if let Some(track) = app.arranger.track_mut() { //track.device = track.device.saturating_sub(1); //return Ok(true) //} Ok(Some(true)) }, // , NONE, "chain_cursor_right", "move cursor right", || { kpat!(KeyCode::Right) => { //if let Some(track) = app.arranger.track_mut() { //track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); //return Ok(true) //} Ok(Some(true)) }, // , NONE, "chain_mode_switch", "switch the display mode", || { kpat!(KeyCode::Char('`')) => { //app.chain_mode = !app.chain_mode; Ok(Some(true)) }, _ => Ok(None) } }); pub enum MixerTrackCommand {} //impl MixerTrackDevice for LV2Plugin {} pub trait MixerTrackDevice: Debug + Send + Sync { fn boxed (self) -> Box where Self: Sized + 'static { Box::new(self) } } impl MixerTrackDevice for Sampler {} impl MixerTrackDevice for Plugin {} const SYM_NAME: &str = ":name"; const SYM_GAIN: &str = ":gain"; const SYM_SAMPLER: &str = "sampler"; const SYM_LV2: &str = "lv2"; from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { let mut _gain = 0.0f64; let mut track = MixerTrack { name: "".into(), audio_ins: vec![], audio_outs: vec![], devices: vec![], }; edn!(edn in args { Edn::Map(map) => { if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) { track.name = n.to_string(); } if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) { _gain = f64::from(*g); } }, Edn::List(args) => match args.first() { // Add a sampler device to the track Some(Edn::Symbol(SYM_SAMPLER)) => { track.devices.push( Box::new(Sampler::from_edn(jack, &args[1..])?) as Box ); panic!( "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", &track.name, args.first().unwrap() ) }, // Add a LV2 plugin to the track. Some(Edn::Symbol(SYM_LV2)) => { track.devices.push( Box::new(Plugin::from_edn(jack, &args[1..])?) as Box ); panic!( "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", &track.name, args.first().unwrap() ) }, None => panic!("empty list track {}", &track.name), _ => panic!("unexpected in track {}: {:?}", &track.name, args.first().unwrap()) }, _ => {} }); Ok(track) }); //impl ArrangerScene { ////TODO ////pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { ////let mut name = None; ////let mut clips = vec![]; ////edn!(edn in args { ////Edn::Map(map) => { ////let key = map.get(&Edn::Key(":name")); ////if let Some(Edn::Str(n)) = key { ////name = Some(*n); ////} else { ////panic!("unexpected key in scene '{name:?}': {key:?}") ////} ////}, ////Edn::Symbol("_") => { ////clips.push(None); ////}, ////Edn::Int(i) => { ////clips.push(Some(*i as usize)); ////}, ////_ => panic!("unexpected in scene '{name:?}': {edn:?}") ////}); ////Ok(ArrangerScene { ////name: Arc::new(name.unwrap_or("").to_string().into()), ////color: ItemColor::random(), ////clips, ////}) ////} //} // 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"), //}); //#[derive(Debug)] //pub struct MIDIPlayer { ///// Global timebase //pub clock: Arc, ///// Start time and clip being played //pub play_clip: Option<(Moment, Option>>)>, ///// Start time and next clip //pub next_clip: Option<(Moment, Option>>)>, ///// Play input through output. //pub monitoring: bool, ///// Write input to sequence. //pub recording: bool, ///// Overdub input to sequence. //pub overdub: bool, ///// Send all notes off //pub reset: bool, // TODO?: after Some(nframes) ///// Record from MIDI ports to current sequence. //pub midi_inputs: Vec>, ///// Play from current sequence to MIDI ports //pub midi_outputs: Vec>, ///// MIDI output buffer //pub midi_note: Vec, ///// MIDI output buffer //pub midi_chunk: Vec>>, ///// Notes currently held at input //pub notes_in: Arc>, ///// Notes currently held at output //pub notes_out: Arc>, //} ///// Methods used primarily by the process callback //impl MIDIPlayer { //pub fn new ( //jack: &Arc>, //clock: &Arc, //name: &str //) -> Usually { //let jack = jack.read().unwrap(); //Ok(Self { //clock: clock.clone(), //clip: None, //next_clip: None, //notes_in: Arc::new(RwLock::new([false;128])), //notes_out: Arc::new(RwLock::new([false;128])), //monitoring: false, //recording: false, //overdub: true, //reset: true, //midi_note: Vec::with_capacity(8), //midi_chunk: vec![Vec::with_capacity(16);16384], //midi_outputs: vec![ //jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())? //], //midi_inputs: vec![ //jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())? //], //}) //} //} use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; pub use clojure_reader::edn::Edn; //#[derive(Debug, Copy, Clone, Default, PartialEq)] //pub struct Items<'a>(&'a [Item<'a>]); //impl<'a> Items<'a> { //fn iter (&'a self) -> ItemsIterator<'a> { //ItemsIterator(0, self.0) //} //} //pub struct ItemsIterator<'a>(usize, &'a [Item<'a>]); //impl<'a> Iterator for ItemsIterator<'a> { //type Item = &'a Item<'a>; //fn next (&mut self) -> Option { //let item = self.1.get(self.0); //self.0 += 1; //item //} //} /* nice but doesn't work without compile time slice concat (which i guess could be implemeted using an unsafe linked list?) never done that one before im ny life, might try use konst::slice_concat; const fn read <'a> ( chars: impl Iterator ) -> Result, ParseError> { use Range::*; let mut state = Range::Nil; let mut tokens: &[Range<'a>] = &[]; while let Some(c) = chars.next() { state = match state { // must begin expression Nil => match c { ' ' => Nil, '(' => Exp(&[]), ':' => Sym(&[]), '1'..'9' => Num(digit(c)), 'a'..'z' => Key(&[&[c]]), _ => return Err(ParseError::Unexpected(c)) }, Num(b) => match c { ' ' => return Ok(Num(digit(c))), '1'..'9' => Num(b*10+digit(c)), _ => return Err(ParseError::Unexpected(c)) } Sym([]) => match c { 'a'..'z' => Sym(&[c]), _ => return Err(ParseError::Unexpected(c)) }, Sym([b @ ..]) => match c { ' ' => return Ok(Sym(&b)), 'a'..'z' | '0'..'9' | '-' => Sym(&[..b, c]), _ => return Err(ParseError::Unexpected(c)) } Key([[b @ ..]]) => match c { ' ' => return Ok(Key(&[&b])), '/' => Key(&[&b, &[]]), 'a'..'z' | '0'..'9' | '-' => Key(&[&[..b, c], &[]]), _ => return Err(ParseError::Unexpected(c)) } Key([s @ .., []]) => match c { 'a'..'z' => Key(&[..s, &[c]]), _ => return Err(ParseError::Unexpected(c)) } Key([s @ .., [b @ ..]]) => match c { '/' => Key([..s, &b, &[]]), 'a'..'z' | '0'..'9' | '-' => Key(&[..s, &[..b, c]]), _ => return Err(ParseError::Unexpected(c)) } // expression must begin with key or symbol Exp([]) => match c { ' ' => Exp(&[]), ')' => return Err(ParseError::Empty), ':' => Exp(&[Sym(&[':'])]), c => Exp(&[Key(&[&[c]])]), }, // expression can't begin with number Exp([Num(num)]) => return Err(ParseError::Unexpected(c)), // symbol begins with : and lowercase a-z Exp([Sym([':'])]) => match c { 'a'..'z' => Exp(&[Sym(&[':', c])]), _ => return Err(ParseError::Unexpected(c)), }, // any other char is part of symbol until space or ) Exp([Sym([':', b @ ..])]) => match c { ')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil }, ' ' => Exp(&[Sym(&[':', ..b]), Nil]), c => Exp(&[Sym(&[':', ..b, c])]), }, // key begins with lowercase a-z Exp([Key([])]) => match c { 'a'..'z' => Exp([Key([[c]])]), _ => return Err(ParseError::Unexpected(c)), }, // any other char is part of key until slash space or ) Exp([Key([[b @ ..]])]) => match c { '/' => Exp(&[Key(&[[..b], []])]), ' ' => Exp(&[Key(&[[..b]]), Nil]), ')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil }, c => Exp(&[Key(&[[..b, c]])]) } // slash adds new section to key Exp([Key([b @ .., []])]) => match c { '/' => Exp(&[Key(&[[..b], []])]), ' ' => Exp(&[Key(&[[..b]]), Nil]), ')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil }, c => Exp(&[Key(&[[..b, c]])]) } } } Ok(state) } */ /// EDN parsing helper. #[macro_export] macro_rules! edn { ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { match $edn { $($pat => $expr),* } }; ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { for $edn in $args { edn!($edn { $($pat => $expr),* }) } }; } pub trait FromEdn: Sized { const ID: &'static str; fn from_edn (context: C, expr: &[Edn<'_>]) -> std::result::Result>; } /// Implements the [FromEdn] trait. #[macro_export] macro_rules! from_edn { ($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => { impl FromEdn<$Context> for $T { const ID: &'static str = $id; fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { $body } } } } use crate::*; use std::sync::Arc; pub enum EdnIterator { Nil, Sym(Arc), Exp(Vec) } impl EdnIterator { pub fn new (item: &EdnItem) -> Self { use EdnItem::*; match item { Sym(t) => Self::Sym(t.clone()), Exp(i) => Self::Exp(i.iter().map(EdnIterator::new).collect()), _ => Self::Nil, } } } impl Iterator for EdnIterator { type Item = EdnItem; fn next (&mut self) -> Option { use EdnIterator::*; match self { Sym(t) => { let t = *t; *self = Nil; Some(Sym(t)) }, Exp(v) => match v.as_mut_slice() { [a] => if let Some(next) = a.next() { Some(next) } else { *self = Exp(v.split_off(1)); self.next() }, _ => { *self = Nil; None } }, _ => { *self = Nil; None } } } }