use crate::*; pub struct ArrangerView<'a> { pub arrangement: &'a Arrangement, pub is_editing: bool, pub width: u16, pub width_mid: u16, pub width_side: u16, pub inputs_height: u16, pub outputs_height: u16, pub scene_last: usize, pub scene_scroll: Fill>, pub scene_selected: Option, pub scenes_height: u16, pub track_scroll: Fill>, pub track_selected: Option, pub tracks_height: u16, pub show_debug_info: bool, } impl<'a> ArrangerView<'a> { pub fn new ( arrangement: &'a Arrangement, editor: Option<&'a MidiEditor> ) -> Self { let is_editing = editor.is_some(); let h_tracks_area = arrangement.h_tracks_area(); let h_scenes_area = arrangement.h_scenes_area(); let h_scenes = arrangement.h_scenes(is_editing); Self { arrangement, is_editing, width: arrangement.w(), width_mid: arrangement.w_tracks_area(is_editing), width_side: arrangement.w_sidebar(is_editing), inputs_height: arrangement.h_inputs(), outputs_height: arrangement.h_outputs(), scenes_height: h_scenes_area, scene_selected: arrangement.selection().scene(), scene_last: arrangement.scenes.len().saturating_sub(1), scene_scroll: Fill::y(Fixed::x(1, ScrollbarV { offset: arrangement.scene_scroll, length: h_scenes_area as usize, total: h_scenes as usize, })), tracks_height: h_tracks_area, track_selected: arrangement.selection().track(), track_scroll: Fill::x(Fixed::y(1, ScrollbarH { offset: arrangement.track_scroll, length: h_tracks_area as usize, total: h_scenes as usize, })), show_debug_info: false } } } impl<'a> Content for ArrangerView<'a> { fn content (&self) -> impl Render { let ins = |x|Bsp::n(self.inputs(), x); let tracks = |x|Bsp::s(self.tracks(), x); let devices = |x|Bsp::s(self.devices(), x); let outs = |x|Bsp::s(self.outputs(), x); let bg = |x|Tui::bg(Reset, x); //let track_scroll = |x|Bsp::s(&self.track_scroll, x); //let scene_scroll = |x|Bsp::e(&self.scene_scroll, x); outs(tracks(devices(ins(bg(self.scenes(&None)))))) } } impl<'a> ArrangerView<'a> { /// Render input matrix. pub(crate) fn inputs (&'a self) -> impl Content + 'a { Tui::bg(Reset, Bsp::s( self.input_intos(), Bsp::s(self.input_routes(), self.input_ports()), )) } /// Render output matrix. pub(crate) fn outputs (&'a self) -> impl Content + 'a { Tui::bg(Reset, Align::n(Bsp::s( Bsp::s(self.output_ports(), self.output_conns()), Bsp::s(self.output_nexts(), self.output_froms()), ))) } /// Render track headers pub(crate) fn tracks (&'a self) -> impl Content + 'a { let Self { width_side, width_mid, track_selected, is_editing, .. } = self; Tryptich::center(3) .left(*width_side, button_3("t", "track", format!("{}", self.arrangement.tracks.len()), *is_editing)) .right(*width_side, button_2("T", "add track", *is_editing)) .middle(*width_mid, per_track(*width_mid, ||self.tracks_with_sizes_scrolled(), |index, track|wrap( if *track_selected == Some(index) { track.color.light } else { track.color.base }.rgb, track.color.lightest.rgb, Tui::bold(true, Fill::xy(Align::nw(&track.name))) ))) } /// Render device switches. pub(crate) fn devices (&'a self) -> impl Content + 'a { let Self { width_side, width_mid, track_selected, is_editing, .. } = self; Tryptich::top(1) .left(*width_side, button_3("d", "devices", format!("{}", 0), *is_editing)) .right(*width_side, button_2("D", "add device", *is_editing)) .middle(*width_mid, per_track_top(*width_mid, ||self.tracks_with_sizes_scrolled(), move|index, track|{ let bg = if *track_selected == Some(index) { track.color.light } else { track.color.base }; let fg = Tui::g(224); track.devices.get(0).map(|device|wrap(bg.rgb, fg, device.name())) })) } /// Default scene height. pub(crate) const H_SCENE: usize = 2; /// Default editor height. pub(crate) const H_EDITOR: usize = 15; /// Render scenes with clips pub(crate) fn scenes (&'a self, editor: &'a Option) -> impl Content + 'a { /// A scene with size and color. type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); let Self { arrangement, width, width_side, width_mid, scenes_height, scene_last, scene_selected, track_selected, is_editing, .. } = self; let selection = Has::::get(self.arrangement); let selected_track = selection.track(); let selected_scene = selection.scene(); Tryptich::center(*scenes_height) .left(*width_side, Map::new( move||arrangement.scenes_with_sizes( *is_editing, Self::H_SCENE, Self::H_EDITOR, selected_track, selected_scene, ).map_while(|(s, scene, y1, y2)|if y2 as u16 > *scenes_height { None } else { Some((s, scene, y1, y2, if s == 0 { None } else { Some(arrangement.scenes()[s-1].color) })) }), move |(s, scene, y1, y2, previous): SceneWithColor, _|{ let height = (1 + y2 - y1) as u16; let name = Some(scene.name.clone()); let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name)))); let same_track = true; let selected = same_track && *scene_selected == Some(s); let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1); let is_last = *scene_last == s; let theme = scene.color; 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 }; Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat { width: 0, height: 0, content, colors: [fg, bg, hi, lo] }))) })) .middle(*width_mid, per_track( *width_mid, ||self.tracks_with_sizes_scrolled(), move|track_index, track|Map::new( move||arrangement.scenes_with_sizes( self.is_editing, Self::H_SCENE, Self::H_EDITOR, selected_track, selected_scene, ).map_while(move|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height { None } else { Some((s, scene, y1, y2, if s == 0 { None } else { Some(self.arrangement.scenes[s-1].clips[track_index].as_ref() .map(|c|c.read().unwrap().color) .unwrap_or(ItemTheme::G[32])) })) }), move|(s, scene, y1, y2, previous): SceneWithColor<'a>, _|{ let (name, theme) = if let Some(clip) = &scene.clips[track_index] { let clip = clip.read().unwrap(); (Some(clip.name.clone()), clip.color) } else { (None, ItemTheme::G[32]) }; let height = (1 + y2 - y1) as u16; let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name)))); let same_track = *track_selected == Some(track_index); let selected = same_track && *scene_selected == Some(s); let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1); let is_last = *scene_last == 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 }; map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat { width: 0, height: 0, content, colors: [fg, bg, hi, lo] }), When( *is_editing && same_track && *scene_selected == Some(s), editor ))) }))) } } pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( width: u16, tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { Align::x(Tui::bg(Reset, Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; map_east(x1 as u16, width, Fixed::x(width, Tui::fg_bg( track.color.lightest.rgb, track.color.base.rgb, callback(index, track))))}))) } pub(crate) fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( width: u16, tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { per_track_top( width, tracks, move|index, track|Fill::y(Align::y(callback(index, track))) ) }