From a66a6a96692f09c9175188f1a558b34faf8b77ef Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 25 Jan 2025 19:00:22 +0100 Subject: [PATCH] deduplicate scene cell rendering --- output/src/op_cond.rs | 1 - tek/src/cli.rs | 4 +- tek/src/model.rs | 17 +- tek/src/view.rs | 385 ++++++++++++++++++++++++------------------ 4 files changed, 225 insertions(+), 182 deletions(-) diff --git a/output/src/op_cond.rs b/output/src/op_cond.rs index 688fbc3e..7f5ad976 100644 --- a/output/src/op_cond.rs +++ b/output/src/op_cond.rs @@ -55,4 +55,3 @@ impl, B: Render> Content for Either { if *cond { a.render(to) } else { b.render(to) } } } - diff --git a/tek/src/cli.rs b/tek/src/cli.rs index 65bd4d74..b5055f93 100644 --- a/tek/src/cli.rs +++ b/tek/src/cli.rs @@ -107,7 +107,7 @@ impl Tek { midi_ins: { let mut midi_ins = vec![]; for (index, connect) in midi_froms.iter().enumerate() { - let port = JackMidiIn::new(jack, &format!("m/{index}"), &[connect.clone()])?; + let port = JackMidiIn::new(jack, &format!("M/{index}"), &[connect.clone()])?; midi_ins.push(port); } midi_ins @@ -115,7 +115,7 @@ impl Tek { midi_outs: { let mut midi_outs = vec![]; for (index, connect) in midi_tos.iter().enumerate() { - let port = JackMidiOut::new(jack, &format!("{index}/m"), &[connect.clone()])?; + let port = JackMidiOut::new(jack, &format!("{index}/M"), &[connect.clone()])?; midi_outs.push(port); } midi_outs diff --git a/tek/src/model.rs b/tek/src/model.rs index 325b6585..386e79a4 100644 --- a/tek/src/model.rs +++ b/tek/src/model.rs @@ -22,6 +22,7 @@ use crate::*; pub perf: PerfModel, pub editing: AtomicBool, pub history: Vec, + pub ports: std::collections::BTreeMap>, /// View definition pub view: SourceIter<'static>, @@ -331,22 +332,6 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync { fn scene_longest (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } - fn scenes_sizes (&self, editing: bool, height: usize, larger: usize,) - -> impl Iterator + Send + Sync - { - let mut y = 0; - let (selected_track, selected_scene) = match self.selected() { - Selection::Clip(t, s) => (Some(t.saturating_sub(1)), Some(s.saturating_sub(1))), - _ => (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 { larger } else { height }; - let data = (s, scene, y, y + height); - y += height; - data - }) - } fn scene (&self) -> Option<&Scene> { self.selected().scene().and_then(|s|self.scenes().get(s)) } diff --git a/tek/src/view.rs b/tek/src/view.rs index e81254b1..e781d2bb 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -50,42 +50,28 @@ impl Default for ViewCache { } view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { ":editor" => (&self.editor).boxed(), + ":inputs" => self.view_inputs().boxed(), + ":outputs" => self.view_outputs().boxed(), ":pool" => self.view_pool().boxed(), ":sample" => ().boxed(),//self.view_sample(self.is_editing()).boxed(), ":sampler" => ().boxed(),//self.view_sampler(self.is_editing(), &self.editor).boxed(), + ":scene-add" => self.view_scene_add().boxed(), + ":scenes" => self.view_scenes().boxed(), ":status" => self.view_editor().boxed(), ":toolbar" => self.view_clock().boxed(), - ":tracks" => self.view_tracks().boxed(), ":track-add" => self.view_track_add().boxed(), - ":inputs" => self.view_inputs().boxed(), - ":outputs" => self.view_outputs().boxed(), - ":scenes" => self.view_scenes().boxed(), - ":scene-add" => self.view_scene_add().boxed(), + ":tracks" => self.view_tracks().boxed(), }); provide_num!(u16: |self: Tek| { - ":w-sidebar-w" => self.w_sidebar(), - ":h-sample-h" => if self.is_editing() { 0 } else { 5 }, - ":w-samples-w" => if self.is_editing() { 4 } else { 11 }, - ":y-samples-y" => if self.is_editing() { 1 } else { 0 }, - ":h-ins" => self.h_inputs(), - ":h-outs" => self.h_outputs(), - ":y-ins" => (self.size.h() as u16).saturating_sub(self.h_inputs()), - ":y-outs" => (self.size.h() as u16).saturating_sub(self.h_outputs()), + ":h-ins" => self.h_inputs(), + ":h-outs" => self.h_outputs(), + ":h-sample" => if self.is_editing() { 0 } else { 5 }, + ":w-samples" => if self.is_editing() { 4 } else { 11 }, + ":w-sidebar" => self.w_sidebar(), + ":y-ins" => (self.size.h() as u16).saturating_sub(self.h_inputs() + 1), + ":y-outs" => (self.size.h() as u16).saturating_sub(self.h_outputs() + 1), + ":y-samples" => if self.is_editing() { 1 } else { 0 }, }); -macro_rules! per_track { - ($area:expr;|$self:ident,$track:ident,$index:ident|$content:expr) => {{ - let tracks = ||$self.tracks_sizes($self.is_editing(), $self.editor_w()); - Box::new(move||Align::x(Map::new(tracks, move|(_, $track, x1, x2), $index| { - let width = (x2 - x1) as u16; - let content = Fixed::y(1, $content); - let styled = Tui::fg_bg($track.color.lightest.rgb, $track.color.base.rgb, content); - Either(x2 >= $area, (), - map_east(x1 as u16, width, Align::y(Fixed::x(width, styled)))) }))).into() }} } -macro_rules! io_header { - ($self:ident, $key:expr, $label:expr, $count:expr, $content:expr) => { - (move||{ - let button = $self.button($key, format!("{:10} ({})", $label, $count)); - Bsp::s(Fill::x(Align::w(button)), $content).boxed() }).into() } } macro_rules! rewrite { ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{$buf.clear();write!($buf, $($rest)*)} } } impl Tek { @@ -189,69 +175,51 @@ impl Tek { Fill::xy(Align::c(Fixed::x(w, b))) )) } - fn view_inputs (&self) -> impl Content + use<'_> { - let fg = Tui::g(224); - let bg = Tui::g(64); - let conn = move|conn: &PortConnect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, conn.info())))); - let header: ThunkBox<_> = io_header!(self, " I ", " midi ins", self.midi_ins.len(), - Map::new(||self.midi_ins.iter(), move|input, index|map_south(index as u16, 1, Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name())))), - Map::new(||input.conn().iter(), move|connect, index|map_south(index as u16, 0, - Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))))); - let cells: ThunkBox<_> = per_track!(self.size.w();|self, track, t|{ - let rec = track.player.recording; - let mon = track.player.monitoring; - let bg = if self.selected().track() == Some(t+1) { track.color.light.rgb } else { track.color.base.rgb }; - let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset }; - Bsp::s( - Self::wrap(bg, fg, Tui::bold(true, Bsp::e( - Tui::fg_bg(if rec { White } else { track.color.darkest.rgb }, bg, "Recrd"), - Tui::fg_bg(if mon { White } else { track.color.darkest.rgb }, bg, "Monit"), - ))), - Map::new(||self.midi_ins.iter(), move|input, index|map_south(index as u16, 1, - Self::wrap(bg, fg, Bsp::e( - Tui::fg_bg(if rec { White } else { track.color.darkest.rgb }, bg, "R▞▞▞▞"), - Tui::fg_bg(if mon { White } else { track.color.darkest.rgb }, bg, "M▞▞▞▞"), - )))))}); - Tui::bg(Black, self.view_row(self.w(), self.h_inputs(), header, cells)) - } - fn h_inputs (&self) -> u16 { - let mut h = 1; - for midi_in in self.midi_ins.iter() { h += 1 + midi_in.conn().len() as u16 } - h - } - fn view_outputs (&self) -> impl Content + use<'_> { - let fg = Tui::g(224); - let bg = Tui::g(64); - let conn = move|conn: &PortConnect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, conn.info())))); - let header: ThunkBox<_> = io_header!(self, " O ", " midi outs", self.midi_outs.len(), - Map::new(||self.midi_outs.iter(), move|output, index|map_south(index as u16, 0, Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(output.name())))), - Map::new(||output.conn().iter(), move|connect, index|map_south(index as u16, 0, - Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))))); - let mute = false; - let solo = false; - let cells: ThunkBox<_> = per_track!(self.size.w();|self, track, t|{ - let bg = if self.selected().track() == Some(t+1) { track.color.light.rgb } else { track.color.base.rgb }; - let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset }; - Bsp::s( - Self::wrap(bg, fg, Tui::bold(true, Bsp::e( - Tui::fg_bg(if mute { White } else { track.color.darkest.rgb }, bg, "Play "), - Tui::fg_bg(if solo { White } else { track.color.darkest.rgb }, bg, "Solo "),))), - Map::new(||self.midi_outs.iter(), move|output, index|map_south(index as u16, 1u16, - Self::wrap(bg, fg, Bsp::e( - Tui::fg_bg(if mute { White } else { track.color.darkest.rgb }, bg, "P▞▞▞▞"), - Tui::fg_bg(if solo { White } else { track.color.darkest.rgb }, bg, "S▞▞▞▞"))))))}); - Tui::bg(Black, self.view_row(self.w(), self.h_outputs(), header, cells)) - } - fn h_outputs (&self) -> u16 { - let mut h = 1; - for midi_out in self.midi_outs.iter() { h += 1 + midi_out.conn().len() as u16 } - h +} +macro_rules! per_track { + ($area:expr;|$self:ident,$track:ident,$index:ident|$content:expr) => {{ + let tracks = ||$self.tracks_sizes($self.is_editing(), $self.editor_w()) + .map_while(|(t, track, x1, x2)|if x2 >= $area { + None } else { Some((t, track, x1, x2)) }); + Box::new(move||Align::x(Map::new(tracks, move|(_, $track, x1, x2), $index| { + let width = (x2 - x1) as u16; + let content = Fixed::y(1, $content); + let styled = Tui::fg_bg($track.color.lightest.rgb, $track.color.base.rgb, content); + map_east(x1 as u16, width, Align::y(Fixed::x(width, styled))) }))).into() }} } +macro_rules! io_header { + ($self:ident, $key:expr, $label:expr, $count:expr, $content:expr) => { + (move||Fill::xy(Align::nw({ + let button = $self.button($key, format!("{:10} ({})", $label, $count)); + Bsp::s(button, $content) + })).boxed()).into() } } +macro_rules! per_track_top { + ($area:expr;|$self:ident,$track:ident,$index:ident|$content:expr) => {{ + let tracks = ||$self.tracks_sizes($self.is_editing(), $self.editor_w()) + .map_while(|(t, track, x1, x2)|if x2 >= $area { + None } else { Some((t, track, x1, x2)) }); + (move||Fill::xy(Align::nw(Map::new(tracks, move|(_, $track, x1, x2), $index| { + let width = (x2 - x1) as u16; + let content = Fixed::y(1, $content); + let styled = Tui::fg_bg($track.color.lightest.rgb, $track.color.base.rgb, content); + map_east(x1 as u16, width, Fixed::x(width, Align::n(styled))) }))).boxed()).into() }} } +impl Tek { + + fn view_row_top <'a> ( + &'a self, w: u16, h: u16, a: impl Content + 'a, b: impl Content + 'a + ) -> impl Content + 'a { + Fixed::y(h, Bsp::a( + Fill::xy(Align::nw(Fixed::xy(self.w_sidebar() as u16, h, a))), + Fill::xy(Align::n(Fixed::xy(w, h, b))) + )) } fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { Bsp::e(Tui::fg_bg(bg, Reset, "▐"), Bsp::w(Tui::fg_bg(bg, Reset, "▌"), Tui::fg_bg(fg, bg, content))) } + + // TRACKS ///////////////////////////////////////////////////////////////////////////////////// + fn w_tracks (&self, editing: bool, bigger: usize) -> u16 { + self.tracks_sizes(editing, bigger).last().map(|(_, _, _, x)|x as u16).unwrap_or(0) + } fn tracks_sizes <'a> (&'a self, editing: bool, bigger: usize) -> impl Iterator + Send + Sync + 'a { @@ -268,11 +236,8 @@ impl Tek { data }) } - fn w_tracks (&self, editing: bool, bigger: usize) -> u16 { - self.tracks_sizes(editing, bigger).last().map(|(_, _, _, x)|x as u16).unwrap_or(0) - } fn view_tracks (&self) -> impl Content + use<'_> { - let height = 1; + let height = 3; let header: ThunkBox<_> = (move||Tui::bg(Tui::g(32), Fill::x(Align::w(self.view_track_add()))).boxed()).into(); let content: ThunkBox<_> = per_track!(self.size.w();|self, track, t|{ @@ -287,7 +252,101 @@ impl Tek { }); self.view_row(self.w(), height, header, content) } - fn scenes_sizes (&self, editing: bool, height: usize, larger: usize,) + + // INPUTS ///////////////////////////////////////////////////////////////////////////////////// + fn inputs_sizes (&self) -> impl Iterator + Send + Sync { + let mut y = 0; + self.midi_ins.iter().enumerate().map(move|(i, input)|{ + let height = 1 + input.conn().len(); + let data = (i, input, y, y + height); + y += height; + data + }) + } + fn h_inputs (&self) -> u16 { + 1 + self.inputs_sizes().last().map(|(_, _, _, y)|y as u16).unwrap_or(0) + } + fn view_inputs (&self) -> impl Content + use<'_> { + let fg = Tui::g(224); + let bg = Tui::g(64); + let header: ThunkBox<_> = io_header!(self, " I ", " midi ins", self.midi_ins.len(), + Map::new(||self.inputs_sizes(), + move|(index, input, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name())))), + Map::new(||input.conn().iter(), move|connect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))))); + + let cells: ThunkBox<_> = per_track_top!(self.size.w();|self, track, t|{ + let rec = track.player.recording; + let mon = track.player.monitoring; + let bg = if self.selected().track() == Some(t+1) { track.color.light.rgb } else { track.color.base.rgb }; + let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset }; + Bsp::s( + Fixed::y(1, Align::n(Self::wrap(bg, fg, Tui::bold(true, Bsp::e( + Tui::fg_bg(if rec { White } else { track.color.darkest.rgb }, bg, "Rec "), + Tui::fg_bg(if mon { White } else { track.color.darkest.rgb }, bg, "Mon ")))))), + Fill::y(Tui::bg(Green, Map::new( + ||self.inputs_sizes(), + move|(index, input, y, y2), _|map_south(y as u16, (y2-y) as u16, + Self::wrap(bg, fg, Bsp::e( + Tui::fg_bg(if rec { White } else { track.color.darkest.rgb }, bg, "R▞▞▞▞"), + Tui::fg_bg(if mon { White } else { track.color.darkest.rgb }, bg, "M▞▞▞▞"))))))))}); + + Bsp::<_, Push>>::s( + Tui::bg(Black, self.view_row_top(self.w(), self.h_inputs(), header, Tui::bg(Red, cells))), + Push::xy(self.w_sidebar(), 1, per_track_top!(self.size.w();|self, track, t|"kyp")) + ) + } + + // OUTPUTS //////////////////////////////////////////////////////////////////////////////////// + fn h_outputs (&self) -> u16 { + 1 + self.outputs_sizes().last().map(|(_, _, _, y)|y as u16).unwrap_or(0) + } + fn outputs_sizes (&self) -> impl Iterator + Send + Sync { + let mut y = 0; + self.midi_outs.iter().enumerate().map(move|(i, output)|{ + let height = 1 + output.conn().len(); + let data = (i, output, y, y + height); + y += height; + data + }) + } + fn view_outputs (&self) -> impl Content + use<'_> { + let fg = Tui::g(224); + let bg = Tui::g(64); + let header: ThunkBox<_> = io_header!(self, " O ", " midi outs", self.midi_outs.len(), + Map::new(||self.outputs_sizes(), + move|(index, output, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(output.name())))), + Map::new(||output.conn().iter(), move|connect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))))); + + let mute = false; + let solo = false; + let cells: ThunkBox<_> = per_track_top!(self.size.w();|self, track, t|{ + let bg = if self.selected().track() == Some(t+1) { track.color.light.rgb } else { track.color.base.rgb }; + let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset }; + Bsp::s( + Fixed::y(1, Self::wrap(bg, fg, Tui::bold(true, Bsp::e( + Tui::fg_bg(if mute { White } else { track.color.darkest.rgb }, bg, "Play "), + Tui::fg_bg(if solo { White } else { track.color.darkest.rgb }, bg, "Solo "),)))), + Fill::y(Map::new( + ||self.outputs_sizes(), + move|(index, output, y, y2), _|map_south(y as u16, (y2-y) as u16, + Self::wrap(bg, fg, Bsp::e( + Tui::fg_bg(if mute { White } else { track.color.darkest.rgb }, bg, "P▞▞▞▞"), + Tui::fg_bg(if solo { White } else { track.color.darkest.rgb }, bg, "S▞▞▞▞")))))))}); + + Tui::bg(Black, self.view_row_top(self.w(), self.h_outputs(), + Tui::bg(Yellow, header), + Tui::bg(Blue, cells))) + } + + // SCENES ///////////////////////////////////////////////////////////////////////////////////// + fn h_scenes (&self, editing: bool, height: usize, larger: usize) -> u16 { + self.scenes_sizes(editing, height, larger).last().map(|(_, _, _, y)|y as u16).unwrap_or(0) + } + fn scenes_sizes (&self, editing: bool, height: usize, larger: usize) -> impl Iterator + Send + Sync { let mut y = 0; @@ -303,91 +362,91 @@ impl Tek { data }) } - fn h_scenes (&self, editing: bool, height: usize, larger: usize) -> u16 { - self.scenes_sizes(editing, height, larger).last().map(|(_, _, _, y)|y as u16).unwrap_or(0) - } fn view_scenes (&self) -> impl Content + use<'_> { - let bstyle = Style::default().fg(Tui::g(0)); - let size_w = self.size.w(); - let size_h = self.size.h(); + let w = self.size.w() as u16; + let h = self.size.h() as u16; let editing = self.is_editing(); let tracks = move||self.tracks_sizes(editing, self.editor_w()); + let header = move||self.view_scenes_header(editing); + let content = move||self.view_scenes_content(editing, h); + self.view_row(w, h, + <_ as Into>>::into(header), + <_ as Into>>::into(content)) + } + fn view_scenes_header (&self, editing: bool) -> impl Content + use<'_> { + let size_h = self.size.h(); + Align::y(Map::new( + move||self.scenes_sizes(editing, 2, 15).map_while( + move|(s, scene, y1, y2)|if y2 > size_h { + None + } else { Some((s, scene, y1, y2, if s == 0 { + None + } else { + Some(self.scenes[s-1].color) + })) }), + move|(_, scene, y1, y2, prev), s| { + let height = (1 + y2 - y1) as u16; + let bg = scene.color; + let fg = scene.color.lightest.rgb; + let name = Some(scene.name.clone()); + map_south(y1 as u16, height, Fixed::y(height, self.view_clip_cell( + true, s, &bg, prev, name, " ⯈ ", fg))) + })).boxed() + } + fn view_scenes_content (&self, editing: bool, height: u16) -> ThunkBox { let selected_track = self.selected().track(); let selected_scene = self.selected().scene(); - let header_cell = move|t, s, bg: &ItemPalette, last: Option, name: Arc|{ - let selected = t && self.selected().scene() == Some(s+1); - let neighbor = t && self.selected().scene() == Some(s); - Phat { - width: 0, - height: 0, - selected, - content: Tui::bold(true, Bsp::e("🭬", name.clone())), - colors: colors( - bg, last, selected, neighbor, s == self.scenes.len().saturating_sub(1) - ) - } - }; - let header = move||{ - Align::y(Map::new( - move||self.scenes_sizes(editing, 2, 15).map_while( - move|(s, scene, y1, y2)|if y2 > size_h { None } else { Some((s, scene, y1, y2, if s == 0 { - None - } else { - Some(self.scenes[s-1].color) - })) }), - move|(_, scene, y1, y2, last), s| { - let height = (1 + y2 - y1) as u16; - map_south(y1 as u16, height, Fixed::y(height, - header_cell(true, s, &scene.color, last, scene.name.clone()))) })).boxed() - }; - let content: ThunkBox<_> = per_track!(self.size.w(); |self, track, t|{ + per_track!(self.size.w(); |self, track, t|{ let tab = " Tab "; let same_track = selected_track == Some(t+1); Map::new( move||self.scenes_sizes(editing, 2, 15).map_while( - move|(s, scene, y1, y2)|if y2 > size_h { None } else { Some((s, scene, y1, y2, if s == 0 { - None - } else { - Some(self.scenes[s-1].clips[t].as_ref() + move|(s, scene, y1, y2)|if y2 as u16 > height { None } else { Some((s, scene, y1, y2, if s == 0 { + None } else { Some(self.scenes[s-1].clips[t].as_ref() .map(|c|c.read().unwrap().color) .unwrap_or(ItemPalette::G[32])) })) }), - move|(_, scene, y1, y2, last), s| { + move|(_, scene, y1, y2, prev), s| { let height = (1 + y2 - y1) as u16; - let (fg, bg) = if let Some(clip) = &scene.clips[t] { + let (name, fg, bg) = if let Some(clip) = &scene.clips[t] { let clip = clip.read().unwrap(); - (clip.color.lightest.rgb, clip.color) + (Some(clip.name.clone()), clip.color.lightest.rgb, clip.color) } else { - (Tui::g(96), ItemPalette::G[32]) + (None, Tui::g(96), ItemPalette::G[32]) }; - let selected = same_track && selected_scene == Some(s+1); - let neighbor = same_track && selected_scene == Some(s); - let active = editing && selected; - map_south(y1 as u16, height, Fixed::y(height, Phat { - width: 0, - height: 0, - selected, - content: { - let clip = scene.clips[t].clone(); - let icon = " ⏹ "; - let name = clip.map(|c|c.read().unwrap().name.clone()); - Bsp::a( - Fill::xy(Align::nw(Tui::fg(fg, Bsp::e(icon, Bsp::e(Tui::bold(true, name), " "))))), - When(active, &self.editor) - ) - }, - colors: colors( - &bg, last, selected, neighbor, s == self.scenes.len().saturating_sub(1) - ), - })) - }) - }); - //let border = move|x|Outer(false, bstyle).enclose_bg(x); - self.view_row( - self.w(), - size_h as u16, - <_ as Into>>::into(header), - <_ as Into>>::into(content), - ) + let active = editing && same_track && selected_scene == Some(s+1); + let edit = |x|Bsp::b(x, When(active, &self.editor)); + map_south(y1 as u16, height, edit(Fixed::y(height, self.view_clip_cell( + same_track, s, &bg, prev, name, " ⏹ ", fg))))}) + }) + } + fn view_clip_cell <'a> ( + &self, + same_track: bool, + scene: usize, + color: &ItemPalette, + prev: Option, + name: Option>, + icon: &'a str, + fg: Color, + ) -> impl Content + use<'a> { + let selected_scene = self.selected().scene(); + let selected = same_track && selected_scene == Some(scene+1); + let neighbor = same_track && selected_scene == Some(scene); + self.view_cell(scene, color, prev, selected, neighbor, + Fill::x(Align::w(Tui::fg(fg, Tui::bold(true, Bsp::e(icon, name)))))) + } + fn view_cell > ( + &self, + scene: usize, + color: &ItemPalette, + prev: Option, + selected: bool, + neighbor: bool, + content: T, + ) -> Phat { + let is_last = scene == self.scenes.len().saturating_sub(1); + let colors = colors(color, prev, selected, neighbor, is_last); + Phat { width: 0, height: 0, selected, content, colors, } } fn w (&self) -> u16 { self.tracks_sizes(self.is_editing(), self.editor_w()) @@ -431,8 +490,8 @@ fn button <'a> ( label: impl Content + 'a ) -> impl Content + 'a { Tui::bold(true, Bsp::e( - Margin::x(1, Tui::fg_bg(Tui::g(0), Tui::orange(), key)), - Margin::x(1, Tui::fg_bg(Tui::g(255), Tui::g(96), label)), + Tui::fg_bg(Tui::g(0), Tui::orange(), key), + Tui::fg_bg(Tui::g(255), Tui::g(96), label), )) } fn colors (