From e62e36d558831354f16a47d722c7dbc4c9baffa7 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 14 Jan 2025 12:41:27 +0100 Subject: [PATCH] separate render/content macros; add has_jack --- input/src/edn_command.rs | 17 ++---- input/src/edn_input.rs | 1 - jack/src/from_jack.rs | 13 ----- jack/src/has_jack.rs | 19 +++++++ jack/src/lib.rs | 1 - midi/src/midi_edit_tui.rs | 3 +- midi/src/midi_pool_tui.rs | 4 +- midi/src/midi_select.rs | 13 ++--- midi/src/piano_h.rs | 2 +- output/src/content.rs | 46 --------------- output/src/direction.rs | 1 - output/src/lib.rs | 6 +- output/src/output.rs | 114 ++++++++++++++++++++++++++++++++++++- output/src/render.rs | 66 --------------------- sampler/src/sampler_tui.rs | 8 +-- tek/src/lib.rs | 14 ++--- time/src/clock_tui.rs | 71 ++++++++--------------- tui/src/tui_border.rs | 8 +-- tui/src/tui_file.rs | 2 +- 19 files changed, 183 insertions(+), 226 deletions(-) delete mode 100644 jack/src/from_jack.rs delete mode 100644 output/src/content.rs delete mode 100644 output/src/render.rs diff --git a/input/src/edn_command.rs b/input/src/edn_command.rs index 0a139050..373fbe3d 100644 --- a/input/src/edn_command.rs +++ b/input/src/edn_command.rs @@ -1,4 +1,8 @@ use crate::*; +/// Turns an EDN item sequence into a command enum variant. +pub trait EdnCommand: Command { + fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self; +} /** Implement `EdnCommand` for given `State` and `Command` */ #[macro_export] macro_rules! edn_command { ($Command:ty : |$state:ident:$State:ty| { $(( @@ -47,16 +51,3 @@ use crate::*; let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg); }; } -/// Turns an EDN symbol sequence into a command enum variant. -pub trait EdnCommand: Command { - fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self; - fn get_isize (state: &C, item: &EdnItem<&str>) -> Option { - None - } - fn get_usize (state: &C, item: &EdnItem<&str>) -> Option { - None - } - fn get_bool (state: &C, item: &EdnItem<&str>) -> Option { - None - } -} diff --git a/input/src/edn_input.rs b/input/src/edn_input.rs index 2a0f8558..f954615a 100644 --- a/input/src/edn_input.rs +++ b/input/src/edn_input.rs @@ -1,5 +1,4 @@ use crate::*; -use EdnItem::*; pub trait EdnInput: Input { fn matches (&self, token: &str) -> bool; diff --git a/jack/src/from_jack.rs b/jack/src/from_jack.rs deleted file mode 100644 index 67e0843d..00000000 --- a/jack/src/from_jack.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::*; - -/// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. -#[macro_export] macro_rules! from_jack { - (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { - type Error = Box; - fn try_from ($jack: &Arc>) -> Usually { - Ok($cb) - } - } - }; -} diff --git a/jack/src/has_jack.rs b/jack/src/has_jack.rs index 068d6b30..42feb1de 100644 --- a/jack/src/has_jack.rs +++ b/jack/src/has_jack.rs @@ -3,3 +3,22 @@ use crate::*; pub trait HasJack { fn jack (&self) -> &Arc>; } + +/// Implement [HasJack]. +#[macro_export] macro_rules! has_jack { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasJack for $Struct $(<$($L),*$($T),*>)? { + fn jack (&$self) -> &Arc> { $cb } + } + }; +} + +/// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. +#[macro_export] macro_rules! from_jack { + (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { + type Error = Box; + fn try_from ($jack: &Arc>) -> Usually { Ok($cb) } + } + }; +} diff --git a/jack/src/lib.rs b/jack/src/lib.rs index 63a51818..4e81bca4 100644 --- a/jack/src/lib.rs +++ b/jack/src/lib.rs @@ -10,7 +10,6 @@ pub(crate) use ::jack::{ Unowned, MidiIn, MidiOut, AudioIn, AudioOut, }; -mod from_jack; pub use self::from_jack::*; mod has_jack; pub use self::has_jack::*; mod jack_audio; pub use self::jack_audio::*; mod jack_connect; pub use self::jack_connect::*; diff --git a/midi/src/midi_edit_tui.rs b/midi/src/midi_edit_tui.rs index af07e9cf..fdeaed9f 100644 --- a/midi/src/midi_edit_tui.rs +++ b/midi/src/midi_edit_tui.rs @@ -1,6 +1,6 @@ use crate::*; has_size!(|self: MidiEditor|&self.size); -render!(TuiOut: (self: MidiEditor) => { +content!(TuiOut: |self: MidiEditor| { self.autoscroll(); //self.autozoom(); self.size.of(&self.mode) @@ -17,7 +17,6 @@ impl MidiEditor { FieldV(color, "Loop", looped.to_string()) ) } - pub fn edit_status (&self) -> impl Content + '_ { let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.length) diff --git a/midi/src/midi_pool_tui.rs b/midi/src/midi_pool_tui.rs index 9e0e6390..6256f73c 100644 --- a/midi/src/midi_pool_tui.rs +++ b/midi/src/midi_pool_tui.rs @@ -1,6 +1,6 @@ use crate::*; pub struct PoolView<'a>(pub bool, pub &'a MidiPool); -render!(TuiOut: (self: PoolView<'a>) => { +content!(TuiOut: |self: PoolView<'a>| { let Self(compact, model) = self; let MidiPool { clips, mode, .. } = self.1; let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into()); @@ -24,7 +24,7 @@ render!(TuiOut: (self: PoolView<'a>) => { )))) }))))) }); -render!(TuiOut: (self: ClipLength) => { +content!(TuiOut: |self: ClipLength| { let bars = ||self.bars_string(); let beats = ||self.beats_string(); let ticks = ||self.ticks_string(); diff --git a/midi/src/midi_select.rs b/midi/src/midi_select.rs index 20776579..0073a8fd 100644 --- a/midi/src/midi_select.rs +++ b/midi/src/midi_select.rs @@ -1,17 +1,16 @@ use crate::*; - pub struct ClipSelected { pub(crate) title: &'static str, pub(crate) name: Arc, pub(crate) color: ItemPalette, pub(crate) time: Arc, } - -render!(TuiOut: (self: ClipSelected) => - FieldV(self.color, self.title, format!("{} {}", self.time, self.name))); - +content!(TuiOut: |self: ClipSelected|FieldV( + self.color, + self.title, + format!("{} {}", self.time, self.name) +)); impl ClipSelected { - /// Shows currently playing clip with beats elapsed pub fn play_clip (state: &T) -> Self { let (name, color) = if let Some((_, Some(clip))) = state.play_clip() { @@ -31,7 +30,6 @@ impl ClipSelected { .unwrap_or_else(||String::from(" ")).into() } } - /// Shows next clip with beats remaining until switchover pub fn next_clip (state: &T) -> Self { let mut time: Arc = String::from("--.-.--").into(); @@ -69,5 +67,4 @@ impl ClipSelected { }; Self { title: "Next", time, name, color, } } - } diff --git a/midi/src/piano_h.rs b/midi/src/piano_h.rs index c8a13048..cdce1830 100644 --- a/midi/src/piano_h.rs +++ b/midi/src/piano_h.rs @@ -40,7 +40,7 @@ impl PianoHorizontal { pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } -render!(TuiOut: (self: PianoHorizontal) => Tui::bg(TuiTheme::g(40), Bsp::s( +content!(TuiOut:|self: PianoHorizontal| Tui::bg(TuiTheme::g(40), Bsp::s( Bsp::e( Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())), self.timeline() diff --git a/output/src/content.rs b/output/src/content.rs deleted file mode 100644 index def71540..00000000 --- a/output/src/content.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::*; -/// Build a [Render]able out of other [Render]ables, -/// then apply optional custom render/layout on top. -pub trait Content: Send + Sync + Sized { - fn content (&self) -> impl Render { () } - fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) } - fn render (&self, output: &mut E) { self.content().render(output) } -} -impl> Content for &C { - fn content (&self) -> impl Render { (*self).content() } - fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) } - fn render (&self, output: &mut E) { (*self).render(output) } -} -/// The platonic ideal unit of [Content]: total emptiness at dead center. -impl Content for () { - fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() } - fn render (&self, _: &mut E) {} -} -impl> Content for Option { - fn content (&self) -> impl Render { - self.as_ref() - } - fn layout (&self, area: E::Area) -> E::Area { - self.as_ref() - .map(|content|content.layout(area)) - .unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into()) - } - fn render (&self, output: &mut E) { - self.as_ref() - .map(|content|content.render(output)); - } -} -//impl, E: Content> Content for Option { - //fn content (&self) -> impl Render { - //self.as_ref() - //} - //fn layout (&self, area: E::Area) -> E::Area { - //self.as_ref() - //.map(|content|content.layout(area)) - //.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into()) - //} - //fn render (&self, output: &mut E) { - //self.as_ref() - //.map(|content|content.render(output)); - //} -//} diff --git a/output/src/direction.rs b/output/src/direction.rs index 38126f4a..044a7d24 100644 --- a/output/src/direction.rs +++ b/output/src/direction.rs @@ -94,7 +94,6 @@ impl, B: Content> BspAreas for Bsp impl<'a, E: Output + 'a, T: EdnViewData<'a, E>> TryFromEdn<'a, T> for Bsp, RenderBox<'a, E>> { fn try_from_edn (s: &'a T, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option { use EdnItem::*; - panic!("({head} {} {})", tail[0], tail[1]); Some(match (head, tail) { (Key("bsp/n"), [a, b]) => Self::n(s.get_content(a).expect("no a"), s.get(b).expect("no b")), (Key("bsp/s"), [a, b]) => Self::s(s.get_content(a).expect("no a"), s.get(b).expect("no b")), diff --git a/output/src/lib.rs b/output/src/lib.rs index ffee4381..7f0831cb 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -6,10 +6,8 @@ mod coordinate; pub use self::coordinate::*; mod size; pub use self::size::*; mod area; pub use self::area::*; -mod output; pub use self::output::*; -mod content; pub use self::content::*; -mod render; pub use self::render::*; -mod thunk; pub use self::thunk::*; +mod output; pub use self::output::*; +mod thunk; pub use self::thunk::*; mod when; pub use self::when::*; mod either; pub use self::either::*; diff --git a/output/src/output.rs b/output/src/output.rs index b8dec0e3..22dfaa8d 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -1,6 +1,6 @@ use crate::*; - -/// Render target +use std::ops::Deref; +/// Render target. pub trait Output: Send + Sync + Sized { /// Unit of length type Unit: Coordinate; @@ -20,3 +20,113 @@ pub trait Output: Send + Sync + Sized { #[inline] fn h (&self) -> Self::Unit { self.area().h() } #[inline] fn wh (&self) -> Self::Size { self.area().wh().into() } } +/// Renderable with dynamic dispatch. +pub trait Render: Send + Sync { + /// Compute layout. + fn layout (&self, area: E::Area) -> E::Area; + /// Write data to display. + fn render (&self, output: &mut E); + /// Perform type erasure, turning `self` into an opaque [RenderBox]. + fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Sized + 'a { + Box::new(self) as RenderBox<'a, E> + } +} +/// Most importantly, every [Content] is also a [Render]. +/// +/// However, the converse does not hold true. +/// Instead, the [Content::content] method returns an +/// opaque [Render] pointer. +impl> Render for C { + fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) } + fn render (&self, output: &mut E) { Content::render(self, output) } +} +/// Opaque pointer to a renderable living on the heap. +/// +/// Return this from [Content::content] to use dynamic dispatch. +pub type RenderBox<'a, E> = Box>; +/// You can render from a box. +impl<'a, E: Output> Content for RenderBox<'a, E> { + fn content (&self) -> impl Render { self.deref() } + //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } +} +/// Opaque pointer to a renderable. +pub type RenderDyn<'a, E> = dyn Render + 'a; +/// You can render from an opaque pointer. +impl<'a, E: Output> Content for &RenderDyn<'a, E> where Self: Sized { + fn content (&self) -> impl Render { self.deref() } + fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) } + fn render (&self, output: &mut E) { Render::render(self.deref(), output) } +} +/// Composable renderable with static dispatch. +pub trait Content: Send + Sync + Sized { + /// Return a [Render]able of a specific type. + fn content (&self) -> impl Render { () } + /// Perform layout. By default, delegates to [Self::content]. + fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) } + /// Draw to output. By default, delegates to [Self::content]. + fn render (&self, output: &mut E) { self.content().render(output) } +} +/// Every pointer to [Content] is a [Content]. +impl> Content for &C { + fn content (&self) -> impl Render { (*self).content() } + fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) } + fn render (&self, output: &mut E) { (*self).render(output) } +} +/// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1)) +impl Content for () { + fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() } + fn render (&self, _: &mut E) {} +} +impl> Content for Option { + fn content (&self) -> impl Render { + self.as_ref() + } + fn layout (&self, area: E::Area) -> E::Area { + self.as_ref() + .map(|content|content.layout(area)) + .unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into()) + } + fn render (&self, output: &mut E) { + self.as_ref() + .map(|content|content.render(output)); + } +} +/// Implement [Content] with composable content for a struct. +#[macro_export] macro_rules! content { + // Implement for all [Output]s. + (|$self:ident:$Struct:ty| $content:expr) => { + impl Content for $Struct { + fn content (&$self) -> impl Render { Some($content) } + } + }; + // Implement for specific [Output]. + ($Output:ty:| + $self:ident: + $Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)? + |$content:expr) => { + impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output> + for $Struct $(<$($($L)? $($T)?),+>)? { + fn content (&$self) -> impl Render<$Output> { $content } + } + }; +} +/// Implement [Content] with custom rendering for a struct. +#[macro_export] macro_rules! render { + (|$self:ident:$Struct:ident $(< + $($L:lifetime),* $($T:ident $(:$Trait:path)?),* + >)?, $to:ident | $render:expr) => { + impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content + for $Struct $(<$($L),* $($T),*>>)? { + fn render (&$self, $to: &mut E) { $render } + } + }; + ($Output:ty:| + $self:ident: + $Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident + |$render:expr) => { + impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output> + for $Struct $(<$($($L)? $($T)?),+>)? { + fn render (&$self, $to: &mut $Output) { $render } + } + }; +} diff --git a/output/src/render.rs b/output/src/render.rs deleted file mode 100644 index 5fe0a137..00000000 --- a/output/src/render.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::*; -use std::ops::Deref; - -/// Custom layout and rendering. -pub trait Render: Send + Sync { - fn layout (&self, area: E::Area) -> E::Area; - fn render (&self, output: &mut E); - fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Sized + 'a { - Box::new(self) as RenderBox<'a, E> - } -} - -pub type RenderDyn<'a, Output> = dyn Render + 'a; -impl<'a, E: Output> Content for &RenderDyn<'a, E> where Self: Sized { - fn content (&self) -> impl Render { self.deref() } - fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) } - fn render (&self, output: &mut E) { Render::render(self.deref(), output) } -} - -pub type RenderBox<'a, E: Output> = Box>; -impl<'a, E: Output> Content for RenderBox<'a, E> { - fn content (&self) -> impl Render { self.deref() } - //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } -} - -impl> Render for C { - fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) } - fn render (&self, output: &mut E) { Content::render(self, output) } -} - -#[macro_export] macro_rules! render { - (($self:ident:$Struct:ty) => $content:expr) => { - impl Content for $Struct { - fn content (&$self) -> impl Render { Some($content) } - } - }; - (|$self:ident:$Struct:ident $(< - $($L:lifetime),* $($T:ident $(:$Trait:path)?),* - >)?, $to:ident | $render:expr) => { - impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content - for $Struct $(<$($L),* $($T),*>>)? { - fn render (&$self, $to: &mut E) { $render } - } - }; - ($Output:ty: - ($self:ident:$Struct:ident $(<$( - $($L:lifetime)? $($T:ident)? $(:$Trait:path)? - ),+>)?) => $content:expr - ) => { - impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output> - for $Struct $(<$($($L)? $($T)?),+>)? { - fn content (&$self) -> impl Render<$Output> { $content } - } - }; - - ($Output:ty: - |$self:ident : $Struct:ident $(<$( - $($L:lifetime)? $($T:ident)? $(:$Trait:path)? - ),+>)?, $to:ident| $render:expr - ) => { - impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output> - for $Struct $(<$($($L)? $($T)?),+>)? { - fn render (&$self, $to: &mut $Output) { $render } - } - }; -} diff --git a/sampler/src/sampler_tui.rs b/sampler/src/sampler_tui.rs index 34f76fcf..5b0c7639 100644 --- a/sampler/src/sampler_tui.rs +++ b/sampler/src/sampler_tui.rs @@ -27,7 +27,7 @@ impl SamplerTui { None } } -render!(TuiOut: (self: SamplerTui) => { +content!(TuiOut: |self: SamplerTui| { let keys_width = 5; let keys = move||"";//SamplerKeys(self); let fg = self.color.base.rgb; @@ -54,8 +54,8 @@ struct SamplesTui { note_pt: usize, height: usize, } -render!(TuiOut: |self: SamplesTui, render|{ - let x = render.area.x(); +render!(TuiOut: |self: SamplesTui, to| { + let x = to.area.x(); let bg_base = self.color.darkest.rgb; let bg_selected = self.color.darker.rgb; let style_empty = Style::default().fg(self.color.base.rgb); @@ -64,7 +64,7 @@ render!(TuiOut: |self: SamplesTui, render|{ let note = self.note_hi - y as usize; let bg = if note == self.note_pt { bg_selected } else { bg_base }; let style = Some(style_empty.bg(bg)); - render.blit(&" (no sample) ", x, render.area.y() + y as u16, style); + to.blit(&" (no sample) ", x, to.area.y() + y as u16, style); } }); impl NoteRange for SamplerTui { diff --git a/tek/src/lib.rs b/tek/src/lib.rs index b2d3c8f9..52bcfbd1 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -56,9 +56,7 @@ has_size!(|self: App|&self.size); has_clock!(|self: App|&self.clock); has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); //has_editor!(|self: App|self.editor.as_ref().expect("no editor")); -impl HasJack for App { - fn jack (&self) -> &Arc> { &self.jack } -} +has_jack!(|self: App|&self.jack); impl HasSampler for App { fn sampler (&self) -> &Option { &self.sampler } fn sampler_mut (&mut self) -> &mut Option { &mut self.sampler } @@ -106,10 +104,10 @@ edn_provide!('a: Box + 'a>: |self: App|{ ":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16, self.scene_header(), self.scene_cells(self.is_editing())).boxed(), }); -render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref()))); +content!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.as_ref()))); handle!(TuiIn: |self: App, input| Ok(None)); #[derive(Debug, Default)] struct Meter<'a>(pub &'a str, pub f32); -render!(TuiOut: (self: Meter<'a>) => col!( +content!(TuiOut: |self: Meter<'a>| col!( Field(TuiTheme::g(128).into(), self.0, format!("{:>+9.3}", self.1)), Fixed::xy(if self.1 >= 0.0 { 13 } else if self.1 >= -1.0 { 12 } @@ -128,7 +126,7 @@ render!(TuiOut: (self: Meter<'a>) => col!( else if self.1 >= -3.0 { Color::Yellow } else { Color::Green }, ())))); #[derive(Debug, Default)] struct Meters<'a>(pub &'a[f32]); -render!(TuiOut: (self: Meters<'a>) => col!( +content!(TuiOut: |self: Meters<'a>| col!( format!("L/{:>+9.3}", self.0[0]), format!("R/{:>+9.3}", self.0[1]) )); @@ -182,7 +180,7 @@ pub trait HasSelection { fn selected (&self) -> &Selection; fn selected_mut (&mut self) -> &mut Selection; } -#[derive(Debug, Default)] struct Track { +#[derive(Debug, Default)] pub struct Track { /// Name of track name: Arc, /// Preferred width of track column @@ -332,7 +330,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync trait Device: Send + Sync + std::fmt::Debug {} impl Device for Sampler {} impl Device for Plugin {} -#[derive(Debug, Default)] struct Scene { +#[derive(Debug, Default)] pub struct Scene { /// Name of scene name: Arc, /// Clips in scene, one per track diff --git a/time/src/clock_tui.rs b/time/src/clock_tui.rs index c09ca8c6..0cab6fda 100644 --- a/time/src/clock_tui.rs +++ b/time/src/clock_tui.rs @@ -2,10 +2,7 @@ use crate::*; use KeyCode::*; use ClockCommand::{Play, Pause}; /// Transport clock app. -pub struct ClockTui { - pub jack: Arc>, - pub clock: Clock, -} +pub struct ClockTui { pub jack: Arc>, pub clock: Clock, } handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event())); keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand { key(Char(' ')) => @@ -15,29 +12,20 @@ keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand { }); has_clock!(|self: ClockTui|&self.clock); audio!(|self: ClockTui, client, scope|ClockAudio(self).process(client, scope)); -render!(TuiOut: (self: ClockTui) => ClockView { - compact: false, - clock: &self.clock -}); - +content!(TuiOut:|self: ClockTui|ClockView { compact: false, clock: &self.clock }); pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock } -impl<'a> ClockView<'a> { - pub fn new (compact: bool, clock: &'a Clock) -> Self { - Self { compact, clock } - } -} -render!(TuiOut: (self: ClockView<'a>) => Outer( - Style::default().fg(TuiTheme::g(255)) -).enclose(row!( +content!(TuiOut: |self: ClockView<'a>| Outer(Style::default().fg(TuiTheme::g(255))).enclose(row!( OutputStats::new(self.compact, self.clock), " ", PlayPause { compact: false, playing: self.clock.is_rolling() }, " ", BeatStats::new(self.compact, self.clock), ))); - +impl<'a> ClockView<'a> { + pub fn new (compact: bool, clock: &'a Clock) -> Self { Self { compact, clock } } +} pub struct PlayPause { pub compact: bool, pub playing: bool } -render!(TuiOut: (self: PlayPause) => Tui::bg( +content!(TuiOut: |self: PlayPause| Tui::bg( if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, Either::new(self.compact, Thunk::new(||Fixed::x(9, Either::new(self.playing, @@ -46,8 +34,14 @@ render!(TuiOut: (self: PlayPause) => Tui::bg( Thunk::new(||Fixed::x(5, Either::new(self.playing, Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))))))); - pub struct BeatStats { compact: bool, bpm: Arc, beat: Arc, time: Arc, } +content!(TuiOut: |self: BeatStats| Either::new(self.compact, + row!(FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm), + FieldV(TuiTheme::g(128).into(), "Beat", &self.beat), + FieldV(TuiTheme::g(128).into(), "Time", &self.time),), + col!(Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"), + Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)), + Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time))))); impl BeatStats { fn new (compact: bool, clock: &Clock) -> Self { let bpm = format!("{:.3}", clock.timebase.bpm.get()).into(); @@ -63,46 +57,27 @@ impl BeatStats { Self { compact, bpm, beat, time } } } -render!(TuiOut: (self: BeatStats) => Either::new(self.compact, - row!( - FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm), - FieldV(TuiTheme::g(128).into(), "Beat", &self.beat), - FieldV(TuiTheme::g(128).into(), "Time", &self.time), - ), - col!( - Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"), - Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)), - Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)), - ))); - pub struct OutputStats { compact: bool, sample_rate: Arc, buffer_size: Arc, latency: Arc, } +content!(TuiOut: |self: OutputStats| Either::new(self.compact, + row!(FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate), + FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size), + FieldV(TuiTheme::g(128).into(), "Lat", &self.latency)), + col!(Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"), + Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"), + Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency")))); impl OutputStats { fn new (compact: bool, clock: &Clock) -> Self { let rate = clock.timebase.sr.get(); let chunk = clock.chunk.load(Relaxed); + let sr = if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)}; Self { compact, - sample_rate: if compact { - format!("{:.1}kHz", rate / 1000.) - } else { - format!("{:.0}Hz", rate) - }.into(), + sample_rate: sr.into(), buffer_size: format!("{chunk}").into(), latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(), } } } -render!(TuiOut: (self: OutputStats) => Either::new(self.compact, - row!( - FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate), - FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size), - FieldV(TuiTheme::g(128).into(), "Lat", &self.latency), - ), - col!( - Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"), - Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"), - Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"), - ))); // TODO: //keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { diff --git a/tui/src/tui_border.rs b/tui/src/tui_border.rs index 264e9ec3..daac6c1a 100644 --- a/tui/src/tui_border.rs +++ b/tui/src/tui_border.rs @@ -1,13 +1,11 @@ use crate::*; pub struct Bordered>(pub S, pub W); - -render!(TuiOut: (self: Bordered>) => { - Fill::xy(lay!(Border(self.0), Padding::xy(1, 1, &self.1))) -}); +content!(TuiOut: |self: Bordered>|Fill::xy( + lay!(Border(self.0), Padding::xy(1, 1, &self.1)) +)); pub struct Border(pub S); - render!(TuiOut: |self: Border, to| { let area = to.area(); if area.w() > 0 && area.y() > 0 { diff --git a/tui/src/tui_file.rs b/tui/src/tui_file.rs index 743897a8..401b8ae7 100644 --- a/tui/src/tui_file.rs +++ b/tui/src/tui_file.rs @@ -20,7 +20,7 @@ pub enum FileBrowserCommand { Chdir(PathBuf), Filter(Arc), } -render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{ +content!(TuiOut: |self: FileBrowser| /*Stack::down(|add|{ let mut i = 0; for (_, name) in self.dirs.iter() { if i >= self.scroll {