diff --git a/bin/cli_arranger.rs b/bin/cli_arranger.rs index 99d106a6..5de516ab 100644 --- a/bin/cli_arranger.rs +++ b/bin/cli_arranger.rs @@ -15,6 +15,9 @@ pub struct ArrangerCli { /// Number of tracks #[arg(short = 'x', long, default_value_t = 16)] tracks: usize, + /// Width of tracks + #[arg(short = 'w', long, default_value_t = 6)] + track_width: usize, /// Number of scenes #[arg(short, long, default_value_t = 8)] scenes: usize, @@ -51,7 +54,7 @@ fn add_tracks (jack: &JackConnection, app: &mut Arranger, cli: &ArrangerCli) -> let track = app.track_add(None, Some( track_color_1.mix(track_color_2, i as f32 / n as f32).into() ))?; - track.width = 4; + track.width = cli.track_width; let name = track.name.read().unwrap(); track.player.midi_ins.push( jack.register_port(&format!("{}I", &name), MidiIn::default())? diff --git a/layout/src/map.rs b/layout/src/map.rs index b6ece522..236f4bde 100644 --- a/layout/src/map.rs +++ b/layout/src/map.rs @@ -5,9 +5,7 @@ pub fn map_south( item_height: O::Unit, item: impl Content ) -> impl Content { - Push::y(item_offset, - Align::n(Fixed::y(item_height, - Fill::x(item)))) + Push::y(item_offset, Align::n(Fixed::y(item_height, Fill::x(item)))) } pub fn map_east( @@ -15,9 +13,7 @@ pub fn map_east( item_width: O::Unit, item: impl Content ) -> impl Content { - Push::x(item_offset, - Align::w(Fixed::y(item_width, - Fill::y(item)))) + Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) } pub struct Map<'a, A, B, I, F, G>(pub PhantomData<&'a()>, pub F, pub G) where diff --git a/src/arranger.rs b/src/arranger.rs index 0a25b3d1..cbe931d9 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -94,7 +94,7 @@ from_jack!(|jack| Arranger { note_buf: vec![], perf: PerfModel::default(), jack: jack.clone(), - compact: false, + compact: true, } }); //render!(TuiOut: (self: Arranger) => { diff --git a/src/arranger/arranger_tui.rs b/src/arranger/arranger_tui.rs index 4893d40a..7903af3b 100644 --- a/src/arranger/arranger_tui.rs +++ b/src/arranger/arranger_tui.rs @@ -1,20 +1,31 @@ use crate::*; +pub(crate) const HEADER_H: u16 = 0; // 5 +pub(crate) const SCENES_W_OFFSET: u16 = 0; render!(TuiOut: (self: Arranger) => { let scenes = &self.scenes; + let scenes_w = 16.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16); 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(), - Bsp::n( - Fixed::y(20, Outer(border_style_2).enclose(&self.editor)), - Outer(border_style_1).enclose( - Bsp::s(self.track_column_headers(), - Bsp::s(self.track_column_inputs(), - Bsp::s(self.track_column_outputs(), ())))))))))) + 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())), @@ -54,47 +65,79 @@ impl<'a> ArrangerVClips<'a> { } } fn row > (color: ItemPalette, field: T) -> impl Content { - Bsp::e(Tui::fg(color.light.rgb, "▎"), Tui::fg(color.lightest.rgb, field)) + 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; + pub fn tracks_with_widths (&self) + -> impl Iterator + { + Self::tracks_with_widths_static(self.tracks.as_slice()) + } + fn tracks_with_widths_static (tracks: &[ArrangerTrack]) + -> impl Iterator + { + let mut x = 0; + tracks.iter().enumerate().map(move |(index, track)|{ + let data = (index, track, x, x + track.width); + x += track.width; + data + }) + } + fn track_column_separators <'a> (&'a self) -> impl Content + 'a { + let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; + let fg = Color::Rgb(64,64,64); + Map::new(move||self.tracks_with_widths(), move|(_n, _track, x1, x2), _i|{ + Push::x(scenes_w, map_east(x1 as u16, (x2 - x1) as u16, + 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, Push::x(scenes_w, Map::new( - ||Arranger::tracks_with_widths(self.tracks.as_slice()), - |(_, track, x1, x2), i| { - let color = track.color(); - map_east(x1 as u16, (x2 - x1) as u16, Tui::fg_bg(color.lightest.rgb, color.base.rgb, - Bsp::s(format!("{}", track.name.read().unwrap()), + 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( - Self::cell_elapsed(track, self.clock().timebase()), - Self::cell_until_next(track, &self.clock().playhead)))))}))) + 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, Push::x(scenes_w, Map::new( - ||Arranger::tracks_with_widths(self.tracks.as_slice()), - |(_, track, x1, x2), i| { - let color = track.color(); - let input = Self::cell_input(track); - map_east(x1 as u16, (x2 - x1) as u16, Tui::bg(color.base.rgb, - row(color, Self::cell_input(track).ok()))) }))) + 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, Push::x(scenes_w, Map::new( - ||Arranger::tracks_with_widths(self.tracks.as_slice()), - |(_, track, x1, x2), i| { - let (w, h) = (Arranger::TRACK_MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - map_east(x1 as u16, (x2 - x1) as u16, Tui::bg(color.base.rgb, - row(color, Self::cell_output(track).ok()))) }))) + 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(); @@ -120,14 +163,32 @@ impl Arranger { } Some(result) } - fn cell_input (track: &ArrangerTrack) -> Usually> { - Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) + + pub fn scenes_with_heights (&self) -> impl Iterator { + let mut y = 0; + self.scenes.iter().enumerate().map(move|(index, scene)|{ + let data = (index, scene, y, y + 1); + y += 1; + data + }) } - /// output port - fn cell_output (track: &ArrangerTrack) -> Usually> { - Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) + 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| { + 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()))); + map_south(y1 as u16, 1, cell) + }) } pub fn scene_heights (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> { let mut total = 0; @@ -155,7 +216,7 @@ impl Arranger { 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::new(||Arranger::tracks_with_widths(tracks), move|(index, track, x1, x2), _| + let clips = Map::new(||Arranger::tracks_with_widths_static(tracks), move|(index, track, x1, x2), _| Push::x((x2 - x1) as u16, Self::cell_clip(scene, index, track, (x2 - x1) as u16, height)) ); Fixed::y(height, Bsp::e(icon, Bsp::e(name, clips))) @@ -204,14 +265,6 @@ impl Arranger { Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) } - fn track_col_sep <'a> (&'a self) -> impl Content + 'a { - let fg = TuiTheme::separator_fg(false); - let x = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Map::new( - move||Self::tracks_with_widths(self.tracks.as_slice()), - move|(_n, _track, x1, _x2), _i|Push::x(x + x1 as u16, Fill::y(Tui::fg(fg, "▎"))) - ) - } pub fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> { let mut widths = vec![]; let mut total = 0; @@ -223,16 +276,6 @@ impl Arranger { widths.push((0, total)); widths } - pub fn tracks_with_widths (tracks: &[ArrangerTrack]) - -> impl Iterator - { - let mut x = 0; - tracks.iter().enumerate().map(move |(index, track)|{ - let data = (index, track, x, x + track.width); - x += track.width; - data - }) - } fn scene_row_sep <'a> (&'a self) -> impl Content + 'a { let fg = Color::Rgb(255,255,255); @@ -250,16 +293,6 @@ impl Arranger { //} //}) } - pub fn scenes_with_heights (&self) - -> impl Iterator - { - let mut y = 0; - self.scenes.iter().enumerate().map(move|(index, scene)|{ - let data = (index, scene, y, y + 1); - y += 1; - data - }) - } fn cursor (&self) -> impl Content + '_ { let color = self.color; diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 0994cc54..54fcfc12 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -17,3 +17,40 @@ impl Content for String { to.blit(self, to.area.x(), to.area.y(), None) } } + +pub struct Repeat<'a>(pub &'a str); + +impl Content for Repeat<'_> { + fn layout (&self, to: [u16;4]) -> [u16;4] { + to + } + fn render (&self, to: &mut TuiOut) { + let [x, y, w, h] = to.area().xywh(); + let a = self.0.len(); + for (v, y) in (y..y+h).enumerate() { + for (u, x) in (x..x+w).enumerate() { + if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { + let u = u % a; + cell.set_symbol(&self.0[u..u+1]); + } + } + } + } +} + +pub struct RepeatV<'a>(pub &'a str); + +impl Content for RepeatV<'_> { + fn layout (&self, to: [u16;4]) -> [u16;4] { + to + } + 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); + } + } + } +}