arranger, transport: despaghettify

This commit is contained in:
🪞👃🪞 2024-08-21 20:09:23 +03:00
parent 33bdf65e8d
commit 1104093395
10 changed files with 430 additions and 253 deletions

View file

@ -53,6 +53,7 @@ submod! {
render_border render_border
render_collect render_collect
render_fill render_fill
render_layered
render_split render_split
render_theme render_theme
time_base time_base

View file

@ -2,10 +2,9 @@
use crate::*; use crate::*;
pub(crate) use ratatui::prelude::CrosstermBackend; 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::layout::Rect;
pub(crate) use ratatui::buffer::{Buffer, Cell}; pub(crate) use ratatui::buffer::{Buffer, Cell};
use ratatui::widgets::WidgetRef;
/// Main thread render loop /// Main thread render loop
pub fn render_thread ( pub fn render_thread (
@ -37,7 +36,7 @@ pub fn render_thread (
} }
/// Trait for things that render to the display. /// Trait for things that render to the display.
pub trait Render { pub trait Render: Send + Sync {
// Render something to an area of the buffer. // Render something to an area of the buffer.
// Returns area used by component. // Returns area used by component.
// This is insufficient but for the most basic dynamic layout algorithms. // This is insufficient but for the most basic dynamic layout algorithms.
@ -76,23 +75,23 @@ impl Render for () {
} }
} }
//impl<T: Render> Render for &T { impl<T: Render> Render for &T {
//fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
//(*self).render(buf, area) (*self).render(buf, area)
//} }
//fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
//Collected::Ref(self) Collected::Ref(self)
//} }
//} }
//impl<T: Render> Render for &mut T { impl<T: Render> Render for &mut T {
//fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
//(**self).render(buf, area) (**self).render(buf, area)
//} }
//fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
//Collected::Ref(self) Collected::Ref(self)
//} }
//} }
impl<T: Render> Render for Option<T> { impl<T: Render> Render for Option<T> {
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> { fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
@ -112,18 +111,18 @@ impl<'a> Render for Box<dyn Render + 'a> {
} }
} }
impl<'a, T: Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> Render for T { //impl<'a, T: Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> Render for T {
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
(*self)(b, a)
}
}
//impl<'a> Render for Box<dyn Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> {
//fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> { //fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
//(*self)(b, a) //(*self)(b, a)
//} //}
//} //}
impl<'a> Render for Box<dyn Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> {
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
(*self)(b, a)
}
}
impl<T: Render> Render for Arc<Mutex<T>> { impl<T: Render> Render for Arc<Mutex<T>> {
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> { fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
self.lock().unwrap().render(b, a) 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<Rect> {
for layer in self.0.iter() {
layer.render(buf, area)?;
}
Ok(area)
}
}
pub struct If<'a>(pub bool, pub &'a (dyn Render + Sync)); pub struct If<'a>(pub bool, pub &'a (dyn Render + Sync));
impl<'a> Render for If<'a> { impl<'a> Render for If<'a> {

View file

@ -16,7 +16,7 @@ impl<'a> Render for Collected<'a> {
} }
} }
pub struct Collection<'a>(Vec<Collected<'a>>); pub struct Collection<'a>(pub Vec<Collected<'a>>);
impl<'a> Collection<'a> { impl<'a> Collection<'a> {
pub fn new () -> Self { pub fn new () -> Self {
@ -27,6 +27,9 @@ impl<'a> Collection<'a> {
pub trait Collect<'a> { pub trait Collect<'a> {
fn add_box (self, item: Box<dyn Render + 'a>) -> Self; fn add_box (self, item: Box<dyn Render + 'a>) -> Self;
fn add_ref (self, item: &'a dyn Render) -> Self; fn add_ref (self, item: &'a dyn Render) -> Self;
fn add <T: Render + Sized + 'a> (self, item: T) -> Self where Self: Sized {
self.add_box(Box::new(item))
}
} }
impl<'a> Collect<'a> for Collection<'a> { impl<'a> Collect<'a> for Collection<'a> {

View file

@ -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<Rect> {
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<dyn Render + 'a>) -> 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
}
}

View file

@ -9,9 +9,9 @@ pub enum Direction {
} }
impl Direction { impl Direction {
pub fn split <'a, const N: usize> (&self, items: [&'a (dyn Render + Sync);N]) -> Split<'a, N> { //pub fn split <'a, const N: usize> (&self, items: [&'a (dyn Render + Sync);N]) -> Split<'a, N> {
Split(*self, items) //Split(*self, items)
} //}
pub fn split_focus <'a> (&self, index: usize, items: Renderables<'a>, style: Style) -> SplitFocus<'a> { pub fn split_focus <'a> (&self, index: usize, items: Renderables<'a>, style: Style) -> SplitFocus<'a> {
SplitFocus(*self, index, items, style) SplitFocus(*self, index, items, style)
} }
@ -23,26 +23,27 @@ impl Direction {
} }
} }
pub struct Split<'a, const N: usize>( pub struct Split<'a>(Collection<'a>, Direction);
pub Direction, pub [&'a (dyn Render + Sync);N]
);
impl<'a, const N: usize> Split<'a, N> { impl<'a> Split<'a> {
pub fn down (items: [&'a (dyn Render + Sync);N]) -> Self { pub fn new (direction: Direction) -> Self {
Self(Direction::Down, items) Self(Collection::new(), direction)
} }
pub fn right (items: [&'a (dyn Render + Sync);N]) -> Self { pub fn down () -> Self {
Self(Direction::Right, items) 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<Rect>)> { pub fn render_areas (&self, buf: &mut Buffer, area: Rect) -> Usually<(Rect, Vec<Rect>)> {
let Rect { mut x, mut y, mut width, mut height } = area; let Rect { mut x, mut y, mut width, mut height } = area;
let mut areas = vec![]; let mut areas = vec![];
for item in self.1 { for item in self.0.0.iter() {
if width == 0 || height == 0 { if width == 0 || height == 0 {
break break
} }
let result = item.render(buf, Rect { x, y, width, height })?; let result = item.render(buf, Rect { x, y, width, height })?;
match self.0 { match self.1 {
Direction::Down => { Direction::Down => {
y = y + result.height; y = y + result.height;
height = height.saturating_sub(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<Rect> { fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
Ok(self.render_areas(buf, area)?.0) Ok(self.render_areas(buf, area)?.0)
} }
} }
impl<'a> Collect<'a> for Split<'a> {
fn add_box (mut self, item: Box<dyn Render + 'a>) -> 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<Rect>)> {
//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<Rect> {
//Ok(self.render_areas(buf, area)?.0)
//}
//}
type Renderables<'a> = &'a [&'a (dyn Render + Send + Sync)]; type Renderables<'a> = &'a [&'a (dyn Render + Send + Sync)];
pub struct SplitFocus<'a>(pub Direction, pub usize, pub Renderables<'a>, pub Style); pub struct SplitFocus<'a>(pub Direction, pub usize, pub Renderables<'a>, pub Style);

View file

@ -1,4 +1,5 @@
use crate::*; use crate::*;
use ratatui::style::Modifier;
pub trait Theme { pub trait Theme {
const BG0: Color; const BG0: Color;
@ -69,3 +70,75 @@ impl Theme for Nord {
const PLAYING: Color = Color::Rgb(60, 100, 50); const PLAYING: Color = Color::Rgb(60, 100, 50);
const SEPARATOR: Color = Color::Rgb(0, 0, 0); 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,
};

View file

@ -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<Rect> {
Ok(area)
}
}
impl<'a> Collect<'a> for Split<'a> {
fn add_box (mut self, item: Box<dyn Render + 'a>) -> 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| { render!(Arranger |self, buf, area| {
let arrangement = Box::new(|buf: &mut Buffer, area: Rect| { let arrangement = Box::new(|buf: &mut Buffer, area: Rect| {

View file

@ -2,34 +2,36 @@ use crate::*;
pub fn draw (state: &Arranger, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> { pub fn draw (state: &Arranger, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
area.height = area.height.min((2 + state.tracks.len() * 2) as u16); area.height = area.height.min((2 + state.tracks.len() * 2) as u16);
Layered([ let tracks = state.tracks.as_slice();
&FillBg(Nord::bg_lo(state.focused, state.entered)), Layered::new()
&Split::right([ .add(FillBg(Nord::bg_lo(state.focused, state.entered)))
&track_name_column(state), .add(Split::right()
&track_mon_column(state), .add(TrackNameColumn(tracks, state.selected))
&track_rec_column(state), .add(TrackMonitorColumn(tracks))
&track_ovr_column(state), .add(TrackRecordColumn(tracks))
&track_del_column(state), .add(TrackOverdubColumn(tracks))
&track_gain_column(state), .add(TrackEraseColumn(tracks))
&track_scenes_column(state), .add(TrackGainColumn(tracks))
]), .add(TrackScenesColumn(tracks, state.scenes.as_slice(), state.selected)))
]).render(buf, area) .render(buf, area)
} }
fn track_name_column <'a> (state: &'a Arranger) -> impl Render + 'a { struct TrackNameColumn<'a>(&'a [Sequencer], ArrangerFocus);
let dim = Some(Style::default().dim());
let yellow = Some(Style::default().yellow().bold().not_dim()); impl<'a> Render for TrackNameColumn<'a> {
let white = Some(Style::default().white().bold().not_dim()); fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
move |buf: &mut Buffer, mut area: Rect| { let Self(tracks, selected) = self;
area.width = 3 + 5.max(track_name_max_len(state.tracks.as_slice())) as u16; 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 let offset = 0; // track scroll offset
for y in 0..area.height { for y in 0..area.height {
if y == 0 { 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 { } else if y % 2 == 0 {
let index = (y as usize - 2) / 2 + offset; let index = (y as usize - 2) / 2 + offset;
if let Some(track) = state.tracks.get(index) { if let Some(track) = tracks.get(index) {
let selected = state.selected.track() == Some(index); let selected = selected.track() == Some(index);
let style = if selected { yellow } else { white }; let style = if selected { yellow } else { white };
format!(" {index:>02} ").blit(buf, area.x, area.y + y, style)?; format!(" {index:>02} ").blit(buf, area.x, area.y + y, style)?;
track.name.blit(buf, area.x + 4, 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 { struct TrackMonitorColumn<'a>(&'a [Sequencer]);
let on = Some(Style::default().not_dim().green().bold());
let off = Some(Style::default().dim()); impl<'a> Render for TrackMonitorColumn<'a> {
move |buf: &mut Buffer, mut area: Rect| { fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let Self(tracks) = self;
let on = Some(Style::default().not_dim().green().bold());
let off = Some(DIM);
area.x = area.x + 1; area.x = area.x + 1;
for y in 0..area.height { for y in 0..area.height {
if y == 0 { if y == 0 {
//" MON ".blit(buf, area.x, area.y + y, style2)?; //" MON ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 0 { } else if y % 2 == 0 {
let index = (y as usize - 2) / 2; 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 }; let style = if track.monitoring { on } else { off };
" MON ".blit(buf, area.x, area.y + y, style)?; " MON ".blit(buf, area.x, area.y + y, style)?;
} else { } 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 { struct TrackRecordColumn<'a>(&'a [Sequencer]);
let on = Some(Style::default().not_dim().red().bold());
let off = Some(Style::default().dim()); impl<'a> Render for TrackRecordColumn<'a> {
move |buf: &mut Buffer, mut area: Rect| { fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let Self(tracks) = self;
let on = Some(Style::default().not_dim().red().bold());
let off = Some(Style::default().dim());
area.x = area.x + 1; area.x = area.x + 1;
for y in 0..area.height { for y in 0..area.height {
if y == 0 { if y == 0 {
//" REC ".blit(buf, area.x, area.y + y, style2)?; //" REC ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 0 { } else if y % 2 == 0 {
let index = (y as usize - 2) / 2; 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 }; let style = if track.recording { on } else { off };
" REC ".blit(buf, area.x, area.y + y, style)?; " REC ".blit(buf, area.x, area.y + y, style)?;
} else { } 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 { struct TrackOverdubColumn<'a>(&'a [Sequencer]);
let on = Some(Style::default().not_dim().yellow().bold());
let off = Some(Style::default().dim()); impl<'a> Render for TrackOverdubColumn<'a> {
move |buf: &mut Buffer, mut area: Rect| { fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let Self(tracks) = self;
let on = Some(Style::default().not_dim().yellow().bold());
let off = Some(Style::default().dim());
area.x = area.x + 1; area.x = area.x + 1;
for y in 0..area.height { for y in 0..area.height {
if y == 0 { if y == 0 {
//" OVR ".blit(buf, area.x, area.y + y, style2)?; //" OVR ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 0 { } else if y % 2 == 0 {
let index = (y as usize - 2) / 2; 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 { " OVR ".blit(buf, area.x, area.y + y, if track.overdub {
on on
} else { } 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 { struct TrackEraseColumn<'a>(&'a [Sequencer]);
let off = Some(Style::default().dim());
move |buf: &mut Buffer, mut area: Rect| { impl<'a> Render for TrackEraseColumn<'a> {
fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let Self(tracks) = self;
let off = Some(Style::default().dim());
area.x = area.x + 1; area.x = area.x + 1;
for y in 0..area.height { for y in 0..area.height {
if y == 0 { if y == 0 {
//" DEL ".blit(buf, area.x, area.y + y, style2)?; //" DEL ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 0 { } else if y % 2 == 0 {
let index = (y as usize - 2) / 2; 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)?; " DEL ".blit(buf, area.x, area.y + y, off)?;
} else { } else {
area.height = y; 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 { struct TrackGainColumn<'a>(&'a [Sequencer]);
let off = Some(Style::default().dim());
move |buf: &mut Buffer, mut area: Rect| { impl<'a> Render for TrackGainColumn<'a> {
fn render (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let Self(tracks) = self;
let off = Some(Style::default().dim());
area.x = area.x + 1; area.x = area.x + 1;
for y in 0..area.height { for y in 0..area.height {
if y == 0 { if y == 0 {
//" GAIN ".blit(buf, area.x, area.y + y, style2)?; //" GAIN ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 0 { } else if y % 2 == 0 {
let index = (y as usize - 2) / 2; 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)?; " +0.0 ".blit(buf, area.x, area.y + y, off)?;
} else { } else {
area.height = y; 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 { struct TrackScenesColumn<'a>(&'a [Sequencer], &'a [Scene], ArrangerFocus);
|buf: &mut Buffer, area: Rect| {
impl<'a> Render for TrackScenesColumn<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Self(tracks, scenes, selected) = self;
let mut x2 = 0; let mut x2 = 0;
let Rect { x, y, height, .. } = area; let Rect { x, y, height, .. } = area;
for (scene_index, scene) in state.scenes.iter().enumerate() { for (scene_index, scene) in scenes.iter().enumerate() {
let active_scene = state.selected.scene() == Some(scene_index); let active_scene = selected.scene() == Some(scene_index);
let sep = Some(if active_scene { let sep = Some(if active_scene {
Style::default().yellow().not_dim() Style::default().yellow().not_dim()
} else { } else {
@ -176,10 +196,10 @@ fn track_scenes_column <'a> (state: &'a Arranger) -> impl Render + 'a {
let mut x3 = scene.name.len() as u16; let mut x3 = scene.name.len() as u16;
scene.name.blit(buf, x + x2, y, sep)?; scene.name.blit(buf, x + x2, y, sep)?;
for (i, clip) in scene.clips.iter().enumerate() { 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 { if let Some(clip) = clip {
let y2 = y + 2 + i as u16 * 2; 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), Some(phrase) => &format!("{}", phrase.read().unwrap().name),
None => "...." None => "...."
}; };
@ -196,4 +216,3 @@ fn track_scenes_column <'a> (state: &'a Arranger) -> impl Render + 'a {
Ok(Rect { x, y, height, width: x2 }) Ok(Rect { x, y, height, width: x2 })
} }
} }

View file

@ -30,20 +30,25 @@ pub fn draw (
) -> Usually<Rect> { ) -> Usually<Rect> {
area.height = 2 + (rows[rows.len() - 1].1 / 96) as u16; area.height = 2 + (rows[rows.len() - 1].1 / 96) as u16;
let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16; let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16;
Layered([ let Arranger { focus_sequencer, focused, entered, selected, .. } = *state;
&FillBg(Nord::bg_lo(state.focused, state.entered)), let tracks = state.tracks.as_ref();
&column_separators(offset, cols), let scenes = state.scenes.as_ref();
&cursor_focus(state, offset, cols, rows), Layered::new()
&Split::down([ .add(FillBg(Nord::bg_lo(state.focused, state.entered)))
&tracks_header(state, cols, offset), .add(ColumnSeparators(offset, cols))
&scene_rows(state, cols, rows, offset), .add(CursorFocus(focus_sequencer, focused, entered, selected, offset, cols, rows))
]), .add(Split::down()
&row_separators(rows), .add(TracksHeader(offset, cols, tracks))
]).render(buf, area) .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 { struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]);
move |buf: &mut Buffer, area: Rect|{
impl<'a> Render for ColumnSeparators<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Self(offset, cols) = self;
let style = Some(Style::default().fg(Nord::SEPARATOR)); let style = Some(Style::default().fg(Nord::SEPARATOR));
for (_, x) in cols.iter() { for (_, x) in cols.iter() {
let x = offset + area.x + *x as u16 - 1; 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 { struct RowSeparators<'a>(&'a [(usize, usize)]);
move |buf: &mut Buffer, area: Rect| {
impl<'a> Render for RowSeparators<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Self(rows) = self;
for (_, y) in rows.iter() { for (_, y) in rows.iter() {
let y = area.y + (*y / 96) as u16 + 1; let y = area.y + (*y / 96) as u16 + 1;
if y >= buf.area.height { if y >= buf.area.height {
@ -72,16 +80,19 @@ fn row_separators <'a> (rows: &'a [(usize, usize)]) -> impl Render + 'a {
} }
} }
fn cursor_focus <'a> ( struct CursorFocus<'a>(
state: &'a Arranger, offset: u16, cols: &'a [(usize, usize)], rows: &'a [(usize, usize)], bool, bool, bool, ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)]
) -> impl Render + 'a { );
move |buf: &mut Buffer, area: Rect| {
let area = match state.selected { impl<'a> Render for CursorFocus<'a> {
ArrangerFocus::Mix => if state.focused fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
&& state.entered let Self(focus_sequencer, focused, entered, selected, offset, cols, rows) = *self;
&& state.selected == ArrangerFocus::Mix 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 area
} else { } else {
area area
@ -93,7 +104,7 @@ fn cursor_focus <'a> (
width: cols[t].0 as u16, width: cols[t].0 as u16,
height: area.height height: area.height
}; };
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered)); fill_bg(buf, area, Nord::bg_hi(focused, entered));
area area
}, },
ArrangerFocus::Scene(s) => { ArrangerFocus::Scene(s) => {
@ -103,7 +114,7 @@ fn cursor_focus <'a> (
width: area.width, width: area.width,
height: (rows[s].0 / 96) as u16 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 area
}, },
ArrangerFocus::Clip(t, s) => { ArrangerFocus::Clip(t, s) => {
@ -125,29 +136,28 @@ fn cursor_focus <'a> (
width: cols[t].0 as u16, width: cols[t].0 as u16,
height: (rows[s].0 / 96) as u16 height: (rows[s].0 / 96) as u16
}; };
let lo = Nord::bg_hi(state.focused, state.entered); let lo = Nord::bg_hi(focused, entered);
let hi = Nord::bg_hier(state.focused, state.entered); let hi = Nord::bg_hier(focused, entered);
fill_bg(buf, track_area, lo); fill_bg(buf, track_area, lo);
fill_bg(buf, scene_area, lo); fill_bg(buf, scene_area, lo);
fill_bg(buf, area, hi); fill_bg(buf, area, hi);
area area
}, },
}; };
if !state.focus_sequencer { if !focus_sequencer {
Corners(Style::default().green().not_dim()).draw(buf, area)?; Corners(Style::default().green().not_dim()).draw(buf, area)?;
} }
Ok(area) Ok(area)
} }
} }
pub fn tracks_header <'a> ( struct TracksHeader<'a>(u16, &'a[(usize, usize)], &'a [Sequencer]);
state: &'a Arranger,
track_cols: &'a [(usize, usize)], impl<'a> Render for TracksHeader<'a> {
offset: u16, fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
) -> impl Render + 'a { let Self(offset, track_cols, tracks) = *self;
move |buf: &mut Buffer, area: Rect| {
let Rect { y, width, .. } = area; 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; let x = *x as u16;
if x > width { if x > width {
break break
@ -158,13 +168,11 @@ pub fn tracks_header <'a> (
} }
} }
pub fn scene_rows <'a> ( struct SceneRows<'a>(u16, &'a[(usize, usize)], &'a[(usize, usize)], &'a[Sequencer], &'a[Scene]);
state: &'a Arranger,
track_cols: &'a [(usize, usize)], impl<'a> Render for SceneRows<'a> {
scene_rows: &'a [(usize, usize)], fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
offset: u16, let Self(offset, track_cols, scene_rows, tracks, scenes) = *self;
) -> impl Render + 'a {
move |buf: &mut Buffer, area: Rect| {
let black = Some(Style::default().fg(Nord::SEPARATOR)); let black = Some(Style::default().fg(Nord::SEPARATOR));
let Rect { mut y, height, .. } = area; let Rect { mut y, height, .. } = area;
for (_, x) in track_cols.iter() { 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 { if y > height {
break break
} }
let h = 1.max((pulses / 96) as u16); let h = 1.max((pulses / 96) as u16);
scene_row(state, buf, Rect { scene_row(tracks, buf, Rect {
x: area.x, x: area.x,
y, y,
width: area.width, width: area.width,
@ -192,8 +200,8 @@ pub fn scene_rows <'a> (
} }
} }
fn scene_row ( fn scene_row <'a> (
state: &Arranger, tracks: &'a[Sequencer],
buf: &mut Buffer, buf: &mut Buffer,
area: Rect, area: Rect,
scene: &Scene, scene: &Scene,
@ -201,7 +209,6 @@ fn scene_row (
offset: u16 offset: u16
) -> Usually<u16> { ) -> Usually<u16> {
let Rect { y, width, .. } = area; let Rect { y, width, .. } = area;
let tracks = state.tracks.as_ref();
let playing = scene.is_playing(tracks); let playing = scene.is_playing(tracks);
(if playing { "" } else { " " }).blit(buf, area.x, y, None)?; (if playing { "" } else { " " }).blit(buf, area.x, y, None)?;
scene.name.blit(buf, area.x + 1, y, None)?; scene.name.blit(buf, area.x + 1, y, None)?;

View file

@ -1,84 +1,112 @@
use crate::*; use crate::*;
const CORNERS: Corners = Corners(NOT_DIM_GREEN);
render!(TransportToolbar |self, buf, area| { render!(TransportToolbar |self, buf, area| {
let mut area = area; let mut area = area;
area.height = 2; area.height = 2;
let gray = Style::default().gray(); let ppq = self.ppq();
let not_dim = Style::default().not_dim(); let bpm = self.bpm();
let not_dim_bold = not_dim.bold(); let pulse = self.pulse();
let corners = Corners(Style::default().green().not_dim()); let usecs = self.usecs();
let ppq = self.ppq();
let bpm = self.bpm();
let pulse = self.pulse();
let usecs = self.usecs();
let Self { quant, sync, focused, entered, .. } = self; let Self { quant, sync, focused, entered, .. } = self;
fill_bg(buf, area, Nord::bg_lo(*focused, *entered)); fill_bg(buf, area, Nord::bg_lo(*focused, *entered));
Split::right([ let active = self.focused && self.entered;
let playing = TransportPlayPauseButton(self.playing);
// Play/Pause button let bpm = TransportBPM(bpm, active && self.selected == TransportFocus::BPM);
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ let quant = TransportQuantize(*quant, active && self.selected == TransportFocus::Quant);
let style = Some(match self.playing { let sync = TransportSync(*sync, active && self.selected == TransportFocus::Sync);
Some(TransportState::Stopped) => gray.dim().bold(), let clock = TransportClock(pulse, ppq, usecs);
Some(TransportState::Starting) => gray.not_dim().bold(), Split::right()
Some(TransportState::Rolling) => gray.not_dim().white().bold(), .add_ref(&playing)
_ => unreachable!(), .add_ref(&bpm)
}); .add_ref(&quant)
let label = match self.playing { .add_ref(&sync)
Some(TransportState::Rolling) => "▶ PLAYING", .add_ref(&clock)
Some(TransportState::Starting) => "READY ...", .render(buf, area)
Some(TransportState::Stopped) => "⏹ STOPPED", });
_ => unreachable!(),
}; #[derive(Copy, Clone)]
let mut result = label.blit(buf, x + 1, y, style)?; struct TransportPlayPauseButton(Option<TransportState>);
result.width = result.width + 1;
Ok(result) render!(TransportPlayPauseButton |self, buf, area| {
}, let Rect { x, y, .. } = area;
let gray = Style::default().gray();
// Beats per minute let style = Some(match self.0 {
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ Some(TransportState::Stopped) => GRAY_DIM.bold(),
"BPM".blit(buf, x, y, Some(not_dim))?; Some(TransportState::Starting) => GRAY_NOT_DIM_BOLD,
let width = format!("{}.{:03}", bpm, bpm * 1000 % 1000).blit(buf, x, y + 1, Some(not_dim_bold))?.width; Some(TransportState::Rolling) => WHITE_NOT_DIM_BOLD,
let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; _ => unreachable!(),
if self.focused && self.entered && self.selected == TransportFocus::BPM { });
corners.draw(buf, Rect { x: area.x - 1, ..area })?; let label = match self.0 {
} Some(TransportState::Rolling) => "▶ PLAYING",
Ok(area) Some(TransportState::Starting) => "READY ...",
}, Some(TransportState::Stopped) => "⏹ STOPPED",
_ => unreachable!(),
// Quantization };
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ let mut result = label.blit(buf, x + 1, y, style)?;
"QUANT".blit(buf, x, y, Some(not_dim))?; result.width = result.width + 1;
let width = ppq_to_name(*quant as usize).blit(buf, x, y + 1, Some(not_dim_bold))?.width; Ok(result)
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 })?; #[derive(Copy, Clone)]
} struct TransportBPM(usize, bool);
Ok(area)
}, render!(TransportBPM |self, buf, area| {
let Rect { x, y, .. } = area;
// Clip launch sync let Self(bpm, highlight) = self;
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ "BPM".blit(buf, x, y, Some(NOT_DIM))?;
"SYNC".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 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 };
let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; if *highlight {
if self.focused && self.entered && self.selected == TransportFocus::Sync { CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?;
corners.draw(buf, Rect { x: area.x - 1, ..area })?; }
} Ok(area)
Ok(area) });
},
#[derive(Copy, Clone)]
// Clock struct TransportQuantize(usize, bool);
&|buf: &mut Buffer, Rect { x, y, width, .. }: Rect|{
let (beats, pulses) = (pulse / ppq, pulse % ppq); render!(TransportQuantize |self, buf, area| {
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); let Rect { x, y, .. } = area;
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); let Self(quant, highlight) = self;
let (minutes, seconds) = (seconds / 60, seconds % 60); "QUANT".blit(buf, x, y, Some(NOT_DIM))?;
let timer = format!("{bars}.{beats}.{pulses:02}"); let width = ppq_to_name(*quant as usize).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width;
timer.blit(buf, x + width - timer.len() as u16 - 1, y + 0, Some(not_dim))?; let area = Rect { x, y, width: (width + 2).max(10), height: 2 };
let timer = format!("{minutes}:{seconds:02}:{msecs:03}"); if *highlight {
timer.blit(buf, x + width - timer.len() as u16 - 1, y + 1, Some(not_dim))?; CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?;
Ok(area) }
} Ok(area)
});
]).render(buf, 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)
}); });