use crate::*; impl Arrangement { pub fn view_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { let mut h = 1u16; for track in self.tracks().iter() { h = h.max(self.midi_ins.len() as u16); } let h = h + 1; let list = Fixed::x(20, Fill::y(Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ add(&Fixed::y(1, Align::w( button_3("i", "nput ", format!("{}", self.midi_ins.len()), false)))); for (index, port) in self.midi_ins.iter().enumerate() { add(&Fixed::y(1, Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))); } }))); self.view_track_row_section(theme, list, button_2("I", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(self.track_width(index, track), Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ let index = 0; add(&Fixed::y(1, track.sequencer.midi_ins.get(0).map(|port| Tui::fg_bg(Rgb(255, 255, 255), track.color.base.rgb, Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))); for (index, port) in self.midi_ins().iter().enumerate() { add(&Fixed::y(1, Align::w(row!( Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), )))); } })))}}))))) } pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { let mut h = 1u16; for track in self.tracks().iter() { h = h.max(track.sequencer.midi_outs.len() as u16); } let h = h + 1; let list = Bsp::s( Fixed::y(1, Fill::x(Align::w(button_3("o", "utput", format!("{}", self.midi_outs.len()), false)))), Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ for (index, port) in self.midi_outs().iter().enumerate() { add(&Fill::x(Align::w(format!("·o{index:02} {}", port.name())))); } }))))); self.view_track_row_section(theme, list, button_2("O", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(self.track_width(index, track), Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ let index = 0; add(&Fixed::y(1, track.sequencer.midi_outs.get(0).map(|port| Tui::fg_bg(Rgb(255, 255, 255), track.color.base.rgb, Fill::x(Align::w(format!("·o{index:02} {}", port.name()))))))); for (index, port) in self.midi_outs().iter().enumerate() { add(&Fixed::y(1, Align::w(Bsp::e( Either(true, Tui::fg(Green, "●play "), "·play "), Either(false, Tui::fg(Yellow, "●solo "), "·solo "), )))); } })))}}))))) } pub fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { let mut h = 2u16; for track in self.tracks().iter() { h = h.max(track.devices.len() as u16); } self.view_track_row_section( theme, button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), button_2("D", "+", false), Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::xy(self.track_width(index, track), h + 1, Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, |_, index|format!("·d{index:02} {}", "--------")))))); } })) } } pub trait HasClipsSize { fn clips_size (&self) -> &Measure; } impl HasClipsSize for Arrangement { fn clips_size (&self) -> &Measure { &self.inner_size } } impl TracksView for Arrangement {} impl ClipsView for T {} pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasEditor + HasClipsSize { fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { let editor_width = self.editor().map(|e|e.width()); let active_track = self.selection().track(); let mut x = 0; self.tracks().iter().enumerate().map_while(move |(index, track)|{ let width = track.width.max(8); if x + width < self.clips_size().w() { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; Some(data) } else { None } }) } fn view_track_row_section <'a> ( &'a self, theme: ItemTheme, button: impl Content, button_add: impl Content, content: impl Content ) -> impl Content { Bsp::w(Fill::y(Fixed::x(4, Align::nw(button_add))), Bsp::e(Fixed::x(20, Fill::y(Align::nw(button))), Fill::xy(Align::c(content)))) } fn view_track_header <'a, T: Content> ( &'a self, theme: ItemTheme, content: T ) -> impl Content { Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) } fn view_track_names (&self, theme: ItemTheme) -> impl Content { self.view_track_row_section( theme, Bsp::s( button_3("t", "rack ", if let Some(track) = self.selection().track() { format!("{track}/{}", self.tracks().len()) } else { format!("{}", self.tracks().len()) }, false), button_3("s", "cene ", if let Some(scene) = self.selection().scene() { format!("{scene}/{}", self.scenes().len()) } else { format!("{}", self.scenes().len()) }, false) ), Bsp::s( button_2("T", "+", false), button_2("S", "+", false), ), Tui::bg(theme.darker.rgb, Fixed::y(2, Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(self.track_width(index, track), Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb } else { track.color.base.rgb }, Bsp::s(Fill::x(Align::nw(Bsp::e( format!("·t{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) ))), ""))) ); } }))))) } fn view_track_outputs <'a> (&'a self, theme: ItemTheme, h: u16) -> impl Content { self.view_track_row_section(theme, Bsp::s( Fill::x(Align::w(button_2("o", "utput", false))), Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ for port in self.midi_outs().iter() { add(&Fill::x(Align::w(port.name()))); } }))), button_2("O", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(self.track_width(index, track), Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), |port, index|Tui::fg(Rgb(255, 255, 255), Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( format!("·o{index:02} {}", port.name()))))))))))); } }))))) } fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 0u16; for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } self.view_track_row_section( theme, button_2("i", "nput", false), button_2("I", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::xy(self.track_width(index, track), h + 1, Align::nw(Bsp::s( Tui::bg(track.color.base.rgb, Fill::x(Align::w(row!( Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), )))), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))))); }}))))) } fn track_width (&self, index: usize, track: &Track) -> u16 { track.width as u16 } } pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { /// Default scene height. const H_SCENE: usize = 2; /// Default editor height. const H_EDITOR: usize = 15; fn h_scenes (&self) -> u16; fn w_side (&self) -> u16; fn w_mid (&self) -> u16; fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { let mut y = 0; self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ let height = if self.selection().scene() == Some(s) && self.editor().is_some() { 8 } else { Self::H_SCENE }; if y + height <= self.clips_size().h() { let data = (s, scene, y, y + height); y += height; Some(data) } else { None } }) } fn view_scenes_names (&self) -> impl Content { Fixed::x(20, Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, scene, ..) in self.scenes_with_sizes() { add(&self.view_scene_name(index, scene)); } })) } fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content { let h = if self.selection().scene() == Some(index) && let Some(editor) = self.editor() { 7 } else { Self::H_SCENE as u16 }; let bg = if self.selection().scene() == Some(index) { scene.color.light.rgb } else { scene.color.base.rgb }; Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s( Fill::x(Align::w(Bsp::e( format!("·s{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &scene.name)) ))), When(self.selection().scene() == Some(index) && self.is_editing(), Fill::xy(Align::nw(Bsp::s( self.editor().as_ref().map(|e|e.clip_status()), self.editor().as_ref().map(|e|e.edit_status()))))))))) } } pub trait ClipsView: TracksView + ScenesView + HasClipsSize + Send + Sync { fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { self.clips_size().of(Fill::xy(Bsp::a( Fill::xy(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), Stack::::east(move|column: &mut dyn FnMut(&dyn Render)|{ for (track_index, track, _, _) in self.tracks_with_sizes() { //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); column(&Fixed::x( track.width as u16, Fill::y(self.view_track_clips(track_index, track)) )) } })))) } fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { Stack::south(move|cell: &mut dyn FnMut(&dyn Render)|{ for (scene_index, scene, ..) in self.scenes_with_sizes() { let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); (format!(" ⏹ {}", &clip.name).into(), clip.color) } else { (" ⏹ -- ".into(), ItemTheme::G[32]) }; let fg = theme.lightest.rgb; let mut outline = theme.base.rgb; let bg = if self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) { outline = theme.lighter.rgb; theme.light.rgb } else if self.selection().track() == Some(track_index) || self.selection().scene() == Some(scene_index) { outline = theme.darkest.rgb; theme.base.rgb } else { theme.dark.rgb }; let w = if self.selection().track() == Some(track_index) && let Some(editor) = self.editor () { editor.width().max(24).max(track.width) } else { track.width } as u16; let y = if self.selection().scene() == Some(scene_index) && let Some(editor) = self.editor () { editor.height().max(12) } else { Self::H_SCENE } as u16; cell(&Fixed::xy(w, y, Bsp::b( Fill::xy(Outer(true, Style::default().fg(outline))), Fill::xy(Bsp::b( Bsp::b( Tui::fg_bg(outline, bg, Fill::xy("")), Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), ), Fill::xy(When(self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) && self.is_editing(), self.editor()))))))); } }) } } pub trait HasWidth { const MIN_WIDTH: usize; /// Increment track width. fn width_inc (&mut self); /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. fn width_dec (&mut self); } impl HasWidth for Track { const MIN_WIDTH: usize = 9; fn width_inc (&mut self) { self.width += 1; } fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } }