diff --git a/src/arranger.rs b/src/arranger.rs index 502d6293..efb6d39f 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -100,8 +100,8 @@ impl ArrangerTui { } } render!(|self: ArrangerTui|{ - let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())); - let transport = TransportView::from((self, Some(ItemPalette::from(TuiTheme::g(96))), true)); + let play = PlayPause(self.clock.is_rolling()); + let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true); let with_transport = |x|col!([row!(![&play, &transport]), &x]); let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x); diff --git a/src/groovebox.rs b/src/groovebox.rs index 00457ed4..a1f24c71 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -117,15 +117,16 @@ render!(|self:Groovebox|{ let pool_w = if self.pool.visible { phrase_w } else { 0 }; let sampler_w = 11; let note_pt = self.editor.note_point(); + let color = self.player.play_phrase().as_ref() + .and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)) + .clone(); Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), Shrink::y(2, col!([ - Fixed::h(2, row!([ - Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), - Fixed::h(2, TransportView::from((self, self.player.play_phrase().as_ref().map(|(_,p)| - p.as_ref().map(|p|p.read().unwrap().color) - ).flatten().clone(), true))), + Fixed::h(3, row!([ + PlayPause(self.clock().is_rolling()), + TransportView::new(self, color, true), ])), Push::x(sampler_w, Fixed::h(1, row!([ PhraseSelector::play_phrase(&self.player), diff --git a/src/sequencer.rs b/src/sequencer.rs index 02577226..b9dc4287 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -57,8 +57,8 @@ render!(|self: SequencerTui|{ ).flatten().clone(); let toolbar = Cond::when(self.transport, row!([ - Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())), - Fixed::h(2, TransportView::from((self, color, true))), + PlayPause(self.clock.is_rolling()), + TransportView::new(self, color, true), ])); let play_queue = Cond::when(self.selectors, row!([ diff --git a/src/space.rs b/src/space.rs index e8019866..72bd9d3c 100644 --- a/src/space.rs +++ b/src/space.rs @@ -11,6 +11,11 @@ mod scroll; pub use self::scroll::*; mod size; pub use self::size::*; mod split; pub use self::split::*; +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq)] +pub enum Direction { North, South, West, East, } +use self::Direction::*; + /// Has static methods for conditional rendering, /// in unary and binary forms. pub struct Cond; @@ -94,6 +99,36 @@ pub trait Size { } } +pub trait HasSize { + fn size (&self) -> &Measure; +} + +#[macro_export] macro_rules! has_size { + (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { + fn size (&$self) -> &Measure<$E> { $cb } + } + } +} + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +pub struct ShowMeasure<'a>(&'a Measure); + +pub trait LayoutDebug { + fn debug > (other: W) -> DebugOverlay { + DebugOverlay(Default::default(), other) + } +} + +pub struct DebugOverlay>(PhantomData, pub W); + pub trait Area: Copy { fn x (&self) -> N; fn y (&self) -> N; @@ -319,14 +354,27 @@ pub enum Margin> { by_axis!(+Margin); -/// A cardinal direction. -#[derive(Copy, Clone, PartialEq)] -pub enum Direction { North, South, West, East, } - /// A binary split with fixed proportion -pub struct Split, B: Render>( - pub bool, pub Direction, pub E::Unit, A, B, PhantomData -); +pub struct Split(pub bool, pub Direction, pub E::Unit, A, B, PhantomData) +where E: Engine, A: Render, B: Render; + +impl, B: Render> Split { + #[inline] pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { + Self(flip, direction, proportion, a, b, Default::default()) + } + #[inline] pub fn n (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, North, proportion, a, b) + } + #[inline] pub fn s (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, South, proportion, a, b) + } + #[inline] pub fn e (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, West, proportion, a, b) + } + #[inline] pub fn w (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, East, proportion, a, b) + } +} pub enum Bsp, Y: Render> { /// X is north of Y @@ -345,11 +393,39 @@ pub enum Bsp, Y: Render> { Null(PhantomData), } +impl, Y: Render> Bsp { + pub fn new (x: X) -> Self { Self::A(Some(x), None) } + pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } + pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) } + pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) } + pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) } + pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) } + pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) } +} + pub struct Stack< E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> >(pub F, pub Direction, PhantomData); +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +> Stack { + #[inline] pub fn new (direction: Direction, build: F) -> Self { + Self(build, direction, Default::default()) + } + #[inline] pub fn right (build: F) -> Self { + Self::new(East, build) + } + #[inline] pub fn down (build: F) -> Self { + Self::new(South, build) + } + #[inline] pub fn up (build: F) -> Self { + Self::new(North, build) + } +} + #[macro_export] macro_rules! col { ([$($expr:expr),* $(,)?]) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) @@ -395,41 +471,11 @@ pub struct Stack< }; } -pub trait HasSize { - fn size (&self) -> &Measure; -} - -#[macro_export] macro_rules! has_size { - (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { - fn size (&$self) -> &Measure<$E> { $cb } - } - } -} - -pub trait LayoutDebug { - fn debug > (other: W) -> DebugOverlay { - DebugOverlay(Default::default(), other) - } -} - -pub struct DebugOverlay>(PhantomData, pub W); - -/// A widget that tracks its render width and height -#[derive(Default)] -pub struct Measure { - _engine: PhantomData, - pub x: Arc, - pub y: Arc, -} - -pub struct ShowMeasure<'a>(&'a Measure); - /// A scrollable area. -pub struct Scroll< +pub struct Scroll(pub F, pub Direction, pub u64, PhantomData) +where E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, pub Direction, pub u64, PhantomData); + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()>; /// Override X and Y coordinates, aligning to corner, side, or center of area pub enum Align> { diff --git a/src/space/split.rs b/src/space/split.rs index e5f6217c..6bc8ef2d 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -27,24 +27,6 @@ impl Direction { } } -impl, B: Render> Split { - pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, direction, proportion, a, b, Default::default()) - } - pub fn n (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, North, proportion, a, b, Default::default()) - } - pub fn s (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, South, proportion, a, b, Default::default()) - } - pub fn e (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, West, proportion, a, b, Default::default()) - } - pub fn w (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, East, proportion, a, b, Default::default()) - } -} - impl, B: Render> Render for Split { fn min_size (&self, to: E::Size) -> Perhaps { Ok(Some(to)) @@ -61,16 +43,6 @@ impl, B: Render> Render for Split { } } -impl, Y: Render> Bsp { - pub fn new (x: X) -> Self { Self::A(Some(x), None) } - pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } - pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) } - pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) } - pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) } - pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) } - pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) } -} - impl, Y: Render> Default for Bsp { fn default () -> Self { Self::Null(Default::default()) @@ -142,24 +114,6 @@ impl, Y: Render> Render for Bsp { } } -impl< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -> Stack { - #[inline] pub fn new (direction: Direction, build: F) -> Self { - Self(build, direction, Default::default()) - } - #[inline] pub fn right (build: F) -> Self { - Self::new(East, build) - } - #[inline] pub fn down (build: F) -> Self { - Self::new(South, build) - } - #[inline] pub fn up (build: F) -> Self { - Self::new(North, build) - } -} - impl Render for Stack where F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> diff --git a/src/transport.rs b/src/transport.rs index 746e8cd7..f9d90e93 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -18,10 +18,13 @@ from_jack!(|jack|TransportTui Self { cursor: (0, 0), focus: TransportFocus::PlayPause }); -has_clock!(|self:TransportTui|&self.clock); -audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope)); -handle!(|self:TransportTui,from|TransportCommand::execute_with_state(self, from)); -render!(|self: TransportTui|TransportView::from((self, None, true))); +has_clock!(|self: TransportTui|&self.clock); +audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); +handle!(|self: TransportTui, from|TransportCommand::execute_with_state(self, from)); +render!(|self: TransportTui|row!([ + Fixed::wh(5, 3, PlayPause(false)), + Fixed::h(3, TransportView::new(self, None, true)) +])); impl std::fmt::Debug for TransportTui { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("TransportTui") @@ -46,8 +49,8 @@ pub struct TransportView { current_sample: f64, current_second: f64, } -impl From<(&T, Option, bool)> for TransportView { - fn from ((state, color, focused): (&T, Option, bool)) -> Self { +impl TransportView { + pub fn new (state: &impl HasClock, color: Option, focused: bool) -> Self { let clock = state.clock(); let rate = clock.timebase.sr.get(); let chunk = clock.chunk.load(Relaxed); @@ -58,61 +61,56 @@ impl From<(&T, Option, bool)> for TransportView { let chunk = format!("{chunk}"); let latency = format!("{latency}"); let color = color.unwrap_or(ItemPalette::from(TuiTheme::g(32))); - if let Some(started) = clock.started.read().unwrap().as_ref() { + let ( + global_sample, global_second, current_sample, current_second, beat + ) = if let Some(started) = clock.started.read().unwrap().as_ref() { let current_sample = (clock.global.sample.get() - started.sample.get())/1000.; let current_usec = clock.global.usec.get() - started.usec.get(); let current_second = current_usec/1000000.; - Self { - color, focused, sr, bpm, ppq, chunk, latency, - started: true, - global_sample: format!("{:.0}k", started.sample.get()/1000.), - global_second: format!("{:.1}s", started.usec.get()/1000.), - current_sample, - current_second, - beat: clock.timebase.format_beats_0( - clock.timebase.usecs_to_pulse(current_usec) - ), - } + let global_sample = format!("{:.0}k", started.sample.get()/1000.); + let global_second = format!("{:.1}s", started.usec.get()/1000.); + let beat = clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(current_usec)); + (global_sample, global_second, current_sample, current_second, beat) } else { - Self { - color, focused, sr, bpm, ppq, chunk, latency, - started: false, - global_sample: format!("{:.0}k", clock.global.sample.get()/1000.), - global_second: format!("{:.1}s", clock.global.usec.get()/1000000.), - current_sample: 0.0, - current_second: 0.0, - beat: format!("000.0.00") - } + let global_sample = format!("{:.0}k", clock.global.sample.get()/1000.); + let global_second = format!("{:.1}s", clock.global.usec.get()/1000000.); + let current_sample = 0.0; + let current_second = 0.0; + let beat = format!("000.0.00"); + (global_sample, global_second, current_sample, current_second, beat) + }; + Self { + color, focused, sr, bpm, ppq, chunk, latency, started: false, + global_sample, global_second, current_sample, current_second, beat } } } render!(|self: TransportView|{ let color = self.color; - struct Field<'a>(&'a str, &'a str, &'a ItemPalette); - render!(|self: Field<'a>|row!([ - Tui::fg_bg(self.2.lightest.rgb, self.2.base.rgb, Tui::bold(true, self.0)), - Tui::fg_bg(self.2.base.rgb, self.2.darkest.rgb, "▌"), - Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, format!("{:>10}", self.1)), - Tui::fg_bg(self.2.darkest.rgb, self.2.base.rgb, "▌"), - ])); - Tui::bg(color.base.rgb, Fill::w(row!([ + Fixed::h(3, Tui::bg(color.base.rgb, Fill::w(row!([ //PlayPause(self.started), " ", col!([ - Field(" Beat", self.beat.as_str(), &color), - Field(" Time", format!("{:.1}s", self.current_second).as_str(), &color), + TransportField(" Beat", self.beat.as_str(), &color), + TransportField(" Time", format!("{:.1}s", self.current_second).as_str(), &color), + TransportField(" BPM", self.bpm.as_str(), &color), + ]), + col!([ + TransportField(" Rate", format!("{}", self.sr).as_str(), &color), + TransportField(" Chunk", format!("{}", self.chunk).as_str(), &color), + TransportField(" Lag", format!("{:.3}ms", self.latency).as_str(), &color), ]), col!([ - Field(" BPM", self.bpm.as_str(), &color), - //Field(" Smpl", format!("{:.1}k", self.current_sample).as_str(), &color), - Field(" Rate", format!("{}", self.sr).as_str(), &color), //Field(" CPU%", format!("{:.1}ms", self.perf).as_str(), &color), ]), - col!([ - Field(" Chunk", format!("{}", self.chunk).as_str(), &color), - Field(" Lag", format!("{:.3}ms", self.latency).as_str(), &color), - ]), - ]))) + ])))) }); +struct TransportField<'a>(&'a str, &'a str, &'a ItemPalette); +render!(|self: TransportField<'a>|row!([ + Tui::fg_bg(self.2.lightest.rgb, self.2.base.rgb, Tui::bold(true, self.0)), + Tui::fg_bg(self.2.base.rgb, self.2.darkest.rgb, "▌"), + Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, format!("{:>10}", self.1)), + Tui::fg_bg(self.2.darkest.rgb, self.2.base.rgb, "▌"), +])); pub struct PlayPause(pub bool); render!(|self: PlayPause|Tui::bg( if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},