somewhat more like it

This commit is contained in:
🪞👃🪞 2025-01-10 20:50:09 +01:00
parent f6ab777c82
commit d3d1670af2
2 changed files with 602 additions and 605 deletions

View file

@ -4,7 +4,6 @@ mod arranger_command; pub use self::arranger_command::*;
mod arranger_scene; pub use self::arranger_scene::*;
mod arranger_select; pub use self::arranger_select::*;
mod arranger_track; pub use self::arranger_track::*;
mod arranger_tui; pub use self::arranger_tui::*;
mod arranger_h;
/// Root view for standalone `tek_arranger`
@ -108,3 +107,605 @@ impl Arranger {
}
}
}
pub(crate) const HEADER_H: u16 = 0; // 5
pub(crate) const SCENES_W_OFFSET: u16 = 0;
render!(TuiOut: (self: Arranger) => {
let scenes_w = 16;
let row = move|h, header, cells|Fixed::y(h, Bsp::e(
Fixed::x(scenes_w, header),
Fixed::y(h, Fill::x(Align::x(cells)))
));
//let row = move|h, header, cells|Align::w(Bsp::e(
//Align::w(Fixed::xy(scenes_w, h, header)),
//Fixed::xy(self.tracks.len() as u16*12, h, cells)
//));
let h = self.size.h() as u16;
let toolbar = |x|Bsp::s(self.toolbar_view(), x);
let pool = |x|Bsp::w(self.pool_view(), x);
let editing = |x|Bsp::n(Bsp::e(self.editor.clip_status(), self.editor.edit_status()), x);
let outputs = |x|Bsp::s(row(2, self.output_row_header(), self.output_row_cells()), Fill::y(x));
let playing = |x|Bsp::s(row(2, self.elapsed_row_header(), self.elapsed_row_cells()), Fill::y(x));
let next = |x|Bsp::s(row(2, self.next_row_header(), self.next_row_cells()), Fill::y(x));
let tracks = |x|Bsp::s(row(3, self.track_row_header(), self.track_row_cells()), Fill::y(x));
let scenes = |x|Bsp::s(row(h.saturating_sub(13), self.scene_row_headers(), self.scene_row_cells()), x);
let inputs = |x|Bsp::n(row(2, self.input_row_header(), self.input_row_cells()), Fill::y(x));
self.size.of(toolbar(pool(editing(inputs(outputs(playing(next(tracks(scenes(Fill::xy("")))))))))))
//let enclosed = |x|Outer(Style::default().fg(Color::Rgb(72,72,72))).enclose(x);
//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
//Bsp::s(arrrrrr, enclosed(&self.editor))
});
impl Arranger {
pub const LEFT_SEP: char = '▎';
pub const TRACK_MIN_WIDTH: usize = 4;
pub fn scenes_with_heights (&self, h: usize) -> impl Iterator<Item = (usize, &ArrangerScene, usize, usize)> {
let mut y = 0;
let editing = self.editing;
let (selected_track, selected_scene) = match self.selected {
ArrangerSelection::Clip(t, s) => (Some(t), Some(s)),
_ => (None, None)
};
self.scenes.iter().enumerate().map(move|(s, scene)|{
let active = editing && selected_track.is_some() && selected_scene == Some(s);
let height = if active { 15 } else { h };
let data = (s, scene, y, y + height);
y += height;
data
})
}
pub fn tracks_with_widths (&self)
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let active = match self.selected {
ArrangerSelection::Track(t) => Some(t),
ArrangerSelection::Clip(t, _) => Some(t),
_ => None
};
Self::tracks_with_widths_static(self.tracks.as_slice(), active)
}
fn tracks_with_widths_static (tracks: &[ArrangerTrack], active: Option<usize>)
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let mut x = 0;
tracks.iter().enumerate().map(move |(index, track)|{
let width = if Some(index) == active { 40 } else { track.width };
let data = (index, track, x, x + width);
x += width;
data
})
}
fn output_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(192);
let bg = TuiTheme::g(48);
(move||Tui::bold(true, Tui::fg_bg(fg, bg, "[ ] Out 1: NI")).boxed()).into()
}
fn output_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
//let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color().dark.into();
let cell = Bsp::s(format!(" M S "), phat_hi(color.dark.rgb, color.darker.rgb));
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell)))
})).boxed()).into()
}
fn elapsed_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Playing")).boxed()).into()
}
fn elapsed_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
//let color = track.color();
let color: ItemPalette = track.color().dark.into();
let timebase = self.clock().timebase();
let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb,
if let Some((_, Some(clip))) = track.player.play_clip().as_ref() {
let length = clip.read().unwrap().length;
let elapsed = track.player.pulses_since_start().unwrap() as usize;
format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64))
} else {
String::new()
});
let cell = Bsp::s(value, phat_hi(color.dark.rgb, color.darker.rgb));
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell))
})).boxed()).into()
}
fn next_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Next")).boxed()).into()
}
fn next_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
let color: ItemPalette = track.color();
let color: ItemPalette = track.color().dark.into();
let cell = Self::cell_until_next(track, &self.clock().playhead);
let cell = Self::cell(color, Tui::bold(true, cell));
let cell = Tui::fg_bg(color.lightest.rgb, color.base.rgb, cell);
let cell = Bsp::s(cell, phat_hi(color.dark.rgb, color.darker.rgb));
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell))
})).boxed()).into()
}
fn track_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into()
}
fn track_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let iter = ||self.tracks_with_widths();
(move||Fixed::y(2, Map::new(iter, move|(_, track, x1, x2), i| {
let color = track.color();
let name = Push::x(1, &track.name);
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16,
Tui::fg_bg(color.lightest.rgb, color.base.rgb,
phat_cell(color, color.darkest.rgb.into(),
Tui::bold(true, name))))) })).boxed()).into()
}
fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(0), TuiTheme::g(200), "[ ] In 1: Korg"))).boxed()).into()
}
fn input_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let cell = Bsp::s("[Rec]", "[Mon]");
let color: ItemPalette = track.color().dark.into();
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell)))
})).boxed()).into()
}
fn scene_row_headers <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||{
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
let selected_scene = match self.selected {
ArrangerSelection::Scene(s) => Some(s),
_ => None
};
Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new(
||self.scenes_with_heights(2),
move|(_, scene, y1, y2), i| {
let h = (y2 - y1) as u16;
let name = format!("🭬{}", &scene.name);
let color = scene.color();
let cell = phat_sel_3(
selected_scene == Some(i),
Push::x(1, Tui::bold(true, name.clone())),
Push::x(1, Tui::bold(true, name)),
if selected_scene.map(|s|s + 1) == Some(i) {
None
} else {
Some(last_color.read().unwrap().base.rgb)
},
if selected_scene == Some(i) {
color.light.rgb
} else {
color.base.rgb
},
Color::Rgb(0, 0, 0)
);
*last_color.write().unwrap() = color;
map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell))
}
))).boxed()
}).into()
}
fn scene_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let editing = self.editing;
let (selected_track, selected_scene) = match self.selected {
ArrangerSelection::Clip(t, s) => (Some(t), Some(s)),
_ => (None, None)
};
let tracks = move||self.tracks_with_widths();
let scenes = ||self.scenes_with_heights(2);
(move||Fixed::y(2, Map::new(tracks, move|(_, track, x1, x2), t| {
let w = (x2 - x1) as u16;
let cell = Bsp::s("[Rec]", "[Mon]");
let color: ItemPalette = track.color().dark.into();
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
let cells = Map::new(scenes, move|(_, scene, y1, y2), s| {
let h = (y2 - y1) as u16;
let color = scene.color();
let name = "";
let last = last_color.read().unwrap().clone();
//let cell = phat_sel_3(
//selected_track == Some(i) && selected_scene == Some(j),
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
//if selected_track == Some(i) && selected_scene.map(|s|s+1) == Some(j) {
//None
//} else {
//Some(TuiTheme::g(32).into())
//},
//TuiTheme::g(32).into(),
//TuiTheme::g(32).into(),
//);
let active = editing && selected_track == Some(t) && selected_scene == Some(s);
let editor = Thunk::new(||&self.editor);
let cell = Thunk::new(move||phat_sel_3(
selected_track == Some(t) && selected_scene == Some(s),
Tui::fg(TuiTheme::g(64), Push::x(1, Tui::bold(true, name.to_string()))),
Tui::fg(TuiTheme::g(64), Push::x(1, Tui::bold(true, name.to_string()))),
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) {
None
} else {
Some(TuiTheme::g(32).into())
},
TuiTheme::g(32).into(),
TuiTheme::g(32).into(),
));
let cell = Either(active, editor, cell);
map_south(
y1 as u16,
h + 1,
Fill::x(cell)
)
});
map_east(
x1 as u16,
w,
Fixed::x(w, Tui::bg(Color::Rgb(0,0,0), Fill::y(cells)).boxed())
)
})).boxed()).into()
}
fn track_column_separators <'a> (&'a self) -> impl Content<TuiOut> + 'a {
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
let fg = Color::Rgb(64,64,64);
Map::new(move||self.tracks_with_widths(), move|(_n, _track, x1, x2), _i|{
Push::x(scenes_w, map_east(x1 as u16, (x2 - x1) as u16,
Fixed::x((x2 - x1) as u16, Tui::fg(fg, RepeatV(&"·")))))
})
}
/// name and width of track
fn cell_name (track: &ArrangerTrack, _w: usize) -> impl Content<TuiOut> {
Tui::bold(true, Tui::fg(track.color.lightest.rgb, track.name().clone()))
}
/// beats until switchover
fn cell_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
-> Option<impl Content<TuiOut>>
{
let timebase = &current.timebase;
let mut result = String::new();
if let Some((t, _)) = track.player.next_clip().as_ref() {
let target = t.pulse.get();
let current = current.pulse.get();
if target > current {
result = format!("-{:>}", timebase.format_beats_0_short(target - current))
}
}
Some(result)
}
fn scene_rows (&self) -> impl Content<TuiOut> + use<'_> {
let scenes_w = 16.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
Map::new(||self.scenes_with_heights(1), move|(_, scene, y1, y2), i| {
let h = (y2 - y1) as u16;
let color = scene.color();
let cell = Self::cell(color, scene.name.clone());
let cell = Fixed::y(h, Fixed::x(scenes_w, cell));
map_south(y1 as u16, 1, cell)
})
}
pub fn scene_heights (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> {
let mut total = 0;
if factor == 0 {
scenes.iter().map(|scene|{
let pulses = scene.pulses().max(PPQ);
total += pulses;
(pulses, total - pulses)
}).collect()
} else {
(0..=scenes.len()).map(|i|{
(factor*PPQ, factor*PPQ*i)
}).collect()
}
}
fn cell_scene <'a> (
tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize
) -> impl Content<TuiOut> + use<'a> {
let height = 1.max((pulses / PPQ) as u16);
let playing = scene.is_playing(tracks);
let icon = Tui::bg(
scene.color.base.rgb, if playing { "" } else { " " }
);
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.clone()))
);
let clips = Map::new(||Arranger::tracks_with_widths_static(tracks, None), move|(index, track, x1, x2), _|
Push::x((x2 - x1) as u16, Self::cell_clip(scene, index, track, (x2 - x1) as u16, height))
);
Fixed::y(height, Bsp::e(icon, Bsp::e(name, clips)))
}
fn cell_clip <'a> (
scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16
) -> impl Content<TuiOut> + use<'a> {
scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{
let clip = clip.read().unwrap();
let mut bg = TuiTheme::border_bg();
let name = clip.name.to_string();
let max_w = name.len().min((w as usize).saturating_sub(2));
let color = clip.color;
bg = color.dark.rgb;
if let Some((_, Some(ref playing))) = track.player.play_clip() {
if *playing.read().unwrap() == *clip {
bg = color.light.rgb
}
};
Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w]))));
}))
}
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock))))
}
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
let w = self.size.w();
let clip_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let pool_w = if self.pool.visible { clip_w } else { 0 };
let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool))))
}
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
}
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(1), |_, _|"")
//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;
//}
////}
//}
//})
}
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 = 16.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
let focused = true;
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)
};
})
}
/// A 1-row cell.
fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> {
Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field))
}
}
/// A phat line
fn phat_lo (fg: Color, bg: Color) -> impl Content<TuiOut> {
Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"")))
}
/// A phat line
fn phat_hi (fg: Color, bg: Color) -> impl Content<TuiOut> {
Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"")))
}
/// A cell that is 3-row on its own, but stacks, giving (N+1)*2 rows per N cells.
fn phat_cell <T: Content<TuiOut>> (
color: ItemPalette, last: ItemPalette, field: T
) -> impl Content<TuiOut> {
Bsp::s(phat_lo(color.base.rgb, last.base.rgb),
Bsp::n(phat_hi(color.base.rgb, last.base.rgb),
Fixed::y(1, Fill::x(Tui::fg_bg(color.lightest.rgb, color.base.rgb, field))),
)
)
}
fn phat_cell_3 <T: Content<TuiOut>> (
field: T, top: Color, middle: Color, bottom: Color
) -> impl Content<TuiOut> {
Bsp::s(phat_lo(middle, top),
Bsp::n(phat_hi(middle, bottom),
Fill::y(Fill::x(Tui::bg(middle, field))),
)
)
}
fn phat_sel_3 <T: Content<TuiOut>> (
selected: bool, field_1: T, field_2: T, top: Option<Color>, middle: Color, bottom: Color
) -> impl Content<TuiOut> {
let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle);
Either(selected,
Tui::bg(middle, Outer(border).enclose( Align::w(Bsp::s("", Bsp::s(field_1, ""))))),
Bsp::s(
Fixed::y(1, top.map(|top|phat_lo(middle, top))),
Bsp::n(phat_hi(middle, bottom),
Fixed::y(1, Fill::x(Tui::bg(middle, field_2))),
)
))
}
//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)
//};
//}
//}
//impl Arranger {
//fn render_mode (state: &Self) -> impl Content<TuiOut> + use<'_> {
//match state.mode {
//ArrangerMode::H => todo!("horizontal arranger"),
//ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
//}
//}
//}
//render!(TuiOut: (self: Arranger) => {
//let pool_w = if self.pool.visible { self.splits[1] } else { 0 };
//let color = self.color;
//let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)),
//Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)),
//Bsp::n(TransportView::new(true, &self.clock),
//Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)),
//Bsp::n(Fill::x(Fixed::y(20,
//Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")),
//Bsp::a(
//Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))),
//Self::render_mode(self))))), Fill::y(&"fixme: self.editor"))))));
//self.size.of(layout)
//});
//Align::n(Fill::xy(lay!(
//Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))),
//Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))),
//Align::n(Fill::xy(ArrangerVColSep::from(self))),
//Align::n(Fill::xy(ArrangerVClips::new(self, 1))),
//Align::n(Fill::xy(ArrangerVCursor::from((self, 1))))))))))))))));
//Align::n(Fill::xy(":")))))))))))));
//"todo:"))))))));
//Bsp::s(
//Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))),
//Bsp::s(
//Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))),
//Fill::x(Fixed::y(1, ArrangerVOuts::from(self)))))))))))));
//Bsp::s(
//Bsp::s(
//Bsp::s(
//Fill::xy(ArrangerVClips::new(self, 1)),
//Fill::x(ArrangerVOuts::from(self)))))

