diff --git a/tek/src/lib.rs b/tek/src/lib.rs index a4ec12da..f00896c0 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -8,9 +8,16 @@ #![feature(trait_alias)] mod cli; pub use self::cli::*; mod model; pub use self::model::*; -mod view; pub use self::view::*; mod keys; pub use self::keys::*; mod audio; pub use self::audio::*; +mod view; pub use self::view::*; +mod view_memo; pub use self::view_memo::*; +mod view_clock; pub use self::view_clock::*; +mod view_meter; pub use self::view_meter::*; +mod view_scene; pub use self::view_scene::*; +mod view_track; pub use self::view_track::*; +mod view_input; pub use self::view_input::*; +mod view_output; pub use self::view_output::*; /// Standard result type. pub type Usually = std::result::Result>; /// Standard optional result type. diff --git a/tek/src/view.rs b/tek/src/view.rs index 615baba5..a0a28069 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -1,8 +1,5 @@ use crate::*; pub(crate) use std::fmt::Write; -mod clip; pub use self::clip::*; -mod input; pub use self::input::*; -mod output; pub use self::output::*; macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { pub(crate) trait $Type<'a> = @@ -14,47 +11,6 @@ def_sizes_iter!(OutputsSizes => JackMidiOut); def_sizes_iter!(PortsSizes => Arc, [PortConnect]); pub(crate) trait ScenesColors<'a> = Iterator>; pub(crate) type SceneColor<'a> = (usize, &'a Scene, usize, usize, Option); - -#[derive(Debug, Default)] struct ViewMemo { value: T, view: Arc> } -impl ViewMemo { - fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } } - fn update (&mut self, newval: T, render: impl Fn(&mut U, &T, &T)->R) -> Option { - if newval != self.value { - let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); - self.value = newval; - return Some(result); - } - None - } -} -#[derive(Debug)] pub(crate) struct ViewCache { - sr: ViewMemo, String>, - buf: ViewMemo, String>, - lat: ViewMemo, String>, - bpm: ViewMemo, String>, - beat: ViewMemo, String>, - time: ViewMemo, String>, - scns: ViewMemo, String>, - trks: ViewMemo, String>, - stop: Arc, - edit: Arc, -} -impl Default for ViewCache { - fn default () -> Self { - Self { - beat: ViewMemo::new(None, String::with_capacity(16)), - time: ViewMemo::new(None, String::with_capacity(16)), - bpm: ViewMemo::new(None, String::with_capacity(16)), - sr: ViewMemo::new(None, String::with_capacity(16)), - buf: ViewMemo::new(None, String::with_capacity(16)), - lat: ViewMemo::new(None, String::with_capacity(16)), - scns: ViewMemo::new(None, String::with_capacity(16)), - trks: ViewMemo::new(None, String::with_capacity(16)), - stop: "⏹".into(), - edit: "edit".into(), - } - } -} view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { ":editor" => (&self.editor).boxed(), ":inputs" => self.view_inputs().boxed(), @@ -79,93 +35,9 @@ provide_num!(u16: |self: Tek| { ":y-samples" => if self.is_editing() { 1 } else { 0 }, }); #[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{$buf.clear();write!($buf, $($rest)*)} } } + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{$buf.clear();write!($buf, $($rest)*)} } +} impl Tek { - fn update_clock (&self) { - let compact = self.size.w() > 80; - let clock = self.clock(); - let rate = clock.timebase.sr.get(); - let chunk = clock.chunk.load(Relaxed) as f64; - let lat = chunk / rate * 1000.; - let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); - let mut fmtd = self.fmtd.write().unwrap(); - fmtd.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); - fmtd.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); - fmtd.sr.update(Some((compact, rate)), |buf,_,_|if compact { - buf.clear(); write!(buf, "{:.1}kHz", rate / 1000.) - } else { - buf.clear(); write!(buf, "{:.0}Hz", rate) - }); - if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { - let pulse = clock.timebase.usecs_to_pulse(now); - let time = now/1000000.; - let bpm = clock.timebase.bpm.get(); - fmtd.beat.update(Some(pulse), - |buf, _, _|{buf.clear();clock.timebase.format_beats_1_to(buf, pulse)}); - fmtd.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); - fmtd.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); - } else { - fmtd.beat.update(None, rewrite!(buf, "-.-.--")); - fmtd.time.update(None, rewrite!(buf, "-.---s")); - fmtd.bpm.update(None, rewrite!(buf, "---.---")); - } - } - fn view_clock (&self) -> impl Content + use<'_> { - self.update_clock(); - let theme = ItemPalette::G[96]; - let fmtd = self.fmtd.read().unwrap(); - Bsp::a( - Fill::xy(Align::w(self.view_play_pause())), - Fill::xy(Align::e(row!( - FieldH(theme, "Sel", self.selected.describe(&self.tracks, &self.scenes)), - FieldH(theme, "SR", fmtd.sr.view.clone()), - FieldH(theme, "Buf", fmtd.buf.view.clone()), - FieldH(theme, "Lat", fmtd.lat.view.clone()), - FieldH(theme, "BPM", fmtd.bpm.view.clone()), - FieldH(theme, "Beat", fmtd.beat.view.clone()), - FieldH(theme, "Time", fmtd.time.view.clone()) - ))) - ) - } - fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content + 'a { - col!( - FieldH(ItemPalette::G[128], label, format!("{:>+9.3}", value)), - Fixed::xy(if value >= 0.0 { 13 } - else if value >= -1.0 { 12 } - else if value >= -2.0 { 11 } - else if value >= -3.0 { 10 } - else if value >= -4.0 { 9 } - else if value >= -6.0 { 8 } - else if value >= -9.0 { 7 } - else if value >= -12.0 { 6 } - else if value >= -15.0 { 5 } - else if value >= -20.0 { 4 } - else if value >= -25.0 { 3 } - else if value >= -30.0 { 2 } - else if value >= -40.0 { 1 } - else { 0 }, 1, Tui::bg(if value >= 0.0 { Red } - else if value >= -3.0 { Yellow } - else { Green }, ()))) - } - fn view_meters (&self, values: &[f32;2]) -> impl Content + use<'_> { - Bsp::s( - format!("L/{:>+9.3}", values[0]), - format!("R/{:>+9.3}", values[1]), - ) - } - fn view_play_pause (&self) -> impl Content + use<'_> { - let playing = self.clock.is_rolling(); - let compact = true;//self.is_editing(); - Tui::bg( - if playing{Rgb(0,128,0)}else{Rgb(128,64,0)}, - Either::new(compact, - Thunk::new(move||Fixed::x(9, Either::new(playing, - Tui::fg(Rgb(0, 255, 0), " PLAYING "), - Tui::fg(Rgb(255, 128, 0), " STOPPED ")))), - Thunk::new(move||Fixed::x(5, Either::new(playing, - Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), - Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))) - } fn view_editor (&self) -> impl Content + use<'_> { self.editor.as_ref() .map(|e|Bsp::e(e.clip_status(), e.edit_status())) @@ -174,82 +46,13 @@ impl Tek { self.pool.as_ref() .map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool))) } -} -impl Tek { - // SIZES ////////////////////////////////////////////////////////////////////////////////////// - fn w (&self) -> u16 { self.size.w() as u16 } - fn h (&self) -> u16 { self.size.h() as u16 } - fn w_sidebar (&self) -> u16 { - self.w() / if self.is_editing() { 16 } else { 8 } as u16 - } - fn w_tracks (&self, editing: bool, bigger: usize) -> u16 { - self.tracks_sizes(editing, bigger).last().map(|(_, _, _, x)|x as u16).unwrap_or(0) - } - fn w_tracks_area (&self) -> u16 { - self.w().saturating_sub(2 * self.w_sidebar()) - } - fn h_tracks_area (&self) -> u16 { - self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 10) - } - fn h_inputs (&self) -> u16 { - 1 + self.inputs_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - fn h_outputs (&self) -> u16 { - 1 + self.outputs_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - fn h_scenes (&self, editing: bool, height: usize, larger: usize) -> u16 { - self.scenes_sizes(editing, height, larger).last().map(|(_, _, _, y)|y as u16).unwrap_or(0) - } - // THINGS WITH SIZES ////////////////////////////////////////////////////////////////////////// - const TRACK_SPACING: usize = 0; - fn tracks_sizes <'a> (&'a self, editing: bool, bigger: usize) -> impl TracksSizes<'a> { - let mut x = 0; - let active = match self.selected() { - Selection::Track(t) if editing => Some(t.saturating_sub(1)), - Selection::Clip(t, _) if editing => Some(t.saturating_sub(1)), - _ => None - }; - self.tracks().iter().enumerate().map(move |(index, track)|{ - let width = if Some(index) == active { bigger } else { track.width.max(8) }; - let data = (index, track, x, x + width); - x += width + Self::TRACK_SPACING; - data - }) - } - fn inputs_sizes (&self) -> impl PortsSizes<'_> { - let mut y = 0; - self.midi_ins.iter().enumerate().map(move|(i, input)|{ - let height = 1 + input.conn().len(); - let data = (i, input.name(), input.conn(), y, y + height); - y += height; - data - }) - } - fn outputs_sizes (&self) -> impl PortsSizes<'_> { - let mut y = 0; - self.midi_outs.iter().enumerate().map(move|(i, output)|{ - let height = 1 + output.conn().len(); - let data = (i, output.name(), output.conn(), y, y + height); - y += height; - data - }) - } - fn scenes_sizes (&self, editing: bool, height: usize, larger: usize) -> impl ScenesSizes<'_> { - let mut y = 0; - let (selected_track, selected_scene) = match self.selected() { - Selection::Clip(t, s) => (Some(t.saturating_sub(1)), Some(s.saturating_sub(1))), - _ => (None, None) - }; - self.scenes().iter().enumerate().map(move|(s, scene)|{ - let active = editing && selected_track.is_some() && selected_scene == Some(s); - let height = if active { larger } else { height }; - let data = (s, scene, y, y + height); - y += height; - data - }) - } - /// COMPONENTS //////////////////////////////////////////////////////////////////////////////// - fn row <'a> ( + + 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 w_tracks_area (&self) -> u16 { self.w().saturating_sub(2 * self.w_sidebar()) } + pub(crate) fn h_tracks_area (&self) -> u16 { self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 10) } + pub(crate) fn row <'a> ( &'a self, w: u16, h: u16, @@ -265,7 +68,7 @@ impl Tek { ), )) } - fn row_top <'a> ( + pub(crate) fn row_top <'a> ( &'a self, w: u16, h: u16, @@ -281,24 +84,7 @@ impl Tek { ), )) } - fn per_track <'a, T: Content + 'a> ( - &'a self, f: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> 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 tracks = move||self.tracks_sizes(self.is_editing(), self.editor_w()).map_while(filter); - Align::x(Tui::bg(Green, Map::new(tracks, move|(index, track, x1, x2), _|{ - let width = (x2 - x1) as u16; - map_east(x1 as u16, width, Fixed::x(width, Tui::fg_bg( - track.color.lightest.rgb, - track.color.base.rgb, - f(index, track)))) }))) } - fn io_ports <'a, T: PortsSizes<'a>> ( + 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, @@ -307,7 +93,7 @@ impl Tek { 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_connections <'a, T: PortsSizes<'a>> ( + 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, @@ -322,13 +108,13 @@ impl Tek { let count = format!("{count}"); Fill::xy(Align::w(Bsp::s(Fill::x(Align::w(self.button3(key, label, count))), content))) } - fn button2 <'a, K, L> (&'a self, key: K, label: L) -> impl Content + 'a + pub(crate) fn button2 <'a, K, L> (&'a self, key: K, label: L) -> impl Content + 'a where K: Content + 'a, L: Content + 'a { let key = Tui::fg_bg(Tui::g(0), Tui::orange(), Bsp::e(Tui::fg_bg(Tui::orange(), Reset, "▐"), Bsp::e(key, Tui::fg(Tui::g(96), "▐")))); Tui::bold(true, Bsp::e(key, When::new(!self.is_editing(), Tui::fg_bg(Tui::g(255), Tui::g(96), label)))) } - fn button3 <'a, K, L, V> (&'a self, key: K, label: L, value: V) -> impl Content + 'a + pub(crate) fn button3 <'a, K, L, V> (&'a self, key: K, label: L, value: V) -> impl Content + 'a where K: Content + 'a, L: Content + 'a, V: Content + 'a { let editing = self.is_editing(); let key = Tui::fg_bg(Tui::g(0), Tui::orange(), @@ -347,7 +133,7 @@ impl Tek { Tui::fg_bg(Tui::g(128), Reset, "▌"), ) ))) } - fn wrap (bg: Color, fg: Color, content: impl Content) -> impl 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))) } } diff --git a/tek/src/view_arranger.edn b/tek/src/view_arranger.edn index d9008a86..72d06f45 100644 --- a/tek/src/view_arranger.edn +++ b/tek/src/view_arranger.edn @@ -1,8 +1,5 @@ (bsp/n (fixed/y 2 :toolbar) - (fill/xy (align/c (bsp/a (fill/xy (align/e :pool)) - (bsp/a - (fill/xy (align/n (bsp/s :inputs :tracks))) - (bsp/a - (fill/xy (align/s :outputs)) - (bsp/s :scenes :scene-add))))))) + (fill/xy (bsp/a + (fill/xy (align/e :pool)) + (bsp/s :inputs (bsp/s :tracks (bsp/n :outputs :scenes)))))) diff --git a/tek/src/view_clock.rs b/tek/src/view_clock.rs new file mode 100644 index 00000000..bbb311cd --- /dev/null +++ b/tek/src/view_clock.rs @@ -0,0 +1,62 @@ +use crate::*; +impl Tek { + fn update_clock (&self) { + let compact = self.size.w() > 80; + let clock = self.clock(); + let rate = clock.timebase.sr.get(); + let chunk = clock.chunk.load(Relaxed) as f64; + let lat = chunk / rate * 1000.; + let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); + let mut fmtd = self.fmtd.write().unwrap(); + fmtd.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); + fmtd.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); + fmtd.sr.update(Some((compact, rate)), |buf,_,_|if compact { + buf.clear(); write!(buf, "{:.1}kHz", rate / 1000.) + } else { + buf.clear(); write!(buf, "{:.0}Hz", rate) + }); + if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { + let pulse = clock.timebase.usecs_to_pulse(now); + let time = now/1000000.; + let bpm = clock.timebase.bpm.get(); + fmtd.beat.update(Some(pulse), + |buf, _, _|{buf.clear();clock.timebase.format_beats_1_to(buf, pulse)}); + fmtd.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); + fmtd.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); + } else { + fmtd.beat.update(None, rewrite!(buf, "-.-.--")); + fmtd.time.update(None, rewrite!(buf, "-.---s")); + fmtd.bpm.update(None, rewrite!(buf, "---.---")); + } + } + pub(crate) fn view_clock (&self) -> impl Content + use<'_> { + self.update_clock(); + let theme = ItemPalette::G[96]; + let fmtd = self.fmtd.read().unwrap(); + Bsp::a( + Fill::xy(Align::w(self.view_play_pause())), + Fill::xy(Align::e(row!( + FieldH(theme, "Sel", self.selected.describe(&self.tracks, &self.scenes)), + FieldH(theme, "SR", fmtd.sr.view.clone()), + FieldH(theme, "Buf", fmtd.buf.view.clone()), + FieldH(theme, "Lat", fmtd.lat.view.clone()), + FieldH(theme, "BPM", fmtd.bpm.view.clone()), + FieldH(theme, "Beat", fmtd.beat.view.clone()), + FieldH(theme, "Time", fmtd.time.view.clone()) + ))) + ) + } + fn view_play_pause (&self) -> impl Content + use<'_> { + let playing = self.clock.is_rolling(); + let compact = true;//self.is_editing(); + Tui::bg( + if playing{Rgb(0,128,0)}else{Rgb(128,64,0)}, + Either::new(compact, + Thunk::new(move||Fixed::x(9, Either::new(playing, + Tui::fg(Rgb(0, 255, 0), " PLAYING "), + Tui::fg(Rgb(255, 128, 0), " STOPPED ")))), + Thunk::new(move||Fixed::x(5, Either::new(playing, + Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), + Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))) + } +} diff --git a/tek/src/view/input.rs b/tek/src/view_input.rs similarity index 79% rename from tek/src/view/input.rs rename to tek/src/view_input.rs index 8e2f8fda..38f4fdce 100644 --- a/tek/src/view/input.rs +++ b/tek/src/view_input.rs @@ -37,4 +37,16 @@ impl Tek { " ------ ")))), ()) ) } + pub(crate) fn h_inputs (&self) -> u16 { + 1 + self.inputs_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + pub(crate) fn inputs_sizes (&self) -> impl PortsSizes<'_> { + let mut y = 0; + self.midi_ins.iter().enumerate().map(move|(i, input)|{ + let height = 1 + input.conn().len(); + let data = (i, input.name(), input.conn(), y, y + height); + y += height; + data + }) + } } diff --git a/tek/src/view_memo.rs b/tek/src/view_memo.rs new file mode 100644 index 00000000..9a364240 --- /dev/null +++ b/tek/src/view_memo.rs @@ -0,0 +1,44 @@ +use crate::*; +#[derive(Debug, Default)] pub(crate) struct ViewMemo { + pub(crate) value: T, + pub(crate) view: Arc> +} +impl ViewMemo { + fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } } + pub(crate) fn update (&mut self, newval: T, render: impl Fn(&mut U, &T, &T)->R) -> Option { + if newval != self.value { + let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); + self.value = newval; + return Some(result); + } + None + } +} +#[derive(Debug)] pub(crate) struct ViewCache { + pub(crate) sr: ViewMemo, String>, + pub(crate) buf: ViewMemo, String>, + pub(crate) lat: ViewMemo, String>, + pub(crate) bpm: ViewMemo, String>, + pub(crate) beat: ViewMemo, String>, + pub(crate) time: ViewMemo, String>, + pub(crate) scns: ViewMemo, String>, + pub(crate) trks: ViewMemo, String>, + pub(crate) stop: Arc, + pub(crate) edit: Arc, +} +impl Default for ViewCache { + fn default () -> Self { + Self { + beat: ViewMemo::new(None, String::with_capacity(16)), + time: ViewMemo::new(None, String::with_capacity(16)), + bpm: ViewMemo::new(None, String::with_capacity(16)), + sr: ViewMemo::new(None, String::with_capacity(16)), + buf: ViewMemo::new(None, String::with_capacity(16)), + lat: ViewMemo::new(None, String::with_capacity(16)), + scns: ViewMemo::new(None, String::with_capacity(16)), + trks: ViewMemo::new(None, String::with_capacity(16)), + stop: "⏹".into(), + edit: "edit".into(), + } + } +} diff --git a/tek/src/view_meter.rs b/tek/src/view_meter.rs new file mode 100644 index 00000000..e9f161c2 --- /dev/null +++ b/tek/src/view_meter.rs @@ -0,0 +1,29 @@ +use crate::*; +impl Tek { + fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content + 'a { + col!( + FieldH(ItemPalette::G[128], label, format!("{:>+9.3}", value)), + Fixed::xy(if value >= 0.0 { 13 } + else if value >= -1.0 { 12 } + else if value >= -2.0 { 11 } + else if value >= -3.0 { 10 } + else if value >= -4.0 { 9 } + else if value >= -6.0 { 8 } + else if value >= -9.0 { 7 } + else if value >= -12.0 { 6 } + else if value >= -15.0 { 5 } + else if value >= -20.0 { 4 } + else if value >= -25.0 { 3 } + else if value >= -30.0 { 2 } + else if value >= -40.0 { 1 } + else { 0 }, 1, Tui::bg(if value >= 0.0 { Red } + else if value >= -3.0 { Yellow } + else { Green }, ()))) + } + fn view_meters (&self, values: &[f32;2]) -> impl Content + use<'_> { + Bsp::s( + format!("L/{:>+9.3}", values[0]), + format!("R/{:>+9.3}", values[1]), + ) + } +} diff --git a/tek/src/view/output.rs b/tek/src/view_output.rs similarity index 78% rename from tek/src/view/output.rs rename to tek/src/view_output.rs index a5489b7d..393af2c5 100644 --- a/tek/src/view/output.rs +++ b/tek/src/view_output.rs @@ -27,4 +27,17 @@ impl Tek { Align::n(Bsp::s(Bsp::s(nexts, froms), Bsp::s(ports, routes))) } + pub(crate) fn h_outputs (&self) -> u16 { + 1 + self.outputs_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + pub(crate) fn outputs_sizes (&self) -> impl PortsSizes<'_> { + let mut y = 0; + self.midi_outs.iter().enumerate().map(move|(i, output)|{ + let height = 1 + output.conn().len(); + let data = (i, output.name(), output.conn(), y, y + height); + y += height; + data + }) + } } + diff --git a/tek/src/view/clip.rs b/tek/src/view_scene.rs similarity index 77% rename from tek/src/view/clip.rs rename to tek/src/view_scene.rs index 27210909..0d55c8cd 100644 --- a/tek/src/view/clip.rs +++ b/tek/src/view_scene.rs @@ -1,22 +1,5 @@ use crate::*; impl Tek { - pub fn view_tracks (&self) -> impl Content + use<'_> { - let w = (self.size.w() as u16).saturating_sub(2 * self.w_sidebar()); - let data = (self.selected.track().unwrap_or(0), self.tracks().len()); - self.fmtd.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); - self.row(w, 1, self.button3("t", "track", self.fmtd.read().unwrap().trks.view.clone()), - self.per_track(|t, track|self.view_track_header(t, track)), - self.button2("T", "add track")) } - fn view_track_header <'a> ( - &self, t: usize, track: &'a Track - ) -> impl Content + use<'a> { - let active = self.selected().track() == Some(t+1); - 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)))) } pub fn view_scenes (&self) -> impl Content + use<'_> { let w_full = self.w(); let h = self.h_tracks_area(); @@ -32,7 +15,7 @@ impl Tek { w_full, (1 + y2 - y1) as u16, y1 as u16, s, scene, prev)), self.per_track(move|t, track|Map::new( move||self.scenes_with_track_colors(editing, h, t), - move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_track( + move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_clip( (1 + y2 - y1) as u16, y1 as u16, scene, prev, s, t, editing, selected_track == Some(t+1), selected_scene))), ())) } fn scenes_with_colors (&self, editing: bool, h: u16) -> impl ScenesColors<'_> { @@ -51,7 +34,7 @@ impl Tek { let bg = scene.color; let fg = scene.color.lightest.rgb; let name = Some(scene.name.clone()); - let cell = self.clip_cell(true, s, &bg, prev, name, " ⯈ ", fg); + let cell = self.view_scene_cell(true, s, &bg, prev, name, " ⯈ ", fg); Fixed::x(width, map_south(offset, height, Fixed::y(height, cell))) } fn scenes_with_track_colors (&self, editing: bool, h: u16, t: usize) -> impl ScenesColors<'_> { self.scenes_sizes(editing, 2, 15).map_while( @@ -64,7 +47,7 @@ impl Tek { .map(|c|c.read().unwrap().color) .unwrap_or(ItemPalette::G[32])) })) }) } - fn view_scene_track ( + fn view_scene_clip ( &self, height: u16, offset: u16, scene: &Scene, prev: Option, s: usize, t: usize, editing: bool, same_track: bool, selected_scene: Option @@ -77,9 +60,9 @@ impl Tek { }; let active = editing && same_track && selected_scene == Some(s+1); let edit = |x|Bsp::b(x, When(active, &self.editor)); - let cell = self.clip_cell(same_track, s, &bg, prev, name, " ⏹ ", fg); + let cell = self.view_scene_cell(same_track, s, &bg, prev, name, " ⏹ ", fg); map_south(offset, height, edit(Fixed::y(height, cell))) } - fn clip_cell <'a> ( + fn view_scene_cell <'a> ( &self, same_track: bool, scene: usize, @@ -114,4 +97,21 @@ impl Tek { let data = (self.selected().scene().unwrap_or(0), self.scenes().len()); self.fmtd.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); self.button3("S", "add scene", self.fmtd.read().unwrap().scns.view.clone()) } + pub(crate) fn h_scenes (&self, editing: bool, height: usize, larger: usize) -> u16 { + self.scenes_sizes(editing, height, larger).last().map(|(_, _, _, y)|y as u16).unwrap_or(0) + } + pub(crate) fn scenes_sizes (&self, editing: bool, height: usize, larger: usize) -> impl ScenesSizes<'_> { + let mut y = 0; + let (selected_track, selected_scene) = match self.selected() { + Selection::Clip(t, s) => (Some(t.saturating_sub(1)), Some(s.saturating_sub(1))), + _ => (None, None) + }; + self.scenes().iter().enumerate().map(move|(s, scene)|{ + let active = editing && selected_track.is_some() && selected_scene == Some(s); + let height = if active { larger } else { height }; + let data = (s, scene, y, y + height); + y += height; + data + }) + } } diff --git a/tek/src/view_track.rs b/tek/src/view_track.rs new file mode 100644 index 00000000..6feb9527 --- /dev/null +++ b/tek/src/view_track.rs @@ -0,0 +1,54 @@ +use crate::*; +impl Tek { + pub fn view_tracks (&self) -> impl Content + use<'_> { + let w = (self.size.w() as u16).saturating_sub(2 * self.w_sidebar()); + let data = (self.selected.track().unwrap_or(0), self.tracks().len()); + self.fmtd.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); + self.row(w, 1, self.button3("t", "track", self.fmtd.read().unwrap().trks.view.clone()), + self.per_track(|t, track|self.view_track_header(t, track)), + self.button2("T", "add track")) } + fn view_track_header <'a> (&self, t: usize, track: &'a Track) -> impl Content + use<'a> { + let active = self.selected().track() == Some(t+1); + 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)))) } + + pub(crate) fn per_track <'a, T: Content + 'a> ( + &'a self, f: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + ) -> impl Content + 'a { + self.per_track_top(move|index, track|Fill::y(Align::y(f(index, track)))) + } + pub(crate) 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 tracks = move||self.tracks_sizes(self.is_editing(), self.editor_w()).map_while(filter); + Align::x(Tui::bg(Green, Map::new(tracks, move|(index, track, x1, x2), _|{ + let width = (x2 - x1) as u16; + map_east(x1 as u16, width, Fixed::x(width, Tui::fg_bg( + track.color.lightest.rgb, + track.color.base.rgb, + f(index, track)))) }))) } + pub(crate) fn w_tracks (&self, editing: bool, bigger: usize) -> u16 { + self.tracks_sizes(editing, bigger).last().map(|(_, _, _, x)|x as u16).unwrap_or(0) + } + fn tracks_sizes <'a> (&'a self, editing: bool, bigger: usize) -> impl TracksSizes<'a> { + let mut x = 0; + let active = match self.selected() { + Selection::Track(t) if editing => Some(t.saturating_sub(1)), + Selection::Clip(t, _) if editing => Some(t.saturating_sub(1)), + _ => None + }; + self.tracks().iter().enumerate().map(move |(index, track)|{ + let width = if Some(index) == active { bigger } else { track.width.max(8) }; + let data = (index, track, x, x + width); + x += width + Self::TRACK_SPACING; + data + }) + } + const TRACK_SPACING: usize = 0; +}