add RenderThunk, LayoutThunk, Map::new, fold some components into arranger methods

This commit is contained in:
🪞👃🪞 2025-01-07 17:38:28 +01:00
parent 38e2e64751
commit b2fb71b405
8 changed files with 268 additions and 178 deletions

View file

@ -1,10 +1,28 @@
use crate::*;
use std::marker::PhantomData;
/// Lazily-evaluated [Render]able.
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T + Send + Sync>(F, PhantomData<E>);
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T + Send + Sync>(PhantomData<E>, F);
impl<E: Output, T: Render<E>, F: Fn()->T + Send + Sync> Thunk<E, T, F> {
pub fn new (thunk: F) -> Self { Self(thunk, Default::default()) }
pub fn new (thunk: F) -> Self { Self(Default::default(), thunk) }
}
impl<E: Output, T: Render<E>, F: Fn()->T + Send + Sync> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.0)() }
fn content (&self) -> impl Render<E> { (self.1)() }
}
pub struct RenderThunk<E: Output, F: Fn(&mut E) + Send + Sync>(PhantomData<E>, F);
impl<E: Output, F: Fn(&mut E) + Send + Sync> RenderThunk<E, F> {
pub fn new (render: F) -> Self { Self(Default::default(), render) }
}
impl<E: Output, F: Fn(&mut E) + Send + Sync> Content<E> for RenderThunk<E, F> {
fn render (&self, to: &mut E) { (self.1)(to) }
}
pub struct LayoutThunk<E: Output, F1: Fn(E::Area)->E::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync>(PhantomData<E>, F1, F2);
impl<E: Output, F1: Fn(E::Area)->E::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync> LayoutThunk<E, F1, F2> {
pub fn new (layout: F1, render: F2) -> Self { Self(Default::default(), layout, render) }
}
impl<E: Output, F1: Fn(E::Area)->E::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync> Content<E> for LayoutThunk<E, F1, F2> {
fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) }
fn render (&self, to: &mut E) { (self.2)(to) }
}

View file