View file

@ -1,604 +0,0 @@
use crate::*;
pub(crate) const HEADER_H: u16 = 0; // 5
pub(crate) const SCENES_W_OFFSET: u16 = 0;
render!(TuiOut: (self: Arranger) => {
let scenes_w = 16;
let row = move|h, header, cells|Align::w(Bsp::e(
Align::w(Fixed::xy(scenes_w, h, header)),
Fixed::xy(self.tracks.len() as u16*12, h, cells)
));
let toolbar = |x|Bsp::s(self.toolbar_view(), x);
let pool = |x|Bsp::w(self.pool_view(), x);
let editing = |x|Bsp::n(Bsp::e(self.editor.clip_status(), self.editor.edit_status()), x);
let outputs = |x|Bsp::s(row(2, self.output_row_header(), self.output_row_cells()), Fill::y(x));
let playing = |x|Bsp::s(row(2, self.elapsed_row_header(), self.elapsed_row_cells()), Fill::y(x));
let next = |x|Bsp::s(row(2, self.next_row_header(), self.next_row_cells()), Fill::y(x));
let tracks = |x|Bsp::s(row(3, self.track_row_header(), self.track_row_cells()), Fill::y(x));
let scenes = |x|Bsp::s(row((self.size.h() as u16).saturating_sub(13),
self.scene_row_headers(),
self.scene_row_cells()), x);
let inputs = |x|Bsp::n(
row(2, self.input_row_header(), self.input_row_cells()),
Fill::y(x));
self.size.of(
toolbar(pool(editing(inputs(outputs(playing(next(tracks(scenes(Fill::xy("")))))))))))
//let enclosed = |x|Outer(Style::default().fg(Color::Rgb(72,72,72))).enclose(x);
//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
//Bsp::s(arrrrrr, enclosed(&self.editor))
});
impl Arranger {
pub const LEFT_SEP: char = '▎';
pub const TRACK_MIN_WIDTH: usize = 4;
pub fn scenes_with_heights (&self, h: usize) -> impl Iterator<Item = (usize, &ArrangerScene, usize, usize)> {
let mut y = 0;
let editing = self.editing;
let (selected_track, selected_scene) = match self.selected {
ArrangerSelection::Clip(t, s) => (Some(t), Some(s)),
_ => (None, None)
};
self.scenes.iter().enumerate().map(move|(s, scene)|{
let active = editing && selected_track.is_some() && selected_scene == Some(s);
let height = if active { 15 } else { h };
let data = (s, scene, y, y + height);
y += height;
data
})
}
pub fn tracks_with_widths (&self)
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let active = match self.selected {
ArrangerSelection::Track(t) => Some(t),
ArrangerSelection::Clip(t, _) => Some(t),
_ => None
};
Self::tracks_with_widths_static(self.tracks.as_slice(), active)
}
fn tracks_with_widths_static (tracks: &[ArrangerTrack], active: Option<usize>)
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let mut x = 0;
tracks.iter().enumerate().map(move |(index, track)|{
let width = if Some(index) == active { 40 } else { track.width };
let data = (index, track, x, x + width);
x += width;
data
})
}
fn output_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(192);
let bg = TuiTheme::g(48);
(move||Tui::bold(true, Tui::fg_bg(fg, bg, "[ ] Out 1: NI")).boxed()).into()
}
fn output_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
//let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color().dark.into();
let cell = Bsp::s(format!(" M S "), phat_hi(color.dark.rgb, color.darker.rgb));
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell)))
})).boxed()).into()
}
fn elapsed_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Playing")).boxed()).into()
}
fn elapsed_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
//let color = track.color();
let color: ItemPalette = track.color().dark.into();
let timebase = self.clock().timebase();
let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb,
if let Some((_, Some(clip))) = track.player.play_clip().as_ref() {
let length = clip.read().unwrap().length;
let elapsed = track.player.pulses_since_start().unwrap() as usize;
format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64))
} else {
String::new()
});
let cell = Bsp::s(value, phat_hi(color.dark.rgb, color.darker.rgb));
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell))
})).boxed()).into()
}
fn next_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Next")).boxed()).into()
}
fn next_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
let color: ItemPalette = track.color();
let color: ItemPalette = track.color().dark.into();
let cell = Self::cell_until_next(track, &self.clock().playhead);
let cell = Self::cell(color, Tui::bold(true, cell));
let cell = Tui::fg_bg(color.lightest.rgb, color.base.rgb, cell);
let cell = Bsp::s(cell, phat_hi(color.dark.rgb, color.darker.rgb));
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell))
})).boxed()).into()
}
fn track_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into()
}
fn track_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let iter = ||self.tracks_with_widths();
(move||Fixed::y(2, Map::new(iter, move|(_, track, x1, x2), i| {
let color = track.color();
let name = Push::x(1, &track.name);
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16,
Tui::fg_bg(color.lightest.rgb, color.base.rgb,
phat_cell(color, color.darkest.rgb.into(),
Tui::bold(true, name))))) })).boxed()).into()
}
fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(0), TuiTheme::g(200), "[ ] In 1: Korg"))).boxed()).into()
}
fn input_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let cell = Bsp::s("[Rec]", "[Mon]");
let color: ItemPalette = track.color().dark.into();
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell)))
})).boxed()).into()
}
fn scene_row_headers <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||{
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
let selected_scene = match self.selected {
ArrangerSelection::Scene(s) => Some(s),
_ => None
};
Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new(
||self.scenes_with_heights(2),
move|(_, scene, y1, y2), i| {
let h = (y2 - y1) as u16;
let name = format!("🭬{}", &scene.name);
let color = scene.color();
let cell = phat_sel_3(
selected_scene == Some(i),
Push::x(1, Tui::bold(true, name.clone())),
Push::x(1, Tui::bold(true, name)),
if selected_scene.map(|s|s + 1) == Some(i) {
None
} else {
Some(last_color.read().unwrap().base.rgb)
},
if selected_scene == Some(i) {
color.light.rgb
} else {
color.base.rgb
},
Color::Rgb(0, 0, 0)
);
*last_color.write().unwrap() = color;
map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell))
}
))).boxed()
}).into()
}
fn scene_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let editing = self.editing;
let (selected_track, selected_scene) = match self.selected {
ArrangerSelection::Clip(t, s) => (Some(t), Some(s)),
_ => (None, None)
};
let tracks = move||self.tracks_with_widths();
let scenes = ||self.scenes_with_heights(2);
(move||Fixed::y(2, Map::new(tracks, move|(_, track, x1, x2), t| {
let w = (x2 - x1) as u16;
let cell = Bsp::s("[Rec]", "[Mon]");
let color: ItemPalette = track.color().dark.into();
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
let cells = Map::new(scenes, move|(_, scene, y1, y2), s| {
let h = (y2 - y1) as u16;
let color = scene.color();
let name = "";
let last = last_color.read().unwrap().clone();
//let cell = phat_sel_3(
//selected_track == Some(i) && selected_scene == Some(j),
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
//if selected_track == Some(i) && selected_scene.map(|s|s+1) == Some(j) {
//None
//} else {
//Some(TuiTheme::g(32).into())
//},
//TuiTheme::g(32).into(),
//TuiTheme::g(32).into(),
//);
let active = editing && selected_track == Some(t) && selected_scene == Some(s);
let editor = Thunk::new(||&self.editor);
let cell = Thunk::new(move||phat_sel_3(
selected_track == Some(t) && selected_scene == Some(s),
Tui::fg(TuiTheme::g(64), Push::x(1, Tui::bold(true, name.to_string()))),
Tui::fg(TuiTheme::g(64), Push::x(1, Tui::bold(true, name.to_string()))),
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) {
None
} else {
Some(TuiTheme::g(32).into())
},
TuiTheme::g(32).into(),
TuiTheme::g(32).into(),
));
let cell = Either(active, editor, cell);
map_south(
y1 as u16,
h + 1,
Fill::x(cell)
)
});
map_east(
x1 as u16,
w,
Fixed::x(w, Tui::bg(Color::Rgb(0,0,0), Fill::y(cells)).boxed())
)
})).boxed()).into()
}
fn track_column_separators <'a> (&'a self) -> impl Content<TuiOut> + 'a {
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
let fg = Color::Rgb(64,64,64);
Map::new(move||self.tracks_with_widths(), move|(_n, _track, x1, x2), _i|{
Push::x(scenes_w, map_east(x1 as u16, (x2 - x1) as u16,
Fixed::x((x2 - x1) as u16, Tui::fg(fg, RepeatV(&"·")))))
})
}
/// name and width of track
fn cell_name (track: &ArrangerTrack, _w: usize) -> impl Content<TuiOut> {
Tui::bold(true, Tui::fg(track.color.lightest.rgb, track.name().clone()))
}
/// beats until switchover
fn cell_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
-> Option<impl Content<TuiOut>>
{
let timebase = &current.timebase;
let mut result = String::new();
if let Some((t, _)) = track.player.next_clip().as_ref() {
let target = t.pulse.get();
let current = current.pulse.get();
if target > current {
result = format!("-{:>}", timebase.format_beats_0_short(target - current))
}
}
Some(result)
}
fn scene_rows (&self) -> impl Content<TuiOut> + use<'_> {
let scenes_w = 16.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
Map::new(||self.scenes_with_heights(1), move|(_, scene, y1, y2), i| {
let h = (y2 - y1) as u16;
let color = scene.color();
let cell = Self::cell(color, scene.name.clone());
let cell = Fixed::y(h, Fixed::x(scenes_w, cell));
map_south(y1 as u16, 1, cell)
})
}
pub fn scene_heights (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> {
let mut total = 0;
if factor == 0 {
scenes.iter().map(|scene|{
let pulses = scene.pulses().max(PPQ);
total += pulses;
(pulses, total - pulses)
}).collect()
} else {
(0..=scenes.len()).map(|i|{
(factor*PPQ, factor*PPQ*i)
}).collect()
}
}
fn cell_scene <'a> (
tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize
) -> impl Content<TuiOut> + use<'a> {
let height = 1.max((pulses / PPQ) as u16);
let playing = scene.is_playing(tracks);
let icon = Tui::bg(
scene.color.base.rgb, if playing { "" } else { " " }
);
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.clone()))
);
let clips = Map::new(||Arranger::tracks_with_widths_static(tracks, None), move|(index, track, x1, x2), _|
Push::x((x2 - x1) as u16, Self::cell_clip(scene, index, track, (x2 - x1) as u16, height))
);
Fixed::y(height, Bsp::e(icon, Bsp::e(name, clips)))
}
fn cell_clip <'a> (
scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16
) -> impl Content<TuiOut> + use<'a> {
scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{
let clip = clip.read().unwrap();
let mut bg = TuiTheme::border_bg();
let name = clip.name.to_string();
let max_w = name.len().min((w as usize).saturating_sub(2));
let color = clip.color;
bg = color.dark.rgb;
if let Some((_, Some(ref playing))) = track.player.play_clip() {
if *playing.read().unwrap() == *clip {
bg = color.light.rgb
}
};
Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w]))));
}))
}
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock))))
}
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
let w = self.size.w();
let clip_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let pool_w = if self.pool.visible { clip_w } else { 0 };
let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool))))
}
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
}
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(1), |_, _|"")
//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;
//}
////}
//}
//})
}
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 = 16.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
let focused = true;
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)
};
})
}
/// A 1-row cell.
fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> {
Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field))
}
}
/// A phat line
fn phat_lo (fg: Color, bg: Color) -> impl Content<TuiOut> {
Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"")))
}
/// A phat line
fn phat_hi (fg: Color, bg: Color) -> impl Content<TuiOut> {
Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"")))
}
/// A cell that is 3-row on its own, but stacks, giving (N+1)*2 rows per N cells.
fn phat_cell <T: Content<TuiOut>> (
color: ItemPalette, last: ItemPalette, field: T
) -> impl Content<TuiOut> {
Bsp::s(phat_lo(color.base.rgb, last.base.rgb),
Bsp::n(phat_hi(color.base.rgb, last.base.rgb),
Fixed::y(1, Fill::x(Tui::fg_bg(color.lightest.rgb, color.base.rgb, field))),
)
)
}
fn phat_cell_3 <T: Content<TuiOut>> (
field: T, top: Color, middle: Color, bottom: Color
) -> impl Content<TuiOut> {
Bsp::s(phat_lo(middle, top),
Bsp::n(phat_hi(middle, bottom),
Fill::y(Fill::x(Tui::bg(middle, field))),
)
)
}
fn phat_sel_3 <T: Content<TuiOut>> (
selected: bool, field_1: T, field_2: T, top: Option<Color>, middle: Color, bottom: Color
) -> impl Content<TuiOut> {
let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle);
Either(selected,
Tui::bg(middle, Outer(border).enclose( Align::w(Bsp::s("", Bsp::s(field_1, ""))))),
Bsp::s(
Fixed::y(1, top.map(|top|phat_lo(middle, top))),
Bsp::n(phat_hi(middle, bottom),
Fixed::y(1, Fill::x(Tui::bg(middle, field_2))),
)
))
}
//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)
//};
//}
//}
//impl Arranger {
//fn render_mode (state: &Self) -> impl Content<TuiOut> + use<'_> {
//match state.mode {
//ArrangerMode::H => todo!("horizontal arranger"),
//ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
//}
//}
//}
//render!(TuiOut: (self: Arranger) => {
//let pool_w = if self.pool.visible { self.splits[1] } else { 0 };
//let color = self.color;
//let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)),
//Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)),
//Bsp::n(TransportView::new(true, &self.clock),
//Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)),
//Bsp::n(Fill::x(Fixed::y(20,
//Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")),
//Bsp::a(
//Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))),
//Self::render_mode(self))))), Fill::y(&"fixme: self.editor"))))));
//self.size.of(layout)
//});
//Align::n(Fill::xy(lay!(
//Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))),
//Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))),
//Align::n(Fill::xy(ArrangerVColSep::from(self))),
//Align::n(Fill::xy(ArrangerVClips::new(self, 1))),
//Align::n(Fill::xy(ArrangerVCursor::from((self, 1))))))))))))))));
//Align::n(Fill::xy(":")))))))))))));
//"todo:"))))))));
//Bsp::s(
//Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))),
//Bsp::s(
//Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))),
//Fill::x(Fixed::y(1, ArrangerVOuts::from(self)))))))))))));
//Bsp::s(
//Bsp::s(
//Bsp::s(
//Fill::xy(ArrangerVClips::new(self, 1)),
//Fill::x(ArrangerVOuts::from(self)))))