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(track.sequencer.midi_ins.len() as u16); } let h = h + 1; self.view_track_row_section( theme, Bsp::s( Fixed::y(1, Fill::x(Align::w(button_3("i", "nput ", format!("{}", self.midi_ins.len()), false)))), Fixed::y(h - 1, Fill::x(Align::nw(Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, port) in self.midi_ins.iter().enumerate() { add(&Fill::x(Align::w(format!("·i{index:02} {}", port.name())))); } }))))), 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, 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()))))))))); }}))))) } 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; self.view_track_row_section( theme, 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())))); } }))))), 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()))))))))))); } }))))) } 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), Fixed::y(h, 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, 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 = active_track.and_then(|_|editor_width).unwrap_or(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, button_2("t", "rack ", false), button_2("T", "+", 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)) ))), Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb } else { track.color.base.rgb }, Fill::x(Align::w(format!("·mute ·solo")))))))); } }))))) } 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 { (if self.selection().track() == Some(index) && let Some(editor) = self.editor() { editor.width().max(24).max(track.width) } else { 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 editing = self.editor().is_some(); let height = Self::H_SCENE; let larger = self.editor().map(|e|e.height()).unwrap_or(Self::H_SCENE); let selected_track = self.selection().track(); let selected_scene = self.selection().scene(); let mut y = 0; self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ let active = editing && selected_track.is_some() && selected_scene == Some(s); let height = if active { larger } else { height }; 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 { 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() { (editor.height() as u16).max(12) } 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( if self.selection().track() == Some(track_index) && let Some(editor) = self.editor() { editor.width().max(24).max(track.width) } else { 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(); (clip.name.clone(), 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; } } }