wip: big flat pt.11, down to 12, update literal render macro

This commit is contained in:
🪞👃🪞 2024-12-30 21:25:02 +01:00
parent a0175dabc8
commit b718e54d33
9 changed files with 302 additions and 239 deletions

View file

@ -11,27 +11,88 @@ pub trait Output<E: Engine> {
fn render_in (&mut self, area: E::Area, widget: &impl Render<E>) -> Usually<()>; fn render_in (&mut self, area: E::Area, widget: &impl Render<E>) -> Usually<()>;
} }
/// Something that can be represented by a renderable component.
pub trait Content<E: Engine>: Send + Sync {
fn content (&self) -> Option<impl Render<E>>;
}
impl<E: Engine, C: Content<E>> Content<E> for &C {
fn content (&self) -> Option<impl Render<E>> {
(*self).content()
}
}
/// Something that writes to an [Output].
pub trait Render<E: Engine>: Send + Sync {
/// Minimum size to use
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
Ok(None)
}
/// Draw to output render target
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
impl<E: Engine> Render<E> for &dyn Render<E> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> { (*self).min_size(to) }
fn render (&self, to: &mut E::Output) -> Usually<()> { (*self).render(to) }
}
impl<E: Engine, R: Render<E>> Render<E> for &R {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> { (*self).min_size(to) }
fn render (&self, to: &mut E::Output) -> Usually<()> { (*self).render(to) }
}
impl<E: Engine, R: Render<E>> Render<E> for Option<R> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
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. /// 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 { #[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) => { (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => {
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Content<E> for $Struct $(<$($L,)* E, $($T),*>)? { impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Content<E> for $Struct $(<$($L,)* E, $($T),*>)? {
fn content (&$self) -> Option<impl Render<$E>> { fn content (&$self) -> Option<impl Render<$E>> { Some($cb) }
Some($cb)
}
} }
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? { impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> { fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
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<()> { 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<E: Engine, $($($L),*$($T $(: $U)?),*)?> Content<E> for $Struct $(<$($L,)* E, $($T),*>)? {
fn content (&self) -> Option<impl Render<E>> { Some(self) }
}
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? {
fn min_size (&$self1, $to1: E::Size) -> Perhaps<E::Size> { $min_size }
fn render (&$self2, $to2: &mut E::Output) -> Usually<()> { $render }
}
};
// Implement from [Content] for a particular [Engine]
(<$E:ty>|$self:ident:$Struct:ident$(< (<$E:ty>|$self:ident:$Struct:ident$(<
$($($L:lifetime),+)? $($($L:lifetime),+)?
$($($T:ident$(:$U:path)?),+)? $($($T:ident$(:$U:path)?),+)?
@ -61,45 +122,35 @@ pub trait Output<E: Engine> {
self.content().map(|content|content.render(to)).unwrap_or(Ok(())) 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<impl Render<$E>> { 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<E: Engine>: Send + Sync {
/// Minimum size to use
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
Ok(None)
}
/// Draw to output render target
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
impl<E: Engine> Render<E> for &dyn Render<E> {}
//impl<E: Engine> Render<E> for &mut dyn Render<E> {}
//impl<E: Engine> Render<E> for Box<dyn Render<E>> {}
impl<E: Engine, R: Render<E>> Render<E> for &R {}
//impl<E: Engine, R: Render<E>> Render<E> for &mut R {}
impl<E: Engine, R: Render<E>> Render<E> for Option<R> {}
//impl<E: Engine, R: Render<E>> Render<E> for Arc<R> {}
//impl<E: Engine, R: Render<E>> Render<E> for Mutex<R> {}
//impl<E: Engine, R: Render<E>> Render<E> for RwLock<R> {}
/// Something that can be represented by a renderable component.
pub trait Content<E: Engine>: Send + Sync {
fn content (&self) -> Option<impl Render<E>>;
}
//impl<E: Engine> Content<E> for &dyn Render<E> {}
//impl<E: Engine> Content<E> for &mut dyn Render<E> {}
//impl<E: Engine> Content<E> for Box<dyn Render<E>> {}
impl<E: Engine, C: Content<E>> Content<E> for &C {
fn content (&self) -> Option<impl Render<E>> {
(*self).content()
}
}
//impl<E: Engine, C: Content<E>> Content<E> for &mut C {} //impl<E: Engine, C: Content<E>> Content<E> for &mut C {}
//impl<E: Engine, C: Content<E>> Content<E> for Option<C> {} //impl<E: Engine, C: Content<E>> Content<E> for Option<C> {}
//impl<E: Engine, C: Content<E>> Content<E> for Arc<C> {} //impl<E: Engine, C: Content<E>> Content<E> for Arc<C> {}

View file

@ -25,57 +25,59 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self {
}), }),
}); });
render!(<Tui>|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{ render!(<Tui>(self: ArrangerVCursor)
let area = to.area(); |layout|Ok([0, 0]),
let focused = true; |render|{
let selected = self.selected; let area = render.area();
let get_track_area = |t: usize| [ let focused = true;
self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(), let selected = self.selected;
self.cols[t].0 as u16, area.h(), let get_track_area = |t: usize| [
]; self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(),
let get_scene_area = |s: usize| [ self.cols[t].0 as u16, area.h(),
area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16, ];
area.w(), (self.rows[s].0 / PPQ) as u16 let get_scene_area = |s: usize| [
]; area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16,
let get_clip_area = |t: usize, s: usize| [ area.w(), (self.rows[s].0 / PPQ) as u16
(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, let get_clip_area = |t: usize, s: usize| [
self.cols[t].0 as u16 + 2, (self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1),
(self.rows[s].0 / PPQ) as u16 HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16,
]; self.cols[t].0 as u16 + 2,
let mut track_area: Option<[u16;4]> = None; (self.rows[s].0 / PPQ) as u16
let mut scene_area: Option<[u16;4]> = None; ];
let mut clip_area: Option<[u16;4]> = None; let mut track_area: Option<[u16;4]> = None;
let area = match selected { let mut scene_area: Option<[u16;4]> = None;
ArrangerSelection::Mix => area, let mut clip_area: Option<[u16;4]> = None;
ArrangerSelection::Track(t) => { let area = match selected {
track_area = Some(get_track_area(t)); ArrangerSelection::Mix => area,
area ArrangerSelection::Track(t) => {
}, track_area = Some(get_track_area(t));
ArrangerSelection::Scene(s) => { area
scene_area = Some(get_scene_area(s)); },
area ArrangerSelection::Scene(s) => {
}, scene_area = Some(get_scene_area(s));
ArrangerSelection::Clip(t, s) => { area
track_area = Some(get_track_area(t)); },
scene_area = Some(get_scene_area(s)); ArrangerSelection::Clip(t, s) => {
clip_area = Some(get_clip_area(t, s)); track_area = Some(get_track_area(t));
area 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); let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0);
to.fill_fg([x + width, y, 1, height], bg); if let Some([x, y, width, height]) = track_area {
} render.fill_fg([x, y, 1, height], bg);
if let Some([_, y, _, height]) = scene_area { render.fill_fg([x + width, y, 1, height], bg);
to.fill_ul([area.x(), y - 1, area.w(), 1], bg); }
to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); if let Some([_, y, _, height]) = scene_area {
} render.fill_ul([area.x(), y - 1, area.w(), 1], bg);
Ok(if focused { render.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
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) } Ok(if focused {
else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) } render.render_in(if let Some(clip_area) = clip_area { clip_area }
else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)? 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)?
})
});

View file

@ -6,43 +6,41 @@ pub struct ArrangerVColSep {
cols: Vec<(usize, usize)>, cols: Vec<(usize, usize)>,
scenes_w: u16 scenes_w: u16
} }
from!(|state:&ArrangerTui|ArrangerVColSep = Self { from!(|state:&ArrangerTui|ArrangerVColSep = Self {
fg: TuiTheme::separator_fg(false), fg: TuiTheme::separator_fg(false),
cols: ArrangerTrack::widths(&state.tracks), cols: ArrangerTrack::widths(&state.tracks),
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16, scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16,
}); });
render!(<Tui>(self: ArrangerVColSep)
render!(<Tui>|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{ |layout|Ok(Some([0, 0])),
let style = Some(Style::default().fg(self.fg)); |render|{
Ok(for x in self.cols.iter().map(|col|col.1) { let style = Some(Style::default().fg(self.fg));
let x = self.scenes_w + to.area().x() + x as u16; Ok(for x in self.cols.iter().map(|col|col.1) {
for y in to.area().y()..to.area().y2() { let x = self.scenes_w + render.area().x() + x as u16;
to.blit(&"", x, y, style); for y in render.area().y()..render.area().y2() {
} render.blit(&"", x, y, style);
}) }
})); })
});
pub struct ArrangerVRowSep { pub struct ArrangerVRowSep {
fg: Color, fg: Color,
rows: Vec<(usize, usize)>, rows: Vec<(usize, usize)>,
} }
from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self { from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self {
fg: TuiTheme::separator_fg(false), fg: TuiTheme::separator_fg(false),
rows: ArrangerScene::ppqs(&args.0.scenes, args.1), rows: ArrangerScene::ppqs(&args.0.scenes, args.1),
}); });
render!(<Tui>(self: ArrangerVRowSep)
render!(<Tui>|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{ |layout|Ok(Some([0, 0])),
Ok(for y in self.rows.iter().map(|row|row.1) { |render|Ok(for y in self.rows.iter().map(|row|row.1) {
let y = to.area().y() + (y / PPQ) as u16 + 1; let y = render.area().y() + (y / PPQ) as u16 + 1;
if y >= to.buffer.area.height { break } if y >= render.buffer.area.height { break }
for x in to.area().x()..to.area().x2().saturating_sub(2) { for x in render.area().x()..render.area().x2().saturating_sub(2) {
if x < to.buffer.area.x && y < to.buffer.area.y { if x < render.buffer.area.x && y < render.buffer.area.y {
let cell = to.buffer.get_mut(x, y); let cell = render.buffer.get_mut(x, y);
cell.modifier = Modifier::UNDERLINED; cell.modifier = Modifier::UNDERLINED;
cell.underline_color = self.fg; cell.underline_color = self.fg;
} }
} }
}) }));
}));

