From 1104093395fca972819664ed4708e58c9d547797 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 Aug 2024 20:09:23 +0300 Subject: [PATCH] arranger, transport: despaghettify --- crates/tek_core/src/lib.rs | 1 + crates/tek_core/src/render.rs | 62 +++---- crates/tek_core/src/render_collect.rs | 5 +- crates/tek_core/src/render_layered.rs | 29 +++ crates/tek_core/src/render_split.rs | 82 +++++++-- crates/tek_core/src/render_theme.rs | 73 ++++++++ crates/tek_sequencer/src/arranger_view.rs | 25 --- crates/tek_sequencer/src/arranger_view_h.rs | 121 +++++++----- crates/tek_sequencer/src/arranger_view_v.rs | 101 +++++----- crates/tek_sequencer/src/transport_render.rs | 184 +++++++++++-------- 10 files changed, 430 insertions(+), 253 deletions(-) create mode 100644 crates/tek_core/src/render_layered.rs diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 73ac37be..4c12a852 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -53,6 +53,7 @@ submod! { render_border render_collect render_fill + render_layered render_split render_theme time_base diff --git a/crates/tek_core/src/render.rs b/crates/tek_core/src/render.rs index d5b96619..a660c676 100644 --- a/crates/tek_core/src/render.rs +++ b/crates/tek_core/src/render.rs @@ -2,10 +2,9 @@ use crate::*; pub(crate) use ratatui::prelude::CrosstermBackend; -pub(crate) use ratatui::style::{Stylize, Style, Color}; +pub(crate) use ratatui::style::Style; pub(crate) use ratatui::layout::Rect; pub(crate) use ratatui::buffer::{Buffer, Cell}; -use ratatui::widgets::WidgetRef; /// Main thread render loop pub fn render_thread ( @@ -37,7 +36,7 @@ pub fn render_thread ( } /// Trait for things that render to the display. -pub trait Render { +pub trait Render: Send + Sync { // Render something to an area of the buffer. // Returns area used by component. // This is insufficient but for the most basic dynamic layout algorithms. @@ -76,23 +75,23 @@ impl Render for () { } } -//impl Render for &T { - //fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - //(*self).render(buf, area) - //} - //fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { - //Collected::Ref(self) - //} -//} +impl Render for &T { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + (*self).render(buf, area) + } + fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { + Collected::Ref(self) + } +} -//impl Render for &mut T { - //fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - //(**self).render(buf, area) - //} - //fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { - //Collected::Ref(self) - //} -//} +impl Render for &mut T { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + (**self).render(buf, area) + } + fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { + Collected::Ref(self) + } +} impl Render for Option { fn render (&self, b: &mut Buffer, a: Rect) -> Usually { @@ -112,18 +111,18 @@ impl<'a> Render for Box { } } -impl<'a, T: Fn(&mut Buffer, Rect) -> Usually + Send + Sync + 'a> Render for T { - fn render (&self, b: &mut Buffer, a: Rect) -> Usually { - (*self)(b, a) - } -} - -//impl<'a> Render for Box Usually + Send + Sync + 'a> { +//impl<'a, T: Fn(&mut Buffer, Rect) -> Usually + Send + Sync + 'a> Render for T { //fn render (&self, b: &mut Buffer, a: Rect) -> Usually { //(*self)(b, a) //} //} +impl<'a> Render for Box Usually + Send + Sync + 'a> { + fn render (&self, b: &mut Buffer, a: Rect) -> Usually { + (*self)(b, a) + } +} + impl Render for Arc> { fn render (&self, b: &mut Buffer, a: Rect) -> Usually { self.lock().unwrap().render(b, a) @@ -203,17 +202,6 @@ impl BigBuffer { } } -pub struct Layered<'a, const N: usize>(pub [&'a (dyn Render + Sync); N]); - -impl<'a, const N: usize> Render for Layered<'a, N> { - fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - for layer in self.0.iter() { - layer.render(buf, area)?; - } - Ok(area) - } -} - pub struct If<'a>(pub bool, pub &'a (dyn Render + Sync)); impl<'a> Render for If<'a> { diff --git a/crates/tek_core/src/render_collect.rs b/crates/tek_core/src/render_collect.rs index 3b10230e..f1c2a00f 100644 --- a/crates/tek_core/src/render_collect.rs +++ b/crates/tek_core/src/render_collect.rs @@ -16,7 +16,7 @@ impl<'a> Render for Collected<'a> { } } -pub struct Collection<'a>(Vec>); +pub struct Collection<'a>(pub Vec>); impl<'a> Collection<'a> { pub fn new () -> Self { @@ -27,6 +27,9 @@ impl<'a> Collection<'a> { pub trait Collect<'a> { fn add_box (self, item: Box) -> Self; fn add_ref (self, item: &'a dyn Render) -> Self; + fn add (self, item: T) -> Self where Self: Sized { + self.add_box(Box::new(item)) + } } impl<'a> Collect<'a> for Collection<'a> { diff --git a/crates/tek_core/src/render_layered.rs b/crates/tek_core/src/render_layered.rs new file mode 100644 index 00000000..5a519557 --- /dev/null +++ b/crates/tek_core/src/render_layered.rs @@ -0,0 +1,29 @@ +use crate::*; + +pub struct Layered<'a>(Collection<'a>); + +impl<'a> Layered<'a> { + pub fn new () -> Self { + Self(Collection::new()) + } +} + +impl<'a> Render for Layered<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + for layer in self.0.0.iter() { + layer.render(buf, area)?; + } + Ok(area) + } +} + +impl<'a> Collect<'a> for Layered<'a> { + fn add_box (mut self, item: Box) -> Self { + self.0 = self.0.add_box(item); + self + } + fn add_ref (mut self, item: &'a dyn Render) -> Self { + self.0 = self.0.add_ref(item); + self + } +} diff --git a/crates/tek_core/src/render_split.rs b/crates/tek_core/src/render_split.rs index db7f63f7..f064c154 100644 --- a/crates/tek_core/src/render_split.rs +++ b/crates/tek_core/src/render_split.rs @@ -9,9 +9,9 @@ pub enum Direction { } impl Direction { - pub fn split <'a, const N: usize> (&self, items: [&'a (dyn Render + Sync);N]) -> Split<'a, N> { - Split(*self, items) - } + //pub fn split <'a, const N: usize> (&self, items: [&'a (dyn Render + Sync);N]) -> Split<'a, N> { + //Split(*self, items) + //} pub fn split_focus <'a> (&self, index: usize, items: Renderables<'a>, style: Style) -> SplitFocus<'a> { SplitFocus(*self, index, items, style) } @@ -23,26 +23,27 @@ impl Direction { } } -pub struct Split<'a, const N: usize>( - pub Direction, pub [&'a (dyn Render + Sync);N] -); +pub struct Split<'a>(Collection<'a>, Direction); -impl<'a, const N: usize> Split<'a, N> { - pub fn down (items: [&'a (dyn Render + Sync);N]) -> Self { - Self(Direction::Down, items) +impl<'a> Split<'a> { + pub fn new (direction: Direction) -> Self { + Self(Collection::new(), direction) } - pub fn right (items: [&'a (dyn Render + Sync);N]) -> Self { - Self(Direction::Right, items) + pub fn down () -> Self { + Self(Collection::new(), Direction::Down) + } + pub fn right () -> Self { + Self(Collection::new(), Direction::Right) } pub fn render_areas (&self, buf: &mut Buffer, area: Rect) -> Usually<(Rect, Vec)> { let Rect { mut x, mut y, mut width, mut height } = area; let mut areas = vec![]; - for item in self.1 { + for item in self.0.0.iter() { if width == 0 || height == 0 { break } let result = item.render(buf, Rect { x, y, width, height })?; - match self.0 { + match self.1 { Direction::Down => { y = y + result.height; height = height.saturating_sub(result.height); @@ -59,12 +60,65 @@ impl<'a, const N: usize> Split<'a, N> { } } -impl<'a, const N: usize> Render for Split<'a, N> { +impl<'a> Render for Split<'a> { fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { Ok(self.render_areas(buf, area)?.0) } } +impl<'a> Collect<'a> for Split<'a> { + fn add_box (mut self, item: Box) -> Self { + self.0 = self.0.add_box(item); + self + } + fn add_ref (mut self, item: &'a dyn Render) -> Self { + self.0 = self.0.add_ref(item); + self + } +} + +//pub struct Split<'a, const N: usize>( + //pub Direction, pub [&'a (dyn Render + Sync);N] +//); + +//impl<'a, const N: usize> Split<'a, N> { + //pub fn down (items: [&'a (dyn Render + Sync);N]) -> Self { + //Self(Direction::Down, items) + //} + //pub fn right (items: [&'a (dyn Render + Sync);N]) -> Self { + //Self(Direction::Right, items) + //} + //pub fn render_areas (&self, buf: &mut Buffer, area: Rect) -> Usually<(Rect, Vec)> { + //let Rect { mut x, mut y, mut width, mut height } = area; + //let mut areas = vec![]; + //for item in self.1 { + //if width == 0 || height == 0 { + //break + //} + //let result = item.render(buf, Rect { x, y, width, height })?; + //match self.0 { + //Direction::Down => { + //y = y + result.height; + //height = height.saturating_sub(result.height); + //}, + //Direction::Right => { + //x = x + result.width; + //width = width.saturating_sub(result.width); + //}, + //_ => unimplemented!() + //}; + //areas.push(area); + //} + //Ok((area, areas)) + //} +//} + +//impl<'a, const N: usize> Render for Split<'a, N> { + //fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + //Ok(self.render_areas(buf, area)?.0) + //} +//} + type Renderables<'a> = &'a [&'a (dyn Render + Send + Sync)]; pub struct SplitFocus<'a>(pub Direction, pub usize, pub Renderables<'a>, pub Style); diff --git a/crates/tek_core/src/render_theme.rs b/crates/tek_core/src/render_theme.rs index 6f66e03e..6e8184a9 100644 --- a/crates/tek_core/src/render_theme.rs +++ b/crates/tek_core/src/render_theme.rs @@ -1,4 +1,5 @@ use crate::*; +use ratatui::style::Modifier; pub trait Theme { const BG0: Color; @@ -69,3 +70,75 @@ impl Theme for Nord { const PLAYING: Color = Color::Rgb(60, 100, 50); const SEPARATOR: Color = Color::Rgb(0, 0, 0); } + +pub const GRAY: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::empty(), +}; + +pub const GRAY_NOT_DIM: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::DIM, +}; + +pub const DIM: Style = Style { + fg: None, + bg: None, + underline_color: None, + add_modifier: Modifier::DIM, + sub_modifier: Modifier::empty(), +}; + +pub const GRAY_DIM: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: Modifier::DIM, + sub_modifier: Modifier::empty(), +}; + +pub const WHITE_NOT_DIM_BOLD: Style = Style { + fg: Some(Color::White), + bg: None, + underline_color: None, + add_modifier: Modifier::BOLD, + sub_modifier: Modifier::DIM, +}; + +pub const GRAY_NOT_DIM_BOLD: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: Modifier::BOLD, + sub_modifier: Modifier::DIM, +}; + +pub const NOT_DIM: Style = Style { + fg: None, + bg: None, + underline_color: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::DIM, +}; + +pub const NOT_DIM_GREEN: Style = Style { + fg: Some(Color::Green), + bg: None, + underline_color: None, + add_modifier: Modifier::BOLD, + sub_modifier: Modifier::DIM, +}; + +pub const NOT_DIM_BOLD: Style = Style { + fg: None, + bg: None, + underline_color: None, + add_modifier: Modifier::BOLD, + sub_modifier: Modifier::DIM, +}; diff --git a/crates/tek_sequencer/src/arranger_view.rs b/crates/tek_sequencer/src/arranger_view.rs index dba7373c..3163e5b6 100644 --- a/crates/tek_sequencer/src/arranger_view.rs +++ b/crates/tek_sequencer/src/arranger_view.rs @@ -20,31 +20,6 @@ impl ArrangerViewMode { } } -struct Split<'a>(Collection<'a>, Direction); - -impl<'a> Split<'a> { - fn new (direction: Direction) -> Self { - Self(Collection::new(), direction) - } -} - -impl<'a> Render for Split<'a> { - fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - Ok(area) - } -} - -impl<'a> Collect<'a> for Split<'a> { - fn add_box (mut self, item: Box) -> Self { - self.0 = self.0.add_box(item); - self - } - fn add_ref (mut self, item: &'a dyn Render) -> Self { - self.0 = self.0.add_ref(item); - self - } -} - render!(Arranger |self, buf, area| { let arrangement = Box::new(|buf: &mut Buffer, area: Rect| { diff --git a/crates/tek_sequencer/src/arranger_view_h.rs b/crates/tek_sequencer/src/arranger_view_h.rs index 9acc2b80..8e6a5938 100644 --- a/crates/tek_sequencer/src/arranger_view_h.rs +++ b/crates/tek_sequencer/src/arranger_view_h.rs @@ -2,34 +2,36 @@ use crate::*; pub fn draw (state: &Arranger, buf: &mut Buffer, mut area: Rect) -> Usually { area.height = area.height.min((2 + state.tracks.len() * 2) as u16); - Layered([ - &FillBg(Nord::bg_lo(state.focused, state.entered)), - &Split::right([ - &track_name_column(state), - &track_mon_column(state), - &track_rec_column(state), - &track_ovr_column(state), - &track_del_column(state), - &track_gain_column(state), - &track_scenes_column(state), - ]), - ]).render(buf, area) + let tracks = state.tracks.as_slice(); + Layered::new() + .add(FillBg(Nord::bg_lo(state.focused, state.entered))) + .add(Split::right() + .add(TrackNameColumn(tracks, state.selected)) + .add(TrackMonitorColumn(tracks)) + .add(TrackRecordColumn(tracks)) + .add(TrackOverdubColumn(tracks)) + .add(TrackEraseColumn(tracks)) + .add(TrackGainColumn(tracks)) + .add(TrackScenesColumn(tracks, state.scenes.as_slice(), state.selected))) + .render(buf, area) } -fn track_name_column <'a> (state: &'a Arranger) -> impl Render + 'a { - let dim = Some(Style::default().dim()); - let yellow = Some(Style::default().yellow().bold().not_dim()); - let white = Some(Style::default().white().bold().not_dim()); - move |buf: &mut Buffer, mut area: Rect| { - area.width = 3 + 5.max(track_name_max_len(state.tracks.as_slice())) as u16; +struct TrackNameColumn<'a>(&'a [Sequencer], ArrangerFocus); + +impl<'a> Render for TrackNameColumn<'a> { + fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually { + let Self(tracks, selected) = self; + let yellow = Some(Style::default().yellow().bold().not_dim()); + let white = Some(Style::default().white().bold().not_dim()); + area.width = 3 + 5.max(track_name_max_len(tracks)) as u16; let offset = 0; // track scroll offset for y in 0..area.height { if y == 0 { - "Mixer".blit(buf, area.x + 1, area.y + y, dim)?; + "Mixer".blit(buf, area.x + 1, area.y + y, Some(DIM))?; } else if y % 2 == 0 { let index = (y as usize - 2) / 2 + offset; - if let Some(track) = state.tracks.get(index) { - let selected = state.selected.track() == Some(index); + if let Some(track) = tracks.get(index) { + let selected = selected.track() == Some(index); let style = if selected { yellow } else { white }; format!(" {index:>02} ").blit(buf, area.x, area.y + y, style)?; track.name.blit(buf, area.x + 4, area.y + y, style)?; @@ -40,17 +42,20 @@ fn track_name_column <'a> (state: &'a Arranger) -> impl Render + 'a { } } -fn track_mon_column <'a> (state: &'a Arranger) -> impl Render + 'a { - let on = Some(Style::default().not_dim().green().bold()); - let off = Some(Style::default().dim()); - move |buf: &mut Buffer, mut area: Rect| { +struct TrackMonitorColumn<'a>(&'a [Sequencer]); + +impl<'a> Render for TrackMonitorColumn<'a> { + fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually { + let Self(tracks) = self; + let on = Some(Style::default().not_dim().green().bold()); + let off = Some(DIM); area.x = area.x + 1; for y in 0..area.height { if y == 0 { //" MON ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 0 { let index = (y as usize - 2) / 2; - if let Some(track) = state.tracks.get(index) { + if let Some(track) = tracks.get(index) { let style = if track.monitoring { on } else { off }; " MON ".blit(buf, area.x, area.y + y, style)?; } else { @@ -64,17 +69,20 @@ fn track_mon_column <'a> (state: &'a Arranger) -> impl Render + 'a { } } -fn track_rec_column <'a> (state: &'a Arranger) -> impl Render + 'a { - let on = Some(Style::default().not_dim().red().bold()); - let off = Some(Style::default().dim()); - move |buf: &mut Buffer, mut area: Rect| { +struct TrackRecordColumn<'a>(&'a [Sequencer]); + +impl<'a> Render for TrackRecordColumn<'a> { + fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually { + let Self(tracks) = self; + let on = Some(Style::default().not_dim().red().bold()); + let off = Some(Style::default().dim()); area.x = area.x + 1; for y in 0..area.height { if y == 0 { //" REC ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 0 { let index = (y as usize - 2) / 2; - if let Some(track) = state.tracks.get(index) { + if let Some(track) = tracks.get(index) { let style = if track.recording { on } else { off }; " REC ".blit(buf, area.x, area.y + y, style)?; } else { @@ -88,17 +96,20 @@ fn track_rec_column <'a> (state: &'a Arranger) -> impl Render + 'a { } } -fn track_ovr_column <'a> (state: &'a Arranger) -> impl Render + 'a { - let on = Some(Style::default().not_dim().yellow().bold()); - let off = Some(Style::default().dim()); - move |buf: &mut Buffer, mut area: Rect| { +struct TrackOverdubColumn<'a>(&'a [Sequencer]); + +impl<'a> Render for TrackOverdubColumn<'a> { + fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually { + let Self(tracks) = self; + let on = Some(Style::default().not_dim().yellow().bold()); + let off = Some(Style::default().dim()); area.x = area.x + 1; for y in 0..area.height { if y == 0 { //" OVR ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 0 { let index = (y as usize - 2) / 2; - if let Some(track) = state.tracks.get(index) { + if let Some(track) = tracks.get(index) { " OVR ".blit(buf, area.x, area.y + y, if track.overdub { on } else { @@ -115,16 +126,19 @@ fn track_ovr_column <'a> (state: &'a Arranger) -> impl Render + 'a { } } -fn track_del_column <'a> (state: &'a Arranger) -> impl Render + 'a { - let off = Some(Style::default().dim()); - move |buf: &mut Buffer, mut area: Rect| { +struct TrackEraseColumn<'a>(&'a [Sequencer]); + +impl<'a> Render for TrackEraseColumn<'a> { + fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually { + let Self(tracks) = self; + let off = Some(Style::default().dim()); area.x = area.x + 1; for y in 0..area.height { if y == 0 { //" DEL ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 0 { let index = (y as usize - 2) / 2; - if let Some(_) = state.tracks.get(index) { + if let Some(_) = tracks.get(index) { " DEL ".blit(buf, area.x, area.y + y, off)?; } else { area.height = y; @@ -137,16 +151,19 @@ fn track_del_column <'a> (state: &'a Arranger) -> impl Render + 'a { } } -fn track_gain_column <'a> (state: &'a Arranger) -> impl Render + 'a { - let off = Some(Style::default().dim()); - move |buf: &mut Buffer, mut area: Rect| { +struct TrackGainColumn<'a>(&'a [Sequencer]); + +impl<'a> Render for TrackGainColumn<'a> { + fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually { + let Self(tracks) = self; + let off = Some(Style::default().dim()); area.x = area.x + 1; for y in 0..area.height { if y == 0 { //" GAIN ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 0 { let index = (y as usize - 2) / 2; - if let Some(_) = state.tracks.get(index) { + if let Some(_) = tracks.get(index) { " +0.0 ".blit(buf, area.x, area.y + y, off)?; } else { area.height = y; @@ -159,12 +176,15 @@ fn track_gain_column <'a> (state: &'a Arranger) -> impl Render + 'a { } } -fn track_scenes_column <'a> (state: &'a Arranger) -> impl Render + 'a { - |buf: &mut Buffer, area: Rect| { +struct TrackScenesColumn<'a>(&'a [Sequencer], &'a [Scene], ArrangerFocus); + +impl<'a> Render for TrackScenesColumn<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + let Self(tracks, scenes, selected) = self; let mut x2 = 0; let Rect { x, y, height, .. } = area; - for (scene_index, scene) in state.scenes.iter().enumerate() { - let active_scene = state.selected.scene() == Some(scene_index); + for (scene_index, scene) in scenes.iter().enumerate() { + let active_scene = selected.scene() == Some(scene_index); let sep = Some(if active_scene { Style::default().yellow().not_dim() } else { @@ -176,10 +196,10 @@ fn track_scenes_column <'a> (state: &'a Arranger) -> impl Render + 'a { let mut x3 = scene.name.len() as u16; scene.name.blit(buf, x + x2, y, sep)?; for (i, clip) in scene.clips.iter().enumerate() { - let active_track = state.selected.track() == Some(i); + let active_track = selected.track() == Some(i); if let Some(clip) = clip { let y2 = y + 2 + i as u16 * 2; - let label = match state.tracks[i].phrases.get(*clip) { + let label = match tracks[i].phrases.get(*clip) { Some(phrase) => &format!("{}", phrase.read().unwrap().name), None => "...." }; @@ -196,4 +216,3 @@ fn track_scenes_column <'a> (state: &'a Arranger) -> impl Render + 'a { Ok(Rect { x, y, height, width: x2 }) } } - diff --git a/crates/tek_sequencer/src/arranger_view_v.rs b/crates/tek_sequencer/src/arranger_view_v.rs index 06cdb121..52c7c9d2 100644 --- a/crates/tek_sequencer/src/arranger_view_v.rs +++ b/crates/tek_sequencer/src/arranger_view_v.rs @@ -30,20 +30,25 @@ pub fn draw ( ) -> Usually { area.height = 2 + (rows[rows.len() - 1].1 / 96) as u16; let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16; - Layered([ - &FillBg(Nord::bg_lo(state.focused, state.entered)), - &column_separators(offset, cols), - &cursor_focus(state, offset, cols, rows), - &Split::down([ - &tracks_header(state, cols, offset), - &scene_rows(state, cols, rows, offset), - ]), - &row_separators(rows), - ]).render(buf, area) + let Arranger { focus_sequencer, focused, entered, selected, .. } = *state; + let tracks = state.tracks.as_ref(); + let scenes = state.scenes.as_ref(); + Layered::new() + .add(FillBg(Nord::bg_lo(state.focused, state.entered))) + .add(ColumnSeparators(offset, cols)) + .add(CursorFocus(focus_sequencer, focused, entered, selected, offset, cols, rows)) + .add(Split::down() + .add(TracksHeader(offset, cols, tracks)) + .add(SceneRows(offset, cols, rows, tracks, scenes))) + .add(RowSeparators(rows)) + .render(buf, area) } -fn column_separators <'a> (offset: u16, cols: &'a [(usize, usize)]) -> impl Render + 'a { - move |buf: &mut Buffer, area: Rect|{ +struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]); + +impl<'a> Render for ColumnSeparators<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + let Self(offset, cols) = self; let style = Some(Style::default().fg(Nord::SEPARATOR)); for (_, x) in cols.iter() { let x = offset + area.x + *x as u16 - 1; @@ -55,8 +60,11 @@ fn column_separators <'a> (offset: u16, cols: &'a [(usize, usize)]) -> impl Rend } } -fn row_separators <'a> (rows: &'a [(usize, usize)]) -> impl Render + 'a { - move |buf: &mut Buffer, area: Rect| { +struct RowSeparators<'a>(&'a [(usize, usize)]); + +impl<'a> Render for RowSeparators<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + let Self(rows) = self; for (_, y) in rows.iter() { let y = area.y + (*y / 96) as u16 + 1; if y >= buf.area.height { @@ -72,16 +80,19 @@ fn row_separators <'a> (rows: &'a [(usize, usize)]) -> impl Render + 'a { } } -fn cursor_focus <'a> ( - state: &'a Arranger, offset: u16, cols: &'a [(usize, usize)], rows: &'a [(usize, usize)], -) -> impl Render + 'a { - move |buf: &mut Buffer, area: Rect| { - let area = match state.selected { - ArrangerFocus::Mix => if state.focused - && state.entered - && state.selected == ArrangerFocus::Mix +struct CursorFocus<'a>( + bool, bool, bool, ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)] +); + +impl<'a> Render for CursorFocus<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + let Self(focus_sequencer, focused, entered, selected, offset, cols, rows) = *self; + let area = match selected { + ArrangerFocus::Mix => if focused + && entered + && selected == ArrangerFocus::Mix { - fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered)); + fill_bg(buf, area, Nord::bg_hi(focused, entered)); area } else { area @@ -93,7 +104,7 @@ fn cursor_focus <'a> ( width: cols[t].0 as u16, height: area.height }; - fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered)); + fill_bg(buf, area, Nord::bg_hi(focused, entered)); area }, ArrangerFocus::Scene(s) => { @@ -103,7 +114,7 @@ fn cursor_focus <'a> ( width: area.width, height: (rows[s].0 / 96) as u16 }; - fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered)); + fill_bg(buf, area, Nord::bg_hi(focused, entered)); area }, ArrangerFocus::Clip(t, s) => { @@ -125,29 +136,28 @@ fn cursor_focus <'a> ( width: cols[t].0 as u16, height: (rows[s].0 / 96) as u16 }; - let lo = Nord::bg_hi(state.focused, state.entered); - let hi = Nord::bg_hier(state.focused, state.entered); + let lo = Nord::bg_hi(focused, entered); + let hi = Nord::bg_hier(focused, entered); fill_bg(buf, track_area, lo); fill_bg(buf, scene_area, lo); fill_bg(buf, area, hi); area }, }; - if !state.focus_sequencer { + if !focus_sequencer { Corners(Style::default().green().not_dim()).draw(buf, area)?; } Ok(area) } } -pub fn tracks_header <'a> ( - state: &'a Arranger, - track_cols: &'a [(usize, usize)], - offset: u16, -) -> impl Render + 'a { - move |buf: &mut Buffer, area: Rect| { +struct TracksHeader<'a>(u16, &'a[(usize, usize)], &'a [Sequencer]); + +impl<'a> Render for TracksHeader<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + let Self(offset, track_cols, tracks) = *self; let Rect { y, width, .. } = area; - for (track, (_, x)) in state.tracks.iter().zip(track_cols) { + for (track, (_, x)) in tracks.iter().zip(track_cols) { let x = *x as u16; if x > width { break @@ -158,13 +168,11 @@ pub fn tracks_header <'a> ( } } -pub fn scene_rows <'a> ( - state: &'a Arranger, - track_cols: &'a [(usize, usize)], - scene_rows: &'a [(usize, usize)], - offset: u16, -) -> impl Render + 'a { - move |buf: &mut Buffer, area: Rect| { +struct SceneRows<'a>(u16, &'a[(usize, usize)], &'a[(usize, usize)], &'a[Sequencer], &'a[Scene]); + +impl<'a> Render for SceneRows<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + let Self(offset, track_cols, scene_rows, tracks, scenes) = *self; let black = Some(Style::default().fg(Nord::SEPARATOR)); let Rect { mut y, height, .. } = area; for (_, x) in track_cols.iter() { @@ -175,12 +183,12 @@ pub fn scene_rows <'a> ( } } } - for (scene, (pulses, _)) in state.scenes.iter().zip(scene_rows) { + for (scene, (pulses, _)) in scenes.iter().zip(scene_rows) { if y > height { break } let h = 1.max((pulses / 96) as u16); - scene_row(state, buf, Rect { + scene_row(tracks, buf, Rect { x: area.x, y, width: area.width, @@ -192,8 +200,8 @@ pub fn scene_rows <'a> ( } } -fn scene_row ( - state: &Arranger, +fn scene_row <'a> ( + tracks: &'a[Sequencer], buf: &mut Buffer, area: Rect, scene: &Scene, @@ -201,7 +209,6 @@ fn scene_row ( offset: u16 ) -> Usually { let Rect { y, width, .. } = area; - let tracks = state.tracks.as_ref(); let playing = scene.is_playing(tracks); (if playing { "▶" } else { " " }).blit(buf, area.x, y, None)?; scene.name.blit(buf, area.x + 1, y, None)?; diff --git a/crates/tek_sequencer/src/transport_render.rs b/crates/tek_sequencer/src/transport_render.rs index f624f07b..b1812714 100644 --- a/crates/tek_sequencer/src/transport_render.rs +++ b/crates/tek_sequencer/src/transport_render.rs @@ -1,84 +1,112 @@ use crate::*; +const CORNERS: Corners = Corners(NOT_DIM_GREEN); + render!(TransportToolbar |self, buf, area| { - let mut area = area; - area.height = 2; - let gray = Style::default().gray(); - let not_dim = Style::default().not_dim(); - let not_dim_bold = not_dim.bold(); - let corners = Corners(Style::default().green().not_dim()); - let ppq = self.ppq(); - let bpm = self.bpm(); - let pulse = self.pulse(); - let usecs = self.usecs(); + let mut area = area; + area.height = 2; + let ppq = self.ppq(); + let bpm = self.bpm(); + let pulse = self.pulse(); + let usecs = self.usecs(); let Self { quant, sync, focused, entered, .. } = self; fill_bg(buf, area, Nord::bg_lo(*focused, *entered)); - Split::right([ - - // Play/Pause button - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - let style = Some(match self.playing { - Some(TransportState::Stopped) => gray.dim().bold(), - Some(TransportState::Starting) => gray.not_dim().bold(), - Some(TransportState::Rolling) => gray.not_dim().white().bold(), - _ => unreachable!(), - }); - let label = match self.playing { - Some(TransportState::Rolling) => "▶ PLAYING", - Some(TransportState::Starting) => "READY ...", - Some(TransportState::Stopped) => "⏹ STOPPED", - _ => unreachable!(), - }; - let mut result = label.blit(buf, x + 1, y, style)?; - result.width = result.width + 1; - Ok(result) - }, - - // Beats per minute - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - "BPM".blit(buf, x, y, Some(not_dim))?; - let width = format!("{}.{:03}", bpm, bpm * 1000 % 1000).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if self.focused && self.entered && self.selected == TransportFocus::BPM { - corners.draw(buf, Rect { x: area.x - 1, ..area })?; - } - Ok(area) - }, - - // Quantization - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - "QUANT".blit(buf, x, y, Some(not_dim))?; - let width = ppq_to_name(*quant as usize).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if self.focused && self.entered && self.selected == TransportFocus::Quant { - corners.draw(buf, Rect { x: area.x - 1, ..area })?; - } - Ok(area) - }, - - // Clip launch sync - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - "SYNC".blit(buf, x, y, Some(not_dim))?; - let width = ppq_to_name(*sync as usize).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if self.focused && self.entered && self.selected == TransportFocus::Sync { - corners.draw(buf, Rect { x: area.x - 1, ..area })?; - } - Ok(area) - }, - - // Clock - &|buf: &mut Buffer, Rect { x, y, width, .. }: Rect|{ - let (beats, pulses) = (pulse / ppq, pulse % ppq); - let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); - let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); - let (minutes, seconds) = (seconds / 60, seconds % 60); - let timer = format!("{bars}.{beats}.{pulses:02}"); - timer.blit(buf, x + width - timer.len() as u16 - 1, y + 0, Some(not_dim))?; - let timer = format!("{minutes}:{seconds:02}:{msecs:03}"); - timer.blit(buf, x + width - timer.len() as u16 - 1, y + 1, Some(not_dim))?; - Ok(area) - } - - ]).render(buf, area) + let active = self.focused && self.entered; + let playing = TransportPlayPauseButton(self.playing); + let bpm = TransportBPM(bpm, active && self.selected == TransportFocus::BPM); + let quant = TransportQuantize(*quant, active && self.selected == TransportFocus::Quant); + let sync = TransportSync(*sync, active && self.selected == TransportFocus::Sync); + let clock = TransportClock(pulse, ppq, usecs); + Split::right() + .add_ref(&playing) + .add_ref(&bpm) + .add_ref(&quant) + .add_ref(&sync) + .add_ref(&clock) + .render(buf, area) +}); + +#[derive(Copy, Clone)] +struct TransportPlayPauseButton(Option); + +render!(TransportPlayPauseButton |self, buf, area| { + let Rect { x, y, .. } = area; + let gray = Style::default().gray(); + let style = Some(match self.0 { + Some(TransportState::Stopped) => GRAY_DIM.bold(), + Some(TransportState::Starting) => GRAY_NOT_DIM_BOLD, + Some(TransportState::Rolling) => WHITE_NOT_DIM_BOLD, + _ => unreachable!(), + }); + let label = match self.0 { + Some(TransportState::Rolling) => "▶ PLAYING", + Some(TransportState::Starting) => "READY ...", + Some(TransportState::Stopped) => "⏹ STOPPED", + _ => unreachable!(), + }; + let mut result = label.blit(buf, x + 1, y, style)?; + result.width = result.width + 1; + Ok(result) +}); + +#[derive(Copy, Clone)] +struct TransportBPM(usize, bool); + +render!(TransportBPM |self, buf, area| { + let Rect { x, y, .. } = area; + let Self(bpm, highlight) = self; + "BPM".blit(buf, x, y, Some(NOT_DIM))?; + let width = format!("{}.{:03}", bpm, bpm * 1000 % 1000).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if *highlight { + CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) +}); + +#[derive(Copy, Clone)] +struct TransportQuantize(usize, bool); + +render!(TransportQuantize |self, buf, area| { + let Rect { x, y, .. } = area; + let Self(quant, highlight) = self; + "QUANT".blit(buf, x, y, Some(NOT_DIM))?; + let width = ppq_to_name(*quant as usize).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if *highlight { + CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) +}); + +#[derive(Copy, Clone)] +struct TransportSync(usize, bool); + +render!(TransportSync |self, buf, area| { + let Rect { x, y, .. } = area; + let Self(sync, highlight) = self; + "SYNC".blit(buf, x, y, Some(NOT_DIM))?; + let width = ppq_to_name(*sync as usize).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if *highlight { + CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) +}); + +#[derive(Copy, Clone)] +struct TransportClock(usize, usize, usize); + +render!(TransportClock |self, buf, area| { + let Rect { x, y, width, .. } = area; + let Self(pulse, ppq, usecs) = self; + let (beats, pulses) = (pulse / ppq, pulse % ppq); + let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); + let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); + let (minutes, seconds) = (seconds / 60, seconds % 60); + let timer = format!("{bars}.{beats}.{pulses:02}"); + timer.blit(buf, x + width - timer.len() as u16 - 1, y + 0, Some(NOT_DIM))?; + let timer = format!("{minutes}:{seconds:02}:{msecs:03}"); + timer.blit(buf, x + width - timer.len() as u16 - 1, y + 1, Some(NOT_DIM))?; + Ok(area) });