diff --git a/input/src/lib.rs b/input/src/lib.rs index fa72cce6..3bd0fc5a 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -6,8 +6,8 @@ mod keymap; pub use self::keymap::*; pub(crate) use ::tek_edn::*; /// Standard error trait. pub(crate) use std::error::Error; -///// Standard result type. -//pub(crate) type Usually = Result>; +/// Standard result type. +#[cfg(test)] pub(crate) type Usually = Result>; /// Standard optional result type. pub(crate) type Perhaps = Result, Box>; #[cfg(test)] #[test] fn test_stub_input () -> Usually<()> { diff --git a/tek/src/view.rs b/tek/src/view.rs index 084002ab..5c1113e8 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -47,68 +47,50 @@ impl Tek { .map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool))) } - pub(crate) fn w (&self) -> u16 { self.size.w() as u16 } - pub(crate) fn h (&self) -> u16 { self.size.h() as u16 } - pub(crate) fn w_sidebar (&self) -> u16 { self.w() / if self.is_editing() { 16 } else { 8 } as u16 } - pub(crate) fn row <'a> ( - &'a self, - w: u16, - h: u16, - a: impl Content + 'a, - b: impl Content + 'a, - c: impl Content + 'a, - ) -> impl Content + 'a { - Fixed::y(h, Bsp::a( - Fill::xy(Align::c(Fixed::x(w, Align::x(b)))), - Bsp::a( - Fill::xy(Align::w(Fixed::x(self.w_sidebar() as u16, a))), - Fill::xy(Align::e(Fixed::x(self.w_sidebar() as u16, c))), - ), - )) - } - pub(crate) fn row_top <'a> ( - &'a self, - w: u16, - h: u16, - a: impl Content + 'a, - b: impl Content + 'a, - c: impl Content + 'a, - ) -> impl Content + 'a { - Fixed::y(h, Bsp::a( - Fill::x(Align::n(Fixed::x(w, Align::x(Tui::bg(Reset, b))))), - Bsp::a( - Fill::x(Align::nw(Fixed::x(self.w_sidebar() as u16, Tui::bg(Reset, a)))), - Fill::x(Align::ne(Fixed::x(self.w_sidebar() as u16, Tui::bg(Reset, c)))), - ), - )) - } - pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( - &'a self, fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a - ) -> impl Content + 'a { - Map::new(iter, - move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), - Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, - &connect.info)))))))))} - pub(crate) fn io_connections <'a, T: PortsSizes<'a>> ( - &'a self, fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a - ) -> impl Content + 'a { - Map::new(iter, - move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, Self::wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), - Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, Self::wrap(bg, fg, Fill::x(""))))))))))} - fn heading <'a> ( - &'a self, key: &'a str, label: &'a str, count: usize, - content: impl Content + Send + Sync + 'a - ) -> impl Content + 'a { - let count = format!("{count}"); - Fill::xy(Align::w(Bsp::s(Fill::x(Align::w(button_3(key, label, count, self.is_editing()))), content))) - } - pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { - Bsp::e(Tui::fg_bg(bg, Reset, "▐"), Bsp::w(Tui::fg_bg(bg, Reset, "▌"), - Tui::fg_bg(fg, bg, content))) } +} + +pub(crate) fn row <'a> ( + w: u16, + h: u16, + s: u16, + a: impl Content + 'a, + b: impl Content + 'a, + c: impl Content + 'a, +) -> impl Content + 'a { + Fixed::y(h, Bsp::a( + Fill::xy(Align::c(Fixed::x(w, Align::x(b)))), + Bsp::a( + Fill::xy(Align::w(Fixed::x(s, a))), + Fill::xy(Align::e(Fixed::x(s, c))), + ), + )) +} + +pub(crate) fn row_top <'a> ( + w: u16, + h: u16, + s: u16, + a: impl Content + 'a, + b: impl Content + 'a, + c: impl Content + 'a, +) -> impl Content + 'a { + Fixed::y(h, Bsp::a( + Fill::x(Align::n(Fixed::x(w, Align::x(Tui::bg(Reset, b))))), + Bsp::a( + Fill::x(Align::nw(Fixed::x(s, Tui::bg(Reset, a)))), + Fill::x(Align::ne(Fixed::x(s, Tui::bg(Reset, c)))), + ), + )) +} + +pub(crate) fn wrap ( + bg: Color, + fg: Color, + content: impl Content +) -> impl Content { + Bsp::e(Tui::fg_bg(bg, Reset, "▐"), + Bsp::w(Tui::fg_bg(bg, Reset, "▌"), + Tui::fg_bg(fg, bg, content))) } pub(crate) fn button_2 <'a, K, L> ( @@ -154,26 +136,27 @@ pub(crate) fn button_3 <'a, K, L, V> ( )); Tui::bold(true, Bsp::e(key, label)) } + +fn heading <'a> ( + key: &'a str, + label: &'a str, + count: usize, + content: impl Content + Send + Sync + 'a, + editing: bool, +) -> impl Content + 'a { + let count = format!("{count}"); + Fill::xy(Align::w(Bsp::s(Fill::x(Align::w(button_3(key, label, count, editing))), content))) +} + #[cfg(test)] mod test { use super::*; #[test] fn test_view () { - let app = Tek::default(); - let _ = app.view_editor(); - let _ = app.view_pool(); - let _ = app.w(); - let _ = app.w_sidebar(); - let _ = app.w_tracks_area(); - let _ = app.h(); - let _ = app.h_tracks_area(); - let _ = app.row(0, 0, "", "", ""); - let _ = app.row_top(0, 0, "", "", ""); - //let _ = app.io_ports(Reset, Reset, ||[].iter()); - //let _ = app.io_connections(Reset, Reset, ||[].iter()); - let _ = app.button_2("", "", true); - let _ = app.button_2("", "", false); - let _ = app.button_3("", "", "", true); - let _ = app.button_3("", "", "", false); - let _ = app.heading("", "", 0, ""); - let _ = Tek::wrap(Reset, Reset, ""); + let _ = button_2("", "", true); + let _ = button_2("", "", false); + let _ = button_3("", "", "", true); + let _ = button_3("", "", "", false); + let _ = heading("", "", 0, "", true); + let _ = heading("", "", 0, "", false); + let _ = wrap(Reset, Reset, ""); } } diff --git a/tek/src/view_arranger.rs b/tek/src/view_arranger.rs index 5be59755..7940f5d9 100644 --- a/tek/src/view_arranger.rs +++ b/tek/src/view_arranger.rs @@ -1,14 +1,14 @@ use crate::*; impl Tek { - const TAB: &str = " Tab"; /// Blit the currently visible section of the arranger to the output. /// /// If the arranger is larger than the available display area, /// the scrollbars determine the portion that will be shown. pub fn view_arranger (&self) -> impl Content + use<'_> { - () + () // TODO } + /// Draw the full arranger to the arranger view buffer. /// /// This should happen on changes to the arrangement view @@ -19,27 +19,25 @@ impl Tek { let height = self.h_scenes() + self.h_inputs() + self.h_outputs(); let buffer = Buffer::empty(ratatui::prelude::Rect { x: 0, y: 0, width, height }); let mut output = TuiOut { buffer, area: [0, 0, width, height] }; - let content = Bsp::s(self.view_inputs(), - Bsp::s(self.view_tracks(), - Bsp::n(self.view_outputs(), self.view_scenes()))); - Content::render(&content, &mut output); + Content::render(&Bsp::s(self.view_inputs(), Bsp::s(self.view_tracks(), + Bsp::n(self.view_outputs(), self.view_scenes()))), &mut output); *self.arranger.write().unwrap() = output.buffer; } + /// Display the current scene scroll state. fn scene_scrollbar (&self) -> impl Content + use<'_> { - Fill::y(Fixed::x(1, ScrollbarV { - offset: self.scene_scroll, - length: self.h_tracks_area() as usize, - total: self.h_scenes() as usize, - })) + let offset = self.scene_scroll; + let length = self.h_tracks_area() as usize; + let total = self.h_scenes() as usize; + Fill::y(Fixed::x(1, ScrollbarV { offset, length, total })) } + /// Display the current track scroll state. fn track_scrollbar (&self) -> impl Content + use<'_> { - Fill::x(Fixed::y(1, ScrollbarH { - offset: self.track_scroll, - length: self.w_tracks_area() as usize, - total: self.w_tracks() as usize, - })) + let offset = self.track_scroll; + let length = self.w_tracks_area() as usize; + let total = self.w_tracks() as usize; + Fill::x(Fixed::y(1, ScrollbarH { offset, length, total })) } fn per_track <'a, T: Content + 'a> ( @@ -47,11 +45,16 @@ impl Tek { ) -> impl Content + 'a { self.per_track_top(move|index, track|Fill::y(Align::y(f(index, track)))) } + fn per_track_top <'a, T: Content + 'a> ( &'a self, f: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { let width = self.w_tracks_area(); - let filter = move|(t, track, x1, x2)|if x2 as u16 >= width {None} else {Some((t, track, x1, x2))}; + let filter = move|(t, track, x1, x2)|if x2 as u16 >= width { + None + } else { + Some((t, track, x1, x2)) + }; let tracks = move||self.tracks_sizes().map_while(filter); Align::x(Tui::bg(Reset, Map::new(tracks, move|(index, track, x1, x2), _|{ let width = (x2 - x1) as u16; @@ -64,26 +67,18 @@ impl Tek { pub fn view_tracks (&self) -> impl Content + use<'_> { let w = (self.size.w() as u16).saturating_sub(2 * self.w_sidebar()); + let s = self.w_sidebar() as u16; let data = (self.selected.track().unwrap_or(0), self.tracks().len()); let editing = self.is_editing(); self.fmtd.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); - self.row(w, 1, button_3("t", "track", self.fmtd.read().unwrap().trks.view.clone(), editing), - self.per_track(|t, track|self.view_track_header(t, track)), - button_2("T", "add track", editing)) - } - - fn view_track_header <'a> (&self, t: usize, track: &'a Track) -> impl Content + use<'a> { - let active = self.selected().track() == Some(t); - let name = &track.name; - let fg = track.color.lightest.rgb; - let bg = if active { track.color.light.rgb } else { track.color.base.rgb }; - let bg2 = Reset;//if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset }; - let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) }; - Self::wrap(bg, fg, Tui::bold(true, Fill::x(Align::nw(name)))) + row(w, 1, s, button_3("t", "track", self.fmtd.read().unwrap().trks.view.clone(), editing), + self.per_track(|t, track|track_header(t, track, self.selected().track() == Some(t))), + button_2("T", "add track", editing)) } pub fn view_scenes (&self) -> impl Content + use<'_> { let editing = self.is_editing(); + let s = self.w_sidebar() as u16; let w = self.w_tracks_area(); let w_full = self.w(); let h = self.h_scenes(); @@ -91,7 +86,7 @@ impl Tek { let selected_track = self.selected().track(); let selected_scene = self.selected().scene(); Tui::bg(Reset, Bsp::s(self.track_scrollbar(), Bsp::e(self.scene_scrollbar(), - Fixed::y(self.h_tracks_area(), self.row(self.w_tracks_area(), h, + Fixed::y(self.h_tracks_area(), row(self.w_tracks_area(), h, s, Map::new( move||self.scenes_with_colors(editing, h_area), move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_name( @@ -105,8 +100,13 @@ impl Tek { } fn view_scene_name ( - &self, width: u16, height: u16, offset: u16, - s: usize, scene: &Scene, prev: Option, + &self, + width: u16, + height: u16, + offset: u16, + s: usize, + scene: &Scene, + prev: Option, ) -> impl Content + use<'_> { let bg = scene.color; let fg = scene.color.lightest.rgb; @@ -168,9 +168,11 @@ impl Tek { self.fmtd.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); button_3("S", "add scene", self.fmtd.read().unwrap().scns.view.clone(), editing) } + pub fn view_outputs (&self) -> impl Content + use<'_> { let editing = self.is_editing(); let w = self.w_tracks_area(); + let s = self.w_sidebar() as u16; let fg = Tui::g(224); let nexts = self.per_track_top(|t, track|Either( track.player.next_clip.is_some(), @@ -180,41 +182,46 @@ impl Tek { .map(|clip|clip.read().unwrap().name.clone())) .flatten().as_ref()))), Thunk::new(||Tui::bg(Reset, " ------ ")))); - let nexts = self.row_top(w, 2, Align::ne("Next:"), nexts, ()); + let nexts = row_top(w, 2, s, Align::ne("Next:"), nexts, ()); let froms = self.per_track_top(|_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default(),)))); - let froms = self.row_top(w, 2, Align::ne("From:"), froms, ()); - let ports = self.row_top(w, 1, + let froms = row_top(w, 2, s, Align::ne("From:"), froms, ()); + let ports = row_top(w, 1, s, button_3("o", "midi outs", format!("{}", self.midi_outs.len()), editing), self.per_track_top(move|t, track|{ let mute = false; let solo = false; let mute = if mute { White } else { track.color.darkest.rgb }; let solo = if solo { White } else { track.color.darkest.rgb }; - let bg = if self.selected().track() == Some(t) { track.color.light.rgb } else { track.color.base.rgb }; - let bg2 = if t > 0 { self.tracks()[t].color.base.rgb } else { Reset }; - Self::wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e( + let bg = if self.selected().track() == Some(t) { + track.color.light.rgb + } else { + track.color.base.rgb + }; + let bg2 = if t > 0 { self.tracks()[t].color.base.rgb } else { Reset }; + wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e( Tui::fg_bg(mute, bg, "Play "), Tui::fg_bg(solo, bg, "Solo ")))))}), button_2("O", "add midi out", editing)); - let routes = self.row_top(w, self.h_outputs() - 1, - self.io_ports(fg, Tui::g(32), ||self.outputs_sizes()), - self.per_track_top(move|t, track|self.io_connections( + let routes = row_top(w, self.h_outputs() - 1, s, + io_ports(fg, Tui::g(32), ||self.outputs_sizes()), + self.per_track_top(move|t, track|io_conns( track.color.dark.rgb, track.color.darker.rgb, ||self.outputs_sizes())), ()); - Align::n(Bsp::s(Bsp::s(nexts, froms), Bsp::s(ports, routes))) } + pub fn view_inputs (&self) -> impl Content + use<'_> { let editing = self.is_editing(); let w = (self.size.w() as u16).saturating_sub(2 * self.w_sidebar()); + let s = self.w_sidebar() as u16; let fg = Tui::g(224); - let routes = self.row_top(w, self.h_inputs() - 1, - self.io_ports(fg, Tui::g(32), ||self.inputs_sizes()), - self.per_track_top(move|t, track|self.io_connections( + let routes = row_top(w, self.h_inputs() - 1, s, + io_ports(fg, Tui::g(32), ||self.inputs_sizes()), + self.per_track_top(move|t, track|io_conns( track.color.dark.rgb, track.color.darker.rgb, ||self.inputs_sizes() )), ()); - let ports = self.row_top(w, 1, + let ports = row_top(w, 1, s, button_3("i", "midi ins", format!("{}", self.midi_ins.len()), editing), self.per_track_top(move|t, track|{ let rec = track.player.recording; @@ -227,18 +234,71 @@ impl Tek { track.color.base.rgb }; let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset }; - Self::wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e( + wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e( Tui::fg_bg(rec, bg, "Rec "), Tui::fg_bg(mon, bg, "Mon "))))) }), button_2("I", "add midi in", editing)); Bsp::s( Bsp::s(routes, ports), - self.row_top(w, 2, + row_top(w, 2, s, Bsp::s(Align::e("Input:"), Align::e("Into:")), self.per_track_top(|_, _|Tui::bg(Reset, Align::c(Bsp::s( OctaveVertical::default(), " ------ ")))), ()) ) } + +} + +fn track_header <'a> (t: usize, track: &'a Track, active: bool) -> impl Content + use<'a> { + let name = &track.name; + let fg = track.color.lightest.rgb; + let bg = if active { track.color.light.rgb } else { track.color.base.rgb }; + let bg2 = Reset;//if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset }; + let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) }; + wrap(bg, fg, Tui::bold(true, Fill::x(Align::nw(name)))) +} + +fn io_ports <'a, T: PortsSizes<'a>> ( + fg: Color, + bg: Color, + iter: impl Fn()->T + Send + Sync + 'a +) -> impl Content + 'a { + Map::new(iter, + move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), + Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, + &connect.info))))))))) +} + +fn io_conns <'a, T: PortsSizes<'a>> ( + fg: Color, + bg: Color, + iter: impl Fn()->T + Send + Sync + 'a +) -> impl Content + 'a { + Map::new(iter, + move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), + Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x("")))))))))) +} + +#[cfg(test)] mod test { + use super::*; + #[test] fn test_view_arranger () { + let app = Tek::default(); + let _ = io_ports(Reset, Reset, ||app.inputs_sizes()); + let _ = io_conns(Reset, Reset, ||app.outputs_sizes()); + let _ = app.per_track(|_, _|()); + let _ = app.per_track_top(|_, _|()); + let _ = app.redraw_arranger(); + //let _ = app.view_editor(); + //let _ = app.view_pool(); + //let _ = app.row(0, 0, "", "", ""); + //let _ = app.row_top(0, 0, "", "", ""); + //let _ = app.io_ports(Reset, Reset, ||[].iter()); + //let _ = app.io_conns(Reset, Reset, ||[].iter()); + } } diff --git a/tek/src/view_sizes.rs b/tek/src/view_sizes.rs index 76299516..7bb2146e 100644 --- a/tek/src/view_sizes.rs +++ b/tek/src/view_sizes.rs @@ -6,6 +6,14 @@ impl Tek { pub(crate) const H_SCENE: usize = 2; /// Default editor height. pub(crate) const H_EDITOR: usize = 15; + + /// Width of display + pub(crate) fn w (&self) -> u16 { + self.size.w() as u16 + } + pub(crate) fn w_sidebar (&self) -> u16 { + self.w() / if self.is_editing() { 16 } else { 8 } as u16 + } /// Width taken by all tracks. pub(crate) fn w_tracks (&self) -> u16 { self.tracks_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) @@ -14,6 +22,10 @@ impl Tek { pub(crate) fn w_tracks_area (&self) -> u16 { self.w().saturating_sub(2 * self.w_sidebar()) } + /// Height of display + pub(crate) fn h (&self) -> u16 { + self.size.h() as u16 + } /// Height available to display tracks. pub(crate) fn h_tracks_area (&self) -> u16 { self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 10) @@ -28,6 +40,21 @@ impl Tek { } /// Height taken by all scenes. pub(crate) fn h_scenes (&self) -> u16 { - self.scenes_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last().map(|(_, _, _, y)|y as u16).unwrap_or(0) + self.scenes_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last() + .map(|(_, _, _, y)|y as u16).unwrap_or(0) + } +} +#[cfg(test)] mod test { + use super::*; + #[test] fn test_view_size () { + let app = Tek::default(); + let _ = app.w(); + let _ = app.w_sidebar(); + let _ = app.w_tracks_area(); + let _ = app.h(); + let _ = app.h_tracks_area(); + let _ = app.h_inputs(); + let _ = app.h_outputs(); + let _ = app.h_scenes(); } } diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 740877af..8e49ddf6 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -39,7 +39,7 @@ pub(crate) use std::ffi::OsString; use std::sync::{Arc, RwLock}; struct TestComponent(String); impl Content for TestComponent { - fn content (&self) -> Option> { + fn content (&self) -> impl Render { Some(self.0.as_str()) } }