diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index 76c99881..9df87746 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -1,5 +1,3 @@ -(@space clock toggle) -(@shift-space clock toggle 0) (@t select :track 0) (@tab edit :clip) (@c color) diff --git a/config/keys_clock.edn b/config/keys_clock.edn new file mode 100644 index 00000000..5a0763be --- /dev/null +++ b/config/keys_clock.edn @@ -0,0 +1,2 @@ +(@space clock toggle) +(@shift-space clock toggle 0) diff --git a/config/keys_edit.edn b/config/keys_edit.edn deleted file mode 100644 index d9ff8eb6..00000000 --- a/config/keys_edit.edn +++ /dev/null @@ -1,30 +0,0 @@ -(@up note/pos :note-pos-next) -(@w note/pos :note-pos-next) -(@down note/pos :note-pos-prev) -(@s note/pos :note-pos-prev) - -(@pgup note/pos :note-pos-next-octave) -(@pgdn note/pos :note-pos-prev-octave) - -(@comma note/len :note-len-prev) -(@period note/len :note-len-next) -(@lt note/len :note-len-prev) -(@gt note/len :note-len-next) - -(@plus note/range :note-range-next) -(@underscore note/range :note-range-prev) - -(@left time/pos :time-pos-prev) -(@a time/pos :time-pos-prev) -(@right time/pos :time-pos-next) -(@d time/pos :time-pos-next) - -(@equal time/zoom :time-zoom-prev) -(@minus time/zoom :time-zoom-next) - -(@z time/lock) - -(@enter note/put) -(@shift-enter note/append) -(@del note/del) -(@shift-del note/del) diff --git a/config/keys_editor.edn b/config/keys_editor.edn new file mode 100644 index 00000000..88245b5c --- /dev/null +++ b/config/keys_editor.edn @@ -0,0 +1,30 @@ +(@up editor note/pos :note-pos-next) +(@w editor note/pos :note-pos-next) +(@down editor note/pos :note-pos-prev) +(@s editor note/pos :note-pos-prev) + +(@pgup editor note/pos :note-pos-next-octave) +(@pgdn editor note/pos :note-pos-prev-octave) + +(@comma editor note/len :note-len-prev) +(@period editor note/len :note-len-next) +(@lt editor note/len :note-len-prev) +(@gt editor note/len :note-len-next) + +(@plus editor note/range :note-range-next) +(@underscore editor note/range :note-range-prev) + +(@left editor time/pos :time-pos-prev) +(@a editor time/pos :time-pos-prev) +(@right editor time/pos :time-pos-next) +(@d editor time/pos :time-pos-next) + +(@equal editor time/zoom :time-zoom-prev) +(@minus editor time/zoom :time-zoom-next) + +(@z editor time/lock) + +(@enter editor note/put) +(@shift-enter editor note/append) +(@del editor note/del) +(@shift-del editor note/del) diff --git a/config/keys_groovebox.edn b/config/keys_groovebox.edn index 9058d778..99e13795 100644 --- a/config/keys_groovebox.edn +++ b/config/keys_groovebox.edn @@ -1,8 +1 @@ -(@space clock toggle) -(@shift-space clock toggle 0) - -(@c color) - -(@q launch) - (@r sampler record/begin :pitch) diff --git a/config/keys_sequencer.edn b/config/keys_sequencer.edn index 39b19380..eb8eba70 100644 --- a/config/keys_sequencer.edn +++ b/config/keys_sequencer.edn @@ -1,6 +1,4 @@ -(@space clock toggle) -(@shift-space clock toggle 0) -(@c color) -(@q launch) -(@shift-I input add) -(@shift-O output add) +(@c color) +(@q launch) +(@shift-I input add) +(@shift-O output add) diff --git a/config/view_groovebox.edn b/config/view_groovebox.edn index b358fe2d..d7c94a3b 100644 --- a/config/view_groovebox.edn +++ b/config/view_groovebox.edn @@ -1,7 +1,7 @@ -(bsp/a :modal - (bsp/s (fixed/y 1 :transport) - (bsp/n (fixed/y 1 :status) - (bsp/n (fixed/y 5 :sample-viewer) - (bsp/w (fixed/x :w-sidebar :pool) - (bsp/e :samples-keys - (fill/y :editor))))))) +(bsp/a :modal + (bsp/s (fixed/y 1 :transport) + (bsp/n (fixed/y 1 :status) + (bsp/n (fixed/y 5 :sample-viewer) + (bsp/w (fixed/x :w-sidebar :pool) + (bsp/e :samples-keys + (fill/y :editor))))))) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 8d914253..bc21fa49 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -6,19 +6,6 @@ macro_rules! ns { ($C:ty, $s:expr, $a:expr, $W:expr) => { <$C>::try_from_expr($s macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; } macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; } -view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { - ":nil" => Box::new("nil"), - ":modal" => self.view_modal(), - ":status" => self.view_status(), - ":transport" => self.view_transport(), - ":arranger" => self.view_arranger(), - ":pool" => self.view_pool(), - ":editor" => self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)), - ":samples-keys" => self.sampler().map(|s|s.view_list(false, self.editor().unwrap())), - ":samples-grid" => self.sampler().map(|s|s.view_grid()), - ":sample-viewer" => self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())), -}); - expose!([self: Tek] ([bool]) ([isize]) @@ -45,6 +32,13 @@ expose!([self: Tek] (":track-next" self.selected.track_next(self.tracks.len())) (":track-prev" self.selected.track_prev()))); +handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.keys.command(self, input) { + let undo = command.execute(self)?; + Some(true) +} else { + None +})); + impose!([app: Tek] (TekCommand: ("menu" [] Some(Self::ToggleMenu)) diff --git a/crates/app/src/keys.rs b/crates/app/src/keys.rs deleted file mode 100644 index c51fedeb..00000000 --- a/crates/app/src/keys.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::*; - -handle!(TuiIn: |self: Tek, input|if let Some(handler) = self.handler { - handler(self, input) -} else { - Ok(None) -}); - -pub fn handle_arranger (app: &mut Tek, input: &TuiIn) -> Perhaps { - Ok((app.is_editing() && app.editor.handle(input)? == Some(true) || - layer(app, input, include_str!("../../../config/keys_global.edn"))? || - layer(app, input, include_str!("../../../config/keys_arranger.edn"))? || - app.selected.is_clip() && layer(app, input, include_str!("../../../config/keys_clip.edn"))? || - app.selected.is_track() && layer(app, input, include_str!("../../../config/keys_track.edn"))? || - app.selected.is_scene() && layer(app, input, include_str!("../../../config/keys_scene.edn"))? || - app.selected.is_mix() && layer(app, input, include_str!("../../../config/keys_mix.edn"))?).then_some(true)) -} - -pub fn handle_sequencer (app: &mut Tek, input: &TuiIn) -> Perhaps { - Ok((app.editor.handle(input)? == Some(true) || - layer(app, input, include_str!("../../../config/keys_global.edn"))? || - layer(app, input, include_str!("../../../config/keys_sequencer.edn"))?).then_some(true)) -} - -pub fn handle_groovebox (app: &mut Tek, input: &TuiIn) -> Perhaps { - Ok((app.editor.handle(input)? == Some(true) || - layer(app, input, include_str!("../../../config/keys_global.edn"))? || - layer(app, input, include_str!("../../../config/keys_groovebox.edn"))?).then_some(true)) -} - -pub fn handle_sampler (app: &mut Tek, input: &TuiIn) -> Perhaps { - Ok((layer(app, input, include_str!("../../../config/keys_global.edn"))? || - layer(app, input, include_str!("../../../config/keys_sampler.edn"))?).then_some(true)) -} - -fn layer (app: &mut Tek, input: &TuiIn, keymap: &str) -> Usually { - if let Some(command) = SourceIter(keymap).command::<_, TekCommand, _>(app, input) { - if let Some(undo) = command.execute(app)? { - app.history.push(undo); - } - return Ok(true) - } - return Ok(false) -} diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs index d2ba0823..27b509c2 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -40,13 +40,13 @@ mod api; pub use self::api::*; mod audio; pub use self::audio::*; mod model; pub use self::model::*; mod view; pub use self::view::*; -mod keys; pub use self::keys::*; #[cfg(test)] #[test] fn test_model () { let mut tek = Tek::default(); let _ = tek.clip(); let _ = tek.toggle_loop(); } + #[cfg(test)] #[test] fn test_model_scene () { let mut app = Tek::default(); let _ = app.scene_longest(); @@ -58,6 +58,7 @@ mod keys; pub use self::keys::*; let _ = scene.pulses(); let _ = scene.is_playing(&[]); } + #[cfg(test)] #[test] fn test_view_clock () { let _ = button_play_pause(true); let mut app = Tek::default(); @@ -65,6 +66,7 @@ mod keys; pub use self::keys::*; let _ = app.view_status(); let _ = app.update_clock(); } + #[cfg(test)] #[test] fn test_view_layout () { let _ = button_2("", "", true); let _ = button_2("", "", false); @@ -74,6 +76,7 @@ mod keys; pub use self::keys::*; let _ = heading("", "", 0, "", false); let _ = wrap(Reset, Reset, ""); } + #[cfg(test)] mod test_view_meter { use super::*; use proptest::prelude::*; @@ -95,6 +98,7 @@ mod keys; pub use self::keys::*; } } } + #[cfg(test)] #[test] fn test_view_iter () { let mut tek = Tek::default(); tek.editor = Some(Default::default()); @@ -105,6 +109,7 @@ mod keys; pub use self::keys::*; //let _: Vec<_> = tek.scenes_with_colors(true, 10).collect(); //let _: Vec<_> = tek.scenes_with_track_colors(true, 10, 10).collect(); } + #[cfg(test)] #[test] fn test_view_sizes () { let app = Tek::default(); let _ = app.w(); diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index bd807637..2dc9f88b 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -50,10 +50,10 @@ pub struct Tek { pub view: SourceIter<'static>, // Cache of formatted strings pub view_cache: Arc>, - // Input handler function - pub handler: OptionResult, Box<(dyn std::error::Error + 'static)>>>, // Modal overlay - pub modal: Option + pub modal: Option, + // Input keymap + pub keys: InputMap<'static, Self, TekCommand, TuiIn, SourceIter<'static>> } impl Tek { diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 97ad9a75..22a66828 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -2,6 +2,225 @@ use crate::*; pub(crate) use std::fmt::Write; pub(crate) use ::tengri::tui::ratatui::prelude::Position; +view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { + ":nil" => Box::new("nil"), + ":modal" => self.view_modal(), + ":status" => self.view_status(), + ":transport" => self.view_transport(), + ":arranger" => self.view_arranger(), + ":pool" => self.view_pool(), + ":editor" => self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)), + ":samples-keys" => self.sampler().map(|s|s.view_list(false, self.editor().unwrap())), + ":samples-grid" => self.sampler().map(|s|s.view_grid()), + ":sample-viewer" => self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())), +}); + +trait ScenesColors<'a> = Iterator>; + +type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); + +impl Tek { + + pub fn view_modal (&self) -> impl Content + use<'_> { + When::new(self.modal.is_some(), Bsp::b( + Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")), + Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), Bsp::b( + Repeat(" "), + Outer(true, Style::default().fg(Tui::g(96))) + .enclose(self.modal.map(|modal|match modal { + Modal::Menu => self.view_modal_menu().boxed(), + Modal::Help => self.view_modal_help().boxed(), + })) + ))) + )) + } + + fn view_modal_menu (&self) -> impl Content { + let options = ||["Projects", "Settings", "Help", "Quit"].iter(); + let option = |a,i|Tui::fg(Color::Rgb(255,255,255), format!("{}", a)); + Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) + } + + fn view_modal_help (&self) -> impl Content { + let bindings = ||TokenIter::new(include_str!("../../../config/keys_groovebox.edn")) + .filter_map(|x|if let Value::Exp(_, iter)=x.value{ + Some(iter) + } else { + None + }); + let binding = |mut binding: TokenIter, _|Bsp::e( + Tui::bold(true, Tui::fg(Color::Rgb(255,192,0), if let Some(Token { + value: Value::Sym(key), .. + }) = binding.next() { + Some(key.to_string()) + } else { + None + })), + Bsp::e(" ", Tui::fg(Color::Rgb(255,255,255), if let Some(Token { + value: Value::Key(command), .. + }) = binding.next() { + Some(command.to_string()) + } else { + None + })), + ); + let layer = Map::south(1, bindings, binding); + Bsp::s(Tui::bold(true, "Help"), Bsp::s("", layer)) + } + + pub fn view_status (&self) -> impl Content + use<'_> { + self.update_clock(); + let cache = self.view_cache.read().unwrap(); + view_status( + self.selected.describe(&self.tracks, &self.scenes), + cache.sr.view.clone(), + cache.buf.view.clone(), + cache.lat.view.clone(), + ) + } + + pub fn view_transport (&self) -> impl Content + use<'_> { + self.update_clock(); + let cache = self.view_cache.read().unwrap(); + view_transport( + self.clock.is_rolling(), + cache.bpm.view.clone(), + cache.beat.view.clone(), + cache.time.view.clone(), + ) + } + + pub fn view_arranger (&self) -> impl Content + use<'_> { + ArrangerView::new(self) + } + + pub fn view_pool (&self) -> impl Content + use<'_> { + self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p))) + } + + /// Spacing between tracks. + pub(crate) const TRACK_SPACING: usize = 0; + + /// Default scene height. + pub(crate) const H_SCENE: usize = 2; + + /// Default editor height. + pub(crate) const H_EDITOR: usize = 15; + + /// Width of display + pub(crate) fn w (&self) -> u16 { + self.size.w() as u16 + } + + pub(crate) fn w_sidebar (&self) -> u16 { + self.w() / if self.is_editing() { 16 } else { 8 } as u16 + } + + /// Width taken by all tracks. + pub(crate) fn w_tracks (&self) -> u16 { + self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) + } + + /// Width available to display tracks. + pub(crate) fn w_tracks_area (&self) -> u16 { + self.w().saturating_sub(2 * self.w_sidebar()) + } + + /// Height of display + pub(crate) fn h (&self) -> u16 { + self.size.h() as u16 + } + + /// Height available to display track headers. + pub(crate) fn h_tracks_area (&self) -> u16 { + 5 + //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) + } + + /// Height available to display tracks. + pub(crate) fn h_scenes_area (&self) -> u16 { + //15 + self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 11) + } + + /// Height taken by all inputs. + pub(crate) fn h_inputs (&self) -> u16 { + 1 + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + + /// Height taken by all outputs. + pub(crate) fn h_outputs (&self) -> u16 { + 1 + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + + /// Height taken by all scenes. + pub(crate) fn h_scenes (&self) -> u16 { + self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last() + .map(|(_, _, _, y)|y as u16).unwrap_or(0) + } + + pub(crate) fn inputs_with_sizes (&self) -> impl PortsSizes<'_> { + let mut y = 0; + self.midi_ins.iter().enumerate().map(move|(i, input)|{ + let height = 1 + input.conn().len(); + let data = (i, input.name(), input.conn(), y, y + height); + y += height; + data + }) + } + + pub(crate) fn outputs_with_sizes (&self) -> impl PortsSizes<'_> { + let mut y = 0; + self.midi_outs.iter().enumerate().map(move|(i, output)|{ + let height = 1 + output.conn().len(); + let data = (i, output.name(), output.conn(), y, y + height); + y += height; + data + }) + } + + pub(crate) fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { + 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), + _ => None + }; + let bigger = self.editor_w(); + self.tracks().iter().enumerate().map(move |(index, track)|{ + let width = if Some(index) == active.copied() { bigger } else { track.width.max(8) }; + let data = (index, track, x, x + width); + x += width + Tek::TRACK_SPACING; + data + }) + } + + pub(crate) fn scenes_with_sizes (&self, editing: bool, height: usize, larger: usize) + -> impl ScenesSizes<'_> + { + 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)), + _ => (None, None) + }; + let mut y = 0; + self.scenes().iter().enumerate().map(move|(s, scene)|{ + let active = editing && selected_track.is_some() && selected_scene == Some(s); + let height = if active { larger } else { height }; + let data = (s, scene, y, y + height); + y += height; + data + }) + } + + pub fn update_clock (&self) { + ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80) + } + +} + pub(crate) struct ArrangerView<'a> { app: &'a Tek, @@ -146,14 +365,8 @@ impl<'a> ArrangerView<'a> { /// Render output matrix. pub(crate) fn outputs (&'a self) -> impl Content + 'a { Tui::bg(Color::Reset, Align::n(Bsp::s( - Bsp::s( - self.output_nexts(), - self.output_froms(), - ), - Bsp::s( - self.output_ports(), - self.output_conns(), - ) + Bsp::s(self.output_nexts(), self.output_froms()), + Bsp::s(self.output_ports(), self.output_conns()), ))) } @@ -369,160 +582,6 @@ impl<'a> ArrangerView<'a> { } -trait ScenesColors<'a> = Iterator>; - -type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); - -impl Tek { - /// Spacing between tracks. - pub(crate) const TRACK_SPACING: usize = 0; - /// Default scene height. - pub(crate) const H_SCENE: usize = 2; - /// Default editor height. - pub(crate) const H_EDITOR: usize = 15; - - /// Width of display - pub(crate) fn w (&self) -> u16 { - self.size.w() as u16 - } - pub(crate) fn w_sidebar (&self) -> u16 { - self.w() / if self.is_editing() { 16 } else { 8 } as u16 - } - /// Width taken by all tracks. - pub(crate) fn w_tracks (&self) -> u16 { - self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) - } - /// Width available to display tracks. - pub(crate) fn w_tracks_area (&self) -> u16 { - self.w().saturating_sub(2 * self.w_sidebar()) - } - /// Height of display - pub(crate) fn h (&self) -> u16 { - self.size.h() as u16 - } - /// Height available to display track headers. - pub(crate) fn h_tracks_area (&self) -> u16 { - 5 - //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) - } - /// Height available to display tracks. - pub(crate) fn h_scenes_area (&self) -> u16 { - //15 - self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 11) - } - /// Height taken by all inputs. - pub(crate) fn h_inputs (&self) -> u16 { - 1 + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all outputs. - pub(crate) fn h_outputs (&self) -> u16 { - 1 + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all scenes. - pub(crate) fn h_scenes (&self) -> u16 { - self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last() - .map(|(_, _, _, y)|y as u16).unwrap_or(0) - } - - pub(crate) fn inputs_with_sizes (&self) -> impl PortsSizes<'_> { - let mut y = 0; - self.midi_ins.iter().enumerate().map(move|(i, input)|{ - let height = 1 + input.conn().len(); - let data = (i, input.name(), input.conn(), y, y + height); - y += height; - data - }) - } - - pub(crate) fn outputs_with_sizes (&self) -> impl PortsSizes<'_> { - let mut y = 0; - self.midi_outs.iter().enumerate().map(move|(i, output)|{ - let height = 1 + output.conn().len(); - let data = (i, output.name(), output.conn(), y, y + height); - y += height; - data - }) - } - - pub(crate) fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { - 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), - _ => None - }; - let bigger = self.editor_w(); - self.tracks().iter().enumerate().map(move |(index, track)|{ - let width = if Some(index) == active.copied() { bigger } else { track.width.max(8) }; - let data = (index, track, x, x + width); - x += width + Tek::TRACK_SPACING; - data - }) - } - - pub(crate) fn scenes_with_sizes (&self, editing: bool, height: usize, larger: usize) - -> impl ScenesSizes<'_> - { - 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)), - _ => (None, None) - }; - let mut y = 0; - self.scenes().iter().enumerate().map(move|(s, scene)|{ - let active = editing && selected_track.is_some() && selected_scene == Some(s); - let height = if active { larger } else { height }; - let data = (s, scene, y, y + height); - y += height; - data - }) - } - - pub fn update_clock (&self) { - ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80) - } - - pub fn view_transport (&self) -> impl Content + use<'_> { - self.update_clock(); - let cache = self.view_cache.read().unwrap(); - view_transport( - self.clock.is_rolling(), - cache.bpm.view.clone(), - cache.beat.view.clone(), - cache.time.view.clone(), - ) - } - - pub fn view_status (&self) -> impl Content + use<'_> { - self.update_clock(); - let cache = self.view_cache.read().unwrap(); - view_status( - self.selected.describe(&self.tracks, &self.scenes), - cache.sr.view.clone(), - cache.buf.view.clone(), - cache.lat.view.clone(), - ) - } - - pub fn view_modal (&self) -> impl Content + use<'_> { - When::new(self.modal.is_some(), Bsp::b( - Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")), - Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), self.modal)) - )) - } - - pub fn view_arranger (&self) -> impl Content + use<'_> { - ArrangerView::new(self) - } - - pub fn view_pool (&self) -> impl Content + use<'_> { - self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p))) - } - -} - /// Define a type alias for iterators of sized items (columns). macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { @@ -577,8 +636,7 @@ fn view_status ( pub(crate) fn button_play_pause (playing: bool) -> impl Content { let compact = true;//self.is_editing(); - Tui::bg( - if playing{Rgb(0,128,0)}else{Rgb(128,64,0)}, + Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, Either::new(compact, Thunk::new(move||Fixed::x(9, Either::new(playing, Tui::fg(Rgb(0, 255, 0), " PLAYING "), @@ -614,18 +672,15 @@ pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content impl Content + use<'_> { - Bsp::s( - format!("L/{:>+9.3}", values[0]), - format!("R/{:>+9.3}", values[1]), - ) + let left = format!("L/{:>+9.3}", values[0]); + let right = format!("R/{:>+9.3}", values[1]); + Bsp::s(left, right) } -pub(crate) fn wrap ( - bg: Color, fg: Color, content: impl Content -) -> impl Content { - Bsp::e(Tui::fg_bg(bg, Reset, "▐"), - Bsp::w(Tui::fg_bg(bg, Reset, "▌"), - Tui::fg_bg(fg, bg, content))) +pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { + let left = Tui::fg_bg(bg, Reset, "▐"); + let right = Tui::fg_bg(bg, Reset, "▌"); + Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content))) } pub(crate) fn button_2 <'a, K, L> ( @@ -848,44 +903,3 @@ impl ViewCache { } } } - -impl Content for Modal { - fn content (&self) -> impl Render { - Bsp::b( - Repeat(" "), - Outer(true, Style::default().fg(Tui::g(96))).enclose(match self { - Self::Menu => self.view_menu().boxed(), - Self::Help => self.view_help().boxed(), - }) - ) - } -} - -impl Modal { - fn view_menu (&self) -> impl Content { - let options = ||["Projects", "Settings", "Help", "Quit"].iter(); - let option = |a,i|Tui::fg(Color::Rgb(255,255,255), format!("{}", a)); - Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) - } - fn view_help (&self) -> impl Content { - let bindings = ||TokenIter::new(include_str!("../../../config/keys_groovebox.edn")) - .filter_map(|x|if let Value::Exp(_, iter)=x.value{ - Some(iter) - } else { - None - }); - let binding = |mut binding: TokenIter, _|Bsp::e( - Tui::bold(true, Tui::fg(Color::Rgb(255,192,0), if let Some(Token { value: Value::Sym(key), .. }) = binding.next() { - Some(key.to_string()) - } else { - None - })), - Bsp::e(" ", Tui::fg(Color::Rgb(255,255,255), if let Some(Token { value: Value::Key(command), .. }) = binding.next() { - Some(command.to_string()) - } else { - None - })), - ); - Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, binding))) - } -} diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index a8c131ba..464e3268 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -106,13 +106,49 @@ impl Cli { color: ItemTheme::random(), clock: Clock::new(jack, self.bpm)?, view: SourceIter(match mode { - LaunchMode::Clock => include_str!("../../config/view_transport.edn"), - LaunchMode::Sequencer => include_str!("../../config/view_sequencer.edn"), - LaunchMode::Groovebox => include_str!("../../config/view_groovebox.edn"), - LaunchMode::Arranger { .. } => include_str!("../../config/view_arranger.edn"), - LaunchMode::Sampler => include_str!("../../config/view_sampler.edn"), + LaunchMode::Clock => + include_str!("../../config/view_transport.edn"), + LaunchMode::Sequencer => + include_str!("../../config/view_sequencer.edn"), + LaunchMode::Groovebox => + include_str!("../../config/view_groovebox.edn"), + LaunchMode::Arranger { .. } => + include_str!("../../config/view_arranger.edn"), + LaunchMode::Sampler => + include_str!("../../config/view_sampler.edn"), _ => todo!("{mode:?}"), }), + keys: match mode { + LaunchMode::Sampler => InputMap::default() + .layer(SourceIter(include_str!("../../config/keys_global.edn"))) + .layer(SourceIter(include_str!("../../config/keys_sampler.edn"))), + LaunchMode::Clock => InputMap::default() + .layer(SourceIter(include_str!("../../config/keys_global.edn"))) + .layer(SourceIter(include_str!("../../config/keys_clock.edn"))), + LaunchMode::Sequencer => InputMap::default() + .layer(SourceIter(include_str!("../../config/keys_global.edn"))) + .layer(SourceIter(include_str!("../../config/keys_editor.edn"))) + .layer(SourceIter(include_str!("../../config/keys_clock.edn"))) + .layer(SourceIter(include_str!("../../config/keys_sequencer.edn"))), + LaunchMode::Groovebox => InputMap::default() + .layer(SourceIter(include_str!("../../config/keys_global.edn"))) + .layer(SourceIter(include_str!("../../config/keys_editor.edn"))) + .layer(SourceIter(include_str!("../../config/keys_clock.edn"))) + .layer(SourceIter(include_str!("../../config/keys_sequencer.edn"))) + .layer(SourceIter(include_str!("../../config/keys_groovebox.edn"))), + LaunchMode::Arranger {..} => InputMap::default() + .layer(SourceIter(include_str!("../../config/keys_global.edn"))) + .layer_if( + |state: &Tek|state.is_editing(), + SourceIter(include_str!("../../config/keys_editor.edn"))) + .layer(SourceIter(include_str!("../../config/keys_clock.edn"))) + .layer(SourceIter(include_str!("../../config/keys_arranger.edn"))) + .layer(SourceIter(include_str!("../../config/keys_clip.edn"))) + .layer(SourceIter(include_str!("../../config/keys_track.edn"))) + .layer(SourceIter(include_str!("../../config/keys_scene.edn"))) + .layer(SourceIter(include_str!("../../config/keys_mix.edn"))), + _ => todo!("{mode:?}"), + }, pool: match mode { LaunchMode::Sequencer | LaunchMode::Groovebox => clip.as_ref().map(Into::into), LaunchMode::Arranger { .. } => Some(Default::default()), @@ -126,16 +162,13 @@ impl Cli { midi_ins, midi_outs, midi_buf: match mode { - LaunchMode::Clock | LaunchMode::Sampler => vec![], - LaunchMode::Sequencer | LaunchMode::Groovebox | LaunchMode::Arranger {..} => vec![vec![];65536], + LaunchMode::Clock + | LaunchMode::Sampler => vec![], + LaunchMode::Sequencer + | LaunchMode::Groovebox + | LaunchMode::Arranger {..} => vec![vec![];65536], _ => todo!("{mode:?}"), }, - handler: Some(match mode { - LaunchMode::Sequencer => handle_sequencer, - LaunchMode::Groovebox => handle_groovebox, - LaunchMode::Sampler => handle_sampler, - _ => handle_arranger, - }), tracks: match mode { LaunchMode::Sequencer => vec![ Track::new_sequencer() diff --git a/crates/midi/src/clip/clip_editor.rs b/crates/midi/src/clip/clip_editor.rs index 6629e106..efdd92d6 100644 --- a/crates/midi/src/clip/clip_editor.rs +++ b/crates/midi/src/clip/clip_editor.rs @@ -3,9 +3,12 @@ use crate::*; /// Contains state for viewing and editing a clip pub struct MidiEditor { - pub mode: PianoHorizontal, + /// Size of editor on screen pub size: Measure, - pub keys: InputMap<'static, Self, MidiEditCommand, TuiIn> + /// View mode and state of editor + pub mode: PianoHorizontal, + /// Input keymap + pub keys: InputMap<'static, Self, MidiEditCommand, TuiIn, SourceIter<'static>> } impl std::fmt::Debug for MidiEditor { @@ -19,10 +22,9 @@ impl std::fmt::Debug for MidiEditor { impl Default for MidiEditor { fn default () -> Self { Self { - mode: PianoHorizontal::new(None), size: Measure::new(), - keys: InputMap::new() - .layer(SourceIter(include_str!("../../../../config/keys_edit.edn"))), + mode: PianoHorizontal::new(None), + keys: InputMap::new(SourceIter(include_str!("../../../../config/keys_editor.edn"))), } } } @@ -206,14 +208,12 @@ atom_command!(MidiEditCommand: |state: MidiEditor| { Show(Option>>), } -handle!(TuiIn: |self: MidiEditor, input|{ - Ok(if let Some(command) = self.keys.command(self, input) { - let _undo = command.execute(self)?; - Some(true) - } else { - None - }) -}); +handle!(TuiIn: |self: MidiEditor, input|Ok(if let Some(command) = self.keys.command(self, input) { + command.execute(self)?; + Some(true) +} else { + None +})); impl Command for MidiEditCommand { fn execute (self, state: &mut MidiEditor) -> Perhaps { diff --git a/crates/midi/src/piano/piano_h.rs b/crates/midi/src/piano/piano_h.rs index 4cbcc1df..d4650cf4 100644 --- a/crates/midi/src/piano/piano_h.rs +++ b/crates/midi/src/piano/piano_h.rs @@ -2,6 +2,7 @@ use crate::*; use Color::*; /// A clip, rendered as a horizontal piano roll. +#[derive(Clone)] pub struct PianoHorizontal { pub clip: Option>>, /// Buffer where the whole clip is rerendered on change diff --git a/crates/midi/src/pool/pool_model.rs b/crates/midi/src/pool/pool_model.rs index 5aec75c0..9ff62d68 100644 --- a/crates/midi/src/pool/pool_model.rs +++ b/crates/midi/src/pool/pool_model.rs @@ -1,6 +1,6 @@ use crate::*; -//#[derive(Debug)] +#[derive(Debug)] pub struct MidiPool { pub visible: bool, /// Collection of clips @@ -10,7 +10,7 @@ pub struct MidiPool { /// Mode switch pub mode: Option, - pub keys: InputMap<'static, Self, PoolCommand, TuiIn>, + pub keys: InputMap<'static, Self, PoolCommand, TuiIn, SourceIter<'static>>, //pub keys: SourceIter<'static>, //pub keys_rename: SourceIter<'static>, //pub keys_length: SourceIter<'static>, @@ -25,9 +25,7 @@ impl Default for MidiPool { clips: Arc::from(RwLock::from(vec![])), clip: 0.into(), mode: None, - keys: InputMap::new() - .layer( - SourceIter(include_str!("../../../../config/keys_edit.edn"))) + keys: InputMap::new(SourceIter(include_str!("../../../../config/keys_pool.edn"))) .layer_if(|pool: &Self|matches!(pool.mode, Some(Import(..))|Some(Export(..))), SourceIter(include_str!("../../../../config/keys_pool_file.edn"))) .layer_if(|pool: &Self|matches!(pool.mode, Some(Rename(..))), diff --git a/crates/midi/src/port/port_out.rs b/crates/midi/src/port/port_out.rs index 1cdee9cb..159d11c6 100644 --- a/crates/midi/src/port/port_out.rs +++ b/crates/midi/src/port/port_out.rs @@ -3,10 +3,13 @@ use crate::*; /// Trait for thing that may output MIDI. pub trait HasMidiOuts { fn midi_outs (&self) -> &Vec; + fn midi_outs_mut (&mut self) -> &mut Vec; + fn has_midi_outs (&self) -> bool { !self.midi_outs().is_empty() } + /// Buffer for serializing a MIDI event. FIXME rename fn midi_note (&mut self) -> &mut Vec; } diff --git a/deps/tengri b/deps/tengri index 35ad3712..4ec51d5b 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 35ad37120554611197c1e30bbed657f310d332c3 +Subproject commit 4ec51d5b694c14ccf617ec4538da04089b17ab92