From 85507bf27e70ff2a52d2e141e44e7f126838cf63 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 21 Jan 2025 17:07:13 +0100 Subject: [PATCH] down to 5 format macro calls in main view.rs --- tek/src/model.rs | 2 +- tek/src/view.rs | 227 +++++++++++++++++++++++++---------------------- 2 files changed, 123 insertions(+), 106 deletions(-) diff --git a/tek/src/model.rs b/tek/src/model.rs index 945a0d3d..ed085597 100644 --- a/tek/src/model.rs +++ b/tek/src/model.rs @@ -34,7 +34,7 @@ use crate::*; pub keys_scene: SourceIter<'static>, pub keys_mix: SourceIter<'static>, - pub fmtd: ViewCache, + pub(crate) fmtd: Arc>, } has_size!(|self: Tek|&self.size); has_clock!(|self: Tek|self.clock); diff --git a/tek/src/view.rs b/tek/src/view.rs index 7394ea63..d0f94bb6 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -1,41 +1,58 @@ use crate::*; use std::fmt::Write; #[derive(Debug)] pub(crate) struct ViewCache { - beat: Arc>, - time: Arc>, - bpm: Arc>, - sr: Arc>, - buf: Arc>, - lat: Arc>, + 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, } impl Default for ViewCache { fn default () -> Self { Self { - beat: Arc::new(RwLock::new(String::with_capacity(16))), - time: Arc::new(RwLock::new(String::with_capacity(16))), - bpm: Arc::new(RwLock::new(String::with_capacity(16))), - sr: Arc::new(RwLock::new(String::with_capacity(16))), - buf: Arc::new(RwLock::new(String::with_capacity(16))), - lat: Arc::new(RwLock::new(String::with_capacity(16))), + 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(), } } } +#[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()) } } +} +impl ViewMemo { + 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 + } +} view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { - ":editor" => (&self.editor).boxed(), - ":pool" => self.view_pool().boxed(), - ":sample" => self.view_sample(self.is_editing()).boxed(), - ":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(), - ":status" => self.view_editor().boxed(), - ":toolbar" => self.view_clock().boxed(), - ":tracks" => self.view_tracks().boxed(), - ":inputs" => self.view_inputs().boxed(), - ":outputs" => self.view_outputs().boxed(), - ":scenes" => self.view_scenes().boxed(), - ":scene-add" => Fill::x(Align::x(Fixed::x(23, button(" C-a ", format!(" add scene ({}/{})", - self.selected().scene().unwrap_or(0), - self.scenes().len()))))).boxed(), + ":editor" => (&self.editor).boxed(), + ":pool" => self.view_pool().boxed(), + ":sample" => self.view_sample(self.is_editing()).boxed(), + ":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(), + ":status" => self.view_editor().boxed(), + ":toolbar" => self.view_clock().boxed(), + ":tracks" => self.view_tracks().boxed(), + ":track-add" => self.view_track_add().boxed(), + ":inputs" => self.view_inputs().boxed(), + ":outputs" => self.view_outputs().boxed(), + ":scenes" => self.view_scenes().boxed(), + ":scene-add" => self.view_scene_add().boxed(), }); provide_num!(u16: |self: Tek| { ":sidebar-w" => self.w_sidebar(), @@ -44,8 +61,7 @@ provide_num!(u16: |self: Tek| { ":samples-y" => if self.is_editing() { 1 } else { 0 }, ":pool-w" => if self.is_editing() { 5 } else { let w = self.size.w(); - if w > 60 { 20 } else if w > 40 { 15 } else { 10 } - } }); + if w > 60 { 20 } else if w > 40 { 15 } else { 10 } } }); macro_rules! per_track { (|$self:ident,$track:ident,$index:ident|$content:expr) => {{ let tracks = ||$self.tracks_sizes($self.is_editing(), $self.editor_w()); @@ -53,76 +69,70 @@ macro_rules! per_track { let width = (x2 - x1) as u16; let content = Fixed::y(1, $content); let styled = Tui::fg_bg($track.color.lightest.rgb, $track.color.base.rgb, content); - map_east(x1 as u16, width, Fixed::x(width, styled)) - }))).into() - }} -} + map_east(x1 as u16, width, Fixed::x(width, styled)) }))).into() }} } macro_rules! io_header { ($self:ident, $key:expr, $label:expr, $count:expr, $content:expr) => { (move||{ let button = $self.button($key, format!("{:10} ({})", $label, $count)); - Bsp::s(Fill::x(Align::w(button)), $content).boxed() - }).into() - } -} + Bsp::s(Fill::x(Align::w(button)), $content).boxed() }).into() } } +macro_rules! rewrite { + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{$buf.clear();write!($buf, $($rest)*)} } } impl Tek { - fn view_clock (&self) -> impl Content + use<'_> { - Outer(false, Style::default().fg(Tui::g(0))).enclose(row!( - self.view_engine_stats(), " ", - self.view_play_pause(), " ", - self.view_beat_stats(), - )) - } - fn view_beat_stats (&self) -> impl Content + use<'_> { - let compact = self.size.w() > 80; - let clock = self.clock(); - let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); - let mut fmtd_beat = self.fmtd.beat.write().unwrap(); - let mut fmtd_time = self.fmtd.time.write().unwrap(); - let mut fmtd_bpm = self.fmtd.bpm.write().unwrap(); - fmtd_beat.clear(); - fmtd_time.clear(); - fmtd_bpm.clear(); - if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { - clock.timebase.format_beats_1_to(&mut*fmtd_beat, clock.timebase.usecs_to_pulse(now)); - write!(&mut fmtd_time, "{:.3}s", now/1000000.); - write!(&mut fmtd_bpm, "{:.3}", clock.timebase.bpm.get()); - } else { - write!(&mut fmtd_beat, "-.-.--"); - write!(&mut fmtd_time, "-.---s"); - write!(&mut fmtd_bpm, "---.---"); - } - let theme = ItemPalette::G[128]; - Thunk::new(move||Either::new(compact, - row!(FieldH(theme, "BPM", self.fmtd.bpm.clone()), - FieldH(theme, "Beat", self.fmtd.beat.clone()), - FieldH(theme, "Time", self.fmtd.time.clone())), - row!(FieldV(theme, "BPM", self.fmtd.bpm.clone()), - FieldV(theme, "Beat", self.fmtd.beat.clone()), - FieldV(theme, "Time", self.fmtd.time.clone())))) - } - fn view_engine_stats (&self) -> impl Content + use<'_> { + 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); - let mut fmtd_sr = self.fmtd.sr.write().unwrap(); - let mut fmtd_buf = self.fmtd.buf.write().unwrap(); - let mut fmtd_lat = self.fmtd.lat.write().unwrap(); - fmtd_sr.clear(); - write!(&mut fmtd_sr, "{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)}); - fmtd_buf.clear(); - write!(&mut fmtd_buf, "{chunk}"); - fmtd_lat.clear(); - write!(&mut fmtd_lat, "{:.1}ms", chunk as f64 / rate * 1000.); - let theme = ItemPalette::G[128]; - Either::new(compact, - row!(FieldH(theme, "SR", self.fmtd.sr.clone()), - FieldH(theme, "Buf", self.fmtd.buf.clone()), - FieldH(theme, "Lat", self.fmtd.lat.clone())), - row!(FieldV(theme, "SR", self.fmtd.sr.clone()), - FieldV(theme, "Buf", self.fmtd.buf.clone()), - FieldV(theme, "Lat", self.fmtd.lat.clone()))) + 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, _, _|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 compact = self.size.w() > 80; + let theme = ItemPalette::G[96]; + Outer(false, Style::default().fg(Tui::g(0))).enclose(row!( + Thunk::new(move||{ + let fmtd = self.fmtd.read().unwrap(); + Either::new(compact, + row!(FieldH(theme, "SR", fmtd.sr.view.clone()), + FieldH(theme, "Buf", fmtd.buf.view.clone()), + FieldH(theme, "Lat", fmtd.lat.view.clone())), + row!(FieldV(theme, "SR", fmtd.sr.view.clone()), + FieldV(theme, "Buf", fmtd.buf.view.clone()), + FieldV(theme, "Lat", fmtd.lat.view.clone()))) }), + " ", + self.view_play_pause(), + " ", + Thunk::new(move||{ + let fmtd = self.fmtd.read().unwrap(); + Either::new(compact, + row!(FieldH(theme, "BPM", fmtd.bpm.view.clone()), + FieldH(theme, "Beat", fmtd.beat.view.clone()), + FieldH(theme, "Time", fmtd.time.view.clone())), + row!(FieldV(theme, "BPM", fmtd.bpm.view.clone()), + FieldV(theme, "Beat", fmtd.beat.view.clone()), + FieldV(theme, "Time", fmtd.time.view.clone()))) }), + )) } fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content + 'a { col!( @@ -169,11 +179,6 @@ impl Tek { fn view_pool (&self) -> impl Content + use<'_> { self.pool.as_ref().map(|pool|PoolView(self.is_editing(), pool)) } - fn view_scene_add (&self) -> impl Content + use<'_> { - button(" C-a ", format!(" add scene ({}/{})", - self.selected().scene().unwrap_or(0), - self.scenes().len())) - } fn view_row <'a> ( &'a self, w: u16, h: u16, a: impl Content + 'a, b: impl Content + 'a ) -> impl Content + 'a { @@ -185,7 +190,7 @@ impl Tek { fn view_inputs (&self) -> impl Content + use<'_> { let fg = Tui::g(224); let bg = Tui::g(64); - let h = 1 + self.midi_ins.len() as u16; + let h = 1 + self.midi_ins.len() as u16; let header: ThunkBox<_> = io_header!(self, " I ", " midi ins", self.midi_ins.len(), self.midi_ins().get(0).map( move|input: &JackPort|Bsp::s( Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name.clone())))), @@ -228,19 +233,16 @@ impl Tek { } fn view_tracks (&self) -> impl Content + use<'_> { let h = 1; - let add_track = ||self.button(" C-t ", format!(" add track ({}/{})", - self.selected.track().unwrap_or(0), - self.tracks().len())); let header: ThunkBox<_> = - (move||Tui::bg(Tui::g(32), Fill::x(Align::w(add_track()))).boxed()).into(); + (move||Tui::bg(Tui::g(32), Fill::x(Align::w(self.view_track_add()))).boxed()).into(); let cells: ThunkBox<_> = per_track!(|self, track, t|{ 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 = 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) }; - let bs = Style::default().fg(bfg).bg(bg); + let fg = track.color.lightest.rgb; + let bg = if active { track.color.light.rgb } else { track.color.base.rgb }; + let bg2 = 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) }; + let bs = Style::default().fg(bfg).bg(bg); let cell = Bsp::e( Tui::fg_bg(bg, bg2, "▐"), Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::nw(name))))); @@ -248,6 +250,20 @@ impl Tek { }); self.view_row(self.w(), 1, header, cells) } + fn view_track_add (&self) -> impl Content + use<'_> { + 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)); + button(" C-t ", Bsp::e(" add track ", + self.fmtd.read().unwrap().trks.view.clone())) + } + fn view_scene_add (&self) -> impl Content + use<'_> { + 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)); + button(" C-a ", Bsp::e(" add scene ", + self.fmtd.read().unwrap().scns.view.clone())) + } fn view_scenes (&self) -> impl Content + use<'_> { let header: ThunkBox<_> = (move||{ let last_color = Arc::new(RwLock::new(ItemPalette::G[0])); @@ -352,7 +368,8 @@ impl Tek { let w = self.w(); let d = 6 + self.midi_ins.len() + self.midi_outs.len(); let h = self.size.h().saturating_sub(d) as u16; - self.view_row(w, h, header, cells)}) } + self.view_row(w, h, header, cells)}) + } fn w (&self) -> u16 { self.tracks_sizes(self.is_editing(), self.editor_w()) .last()