diff --git a/crates/tek/src/tui/arranger_mode_v.rs b/crates/tek/src/tui/arranger_mode_v.rs index 921761d1..9f35c3ee 100644 --- a/crates/tek/src/tui/arranger_mode_v.rs +++ b/crates/tek/src/tui/arranger_mode_v.rs @@ -1,6 +1,14 @@ use crate::*; + +mod v_clips; pub(crate) use self::v_clips::*; +mod v_cursor; pub(crate) use self::v_cursor::*; +mod v_head; pub(crate) use self::v_head::*; +mod v_io; pub(crate) use self::v_io::*; +mod v_sep; pub(crate) use self::v_sep::*; + const HEADER_H: u16 = 5; const SCENES_W_OFFSET: u16 = 3; + impl ArrangerTui { pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Render + use<'_> { lay!([ @@ -16,278 +24,4 @@ impl ArrangerTui { ]) } } -pub struct ArrangerVColSep { fg: Color, cols: Vec<(usize, usize)>, scenes_w: u16, } -from!(|state:&ArrangerTui|ArrangerVColSep = Self { - fg: TuiTheme::separator_fg(false), - cols: ArrangerTrack::widths(state.tracks()), - scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16, -}); -render!(|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{ - let style = Some(Style::default().fg(self.fg)); - Ok(for x in self.cols.iter().map(|col|col.1) { - let x = self.scenes_w + to.area().x() + x as u16; - for y in to.area().y()..to.area().y2() { - to.blit(&"▎", x, y, style); - } - }) -})); -pub struct ArrangerVRowSep { fg: Color, rows: Vec<(usize, usize)>, } -from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self { - fg: TuiTheme::separator_fg(false), - rows: ArrangerScene::ppqs(args.0.scenes(), args.1), -}); -render!(|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{ - Ok(for y in self.rows.iter().map(|row|row.1) { - let y = to.area().y() + (y / PPQ) as u16 + 1; - if y >= to.buffer.area.height { break } - for x in to.area().x()..to.area().x2().saturating_sub(2) { - if x < to.buffer.area.x && y < to.buffer.area.y { - let cell = to.buffer.get_mut(x, y); - cell.modifier = Modifier::UNDERLINED; - cell.underline_color = self.fg; - } - } - }) -})); -pub struct ArrangerVHead<'a> { - scenes_w: u16, - timebase: &'a Arc, - current: &'a Arc, - tracks: &'a [ArrangerTrack], -} -from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A - tracks: state.tracks(), - timebase: state.clock().timebase(), - current: &state.clock().playhead, - scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16, -}); -render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( - (_, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - fn row > (color: ItemPalette, field: &T) -> impl Render + use<'_, T> { - row!([ - Tui::fg(color.light.rgb, "▎"), - Tui::fg(color.lightest.rgb, field) - ]) - } - Tui::bg(color.base.rgb, Tui::min_xy(w as u16, h, Fixed::wh(w as u16, 5, col!([ - row(color, &Self::format_name(track, w)), - row(color, &Self::format_input(track)?), - row(color, &Self::format_output(track)?), - row(color, &Self::format_elapsed(track, &self.timebase)), - row(color, &Self::format_until_next(track, &self.current)), - ])))) - } -))); -impl<'a> ArrangerVHead<'a> { - /// name and width of track - fn format_name (track: &ArrangerTrack, w: usize) -> impl Render { - let name = track.name().read().unwrap().clone(); - Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) - } - /// input port - fn format_input (track: &ArrangerTrack) -> Usually> { - Ok(format!(">{}", track.player.midi_ins().get(0).map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) - } - /// output port - fn format_output (track: &ArrangerTrack) -> Usually> { - Ok(format!("<{}", track.player.midi_outs().get(0).map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) - } - /// beats elapsed - fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Render { - let mut result = String::new(); - if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { - let length = phrase.read().unwrap().length; - let elapsed = track.player.pulses_since_start().unwrap(); - let elapsed = timebase.format_beats_1_short( - (elapsed as usize % length) as f64 - ); - result = format!("+{elapsed:>}") - } - result - } - /// beats until switchover - fn format_until_next (track: &ArrangerTrack, current: &Arc) - -> Option> - { - let timebase = ¤t.timebase; - let mut result = String::new(); - if let Some((t, _)) = track.player.next_phrase().as_ref() { - let target = t.pulse.get(); - let current = current.pulse.get(); - if target > current { - let remaining = target - current; - result = format!("-{:>}", timebase.format_beats_0_short(remaining)) - } - } - Some(result) - } -} - -pub struct ArrangerVIns<'a> { - size: &'a Measure, - tracks: &'a Vec, -} -from!(<'a>|args: &'a ArrangerTui|ArrangerVIns<'a> = Self { - size: &args.size, - tracks: &args.tracks, -}); -render!(|self: ArrangerVIns<'a>|""); - -pub struct ArrangerVOuts<'a> { - size: &'a Measure, - tracks: &'a Vec, -} -from!(<'a>|args: &'a ArrangerTui|ArrangerVOuts<'a> = Self { - size: &args.size, - tracks: &args.tracks, -}); -render!(|self: ArrangerVOuts<'a>|""); - -pub struct ArrangerVClocks<'a> { - size: &'a Measure, - tracks: &'a Vec, -} -from!(<'a>|args: &'a ArrangerTui|ArrangerVClocks<'a> = Self { - size: &args.size, - tracks: &args.tracks, -}); -render!(|self: ArrangerVClocks<'a>|""); - -pub struct ArrangerVClips<'a> { - size: &'a Measure, - scenes: &'a Vec, - tracks: &'a Vec, - rows: Vec<(usize, usize)>, -} -from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVClips<'a> = Self { - size: &args.0.size, - scenes: &args.0.scenes, - tracks: &args.0.tracks, - rows: ArrangerScene::ppqs(args.0.scenes(), args.1), -}); -render!(|self: ArrangerVClips<'a>|Fill::wh( - col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => { - Self::format_scene(&self.tracks, scene, pulses) - }) -)); -impl<'a> ArrangerVClips<'a> { - fn format_scene ( - tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize - ) -> impl Render + use<'a> { - let height = 1.max((pulses / PPQ) as u16); - let playing = scene.is_playing(tracks); - Fixed::h(height, row!([ - if playing { "▶ " } else { " " }, - Tui::bold(true, scene.name.read().unwrap().as_str()), - row!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { - Self::format_clip(scene, index, track, (x2 - x1) as u16, height) - })]) - ) - } - fn format_clip ( - scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 - ) -> impl Render + use<'a> { - Fixed::wh(w, h, Layers::new(move |add|{ - let mut bg = TuiTheme::border_bg(); - if let Some(Some(phrase)) = scene.clips.get(index) { - let name = &(phrase as &Arc>).read().unwrap().name; - let name = format!("{}", name); - let max_w = name.len().min((w as usize).saturating_sub(2)); - let color = phrase.read().unwrap().color; - bg = color.dark.rgb; - if let Some((_, Some(ref playing))) = track.player.play_phrase() { - if *playing.read().unwrap() == *phrase.read().unwrap() { - bg = color.light.rgb - } - }; - add(&Tui::push_x(1, Tui::bg(bg, - Tui::push_x(1, Fixed::w(w as u16, &name.as_str()[0..max_w]))) - ))?; - } - //add(&Background(bg)) - Ok(()) - })) - } -} - -pub struct ArrangerVCursor { - cols: Vec<(usize, usize)>, - rows: Vec<(usize, usize)>, - color: ItemPalette, - reticle: Reticle, - selected: ArrangerSelection, - scenes_w: u16, -} -from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self { - cols: ArrangerTrack::widths(args.0.tracks()), - rows: ArrangerScene::ppqs(args.0.scenes(), args.1), - selected: args.0.selected(), - scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(args.0.scenes()) as u16, - color: args.0.color, - reticle: Reticle(Style { - fg: Some(args.0.color.lighter.rgb), - bg: None, - underline_color: None, - add_modifier: Modifier::empty(), - sub_modifier: Modifier::DIM - }), -}); -render!(|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{ - let area = to.area(); - let focused = true; - let selected = self.selected; - let get_track_area = |t: usize| [ - self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(), - self.cols[t].0 as u16, area.h(), - ]; - let get_scene_area = |s: usize| [ - area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16, - area.w(), (self.rows[s].0 / PPQ) as u16 - ]; - let get_clip_area = |t: usize, s: usize| [ - (self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1), - HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16, - self.cols[t].0 as u16 + 2, - (self.rows[s].0 / PPQ) as u16 - ]; - let mut track_area: Option<[u16;4]> = None; - let mut scene_area: Option<[u16;4]> = None; - let mut clip_area: Option<[u16;4]> = None; - let area = match selected { - ArrangerSelection::Mix => area, - ArrangerSelection::Track(t) => { - track_area = Some(get_track_area(t)); - area - }, - ArrangerSelection::Scene(s) => { - scene_area = Some(get_scene_area(s)); - area - }, - ArrangerSelection::Clip(t, s) => { - track_area = Some(get_track_area(t)); - scene_area = Some(get_scene_area(s)); - clip_area = Some(get_clip_area(t, s)); - area - }, - }; - let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0); - if let Some([x, y, width, height]) = track_area { - to.fill_fg([x, y, 1, height], bg); - to.fill_fg([x + width, y, 1, height], bg); - } - if let Some([_, y, _, height]) = scene_area { - to.fill_ul([area.x(), y - 1, area.w(), 1], bg); - to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); - } - Ok(if focused { - to.render_in(if let Some(clip_area) = clip_area { clip_area } - else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) } - else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) } - else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)? - }) -})); diff --git a/crates/tek/src/tui/arranger_mode_v/v_clips.rs b/crates/tek/src/tui/arranger_mode_v/v_clips.rs new file mode 100644 index 00000000..0c8d073b --- /dev/null +++ b/crates/tek/src/tui/arranger_mode_v/v_clips.rs @@ -0,0 +1,62 @@ +use crate::*; +use super::*; + +pub struct ArrangerVClips<'a> { + size: &'a Measure, + scenes: &'a Vec, + tracks: &'a Vec, + rows: Vec<(usize, usize)>, +} + +from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVClips<'a> = Self { + size: &args.0.size, + scenes: &args.0.scenes, + tracks: &args.0.tracks, + rows: ArrangerScene::ppqs(args.0.scenes(), args.1), +}); + +render!(|self: ArrangerVClips<'a>|Fill::wh( + col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => { + Self::format_scene(&self.tracks, scene, pulses) + }) +)); + +impl<'a> ArrangerVClips<'a> { + fn format_scene ( + tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize + ) -> impl Render + use<'a> { + let height = 1.max((pulses / PPQ) as u16); + let playing = scene.is_playing(tracks); + Fixed::h(height, row!([ + if playing { "▶ " } else { " " }, + Tui::bold(true, scene.name.read().unwrap().as_str()), + row!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { + Self::format_clip(scene, index, track, (x2 - x1) as u16, height) + })]) + ) + } + fn format_clip ( + scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 + ) -> impl Render + use<'a> { + Fixed::wh(w, h, Layers::new(move |add|{ + let mut bg = TuiTheme::border_bg(); + if let Some(Some(phrase)) = scene.clips.get(index) { + let name = &(phrase as &Arc>).read().unwrap().name; + let name = format!("{}", name); + let max_w = name.len().min((w as usize).saturating_sub(2)); + let color = phrase.read().unwrap().color; + bg = color.dark.rgb; + if let Some((_, Some(ref playing))) = track.player.play_phrase() { + if *playing.read().unwrap() == *phrase.read().unwrap() { + bg = color.light.rgb + } + }; + add(&Tui::push_x(1, Tui::bg(bg, + Tui::push_x(1, Fixed::w(w as u16, &name.as_str()[0..max_w]))) + ))?; + } + //add(&Background(bg)) + Ok(()) + })) + } +} diff --git a/crates/tek/src/tui/arranger_mode_v/v_cursor.rs b/crates/tek/src/tui/arranger_mode_v/v_cursor.rs new file mode 100644 index 00000000..3dfb0123 --- /dev/null +++ b/crates/tek/src/tui/arranger_mode_v/v_cursor.rs @@ -0,0 +1,81 @@ +use crate::*; +use super::*; + +pub struct ArrangerVCursor { + cols: Vec<(usize, usize)>, + rows: Vec<(usize, usize)>, + color: ItemPalette, + reticle: Reticle, + selected: ArrangerSelection, + scenes_w: u16, +} + +from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self { + cols: ArrangerTrack::widths(args.0.tracks()), + rows: ArrangerScene::ppqs(args.0.scenes(), args.1), + selected: args.0.selected(), + scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(args.0.scenes()) as u16, + color: args.0.color, + reticle: Reticle(Style { + fg: Some(args.0.color.lighter.rgb), + bg: None, + underline_color: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::DIM + }), +}); + +render!(|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{ + let area = to.area(); + let focused = true; + let selected = self.selected; + let get_track_area = |t: usize| [ + self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(), + self.cols[t].0 as u16, area.h(), + ]; + let get_scene_area = |s: usize| [ + area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16, + area.w(), (self.rows[s].0 / PPQ) as u16 + ]; + let get_clip_area = |t: usize, s: usize| [ + (self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1), + HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16, + self.cols[t].0 as u16 + 2, + (self.rows[s].0 / PPQ) as u16 + ]; + let mut track_area: Option<[u16;4]> = None; + let mut scene_area: Option<[u16;4]> = None; + let mut clip_area: Option<[u16;4]> = None; + let area = match selected { + ArrangerSelection::Mix => area, + ArrangerSelection::Track(t) => { + track_area = Some(get_track_area(t)); + area + }, + ArrangerSelection::Scene(s) => { + scene_area = Some(get_scene_area(s)); + area + }, + ArrangerSelection::Clip(t, s) => { + track_area = Some(get_track_area(t)); + scene_area = Some(get_scene_area(s)); + clip_area = Some(get_clip_area(t, s)); + area + }, + }; + let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0); + if let Some([x, y, width, height]) = track_area { + to.fill_fg([x, y, 1, height], bg); + to.fill_fg([x + width, y, 1, height], bg); + } + if let Some([_, y, _, height]) = scene_area { + to.fill_ul([area.x(), y - 1, area.w(), 1], bg); + to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); + } + Ok(if focused { + to.render_in(if let Some(clip_area) = clip_area { clip_area } + else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) } + else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) } + else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)? + }) +})); diff --git a/crates/tek/src/tui/arranger_mode_v/v_head.rs b/crates/tek/src/tui/arranger_mode_v/v_head.rs new file mode 100644 index 00000000..7b22a523 --- /dev/null +++ b/crates/tek/src/tui/arranger_mode_v/v_head.rs @@ -0,0 +1,83 @@ +use crate::*; +use super::*; + +pub struct ArrangerVHead<'a> { + scenes_w: u16, + timebase: &'a Arc, + current: &'a Arc, + tracks: &'a [ArrangerTrack], +} + +from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A + tracks: state.tracks(), + timebase: state.clock().timebase(), + current: &state.clock().playhead, + scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16, +}); + +render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( + (_, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => { + let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); + let color = track.color(); + fn row > (color: ItemPalette, field: &T) -> impl Render + use<'_, T> { + row!([ + Tui::fg(color.light.rgb, "▎"), + Tui::fg(color.lightest.rgb, field) + ]) + } + Tui::bg(color.base.rgb, Tui::min_xy(w as u16, h, Fixed::wh(w as u16, 5, col!([ + row(color, &Self::format_name(track, w)), + row(color, &Self::format_input(track)?), + row(color, &Self::format_output(track)?), + row(color, &Self::format_elapsed(track, &self.timebase)), + row(color, &Self::format_until_next(track, &self.current)), + ])))) + } +))); + +impl<'a> ArrangerVHead<'a> { + /// name and width of track + fn format_name (track: &ArrangerTrack, w: usize) -> impl Render { + let name = track.name().read().unwrap().clone(); + Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) + } + /// input port + fn format_input (track: &ArrangerTrack) -> Usually> { + Ok(format!(">{}", track.player.midi_ins().get(0).map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into()))) + } + /// output port + fn format_output (track: &ArrangerTrack) -> Usually> { + Ok(format!("<{}", track.player.midi_outs().get(0).map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into()))) + } + /// beats elapsed + fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Render { + let mut result = String::new(); + if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { + let length = phrase.read().unwrap().length; + let elapsed = track.player.pulses_since_start().unwrap(); + let elapsed = timebase.format_beats_1_short( + (elapsed as usize % length) as f64 + ); + result = format!("+{elapsed:>}") + } + result + } + /// beats until switchover + fn format_until_next (track: &ArrangerTrack, current: &Arc) + -> Option> + { + let timebase = ¤t.timebase; + let mut result = String::new(); + if let Some((t, _)) = track.player.next_phrase().as_ref() { + let target = t.pulse.get(); + let current = current.pulse.get(); + if target > current { + let remaining = target - current; + result = format!("-{:>}", timebase.format_beats_0_short(remaining)) + } + } + Some(result) + } +} diff --git a/crates/tek/src/tui/arranger_mode_v/v_io.rs b/crates/tek/src/tui/arranger_mode_v/v_io.rs new file mode 100644 index 00000000..c55d8c42 --- /dev/null +++ b/crates/tek/src/tui/arranger_mode_v/v_io.rs @@ -0,0 +1,26 @@ +use crate::*; +use super::*; + +pub struct ArrangerVIns<'a> { + size: &'a Measure, + tracks: &'a Vec, +} + +from!(<'a>|args: &'a ArrangerTui|ArrangerVIns<'a> = Self { + size: &args.size, + tracks: &args.tracks, +}); + +render!(|self: ArrangerVIns<'a>|""); + +pub struct ArrangerVOuts<'a> { + size: &'a Measure, + tracks: &'a Vec, +} + +from!(<'a>|args: &'a ArrangerTui|ArrangerVOuts<'a> = Self { + size: &args.size, + tracks: &args.tracks, +}); + +render!(|self: ArrangerVOuts<'a>|""); diff --git a/crates/tek/src/tui/arranger_mode_v/v_sep.rs b/crates/tek/src/tui/arranger_mode_v/v_sep.rs new file mode 100644 index 00000000..acde7617 --- /dev/null +++ b/crates/tek/src/tui/arranger_mode_v/v_sep.rs @@ -0,0 +1,48 @@ +use crate::*; +use super::*; + +pub struct ArrangerVColSep { + fg: Color, + cols: Vec<(usize, usize)>, + scenes_w: u16 +} + +from!(|state:&ArrangerTui|ArrangerVColSep = Self { + fg: TuiTheme::separator_fg(false), + cols: ArrangerTrack::widths(state.tracks()), + scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16, +}); + +render!(|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{ + let style = Some(Style::default().fg(self.fg)); + Ok(for x in self.cols.iter().map(|col|col.1) { + let x = self.scenes_w + to.area().x() + x as u16; + for y in to.area().y()..to.area().y2() { + to.blit(&"▎", x, y, style); + } + }) +})); + +pub struct ArrangerVRowSep { + fg: Color, + rows: Vec<(usize, usize)>, +} + +from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self { + fg: TuiTheme::separator_fg(false), + rows: ArrangerScene::ppqs(args.0.scenes(), args.1), +}); + +render!(|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{ + Ok(for y in self.rows.iter().map(|row|row.1) { + let y = to.area().y() + (y / PPQ) as u16 + 1; + if y >= to.buffer.area.height { break } + for x in to.area().x()..to.area().x2().saturating_sub(2) { + if x < to.buffer.area.x && y < to.buffer.area.y { + let cell = to.buffer.get_mut(x, y); + cell.modifier = Modifier::UNDERLINED; + cell.underline_color = self.fg; + } + } + }) +}));