diff --git a/engine/src/output.rs b/engine/src/output.rs index 4f361ce6..2de19448 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -39,6 +39,60 @@ impl Content for () {} impl> Content for &T {} +impl> Content for Option {} + +#[macro_export] macro_rules! render { + (($self:ident:$Struct:ty) => $content:expr) => { + impl Content for $Struct { + fn content (&$self) -> Option> { + Some($content) + } + } + }; + (|$self:ident:$Struct:ident $(< + $($L:lifetime),* + $($T:ident $(:$Trait:path)?),* + >)?, $to:ident | $render:expr) => { + impl <$($($L),*)? E: Engine, $($T$(:$Trait)?),*> Content + for $Struct $(<$($L),* $($T),*>>)? { + fn render (&$self, $to: &mut E::Output) { + Some($render) + } + } + }; + ($Engine:ty: + ($self:ident:$Struct:ident $(<$( + $($L:lifetime)? + $($T:ident)? + $(:$Trait:path)? + ),+>)?) => $content:expr + ) => { + impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Engine> + for $Struct $(<$($($L)? $($T)?),+>)? + { + fn content (&$self) -> Option> { + Some($content) + } + } + }; + + ($Engine:ty: + |$self:ident : $Struct:ident $(<$( + $($L:lifetime)? + $($T:ident)? + $(:$Trait:path)? + ),+>)?, $to:ident| $render:expr + ) => { + impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Engine> + for $Struct $(<$($($L)? $($T)?),+>)? + { + fn render (&$self, $to: &mut <$Engine as Engine>::Output) { + Some($render) + } + } + }; +} + //impl> Render for &R {} //pub trait Render: Send + Sync { diff --git a/layout/src/collection.rs b/layout/src/collection.rs index ef390198..317e7c46 100644 --- a/layout/src/collection.rs +++ b/layout/src/collection.rs @@ -1,30 +1,10 @@ //! Groupings of elements. -mod bsp; pub use self::bsp::*; mod split; pub use self::split::*; mod stack; pub use self::stack::*; use crate::*; -/// A function or closure that emits renderables. -pub trait Collector: Send + Sync + Fn(&mut dyn FnMut(&dyn Content)) {} - -/// Any function or closure that emits renderables for the given engine matches [CollectCallback]. -impl Collector for F -where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content)) {} - -pub struct Map(PhantomData, I, F) - where E: Engine, I: Iterator, R: Content, F: Fn(T)->R; - -pub struct Reduce(PhantomData<(E, R)>, I, F) - where E: Engine, I: Iterator, R: Content, F: Fn(&dyn Content, T)->R; - -impl+Send+Sync, R: Content, F: Fn(&dyn Content, T)->R+Send+Sync> Content for Reduce { - fn render (&self, _to: &mut E::Output) { - todo!() - } -} - /// Conditional rendering, in unary and binary forms. pub struct Cond; @@ -74,3 +54,70 @@ impl, B: Content> Content for Either { if *cond { a.render(to) } else { b.render(to) } } } + +/// A function or closure that emits renderables. +pub trait Collector: Send + Sync + Fn(&mut dyn FnMut(&dyn Content)) {} + +/// Any function or closure that emits renderables for the given engine matches [CollectCallback]. +impl Collector for F +where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content)) {} + +/* +/// Rendering of iterable collections, one-to-one and many-to one. +pub struct Coll; + +impl Coll { + pub fn map (iterator: I, callback: F) -> Map where + E: Engine, + I: Iterator + Send + Sync, + R: Content, + F: Fn(T, usize)->R + Send + Sync + { + Map(Default::default(), iterator, callback) + } + pub fn reduce (iterator: I, callback: F) -> Reduce where + E: Engine, + I: Iterator + Send + Sync, + R: Content, + F: Fn(R, T, usize) -> R + Send + Sync + { + Reduce(Default::default(), iterator, callback) + } +} + +pub struct Map<'a, E, T, I, R, F>(PhantomData, &'a mut I, F) where + E: Engine, + I: Iterator + Send + Sync, + R: Content, + F: Fn(T, usize)->R + Send + Sync; +impl<'a, E, T, I, R, F> Content for Map<'a, E, T, I, R, F> where + E: Engine, + I: Iterator + Send + Sync, + R: Content, + F: Fn(T, usize)->R + Send + Sync +{ + fn render (&self, to: &mut E::Output) { + let mut index = 0; + for item in self.1 { + (self.2)(item, index).render(to); + index += 1; + } + } +} + +pub struct Reduce(PhantomData<(E, R)>, I, F) where + E: Engine, + I: Iterator + Send + Sync, + R: Content, + F: Fn(R, T, usize) -> R + Send + Sync; +impl Content for Reduce where + E: Engine, + I: Iterator + Send + Sync, + R: Content, + F: Fn(R, T, usize) -> R + Send + Sync +{ + fn render (&self, to: &mut E::Output) { + todo!() + } +} +*/ diff --git a/layout/src/collection/bsp.rs b/layout/src/collection/bsp.rs deleted file mode 100644 index ae5084fa..00000000 --- a/layout/src/collection/bsp.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::*; - -/// Renders multiple things on top of each other, -macro_rules! lay { - ($($expr:expr),*) => {{ - let layers = (); - $(let layers = Bsp::b(layers, $expr);)*; - layers - }} -} - -/// Renders multiple things on top of each other, -macro_rules! col { - ($($expr:expr),*) => {{ - let layers = (); - $(let layers = Bsp::s(layers, $expr);)*; - layers - }} -} - -pub enum Bsp, Y: Content> { - /// X is north of Y - N(f64, Option, Option), - /// X is south of Y - S(f64, Option, Option), - /// X is east of Y - E(f64, Option, Option), - /// X is west of Y - W(f64, Option, Option), - /// X is above Y - A(Option, Option), - /// X is below Y - B(Option, Option), - /// Should be avoided. - Null(PhantomData), -} - -impl, Y: Content> Bsp { - pub fn n (p: f64, x: X, y: Y) -> Self { Self::N(p, Some(x), Some(y)) } - pub fn s (p: f64, x: X, y: Y) -> Self { Self::S(p, Some(x), Some(y)) } - pub fn e (p: f64, x: X, y: Y) -> Self { Self::E(p, Some(x), Some(y)) } - pub fn w (p: f64, x: X, y: Y) -> Self { Self::W(p, 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: Content> Default for Bsp { - fn default () -> Self { - Self::Null(Default::default()) - } -} - -impl, Y: Content> Content for Bsp { - fn render (&self, to: &mut E::Output) { - let n = E::Size::zero(); - let s = to.wh(); - match self { - Self::Null(_) => {}, - //Self::S(p, a, b) => { - //let s_a = a.min_size(s)?.unwrap_or(n); - //let _ = b.min_size(s)?.unwrap_or(n); - //let h = s_a.h().into(); - //to.render_in(to.area().clip_h(h).into(), a); - //to.render_in(to.area().shrink_y(h).push_y(h).into(), b); - //}, - //Self::E(p, a, b) => { - //let s_a = a.min_size(s)?.unwrap_or(n); - //let _ = b.min_size(s)?.unwrap_or(n); - //let w = s_a.w().into(); - //to.render_in(to.area().clip_w(w).into(), a); - //to.render_in(to.area().push_x(w).shrink_x(w).into(), b); - //}, - //Self::W(p, a, b) => { - //let s_a = a.min_size(s)?.unwrap_or(n); - //let _ = b.min_size(s)?.unwrap_or(n); - //let w = (to.area().w() - s_a.w()).into(); - //to.render_in(to.area().push_x(w).into(), a); - //to.render_in(to.area().shrink_x(w).into(), b); - //}, - //Self::N(p, a, b) => { - //let s_a = a.min_size(s)?.unwrap_or(n); - //let _ = b.min_size(s)?.unwrap_or(n); - //let h = to.area().h() - s_a.h(); - //to.render_in(to.area().push_y(h).into(), a); - //to.render_in(to.area().shrink_y(h).into(), b); - //}, - _ => todo!() - } - } -} diff --git a/layout/src/direction.rs b/layout/src/direction.rs index 8c189e3f..1b56954c 100644 --- a/layout/src/direction.rs +++ b/layout/src/direction.rs @@ -50,3 +50,110 @@ impl Direction { } } } + +/// Renders multiple things on top of each other, +#[macro_export] macro_rules! lay { + ($($expr:expr),* $(,)?) => {{ + let bsp = (); + $(let bsp = Bsp::b(bsp, $expr);)*; + bsp + }} +} + +/// Stack southward. +#[macro_export] macro_rules! col { + ($($expr:expr),* $(,)?) => {{ + let bsp = (); + $(let bsp = Bsp::s(bsp, $expr);)*; + bsp + }}; +} + +/// Stack northward. +#[macro_export] macro_rules! col_up { + ($($expr:expr),* $(,)?) => {{ + let bsp = (); + $(let bsp = Bsp::n(bsp, $expr);)*; + bsp + }} +} + +/// Stack eastward. +#[macro_export] macro_rules! row { + ($($expr:expr),* $(,)?) => {{ + let bsp = (); + $(let bsp = Bsp::e(bsp, $expr);)*; + bsp + }}; +} + +pub enum Bsp, Y: Content> { + /// X is north of Y + N(Option, Option, Option), + /// X is south of Y + S(Option, Option, Option), + /// X is east of Y + E(Option, Option, Option), + /// X is west of Y + W(Option, Option, Option), + /// X is above Y + A(Option, Option), + /// X is below Y + B(Option, Option), + /// Should be avoided. + Null(PhantomData), +} + +impl, Y: Content> Bsp { + pub fn n (x: X, y: Y) -> Self { Self::N(None, Some(x), Some(y)) } + pub fn s (x: X, y: Y) -> Self { Self::S(None, Some(x), Some(y)) } + pub fn e (x: X, y: Y) -> Self { Self::E(None, Some(x), Some(y)) } + pub fn w (x: X, y: Y) -> Self { Self::W(None, 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: Content> Default for Bsp { + fn default () -> Self { + Self::Null(Default::default()) + } +} + +impl, Y: Content> Content for Bsp { + fn render (&self, to: &mut E::Output) { + let n = E::Size::zero(); + let s = to.wh(); + match self { + Self::Null(_) => {}, + //Self::S(p, a, b) => { + //let s_a = a.min_size(s)?.unwrap_or(n); + //let _ = b.min_size(s)?.unwrap_or(n); + //let h = s_a.h().into(); + //to.render_in(to.area().clip_h(h).into(), a); + //to.render_in(to.area().shrink_y(h).push_y(h).into(), b); + //}, + //Self::E(p, a, b) => { + //let s_a = a.min_size(s)?.unwrap_or(n); + //let _ = b.min_size(s)?.unwrap_or(n); + //let w = s_a.w().into(); + //to.render_in(to.area().clip_w(w).into(), a); + //to.render_in(to.area().push_x(w).shrink_x(w).into(), b); + //}, + //Self::W(p, a, b) => { + //let s_a = a.min_size(s)?.unwrap_or(n); + //let _ = b.min_size(s)?.unwrap_or(n); + //let w = (to.area().w() - s_a.w()).into(); + //to.render_in(to.area().push_x(w).into(), a); + //to.render_in(to.area().shrink_x(w).into(), b); + //}, + //Self::N(p, a, b) => { + //let s_a = a.min_size(s)?.unwrap_or(n); + //let _ = b.min_size(s)?.unwrap_or(n); + //let h = to.area().h() - s_a.h(); + //to.render_in(to.area().push_y(h).into(), a); + //to.render_in(to.area().shrink_y(h).into(), b); + //}, + _ => todo!() + } + } +} diff --git a/layout/src/transform.rs b/layout/src/transform.rs index 446f24ae..7f01a639 100644 --- a/layout/src/transform.rs +++ b/layout/src/transform.rs @@ -26,12 +26,12 @@ macro_rules! transform_xy { } impl> Content for $Enum { fn content (&self) -> Option> { - match self { - Self::_Unused(_) => None, - Self::X(item) => Some(item), - Self::Y(item) => Some(item), - Self::XY(item) => Some(item), - } + Some(match self { + Self::X(item) => item, + Self::Y(item) => item, + Self::XY(item) => item, + _ => unreachable!() + }) } fn area (&$self, $to: ::Area) -> ::Area { $area @@ -42,16 +42,13 @@ macro_rules! transform_xy { transform_xy!(self: Fill |to|{ let [x0, y0, wmax, hmax] = to.xywh(); - if let Some(content) = self.content() { - let [x, y, w, h] = content.area(to).xywh(); - return match self { - Self::X(_) => [x0, y, wmax, h], - Self::Y(_) => [x, y0, w, hmax], - Self::XY(_) => [x0, y0, wmax, hmax], - _ => unreachable!() - }.into() - } - return [0.into(), 0.into(), 0.into(), 0.into(),].into() + let [x, y, w, h] = self.content().unwrap().area(to).xywh(); + return match self { + Self::X(_) => [x0, y, wmax, h], + Self::Y(_) => [x, y0, w, hmax], + Self::XY(_) => [x0, y0, wmax, hmax], + _ => unreachable!() + }.into() }); /// Defines an enum that transforms its content parametrically diff --git a/src/arranger.rs b/src/arranger.rs index e4e14924..5c3c5454 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -92,14 +92,14 @@ from_jack!(|jack| ArrangerTui { } }); impl ArrangerTui { - fn render_mode (state: &Self) -> impl Render + use<'_> { + fn render_mode (state: &Self) -> impl Content + use<'_> { match state.mode { ArrangerMode::H => todo!("horizontal arranger"), ArrangerMode::V(factor) => Self::render_mode_v(state, factor), } } } -render!(|self: ArrangerTui|{ +render!(Tui: (self: ArrangerTui) => { 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]); @@ -115,10 +115,10 @@ render!(|self: ArrangerTui|{ add(&Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?; add(&Self::render_mode(self)) }); - Some(with_size(with_status(with_editbar(with_pool(with_transport(col!([ + with_size(with_status(with_editbar(with_pool(with_transport(col!([ Fill::x(Fixed::y(20, arranger())), Fill::xy(&self.editor), - ]))))))) + ])))))) }); audio!(|self: ArrangerTui, client, scope|{ // Start profiling cycle diff --git a/src/arranger/arranger_mode.rs b/src/arranger/arranger_mode.rs index 364bf26d..d1f5b32c 100644 --- a/src/arranger/arranger_mode.rs +++ b/src/arranger/arranger_mode.rs @@ -7,7 +7,7 @@ pub enum ArrangerMode { /// Tracks are rows H, } -render!(|self: ArrangerMode|""); +impl Content for ArrangerMode {} /// Arranger display mode can be cycled impl ArrangerMode { /// Cycle arranger display mode diff --git a/src/arranger/arranger_v/v_clips.rs b/src/arranger/arranger_v/v_clips.rs index b0805ffe..1f72f13c 100644 --- a/src/arranger/arranger_v/v_clips.rs +++ b/src/arranger/arranger_v/v_clips.rs @@ -7,39 +7,42 @@ pub struct ArrangerVClips<'a> { tracks: &'a Vec, rows: Vec<(usize, usize)>, } - -from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVClips<'a> = Self { - size: &args.0.size, - scenes: &args.0.scenes, - tracks: &args.0.tracks, - rows: ArrangerScene::ppqs(&args.0.scenes, args.1), -}); - -render!(|self: ArrangerVClips<'a>|Fill::xy( - col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => { - Self::format_scene(self.tracks, scene, pulses) - }) -)); - impl<'a> ArrangerVClips<'a> { + pub fn new (state: &'a ArrangerTui, zoom: usize) -> Self { + Self { + size: &state.size, + tracks: &state.tracks, + scenes: &state.scenes, + rows: ArrangerScene::ppqs(&state.scenes, zoom), + } + } +} +impl<'a, E: Engine> Content for ArrangerVClips<'a> { + fn content (&self) -> Option> { + let iter = self.scenes.iter().zip(self.rows.iter().map(|row|row.0)); + let col = col_iter!(iter => |(scene, pulses)|Self::format_scene(self.tracks, scene, pulses)); + Some(Fill::xy(col)) + } +} +impl<'a> ArrangerVClips<'a> { + fn format_scene ( tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize - ) -> impl Render + use<'a> { + ) -> impl Content + use<'a> { let height = 1.max((pulses / PPQ) as u16); let playing = scene.is_playing(tracks); - Fixed::y(height, row!([ + Fixed::y(height, row!( Tui::bg(scene.color.base.rgb, if playing { "▶ " } else { " " }), Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, Expand::x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), - row!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { + row_iter!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { Self::format_clip(scene, index, track, (x2 - x1) as u16, height) - })]) - ) - } + })))} + fn format_clip ( scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 - ) -> impl Render + use<'a> { + ) -> impl Content + use<'a> { Fixed::xy(w, h, Layers::new(move |add|{ if let Some(Some(phrase)) = scene.clips.get(index) { let mut bg = TuiTheme::border_bg(); diff --git a/src/arranger/arranger_v/v_cursor.rs b/src/arranger/arranger_v/v_cursor.rs index eb945a85..71532ad9 100644 --- a/src/arranger/arranger_v/v_cursor.rs +++ b/src/arranger/arranger_v/v_cursor.rs @@ -24,11 +24,9 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self { sub_modifier: Modifier::DIM }), }); - -render!((self: ArrangerVCursor) - |layout|Ok(Some([0, 0])), - |render|{ - let area = render.area(); +impl Content for ArrangerVCursor { + fn render (&self, to: &mut TuiOutput) { + let area = to.area(); let focused = true; let selected = self.selected; let get_track_area = |t: usize| [ @@ -67,17 +65,18 @@ render!((self: ArrangerVCursor) }; let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0); if let Some([x, y, width, height]) = track_area { - render.fill_fg([x, y, 1, height], bg); - render.fill_fg([x + width, y, 1, height], bg); + to.fill_fg([x, y, 1, height], bg); + to.fill_fg([x + width, y, 1, height], bg); } if let Some([_, y, _, height]) = scene_area { - render.fill_ul([area.x(), y - 1, area.w(), 1], bg); - render.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); + to.fill_ul([area.x(), y - 1, area.w(), 1], bg); + to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); } Ok(if focused { - render.render_in(if let Some(clip_area) = clip_area { clip_area } + to.render_in(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(self.scenes_w) } else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)? }) - }); + } +} diff --git a/src/arranger/arranger_v/v_head.rs b/src/arranger/arranger_v/v_head.rs index 52a47a86..38344ad3 100644 --- a/src/arranger/arranger_v/v_head.rs +++ b/src/arranger/arranger_v/v_head.rs @@ -15,44 +15,48 @@ from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16, }); -render!(|self: ArrangerVHead<'a>|Push::x(self.scenes_w, row!( - (_, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - fn row > (color: ItemPalette, field: &T) -> impl Render + use<'_, T> { - row!([ - Tui::fg(color.light.rgb, "▎"), - Tui::fg(color.lightest.rgb, field) - ]) +impl Content for ArrangerVCursor { + fn content (&self) -> Option> { + + fn row > (color: ItemPalette, field: &T) -> impl Content + use<'_, T> { + row!([Tui::fg(color.light.rgb, "▎"), Tui::fg(color.lightest.rgb, field)]) } - Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::xy(w as u16, 5, col!([ - row(color, &Self::format_name(track, w)), - row(color, &Self::format_input(track)?), - row(color, &Self::format_output(track)?), - row(color, &Self::format_elapsed(track, self.timebase)), - row(color, &Self::format_until_next(track, self.current)), - ])))) + + Some(Push::x(self.scenes_w, row_iter!( + (_0, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => { + let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); + let color = track.color(); + Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::xy(w as u16, 5, col!([ + row(color, &Self::format_name(track, w)), + row(color, &Self::format_input(track)?), + row(color, &Self::format_output(track)?), + row(color, &Self::format_elapsed(track, self.timebase)), + row(color, &Self::format_until_next(track, self.current)), + ])))) + } + ))) + } -))); +} impl ArrangerVHead<'_> { /// name and width of track - fn format_name (track: &ArrangerTrack, _w: usize) -> impl Render { + fn format_name (track: &ArrangerTrack, _w: usize) -> impl Content { let name = track.name().read().unwrap().clone(); Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) } /// input port - fn format_input (track: &ArrangerTrack) -> Usually> { + fn format_input (track: &ArrangerTrack) -> Usually> { Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) .transpose()?.unwrap_or("?".into()))) } /// output port - fn format_output (track: &ArrangerTrack) -> Usually> { + fn format_output (track: &ArrangerTrack) -> Usually> { Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name()) .transpose()?.unwrap_or("?".into()))) } /// beats elapsed - fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Render { + fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Content { let mut result = String::new(); if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { let length = phrase.read().unwrap().length; @@ -66,7 +70,7 @@ impl ArrangerVHead<'_> { } /// beats until switchover fn format_until_next (track: &ArrangerTrack, current: &Arc) - -> Option> + -> Option> { let timebase = ¤t.timebase; let mut result = String::new(); diff --git a/src/arranger/arranger_v/v_io.rs b/src/arranger/arranger_v/v_io.rs index c55d8c42..31c582e0 100644 --- a/src/arranger/arranger_v/v_io.rs +++ b/src/arranger/arranger_v/v_io.rs @@ -11,7 +11,7 @@ from!(<'a>|args: &'a ArrangerTui|ArrangerVIns<'a> = Self { tracks: &args.tracks, }); -render!(|self: ArrangerVIns<'a>|""); +render!(Tui: (self: ArrangerVIns<'a>) => ""); pub struct ArrangerVOuts<'a> { size: &'a Measure, @@ -23,4 +23,4 @@ from!(<'a>|args: &'a ArrangerTui|ArrangerVOuts<'a> = Self { tracks: &args.tracks, }); -render!(|self: ArrangerVOuts<'a>|""); +render!(Tui: (self: ArrangerVOuts<'a>) => ""); diff --git a/src/arranger/arranger_v/v_sep.rs b/src/arranger/arranger_v/v_sep.rs index ddf26692..80173d09 100644 --- a/src/arranger/arranger_v/v_sep.rs +++ b/src/arranger/arranger_v/v_sep.rs @@ -11,17 +11,15 @@ from!(|state:&ArrangerTui|ArrangerVColSep = Self { cols: ArrangerTrack::widths(&state.tracks), scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16, }); -render!((self: ArrangerVColSep) - |layout|Ok(Some([0, 0])), - |render|{ - let style = Some(Style::default().fg(self.fg)); - Ok(for x in self.cols.iter().map(|col|col.1) { - let x = self.scenes_w + render.area().x() + x as u16; - for y in render.area().y()..render.area().y2() { - render.blit(&"▎", x, y, style); - } - }) - }); +render!(Tui: |self: ArrangerVColSep, to| { + let style = Some(Style::default().fg(self.fg)); + Ok(for x in self.cols.iter().map(|col|col.1) { + let x = self.scenes_w + to.area().x() + x as u16; + for y in to.area().y()..to.area().y2() { + to.blit(&"▎", x, y, style); + } + }) +}); pub struct ArrangerVRowSep { fg: Color, @@ -31,16 +29,14 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self { fg: TuiTheme::separator_fg(false), rows: ArrangerScene::ppqs(&args.0.scenes, args.1), }); -render!((self: ArrangerVRowSep) - |layout|Ok(Some([0, 0])), - |render|Ok(for y in self.rows.iter().map(|row|row.1) { - let y = render.area().y() + (y / PPQ) as u16 + 1; - if y >= render.buffer.area.height { break } - for x in render.area().x()..render.area().x2().saturating_sub(2) { - if x < render.buffer.area.x && y < render.buffer.area.y { - let cell = render.buffer.get_mut(x, y); - cell.modifier = Modifier::UNDERLINED; - cell.underline_color = self.fg; - } +render!(Tui: |self: ArrangerVRowSep, to|for y in self.rows.iter().map(|row|row.1) { + 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 { + let cell = to.buffer.get_mut(x, y); + cell.modifier = Modifier::UNDERLINED; + cell.underline_color = self.fg; } - })); + } +}); diff --git a/src/border.rs b/src/border.rs index 522effa2..9433d243 100644 --- a/src/border.rs +++ b/src/border.rs @@ -1,39 +1,34 @@ use crate::*; -pub struct Bordered>(pub S, pub W); +pub struct Bordered>(pub S, pub W); -render!(|self: Bordered>|{ +render!(Tui: (self: Bordered>) => { Fill::xy(lay!([Border(self.0), Padding::xy(1, 1, &self.1)])) }); pub struct Border(pub S); -impl Render for Border { - fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { - Ok(Some([0, 0])) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - if area.w() > 0 && area.y() > 0 { - to.blit(&self.0.nw(), area.x(), area.y(), self.0.style()); - to.blit(&self.0.ne(), area.x() + area.w() - 1, area.y(), self.0.style()); - to.blit(&self.0.sw(), area.x(), area.y() + area.h() - 1, self.0.style()); - to.blit(&self.0.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.0.style()); - for x in area.x()+1..area.x()+area.w()-1 { - to.blit(&self.0.n(), x, area.y(), self.0.style()); - to.blit(&self.0.s(), x, area.y() + area.h() - 1, self.0.style()); - } - for y in area.y()+1..area.y()+area.h()-1 { - to.blit(&self.0.w(), area.x(), y, self.0.style()); - to.blit(&self.0.e(), area.x() + area.w() - 1, y, self.0.style()); - } +render!(Tui: |self: Border, to| { + let area = to.area(); + if area.w() > 0 && area.y() > 0 { + to.blit(&self.0.nw(), area.x(), area.y(), self.0.style()); + to.blit(&self.0.ne(), area.x() + area.w() - 1, area.y(), self.0.style()); + to.blit(&self.0.sw(), area.x(), area.y() + area.h() - 1, self.0.style()); + to.blit(&self.0.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.0.style()); + for x in area.x()+1..area.x()+area.w()-1 { + to.blit(&self.0.n(), x, area.y(), self.0.style()); + to.blit(&self.0.s(), x, area.y() + area.h() - 1, self.0.style()); + } + for y in area.y()+1..area.y()+area.h()-1 { + to.blit(&self.0.w(), area.x(), y, self.0.style()); + to.blit(&self.0.e(), area.x() + area.w() - 1, y, self.0.style()); } - Ok(()) } -} + Ok(()) +}); pub trait BorderStyle: Send + Sync + Copy { - fn wrap > (self, w: W) -> Bordered { + fn wrap > (self, w: W) -> Bordered { Bordered(self, w) } const NW: &'static str = ""; @@ -134,7 +129,7 @@ macro_rules! border { } #[derive(Copy, Clone)] pub struct $T(pub Style); - impl Render for $T { + impl Content for $T { fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) } fn render (&self, to: &mut TuiOutput) -> Usually<()> { self.draw(to) } } diff --git a/src/file.rs b/src/file.rs index 9911058d..491cf2c8 100644 --- a/src/file.rs +++ b/src/file.rs @@ -20,25 +20,23 @@ pub enum FileBrowserCommand { Chdir(PathBuf), Filter(String), } -render!(|self: FileBrowser|{ - Stack::down(|add|{ - let mut i = 0; - for (_, name) in self.dirs.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; +render!(Tui: (self: FileBrowser) => Stack::down(|add|{ + let mut i = 0; + for (_, name) in self.dirs.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; } - for (_, name) in self.files.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; + i += 1; + } + for (_, name) in self.files.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; } - add(&format!("{}/{i}", self.index))?; - Ok(()) - }) -}); + i += 1; + } + add(&format!("{}/{i}", self.index))?; + Ok(()) +})); impl FileBrowser { pub fn new (cwd: Option) -> Usually { let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; diff --git a/src/focus.rs b/src/focus.rs index 1e42348d..b08f482e 100644 --- a/src/focus.rs +++ b/src/focus.rs @@ -243,7 +243,7 @@ impl FocusOrder for T { } pub trait FocusWrap { - fn wrap > (self, focus: T, content: &'_ W) -> impl Render + '_; + fn wrap > (self, focus: T, content: &'_ W) -> impl Content + '_; } pub fn to_focus_command (input: &TuiInput) -> Option> { diff --git a/src/groovebox.rs b/src/groovebox.rs index 259de27b..55b8d320 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -111,7 +111,7 @@ audio!(|self: Groovebox, client, scope|{ self.perf.update(t0, scope); Control::Continue }); -render!(|self:Groovebox|{ +render!(Tui: (self: Groovebox) => { let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.pool.visible { phrase_w } else { 0 }; @@ -120,19 +120,21 @@ render!(|self:Groovebox|{ let color = self.player.play_phrase().as_ref() .and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)) .clone(); - let transport = Fixed::y(3, row!([ + let transport = Fixed::y(3, row!( PlayPause(self.clock().is_rolling()), TransportView::new(self, color, true), - ])); - let selector = Push::x(sampler_w, Fixed::y(1, row!(![ + )); + let selector = Push::x(sampler_w, Fixed::y(1, row!( PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), - ]))); - let pool = move|x|Split::w(false, pool_w, Pull::y(1, Fill::y(Align::e(PoolView(&self.pool)))), x); - let sampler = move|x|Split::e(false, sampler_w, Fill::xy(col!([ + ))); + let pool = move|x|Split::w(false, pool_w, + Pull::y(1, Fill::y(Align::e(PoolView(&self.pool)))), + x); + let sampler = move|x|Split::e(false, sampler_w, Fill::xy(col!( Meters(self.sampler.input_meter.as_ref()), GrooveboxSamples(self) - ])), x); + )), x); let status = EditStatus(&self.sampler, &self.editor, note_pt, pool(sampler(&self.editor))); Fill::xy(lay!([ &self.size, @@ -145,9 +147,9 @@ render!(|self:Groovebox|{ ])) }); -struct EditStatus<'a, T: Render>(&'a Sampler, &'a MidiEditor, usize, T); -impl<'a, T: Render> Content for EditStatus<'a, T> { - fn content (&self) -> Option> { +struct EditStatus<'a, T: Content>(&'a Sampler, &'a MidiEditor, usize, T); +impl<'a, T: Content> Content for EditStatus<'a, T> { + fn content (&self) -> Option> { Some(Split::n(false, 9, col!([ row!(|add|{ if let Some(sample) = &self.0.mapped[self.2] { @@ -169,7 +171,7 @@ impl<'a, T: Render> Content for EditStatus<'a, T> { ]), &self.3)) } } -impl<'a, T: Render> Render for EditStatus<'a, T> { +impl<'a, T: Content> Content for EditStatus<'a, T> { fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { self.content().unwrap().min_size(to) } @@ -179,11 +181,11 @@ impl<'a, T: Render> Render for EditStatus<'a, T> { } struct GrooveboxSamples<'a>(&'a Groovebox); -render!(|self: GrooveboxSamples<'a>|{ +render!(Tui: (self: GrooveboxSamples<'a>) => { let note_lo = self.0.editor.note_lo().load(Relaxed); let note_pt = self.0.editor.note_point(); let note_hi = self.0.editor.note_hi(); - Fill::xy(col!(note in (note_lo..=note_hi).rev() => { + Fill::xy(col_iter!((note_lo..=note_hi).rev() => |note| { let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset }; let mut fg = TuiTheme::g(160); if let Some((index, _)) = self.0.sampler.recording { diff --git a/src/lib.rs b/src/lib.rs index 05cc3b29..db8b4397 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,9 @@ pub(crate) use ::tek_layout::{ tek_engine::{ Usually, Perhaps, Engine, Size, Area, - Output, Content, Render, render, - Input, Handle, handle, kexp, key_pat, key_event_pat, key_event_expr, + Output, Content, render, + Input, Handle, handle, + kexp, key_pat, key_event_pat, key_event_expr, tui::{ Tui, TuiInput, TuiOutput, crossterm::{ diff --git a/src/meter.rs b/src/meter.rs index b52b8429..536f38a9 100644 --- a/src/meter.rs +++ b/src/meter.rs @@ -2,7 +2,7 @@ use crate::*; pub struct Meters<'a>(pub &'a[f32]); -render!(|self: Meters<'a>|col!([ +render!(Tui: (self: Meters<'a>) => col!([ &format!("L/{:>+9.3}", self.0[0]), &format!("R/{:>+9.3}", self.0[1]), ])); diff --git a/src/midi/midi_editor.rs b/src/midi/midi_editor.rs index 50da17c5..8f4da8fa 100644 --- a/src/midi/midi_editor.rs +++ b/src/midi/midi_editor.rs @@ -96,7 +96,7 @@ impl Command for PhraseCommand { /// Contains state for viewing and editing a phrase pub struct MidiEditor { - /// Renders the phrase + /// Contents the phrase pub mode: Box, pub size: Measure } @@ -111,7 +111,7 @@ impl Default for MidiEditor { has_size!(|self: MidiEditor|&self.size); -render!(|self: MidiEditor|{ +render!(Tui: (self: MidiEditor) => { self.autoscroll(); self.autozoom(); &self.mode @@ -119,7 +119,7 @@ render!(|self: MidiEditor|{ //render!(|self: MidiEditor|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks -pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + Debug + Send + Sync { +pub trait PhraseViewMode: Content + HasSize + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize); fn redraw (&mut self); fn phrase (&self) -> &Option>>; @@ -131,18 +131,10 @@ pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + D } impl Content for Box { - fn content (&self) -> Option> { + fn content (&self) -> Option> { Some(&(*self)) } } -impl Render for Box { - fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - self.content().unwrap().min_size(to) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - self.content().unwrap().render(to) - } -} impl MidiView for MidiEditor {} diff --git a/src/piano_h.rs b/src/piano_h.rs index b63d544e..cd9993fa 100644 --- a/src/piano_h.rs +++ b/src/piano_h.rs @@ -6,7 +6,6 @@ use self::piano_h_cursor::*; mod piano_h_keys; pub(crate) use self::piano_h_keys::*; -pub use self::piano_h_keys::render_keys_v; mod piano_h_notes; use self::piano_h_notes::*; @@ -54,38 +53,30 @@ impl PianoHorizontal { } } -render!(|self: PianoHorizontal|{ - +render!(Tui: (self: PianoHorizontal) => { let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) { (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) } else { (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) }; - - let field = move|x, y|row!([ + let field = move|x, y|row!( Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), Tui::fg_bg(color.lighter.rgb, color.dark.rgb, &y), - ]); - + ); let keys_width = 5; let keys = move||PianoHorizontalKeys(self); - let timeline = move||PianoHorizontalTimeline(self); - let notes = move||PianoHorizontalNotes(self); - let cursor = move||PianoHorizontalCursor(self); - let border = Fill::xy(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); let with_border = |x|lay!([border, Padding::xy(0, 0, &x)]); - - with_border(lay!([ - Push::x(0, row!(![ + with_border(lay!( + Push::x(0, row!( //" ", field("Edit:", name.to_string()), " ", field("Length:", length.to_string()), " ", field("Loop:", looped.to_string()) - ])), + )), Padding::xy(0, 1, Fill::xy(Bsp::s( Fixed::y(1, Bsp::e( Fixed::x(self.keys_width, ""), @@ -95,14 +86,14 @@ render!(|self: PianoHorizontal|{ Fixed::x(self.keys_width, keys()), Fill::xy(lay!([ &self.size, - Fill::xy(lay!([ + Fill::xy(lay!( Fill::xy(notes()), Fill::xy(cursor()), - ])) + )) ])), ), ))) - ])) + )) }); impl PianoHorizontal { diff --git a/src/piano_h/piano_h_cursor.rs b/src/piano_h/piano_h_cursor.rs index 6674f2d0..7e7a4cbf 100644 --- a/src/piano_h/piano_h_cursor.rs +++ b/src/piano_h/piano_h_cursor.rs @@ -2,34 +2,32 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal); -render!((self: PianoHorizontalCursor<'a>) - |layout|Ok(Some([0, 0])), - |render|Ok({ - let style = Some(Style::default().fg(self.0.color.lightest.rgb)); - let note_hi = self.0.note_hi(); - let note_len = self.0.note_len(); - let note_lo = self.0.note_lo().get(); - let note_point = self.0.note_point(); - let time_point = self.0.time_point(); - let time_start = self.0.time_start().get(); - let time_zoom = self.0.time_zoom().get(); - let [x0, y0, w, _] = render.area().xywh(); - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - if note == note_point { - for x in 0..w { - let screen_x = x0 + x; - let time_1 = time_start + x as usize * time_zoom; - let time_2 = time_1 + time_zoom; - if time_1 <= time_point && time_point < time_2 { - render.blit(&"█", screen_x, screen_y, style); - let tail = note_len as u16 / time_zoom as u16; - for x_tail in (screen_x + 1)..(screen_x + tail) { - render.blit(&"▂", x_tail, screen_y, style); - } - break +render!(Tui: |self: PianoHorizontalCursor<'a>, render|{ + let style = Some(Style::default().fg(self.0.color.lightest.rgb)); + let note_hi = self.0.note_hi(); + let note_len = self.0.note_len(); + let note_lo = self.0.note_lo().get(); + let note_point = self.0.note_point(); + let time_point = self.0.time_point(); + let time_start = self.0.time_start().get(); + let time_zoom = self.0.time_zoom().get(); + let [x0, y0, w, _] = render.area().xywh(); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + if note == note_point { + for x in 0..w { + let screen_x = x0 + x; + let time_1 = time_start + x as usize * time_zoom; + let time_2 = time_1 + time_zoom; + if time_1 <= time_point && time_point < time_2 { + render.blit(&"█", screen_x, screen_y, style); + let tail = note_len as u16 / time_zoom as u16; + for x_tail in (screen_x + 1)..(screen_x + tail) { + render.blit(&"▂", x_tail, screen_y, style); } + break } - break } + break } - })); + } +}); diff --git a/src/piano_h/piano_h_keys.rs b/src/piano_h/piano_h_keys.rs index 386c5cd8..be02b317 100644 --- a/src/piano_h/piano_h_keys.rs +++ b/src/piano_h/piano_h_keys.rs @@ -3,9 +3,28 @@ use super::note_y_iter; pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); -render!((self: PianoHorizontalKeys<'a>) - |layout|Ok(Some([0, 0])), - |render|Ok(render_keys_v(render, self))); +render!(Tui: |self: PianoHorizontalKeys<'a>, to|{ + let state = self.0; + let color = state.color(); + let note_lo = state.note_lo().get(); + let note_hi = state.note_hi(); + let note_point = state.note_point(); + let [x, y0, w, h] = to.area().xywh(); + let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); + let off_style = Some(Style::default().fg(TuiTheme::g(160))); + let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold()); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + to.blit(&to_key(note), x, screen_y, key_style); + if note > 127 { + continue + } + if note == note_point { + to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style) + } else { + to.blit(&to_note_name(note), x, screen_y, off_style) + }; + } +}); has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base); @@ -21,28 +40,6 @@ impl<'a> NotePoint for PianoHorizontalKeys<'a> { fn set_note_point (&self, x: usize) { self.0.set_note_point(x) } } -pub fn render_keys_v (to: &mut TuiOutput, state: &T) { - let color = state.color(); - let note_lo = state.note_lo().get(); - let note_hi = state.note_hi(); - let note_point = state.note_point(); - let [x, y0, w, h] = to.area().xywh(); - let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); - let off_style = Some(Style::default().fg(TuiTheme::g(160))); - let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold()); - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - to.blit(&to_key(note), x, screen_y, key_style); - if note > 127 { - continue - } - if note == note_point { - to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style) - } else { - to.blit(&to_note_name(note), x, screen_y, off_style) - }; - } -} - fn to_key (note: usize) -> &'static str { match note % 12 { 11 => "████▌", diff --git a/src/piano_h/piano_h_notes.rs b/src/piano_h/piano_h_notes.rs index e2ff9012..59041666 100644 --- a/src/piano_h/piano_h_notes.rs +++ b/src/piano_h/piano_h_notes.rs @@ -3,32 +3,30 @@ use super::note_y_iter; pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal); -render!((self: PianoHorizontalNotes<'a>) - |layout|Ok(Some([0, 0])), - |render|Ok({ - let time_start = self.0.time_start().get(); - let note_lo = self.0.note_lo().get(); - let note_hi = self.0.note_hi(); - let note_point = self.0.note_point(); - let source = &self.0.buffer; - let [x0, y0, w, h] = render.area().xywh(); - if h as usize != self.0.note_axis().get() { - panic!("area height mismatch"); - } - for (area_x, screen_x) in (x0..x0+w).enumerate() { - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - let source_x = time_start + area_x; - let source_y = note_hi - area_y; - // TODO: enable loop rollover: - //let source_x = (time_start + area_x) % source.width.max(1); - //let source_y = (note_hi - area_y) % source.height.max(1); - let is_in_x = source_x < source.width; - let is_in_y = source_y < source.height; - if is_in_x && is_in_y { - if let Some(source_cell) = source.get(source_x, source_y) { - *render.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); - } +render!(Tui: |self: PianoHorizontalNotes<'a>, render|{ + let time_start = self.0.time_start().get(); + let note_lo = self.0.note_lo().get(); + let note_hi = self.0.note_hi(); + let note_point = self.0.note_point(); + let source = &self.0.buffer; + let [x0, y0, w, h] = render.area().xywh(); + if h as usize != self.0.note_axis().get() { + panic!("area height mismatch"); + } + for (area_x, screen_x) in (x0..x0+w).enumerate() { + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + let source_x = time_start + area_x; + let source_y = note_hi - area_y; + // TODO: enable loop rollover: + //let source_x = (time_start + area_x) % source.width.max(1); + //let source_y = (note_hi - area_y) % source.height.max(1); + let is_in_x = source_x < source.width; + let is_in_y = source_y < source.height; + if is_in_x && is_in_y { + if let Some(source_cell) = source.get(source_x, source_y) { + *render.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); } } } - })); + } +}); diff --git a/src/piano_h/piano_h_time.rs b/src/piano_h/piano_h_time.rs index 7c9fa3ca..7def5e37 100644 --- a/src/piano_h/piano_h_time.rs +++ b/src/piano_h/piano_h_time.rs @@ -1,20 +1,17 @@ use crate::*; pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); -render!((self: PianoHorizontalTimeline<'a>) - |layout|Ok(Some([0, 0])), - |render|{ - let [x, y, w, h] = render.area(); - let style = Some(Style::default().dim()); - let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); - for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { - let t = area_x as usize * self.0.time_zoom().get(); - if t < length { - render.blit(&"|", screen_x, y, style); - } +render!(Tui: |self: PianoHorizontalTimeline<'a>, render|{ + let [x, y, w, h] = render.area(); + let style = Some(Style::default().dim()); + let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); + for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { + let t = area_x as usize * self.0.time_zoom().get(); + if t < length { + render.blit(&"|", screen_x, y, style); } - Ok(()) - }); + } +}); //Tui::fg_bg( //self.0.color.lightest.rgb, diff --git a/src/pool.rs b/src/pool.rs index 1d761364..9c6b6186 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -200,7 +200,7 @@ impl PoolModel { } pub struct PoolView<'a>(pub(crate) &'a PoolModel); // TODO: Display phrases always in order of appearance -render!(|self: PoolView<'a>|{ +render!(Tui: (self: PoolView<'a>) => { let PoolModel { phrases, mode, .. } = self.0; let bg = TuiTheme::g(32); let title_color = TuiTheme::ti1(); diff --git a/src/pool/phrase_length.rs b/src/pool/phrase_length.rs index 19f532d4..076c9d62 100644 --- a/src/pool/phrase_length.rs +++ b/src/pool/phrase_length.rs @@ -69,20 +69,20 @@ impl PhraseLengthFocus { } } -render!(|self: PhraseLength|{ +render!(Tui: (self: PhraseLength) => { let bars = ||self.bars_string(); let beats = ||self.beats_string(); let ticks = ||self.ticks_string(); - row!(move|add|match self.focus { + match self.focus { None => - add(&row!([" ", bars(), ".", beats(), ".", ticks()])), + row!(" ", bars(), ".", beats(), ".", ticks()), Some(PhraseLengthFocus::Bar) => - add(&row!(["[", bars(), "]", beats(), ".", ticks()])), + row!("[", bars(), "]", beats(), ".", ticks()), Some(PhraseLengthFocus::Beat) => - add(&row!([" ", bars(), "[", beats(), "]", ticks()])), + row!(" ", bars(), "[", beats(), "]", ticks()), Some(PhraseLengthFocus::Tick) => - add(&row!([" ", bars(), ".", beats(), "[", ticks()])), - }) + row!(" ", bars(), ".", beats(), "[", ticks()), + } }); #[derive(Copy, Clone, Debug, PartialEq)] diff --git a/src/pool/phrase_selector.rs b/src/pool/phrase_selector.rs index 19202ea1..3620186e 100644 --- a/src/pool/phrase_selector.rs +++ b/src/pool/phrase_selector.rs @@ -8,7 +8,7 @@ pub struct PhraseSelector { } // TODO: Display phrases always in order of appearance -render!(|self: PhraseSelector|Fixed::xy(24, 1, row!([ +render!(Tui: (self: PhraseSelector) => Fixed::xy(24, 1, row!([ Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)), Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([ format!("{:8}", &self.name[0..8.min(self.name.len())]), diff --git a/src/sampler/sample_import.rs b/src/sampler/sample_import.rs index 9d3fb60c..167daf80 100644 --- a/src/sampler/sample_import.rs +++ b/src/sampler/sample_import.rs @@ -170,7 +170,7 @@ fn draw_sample ( Ok(label1.len() + label2.len() + 4) } -impl Render for AddSampleModal { +impl Content for AddSampleModal { fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { todo!() //Align::Center(()).layout(to) diff --git a/src/sampler/sample_viewer.rs b/src/sampler/sample_viewer.rs index ee4222f5..8fb53ab6 100644 --- a/src/sampler/sample_viewer.rs +++ b/src/sampler/sample_viewer.rs @@ -5,55 +5,53 @@ use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points, Line}}}; const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)]; pub struct SampleViewer(pub Option>>); -render!((self: SampleViewer) - |layout|Ok(Some([0, 0])), - |render|{ +render!(Tui: |self: SampleViewer, to|{ - let [x, y, width, height] = render.area(); + let [x, y, width, height] = to.area(); - let area = Rect { x, y, width, height }; - let min_db = -40.0; + let area = Rect { x, y, width, height }; + let min_db = -40.0; - let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec) = - if let Some(sample) = &self.0 { - let sample = sample.read().unwrap(); - let start = sample.start as f64; - let end = sample.end as f64; - let length = end - start; - let step = length / width as f64; - let mut t = start; - let mut lines = vec![]; - while t < end { - let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)]; - let total: f32 = chunk.iter().map(|x|x.abs()).sum(); - let count = chunk.len() as f32; - let meter = 10. * (total / count).log10(); - let x = t as f64; - let y = meter as f64; - lines.push(Line::new(x, min_db, x, y, Color::Green)); - t += step / 2.; - } - ( - [sample.start as f64, sample.end as f64], - [min_db, 0.], - lines - ) - } else { - ( - [0.0, width as f64], - [0.0, height as f64], - vec![ - Line::new(0.0, 0.0, width as f64, height as f64, Color::Red), - Line::new(width as f64, 0.0, 0.0, height as f64, Color::Red), - ] - ) - }; + let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec) = + if let Some(sample) = &self.0 { + let sample = sample.read().unwrap(); + let start = sample.start as f64; + let end = sample.end as f64; + let length = end - start; + let step = length / width as f64; + let mut t = start; + let mut lines = vec![]; + while t < end { + let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)]; + let total: f32 = chunk.iter().map(|x|x.abs()).sum(); + let count = chunk.len() as f32; + let meter = 10. * (total / count).log10(); + let x = t as f64; + let y = meter as f64; + lines.push(Line::new(x, min_db, x, y, Color::Green)); + t += step / 2.; + } + ( + [sample.start as f64, sample.end as f64], + [min_db, 0.], + lines + ) + } else { + ( + [0.0, width as f64], + [0.0, height as f64], + vec![ + Line::new(0.0, 0.0, width as f64, height as f64, Color::Red), + Line::new(width as f64, 0.0, 0.0, height as f64, Color::Red), + ] + ) + }; - Canvas::default() - .x_bounds(x_bounds) - .y_bounds(y_bounds) - .paint(|ctx| { for line in lines.iter() { ctx.draw(line) } }) - .render(area, &mut render.buffer); + Canvas::default() + .x_bounds(x_bounds) + .y_bounds(y_bounds) + .paint(|ctx| { for line in lines.iter() { ctx.draw(line) } }) + .render(area, &mut to.buffer); - Ok(()) - }); + Ok(()) +}); diff --git a/src/sampler/sampler_tui.rs b/src/sampler/sampler_tui.rs index 750a82d7..ff2d5ed8 100644 --- a/src/sampler/sampler_tui.rs +++ b/src/sampler/sampler_tui.rs @@ -44,7 +44,7 @@ impl SamplerTui { //} //}); -render!(|self: SamplerTui|{ +render!(Tui: (self: SamplerTui) => { let keys_width = 5; let keys = move||"";//SamplerKeys(self); let fg = self.color.base.rgb; @@ -72,21 +72,19 @@ struct SamplesTui { note_pt: usize, height: usize, } -render!((self: SamplesTui) - |area|Ok(Some([15, 1])), - |render|Ok({ - let x = render.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); - let style_full = Style::default().fg(self.color.lighter.rgb); - for y in 0..self.height { - 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) - } - })); +render!(Tui: |self: SamplesTui, render|{ + let x = render.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); + let style_full = Style::default().fg(self.color.lighter.rgb); + for y in 0..self.height { + 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) + } +}); impl NoteRange for SamplerTui { fn note_lo (&self) -> &AtomicUsize { &self.note_lo } diff --git a/src/sequencer.rs b/src/sequencer.rs index ac0ee3d9..a2cfa2a6 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -40,7 +40,7 @@ from_jack!(|jack|SequencerTui { clock, } }); -render!(|self: SequencerTui|{ +render!(Tui: (self: SequencerTui) => { let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.phrases.visible { phrase_w } else { 0 }; diff --git a/src/status.rs b/src/status.rs index bafbe953..10af8984 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,4 +1,180 @@ -mod status_arranger; pub(crate) use self::status_arranger::*; -mod status_edit; pub(crate) use self::status_edit::*; -mod status_sequencer; pub(crate) use self::status_sequencer::*; -mod status_groovebox; pub(crate) use self::status_groovebox::*; +use crate::*; + +pub struct MidiEditStatus<'a>(pub &'a MidiEditor); +render!(Tui: (self:MidiEditStatus<'a>) => { + + let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { + (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) + } else { + (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) + }; + + let field = move|x, y|row!([ + Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), + Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), + Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y), + ]); + + let bg = color.darkest.rgb; + let fg = color.lightest.rgb; + Tui::bg(bg, Fill::x(Tui::fg(fg, row!([ + field(" Time", format!("{}/{}-{} ({}*{}) {}", + self.0.time_point(), self.0.time_start().get(), self.0.time_end(), + self.0.time_axis().get(), self.0.time_zoom().get(), + if self.0.time_lock().get() { "[lock]" } else { " " })), + " ", + field(" Note", format!("{} ({}) {} | {}-{} ({})", + self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), + to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), + self.0.note_axis().get())) + ])))) +}); + +/// Status bar for sequencer app +#[derive(Clone)] +pub struct SequencerStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) playing: bool, +} +from!(|state:&SequencerTui|SequencerStatus = { + 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!(Tui: (self: SequencerStatus) => Fixed::y(2, lay!([ + Self::help(), + Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl SequencerStatus { + 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]) + } +} + +/// 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!(Tui: (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) + } +} + +/// Status bar for arranger app +#[derive(Clone)] +pub struct ArrangerStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) playing: bool, +} +from!(|state:&ArrangerTui|ArrangerStatus = { + 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!(Tui: (self: ArrangerStatus) => Fixed::y(2, lay!([ + Self::help(), + Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl ArrangerStatus { + 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"), + single(" Ctrl", " scroll"), + single(" ▲▼▶◀", " cell"), + double(("p", "put"), ("g", "get")), + double(("q", "enqueue"), ("e", "edit")), + single(" wsad", " note"), + double(("a", "append"), ("s", "set"),), + double((",.", "length"), ("<>", "triplet"),), + double(("[]", "phrase"), ("{}", "order"),), + ])) + } + fn stats (&self) -> impl Content + use<'_> { + row!([&self.cpu, &self.size]) + } +} diff --git a/src/status/status_arranger.rs b/src/status/status_arranger.rs deleted file mode 100644 index 034b6888..00000000 --- a/src/status/status_arranger.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::*; - -/// Status bar for arranger app -#[derive(Clone)] -pub struct ArrangerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) playing: bool, -} -from!(|state:&ArrangerTui|ArrangerStatus = { - 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!(|self: ArrangerStatus|Fixed::y(2, lay!([ - Self::help(), - Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -]))); -impl ArrangerStatus { - fn help () -> impl Render { - 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"), - single(" Ctrl", " scroll"), - single(" ▲▼▶◀", " cell"), - double(("p", "put"), ("g", "get")), - double(("q", "enqueue"), ("e", "edit")), - single(" wsad", " note"), - double(("a", "append"), ("s", "set"),), - double((",.", "length"), ("<>", "triplet"),), - double(("[]", "phrase"), ("{}", "order"),), - ])) - } - fn stats (&self) -> impl Render + use<'_> { - row!([&self.cpu, &self.size]) - } -} diff --git a/src/status/status_edit.rs b/src/status/status_edit.rs deleted file mode 100644 index f98f7a04..00000000 --- a/src/status/status_edit.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::*; - -pub struct MidiEditStatus<'a>(pub &'a MidiEditor); -render!(|self:MidiEditStatus<'a>|{ - - let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { - (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) - } else { - (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) - }; - - let field = move|x, y|row!([ - Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), - Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), - Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y), - ]); - - let bg = color.darkest.rgb; - let fg = color.lightest.rgb; - Tui::bg(bg, Fill::x(Tui::fg(fg, row!([ - field(" Time", format!("{}/{}-{} ({}*{}) {}", - self.0.time_point(), self.0.time_start().get(), self.0.time_end(), - self.0.time_axis().get(), self.0.time_zoom().get(), - if self.0.time_lock().get() { "[lock]" } else { " " })), - " ", - field(" Note", format!("{} ({}) {} | {}-{} ({})", - self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), - to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), - self.0.note_axis().get())) - ])))) -}); diff --git a/src/status/status_groovebox.rs b/src/status/status_groovebox.rs deleted file mode 100644 index c6ffd486..00000000 --- a/src/status/status_groovebox.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::*; - -/// 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!(|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 Render { - 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 Render + use<'_> { - row!([&self.cpu, &self.size]) - } -} diff --git a/src/status/status_sequencer.rs b/src/status/status_sequencer.rs deleted file mode 100644 index 07334a59..00000000 --- a/src/status/status_sequencer.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::*; - -/// Status bar for sequencer app -#[derive(Clone)] -pub struct SequencerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) playing: bool, -} -from!(|state:&SequencerTui|SequencerStatus = { - 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!(|self: SequencerStatus|Fixed::y(2, lay!([ - Self::help(), - Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -]))); -impl SequencerStatus { - fn help () -> impl Render { - 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 Render + use<'_> { - row!([&self.cpu, &self.size]) - } -} diff --git a/src/style.rs b/src/style.rs index 2fa104b4..ac5cf80d 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,30 +1,30 @@ use crate::*; pub trait TuiStyle { - fn fg > (color: Color, w: W) -> Foreground { + fn fg > (color: Color, w: W) -> Foreground { Foreground(color, w) } - fn bg > (color: Color, w: W) -> Background { + fn bg > (color: Color, w: W) -> Background { Background(color, w) } - fn fg_bg > (fg: Color, bg: Color, w: W) -> Background> { + fn fg_bg > (fg: Color, bg: Color, w: W) -> Background> { Background(bg, Foreground(fg, w)) } - fn bold > (on: bool, w: W) -> Bold { + fn bold > (on: bool, w: W) -> Bold { Bold(on, w) } - fn border , S: BorderStyle> (style: S, w: W) -> Bordered { + fn border , S: BorderStyle> (style: S, w: W) -> Bordered { Bordered(style, w) } } impl TuiStyle for Tui {} -pub struct Bold>(pub bool, W); +pub struct Bold>(pub bool, W); -impl> Render for Bold { - fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - self.1.min_size(to) +impl> Content for Bold { + fn content (&self) -> Option> { + Some(&self.1) } fn render (&self, to: &mut TuiOutput) -> Usually<()> { to.fill_bold(to.area(), self.0); @@ -32,11 +32,11 @@ impl> Render for Bold { } } -pub struct Foreground>(pub Color, W); +pub struct Foreground>(pub Color, W); -impl> Render for Foreground { - fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - self.1.min_size(to) +impl> Content for Foreground { + fn content (&self) -> Option> { + Some(&self.1) } fn render (&self, to: &mut TuiOutput) -> Usually<()> { to.fill_fg(to.area(), self.0); @@ -44,11 +44,11 @@ impl> Render for Foreground { } } -pub struct Background>(pub Color, W); +pub struct Background>(pub Color, W); -impl> Render for Background { - fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - self.1.min_size(to) +impl> Content for Background { + fn content (&self) -> Option> { + Some(&self.1) } fn render (&self, to: &mut TuiOutput) -> Usually<()> { to.fill_bg(to.area(), self.0); @@ -56,11 +56,11 @@ impl> Render for Background { } } -pub struct Styled>(pub Option