From b718e54d332366e87e4a266fec7422454c4a6770 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 21:25:02 +0100 Subject: [PATCH] wip: big flat pt.11, down to 12, update literal render macro --- engine/src/output.rs | 135 +++++++++++++++++++--------- src/arranger/arranger_v/v_cursor.rs | 110 ++++++++++++----------- src/arranger/arranger_v/v_sep.rs | 42 +++++---- src/lib.rs | 4 +- src/piano_h/piano_h_cursor.rs | 52 +++++------ src/piano_h/piano_h_keys.rs | 8 +- src/piano_h/piano_h_notes.rs | 66 ++++++-------- src/piano_h/piano_h_time.rs | 25 +++--- src/sampler/sample_viewer.rs | 99 +++++++++++--------- 9 files changed, 302 insertions(+), 239 deletions(-) diff --git a/engine/src/output.rs b/engine/src/output.rs index c17e849b..ff2d4a16 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -11,27 +11,88 @@ pub trait Output { fn render_in (&mut self, area: E::Area, widget: &impl Render) -> Usually<()>; } +/// Something that can be represented by a renderable component. +pub trait Content: Send + Sync { + fn content (&self) -> Option>; +} +impl> Content for &C { + fn content (&self) -> Option> { + (*self).content() + } +} + +/// Something that writes to an [Output]. +pub trait Render: Send + Sync { + /// Minimum size to use + fn min_size (&self, _: E::Size) -> Perhaps { + Ok(None) + } + /// Draw to output render target + fn render (&self, _: &mut E::Output) -> Usually<()> { + Ok(()) + } +} +impl Render for &dyn Render { + fn min_size (&self, to: E::Size) -> Perhaps { (*self).min_size(to) } + fn render (&self, to: &mut E::Output) -> Usually<()> { (*self).render(to) } +} +impl> Render for &R { + fn min_size (&self, to: E::Size) -> Perhaps { (*self).min_size(to) } + fn render (&self, to: &mut E::Output) -> Usually<()> { (*self).render(to) } +} +impl> Render for Option { + fn min_size (&self, to: E::Size) -> Perhaps { + if let Some(content) = self { + content.min_size(to) + } else { + Ok(None) + } + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + if let Some(content) = self { + content.render(to)?; + } + Ok(()) + } +} + /// Define custom content for a struct. +/// +/// This code wires the `Content` and `Render` traits together, +/// since the only way to have the cake and eat it too is by +/// implementing both traits for a given renderable. #[macro_export] macro_rules! render { - // Implement for all engines + // Implement from [Content] for all [Engine]s (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => { impl Content for $Struct $(<$($L,)* E, $($T),*>)? { - fn content (&$self) -> Option> { - Some($cb) - } + fn content (&$self) -> Option> { Some($cb) } } impl Render for $Struct $(<$($L,)* E, $($T),*>)? { fn min_size (&self, to: E::Size) -> Perhaps { - self.content().map(|content|content.min_size(to)).unwrap_or(Ok(None)) + self.content().unwrap().min_size(to) } fn render (&self, to: &mut E::Output) -> Usually<()> { - self.content().map(|content|content.render(to)).unwrap_or(Ok(())) + self.content().unwrap().render(to) } } }; - // Implement for a specific engine + // Implement from [min_size] and [render] callbacks for all engines + ($Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)? + |$self1:ident, $to1:ident|$min_size:expr, + |$self2:ident, $to2:ident|$render:expr) => + { + impl Content for $Struct $(<$($L,)* E, $($T),*>)? { + fn content (&self) -> Option> { Some(self) } + } + impl Render for $Struct $(<$($L,)* E, $($T),*>)? { + fn min_size (&$self1, $to1: E::Size) -> Perhaps { $min_size } + fn render (&$self2, $to2: &mut E::Output) -> Usually<()> { $render } + } + }; + + // Implement from [Content] for a particular [Engine] (<$E:ty>|$self:ident:$Struct:ident$(< $($($L:lifetime),+)? $($($T:ident$(:$U:path)?),+)? @@ -61,45 +122,35 @@ pub trait Output { self.content().map(|content|content.render(to)).unwrap_or(Ok(())) } } + }; + + // Implement from [min_size] and [render] callbacks for a particular [Engine] + (<$E:ty>($self:ident:$Struct:ident$(<$($L:lifetime),*$(,$T:ident$(:$U:path)?)*$(,)?>)?) + |$to1:ident|$min_size:expr, |$to2:ident|$render:expr) => + { + impl $(< + $($L),* $($($T$(:$U)?),+)? + >)? Content<$E> for $Struct $(< + $($L),* $($($T),+)? + >)? { + fn content (&self) -> Option> { Some(self) } + } + impl $(< + $($L),* $($($T$(:$U)?),+)? + >)? Render<$E> for $Struct $(< + $($L),* $($($T),+)? + >)? { + fn min_size (&$self, $to1: <$E as Engine>::Size) -> Perhaps<<$E as Engine>::Size> { + $min_size + } + fn render (&$self, $to2: &mut <$E as Engine>::Output) -> Usually<()> { + $render + } + } } } -/// Write content to output buffer. -pub trait Render: Send + Sync { - /// Minimum size to use - fn min_size (&self, _: E::Size) -> Perhaps { - Ok(None) - } - /// Draw to output render target - fn render (&self, _: &mut E::Output) -> Usually<()> { - Ok(()) - } -} - -impl Render for &dyn Render {} -//impl Render for &mut dyn Render {} -//impl Render for Box> {} -impl> Render for &R {} -//impl> Render for &mut R {} -impl> Render for Option {} -//impl> Render for Arc {} -//impl> Render for Mutex {} -//impl> Render for RwLock {} - -/// Something that can be represented by a renderable component. -pub trait Content: Send + Sync { - fn content (&self) -> Option>; -} - -//impl Content for &dyn Render {} -//impl Content for &mut dyn Render {} -//impl Content for Box> {} -impl> Content for &C { - fn content (&self) -> Option> { - (*self).content() - } -} //impl> Content for &mut C {} //impl> Content for Option {} //impl> Content for Arc {} diff --git a/src/arranger/arranger_v/v_cursor.rs b/src/arranger/arranger_v/v_cursor.rs index 0dcd85e2..79474951 100644 --- a/src/arranger/arranger_v/v_cursor.rs +++ b/src/arranger/arranger_v/v_cursor.rs @@ -25,57 +25,59 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self { }), }); -render!(|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{ - let area = to.area(); - let focused = true; - let selected = self.selected; - let get_track_area = |t: usize| [ - self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(), - self.cols[t].0 as u16, area.h(), - ]; - let get_scene_area = |s: usize| [ - area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16, - area.w(), (self.rows[s].0 / PPQ) as u16 - ]; - let get_clip_area = |t: usize, s: usize| [ - (self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1), - HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16, - self.cols[t].0 as u16 + 2, - (self.rows[s].0 / PPQ) as u16 - ]; - let mut track_area: Option<[u16;4]> = None; - let mut scene_area: Option<[u16;4]> = None; - let mut clip_area: Option<[u16;4]> = None; - let area = match selected { - ArrangerSelection::Mix => area, - ArrangerSelection::Track(t) => { - track_area = Some(get_track_area(t)); - area - }, - ArrangerSelection::Scene(s) => { - scene_area = Some(get_scene_area(s)); - area - }, - ArrangerSelection::Clip(t, s) => { - track_area = Some(get_track_area(t)); - scene_area = Some(get_scene_area(s)); - clip_area = Some(get_clip_area(t, s)); - area - }, - }; - let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0); - if let Some([x, y, width, height]) = track_area { - to.fill_fg([x, y, 1, height], bg); - to.fill_fg([x + width, y, 1, height], bg); - } - if let Some([_, y, _, height]) = scene_area { - to.fill_ul([area.x(), y - 1, area.w(), 1], bg); - to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); - } - Ok(if focused { - to.render_in(if let Some(clip_area) = clip_area { clip_area } - else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) } - else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) } - else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)? - }) -})); +render!((self: ArrangerVCursor) + |layout|Ok([0, 0]), + |render|{ + let area = render.area(); + let focused = true; + let selected = self.selected; + let get_track_area = |t: usize| [ + self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(), + self.cols[t].0 as u16, area.h(), + ]; + let get_scene_area = |s: usize| [ + area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16, + area.w(), (self.rows[s].0 / PPQ) as u16 + ]; + let get_clip_area = |t: usize, s: usize| [ + (self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1), + HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16, + self.cols[t].0 as u16 + 2, + (self.rows[s].0 / PPQ) as u16 + ]; + let mut track_area: Option<[u16;4]> = None; + let mut scene_area: Option<[u16;4]> = None; + let mut clip_area: Option<[u16;4]> = None; + let area = match selected { + ArrangerSelection::Mix => area, + ArrangerSelection::Track(t) => { + track_area = Some(get_track_area(t)); + area + }, + ArrangerSelection::Scene(s) => { + scene_area = Some(get_scene_area(s)); + area + }, + ArrangerSelection::Clip(t, s) => { + track_area = Some(get_track_area(t)); + scene_area = Some(get_scene_area(s)); + clip_area = Some(get_clip_area(t, s)); + area + }, + }; + let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0); + if let Some([x, y, width, height]) = track_area { + render.fill_fg([x, y, 1, height], bg); + render.fill_fg([x + width, y, 1, height], bg); + } + if let Some([_, y, _, height]) = scene_area { + render.fill_ul([area.x(), y - 1, area.w(), 1], bg); + render.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); + } + Ok(if focused { + render.render_in(if let Some(clip_area) = clip_area { clip_area } + else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) } + else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) } + else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)? + }) + }); diff --git a/src/arranger/arranger_v/v_sep.rs b/src/arranger/arranger_v/v_sep.rs index f895e27e..ddf26692 100644 --- a/src/arranger/arranger_v/v_sep.rs +++ b/src/arranger/arranger_v/v_sep.rs @@ -6,43 +6,41 @@ pub struct ArrangerVColSep { cols: Vec<(usize, usize)>, scenes_w: u16 } - from!(|state:&ArrangerTui|ArrangerVColSep = Self { fg: TuiTheme::separator_fg(false), cols: ArrangerTrack::widths(&state.tracks), scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16, }); - -render!(|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{ - let style = Some(Style::default().fg(self.fg)); - Ok(for x in self.cols.iter().map(|col|col.1) { - let x = self.scenes_w + to.area().x() + x as u16; - for y in to.area().y()..to.area().y2() { - to.blit(&"▎", x, y, style); - } - }) -})); +render!((self: ArrangerVColSep) + |layout|Ok(Some([0, 0])), + |render|{ + let style = Some(Style::default().fg(self.fg)); + Ok(for x in self.cols.iter().map(|col|col.1) { + let x = self.scenes_w + render.area().x() + x as u16; + for y in render.area().y()..render.area().y2() { + render.blit(&"▎", x, y, style); + } + }) + }); pub struct ArrangerVRowSep { fg: Color, rows: Vec<(usize, usize)>, } - from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self { fg: TuiTheme::separator_fg(false), rows: ArrangerScene::ppqs(&args.0.scenes, args.1), }); - -render!(|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{ - Ok(for y in self.rows.iter().map(|row|row.1) { - let y = to.area().y() + (y / PPQ) as u16 + 1; - if y >= to.buffer.area.height { break } - for x in to.area().x()..to.area().x2().saturating_sub(2) { - if x < to.buffer.area.x && y < to.buffer.area.y { - let cell = to.buffer.get_mut(x, y); +render!((self: ArrangerVRowSep) + |layout|Ok(Some([0, 0])), + |render|Ok(for y in self.rows.iter().map(|row|row.1) { + let y = render.area().y() + (y / PPQ) as u16 + 1; + if y >= render.buffer.area.height { break } + for x in render.area().x()..render.area().x2().saturating_sub(2) { + if x < render.buffer.area.x && y < render.buffer.area.y { + let cell = render.buffer.get_mut(x, y); cell.modifier = Modifier::UNDERLINED; cell.underline_color = self.fg; } } - }) -})); + })); diff --git a/src/lib.rs b/src/lib.rs index 3f60a456..92302e94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,8 @@ pub(crate) use ::tek_layout::{ tek_engine::{ Usually, Perhaps, Engine, Size, Area, - Content, Render, render, - Handle, handle, kexp, key_pat, key_event_pat, key_event_expr, + Output, Content, Render, render, + Input, Handle, handle, kexp, key_pat, key_event_pat, key_event_expr, Tui, TuiInput, TuiOutput, crossterm::{ self, diff --git a/src/piano_h/piano_h_cursor.rs b/src/piano_h/piano_h_cursor.rs index 2f5ef130..ffaf0e5d 100644 --- a/src/piano_h/piano_h_cursor.rs +++ b/src/piano_h/piano_h_cursor.rs @@ -2,32 +2,34 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalCursor<'a>|render(|to: &mut TuiOutput|Ok({ - let style = Some(Style::default().fg(self.0.color.lightest.rgb)); - let note_hi = self.0.note_hi(); - let note_len = self.0.note_len(); - let note_lo = self.0.note_lo().get(); - let note_point = self.0.note_point(); - let time_point = self.0.time_point(); - let time_start = self.0.time_start().get(); - let time_zoom = self.0.time_zoom().get(); - let [x0, y0, w, _] = to.area().xywh(); - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - if note == note_point { - for x in 0..w { - let screen_x = x0 + x; - let time_1 = time_start + x as usize * time_zoom; - let time_2 = time_1 + time_zoom; - if time_1 <= time_point && time_point < time_2 { - to.blit(&"█", screen_x, screen_y, style); - let tail = note_len as u16 / time_zoom as u16; - for x_tail in (screen_x + 1)..(screen_x + tail) { - to.blit(&"▂", x_tail, screen_y, style); +render!((self: PianoHorizontalCursor<'a>) + |layout|Ok([0, 0]), + |render|{ + let style = Some(Style::default().fg(self.0.color.lightest.rgb)); + let note_hi = self.0.note_hi(); + let note_len = self.0.note_len(); + let note_lo = self.0.note_lo().get(); + let note_point = self.0.note_point(); + let time_point = self.0.time_point(); + let time_start = self.0.time_start().get(); + let time_zoom = self.0.time_zoom().get(); + let [x0, y0, w, _] = render.area().xywh(); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + if note == note_point { + for x in 0..w { + let screen_x = x0 + x; + let time_1 = time_start + x as usize * time_zoom; + let time_2 = time_1 + time_zoom; + if time_1 <= time_point && time_point < time_2 { + render.blit(&"█", screen_x, screen_y, style); + let tail = note_len as u16 / time_zoom as u16; + for x_tail in (screen_x + 1)..(screen_x + tail) { + render.blit(&"▂", x_tail, screen_y, style); + } + break } - break } + break } - break } - } -}))); + }); diff --git a/src/piano_h/piano_h_keys.rs b/src/piano_h/piano_h_keys.rs index 2dbab758..386c5cd8 100644 --- a/src/piano_h/piano_h_keys.rs +++ b/src/piano_h/piano_h_keys.rs @@ -2,12 +2,18 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); + +render!((self: PianoHorizontalKeys<'a>) + |layout|Ok(Some([0, 0])), + |render|Ok(render_keys_v(render, self))); + has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base); + impl<'a> NoteRange for PianoHorizontalKeys<'a> { fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() } fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() } } + impl<'a> NotePoint for PianoHorizontalKeys<'a> { fn note_len (&self) -> usize { self.0.note_len() } fn set_note_len (&self, x: usize) { self.0.set_note_len(x) } diff --git a/src/piano_h/piano_h_notes.rs b/src/piano_h/piano_h_notes.rs index 7b9910a7..e2ff9012 100644 --- a/src/piano_h/piano_h_notes.rs +++ b/src/piano_h/piano_h_notes.rs @@ -2,45 +2,33 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalNotes<'a>|render(|to: &mut TuiOutput|Ok({ - let time_start = self.0.time_start().get(); - let note_lo = self.0.note_lo().get(); - let note_hi = self.0.note_hi(); - let note_point = self.0.note_point(); - let source = &self.0.buffer; - let [x0, y0, w, h] = to.area().xywh(); - if h as usize != self.0.note_axis().get() { - panic!("area height mismatch"); - } - for (area_x, screen_x) in (x0..x0+w).enumerate() { - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - //if area_x % 10 == 0 { - //to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None); - //} - let source_x = time_start + area_x; - let source_y = note_hi - area_y; - ////// TODO: enable loop rollover: - //////let source_x = (time_start + area_x) % source.width.max(1); - //////let source_y = (note_hi - area_y) % source.height.max(1); - let is_in_x = source_x < source.width; - let is_in_y = source_y < source.height; - if is_in_x && is_in_y { - if let Some(source_cell) = source.get(source_x, source_y) { - *to.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); + +render!((self: PianoHorizontalNotes<'a>) + |layout|Ok(Some([0, 0])), + |render|Ok({ + let time_start = self.0.time_start().get(); + let note_lo = self.0.note_lo().get(); + let note_hi = self.0.note_hi(); + let note_point = self.0.note_point(); + let source = &self.0.buffer; + let [x0, y0, w, h] = render.area().xywh(); + if h as usize != self.0.note_axis().get() { + panic!("area height mismatch"); + } + for (area_x, screen_x) in (x0..x0+w).enumerate() { + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + let source_x = time_start + area_x; + let source_y = note_hi - area_y; + // TODO: enable loop rollover: + //let source_x = (time_start + area_x) % source.width.max(1); + //let source_y = (note_hi - area_y) % source.height.max(1); + let is_in_x = source_x < source.width; + let is_in_y = source_y < source.height; + if is_in_x && is_in_y { + if let Some(source_cell) = source.get(source_x, source_y) { + *render.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); + } } } } - } - //let debug = true; - //if debug { - //let x0=20+x0; - //to.blit(&format!("KYP "), x0, y0, None); - //to.blit(&format!("x0={x0} "), x0, y0+1, None); - //to.blit(&format!("y0={y0} "), x0, y0+2, None); - //to.blit(&format!("note_lo={note_lo}, {} ", to_note_name(note_lo)), x0, y0+3, None); - //to.blit(&format!("note_hi={note_hi}, {} ", to_note_name(note_hi)), x0, y0+4, None); - //to.blit(&format!("note_point={note_point}, {} ", to_note_name(note_point)), x0, y0+5, None); - //to.blit(&format!("time_start={time_start} "), x0, y0+6, None); - //return Ok(()); - //} -}))); + })); diff --git a/src/piano_h/piano_h_time.rs b/src/piano_h/piano_h_time.rs index 1b76ecb0..7c9fa3ca 100644 --- a/src/piano_h/piano_h_time.rs +++ b/src/piano_h/piano_h_time.rs @@ -1,18 +1,21 @@ use crate::*; pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalTimeline<'a>|render(|to: &mut TuiOutput|{ - let [x, y, w, h] = to.area(); - let style = Some(Style::default().dim()); - let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); - for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { - let t = area_x as usize * self.0.time_zoom().get(); - if t < length { - to.blit(&"|", screen_x, y, style); +render!((self: PianoHorizontalTimeline<'a>) + |layout|Ok(Some([0, 0])), + |render|{ + let [x, y, w, h] = render.area(); + let style = Some(Style::default().dim()); + let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); + for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { + let t = area_x as usize * self.0.time_zoom().get(); + if t < length { + render.blit(&"|", screen_x, y, style); + } } - } - Ok(()) -})); + Ok(()) + }); + //Tui::fg_bg( //self.0.color.lightest.rgb, //self.0.color.darkest.rgb, diff --git a/src/sampler/sample_viewer.rs b/src/sampler/sample_viewer.rs index 12ddb3e2..c6b2e1c3 100644 --- a/src/sampler/sample_viewer.rs +++ b/src/sampler/sample_viewer.rs @@ -5,48 +5,61 @@ use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points, Line}}}; const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)]; pub struct SampleViewer(pub Option>>); -render!(|self: SampleViewer|render(|to: &mut TuiOutput|{ - let [x, y, width, height] = to.area(); - let area = Rect { x, y, width, height }; - let min_db = -40.0; - let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec) = - if let Some(sample) = &self.0 { - let sample = sample.read().unwrap(); - let start = sample.start as f64; - let end = sample.end as f64; - let length = end - start; - let step = length / width as f64; - let mut t = start; - let mut lines = vec![]; - while t < end { - let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)]; - let total: f32 = chunk.iter().map(|x|x.abs()).sum(); - let count = chunk.len() as f32; - let meter = 10. * (total / count).log10(); - let x = t as f64; - let y = meter as f64; - lines.push(Line::new(x, min_db, x, y, Color::Green)); - t += step / 2.; +render!((self: SampleViewer) + |layout|Ok(Some([0, 0])), + |render|{ + + let [x, y, width, height] = render.area(); + + let area = Rect { x, y, width, height }; + let min_db = -40.0; + + let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec) = + if let Some(sample) = &self.0 { + let sample = sample.read().unwrap(); + let start = sample.start as f64; + let end = sample.end as f64; + let length = end - start; + let step = length / width as f64; + let mut t = start; + let mut lines = vec![]; + while t < end { + let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)]; + let total: f32 = chunk.iter().map(|x|x.abs()).sum(); + let count = chunk.len() as f32; + let meter = 10. * (total / count).log10(); + let x = t as f64; + let y = meter as f64; + lines.push(Line::new(x, min_db, x, y, Color::Green)); + t += step / 2.; + } + ( + [sample.start as f64, sample.end as f64], + [min_db, 0.], + lines + ) + } else { + ( + [0.0, width as f64], + [0.0, height as f64], + vec![ + Line::new(0.0, 0.0, width as f64, height as f64, Color::Red), + Line::new(width as f64, 0.0, 0.0, height as f64, Color::Red), + ] + ) + }; + + let draw = |ctx| { + for line in lines.iter() { + ctx.draw(line) } - ( - [sample.start as f64, sample.end as f64], - [min_db, 0.], - lines - ) - } else { - ( - [0.0, width as f64], - [0.0, height as f64], - vec![ - Line::new(0.0, 0.0, width as f64, height as f64, Color::Red), - Line::new(width as f64, 0.0, 0.0, height as f64, Color::Red), - ] - ) }; - Canvas::default().x_bounds(x_bounds).y_bounds(y_bounds).paint(|ctx|{ - for line in lines.iter() { - ctx.draw(line) - } - }).render(area, &mut to.buffer); - Ok(()) -})); + + Canvas::default() + .x_bounds(x_bounds) + .y_bounds(y_bounds) + .paint(draw) + .render(area, &mut render.buffer); + + Ok(()) + });