From f0528914731eb9f3141ca9bc5388becedeb2e377 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 7 Jan 2025 21:11:56 +0100 Subject: [PATCH] sensible arranger entrypoint! now let's see whats up with the align modifiers --- engine/src/render.rs | 1 + engine/src/thunk.rs | 24 +++- src/arranger.rs | 3 - src/arranger/arranger_scene.rs | 2 +- src/arranger/arranger_track.rs | 2 +- src/arranger/arranger_tui.rs | 236 +++++++++++++++++++-------------- tui/src/tui_content.rs | 17 ++- 7 files changed, 180 insertions(+), 105 deletions(-) diff --git a/engine/src/render.rs b/engine/src/render.rs index eeb45b67..5fe0a137 100644 --- a/engine/src/render.rs +++ b/engine/src/render.rs @@ -20,6 +20,7 @@ impl<'a, E: Output> Content for &RenderDyn<'a, E> where Self: Sized { pub type RenderBox<'a, E: Output> = Box>; impl<'a, E: Output> Content for RenderBox<'a, E> { fn content (&self) -> impl Render { self.deref() } + //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } } impl> Render for C { diff --git a/engine/src/thunk.rs b/engine/src/thunk.rs index 26297641..b24e3d15 100644 --- a/engine/src/thunk.rs +++ b/engine/src/thunk.rs @@ -4,12 +4,34 @@ use std::marker::PhantomData; /// Lazily-evaluated [Render]able. pub struct Thunk, F: Fn()->T + Send + Sync>(PhantomData, F); impl, F: Fn()->T + Send + Sync> Thunk { - pub fn new (thunk: F) -> Self { Self(Default::default(), thunk) } + pub fn new (thunk: F) -> Self { + Self(Default::default(), thunk) + } } impl, F: Fn()->T + Send + Sync> Content for Thunk { fn content (&self) -> impl Render { (self.1)() } } +pub struct BoxThunk<'a, E: Output>(PhantomData, BoxBox + 'a> + Send + Sync + 'a>); +impl<'a, E: Output> BoxThunk<'a, E> { + pub fn new (thunk: BoxBox + 'a> + Send + Sync + 'a>) -> Self { + Self(Default::default(), thunk) + } +} +impl<'a, E: Output> Content for BoxThunk<'a, E> { + fn content (&self) -> impl Render { (self.1)() } +} +impl<'a, E: Output, F: Fn()->T + Send + Sync + 'a, T: Render + Send + Sync + 'a> From for BoxThunk<'a, E> { + fn from (f: F) -> Self { + Self(Default::default(), Box::new(move||f().boxed())) + } +} +//impl<'a, E: Output, F: Fn()->Box + 'a> + Send + Sync + 'a> From for BoxThunk<'a, E> { + //fn from (f: F) -> Self { + //Self(Default::default(), Box::new(f)) + //} +//} + pub struct RenderThunk(PhantomData, F); impl RenderThunk { pub fn new (render: F) -> Self { Self(Default::default(), render) } diff --git a/src/arranger.rs b/src/arranger.rs index cbe931d9..5f46b04f 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -9,9 +9,6 @@ mod arranger_tui; pub(crate) use self::arranger_tui::*; mod arranger_mode; pub(crate) use self::arranger_mode::*; mod arranger_h; -pub(crate) const HEADER_H: u16 = 0; // 5 -pub(crate) const SCENES_W_OFFSET: u16 = 3; - /// Root view for standalone `tek_arranger` pub struct Arranger { jack: Arc>, diff --git a/src/arranger/arranger_scene.rs b/src/arranger/arranger_scene.rs index 6680ec33..100332b0 100644 --- a/src/arranger/arranger_scene.rs +++ b/src/arranger/arranger_scene.rs @@ -17,7 +17,7 @@ impl Arranger { todo!("delete scene"); } fn scene_default_name (&self) -> String { - format!("S{:3>}", self.scenes.len() + 1) + format!("Sc{:3>}", self.scenes.len() + 1) } pub fn selected_scene (&self) -> Option<&ArrangerScene> { self.selected.scene().and_then(|s|self.scenes.get(s)) diff --git a/src/arranger/arranger_track.rs b/src/arranger/arranger_track.rs index 54cbe5d7..77b1497d 100644 --- a/src/arranger/arranger_track.rs +++ b/src/arranger/arranger_track.rs @@ -1,7 +1,7 @@ use crate::*; impl Arranger { pub fn track_next_name (&self) -> String { - format!("T{}", self.tracks.len() + 1) + format!("Tr{}", self.tracks.len() + 1) } pub fn track_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerTrack> diff --git a/src/arranger/arranger_tui.rs b/src/arranger/arranger_tui.rs index 7903af3b..4d9e019a 100644 --- a/src/arranger/arranger_tui.rs +++ b/src/arranger/arranger_tui.rs @@ -7,46 +7,18 @@ render!(TuiOut: (self: Arranger) => { let scene_heights = Arranger::scene_heights(scenes, 1); let border_style_1 = Style::default().fg(Color::Rgb(48,48,48)); let border_style_2 = Style::default().fg(Color::Rgb(72,72,72)); - let frame = Bsp::s(self.track_column_headers(), - Bsp::n(self.track_column_inputs(), - Bsp::s(self.track_column_outputs(), - Bsp::b(self.track_column_separators(), "")))); - let arranger = Bsp::n( - Fixed::y(20, Outer(border_style_2).enclose(&self.editor)), - Outer(border_style_1).enclose(Bsp::w(frame, - Bsp::s(Fixed::y(1, Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track"))), - Bsp::s(Fixed::y(1, Tui::bold(true, Tui::fg(TuiTheme::g(128), "Playing"))), - Bsp::s(Fixed::y(1, Tui::bold(true, Tui::fg(TuiTheme::g(128), "Next"))), - Bsp::s(Fixed::y(1, Tui::bold(true, Tui::fg(TuiTheme::g(128), "Out 1: NI"))), - Bsp::n(Fixed::y(1, Tui::bold(true, Tui::fg(TuiTheme::g(128), "In 1: Korg"))), - Fixed::x(scenes_w, self.scene_row_headers()))))))))); - self.size.of( - Bsp::s(self.toolbar_view(), - Bsp::n(self.status_view(), - Bsp::w(self.pool_view(), - Bsp::n(self.editor_status_view(), - arranger))))) - //Outer(border_style).enclose(Tui::bg(Color::Rgb(0,32,0), self.track_column_headers())))))))) [>Align::nw(Fill::xy(Bsp::s( - //Align::w(Fill::x( - //Bsp::s(Align::w(Fixed::y(3, self.track_column_headers())), - //Bsp::s(Fixed::y(1, self.ins()), Fill::x(Fixed::y(1, self.outs())))) - //)),*/ - //"" - //Bsp::a( - //Bsp::a( - //Map::new( - //move||scenes.iter(),//.zip(scene_heights.iter().map(|row|row.0)), - //move|scene, i|Arranger::cell_scene(&self.tracks, scene, scene_heights[i].0) - //), - //self.cursor(), - //), - //Bsp::a( - //Fill::xy(self.scene_row_sep()), - //"" - ////Fill::xy(ArrangerVColSep::from(self)) - //) - //) - //))))))))))) + let toolbar = |x|Bsp::s(self.toolbar_view(), x); + let status = |x|Bsp::n(self.status_view(), x); + let pool = |x|Bsp::w(self.pool_view(), x); + let editing = |x|Bsp::n(self.editor_status_view(), x); + let editor = Fixed::y(20, Outer(border_style_2).enclose(&self.editor)); + let arrrrrr = Fixed::y(20, Map::new( + move||self.grid_rows(), + move|(height, header, cells), index|map_south( + index as u16, height, Fill::x(Align::w(Bsp::e( + Fixed::x(scenes_w, header), + cells)))))); + self.size.of(toolbar(status(pool(editing(Bsp::n(editor, arrrrrr)))))) }); pub struct ArrangerVClips<'a> { size: &'a Measure, @@ -64,12 +36,129 @@ impl<'a> ArrangerVClips<'a> { } } } -fn row > (color: ItemPalette, field: T) -> impl Content { - Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) -} impl Arranger { pub const LEFT_SEP: char = '▎'; pub const TRACK_MIN_WIDTH: usize = 4; + + fn grid_rows <'a> (&'a self) -> impl Iterator, BoxThunk<'a, TuiOut>)> + 'a { + [ + (2, self.output_row_header(), self.output_row_cells()), + (2, self.elapsed_row_header(), self.elapsed_row_cells()), + (2, self.next_row_header(), self.next_row_cells()), + (2, self.track_row_header(), self.track_row_cells()), + (2, self.input_row_header(), self.input_row_cells()), + (2 * (self.scenes.len() as u16 + 1), self.scene_row_headers(), self.scene_row_cells()), + ].into_iter() + } + + /// A 1-row cell. + fn cell > (color: ItemPalette, field: T) -> impl Content { + Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) + } + + /// A cell that is 3-row on its own, but stacks, giving (N+1)*2 rows per N cells. + fn phat_cell > (color: ItemPalette, last: ItemPalette, field: T) -> impl Content { + Bsp::s( + Fixed::y(1, Tui::fg_bg(color.base.rgb, last.base.rgb, RepeatH(&"▄"))), + Bsp::s( + Fixed::y(1, Fill::x(Tui::fg_bg(color.lightest.rgb, color.base.rgb, field))), + Fixed::y(1, Tui::fg_bg(color.base.rgb, Color::Reset, RepeatH(&"▀"))), + ) + ) + } + + fn track_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into() + } + fn track_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + (move||Fixed::y(1, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { + let color = track.color(); + Push::x(scenes_w, Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, + Tui::fg_bg(color.lightest.rgb, color.base.rgb, + Self::cell(color, Tui::bold(true, format!("{}", track.name.read().unwrap()))))))) + })).boxed()).into() + } + + fn elapsed_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Playing")).boxed()).into() + } + fn elapsed_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + (move||Fixed::y(1, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { + let color = track.color(); + Push::x(scenes_w, Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, + Tui::fg_bg(color.lightest.rgb, color.base.rgb, Self::cell_elapsed(track, self.clock().timebase()))))) + })).boxed()).into() + } + + fn next_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Next")).boxed()).into() + } + fn next_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + (move||Fixed::y(1, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { + let color: ItemPalette = track.color(); + Push::x(scenes_w, Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, + Tui::fg_bg(color.lightest.rgb, color.base.rgb, Self::cell(color, Tui::bold(true, Self::cell_until_next(track, &self.clock().playhead))))))) + })).boxed()).into() + } + + fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(0), TuiTheme::g(200), "[ ] In 1: Korg"))).boxed()).into() + } + fn input_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + (move||Fixed::y(1, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { + let w = (x2 - x1) as u16; + let cell = Self::cell_input(track).ok(); + let color: ItemPalette = track.color().dark.into(); + Push::x(scenes_w, map_east(x1 as u16, w, Fixed::x(w, Fixed::y(1, Self::cell(color, cell))))) + })).boxed()).into() + } + fn cell_input (track: &ArrangerTrack) -> Usually> { + Ok(format!("RecMon"))/*, track.player.midi_ins().first().map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into())))*/ + } + + fn output_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(0), TuiTheme::g(200), "[ ] Out 1: NI"))).boxed()).into() + } + fn output_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + (move||Fixed::y(1, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { + let w = (x2 - x1) as u16; + let cell = Self::cell_output(track).ok(); + let color: ItemPalette = track.color().dark.into(); + Push::x(scenes_w, map_east(x1 as u16, w, Fixed::x(w, Fixed::y(1, Self::cell(color, cell))))) + })).boxed()).into() + } + /// output port + fn cell_output (track: &ArrangerTrack) -> Usually> { + Ok(format!("MutSol"))/*, track.player.midi_outs().first().map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into())))*/ + } + + fn scene_row_headers <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||{ + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); + Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new( + ||self.scenes_with_heights(2), + move|(_, scene, y1, y2), i| { + let h = (y2 - y1) as u16; + let color = scene.color(); + let cell = Self::phat_cell(color, *last_color.read().unwrap(), scene.name.read().unwrap().clone()); + *last_color.write().unwrap() = color; + map_south(y1 as u16, 2, Fill::x(cell)) + } + ))).boxed() + }).into() + } + fn scene_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||"".boxed()).into() + } + pub fn tracks_with_widths (&self) -> impl Iterator { @@ -93,51 +182,11 @@ impl Arranger { Fixed::x((x2 - x1) as u16, Tui::fg(fg, RepeatV(&"·"))))) }) } - fn track_column_headers (&self) -> impl Content + use<'_> { - let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Fixed::y(3, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { - let color = track.color(); - Push::x(scenes_w, Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, - Tui::fg_bg(color.lightest.rgb, color.base.rgb, - Bsp::s(row(color, Tui::bold(true, format!("{}", track.name.read().unwrap()))), - Bsp::s( - row(color, Self::cell_elapsed(track, self.clock().timebase())), - row(color, Self::cell_until_next(track, &self.clock().playhead)) - )))))) - })) - } - fn track_column_inputs (&self) -> impl Content + use<'_> { - let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Fixed::y(1, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { - let w = (x2 - x1) as u16; - let color = track.color(); - let cell = Self::cell_input(track).ok(); - Push::x(scenes_w, map_east(x1 as u16, w, Fixed::x(w, Fixed::y(1, row(color, cell))))) - })) - } - fn track_column_outputs (&self) -> impl Content + use<'_> { - let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Fixed::y(1, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { - let w = (x2 - x1) as u16; - let color = track.color(); - let cell = Self::cell_output(track).ok(); - Push::x(scenes_w, map_east(x1 as u16, w, Fixed::x(w, Fixed::y(1, row(color, cell))))) - })) - } /// name and width of track fn cell_name (track: &ArrangerTrack, _w: usize) -> impl Content { let name = track.name().read().unwrap().clone(); Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) } - fn cell_input (track: &ArrangerTrack) -> Usually> { - Ok(format!("⦗R⦘⦗M⦘"))/*, track.player.midi_ins().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into())))*/ - } - /// output port - fn cell_output (track: &ArrangerTrack) -> Usually> { - Ok(format!("⦗M⦘⦗S⦘"))/*, track.player.midi_outs().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into())))*/ - } /// beats elapsed fn cell_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Content { let mut result = String::new(); @@ -164,29 +213,20 @@ impl Arranger { Some(result) } - pub fn scenes_with_heights (&self) -> impl Iterator { + pub fn scenes_with_heights (&self, h: usize) -> impl Iterator { let mut y = 0; self.scenes.iter().enumerate().map(move|(index, scene)|{ - let data = (index, scene, y, y + 1); - y += 1; + let data = (index, scene, y, y + h); + y += h; data }) } - fn scene_row_headers (&self) -> impl Content + use<'_> { - let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Fill::y(Map::new(||self.scenes_with_heights(), move|(_, scene, y1, y2), i| { - let h = (y2 - y1) as u16; - let color = scene.color(); - let cell = row(color, scene.name.read().unwrap().clone()); - map_south(y1 as u16, 1, cell) - })) - } fn scene_rows (&self) -> impl Content + use<'_> { let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Map::new(||self.scenes_with_heights(), move|(_, scene, y1, y2), i| { + Map::new(||self.scenes_with_heights(1), move|(_, scene, y1, y2), i| { let h = (y2 - y1) as u16; let color = scene.color(); - let cell = Fixed::y(h, Fixed::x(scenes_w, row(color, scene.name.read().unwrap().clone()))); + let cell = Fixed::y(h, Fixed::x(scenes_w, Self::cell(color, scene.name.read().unwrap().clone()))); map_south(y1 as u16, 1, cell) }) } @@ -279,7 +319,7 @@ impl Arranger { fn scene_row_sep <'a> (&'a self) -> impl Content + 'a { let fg = Color::Rgb(255,255,255); - Map::new(move||self.scenes_with_heights(), |_, _|"") + Map::new(move||self.scenes_with_heights(1), |_, _|"") //Map(||rows.iter(), |(_n, _scene, y1, _y2), _i| { //let y = to.area().y() + (y / PPQ) as u16 + 1; //if y >= to.buffer.area.height { break } diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 54fcfc12..404cdb03 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -46,7 +46,6 @@ impl Content for RepeatV<'_> { } fn render (&self, to: &mut TuiOut) { let [x, y, w, h] = to.area().xywh(); - let a = self.0.len(); for y in y..y+h { if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { cell.set_symbol(&self.0); @@ -54,3 +53,19 @@ impl Content for RepeatV<'_> { } } } + +pub struct RepeatH<'a>(pub &'a str); + +impl Content for RepeatH<'_> { + fn layout (&self, to: [u16;4]) -> [u16;4] { + to + } + fn render (&self, to: &mut TuiOut) { + let [x, y, w, h] = to.area().xywh(); + for x in x..x+w { + if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { + cell.set_symbol(&self.0); + } + } + } +}