@ -10,20 +10,30 @@ pub fn map_south<O: Output>(
Fill::x(item))))
}
pub struct Map<A, B, I, F, G>(pub F, pub G) where
pub struct Map<'a, A, B, I, F, G>(pub PhantomData<&'a()>, pub F, pub G) where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync;
impl<E, A, B, I, F, G> Content<E> for Map<A, B, I, F, G> where
impl<'a, A, B, I, F, G> Map<'a, A, B, I, F, G> where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
{
pub fn new (f: F, g: G) -> Self {
Self(Default::default(), f, g)
}
}
impl<'a, E, A, B, I, F, G> Content<E> for Map<'a, A, B, I, F, G> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
{
fn layout (&self, area: E::Area) -> E::Area {
let Self(get_iterator, callback) = self;
let Self(_, get_iterator, callback) = self;
let mut index = 0;
let [mut min_x, mut min_y] = area.center();
let [mut max_x, mut max_y] = area.center();
@ -42,7 +52,7 @@ impl<E, A, B, I, F, G> Content<E> for Map<A, B, I, F, G> where
area.center_xy([w.into(), h.into()].into()).into()
}
fn render (&self, to: &mut E) {
let Self(get_iterator, callback) = self;
let Self(_, get_iterator, callback) = self;
let mut index = 0;
//let area = self.layout(to.area());
for item in get_iterator() {

View file

@ -37,27 +37,6 @@ impl Arranger {
has_clock!(|self:ArrangerTrack|self.player.clock());
has_player!(|self:ArrangerTrack|self.player);
impl ArrangerTrack {
pub fn widths (tracks: &[Self]) -> Vec<(usize, usize)> {
let mut widths = vec![];
let mut total = 0;
for track in tracks.iter() {
let width = track.width;
widths.push((width, total));
total += width;
}
widths.push((0, total));
widths
}
pub fn with_widths (tracks: &[ArrangerTrack])
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let mut x = 0;
tracks.iter().enumerate().map(move |(index, track)|{
let data = (index, track, x, x + track.width);
x += track.width;
data
})
}
/// Name of track
pub fn name (&self) -> &Arc<RwLock<String>> {
&self.name

View file

@ -23,23 +23,32 @@ use crate::*;
//});
render!(TuiOut: (self: Arranger) => {
let scenes = &self.scenes;
let ppqs = Arranger::ppqs(scenes, 1);
let scene_heights = Arranger::scene_heights(scenes, 1);
Fill::xy(self.size.of(
Bsp::s(self.toolbar_view(),
Bsp::n(self.status_view(),
Bsp::n(self.selector_view(),
Bsp::w(self.pool_view(),
Bsp::s(Align::n(Fill::x(Fixed::y(3, self.header()))),
Bsp::s(Align::n(Fill::x(Fixed::y(1, self.ins()))),
Bsp::s(Align::n(Fill::x(Fixed::y(1, self.outs()))),
Fill::xy(
Bsp::a(Fill::xy(Fill::xy(Map(
move||scenes.iter(),//.zip(ppqs.iter().map(|row|row.0)),
move|scene, i|Arranger::format_scene(&self.tracks, scene, ppqs[i].0)))),
Bsp::n(self.selector_view(),
Bsp::n(Fixed::y(20, &self.editor),
Bsp::s(
Align::w(Fill::x(
Bsp::s(Fixed::y(3, Align::w(self.header())),
Bsp::s(Fixed::y(1, self.ins()), Fill::x(Fixed::y(1, self.outs()))))
)),
Bsp::a(
Bsp::a(
Fill::xy(ArrangerVRowSep::from((self, 1))),
Fill::xy(ArrangerVColSep::from(self))))))))))))))
});
Map::new(
move||scenes.iter(),//.zip(scene_heights.iter().map(|row|row.0)),
move|scene, i|Arranger::format_scene(&self.tracks, scene, scene_heights[i].0)
),
self.cursor(),
),
Bsp::a(
Fill::xy(self.scene_row_sep()),
""
//Fill::xy(ArrangerVColSep::from(self))
)
)))))))))});
//Align::n(Fill::xy(lay!(
//Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))),
//Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))),
@ -71,7 +80,7 @@ impl<'a> ArrangerVClips<'a> {
size: &state.size,
tracks: &state.tracks,
scenes: &state.scenes,
rows: Arranger::ppqs(&state.scenes, zoom),
rows: Arranger::scene_heights(&state.scenes, zoom),
}
}
}
@ -81,7 +90,7 @@ fn row <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOu
impl Arranger {
fn header (&self) -> impl Content<TuiOut> + use<'_> {
let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16;
Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| {
Push::x(scenes_w, Map::new(||Arranger::tracks_with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color();
let title = format!("{} {} {}", track.name.read().unwrap(), x1, x2);
@ -92,7 +101,7 @@ impl Arranger {
}
fn ins (&self) -> impl Content<TuiOut> + use<'_> {
let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16;
Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| {
Push::x(scenes_w, Map::new(||Arranger::tracks_with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color();
let input = Self::format_input(track);
@ -102,14 +111,14 @@ impl Arranger {
}
fn outs (&self) -> impl Content<TuiOut> + use<'_> {
let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16;
Push::x(scenes_w, Map(||ArrangerTrack::with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| {
Push::x(scenes_w, Map::new(||Arranger::tracks_with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color();
Fill::xy(Align::n(Push::x(x2 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h,
Fixed::xy(w as u16, 5, row(color, Self::format_output(track).ok())))))))
}))
}
pub fn ppqs (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> {
pub fn scene_heights (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> {
let mut total = 0;
if factor == 0 {
scenes.iter().map(|scene|{
@ -135,7 +144,7 @@ impl Arranger {
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone()))
);
let clips = Map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _|
let clips = Map::new(||Arranger::tracks_with_widths(tracks), move|(index, track, x1, x2), _|
Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height))
);
Fixed::y(height, Bsp::e(icon, Bsp::e(name, clips)))
@ -226,129 +235,209 @@ impl Arranger {
Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name())
.transpose()?.unwrap_or("?".into())))
}
}
pub struct ArrangerVColSep {
fg: Color,
cols: Vec<(usize, usize)>,
scenes_w: u16
}
from!(|state:&Arranger|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!(TuiOut: |self: ArrangerVColSep, to| {
let style = Some(Style::default().fg(self.fg));
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);
fn track_col_sep <'a> (&'a self) -> impl Content<TuiOut> + 'a {
let fg = TuiTheme::separator_fg(false);
let x = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16;
Map::new(
move||Self::tracks_with_widths(self.tracks.as_slice()),
move|(_n, _track, x1, _x2), _i|Push::x(x + x1 as u16, Fill::y(Tui::fg(fg, "")))
)
}
pub fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> {
let mut widths = vec![];
let mut total = 0;
for track in tracks.iter() {
let width = track.width;
widths.push((width, total));
total += width;
}
widths.push((0, total));
widths
}
});
pub struct ArrangerVRowSep {
fg: Color,
rows: Vec<(usize, usize)>,
}
from!(|args:(&Arranger, usize)|ArrangerVRowSep = Self {
fg: Color::Rgb(255,255,255,),
rows: Arranger::ppqs(&args.0.scenes, args.1),
});
render!(TuiOut: |self: ArrangerVRowSep, to|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 {
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
cell.modifier = Modifier::UNDERLINED;
cell.underline_color = self.fg;
}
//}
pub fn tracks_with_widths (tracks: &[ArrangerTrack])
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let mut x = 0;
tracks.iter().enumerate().map(move |(index, track)|{
let data = (index, track, x, x + track.width);
x += track.width;
data
})
}
});
pub struct ArrangerVCursor {
cols: Vec<(usize, usize)>,
rows: Vec<(usize, usize)>,
color: ItemPalette,
reticle: Reticle,
selected: ArrangerSelection,
scenes_w: u16,
}
fn scene_row_sep <'a> (&'a self) -> impl Content<TuiOut> + 'a {
let fg = Color::Rgb(255,255,255);
Map::new(move||self.scenes_with_heights(), |_, _|"")
//Map(||rows.iter(), |(_n, _scene, y1, _y2), _i| {
//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 {
//if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
//cell.modifier = Modifier::UNDERLINED;
//cell.underline_color = fg;
//}
////}
//}
//})
}
pub fn scenes_with_heights (&self)
-> impl Iterator<Item = (usize, &ArrangerScene, usize, usize)>
{
let mut y = 0;
self.scenes.iter().enumerate().map(move|(index, scene)|{
let data = (index, scene, y, y + 1);
y += 1;
data
})
}
from!(|args:(&Arranger, usize)|ArrangerVCursor = Self {
cols: ArrangerTrack::widths(&args.0.tracks),
rows: Arranger::ppqs(&args.0.scenes, args.1),
selected: args.0.selected(),
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.0.scenes) as u16,
color: args.0.color,
reticle: Reticle(Style {
fg: Some(args.0.color.lighter.rgb),
bg: None,
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::DIM
}),
});
impl Content<TuiOut> for ArrangerVCursor {
fn render (&self, to: &mut TuiOut) {
let area = to.area();
fn cursor (&self) -> impl Content<TuiOut> + '_ {
let color = self.color;
let bg = color.lighter.rgb;//Color::Rgb(0, 255, 0);
let selected = self.selected();
let cols = Arranger::track_widths(&self.tracks);
let rows = Arranger::scene_heights(&self.scenes, 1);
let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16;
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);
}
if focused {
to.place(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)
};
let reticle = Reticle(Style {
fg: Some(self.color.lighter.rgb),
bg: None,
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::DIM
});
RenderThunk::new(move|to: &mut TuiOut|{
let area = to.area();
let [x, y, w, h] = area.xywh();
let mut track_area: Option<[u16;4]> = match selected {
ArrangerSelection::Track(t) | ArrangerSelection::Clip(t, _) => Some([
x + scenes_w + cols[t].1 as u16, y,
cols[t].0 as u16, h,
]),
_ => None
};
let mut scene_area: Option<[u16;4]> = match selected {
ArrangerSelection::Scene(s) | ArrangerSelection::Clip(_, s) => Some([
x, y + HEADER_H + (rows[s].1 / PPQ) as u16,
w, (rows[s].0 / PPQ) as u16
]),
_ => None
};
let mut clip_area: Option<[u16;4]> = match selected {
ArrangerSelection::Clip(t, s) => Some([
(scenes_w + x + cols[t].1 as u16).saturating_sub(1),
HEADER_H + y + (rows[s].1/PPQ) as u16,
cols[t].0 as u16 + 2,
(rows[s].0 / PPQ) as u16
]),
_ => None
};
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([x, y - 1, w, 1], bg);
to.fill_ul([x, y + height - 1, w, 1], bg);
}
if focused {
to.place(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(scenes_w)
} else {
area.clip_w(scenes_w).clip_h(HEADER_H)
}, &reticle)
};
})
}
}
//pub struct ArrangerVCursor {
//cols: Vec<(usize, usize)>,
//rows: Vec<(usize, usize)>,
//color: ItemPalette,
//reticle: Reticle,
//selected: ArrangerSelection,
//scenes_w: u16,
//}
//from!(|args:(&Arranger, usize)|ArrangerVCursor = Self {
//cols: Arranger::track_widths(&args.0.tracks),
//rows: Arranger::scene_heights(&args.0.scenes, args.1),
//selected: args.0.selected(),
//scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.0.scenes) as u16,
//color: args.0.color,
//reticle: Reticle(Style {
//fg: Some(args.0.color.lighter.rgb),
//bg: None,
//underline_color: None,
//add_modifier: Modifier::empty(),
//sub_modifier: Modifier::DIM
//}),
//});
//impl Content<TuiOut> for ArrangerVCursor {
//fn render (&self, to: &mut TuiOut) {
//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);
//}
//if focused {
//to.place(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

