mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 03:36:41 +01:00
wip: big flat pt.11, down to 12, update literal render macro
This commit is contained in:
parent
a0175dabc8
commit
b718e54d33
9 changed files with 302 additions and 239 deletions
|
|
@ -11,27 +11,88 @@ pub trait Output<E: Engine> {
|
|||
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.
|
||||
///
|
||||
/// 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<E: Engine, $($($L),*$($T $(: $U)?),*)?> Content<E> for $Struct $(<$($L,)* E, $($T),*>)? {
|
||||
fn content (&$self) -> Option<impl Render<$E>> {
|
||||
Some($cb)
|
||||
}
|
||||
fn content (&$self) -> Option<impl Render<$E>> { Some($cb) }
|
||||
}
|
||||
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? {
|
||||
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<()> {
|
||||
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$(<
|
||||
$($($L:lifetime),+)?
|
||||
$($($T:ident$(:$U:path)?),+)?
|
||||
|
|
@ -61,45 +122,35 @@ pub trait Output<E: Engine> {
|
|||
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 Option<C> {}
|
||||
//impl<E: Engine, C: Content<E>> Content<E> for Arc<C> {}
|
||||
|
|
|
|||
|
|
@ -25,57 +25,59 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self {
|
|||
}),
|
||||
});
|
||||
|
||||
render!(<Tui>|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!(<Tui>(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)?
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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!(<Tui>|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!(<Tui>(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!(<Tui>|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!(<Tui>(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;
|
||||
}
|
||||
}
|
||||
})
|
||||
}));
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -2,32 +2,34 @@ use crate::*;
|
|||
use super::note_y_iter;
|
||||
|
||||
pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal);
|
||||
render!(<Tui>|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!(<Tui>(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
|
||||
}
|
||||
}
|
||||
})));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ use crate::*;
|
|||
use super::note_y_iter;
|
||||
|
||||
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);
|
||||
|
||||
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) }
|
||||
|
|
|
|||
|
|
@ -2,45 +2,33 @@ use crate::*;
|
|||
use super::note_y_iter;
|
||||
|
||||
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();
|
||||
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!(<Tui>(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(());
|
||||
//}
|
||||
})));
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal);
|
||||
render!(<Tui>|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!(<Tui>(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,
|
||||
|
|
|
|||
|
|
@ -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<Arc<RwLock<Sample>>>);
|
||||
render!(<Tui>|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<Line>) =
|
||||
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!(<Tui>(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<Line>) =
|
||||
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(())
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue