use crate::*; impl TracksView for T where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor {} pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor { fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> { self.tracks_with_sizes(&self.selection(), self.is_editing().then_some(20/*FIXME*/)) .map_while(move|(t, track, x1, x2)| ((x2 as u16) < self.tracks_width_available()) .then_some((t, track, x1, x2))) } 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 { let content = Fixed::y(1, Align::w(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(&self.selection(), None) .skip(self.track_scroll()) { add(&Fixed::x(track.width as u16, Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb } else { track.color.base.rgb }, Align::nw(Tui::fg( Rgb(255, 255, 255), Tui::bold(true, format!("{}", track.name))))))); } })))))); Bsp::w( self.view_track_header(theme, row!( Tui::bold(true, button_2("t", "rack ", false)), button_2("T", "+", false) )), content ) } fn view_track_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut max_outputs = 0u16; for track in self.tracks().iter() { max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16); } let content = Align::w(Fixed::y(1 + max_outputs*2, 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(&self.selection(), None) .skip(self.track_scroll()) { add(&Fixed::x(track.width as u16, Align::nw(Bsp::s( Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb } else { track.color.base.rgb }, Fill::x(Align::w(format!("·mute ·solo")))), Map::south(2, ||track.sequencer.midi_outs.iter(), |port, index|Tui::fg(Rgb(255, 255, 255), Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( format!("·o{index}: {}", port.name()))))))))))); } })))))); Bsp::w( self.view_track_header(theme, row!( Tui::bold(true, button_2("o", "utput", false)), button_2("O", "+", false) )), content ) } fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 2u16; for track in self.tracks().iter() { h = h.max(track.devices.len() as u16); } Bsp::w( self.view_track_header(theme, row!( Tui::bold(true, button_2("d", "evice", 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(&self.selection(), None) .skip(self.track_scroll()) { add(&Fixed::xy(track.width as u16, h + 1, Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, |_, index|format!("·d{index}: {}", "--------")))))); } })))))) } 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); } let content = 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(&self.selection(), None) .skip(self.track_scroll()) { add(&Fixed::xy(track.width as u16, h + 1, Align::nw(Bsp::n( Tui::bg(track.color.base.rgb, Fill::x(Align::w(format!("·mon ·rec ·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}: {}", port.name()))))))))); } })))); Bsp::w( self.view_track_header(theme, row!( Tui::bold(true, button_2("i", "nputs", false)), button_2("I", "+", false) )), Fixed::y(h, Fill::x(Align::w(Fixed::y(h + 1, content)))), ) } } pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + 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 view_scenes_names (&self) -> impl Content { Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) { 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!(" {index:2} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", 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()))))))))) } } impl ClipsView for T {} pub trait ClipsView: TracksView + ScenesView + Send + Sync { fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { Fill::xy(Stack::::east(move|column: &mut dyn FnMut(&dyn Render)|{ for (track_index, track, _, _) in self .tracks_with_sizes(&self.selection(), None) .skip(self.track_scroll()) { //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().iter().enumerate().skip(self.scene_scroll()) { let (name, theme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); (Some(clip.name.clone()), clip.color) } else { (None, 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.lightest.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 }; cell(&Fixed::xy( if self.selection().track() == Some(track_index) && let Some(editor) = self.editor () { editor.width().max(24).max(track.width) } else { track.width } as u16, if self.selection().scene() == Some(scene_index) && let Some(editor) = self.editor () { editor.height().max(12) } else { Self::H_SCENE } as u16, Bsp::b( Fill::xy(Outer(true, Style::default().fg(outline))), Fill::xy(Bsp::b( Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into()))))), Fill::xy(When(self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) && self.is_editing(), self.editor()))))))); //let (name, theme) = if let Some(clip) = &scene.clips.get(track_index).flatten() { //let clip = clip.read().unwrap(); //(Some(clip.name.clone()), clip.color) //} else { //(None, ItemTheme::G[32]) //}; //let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name)))); //let same_track = self.track_selected() == Some(track_index); //let selected = same_track && self.scene_selected() == Some(s); //let neighbor = same_track && s > 0 && self.scene_selected() == Some(s - 1); //let is_last = self.scenes().len().saturating_sub(1) == s; //let fg = theme.lightest.rgb; //let bg = if selected { theme.light } else { theme.base }.rgb; //let hi = if let Some(previous) = previous { //if neighbor { previous.light.rgb } else { previous.base.rgb } //} else { //Reset //}; //let lo = if is_last { //Reset //} else if selected { //theme.light.rgb //} else { //theme.base.rgb //}; //let height = (1 + y2 - y1) as u16; //let is_editing = false; //FIXME //let editor = (); //FIXME //cell(&Fixed::xy(track.width as u16, 2, Bsp::b(Fixed::y(height, Phat { //width: 0, height: 0, content, colors: [fg, bg, hi, lo] //}), When( //is_editing && same_track && self.scene_selected() == Some(s), //editor //)))) } }) } fn scenes_clips_2 <'a> ( &'a self, theme: ItemTheme ) -> impl Content + 'a { Fixed::y(self.scenes().len() as u16 * 2, Tui::bg(theme.darker.rgb, Align::w(Fill::x(Map::new(||self.scenes().iter().skip(self.scene_scroll()), move|scene: &'a Scene, index|self.track_scenes(index, scene)))))) } fn track_scenes <'a> ( &'a self, scene_index: usize, scene: &'a Scene ) -> impl Content + 'a { Push::y(scene_index as u16 * 2u16, Fixed::xy(20, 2, Map::new( move||scene.clips.iter().skip(self.track_scroll()), move|clip: &'a Option>>, track_index| self.track_scene_clip(scene_index, scene, track_index, clip)))) } fn track_scene_clip ( &self, scene_index: usize, scene: &Scene, track_index: usize, clip: &Option>> ) -> impl Content { let (theme, text) = if let Some(clip) = clip { let clip = clip.read().unwrap(); (clip.color, clip.name.clone()) } else { (scene.color, Default::default()) }; Push::x(track_index as u16 * 14, Tui::bg(theme.dark.rgb, Bsp::e( format!(" {scene_index:2} {track_index:2} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", text)))))) } } 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; } } }