View file

@ -9,8 +9,8 @@ pub(crate) use ::tek_layout::{
tek_engine::{ tek_engine::{
Usually, Perhaps, Usually, Perhaps,
Engine, Size, Area, Engine, Size, Area,
Content, Render, render, Output, Content, Render, render,
Handle, handle, kexp, key_pat, key_event_pat, key_event_expr, Input, Handle, handle, kexp, key_pat, key_event_pat, key_event_expr,
Tui, TuiInput, TuiOutput, Tui, TuiInput, TuiOutput,
crossterm::{ crossterm::{
self, self,

View file

@ -2,32 +2,34 @@ use crate::*;
use super::note_y_iter; use super::note_y_iter;
pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal); pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to: &mut TuiOutput|Ok({ render!(<Tui>(self: PianoHorizontalCursor<'a>)
let style = Some(Style::default().fg(self.0.color.lightest.rgb)); |layout|Ok([0, 0]),
let note_hi = self.0.note_hi(); |render|{
let note_len = self.0.note_len(); let style = Some(Style::default().fg(self.0.color.lightest.rgb));
let note_lo = self.0.note_lo().get(); let note_hi = self.0.note_hi();
let note_point = self.0.note_point(); let note_len = self.0.note_len();
let time_point = self.0.time_point(); let note_lo = self.0.note_lo().get();
let time_start = self.0.time_start().get(); let note_point = self.0.note_point();
let time_zoom = self.0.time_zoom().get(); let time_point = self.0.time_point();
let [x0, y0, w, _] = to.area().xywh(); let time_start = self.0.time_start().get();
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { let time_zoom = self.0.time_zoom().get();
if note == note_point { let [x0, y0, w, _] = render.area().xywh();
for x in 0..w { for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
let screen_x = x0 + x; if note == note_point {
let time_1 = time_start + x as usize * time_zoom; for x in 0..w {
let time_2 = time_1 + time_zoom; let screen_x = x0 + x;
if time_1 <= time_point && time_point < time_2 { let time_1 = time_start + x as usize * time_zoom;
to.blit(&"", screen_x, screen_y, style); let time_2 = time_1 + time_zoom;
let tail = note_len as u16 / time_zoom as u16; if time_1 <= time_point && time_point < time_2 {
for x_tail in (screen_x + 1)..(screen_x + tail) { render.blit(&"", screen_x, screen_y, style);
to.blit(&"", x_tail, 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
} }
} });
})));

View file

@ -2,12 +2,18 @@ use crate::*;
use super::note_y_iter; use super::note_y_iter;
pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self))));
render!(<Tui>(self: PianoHorizontalKeys<'a>)
|layout|Ok(Some([0, 0])),
|render|Ok(render_keys_v(render, self)));
has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base); has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base);
impl<'a> NoteRange for PianoHorizontalKeys<'a> { impl<'a> NoteRange for PianoHorizontalKeys<'a> {
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() } fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() }
fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() } fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() }
} }
impl<'a> NotePoint for PianoHorizontalKeys<'a> { impl<'a> NotePoint for PianoHorizontalKeys<'a> {
fn note_len (&self) -> usize { self.0.note_len() } fn note_len (&self) -> usize { self.0.note_len() }
fn set_note_len (&self, x: usize) { self.0.set_note_len(x) } fn set_note_len (&self, x: usize) { self.0.set_note_len(x) }

View file

@ -2,45 +2,33 @@ use crate::*;
use super::note_y_iter; use super::note_y_iter;
pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal); pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalNotes<'a>|render(|to: &mut TuiOutput|Ok({
let time_start = self.0.time_start().get(); render!(<Tui>(self: PianoHorizontalNotes<'a>)
let note_lo = self.0.note_lo().get(); |layout|Ok(Some([0, 0])),
let note_hi = self.0.note_hi(); |render|Ok({
let note_point = self.0.note_point(); let time_start = self.0.time_start().get();
let source = &self.0.buffer; let note_lo = self.0.note_lo().get();
let [x0, y0, w, h] = to.area().xywh(); let note_hi = self.0.note_hi();
if h as usize != self.0.note_axis().get() { let note_point = self.0.note_point();
panic!("area height mismatch"); let source = &self.0.buffer;
} let [x0, y0, w, h] = render.area().xywh();
for (area_x, screen_x) in (x0..x0+w).enumerate() { if h as usize != self.0.note_axis().get() {
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { panic!("area height mismatch");
//if area_x % 10 == 0 { }
//to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None); 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_x = time_start + area_x;
let source_y = note_hi - area_y; let source_y = note_hi - area_y;
////// TODO: enable loop rollover: // TODO: enable loop rollover:
//////let source_x = (time_start + area_x) % source.width.max(1); //let source_x = (time_start + area_x) % source.width.max(1);
//////let source_y = (note_hi - area_y) % source.height.max(1); //let source_y = (note_hi - area_y) % source.height.max(1);
let is_in_x = source_x < source.width; let is_in_x = source_x < source.width;
let is_in_y = source_y < source.height; let is_in_y = source_y < source.height;
if is_in_x && is_in_y { if is_in_x && is_in_y {
if let Some(source_cell) = source.get(source_x, source_y) { if let Some(source_cell) = source.get(source_x, source_y) {
*to.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); *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(());
//}
})));

View file

@ -1,18 +1,21 @@
use crate::*; use crate::*;
pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalTimeline<'a>|render(|to: &mut TuiOutput|{ render!(<Tui>(self: PianoHorizontalTimeline<'a>)
let [x, y, w, h] = to.area(); |layout|Ok(Some([0, 0])),
let style = Some(Style::default().dim()); |render|{
let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); let [x, y, w, h] = render.area();
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { let style = Some(Style::default().dim());
let t = area_x as usize * self.0.time_zoom().get(); let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
if t < length { for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
to.blit(&"|", screen_x, y, style); 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( //Tui::fg_bg(
//self.0.color.lightest.rgb, //self.0.color.lightest.rgb,
//self.0.color.darkest.rgb, //self.0.color.darkest.rgb,

View file

@ -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.)]; const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
pub struct SampleViewer(pub Option<Arc<RwLock<Sample>>>); pub struct SampleViewer(pub Option<Arc<RwLock<Sample>>>);
render!(<Tui>|self: SampleViewer|render(|to: &mut TuiOutput|{ render!(<Tui>(self: SampleViewer)
let [x, y, width, height] = to.area(); |layout|Ok(Some([0, 0])),
let area = Rect { x, y, width, height }; |render|{
let min_db = -40.0;
let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec<Line>) = let [x, y, width, height] = render.area();
if let Some(sample) = &self.0 {
let sample = sample.read().unwrap(); let area = Rect { x, y, width, height };
let start = sample.start as f64; let min_db = -40.0;
let end = sample.end as f64;
let length = end - start; let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec<Line>) =
let step = length / width as f64; if let Some(sample) = &self.0 {
let mut t = start; let sample = sample.read().unwrap();
let mut lines = vec![]; let start = sample.start as f64;
while t < end { let end = sample.end as f64;
let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)]; let length = end - start;
let total: f32 = chunk.iter().map(|x|x.abs()).sum(); let step = length / width as f64;
let count = chunk.len() as f32; let mut t = start;
let meter = 10. * (total / count).log10(); let mut lines = vec![];
let x = t as f64; while t < end {
let y = meter as f64; let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)];
lines.push(Line::new(x, min_db, x, y, Color::Green)); let total: f32 = chunk.iter().map(|x|x.abs()).sum();
t += step / 2.; 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() { Canvas::default()
ctx.draw(line) .x_bounds(x_bounds)
} .y_bounds(y_bounds)
}).render(area, &mut to.buffer); .paint(draw)
Ok(()) .render(area, &mut render.buffer);
}));
Ok(())
});