From 9aeb792f7dc0d4ee115d619d62916710ca1a3e9f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 15 May 2025 23:06:15 +0300 Subject: [PATCH] wip: new old arranger scenes --- config/config_arranger.edn | 7 +- crates/app/src/view.rs | 30 ++++ crates/device/src/arranger/arranger_port.rs | 22 +-- crates/device/src/arranger/arranger_scenes.rs | 151 ++++++++++++++++++ crates/device/src/arranger/arranger_tracks.rs | 15 -- crates/device/src/arranger/arranger_view.rs | 148 +---------------- 6 files changed, 197 insertions(+), 176 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 07f0d65a..7fb8416f 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -21,12 +21,11 @@ (bsp/s (fixed/y 8 (bsp/e (fixed/x 20 (fill/y (align/n (bsp/s :view-status-v - (bsp/s :view-audio-ports-status :view-editor-status))))) + (bsp/s (bsp/s :view-audio-ins-status :view-audio-outs-status) + :view-editor-status))))) (fill/xy (align/n (bsp/s :view-arranger-track-names (bsp/s :view-arranger-track-outputs (bsp/s :view-arranger-track-devices :view-arranger-track-inputs))))))) (bsp/w (fixed/x 20 :view-pool) - (bsp/e - (fixed/x 20 (fill/xy (align/n :view-arranger-scene-names))) - (fill/xy (align/n :view-arranger-scene-clips))))))) + :view-arranger-scenes))) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 1f617606..a84c9bb6 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -104,6 +104,9 @@ impl App { pub fn view_arranger (&self) -> impl Content + use<'_> { ArrangerView::new(&self.project, self.editor.as_ref()) } + pub fn view_arranger_scenes (&self) -> impl Content + use<'_> { + self.scenes_view(&self.editor) + } pub fn view_arranger_scene_names <'a> (&'a self) -> impl Content + use<'a> { let h = self.project.scenes.len() as u16 * 2; let bg = self.color.darker.rgb; @@ -352,6 +355,33 @@ impl App { } } +impl ArrangerSceneRows for App { + fn arrangement (&self) -> &Arrangement { + self.project + } + fn scenes_height (&self) -> u16 { + self.project.scenes_height + } + fn width_side (&self) -> u16 { + 20 + } + fn width_mid (&self) -> u16 { + self.width().saturating_sub(self.width_side() * 2) + } + fn scene_selected (&self) -> Option { + self.project.selection.scene() + } + fn scene_last (&self) -> usize { + self.project.scenes.len().saturating_sub(1) + } + fn track_selected (&self) -> Option { + self.project.selection.track() + } + fn is_editing (&self) -> bool { + self.is_editing + } +} + pub(crate) fn heading <'a> ( key: &'a str, label: &'a str, diff --git a/crates/device/src/arranger/arranger_port.rs b/crates/device/src/arranger/arranger_port.rs index a5a1a586..cf7a65b4 100644 --- a/crates/device/src/arranger/arranger_port.rs +++ b/crates/device/src/arranger/arranger_port.rs @@ -7,7 +7,7 @@ impl<'a> ArrangerView<'a> { .left(self.width_side, io_ports(Tui::g(224), Tui::g(32), ||self.arrangement.midi_ins_with_sizes())) .middle(self.width_mid, - per_track_top(self.width_mid, ||self.tracks_with_sizes_scrolled(), + per_track_top(||self.tracks_with_sizes_scrolled(), move|_, &Track { color, .. }|io_conns( color.dark.rgb, color.darker.rgb, @@ -22,9 +22,7 @@ impl<'a> ArrangerView<'a> { .right(self.width_side, button_2("I", "add midi in", self.is_editing)) .middle(self.width_mid, - per_track_top( - self.width_mid, - ||self.tracks_with_sizes_scrolled(), + per_track_top(||self.tracks_with_sizes_scrolled(), move|t, track|{ let rec = track.sequencer.recording; let mon = track.sequencer.monitoring; @@ -48,9 +46,7 @@ impl<'a> ArrangerView<'a> { .left(self.width_side, Bsp::s(Align::e("Input:"), Align::e("Into clip:"))) .middle(self.width_mid, - per_track_top( - self.width_mid, - ||self.tracks_with_sizes_scrolled(), + per_track_top(||self.tracks_with_sizes_scrolled(), |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) } @@ -61,16 +57,14 @@ impl<'a> ArrangerView<'a> { pub(crate) fn output_nexts (&self) -> impl Content + '_ { Tryptich::top(2) .left(self.width_side, Align::ne("From clip:")) - .middle(self.width_mid, per_track_top( - self.width_mid, - ||self.tracks_with_sizes_scrolled(), + .middle(self.width_mid, per_track_top(||self.tracks_with_sizes_scrolled(), |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) } pub(crate) fn output_froms (&'a self) -> impl Content + 'a { let label = Align::ne("Next clip:"); Tryptich::top(2).left(self.width_side, label).middle(self.width_mid, per_track_top( - self.width_mid, ||self.tracks_with_sizes_scrolled(), |t, track|{ + ||self.tracks_with_sizes_scrolled(), |t, track|{ let queued = track.sequencer.next_clip.is_some(); let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ ")); let queued_clip = Thunk::new(||{ @@ -109,7 +103,7 @@ impl<'a> ArrangerView<'a> { } pub(crate) fn output_map (&'a self) -> impl Content + 'a { - per_track_top(self.width_mid, ||self.tracks_with_sizes_scrolled(), move|i, t|{ + per_track_top(||self.tracks_with_sizes_scrolled(), move|i, t|{ let mute = false; let solo = false; let mute = if mute { White } else { t.color.darkest.rgb }; @@ -130,9 +124,7 @@ impl<'a> ArrangerView<'a> { Tryptich::top(self.arrangement.h_outputs()) .left(self.width_side, io_ports( Tui::g(224), Tui::g(32), ||self.arrangement.midi_outs_with_sizes())) - .middle(self.width_mid, per_track_top( - self.width_mid, - ||self.tracks_with_sizes_scrolled(), + .middle(self.width_mid, per_track_top(||self.tracks_with_sizes_scrolled(), |_, t|io_conns( t.color.dark.rgb, t.color.darker.rgb, diff --git a/crates/device/src/arranger/arranger_scenes.rs b/crates/device/src/arranger/arranger_scenes.rs index 8603e603..941b99ba 100644 --- a/crates/device/src/arranger/arranger_scenes.rs +++ b/crates/device/src/arranger/arranger_scenes.rs @@ -1,5 +1,156 @@ use crate::*; +pub type SceneWith<'a, T: Send + Sync> = (usize, &'a Scene, usize, usize, T); + +pub trait ArrangerSceneRows: Send + Sync { + /// Default scene height. + const H_SCENE: usize = 2; + /// Default editor height. + const H_EDITOR: usize = 15; + /// Render scenes with clips + fn scenes_view <'a> (&'a self, editor: &'a Option) -> impl Content + 'a { + Tryptich::center(self.scenes_height()) + .left(self.width_side(), self.scenes_names()) + .middle(self.width_mid(), self.scenes_clips(editor)) + } + fn is_editing (&self) -> bool; + fn arrangement (&self) -> &Arrangement; + fn scene_last (&self) -> usize; + fn scene_selected (&self) -> Option; + fn track_selected (&self) -> Option; + fn scenes_height (&self) -> u16; + fn width_side (&self) -> u16; + fn width_mid (&self) -> u16; + fn scenes_names (&self) -> impl Content { + Map::new(move||self.scenes_with_prev_color(), + move|(s, scene, y1, y2, previous): SceneWith<'_, Option>, _|{ + 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 selected = self.scene_selected() == Some(s); + let neighbor = s > 0 && self.scene_selected() == Some(s - 1); + let is_last = self.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] + }))) + }) + } + fn scenes_with_prev_color (&self) -> impl Iterator>> + Send + Sync { + self.scenes_iter().map(|(s, scene, y1, y2)|(s, scene, y1, y2, + (s>0).then_some(self.arrangement().scenes()[s-1].color))) + } + fn scenes_clips <'a> (&'a self, editor: &'a Option) + -> impl Content + 'a + { + per_track(||self.tracks_with_sizes_scrolled(), + move|track_index, track|Map::new(move||self.scenes_with_clip(track_index), + move|(s, scene, y1, y2, previous): SceneWith<'_, Option>, _|{ + 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 = 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.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( + self.is_editing() && same_track && self.scene_selected() == Some(s), + editor + ))) + })) + } + fn scenes_with_clip (&self, track_index: usize) -> impl Iterator>> + Send + Sync { + self.scenes_iter().map(move|(s, scene, y1, y2)|(s, scene, y1, y2, + (s>0).then_some(self.arrangement().scenes()[s-1].clips[track_index].as_ref() + .map(|c|c.read().unwrap().color) + .unwrap_or(ItemTheme::G[32])))) + } + /// A scene with size and color. + fn scenes_iter (&self) -> impl Iterator + Send + Sync { + let selection = Has::::get(self.arrangement()); + self.arrangement().scenes_with_sizes( + self.is_editing(), + Self::H_SCENE, Self::H_EDITOR, + selection.track(), selection.scene(), + ).map_while(|(s, scene, y1, y2)|(y2<=self.scenes_height() as usize) + .then_some((s, scene, y1, y2))) + } + fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> { + self.arrangement() + .tracks_with_sizes( + &self.arrangement().selection(), + self.is_editing().then_some(20/*FIXME*/) + ) + .map_while(move|(t, track, x1, x2)|{ + (self.width_mid() > x2 as u16).then_some((t, track, x1, x2)) + }) + } +} + +impl<'a> ArrangerSceneRows for ArrangerView<'a> { + fn arrangement (&self) -> &Arrangement { + self.arrangement + } + fn scenes_height (&self) -> u16 { + self.scenes_height + } + fn width_side (&self) -> u16 { + self.width_side + } + fn width_mid (&self) -> u16 { + self.width_mid + } + fn scene_selected (&self) -> Option { + self.arrangement.selection.scene() + } + fn scene_last (&self) -> usize { + self.scene_last + } + fn track_selected (&self) -> Option { + self.arrangement.selection.track() + } + fn is_editing (&self) -> bool { + self.is_editing + } +} + impl> + Send + Sync> HasScenes for T {} pub trait HasScenes: Has> + Send + Sync { diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index 09a3d273..471c0b88 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -69,21 +69,6 @@ pub trait HasTracks: Has> + Send + Sync { const TRACK_SPACING: usize = 0; } -impl<'a> ArrangerView<'a> { - pub(crate) fn tracks_with_sizes_scrolled (&'a self) - -> impl TracksSizes<'a> - { - self.arrangement - .tracks_with_sizes( - &self.arrangement.selection(), - self.is_editing.then_some(20/*FIXME*/) - ) - .map_while(move|(t, track, x1, x2)|{ - (self.width_mid > x2 as u16).then_some((t, track, x1, x2)) - }) - } -} - impl Arrangement { /// Add multiple tracks pub fn tracks_add ( diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index cbd3a4b1..b464a870 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -60,7 +60,7 @@ impl<'a> Content for ArrangerView<'a> { 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); - self.arrangement.size.of(outs(tracks(devices(ins(bg(self.scenes(&None))))))) + self.arrangement.size.of(outs(tracks(devices(ins(bg(self.scenes_view(&None))))))) } } @@ -72,7 +72,6 @@ impl<'a> ArrangerView<'a> { 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( @@ -80,14 +79,14 @@ impl<'a> ArrangerView<'a> { 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)) + .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(), + .middle(*width_mid, per_track(||self.tracks_with_sizes_scrolled(), |index, track|wrap( if *track_selected == Some(index) { track.color.light @@ -98,14 +97,13 @@ impl<'a> ArrangerView<'a> { 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(), + .middle(*width_mid, per_track_top(||self.tracks_with_sizes_scrolled(), move|index, track|{ let bg = if *track_selected == Some(index) { track.color.light @@ -116,138 +114,9 @@ impl<'a> ArrangerView<'a> { 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 { @@ -261,13 +130,8 @@ pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( } 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))) - ) + per_track_top(tracks, move|index, track|Fill::y(Align::y(callback(index, track)))) }