From 8adbdc5bc73e78e555f72f67c5303ca5702ad4b3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 1 May 2025 16:18:00 +0300 Subject: [PATCH] add new Selection variants --- crates/app/src/api.rs | 4 +- crates/app/src/model.rs | 182 +++++++++++++++++++++++++--------------- crates/app/src/view.rs | 80 +++++++++--------- crates/cli/tek.rs | 4 +- 4 files changed, 161 insertions(+), 109 deletions(-) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index fddb175b..c540066f 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -23,7 +23,7 @@ expose!([self: Tek] (":track" self.selected.track())) ([MaybeClip] (":clip" match self.selected { - Selection::Clip(t, s) => self.scenes[s].clips[t].clone(), + Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(), _ => None })) ([Selection] @@ -70,7 +70,7 @@ impose!([app: Tek] (0, 0) => Self::Select(Selection::Mix), (t, 0) => Self::Select(Selection::Track(t)), (0, s) => Self::Select(Selection::Scene(s)), - (t, s) => Self::Select(Selection::Clip(t, s)) }))) + (t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) (ClipCommand: ("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index ce9b5bed..0ed65783 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -121,10 +121,11 @@ impl Tek { /// Add and focus a track pub(crate) fn track_add_focus (&mut self) -> Usually { + use Selection::*; let index = self.track_add(None, None, &[], &[])?.0; self.selected = match self.selected { - Selection::Track(t) => Selection::Track(index), - Selection::Clip(t, s) => Selection::Clip(index, s), + Track(_) => Track(index), + TrackClip { track, scene } => TrackClip { track: index, scene }, _ => self.selected }; Ok(index) @@ -177,10 +178,11 @@ impl Tek { /// Add and focus an empty scene pub fn scene_add_focus (&mut self) -> Usually { + use Selection::*; let index = self.scene_add(None, None)?.0; self.selected = match self.selected { - Selection::Scene(s) => Selection::Scene(index), - Selection::Clip(t, s) => Selection::Clip(t, index), + Scene(_) => Scene(index), + TrackClip { track, scene } => TrackClip { track, scene: index }, _ => self.selected }; Ok(index) @@ -215,15 +217,15 @@ impl Tek { // Create new clip in pool when entering empty cell pub fn clip_auto_create (&mut self) { if let Some(ref pool) = self.pool - && let Selection::Clip(t, s) = self.selected - && let Some(scene) = self.scenes.get_mut(s) - && let Some(slot) = scene.clips.get_mut(t) + && let Selection::TrackClip { track, scene } = self.selected + && let Some(scene) = self.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) && slot.is_none() { let (index, mut clip) = pool.add_new_clip(); // autocolor: new clip colors from scene and track color clip.write().unwrap().color = ItemColor::random_near( - self.tracks[t].color.base.mix( + self.tracks[track].color.base.mix( scene.color.base, 0.5 ), @@ -239,9 +241,9 @@ impl Tek { // Remove clip from arrangement when exiting empty clip editor pub fn clip_auto_remove (&mut self) { if let Some(ref mut pool) = self.pool - && let Selection::Clip(t, s) = self.selected - && let Some(scene) = self.scenes.get_mut(s) - && let Some(slot) = scene.clips.get_mut(t) + && let Selection::TrackClip { track, scene } = self.selected + && let Some(scene) = self.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) && let Some(clip) = slot.as_mut() { let mut swapped = None; @@ -304,8 +306,10 @@ impl Tek { // autoedit: load focused clip in editor. if let Some(ref mut editor) = self.editor { editor.set_clip(match self.selected { - Selection::Clip(t, s) if let Some(Some(Some(clip))) = self - .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), + Selection::TrackClip { track, scene } + if let Some(Some(Some(clip))) = self + .scenes.get(scene) + .map(|s|s.clips.get(track)) => Some(clip), _ => None }); } @@ -320,14 +324,15 @@ impl Tek { /// Launch a clip or scene pub(crate) fn launch (&mut self) { + use Selection::*; match self.selected { - Selection::Track(t) => { + Track(t) => { self.tracks[t].player.enqueue_next(None) }, - Selection::Clip(t, s) => { - self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) + TrackClip { track, scene } => { + self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref()) }, - Selection::Scene(s) => { + Scene(s) => { for t in 0..self.tracks.len() { self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) } @@ -348,25 +353,26 @@ impl Tek { /// Set the color of the selected entity pub fn set_color (&mut self, palette: Option) -> Option { + use Selection::*; let palette = palette.unwrap_or_else(||ItemTheme::random()); Some(match self.selected { - Selection::Mix => { + Mix => { let old = self.color; self.color = palette; old }, - Selection::Track(t) => { - let old = self.tracks[t].color; - self.tracks[t].color = palette; - old - } - Selection::Scene(s) => { + Scene(s) => { let old = self.scenes[s].color; self.scenes[s].color = palette; old } - Selection::Clip(t, s) => { - if let Some(ref clip) = self.scenes[s].clips[t] { + Track(t) => { + let old = self.tracks[t].color; + self.tracks[t].color = palette; + old + } + TrackClip { track, scene } => { + if let Some(ref clip) = self.scenes[scene].clips[track] { let mut clip = clip.write().unwrap(); let old = clip.color; clip.color = palette; @@ -374,7 +380,8 @@ impl Tek { } else { return None } - } + }, + _ => todo!() }) } @@ -426,12 +433,22 @@ pub enum Modal { pub enum Selection { /// The whole mix is selected #[default] Mix, - /// A track is selected. - Track(usize), + /// A MIDI input is selected. + Input(usize), + /// A MIDI output is selected. + Output(usize), /// A scene is selected. Scene(usize), + /// A track is selected. + Track(usize), /// A clip (track × scene) is selected. - Clip(usize, usize), + TrackClip { track: usize, scene: usize }, + /// A track's MIDI input connection is selected. + TrackInput { track: usize, port: usize }, + /// A track's MIDI output connection is selected. + TrackOutput { track: usize, port: usize }, + /// A track device slot is selected. + TrackDevice { track: usize, device: usize }, } /// Focus identification methods @@ -446,70 +463,101 @@ impl Selection { matches!(self, Self::Scene(_)) } pub fn is_clip (&self) -> bool { - matches!(self, Self::Clip(_, _)) + matches!(self, Self::TrackClip {..}) } pub fn track (&self) -> Option { use Selection::*; - match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None } + match self { + Track(track) + | TrackClip { track, .. } + | TrackInput { track, .. } + | TrackOutput { track, .. } + | TrackDevice { track, .. } => Some(*track), + _ => None + } } pub fn track_next (&self, len: usize) -> Self { + use Selection::*; match self { - Selection::Mix => Selection::Track(0), - Selection::Track(t) if t + 1 < len => Selection::Track(t + 1), - Selection::Track(t) => Selection::Mix, - Selection::Scene(s) => Selection::Clip(0, *s), - Selection::Clip(t, s) if t + 1 < len => Selection::Clip(t + 1, *s), - Selection::Clip(t, s) => Selection::Scene(*s), + Mix => Track(0), + Scene(s) => TrackClip { track: 0, scene: *s }, + Track(t) => if t + 1 < len { + Track(t + 1) + } else { + Mix + }, + TrackClip {track, scene} => if track + 1 < len { + TrackClip { track: track + 1, scene: *scene } + } else { + Scene(*scene) + }, + _ => todo!() } } pub fn track_prev (&self) -> Self { + use Selection::*; match self { - Selection::Mix => Selection::Mix, - Selection::Scene(s) => Selection::Scene(*s), - Selection::Track(0) => Selection::Mix, - Selection::Track(t) => Selection::Track(t - 1), - Selection::Clip(0, s) => Selection::Scene(*s), - Selection::Clip(t, s) => Selection::Clip(t - 1, *s), + Mix => Mix, + Scene(s) => Scene(*s), + Track(0) => Mix, + Track(t) => Track(t - 1), + TrackClip { track: 0, scene } => Scene(*scene), + TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene }, + _ => todo!() } } pub fn scene (&self) -> Option { use Selection::*; - match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None } + match self { + Scene(scene) | TrackClip { scene, .. } => Some(*scene), + _ => None + } } pub fn scene_next (&self, len: usize) -> Self { + use Selection::*; match self { - Selection::Mix => Selection::Scene(0), - Selection::Track(t) => Selection::Clip(*t, 0), - Selection::Scene(s) if s + 1 < len => Selection::Scene(s + 1), - Selection::Scene(s) => Selection::Mix, - Selection::Clip(t, s) if s + 1 < len => Selection::Clip(*t, s + 1), - Selection::Clip(t, s) => Selection::Track(*t), + Mix => Scene(0), + Track(t) => TrackClip { track: *t, scene: 0 }, + Scene(s) => if s + 1 < len { + Scene(s + 1) + } else { + Mix + }, + TrackClip { track, scene } => if scene + 1 < len { + TrackClip { track: *track, scene: scene + 1 } + } else { + Track(*track) + }, + _ => todo!() } } pub fn scene_prev (&self) -> Self { + use Selection::*; match self { - Selection::Mix => Selection::Mix, - Selection::Track(t) => Selection::Track(*t), - Selection::Scene(0) => Selection::Mix, - Selection::Scene(s) => Selection::Scene(s - 1), - Selection::Clip(t, 0) => Selection::Track(*t), - Selection::Clip(t, s) => Selection::Clip(*t, s - 1), + Mix | Scene(0) => Mix, + Scene(s) => Scene(s - 1), + Track(t) => Track(*t), + TrackClip { track, scene: 0 } => Track(*track), + TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 }, + _ => todo!() } } pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc { + use Selection::*; format!("{}", match self { - Self::Mix => "Everything".to_string(), - Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) - .unwrap_or_else(||"T??".into()), - Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) + Mix => "Everything".to_string(), + Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) .unwrap_or_else(||"S??".into()), - Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { - (Some(_), Some(scene)) => match scene.clip(*t) { - Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name), - None => format!("T{t} S{s}: Empty") + Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) + .unwrap_or_else(||"T??".into()), + TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) { + (Some(_), Some(s)) => match s.clip(*track) { + Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name), + None => format!("T{track} S{scene}: Empty") }, - _ => format!("T{t} S{s}: Empty"), - } + _ => format!("T{track} S{scene}: Empty"), + }, + _ => todo!() }).into() } } diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index f4d8f8e0..498ebbbd 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -181,11 +181,12 @@ impl Tek { } pub(crate) fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { + use Selection::*; let mut x = 0; let editing = self.is_editing(); let active = match self.selected() { - Selection::Track(t) if editing => Some(t), - Selection::Clip(t, _) if editing => Some(t), + Track(t) if editing => Some(t), + TrackClip { track, .. } if editing => Some(track), _ => None }; let bigger = self.editor_w(); @@ -200,10 +201,11 @@ impl Tek { pub(crate) fn scenes_with_sizes (&self, editing: bool, height: usize, larger: usize) -> impl ScenesSizes<'_> { + use Selection::*; let (selected_track, selected_scene) = match self.selected() { - Selection::Track(t) => (Some(*t), None), - Selection::Scene(s) => (None, Some(*s)), - Selection::Clip(t, s) => (Some(*t), Some(*s)), + Track(t) => (Some(*t), None), + Scene(s) => (None, Some(*s)), + TrackClip { track, scene } => (Some(*track), Some(*scene)), _ => (None, None) }; let mut y = 0; @@ -304,8 +306,10 @@ impl<'a> ArrangerView<'a> { /// Render input matrix. pub(crate) fn inputs (&'a self) -> impl Content + 'a { - Tui::bg(Color::Reset, - Bsp::s(Bsp::s(self.input_routes(), self.input_ports()), self.input_intos())) + Tui::bg(Color::Reset, Bsp::s( + Bsp::s(self.input_routes(), self.input_ports()), + self.input_intos() + )) } fn input_routes (&'a self) -> impl Content + 'a { @@ -313,13 +317,12 @@ impl<'a> ArrangerView<'a> { .left(self.width_side, io_ports(Tui::g(224), Tui::g(32), ||self.app.inputs_with_sizes())) .middle(self.width_mid, - per_track_top( - self.width_mid, - ||self.app.tracks_with_sizes(), - move|_, &Track { color, .. }|{ - io_conns(color.dark.rgb, color.darker.rgb, ||self.app.inputs_with_sizes()) - } - )) + per_track_top(self.width_mid, ||self.app.tracks_with_sizes(), + move|_, &Track { color, .. }|io_conns( + color.dark.rgb, + color.darker.rgb, + ||self.app.inputs_with_sizes() + ))) } fn input_ports (&'a self) -> impl Content + 'a { @@ -353,14 +356,12 @@ impl<'a> ArrangerView<'a> { fn input_intos (&'a self) -> impl Content + 'a { Tryptich::top(2) .left(self.width_side, - Bsp::s(Align::e("Input:"), Align::e("Into:"))) + Bsp::s(Align::e("Input:"), Align::e("Into clip:"))) .middle(self.width_mid, per_track_top( self.width_mid, ||self.app.tracks_with_sizes(), - |_, _|{ - Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))) - })) + |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) } /// Render output matrix. @@ -370,20 +371,17 @@ impl<'a> ArrangerView<'a> { Bsp::s(self.output_ports(), self.output_conns()), ))) } - fn output_nexts (&'a self) -> impl Content + 'a { Tryptich::top(2) - .left(self.width_side, Align::ne("From:")) + .left(self.width_side, Align::ne("From clip:")) .middle(self.width_mid, per_track_top( self.width_mid, ||self.tracks_with_sizes_scrolled(), - |_, _|{ - Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))) - })) + |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) } fn output_froms (&'a self) -> impl Content + 'a { Tryptich::top(2) - .left(self.width_side, Align::ne("Next:")) + .left(self.width_side, Align::ne("Next clip:")) .middle(self.width_mid, per_track_top( self.width_mid, ||self.tracks_with_sizes_scrolled(), @@ -412,7 +410,11 @@ impl<'a> ArrangerView<'a> { let solo = false; let mute = if mute { White } else { t.color.darkest.rgb }; let solo = if solo { White } else { t.color.darkest.rgb }; - let bg_1 = if self.track_selected == Some(i) { t.color.light.rgb } else { t.color.base.rgb }; + let bg_1 = if self.track_selected == Some(i) { + t.color.light.rgb + } else { + t.color.base.rgb + }; let bg_2 = if i > 0 { t.color.base.rgb } else { Reset }; let mute = Tui::fg_bg(mute, bg_1, "Play "); let solo = Tui::fg_bg(solo, bg_1, "Solo "); @@ -740,24 +742,26 @@ pub(crate) fn heading <'a> ( pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a ) -> impl Content + 'a { - Map::new(iter, - move|(index, name, connections, y, y2): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| - map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), - Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, - &connect.info))))))))) + Map::new(iter, move|( + index, name, connections, y, y2 + ): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| + map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), + Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, + &connect.info))))))))) } pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a ) -> impl Content + 'a { - Map::new(iter, - move|(index, name, connections, y, y2): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| - map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), - Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x("")))))))))) + Map::new(iter, move|( + index, name, connections, y, y2 + ): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| + map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), + Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x("")))))))))) } pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 0cebdf0d..bf0cd6d6 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -185,12 +185,12 @@ impl Cli { _ => vec![] }, scenes, - selected: Selection::Clip(0, 0), + selected: Selection::TrackClip { track: 0, scene: 0 }, ..Default::default() }; if let &LaunchMode::Arranger { scenes, tracks, track_width, .. } = mode { app.arranger = Default::default(); - app.selected = Selection::Clip(1, 1); + app.selected = Selection::TrackClip { track: 1, scene: 1 }; app.scenes_add(scenes)?; app.tracks_add(tracks, Some(track_width), &[], &[])?; }