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<()>;
}
/// 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> {}

View file

@ -25,8 +25,10 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self {
}),
});
render!(<Tui>|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{
let area = to.area();
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| [
@ -65,17 +67,17 @@ render!(<Tui>|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{
};
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);
render.fill_fg([x, y, 1, height], bg);
render.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);
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 {
to.render_in(if let Some(clip_area) = clip_area { clip_area }
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)?
})
}));
});

View file

@ -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|{
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 + to.area().x() + x as u16;
for y in to.area().y()..to.area().y2() {
to.blit(&"", x, y, style);
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;
}
}
})
}));

View file

@ -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,

View file

@ -2,7 +2,9 @@ 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({
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();
@ -11,7 +13,7 @@ render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to: &mut TuiOutput|Ok({
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();
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 {
@ -19,10 +21,10 @@ render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to: &mut TuiOutput|Ok({
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);
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) {
to.blit(&"", x_tail, screen_y, style);
render.blit(&"", x_tail, screen_y, style);
}
break
}
@ -30,4 +32,4 @@ render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to: &mut TuiOutput|Ok({
break
}
}
})));
});

View file

@ -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) }

View file

@ -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({
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] = to.area().xywh();
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) {
//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);
// 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.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::*;
pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalTimeline<'a>|render(|to: &mut TuiOutput|{
let [x, y, w, h] = to.area();
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 {
to.blit(&"|", screen_x, y, style);
render.blit(&"|", screen_x, y, style);
}
}
Ok(())
}));
});
//Tui::fg_bg(
//self.0.color.lightest.rgb,
//self.0.color.darkest.rgb,

View file

@ -5,10 +5,15 @@ 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();
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();
@ -43,10 +48,18 @@ render!(<Tui>|self: SampleViewer|render(|to: &mut TuiOutput|{
]
)
};
Canvas::default().x_bounds(x_bounds).y_bounds(y_bounds).paint(|ctx|{
let draw = |ctx| {
for line in lines.iter() {
ctx.draw(line)
}
}).render(area, &mut to.buffer);
};
Canvas::default()
.x_bounds(x_bounds)
.y_bounds(y_bounds)
.paint(draw)
.render(area, &mut render.buffer);
Ok(())
}));
});