diff --git a/tek/src/groovebox.edn b/tek/src/groovebox.edn new file mode 100644 index 00000000..2aa2b3c1 --- /dev/null +++ b/tek/src/groovebox.edn @@ -0,0 +1 @@ +(bsp/s :toolbar (bsp/s :sample (bsp/s :status (bsp/w :pool (bsp/e :sampler :editor))))) diff --git a/tek/src/groovebox.rs b/tek/src/groovebox.rs index a5bb6833..918f3c42 100644 --- a/tek/src/groovebox.rs +++ b/tek/src/groovebox.rs @@ -1,27 +1,101 @@ -mod groovebox_command; pub use self::groovebox_command::*; -mod groovebox_tui; pub use self::groovebox_tui::*; - use crate::*; use super::*; -use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; +use self::GrooveboxCommand as Cmd; +use EdnItem::*; use ClockCommand::{Play, Pause}; -use GrooveboxCommand as Cmd; use MidiEditCommand::*; use MidiPoolCommand::*; - +use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; +use std::marker::ConstParamTy; pub struct Groovebox { pub _jack: Arc>, - pub player: MidiPlayer, - pub pool: PoolModel, - pub editor: MidiEditor, + pub player: MidiPlayer, + pub pool: PoolModel, + pub editor: MidiEditor, pub sampler: Sampler, - pub compact: bool, - pub size: Measure, - pub status: bool, + pub compact: bool, + pub size: Measure, + pub status: bool, pub note_buf: Vec, pub midi_buf: Vec>>, - pub perf: PerfModel, + pub perf: PerfModel, +} +render!(TuiOut: (self: Groovebox) => self.size.of(EdnView::from_source(self, Self::EDN))); +// this works: +//render!(TuiOut: (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))))))))); +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_view().boxed(), + Sym(":status") => self.status_view().boxed(), + Sym(":toolbar") => self.toolbar_view().boxed(), + Sym(":sampler") => self.sampler_view().boxed(), + Sym(":sample") => self.sample_view().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!("groovebox.edn"); + fn toolbar_view (&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(TransportView::new(true, &self.player.clock)), + ))) + } + fn status_view (&self) -> impl Content + use<'_> { + row!( + self.player.play_status(), + self.player.next_status(), + self.editor.clip_status(), + self.editor.edit_status(), + ) + } + 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( + Bsp::a( + Fill::x(Align::w(Fixed::y(1, SamplerStatus(&self.sampler, note_pt)))), + SampleViewer::from_sampler(&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 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( + SampleList::new(self.compact, &self.sampler, &self.editor)))) + } } audio!(|self: Groovebox, client, scope|{ @@ -58,46 +132,216 @@ audio!(|self: Groovebox, client, scope|{ }); has_clock!(|self: Groovebox|self.player.clock()); - -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(self.player.play_status()), - ":clip-next" => Box::new(self.player.next_status()), - ":clip-edit" => Box::new(self.editor.clip_status()), - ":edit-stat" => Box::new(self.editor.edit_status()), - ":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:?}") - } - } +pub enum GrooveboxCommand { + Compact(bool), + History(isize), + Clock(ClockCommand), + Pool(PoolCommand), + Editor(MidiEditCommand), + Enqueue(Option>>), + Sampler(SamplerCommand), } +command!(|self: GrooveboxCommand, state: Groovebox|match self { + Self::Enqueue(clip) => { + state.player.enqueue_next(clip.as_ref()); + None + }, + 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(Some(state.pool.clip())); + undo + }, + // update color in all places simultaneously + PoolCommand::Phrase(SetColor(index, _)) => { + let undo = cmd.delegate(&mut state.pool, Self::Pool)?; + state.editor.set_clip(Some(state.pool.clip())); + undo + }, + _ => cmd.delegate(&mut state.pool, Self::Pool)? + }, + Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?, + Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, + Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, + Self::History(delta) => { todo!("undo/redo") }, + Self::Compact(compact) => if state.compact != compact { + state.compact = compact; + Some(Self::Compact(!compact)) + } else { + None + }, +}); + +handle!(TuiIn: |self: Groovebox, input| + GrooveboxCommand::execute_with_state(self, input.event())); + +keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { + // Tab: Toggle compact mode + key(Tab) => Cmd::Compact(!state.compact), + // q: Enqueue currently edited clip + key(Char('q')) => Cmd::Enqueue(Some(state.pool.clip().clone())), + // 0: Enqueue clip 0 (stop all) + key(Char('0')) => Cmd::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(' '))) => Cmd::Clock( + if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } + ), + // Shift-R: toggle recording + shift(key(Char('R'))) => Cmd::Sampler(if state.sampler.recording.is_some() { + SamplerCommand::RecordFinish + } else { + SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8)) + }), + // Shift-Del: delete sample + shift(key(Delete)) => Cmd::Sampler( + SamplerCommand::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(); + Cmd::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) { + Cmd::Editor(command) +} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { + Cmd::Pool(command) +} else { + return None +}); + +///// 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(), + //Bsp::n(self.sample_view(), + //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()) + //} +//} + +//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/tek/src/groovebox/groovebox.edn b/tek/src/groovebox/groovebox.edn deleted file mode 100644 index a7350fa5..00000000 --- a/tek/src/groovebox/groovebox.edn +++ /dev/null @@ -1,17 +0,0 @@ -(bsp/s - (fill/x (fixed/y 2 (bsp/a - (align/x :transport) - (bsp/a - (align/w :input-meter-l) - (align/e :input-meter-r))))) - (bsp/n - (bsp/e :clip-play (bsp/e :clip-next (bsp/e :clip-edit :edit-stat))) - (bsp/n - (max/y :sample-h (fill/xy :sample-view)) - (bsp/n - (align/w (fixed/y 1 :sample-stat)) - (bsp/n - (fixed/x :pool-w :pool-view) - (fill/xy (bsp/e - (fixed/x :samples-w (push/y :samples-y :samples-view)) - :midi-view))))))) diff --git a/tek/src/groovebox/groovebox_command.rs b/tek/src/groovebox/groovebox_command.rs deleted file mode 100644 index 0bc61109..00000000 --- a/tek/src/groovebox/groovebox_command.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::*; -use super::*; - -use self::GrooveboxCommand as Cmd; -use ClockCommand::{Play, Pause}; - -pub enum GrooveboxCommand { - Compact(bool), - History(isize), - Clock(ClockCommand), - Pool(PoolCommand), - Editor(MidiEditCommand), - Enqueue(Option>>), - Sampler(SamplerCommand), -} - -command!(|self: GrooveboxCommand, state: Groovebox|match self { - Self::Enqueue(clip) => { - state.player.enqueue_next(clip.as_ref()); - None - }, - 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(Some(state.pool.clip())); - undo - }, - // update color in all places simultaneously - PoolCommand::Phrase(SetColor(index, _)) => { - let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - state.editor.set_clip(Some(state.pool.clip())); - undo - }, - _ => cmd.delegate(&mut state.pool, Self::Pool)? - }, - Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?, - Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, - Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - Self::History(delta) => { todo!("undo/redo") }, - Self::Compact(compact) => if state.compact != compact { - state.compact = compact; - Some(Self::Compact(!compact)) - } else { - None - }, -}); - -handle!(TuiIn: |self: Groovebox, input| - GrooveboxCommand::execute_with_state(self, input.event())); - -keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { - // Tab: Toggle compact mode - key(Tab) => Cmd::Compact(!state.compact), - // q: Enqueue currently edited clip - key(Char('q')) => Cmd::Enqueue(Some(state.pool.clip().clone())), - // 0: Enqueue clip 0 (stop all) - key(Char('0')) => Cmd::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(' '))) => Cmd::Clock( - if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } - ), - // Shift-R: toggle recording - shift(key(Char('R'))) => Cmd::Sampler(if state.sampler.recording.is_some() { - SamplerCommand::RecordFinish - } else { - SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8)) - }), - // Shift-Del: delete sample - shift(key(Delete)) => Cmd::Sampler( - SamplerCommand::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(); - Cmd::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) { - Cmd::Editor(command) -} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - Cmd::Pool(command) -} else { - return None -}); diff --git a/tek/src/groovebox/groovebox_tui.rs b/tek/src/groovebox/groovebox_tui.rs deleted file mode 100644 index feab9913..00000000 --- a/tek/src/groovebox/groovebox_tui.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::*; -use super::*; -use std::marker::ConstParamTy; -use EdnItem::*; - -const EDN: &'static str = include_str!("groovebox.edn"); - -// this works: -render!(TuiOut: (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))))))))); - -// this almost does: -//impl Content for Groovebox { - //fn content (&self) -> impl Render { - //self.size.of(EdnView::from_source(self, EDN)) - //} -//} - -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!( - self.player.play_status(), - self.player.next_status(), - self.editor.clip_status(), - self.editor.edit_status(), - ) - } - 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 { 40 }; - 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)))) - } -} - -///// 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(), - //Bsp::n(self.sample_view(), - //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()) - //} -//} - -//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/tek/src/pool/pool_tui.rs b/tek/src/pool/pool_tui.rs index 528885a4..bd7bbfc2 100644 --- a/tek/src/pool/pool_tui.rs +++ b/tek/src/pool/pool_tui.rs @@ -4,10 +4,10 @@ pub struct PoolView<'a>(pub bool, pub &'a PoolModel); render!(TuiOut: (self: PoolView<'a>) => { let Self(compact, model) = self; let PoolModel { clips, mode, .. } = self.1; - let color = self.1.clip().read().unwrap().color; - Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, Outer( - Style::default().fg(color.dark.rgb).bg(color.darkest.rgb) - ).enclose(Map::new(||model.clips().iter(), |clip, i|{ + let color = self.1.clip().read().unwrap().color; + let on_bg = |x|Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x)); + let border = |x|Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x); + on_bg(border(Map::new(||model.clips().iter(), |clip, i|{ let item_height = 1; let item_offset = i as u16 * item_height; let selected = i == model.clip_index(); @@ -15,10 +15,10 @@ render!(TuiOut: (self: PoolView<'a>) => { let name = if *compact { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; let length = if *compact { String::default() } else { format!("{length} ") }; map_south(item_offset, item_height, Tui::bg(if selected { color.light.rgb } else { color.base.rgb }, lay!( - Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, name))), - Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, length))), - Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))), - Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))), + Fill::x(Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, name)))), + Fill::x(Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, length)))), + Fill::x(Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶"))))), + Fill::x(Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀"))))), ))) - })))) + }))) }); diff --git a/tek/src/sequencer.edn b/tek/src/sequencer.edn index 4827ef70..e7634cb4 100644 --- a/tek/src/sequencer.edn +++ b/tek/src/sequencer.edn @@ -1 +1 @@ -:editor +(bsp/s :toolbar (bsp/s :status (bsp/w :pool :editor))) diff --git a/tek/src/sequencer.rs b/tek/src/sequencer.rs index 399a9055..d9771296 100644 --- a/tek/src/sequencer.rs +++ b/tek/src/sequencer.rs @@ -27,40 +27,31 @@ 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(), + 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:?}") } } - } -//render!(TuiOut: (self: Sequencer) => self.size.of( - //Bsp::s(self.toolbar_view(), - //Bsp::n(self.selector_view(), - //Bsp::n(self.status_view(), - //Bsp::w(self.pool_view(), Fill::xy(&self.editor))))))); impl Sequencer { const EDN: &'static str = include_str!("sequencer.edn"); fn toolbar_view (&self) -> impl Content + use<'_> { Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock)))) } fn status_view (&self) -> impl Content + use<'_> { - row!( + Bsp::e( When(self.selectors, Bsp::e( self.player.play_status(), self.player.next_status(), )), - self.editor.clip_status(), - self.editor.edit_status(), - ) - } - fn selector_view (&self) -> impl Content + use<'_> { - row!( - self.player.play_status(), - self.player.next_status(), - self.editor.clip_status(), - self.editor.edit_status(), + Bsp::e( + self.editor.clip_status(), + self.editor.edit_status(), + ) ) } fn pool_view (&self) -> impl Content + use<'_> {