From 38e2e64751f78a5576d7df989e67d85379654a9c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 6 Jan 2025 23:12:25 +0100 Subject: [PATCH] fixed Map operator! --- engine/src/render.rs | 2 +- layout/src/map.rs | 2 +- layout/src/transform_xy_unit.rs | 14 +- src/arranger.rs | 160 +------------- src/arranger/arranger_scene.rs | 14 -- src/arranger/arranger_tui.rs | 354 ++++++++++++++++++++++++++++++ src/arranger/arranger_v.rs | 17 -- src/arranger/arranger_v_clips.rs | 64 ------ src/arranger/arranger_v_cursor.rs | 87 -------- src/arranger/arranger_v_head.rs | 41 ---- src/arranger/arranger_v_sep.rs | 43 ---- 11 files changed, 362 insertions(+), 436 deletions(-) create mode 100644 src/arranger/arranger_tui.rs delete mode 100644 src/arranger/arranger_v.rs delete mode 100644 src/arranger/arranger_v_clips.rs delete mode 100644 src/arranger/arranger_v_cursor.rs delete mode 100644 src/arranger/arranger_v_head.rs delete mode 100644 src/arranger/arranger_v_sep.rs diff --git a/engine/src/render.rs b/engine/src/render.rs index 22649c7d..eeb45b67 100644 --- a/engine/src/render.rs +++ b/engine/src/render.rs @@ -12,7 +12,7 @@ pub trait Render: Send + Sync { pub type RenderDyn<'a, Output> = dyn Render + 'a; impl<'a, E: Output> Content for &RenderDyn<'a, E> where Self: Sized { - fn content (&self) -> impl Render { *self } + fn content (&self) -> impl Render { self.deref() } fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) } fn render (&self, output: &mut E) { Render::render(self.deref(), output) } } diff --git a/layout/src/map.rs b/layout/src/map.rs index bbeee8a2..3a917b7f 100644 --- a/layout/src/map.rs +++ b/layout/src/map.rs @@ -48,7 +48,7 @@ impl Content for Map where for item in get_iterator() { let item = callback(item, index); //to.place(area.into(), &item); - to.place(to.area().into(), &item); + to.place(item.layout(to.area()), &item); index += 1; } } diff --git a/layout/src/transform_xy_unit.rs b/layout/src/transform_xy_unit.rs index 51b07885..3e600471 100644 --- a/layout/src/transform_xy_unit.rs +++ b/layout/src/transform_xy_unit.rs @@ -57,11 +57,9 @@ transform_xy_unit!(|self: Fixed, area|{ transform_xy_unit!(|self: Shrink, area|Render::layout(&self.content(), [ area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy()) ].into())); - transform_xy_unit!(|self: Expand, area|Render::layout(&self.content(), [ area.x(), area.y(), area.w() + self.dx(), area.h() + self.dy() ].into())); - transform_xy_unit!(|self: Min, area|{ let area = Render::layout(&self.content(), area); match self { @@ -69,7 +67,6 @@ transform_xy_unit!(|self: Min, area|{ Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)], Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)] }}); - transform_xy_unit!(|self: Max, area|{ let area = Render::layout(&self.content(), area); match self { @@ -77,23 +74,20 @@ transform_xy_unit!(|self: Max, area|{ Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().min(*mh)], Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().min(*mw), area.h().min(*mh)], }}); - -transform_xy_unit!(|self: Push, area|Render::layout(&self.content(), [ - area.x() + self.dx(), area.y() + self.dy(), area.w(), area.h() -].into())); - +transform_xy_unit!(|self: Push, area|{ + let area = Render::layout(&self.content(), area); + [area.x() + self.dx(), area.y() + self.dy(), area.w(), area.h()] +}); transform_xy_unit!(|self: Pull, area|{ let area = Render::layout(&self.content(), area); [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] }); - transform_xy_unit!(|self: Margin, area|{ let area = Render::layout(&self.content(), area); let dx = self.dx(); let dy = self.dy(); [area.x().minus(dx), area.y().minus(dy), area.w() + dy + dy, area.h() + dy + dy] }); - transform_xy_unit!(|self: Padding, area|{ let area = Render::layout(&self.content(), area); let dx = self.dx(); diff --git a/src/arranger.rs b/src/arranger.rs index 5f045e70..a4b4562d 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -4,13 +4,11 @@ mod arranger_command; pub(crate) use self::arranger_command::*; mod arranger_scene; pub(crate) use self::arranger_scene::*; mod arranger_select; pub(crate) use self::arranger_select::*; mod arranger_track; pub(crate) use self::arranger_track::*; +mod arranger_tui; pub(crate) use self::arranger_tui::*; mod arranger_mode; pub(crate) use self::arranger_mode::*; mod arranger_h; -mod arranger_v_clips; pub(crate) use self::arranger_v_clips::*; -mod arranger_v_cursor; pub(crate) use self::arranger_v_cursor::*; -mod arranger_v_sep; pub(crate) use self::arranger_v_sep::*; -pub(crate) const HEADER_H: u16 = 5; +pub(crate) const HEADER_H: u16 = 0; // 5 pub(crate) const SCENES_W_OFFSET: u16 = 3; /// Root view for standalone `tek_arranger` @@ -98,160 +96,6 @@ from_jack!(|jack| Arranger { compact: false, } }); -//impl Arranger { - //fn render_mode (state: &Self) -> impl Content + use<'_> { - //match state.mode { - //ArrangerMode::H => todo!("horizontal arranger"), - //ArrangerMode::V(factor) => Self::render_mode_v(state, factor), - //} - //} -//} -//render!(TuiOut: (self: Arranger) => { - //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; - //let color = self.color; - //let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)), - //Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)), - //Bsp::n(TransportView::new(true, &self.clock), - //Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)), - //Bsp::n(Fill::x(Fixed::y(20, - //Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")), - //Bsp::a( - //Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))), - //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); - //self.size.of(layout) -//}); -render!(TuiOut: (self: Arranger) => self.size.of( - Bsp::s(self.toolbar_view(), - Bsp::n(self.status_view(), - Bsp::n(self.selector_view(), - Bsp::s(Align::nw(Fill::x(Fixed::y(3, self.header()))), - Bsp::s(Align::nw(Fill::x(Fixed::y(1, self.ins()))), - Bsp::n(Align::nw(Fill::x(Fixed::y(1, self.outs()))), - Bsp::w(self.pool_view(), Fill::xy(lay!( - Align::nw(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))), - Align::nw(Fill::xy(ArrangerVColSep::from(self))), - Align::nw(Fill::xy(ArrangerVRowSep::from((self, 1)))), - Align::nw(Fill::xy(ArrangerVCursor::from((self, 1)))), - Align::nw(Fill::xy(":"))))))))))))); - //"todo:")))))))); - //Bsp::s( - //Align::nw(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))), - //Bsp::s( - //Fixed::y(20, Align::nw(ArrangerVClips::new(self, 1))), - //Fill::x(Fixed::y(1, ArrangerVOuts::from(self))))))))))))); - //Bsp::s( - //Bsp::s( - //Bsp::s( - //Fill::xy(ArrangerVClips::new(self, 1)), - //Fill::x(ArrangerVOuts::from(self))))) - -impl Arranger { - fn toolbar_view (&self) -> impl Content + use<'_> { - Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock)))) - } - fn status_view (&self) -> impl Content + use<'_> { - ArrangerStatus::from(self) - //let edit_clip = MidiEditClip(&self.editor); - ////let selectors = When(false, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); - //row!([>selectors,<] edit_clip, MidiEditStatus(&self.editor)) - } - fn selector_view (&self) -> impl Content + use<'_> { - row!( - //ClipSelected::play_phrase(&self.player), - //ClipSelected::next_phrase(&self.player), - MidiEditClip(&self.editor), - MidiEditStatus(&self.editor), - ) - } - fn pool_view (&self) -> impl Content + use<'_> { - let w = self.size.w(); - let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let pool_w = if self.pool.visible { phrase_w } else { 0 }; - let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); - Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) - } - fn header (&self) -> impl Content + use<'_> { - let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - fn row > (color: ItemPalette, field: T) -> impl Content { - row!(Tui::fg(color.light.rgb, "▎"), Tui::fg(color.lightest.rgb, field)) - } - Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - Push::x(x1 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::xy(w as u16, 5, col!( - row(color, Self::format_name(track, w)), - row(color, Self::format_elapsed(track, self.clock().timebase())), - row(color, Self::format_until_next(track, &self.clock().playhead)), - ))))) - })) - } - /// name and width of track - fn format_name (track: &ArrangerTrack, _w: usize) -> impl Content { - let name = track.name().read().unwrap().clone(); - Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) - } - /// beats elapsed - fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Content { - let mut result = String::new(); - if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { - let length = phrase.read().unwrap().length; - 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) - } - fn ins (&self) -> impl Content + use<'_> { - let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - fn row > (color: ItemPalette, field: T) -> impl Content { - row!(Tui::fg(color.light.rgb, "▎"), Tui::fg(color.lightest.rgb, field)) - } - Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - let input = Self::format_input(track); - Push::x(x1 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::xy(w as u16, 5, row(color, Self::format_input(track).ok()))))) - })) - } - fn format_input (track: &ArrangerTrack) -> Usually> { - Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) - } - fn outs (&self) -> impl Content + use<'_> { - let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - fn row > (color: ItemPalette, field: T) -> impl Content { - row!(Tui::fg(color.light.rgb, "▎"), Tui::fg(color.lightest.rgb, field)) - } - Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - Push::x(x2 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::xy(w as u16, 5, row(color, Self::format_output(track).ok()))))) - })) - } - /// output port - fn format_output (track: &ArrangerTrack) -> Usually> { - Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) - } -} //render!(TuiOut: (self: Arranger) => { //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; //let color = self.color; diff --git a/src/arranger/arranger_scene.rs b/src/arranger/arranger_scene.rs index 273d7363..6680ec33 100644 --- a/src/arranger/arranger_scene.rs +++ b/src/arranger/arranger_scene.rs @@ -44,20 +44,6 @@ impl ArrangerScene { pub fn color (&self) -> ItemPalette { self.color } - pub fn ppqs (scenes: &[Self], factor: usize) -> Vec<(usize, usize)> { - let mut total = 0; - if factor == 0 { - scenes.iter().map(|scene|{ - let pulses = scene.pulses().max(PPQ); - total += pulses; - (pulses, total - pulses) - }).collect() - } else { - (0..=scenes.len()).map(|i|{ - (factor*PPQ, factor*PPQ*i) - }).collect() - } - } pub fn longest_name (scenes: &[Self]) -> usize { scenes.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) } diff --git a/src/arranger/arranger_tui.rs b/src/arranger/arranger_tui.rs new file mode 100644 index 00000000..84abcef7 --- /dev/null +++ b/src/arranger/arranger_tui.rs @@ -0,0 +1,354 @@ +use crate::*; +//impl Arranger { + //fn render_mode (state: &Self) -> impl Content + use<'_> { + //match state.mode { + //ArrangerMode::H => todo!("horizontal arranger"), + //ArrangerMode::V(factor) => Self::render_mode_v(state, factor), + //} + //} +//} +//render!(TuiOut: (self: Arranger) => { + //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; + //let color = self.color; + //let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)), + //Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)), + //Bsp::n(TransportView::new(true, &self.clock), + //Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)), + //Bsp::n(Fill::x(Fixed::y(20, + //Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")), + //Bsp::a( + //Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))), + //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); + //self.size.of(layout) +//}); +render!(TuiOut: (self: Arranger) => { + let scenes = &self.scenes; + let ppqs = Arranger::ppqs(scenes, 1); + Fill::xy(self.size.of( + Bsp::s(self.toolbar_view(), + Bsp::n(self.status_view(), + Bsp::n(self.selector_view(), + Bsp::w(self.pool_view(), + Bsp::s(Align::n(Fill::x(Fixed::y(3, self.header()))), + Bsp::s(Align::n(Fill::x(Fixed::y(1, self.ins()))), + Bsp::s(Align::n(Fill::x(Fixed::y(1, self.outs()))), + Fill::xy( + Bsp::a(Fill::xy(Fill::xy(Map( + move||scenes.iter(),//.zip(ppqs.iter().map(|row|row.0)), + move|scene, i|Arranger::format_scene(&self.tracks, scene, ppqs[i].0)))), + Bsp::a( + Fill::xy(ArrangerVRowSep::from((self, 1))), + Fill::xy(ArrangerVColSep::from(self)))))))))))))) +}); + //Align::n(Fill::xy(lay!( + //Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))), + //Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))), + //Align::n(Fill::xy(ArrangerVColSep::from(self))), + //Align::n(Fill::xy(ArrangerVClips::new(self, 1))), + //Align::n(Fill::xy(ArrangerVCursor::from((self, 1)))))))))))))))); + //Align::n(Fill::xy(":"))))))))))))); + //"todo:")))))))); + //Bsp::s( + //Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))), + //Bsp::s( + //Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))), + //Fill::x(Fixed::y(1, ArrangerVOuts::from(self))))))))))))); + //Bsp::s( + //Bsp::s( + //Bsp::s( + //Fill::xy(ArrangerVClips::new(self, 1)), + //Fill::x(ArrangerVOuts::from(self))))) + +pub struct ArrangerVClips<'a> { + size: &'a Measure, + scenes: &'a Vec, + tracks: &'a Vec, + rows: Vec<(usize, usize)>, +} +impl<'a> ArrangerVClips<'a> { + pub fn new (state: &'a Arranger, zoom: usize) -> Self { + Self { + size: &state.size, + tracks: &state.tracks, + scenes: &state.scenes, + rows: Arranger::ppqs(&state.scenes, zoom), + } + } +} +fn row > (color: ItemPalette, field: T) -> impl Content { + Bsp::e(Tui::fg(color.light.rgb, "▎"), Tui::fg(color.lightest.rgb, field)) +} +impl Arranger { + fn header (&self) -> impl Content + use<'_> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { + let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); + let color = track.color(); + let title = format!("{} {} {}", track.name.read().unwrap(), x1, x2); + Push::x(x1 as u16, Tui::bg(Color::Rgb(50,0,0), title)) })) + //Bsp::s(row(color, Self::format_name(track, w)), + //Bsp::s(row(color, Self::format_elapsed(track, self.clock().timebase())), + //Bsp::s(row(color, Self::format_until_next(track, &self.clock().playhead)), "test1")))))}) + } + fn ins (&self) -> impl Content + use<'_> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { + let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); + let color = track.color(); + let input = Self::format_input(track); + Fill::xy(Align::n(Push::x(x1 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h, + Fixed::xy(w as u16, 5, row(color, Self::format_input(track).ok()))))))) + })) + } + fn outs (&self) -> impl Content + use<'_> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { + let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); + let color = track.color(); + Fill::xy(Align::n(Push::x(x2 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h, + Fixed::xy(w as u16, 5, row(color, Self::format_output(track).ok()))))))) + })) + } + pub fn ppqs (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> { + let mut total = 0; + if factor == 0 { + scenes.iter().map(|scene|{ + let pulses = scene.pulses().max(PPQ); + total += pulses; + (pulses, total - pulses) + }).collect() + } else { + (0..=scenes.len()).map(|i|{ + (factor*PPQ, factor*PPQ*i) + }).collect() + } + } + + fn format_scene <'a> ( + tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize + ) -> impl Content + use<'a> { + let height = 1.max((pulses / PPQ) as u16); + let playing = scene.is_playing(tracks); + let icon = Tui::bg( + scene.color.base.rgb, if playing { "▶ " } else { " " } + ); + let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, + Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone())) + ); + let clips = Map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _| + Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height)) + ); + Fixed::y(height, Bsp::e(icon, Bsp::e(name, clips))) + } + + fn format_clip <'a> ( + scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 + ) -> impl Content + use<'a> { + scene.clips.get(index).map(|clip|clip.as_ref().map(|phrase|{ + let phrase = phrase.read().unwrap(); + let mut bg = TuiTheme::border_bg(); + let name = phrase.name.to_string(); + let max_w = name.len().min((w as usize).saturating_sub(2)); + let color = phrase.color; + bg = color.dark.rgb; + if let Some((_, Some(ref playing))) = track.player.play_phrase() { + if *playing.read().unwrap() == *phrase { + bg = color.light.rgb + } + }; + Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w])))); + })) + } + fn toolbar_view (&self) -> impl Content + use<'_> { + Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock)))) + } + fn status_view (&self) -> impl Content + use<'_> { + ArrangerStatus::from(self) + //let edit_clip = MidiEditClip(&self.editor); + ////let selectors = When(false, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); + //row!([>selectors,<] edit_clip, MidiEditStatus(&self.editor)) + } + fn selector_view (&self) -> impl Content + use<'_> { + Bsp::e( + //ClipSelected::play_phrase(&self.player), + //ClipSelected::next_phrase(&self.player), + MidiEditClip(&self.editor), + MidiEditStatus(&self.editor), + ) + } + fn pool_view (&self) -> impl Content + use<'_> { + let w = self.size.w(); + let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + let pool_w = if self.pool.visible { phrase_w } else { 0 }; + let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); + Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) + } + /// name and width of track + fn format_name (track: &ArrangerTrack, _w: usize) -> impl Content { + let name = track.name().read().unwrap().clone(); + Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) + } + /// beats elapsed + fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Content { + let mut result = String::new(); + if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { + let length = phrase.read().unwrap().length; + 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) + } + fn format_input (track: &ArrangerTrack) -> Usually> { + Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into()))) + } + /// output port + fn format_output (track: &ArrangerTrack) -> Usually> { + Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into()))) + } +} +pub struct ArrangerVColSep { + fg: Color, + cols: Vec<(usize, usize)>, + scenes_w: u16 +} +from!(|state:&Arranger|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!(TuiOut: |self: ArrangerVColSep, to| { + let style = Some(Style::default().fg(self.fg)); + 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:(&Arranger, usize)|ArrangerVRowSep = Self { + fg: Color::Rgb(255,255,255,), + rows: Arranger::ppqs(&args.0.scenes, args.1), +}); +render!(TuiOut: |self: ArrangerVRowSep, to|for y in self.rows.iter().map(|row|row.1) { + let y = to.area().y() + (y / PPQ) as u16 + 1; + if y >= to.buffer.area.height { break } + for x in to.area().x()..to.area().x2().saturating_sub(2) { + //if x < to.buffer.area.x && y < to.buffer.area.y { + if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { + cell.modifier = Modifier::UNDERLINED; + cell.underline_color = self.fg; + } + //} + } +}); + +pub struct ArrangerVCursor { + cols: Vec<(usize, usize)>, + rows: Vec<(usize, usize)>, + color: ItemPalette, + reticle: Reticle, + selected: ArrangerSelection, + scenes_w: u16, +} + +from!(|args:(&Arranger, usize)|ArrangerVCursor = Self { + cols: ArrangerTrack::widths(&args.0.tracks), + rows: Arranger::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 + }), +}); +impl Content for ArrangerVCursor { + fn render (&self, to: &mut TuiOut) { + 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); + } + if focused { + to.place(if let Some(clip_area) = clip_area { + clip_area + } else if let Some(track_area) = track_area { + track_area.clip_h(HEADER_H) + } else if let Some(scene_area) = scene_area { + scene_area.clip_w(self.scenes_w) + } else { + area.clip_w(self.scenes_w).clip_h(HEADER_H) + }, &self.reticle) + }; + } +} diff --git a/src/arranger/arranger_v.rs b/src/arranger/arranger_v.rs deleted file mode 100644 index 3408d004..00000000 --- a/src/arranger/arranger_v.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::*; -// egyptian snakes den -impl Arranger { - pub fn render_mode_v (state: &Arranger, factor: usize) -> impl Content + use<'_> { - lay!( - ArrangerVColSep::from(state), - ArrangerVRowSep::from((state, factor)), - col!( - //ArrangerVHead::from(state), - ArrangerVIns::from(state), - ArrangerVClips::new(state, factor), - ArrangerVOuts::from(state), - ), - ArrangerVCursor::from((state, factor)), - ) - } -} diff --git a/src/arranger/arranger_v_clips.rs b/src/arranger/arranger_v_clips.rs deleted file mode 100644 index 85ca08e4..00000000 --- a/src/arranger/arranger_v_clips.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::*; -use super::*; - -pub struct ArrangerVClips<'a> { - size: &'a Measure, - scenes: &'a Vec, - tracks: &'a Vec, - rows: Vec<(usize, usize)>, -} -impl<'a> ArrangerVClips<'a> { - pub fn new (state: &'a Arranger, zoom: usize) -> Self { - Self { - size: &state.size, - tracks: &state.tracks, - scenes: &state.scenes, - rows: ArrangerScene::ppqs(&state.scenes, zoom), - } - } -} -impl<'a> Content for ArrangerVClips<'a> { - fn content (&self) -> impl Render { - let iter = ||self.scenes.iter().zip(self.rows.iter().map(|row|row.0)); - let col = Map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses)); - Fill::xy(col) - } -} -impl<'a> ArrangerVClips<'a> { - - fn format_scene ( - tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize - ) -> impl Content + use<'a> { - let height = 1.max((pulses / PPQ) as u16); - let playing = scene.is_playing(tracks); - let icon = Tui::bg( - scene.color.base.rgb, if playing { "▶ " } else { " " } - ); - let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, - Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone())) - ); - let clips = Map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _| - Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height)) - ); - Fixed::y(height, row!(icon, name, clips)) - } - - fn format_clip ( - scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 - ) -> impl Content + use<'a> { - scene.clips.get(index).map(|clip|clip.as_ref().map(|phrase|{ - let phrase = phrase.read().unwrap(); - let mut bg = TuiTheme::border_bg(); - let name = phrase.name.to_string(); - let max_w = name.len().min((w as usize).saturating_sub(2)); - let color = phrase.color; - bg = color.dark.rgb; - if let Some((_, Some(ref playing))) = track.player.play_phrase() { - if *playing.read().unwrap() == *phrase { - bg = color.light.rgb - } - }; - Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w])))); - })) - } -} diff --git a/src/arranger/arranger_v_cursor.rs b/src/arranger/arranger_v_cursor.rs deleted file mode 100644 index 89e4a4d0..00000000 --- a/src/arranger/arranger_v_cursor.rs +++ /dev/null @@ -1,87 +0,0 @@ -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:(&Arranger, 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 - }), -}); -impl Content for ArrangerVCursor { - fn render (&self, to: &mut TuiOut) { - 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); - } - if focused { - to.place(if let Some(clip_area) = clip_area { - clip_area - } else if let Some(track_area) = track_area { - track_area.clip_h(HEADER_H) - } else if let Some(scene_area) = scene_area { - scene_area.clip_w(self.scenes_w) - } else { - area.clip_w(self.scenes_w).clip_h(HEADER_H) - }, &self.reticle) - }; - } -} diff --git a/src/arranger/arranger_v_head.rs b/src/arranger/arranger_v_head.rs deleted file mode 100644 index 86069a59..00000000 --- a/src/arranger/arranger_v_head.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::*; -use super::*; - -pub struct ArrangerVIns<'a> { - size: &'a Measure, - tracks: &'a Vec, - scenes_w: u16, -} -impl ArrangerVIns<'_> { - /// input port - fn format_input (track: &ArrangerTrack) -> Usually> { - Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) - } -} - -from!(<'a>|args: &'a Arranger|ArrangerVIns<'a> = Self { - size: &args.size, - tracks: &args.tracks, - scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.scenes) as u16, -}); - -render!(TuiOut: (self: ArrangerVIns<'a>) => { -}); - -pub struct ArrangerVOuts<'a> { - size: &'a Measure, - tracks: &'a Vec, - scenes_w: u16, -} -impl ArrangerVOuts<'_> { -} - -from!(<'a>|args: &'a Arranger|ArrangerVOuts<'a> = Self { - size: &args.size, - tracks: &args.tracks, - scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.scenes) as u16, -}); - -render!(TuiOut: (self: ArrangerVOuts<'a>) => { -}); diff --git a/src/arranger/arranger_v_sep.rs b/src/arranger/arranger_v_sep.rs deleted file mode 100644 index d522f6cd..00000000 --- a/src/arranger/arranger_v_sep.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::*; -use super::*; - -pub struct ArrangerVColSep { - fg: Color, - cols: Vec<(usize, usize)>, - scenes_w: u16 -} -from!(|state:&Arranger|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!(TuiOut: |self: ArrangerVColSep, to| { - let style = Some(Style::default().fg(self.fg)); - 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:(&Arranger, usize)|ArrangerVRowSep = Self { - fg: Color::Rgb(255,255,255,), - rows: ArrangerScene::ppqs(&args.0.scenes, args.1), -}); -render!(TuiOut: |self: ArrangerVRowSep, to|for y in self.rows.iter().map(|row|row.1) { - let y = to.area().y() + (y / PPQ) as u16 + 1; - if y >= to.buffer.area.height { break } - for x in to.area().x()..to.area().x2().saturating_sub(2) { - //if x < to.buffer.area.x && y < to.buffer.area.y { - if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { - cell.modifier = Modifier::UNDERLINED; - cell.underline_color = self.fg; - } - //} - } -});