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_collect
render_fill
render_layered
render_split
render_theme
time_base

View file

@ -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<T: Render> Render for &T {
//fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
//(*self).render(buf, area)
//}
//fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
//Collected::Ref(self)
//}
//}
impl<T: Render> Render for &T {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
(*self).render(buf, area)
}
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
Collected::Ref(self)
}
}
//impl<T: Render> Render for &mut T {
//fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
//(**self).render(buf, area)
//}
//fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
//Collected::Ref(self)
//}
//}
impl<T: Render> Render for &mut T {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
(**self).render(buf, area)
}
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
Collected::Ref(self)
}
}
impl<T: Render> Render for Option<T> {
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 {
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> {
//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> {
(*self)(b, a)
}
}
impl<T: Render> Render for Arc<Mutex<T>> {
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
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));
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> {
pub fn new () -> Self {
@ -27,6 +27,9 @@ impl<'a> Collection<'a> {
pub trait Collect<'a> {
fn add_box (self, item: Box<dyn Render + 'a>) -> 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> {

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 {
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<Rect>)> {
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<Rect> {
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)];
pub struct SplitFocus<'a>(pub Direction, pub usize, pub Renderables<'a>, pub Style);

View file

@ -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,
};

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| {
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> {
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<Rect> {
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<Rect> {
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<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;
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<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;
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<Rect> {
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<Rect> {
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<Rect> {
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 })
}
}

View file

@ -30,20 +30,25 @@ pub fn draw (
) -> Usually<Rect> {
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<Rect> {
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<Rect> {
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<Rect> {
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<Rect> {
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<Rect> {
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<u16> {
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)?;

View file

@ -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<TransportState>);
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)
});