diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 2b67db1c..6249a06a 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -26,5 +26,5 @@ (bsp/s :view-arranger-track-outputs (bsp/s :view-arranger-track-devices :view-arranger-track-inputs))))))) (fill/xy (bsp/e - (bsp/n (max/y 9 :view-editor-status) (fixed/x 20 (align/nw :view-arranger-scenes-names))) + (fixed/x 20 (align/nw :view-arranger-scenes-names)) :view-arranger-scenes-clips))))) diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index 0199a07b..23da3a49 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -1,6 +1,7 @@ (@c color) (@q launch) (@t select :select-track-header) +(@s select :select-scene-header) (@tab edit :clip-selected) (@shift-I input add) (@shift-O output add) diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index b5e6ce76..58c8eeab 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -204,10 +204,10 @@ impl App { 6.max(self.height() as u16 * 3 / 9) } fn focus_editor (&self) -> bool { - self.is_editing() + self.project.editor.is_some() } fn is_editing (&self) -> bool { - HasEditor::is_editing(self) + self.project.editor.is_some() } fn focus_message (&self) -> bool { matches!(self.dialog, Some(Dialog::Message(..))) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 30665305..8801a26f 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -237,9 +237,6 @@ impl App { } impl ScenesView for App { - fn arrangement (&self) -> &Arrangement { - &self.project - } fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 24eb1449..d0a4698e 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -205,16 +205,13 @@ impl Arrangement { } impl ScenesView for Arrangement { - fn arrangement (&self) -> &Arrangement { - self - } - fn h_scenes (&self) -> u16 { + fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } - fn w_side (&self) -> u16 { + fn w_side (&self) -> u16 { (self.width() as u16 * 2 / 10).max(20) } - fn w_mid (&self) -> u16 { + fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(2 * self.w_side()).max(40) } } diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 04977aac..0488314b 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -6,179 +6,6 @@ where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScrol pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor { - /// Height taken by all inputs. - fn h_inputs (&self) -> u16 { - self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all outputs. - fn h_outputs (&self) -> u16 { - self.midi_outs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Render input matrix. - fn view_inputs_0 (&self) -> impl Content + '_ { - Tui::bg(Reset, Bsp::s( - self.view_input_intos(), - Bsp::s(self.view_input_routes(), self.view_input_ports()), - )) - } - fn view_input_ports (&self) -> impl Content + '_ { - let is_editing = self.is_editing(); - Tryptich::top(1) - .left(20, button_3("i", "midi ins", format!("{}", - self.midi_ins().len()), is_editing)) - .right(20, button_2("I", "add midi in", is_editing)) - .middle(self.width().saturating_sub(40) as u16, - per_track_top(||self.tracks_with_sizes_scrolled(), - move|t, track|{ - let rec = track.sequencer.recording; - let mon = track.sequencer.monitoring; - let rec = if rec { White } else { track.color.darkest.rgb }; - let mon = if mon { White } else { track.color.darkest.rgb }; - let bg = if self.arrangement().selection().track() == Some(t) { - track.color.light.rgb - } else { - track.color.base.rgb - }; - //let bg2 = if t > 0 { track.color.base.rgb } else { Reset }; - wrap(bg, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e( - Tui::fg_bg(rec, bg, "Rec "), - Tui::fg_bg(mon, bg, "Mon "), - )))) - })) - } - fn view_input_routes (&self) -> impl Content + '_ { - Tryptich::top(self.h_inputs()) - .left(self.w_side(), - io_ports(Tui::g(224), Tui::g(32), ||self.midi_ins_with_sizes())) - .middle(self.w_mid(), - per_track_top(||self.tracks_with_sizes_scrolled(), - move|_, &Track { color, .. }|io_conns( - color.dark.rgb, color.darker.rgb, ||self.midi_ins_with_sizes()))) - } - fn view_input_intos (&self) -> impl Content + '_ { - Tryptich::top(2) - .left(self.w_side(), - Bsp::s(Align::e("Input:"), Align::e("Into clip:"))) - .middle(self.w_mid(), - per_track_top(||self.tracks_with_sizes_scrolled(), - |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) - } - /// Render output matrix. - fn view_outputs_0 (&self) -> impl Content + '_ { - Tui::bg(Reset, Align::n(Bsp::s( - Bsp::s(self.view_output_ports(), self.view_output_conns()), - Bsp::s(self.view_output_nexts(), self.view_output_froms()), - ))) - } - fn view_output_ports (&self) -> impl Content + '_ { - Tryptich::top(1) - .left(self.w_side(), self.view_output_count()) - .right(self.w_side(), self.view_output_add()) - .middle(self.w_mid(), self.view_output_map()) - } - fn view_output_count (&self) -> impl Content { - button_3("o", "midi outs", format!("{}", self.midi_outs().len()), - self.is_editing() - ) - } - fn view_output_add (&self) -> impl Content { - button_2("O", "add midi out", self.is_editing()) - } - fn view_output_map (&self) -> impl Content + '_ { - 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 }; - let solo = if solo { White } else { t.color.darkest.rgb }; - let bg_1 = if self.arrangement().selection().track() == 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 "); - wrap(bg_1, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(mute, solo)))) - }) - } - fn view_output_conns (&self) -> impl Content + '_ { - Tryptich::top(self.h_outputs()) - .left(self.w_side(), io_ports( - Tui::g(224), Tui::g(32), ||self.midi_outs_with_sizes())) - .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), - |_, t|io_conns( - t.color.dark.rgb, - t.color.darker.rgb, - ||self.midi_outs_with_sizes() - ))) - } - fn view_output_nexts (&self) -> impl Content + '_ { - Tryptich::top(2).left(self.w_side(), Align::ne("From clip:")) - .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), - |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) - } - fn view_output_froms (&self) -> impl Content + '_ { - let label = Align::ne("Next clip:"); - Tryptich::top(2).left(self.w_side(), label) - .middle(self.w_mid(), per_track_top( - ||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(||{ - Tui::bg(Reset, if let Some((_, clip)) = track.sequencer.next_clip.as_ref() { - if let Some(clip) = clip { - clip.read().unwrap().name.clone() - } else { - "Stop".into() - } - } else { - "".into() - }) - }); - Either(queued, queued_clip, queued_blank) - })) - } - /// Render track headers - fn view_tracks_0 (&self) -> impl Content + '_ { - let w_side = self.w_side(); - let w_mid = self.w_mid(); - let is_editing = self.is_editing(); - let track_selected = self.arrangement().selection().track(); - Tryptich::center(3) - .left(w_side, - button_3("t", "track", format!("{}", self.tracks().len()), is_editing)) - .right(w_side, button_2("T", "add track", is_editing)) - .middle(w_mid, per_track(||self.tracks_with_sizes_scrolled(), - move|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. - fn view_devices_0 (&self) -> impl Content + '_ { - let w_side = self.w_side(); - let w_mid = self.w_mid(); - let is_editing = self.is_editing(); - let track_selected = self.arrangement().selection().track(); - Tryptich::top(1) - .left(w_side, button_3("d", "devices", format!("{}", 0), is_editing)) - .right(w_side, button_2("D", "add device", is_editing)) - .middle(w_mid, per_track_top(||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())) - })) - } fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } @@ -188,6 +15,11 @@ pub trait TracksView: ((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)|{ @@ -230,11 +62,11 @@ pub trait TracksView: track.color.light.rgb } else { track.color.base.rgb - }, Fill::x(Align::w(format!("[mut] [sol]")))), + }, 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()))))))))))); + format!("·o{index}: {}", port.name()))))))))))); } })))))); Bsp::w( @@ -245,35 +77,6 @@ pub trait TracksView: content ) } - 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::s( - Tui::bg(track.color.base.rgb, - Fill::x(Align::w(format!("[rec] [mon]")))), - 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)))), - ) - } fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 2u16; for track in self.tracks().iter() { @@ -292,14 +95,38 @@ pub trait TracksView: { 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}: {}", "--------")))))); + |_, index|format!("·d{index}: {}", "--------")))))); } })))))) } - 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_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)))), + ) } } @@ -308,10 +135,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { const H_SCENE: usize = 2; /// Default editor height. const H_EDITOR: usize = 15; - fn arrangement (&self) -> &Arrangement; - fn h_scenes (&self) -> u16; - fn w_side (&self) -> u16; - fn w_mid (&self) -> u16; + 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()) { @@ -320,81 +146,25 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { }) } fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content { - Fixed::xy(20, if self.selection().scene() == Some(index) && let Some(editor) = self.editor () { + 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 - }, Tui::bg(if self.selection().scene() == Some(index) { + }; + let bg = if self.selection().scene() == Some(index) { scene.color.light.rgb } else { scene.color.base.rgb - }, Align::nw(Bsp::e( - format!(" {index:2} "), - Tui::fg(Rgb(255, 255, 255), - Tui::bold(true, format!("{}", scene.name))))))) - //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.scenes().len().saturating_sub(1) == 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 - //}; - //add(&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(||self.arrangement().scenes()[s-1].color))) - } - fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> impl Content + 'a { - Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ - Tui::fg_bg(track.color.lightest.rgb, track.color.darker.rgb, - map_east(x1 as u16, (x2 - x1) as u16, callback(index, track))) }) - } - 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(||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( - false, // FIXME self.is_editing(), - Self::H_SCENE, Self::H_EDITOR, - selection.track(), selection.scene(), - ).map_while(|(s, scene, y1, y2)|(y2<=self.h_scenes() as usize) - .then_some((s, scene, y1, y2))) - } - /// Height required to display all scenes. - fn h_scenes_total (&self) -> u16 { - self.scenes_with_sizes( - self.is_editing(), - Self::H_SCENE, - Self::H_EDITOR, - self.selection().track(), - self.selection().scene(), - ) - .last() - .map(|(_, _, _, y)|y as u16).unwrap_or(0) + }; + 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()))))))))) } } diff --git a/crates/device/src/editor/editor_view.rs b/crates/device/src/editor/editor_view.rs index 454290a9..7e0c9661 100644 --- a/crates/device/src/editor/editor_view.rs +++ b/crates/device/src/editor/editor_view.rs @@ -13,9 +13,9 @@ impl MidiEditor { (clip.color, clip.name.clone(), clip.length, clip.looped) } else { (ItemTheme::G[64], String::new().into(), 0, false) }; Fixed::x(20, col!( - Fill::x(Align::w(FieldV(color, "Clip ", format!("{name}")))), - Fill::x(Align::w(FieldH(color, "Length", format!("{length}")))), - Fill::x(Align::w(FieldH(color, "Loop ", looped.to_string()))), + Fill::x(Align::w(Bsp::e(" Clip ", format!("{name}")))), + Fill::x(Align::w(Bsp::e(" Length ", format!("{length}")))), + Fill::x(Align::w(Bsp::e(" Loop ", looped.to_string()))), )) } @@ -31,9 +31,9 @@ impl MidiEditor { let note_pos = format!("{:>3}", note_pos); let note_len = format!("{:>4}", self.get_note_len()); Fixed::x(20, col!( - Fill::x(Align::w(FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos}")))), - Fill::x(Align::w(FieldH(color, "Lock", format!("{time_lock}")))), - Fill::x(Align::w(FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")))), + Fill::x(Align::w(Bsp::e(" Time ", format!("{length}/{time_zoom}+{time_pos}")))), + Fill::x(Align::w(Bsp::e(" Lock ", format!("{time_lock}")))), + Fill::x(Align::w(Bsp::e(" Note ", format!("{note_name} {note_pos} {note_len}")))), )) }