From 53f443f4bd74619c55ba02578a7836742a6d76e1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 9 Feb 2025 16:49:50 +0100 Subject: [PATCH] wip: more flattening and view_arranger --- tek/src/view.rs | 37 +++++++++------------------- tek/src/view_arranger.edn | 4 ++- tek/src/view_arranger.rs | 46 ++++++++++++++++++++++++---------- tek/src/view_clock.rs | 32 +++++++++++++----------- tek/src/view_memo.rs | 4 ++- tek/src/view_meter.rs | 52 +++++++++++++++++++-------------------- 6 files changed, 94 insertions(+), 81 deletions(-) diff --git a/tek/src/view.rs b/tek/src/view.rs index 5c1113e8..983c6362 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -1,5 +1,10 @@ use crate::*; pub(crate) use std::fmt::Write; +pub(crate) use ::tek_tui::ratatui::prelude::Position; +/// Clear a pre-allocated buffer, then write into it. +#[macro_export] macro_rules! rewrite { + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{$buf.clear();write!($buf, $($rest)*)} } } +/// Define a type alias for iterators of sized items (columns). macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { pub(crate) trait $Type<'a> = @@ -9,13 +14,13 @@ def_sizes_iter!(TracksSizes => Track); def_sizes_iter!(InputsSizes => JackMidiIn); 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); +pub(crate) trait ScenesColors<'a> = Iterator>; +pub(crate) type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { - ":editor" => (&self.editor).boxed(), + ":arranger" => self.view_arranger().boxed(), + ":editor" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(), ":inputs" => self.view_inputs().boxed(), ":outputs" => self.view_outputs().boxed(), - ":pool" => self.view_pool().boxed(), ":sample" => ().boxed(),//self.view_sample(self.is_editing()).boxed(), ":sampler" => ().boxed(),//self.view_sampler(self.is_editing(), &self.editor).boxed(), ":scene-add" => self.view_scene_add().boxed(), @@ -23,6 +28,9 @@ view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { ":transport" => self.view_transport().boxed(), ":status" => self.view_status().boxed(), ":tracks" => self.view_tracks().boxed(), + ":pool" => self.pool.as_ref() + .map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool))) + .boxed(), }); provide_num!(u16: |self: Tek| { ":h-ins" => self.h_inputs(), @@ -34,21 +42,6 @@ provide_num!(u16: |self: Tek| { ":y-outs" => (self.size.h() as u16).saturating_sub(self.h_outputs() + 1), ":y-samples" => if self.is_editing() { 1 } else { 0 }, }); -#[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{$buf.clear();write!($buf, $($rest)*)} } -} -impl Tek { - fn view_editor (&self) -> impl Content + use<'_> { - self.editor.as_ref() - .map(|e|Bsp::e(e.clip_status(), e.edit_status())) - } - fn view_pool (&self) -> impl Content + use<'_> { - self.pool.as_ref() - .map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool))) - } - -} - pub(crate) fn row <'a> ( w: u16, h: u16, @@ -65,7 +58,6 @@ pub(crate) fn row <'a> ( ), )) } - pub(crate) fn row_top <'a> ( w: u16, h: u16, @@ -82,7 +74,6 @@ pub(crate) fn row_top <'a> ( ), )) } - pub(crate) fn wrap ( bg: Color, fg: Color, @@ -92,7 +83,6 @@ pub(crate) fn wrap ( Bsp::w(Tui::fg_bg(bg, Reset, "▌"), Tui::fg_bg(fg, bg, content))) } - pub(crate) fn button_2 <'a, K, L> ( key: K, label: L, @@ -108,7 +98,6 @@ pub(crate) fn button_2 <'a, K, L> ( let label = When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label)); Tui::bold(true, Bsp::e(key, label)) } - pub(crate) fn button_3 <'a, K, L, V> ( key: K, label: L, @@ -136,7 +125,6 @@ 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, @@ -147,7 +135,6 @@ fn heading <'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 () { diff --git a/tek/src/view_arranger.edn b/tek/src/view_arranger.edn index a279383a..f1394971 100644 --- a/tek/src/view_arranger.edn +++ b/tek/src/view_arranger.edn @@ -1 +1,3 @@ -(bsp/n (fixed/y 1 :transport) (bsp/s (fixed/y 1 :status) (fill/xy (bsp/a (fill/xy (align/e :pool)) :arranger)))) +(bsp/n (fixed/y 1 :transport) + (bsp/s (fixed/y 1 :status) + (fill/xy (bsp/a (fill/xy (align/e :pool)) :arranger)))) diff --git a/tek/src/view_arranger.rs b/tek/src/view_arranger.rs index 7940f5d9..7c0848c6 100644 --- a/tek/src/view_arranger.rs +++ b/tek/src/view_arranger.rs @@ -1,19 +1,11 @@ use crate::*; impl Tek { - /// 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 - /// other than scrolling. Scrolling should just determine - /// which part of the arranger buffer to blit to output. + /// This should happen on changes to the arrangement contents, + /// i.e. not on scroll. Scrolling just determines which + /// part of the arranger buffer to blit in [view_arranger] pub fn redraw_arranger (&self) { let width = self.w_tracks(); let height = self.h_scenes() + self.h_inputs() + self.h_outputs(); @@ -24,6 +16,34 @@ impl Tek { *self.arranger.write().unwrap() = output.buffer; } + /// Blit the currently visible section of the arranger view buffer to the output. + /// + /// If the arranger is larger than the available display area, + /// the scrollbars determine the portion that will be shown. + /// + /// This function is called on every frame, but is relatively cheap + /// as the rendering logic of [redraw_arranger] is only invoked on + /// changes to the content. + pub fn view_arranger (&self) -> impl Content + use<'_> { + ThunkRender::new(move|to: &mut TuiOut|{ + let [x0, y0, w, h] = to.area().xywh(); + let source = self.arranger.read().unwrap(); + for (source_x, target_x) in (x0..x0+w).enumerate() { + for (source_y, target_y) in (y0..y0+h).enumerate() { + let source_pos = Position::from((source_x as u16, source_y as u16)); + let target_pos = Position::from((target_x, target_y)); + if let Some(target) = to.buffer.cell_mut(target_pos) { + if let Some(source) = source.cell(source_pos) { + *target = source.clone(); + } + } + } + } + // TODO + () + }) + } + /// Display the current scene scroll state. fn scene_scrollbar (&self) -> impl Content + use<'_> { let offset = self.scene_scroll; @@ -89,11 +109,11 @@ impl Tek { 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( + move|(s, scene, y1, y2, prev): SceneWithColor, _|self.view_scene_name( 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_area, t), - move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_clip( + move|(s, scene, y1, y2, prev): SceneWithColor, _|self.view_scene_clip( w, (1 + y2 - y1) as u16, y1 as u16, scene, prev, s, t, editing, selected_track == Some(t), selected_scene))), () ))))) diff --git a/tek/src/view_clock.rs b/tek/src/view_clock.rs index f51c9127..2de619cc 100644 --- a/tek/src/view_clock.rs +++ b/tek/src/view_clock.rs @@ -34,7 +34,7 @@ impl Tek { let theme = ItemPalette::G[96]; let fmtd = self.fmtd.read().unwrap(); Tui::bg(Black, row!(Bsp::a( - Fill::xy(Align::w(self.view_play_pause())), + Fill::xy(Align::w(button_play_pause(self.clock.is_rolling()))), Fill::xy(Align::e(row!( FieldH(theme, "BPM", fmtd.bpm.view.clone()), FieldH(theme, "Beat", fmtd.beat.view.clone()), @@ -47,7 +47,9 @@ impl Tek { let theme = ItemPalette::G[96]; let fmtd = self.fmtd.read().unwrap(); Tui::bg(Black, row!(Bsp::a( - Fill::xy(Align::w(FieldH(theme, "Selected", self.selected.describe(&self.tracks, &self.scenes)))), + Fill::xy(Align::w( + FieldH(theme, "Selected", self.selected.describe(&self.tracks, &self.scenes)) + )), Fill::xy(Align::e(row!( FieldH(theme, "SR", fmtd.sr.view.clone()), FieldH(theme, "Buf", fmtd.buf.view.clone()), @@ -55,17 +57,17 @@ impl Tek { ))) ))) } - 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 button_play_pause (playing: bool) -> impl Content { + 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_memo.rs b/tek/src/view_memo.rs index 9a364240..55a2ecf8 100644 --- a/tek/src/view_memo.rs +++ b/tek/src/view_memo.rs @@ -4,7 +4,9 @@ use crate::*; pub(crate) view: Arc> } impl ViewMemo { - fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } } + 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); diff --git a/tek/src/view_meter.rs b/tek/src/view_meter.rs index e9f161c2..a68128c6 100644 --- a/tek/src/view_meter.rs +++ b/tek/src/view_meter.rs @@ -1,29 +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]), - ) - } +} +fn view_meter <'a> (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 (values: &[f32;2]) -> impl Content + use<'_> { + Bsp::s( + format!("L/{:>+9.3}", values[0]), + format!("R/{:>+9.3}", values[1]), + ) }