@ -10,15 +10,7 @@ pub(crate) use ::tek_tui::{
*,
tek_edn::*,
tek_layout::*,
tek_engine::{
from,
Usually, Perhaps,
Output, Content, Render, RenderBox, Thunk, render, Engine, Size, Area,
Input, handle, Handle, command, Command, input_to_command, InputToCommand, keymap, EventMap,
},
Tui,
TuiIn, key, ctrl, shift, alt, kexp, kpat,
TuiOut,
tek_engine::*,
crossterm::{
self,
event::{

View file

@ -25,7 +25,9 @@ render!(TuiOut: |self: PianoHorizontalNotes<'a>, render|{
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();
if let Some(cell) = render.buffer.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) {
*cell = source_cell.clone();
}
}
}
}

View file

@ -7,7 +7,7 @@ render!(TuiOut: (self: PoolView<'a>) => {
let color = self.1.phrase().read().unwrap().color;
Outer(
Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)
).enclose(Map(||model.phrases().iter(), |clip, i|{
).enclose(Map::new(||model.phrases().iter(), |clip, i|{
let item_height = 1;
let item_offset = i as u16 * item_height;
let selected = i == model.phrase_index();

View file

@ -17,7 +17,7 @@ render!(TuiOut: (self: SampleList<'a>) => {
let note_lo = editor.note_lo().load(Relaxed);
let note_pt = editor.note_point();
let note_hi = editor.note_hi();
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map(move||(note_lo..=note_hi).rev(), move|note, i| {
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));