From 406aabde6e0737cf8964fa68b56e5f2de085754c Mon Sep 17 00:00:00 2001 From: i do not exist Date: Wed, 12 Aug 2026 11:32:51 +0300 Subject: [PATCH 1/3] rework command/namespace dispatch --- Cargo.toml | 3 +- Justfile | 8 +- proc/src/lib.rs | 216 +++++++++++++------ shell.nix | 1 + src/device/arrange/scene.rs | 32 +-- src/device/arrange/track.rs | 60 +++--- src/device/browse.rs | 12 +- src/device/clock.rs | 67 +++++- src/device/editor.rs | 24 +-- src/device/pool.rs | 34 ++- src/device/sampler.rs | 6 +- src/device/sequence.rs | 14 +- src/tek.edn | 75 ++++--- src/tek.rs | 406 +++++++++++++++++++----------------- tengri | 2 +- 15 files changed, 586 insertions(+), 374 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fcc3c5d6..1b93bd3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ name = "tek" path = "src/tek.rs" [target.'cfg(target_os = "linux")'] -rustflags = ["-C", "link-arg=-fuse-ld=mold"] +linker = "clang" +rustflags = ["-Clink-arg=-fuse-ld=mold", "-Clink-arg=-Wl,--no-rosegment"] [dependencies] tek_proc = { path = "./proc" } diff --git a/Justfile b/Justfile index 33f077c9..87637b33 100644 --- a/Justfile +++ b/Justfile @@ -1,7 +1,11 @@ #export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold" export RUST_BACKTRACE := "1" -default +ARGS="new": +[default] +list: + just -l + +now +ARGS="new": cargo run -- {{ARGS}} doc +ARGS="": @@ -44,7 +48,7 @@ run-init: rm -rf ~/.config/tek && {{debug}} prof: - CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -- + CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -- new release := "reset && cargo run --release --" release: diff --git a/proc/src/lib.rs b/proc/src/lib.rs index fcd4ff7e..691fd5b3 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -48,7 +48,7 @@ attribute!(commands { impl Parse for CustomAttributeMeta { /// Parse contents of `#[command(...)]` attribute tag. fn parse (input: ParseStream) -> Result { - let meta: Expr = input.parse()?; + let meta: Expr = input.parse()?; Ok(match meta { // Struct name only Expr::Path(ExprPath { path, .. }) => Self(path, None), @@ -167,69 +167,18 @@ attribute!(commands { let mut keywords = quote! {}; let mut expressions = quote! {}; for (ident, (variant, inputs, keyword)) in items.iter() { - let mut typed = quote! {}; - let mut params = quote! {}; - let mut has_args = false; - for arg in inputs.iter() { - if let FnArg::Typed(PatType { pat, ty, .. }) = arg { - has_args = true; - append(&mut typed, quote! { #pat: #ty, }); - append(&mut params, quote! { #pat, }); - } - } - append(&mut variants, if has_args { - quote! { #variant { #typed }, } - } else { - quote! { #variant, } - }); - append(&mut dispatch, if has_args { - quote! { #command::#variant { #params } => state.#ident(#params), } - } else { - quote! { #command::#variant => state.#ident(), } - }); - let keyword = if let Some(namespace) = namespace { - format!("{}/{}", namespace.value(), keyword.value()) - } else { - keyword.value() - }; - if has_args { - append(&mut expressions, quote! { - #keyword (#typed) => { #command::#variant { #params } }, - }); - } else { - append(&mut keywords, quote! { - #keyword => #command::#variant, - }); - } + write_variant( + &mut expressions, &mut keywords, &mut variants, &mut dispatch, + ident, command, variant, inputs, namespace, keyword, + ) } let impls = match item { - Item::Impl(ItemImpl { generics, .. }) => { - let lts = Punctuated::<_, Comma>::from_iter(generics.lifetimes()); - let tys = Punctuated::<_, Comma>::from_iter(generics.type_params()); - let cns = Punctuated::<_, Comma>::from_iter(generics.const_params()); - quote! { - impl<'n, #tys> dizzle::Namespace<'n, #command> for #state { - symbols!('n |state: Self| -> #command { #keywords }); - expressions!('n |state: Self| -> #command { #expressions }); - } - impl #generics #command { - pub fn act (self, state: &mut #state) -> Perhaps { - match self { #dispatch _ => unreachable!() } - } - } - } - }, - Item::Trait { .. } => quote! { - impl<'n, T: #state + 'n> dizzle::Namespaced<'n, T> for #command { - symbols!('n |state: T| -> Self { #keywords }); - expressions!('n |state: T| -> Self { #expressions }); - } - impl #command { - pub fn act <'n, T: #state + 'n> (self, state: &mut T) -> Perhaps { - match self { #dispatch _ => unreachable!() } - } - } - }, + Item::Impl(ItemImpl { generics, .. }) => write_for_struct( + keywords, expressions, dispatch, state, command, generics, + ), + Item::Trait { .. } => write_for_trait( + keywords, expressions, dispatch, state, command, + ), _ => panic!("trait or inherent impl needed for #[commands]") }; append(out, quote! { @@ -240,6 +189,145 @@ attribute!(commands { }) } } + + fn write_variant ( + exps: &mut TokenStream2, + syms: &mut TokenStream2, + vars: &mut TokenStream2, + disp: &mut TokenStream2, + ident: &Ident, + command: &Path, + variant: &Ident, + inputs: &[FnArg], + namespace: &Option, + keyword: &LitStr, + ) { + let keyword = if let Some(namespace) = namespace { + format!("{}/{}", namespace.value(), keyword.value()) + } else { + keyword.value() + }; + let mut typed = quote! {}; + let mut params = quote! {}; + let mut obtain = quote! {}; + let mut has_args = false; + for arg in inputs.iter() { + if let FnArg::Typed(PatType { pat, ty, .. }) = arg { + has_args = true; + append(&mut typed, quote! { #pat: #ty, }); + append(&mut params, quote! { #pat, }); + append(&mut obtain, quote! { let #pat: #ty = { + let head = tail.head()?.unwrap_or_default(); + let tail = tail.tail()?.unwrap_or_default(); + match dizzle::Namespace::<#ty>::namespace(state, &head)? { + Some(arg) => arg, + None => return Err(format!("{}: arg \"{}\" ({}) got: {head} {tail}", + #keyword, stringify!(#pat), stringify!(#ty), + ).into()) + } + };}); + } + } + if has_args { + append(vars, quote! { #variant { #typed }, }); + append(disp, quote! { #command::#variant { #params } => state.#ident(#params), }); + append(exps, quote! { + let tail_base = tail; + if head.src()? == Some(#keyword) { + let tail = tail_base; + #obtain + return Ok(Some(#command::#variant { #params })) + } + }); + } else { + append(vars, quote! { #variant, }); + append(disp, quote! { #command::#variant => state.#ident(), }); + append(syms, quote! { + if word == #keyword { return Ok(Some(#command::#variant)); } + }); + } + } + + fn write_for_struct ( + syms: TokenStream2, + exps: TokenStream2, + disp: TokenStream2, + state: &Path, + command: &Path, + generics: &syn::Generics, + ) -> TokenStream2 { + //let lts = Punctuated::<_, Comma>::from_iter(generics.lifetimes()); + //let cns = Punctuated::<_, Comma>::from_iter(generics.const_params()); + let tys = Punctuated::<_, Comma>::from_iter(generics.type_params()); + quote! { + impl<#tys> dizzle::Namespace<#command> for #state { + //def_namespace_symbols!('n |state: Self| -> #command { + //#syms + //}); + //def_namespace_exps!('n |state: Self| -> #command { + //#exps + //}); + fn namespace_symbol (&self, word: impl Symbol) -> Perhaps<#command> { + if let Some(word) = word.word()? { + #syms + } + Ok(None) + } + fn namespace_expression (&self, expr: impl Expression) -> Perhaps<#command> { + let state = self; + if let Some(expr) = expr.expr()? { + let head = expr.head()?; + let tail = expr.tail()?; + #exps + } + Ok(None) + } + } + impl #generics dizzle::Dispatch<#state> for #command { + fn dispatch (self, state: &mut #state) -> Perhaps { + match self { + #disp + _ => unreachable!() + } + } + } + } + } + + fn write_for_trait ( + syms: TokenStream2, + exps: TokenStream2, + disp: TokenStream2, + state: &Path, + command: &Path, + ) -> TokenStream2 { + quote! { + impl dizzle::Namespaced for #command { + fn namespaced_symbol (state: &T, word: L) -> Perhaps<#command> { + if let Some(word) = word.word()? { + #syms + } + Ok(None) + } + fn namespaced_expression (state: &T, expr: L) -> Perhaps<#command> { + if let Some(expr) = expr.expr()? { + let head = expr.head()?; + let tail = expr.tail()?; + #exps + } + Ok(None) + } + } + impl dizzle::Dispatch for #command { + fn dispatch (self, state: &mut T) -> Perhaps { + match self { + #disp + _ => unreachable!() + } + } + } + } + } }); attribute!(command { @@ -393,8 +481,8 @@ attribute!(keyword { append(out, quote! { #item - impl<'n> Namespace<'n, #ident> for #state { - symbols!('n |_state: #state| -> #ident { #body, }); + impl Namespace<#ident> for #state { + def_namespace_symbols!(|_state: #state| -> #ident { #body, }); } }) } diff --git a/shell.nix b/shell.nix index 7417c55d..66738184 100755 --- a/shell.nix +++ b/shell.nix @@ -9,6 +9,7 @@ pkgs.grcov pkgs.libclang pkgs.mold + pkgs.perf pkgs.pkg-config pkgs.watchexec ]; diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index eae2e854..ce45629e 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -73,32 +73,32 @@ pub trait HasScene: AsRefOpt + AsMutOpt { } impl Namespace<'a, usize> - + for<'a> Namespace<'a, Arc> - + for<'a> Namespace<'a, ItemTheme> + + Namespace + + Namespace> + + Namespace > SceneController for T {} #[tek_proc::commands(SceneCommand)] pub trait SceneController: HasScene - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Arc> - + for<'a> Namespace<'a, ItemTheme> + + Namespace + + Namespace> + + Namespace { #[command(SetSize = "scene/size")] fn scene_set_size (&mut self, size: usize) -> Perhaps - where Self: for<'a> Namespace<'a, usize> + where Self: Namespace { todo!() } #[command(SetZoom = "scene/zoom")] fn scene_set_zoom (&mut self, size: usize) -> Perhaps - where Self: for<'a> Namespace<'a, usize> + where Self: Namespace { todo!() } #[command(SetName = "scene/name")] fn scene_set_name (&mut self, name: Arc) -> Perhaps - where Self: for<'a> Namespace<'a, Arc> + where Self: Namespace> { Ok(self.scene_mut().map(|scene|swap_value( &mut scene.name, @@ -108,7 +108,7 @@ pub trait SceneController: HasScene } #[command(SetColor = "scene/color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps - where Self: for<'a> Namespace<'a, ItemTheme> + where Self: Namespace { Ok(self.scene_mut().map(|scene|swap_value( &mut scene.color, @@ -172,16 +172,16 @@ pub trait HasScenes: AsRef> + AsMut> { } impl Namespace<'a, usize> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>> + + Namespace + + Namespace> + + Namespace>> > ScenesController for T {} #[tek_proc::commands(ScenesCommand)] pub trait ScenesController: HasScenes - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>> + + Namespace + + Namespace> + + Namespace>> { // TODO } diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs index 2aa64c92..78c27a72 100644 --- a/src/device/arrange/track.rs +++ b/src/device/arrange/track.rs @@ -85,18 +85,18 @@ pub trait HasTrack: AsRefOpt + AsMutOpt { } impl Namespace<'a, usize> - + for<'a> Namespace<'a, Arc> - + for<'a> Namespace<'a, ItemTheme> - + for<'a> Namespace<'a, Option> + + Namespace + + Namespace> + + Namespace + + Namespace> > TrackController for T {} #[tek_proc::commands(TrackCommand)] pub trait TrackController: HasTrack - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Arc> - + for<'a> Namespace<'a, ItemTheme> - + for<'a> Namespace<'a, Option> + + Namespace + + Namespace> + + Namespace + + Namespace> { #[command(Stop = "track/stop")] fn track_stop (&mut self) -> Perhaps { @@ -259,27 +259,31 @@ pub trait HasTracks: AsRef> + AsMut> + HasClock + HasTrack } impl Namespace<'a, usize> - + for<'a> Namespace<'a, Arc<[Connect]>> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>> - + for<'a> Namespace<'a, Option>>>>> + + HasScenes + + HasJack<'static> + + Namespace + + Namespace> + + Namespace> + + Namespace>> + + Namespace> + + Namespace>> + + Namespace>>>>> > TracksController for T {} #[tek_proc::commands(TracksCommand)] pub trait TracksController: HasTracks - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Arc<[Connect]>> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>> - + for<'a> Namespace<'a, Option>>>>> + + HasScenes + + HasJack<'static> + + Namespace + + Namespace> + + Namespace> + + Namespace>> + + Namespace> + + Namespace>> + + Namespace>>>>> { - #[command(Stop = "tracks/stop")] + #[command(Stop = "stop")] /// Stop all playing clips fn tracks_stop_all (&mut self) -> Perhaps { for track in self.tracks_mut().iter_mut() { @@ -288,7 +292,7 @@ pub trait TracksController: HasTracks Ok(None) } - #[command(Launch = "tracks/launch")] + #[command(Launch = "launch")] /// Launch multiple clips fn tracks_launch ( &mut self, clips: Option>>>> @@ -304,6 +308,14 @@ pub trait TracksController: HasTracks } Ok(None) } + + #[command(Add = "add")] + fn tracks_add (&mut self) -> Perhaps + where Self: HasScenes + HasJack<'static> + { + let (index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; + Ok(None) + } } impl< diff --git a/src/device/browse.rs b/src/device/browse.rs index f4498964..a723488e 100644 --- a/src/device/browse.rs +++ b/src/device/browse.rs @@ -11,11 +11,17 @@ impl App { } } +impl + + Namespace + + Namespace> +> BrowseController for T {} + #[tek_proc::commands(BrowseCommand = "browse")] pub trait BrowseController: - for<'a> Namespace<'a, usize> + - for<'a> Namespace<'a, PathBuf> + - for<'a> Namespace<'a, Arc> + Namespace + + Namespace + + Namespace> { /// Toggle visibility of browser #[command(Show = "show")] diff --git a/src/device/clock.rs b/src/device/clock.rs index a8e78b56..e37779ea 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -66,16 +66,16 @@ pub trait HasClock: AsRef + AsMut { } impl Namespace<'a, u32> - + for<'a> Namespace<'a, f64> - + for<'a> Namespace<'a, Option> + + Namespace + + Namespace + + Namespace> > ClockController for T {} #[tek_proc::commands(ClockCommand = "clock")] pub trait ClockController: HasClock - + for<'a> Namespace<'a, u32> - + for<'a> Namespace<'a, f64> - + for<'a> Namespace<'a, Option> + + Namespace + + Namespace + + Namespace> { #[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps { @@ -468,3 +468,58 @@ impl_time_unit!(Ppq); impl_time_unit!(Pulse); impl_time_unit!(Bpm); impl_time_unit!(LaunchSync); + +/// ``` +/// let x = ""; +/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref()); +/// let _ = tek::view_transport(false, x.as_ref(), x.as_ref(), x.as_ref()); +/// ``` +pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw { + let theme = ItemTheme::G[96]; + bg(Black, east!(above( + button_play_pause(play, false).align_w(), + east!( + field_h(theme, "BPM", bpm), + field_h(theme, "Beat", beat), + field_h(theme, "Time", time), + ).align_e().full_wh() + ))) +} + +/// ``` +/// let x = ""; +/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref()); +/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref()); +/// ``` +pub fn view_status (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw { + let theme = ItemTheme::G[96]; + let sr = field_h(theme, "SR", sr); + let buf = field_h(theme, "Buf", buf); + let lat = field_h(theme, "Lat", lat); + bg(Black, east!(above( + sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(), + east!(sr, buf, lat).align_e().full_wh(), + ))) +} + +/// ``` +/// let _ = tek::button_play_pause(true, true); +/// let _ = tek::button_play_pause(true, false); +/// let _ = tek::button_play_pause(false, true); +/// let _ = tek::button_play_pause(false, false); +/// ``` +pub fn button_play_pause (playing: bool, compact: bool) -> impl Draw { + bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, + either(compact, + draw(move|to: &mut Tui|either(playing, + fg(Rgb(0, 255, 0), " PLAYING "), + fg(Rgb(255, 128, 0), " STOPPED "), + ).exact_w(9).draw(to)), + draw(move|to: &mut Tui|either(playing, + fg(Rgb(0, 255, 0), south(" ๐Ÿญ๐Ÿญ‘๐Ÿฌฝ ", " ๐Ÿญž๐Ÿญœ๐Ÿญ˜ ",)), + fg(Rgb(255, 128, 0), south(" โ–—โ–„โ–– ", " โ–โ–€โ–˜ ",)), + ).exact_w(5).draw(to)), + ) + ) +} + diff --git a/src/device/editor.rs b/src/device/editor.rs index 9cee7a03..c8dbf86a 100644 --- a/src/device/editor.rs +++ b/src/device/editor.rs @@ -67,22 +67,22 @@ pub trait HasEditor: AsRefOpt + AsMutOpt { impl+AsMutOpt> HasEditor for T {} impl Namespace<'a, u32> - + for<'a> Namespace<'a, f64> - + for<'a> Namespace<'a, bool> - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>>> + + Namespace + + Namespace + + Namespace + + Namespace + + Namespace> + + Namespace>>> > MidiEditController for T {} #[tek_proc::commands(MidiEditCommand = "edit")] pub trait MidiEditController: HasEditor - + for<'a> Namespace<'a, u32> - + for<'a> Namespace<'a, f64> - + for<'a> Namespace<'a, bool> - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option>>> + + Namespace + + Namespace + + Namespace + + Namespace + + Namespace> + + Namespace>>> { #[command(Show = "show")] fn show (&mut self, clip: Option>>) -> Perhaps { diff --git a/src/device/pool.rs b/src/device/pool.rs index 2ae3c95b..1c1a2057 100644 --- a/src/device/pool.rs +++ b/src/device/pool.rs @@ -70,28 +70,24 @@ pub trait HasPool: AsRef + AsMut { } impl Namespace<'a, bool> - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Arc> - + for<'a> Namespace<'a, PathBuf> - + for<'a> Namespace<'a, MidiClip> - + for<'a> Namespace<'a, ItemColor> - + for<'a> Namespace<'a, PoolCommand> - + for<'a> Namespace<'a, PoolCommand> - + for<'a> Namespace<'a, BrowseCommand> + + Namespace + + Namespace + + Namespace> + + Namespace + + Namespace + + Namespace + + Namespace > PoolController for T {} #[tek_proc::commands(PoolCommand = "pool")] pub trait PoolController: HasPool - + for<'a> Namespace<'a, bool> - + for<'a> Namespace<'a, usize> - + for<'a> Namespace<'a, Arc> - + for<'a> Namespace<'a, PathBuf> - + for<'a> Namespace<'a, MidiClip> - + for<'a> Namespace<'a, ItemColor> - + for<'a> Namespace<'a, PoolCommand> - + for<'a> Namespace<'a, PoolCommand> - + for<'a> Namespace<'a, BrowseCommand> + + Namespace + + Namespace + + Namespace> + + Namespace + + Namespace + + Namespace + + Namespace { #[command(Show = "show")] @@ -139,7 +135,7 @@ pub trait PoolController: HasPool for event in events.iter() { clip.notes[event.0 as usize].push(event.2); } - Ok(PoolCommand::Add { index, clip }.act(self)?) + Ok(PoolCommand::Add { index, clip }.dispatch(self)?) } ///// Export to file diff --git a/src/device/sampler.rs b/src/device/sampler.rs index b2808ac7..f9203341 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -21,7 +21,7 @@ impl + AsMut> HasSampler for T {} #[tek_proc::commands(SamplerCommand = "sampler")] pub trait SamplerController: HasSampler - + for<'a> Namespace<'a, usize> + + Namespace { #[command(RecordToggle = "rec-toggle")] @@ -30,10 +30,10 @@ pub trait SamplerController: HasSampler { let sampler = self.sampler_mut(); let recording = sampler.recording.as_ref().map(|x|x.0); - let _ = SamplerCommand::RecordFinish.act(self)?; + let _ = SamplerCommand::RecordFinish.dispatch(self)?; // autoslice: continue recording at next slot if recording != Some(slot) { - SamplerCommand::RecordBegin { slot }.act(self) + SamplerCommand::RecordBegin { slot }.dispatch(self) } else { Ok(None) } diff --git a/src/device/sequence.rs b/src/device/sequence.rs index cb60604f..bcc40d42 100644 --- a/src/device/sequence.rs +++ b/src/device/sequence.rs @@ -245,15 +245,21 @@ pub trait HasMidiClip { fn clip (&self) -> Option>>; } +impl HasMidiClip for App { + fn clip (&self) -> Option>> { + None + } +} + impl Namespace<'a, Option> - + for<'a> Namespace<'a, Option> + + Namespace> + + Namespace> > MidiClipController for T {} #[tek_proc::commands(MidiClipCommand)] pub trait MidiClipController: HasMidiClip - + for<'a> Namespace<'a, Option> - + for<'a> Namespace<'a, Option> + + Namespace> + + Namespace> { #[command(SetColor = "clip/color")] diff --git a/src/tek.edn b/src/tek.edn index 198f95b9..93e217ba 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -18,10 +18,13 @@ (align/s (bsp/e :ports/out (bsp/e :transport :ports/in))) (align/c (bsp/s (align/x (bg (g 36) :logo)) (bg (g 24) :dialog/menu)))))) +(keys :back (@escape (back))) +(keys :confirm (@enter (dialog confirm))) +(keys :axis/y (@up (axis (dec :y))) + (@down (axis (inc :y)))) + (mode :arranger (name Arranger) (info Launch grid.) - (keys (see :clock :color :launch :scenes :tracks :global) - (@tab project/edit) (@shift/I project/input/add) (@shift/O project/output/add) - (@shift/D dialog/show :dialog/device)) + (keys :clock :color :launch :scenes :tracks :global) (mode :editor (keys :editor)) (mode :dialog (keys :dialog)) (mode :message (keys :message)) @@ -46,6 +49,27 @@ (bg (g 80) (bsp/s :scenes/names :editor)) (bg (g 90) :scenes)))))))))))))) +(keys :clock (@space clock/toggle 0) + (@shift/space clock/toggle 0)) + +(keys :color (@c color)) + +(keys :launch (@q launch)) + +(keys :scenes (@s (select :select/scene)) + (@shift/S (scenes add)) + (@up (select :select/scene/dec)) + (@down (select :select/scene/inc))) + +(keys :tracks (@t (select :select/track)) + (@shift/T (tracks add)) + (@left (select :select/track/dec)) + (@right (select :select/track/inc))) + +(keys :global (see :history :saveload) + (@f8 dialog :options) + (@f10 dialog :quit)) + (view :ports/out (bsp/s (align/w (text L-AUDIO-OUT)) (bsp/e (text MIDI-OUT) @@ -86,18 +110,24 @@ (view :groove/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor))) -(keys :back (@escape back)) -(keys :confirm (@enter confirm)) -(keys :axis/x (@left app/dec :x) (@right app/inc :x)) -(keys :axis/x2 (@shift/left app/dec :x2) (@shift/right app/inc :x2)) -(keys :axis/y (@up app/dec :y) (@down app/inc :y)) -(keys :axis/y2 (@shift/up app/dec :y2) (@shift/down app/inc :y2)) -(keys :axis/z (@minus app/dec :z) (@equal app/inc :z)) -(keys :axis/z2 (@underscore app/dec :z2) (@plus app/inc :z2)) -(keys :axis/i (@comma app/dec :i) (@period app/inc :z)) -(keys :axis/i2 (@lt app/dec :i2) (@gt app/inc :z2)) -(keys :axis/w (@openbracket app/dec :w) (@closebracket app/inc :w)) -(keys :axis/w2 (@openbrace app/dec :w2) (@closebrace app/inc :w2)) +(keys :axis/x (@left dec :x) + (@right inc :x)) +(keys :axis/x2 (@shift/left dec :x2) + (@shift/right inc :x2)) +(keys :axis/y2 (@shift/up dec :y2) + (@shift/down inc :y2)) +(keys :axis/z (@minus dec :z) + (@equal inc :z)) +(keys :axis/z2 (@underscore dec :z2) + (@plus inc :z2)) +(keys :axis/i (@comma dec :i) + (@period inc :z)) +(keys :axis/i2 (@lt dec :i2) + (@gt inc :z2)) +(keys :axis/w (@openbracket dec :w) + (@closebracket inc :w)) +(keys :axis/w2 (@openbrace dec :w2) + (@closebrace inc :w2)) (keys :focus) (keys :editor (see :axis/i :axis/i2 :axis/y :page :editor/view :editor/add :editor/del)) @@ -117,19 +147,11 @@ (@down sampler/select :sample/below) (@left sampler/select :sample/to/left) (@right sampler/select :sample/to/right)) -(keys :tracks (@t select :select/track) - (@shift/T project/track/add) - (@left select :select/track/dec) - (@right select :select/track/inc)) (keys :track (see :color :launch :axis/z :axis/z2 :delete) (@r toggle :rec) (@m toggle :mon) (@p toggle :play) (@P toggle :solo)) -(keys :scenes (@s select :select/scene) - (@shift/S project/scene/add) - (@up select :select/scene/dec) - (@down select :select/scene/inc)) (keys :scene (see :color :launch :axis/z :axis/z2 :delete)) (keys :help (@f1 dialog :help)) (keys :page (@pgup page/up) @@ -144,13 +166,6 @@ (@r redo 1)) (keys :saveload (@f6 dialog :save) (@f9 dialog :load)) -(keys :color (@c color)) -(keys :launch (@q launch)) -(keys :clock (@space clock/toggle 0) - (@shift/space clock/toggle 0)) -(keys :global (see :history :saveload) - (@f8 dialog :options) - (@f10 dialog :quit)) (keys :clip (see :color :launch :axis/z :axis/z2 :delete) (@l toggle :loop)) (keys :sequencer (see :color :launch) diff --git a/src/tek.rs b/src/tek.rs index fd2fb982..d969ab6a 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -620,7 +620,7 @@ mod app { /// ``` /// use tek::{HasTracks, HasScenes, TracksView, ScenesView}; /// let mut app = tek::App::default(); - /// let _ = app.scene_add(None, None).unwrap(); + /// let _ = app.scenes_add_one(None, None).unwrap(); /// let _ = app.update_clock(); /// app.project.editor = Some(Default::default()); /// //let _: Vec<_> = app.project.inputs_with_sizes().collect(); @@ -639,21 +639,30 @@ mod app { /// let _ = app.project.h_scenes(); /// ``` #[derive(Default, Debug)] - #[namespace(u8)] - #[namespace(isize)] - #[namespace(ItemTheme)] + #[namespace(Arc<[Connect]> App::get_arc_array_connect)] #[namespace(Arc App::get_arc_str)] - #[namespace(u16 App::get_u16)] - #[namespace(usize App::get_usize)] - #[namespace(bool App::get_bool)] - #[namespace(Selection App::get_selection)] #[namespace(Color App::get_color)] - #[namespace(Option App::get_opt_u7)] - #[namespace(Option App::get_opt_u16)] - #[namespace(Option App::get_opt_usize)] - #[namespace(Option>> App::get_clip)] - #[namespace(Dialog App::get_dialog)] #[namespace(ControlAxis App::get_axis)] + #[namespace(Dialog App::get_dialog)] + #[namespace(MidiClip)] + #[namespace(ItemColor)] + #[namespace(ItemTheme)] + #[namespace(Option>> App::get_clip)] + #[namespace(Option> App::get_opt_arc_array_connect)] + #[namespace(Option> App::get_opt_arc_str)] + #[namespace(Option App::get_opt_itemtheme)] + #[namespace(Option>>>> App::get_opt_vec_opt_arc_rwlock_midiclip)] + #[namespace(Option App::get_opt_u16)] + #[namespace(Option App::get_opt_u7)] + #[namespace(Option App::get_opt_usize)] + #[namespace(Option App::get_opt_bool)] + #[namespace(Selection App::get_selection)] + #[namespace(bool App::get_bool)] + #[namespace(u16 App::get_u16)] + #[namespace(PathBuf App::get_path_buf)] + #[namespace(isize)] + #[namespace(u8)] + #[namespace(usize App::get_usize)] pub struct App { /// Exit flag pub exit: Exit, @@ -709,6 +718,10 @@ mod app { } } + fn get_path_buf (&self, src: impl Language) -> Perhaps { + todo!() + } + fn get_arc_str (&self, src: impl Language) -> Perhaps> { Ok(src.src()?.map(|x|x.into())) } @@ -827,12 +840,34 @@ mod app { fn get_axis (&self, src: impl Language) -> Perhaps { Ok(src.word()?.map(|word|Ok(match word { - "x" => ControlAxis::X, - "y" => ControlAxis::Y, + ":x" => ControlAxis::X, + ":y" => ControlAxis::Y, _ => return Err(format!("unknown axis {word}")) })).transpose()?) } + fn get_opt_itemtheme (&self, src: impl Language) -> Perhaps> { + todo!() + } + fn get_opt_vec_opt_arc_rwlock_midiclip (&self, src: impl Language) -> Perhaps>>>>> { + todo!() + } + fn get_opt_arc_array_connect (&self, src: impl Language) -> Perhaps>> { + todo!() + } + + fn get_arc_array_connect (&self, src: impl Language) -> Perhaps> { + todo!() + } + + fn get_opt_arc_str (&self, src: impl Language) -> Perhaps>> { + todo!() + } + + fn get_opt_bool (&self, src: impl Language) -> Perhaps> { + todo!() + } + } } @@ -879,56 +914,171 @@ mod bind { /// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); /// ``` #[derive(Clone)] - pub struct Condition( - pub Arcbool + Send + Sync>> - ); + pub struct Condition(pub Arcbool + Send + Sync>>); tui_keys!(self: App, input { - let commands = collect_commands(self, input)?; - let results = execute_commands(self, commands)?; - self.history.extend(results.into_iter()); - Ok(()) - }); - - fn collect_commands (app: &App, input: &TuiEvent) - -> Usually> - { - let mut commands = vec![]; - app.mode.as_ref().and_then(|m|app.config.get_mode(m)).map(|mode|{ + let name = self.mode.as_ref(); + let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone); + if let Some(mode) = mode { + let binds = self.config.binds.clone(); for id in mode.keys.iter() { - if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) + if let Some(event_map) = binds.read().unwrap().get(id.as_ref()) && let Some(bindings) = event_map.query(input) { for binding in bindings { for command in binding.commands.iter() { - if let Some(command) = app.namespace(command)? as Option { - commands.push(command) + if Self::command::(self, command)? { + break } } } } } - Ok::<_, Box>(()) - }).transpose()?; - Ok(commands) + } + Ok(()) + }); + + impl App { + fn command < + T: Dispatch + Clone + Into + > (state: &mut Self, src: &str) -> Usually where + Self: Namespace, + { + if let Some(command) = Namespace::::namespace(state, src)? { + match command.clone().dispatch(state) { + Err(err) => { + state.history.push((command.into(), None)); + return Err(err) + }, + Ok(undo) => { + state.history.push((command.into(), undo.map(Into::into))); + return Ok(true) + } + }; + } + Ok(false) + } } - fn execute_commands (app: &mut App, commands: Vec) - -> Usually)>> - { - let mut history = vec![]; - for command in commands.into_iter() { - let result = command.clone().act(app); - match result { - Err(err) => { - history.push((command, None)); - return Err(err) - }, - Ok(undo) => { - history.push((command, undo)); - } - }; + //#[derive(Clone, Debug)] + //pub enum AppCommand { + //Axis(AxisCommand), + //Browse(BrowseCommand), + //Dialog(DialogCommand), + //MidiClip(MidiClipCommand), + //Pool(PoolCommand), + //Scene(SceneCommand), + //Scenes(ScenesCommand), + //Track(TrackCommand), + //Tracks(TracksCommand), + //} + + impl_from!(AppCommand: |x: AxisCommand| AppCommand::Axis { command: x }); + impl_from!(AppCommand: |x: BrowseCommand| AppCommand::Browse { command: x }); + impl_from!(AppCommand: |x: DialogCommand| AppCommand::Dialog { command: x }); + impl_from!(AppCommand: |x: MidiClipCommand| AppCommand::MidiClip { command: x }); + impl_from!(AppCommand: |x: PoolCommand| AppCommand::Pool { command: x }); + impl_from!(AppCommand: |x: SceneCommand| AppCommand::Scene { command: x }); + impl_from!(AppCommand: |x: ScenesCommand| AppCommand::Scenes { command: x }); + impl_from!(AppCommand: |x: TrackCommand| AppCommand::Track { command: x }); + impl_from!(AppCommand: |x: TracksCommand| AppCommand::Tracks { command: x }); + #[tek_proc::commands(AppCommand)] + impl App { + #[command(Axis = "axis")] + pub fn command_axis (&mut self, command: AxisCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Browse = "browse")] + pub fn command_browse (&mut self, command: BrowseCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Dialog = "dialog")] + pub fn command_dialog (&mut self, command: DialogCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(MidiClip = "clip")] + pub fn command_midi_clip (&mut self, command: MidiClipCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Pool = "pool")] + pub fn command_pool (&mut self, command: PoolCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Scene = "scene")] + pub fn command_scene (&mut self, command: SceneCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Scenes = "scenes")] + pub fn command_scenes (&mut self, command: ScenesCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Track = "track")] + pub fn command_track (&mut self, command: TrackCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Tracks = "tracks")] + pub fn command_tracks (&mut self, command: TracksCommand) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + } + + #[tek_proc::commands(DialogCommand)] + impl App { + /// Cancel current dialog + #[command(Cancel = "cancel")] + pub fn cancel (&mut self) -> Perhaps { + todo!() + } + /// Confirm current dialog selection. + #[command(Confirm = "confirm")] + pub fn confirm (&mut self) -> Perhaps { + Ok(match &self.dialog { + Dialog::Menu(index, items) => { + let callback = items.0[*index].1.clone(); + callback(self)?; + None + }, + _ => todo!(), + }) + } + /// Swap currently active modal dialog. + /// + /// ``` + /// let _ = tek::App::default().set_dialog(tek::Dialog::welcome()); + /// ``` + #[command(SetDialog = "seg")] + pub fn set_dialog (&mut self, dialog: Dialog) -> Perhaps { + let mut dialog = dialog.clone(); + std::mem::swap(&mut self.dialog, &mut dialog); + Ok(Some(DialogCommand::SetDialog { dialog })) + } + } + + #[tek_proc::commands(AxisCommand)] + impl App { + /// Increment a given data axis. + #[command(Inc = "inc")] + pub fn inc (&mut self, axis: ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => todo!(), + (Dialog::Menu(_, _), ControlAxis::Y) => { + DialogCommand::SetDialog { dialog: self.dialog.menu_next() }.dispatch(self)?; + None + }, + _ => todo!() + }) + } + /// Decrement a given data axis. + #[command(Dec = "dec")] + pub fn dec (&mut self, axis: ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => None, + (Dialog::Menu(_, _), ControlAxis::Y) => { + DialogCommand::SetDialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; + None + }, + _ => todo!() + }) } - Ok(history) } pub(crate) fn load_bind (binds: &Binds, name: &impl AsRef, body: &impl Language) -> Usually<()> { @@ -1012,75 +1162,6 @@ mod bind { impl_debug!(Condition |self, w| { write!(w, "*") }); - #[tek_proc::commands(AppCommand = "app")] - impl App { - - #[command(Nop = "nop")] - pub fn nop (&mut self) -> Perhaps { - Ok(None) - } - - #[command(Cancel = "cancel")] - pub fn cancel (&mut self) -> Perhaps { - todo!() - } - - #[command(Inc = "inc")] - pub fn inc (&mut self, axis: ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => todo!(), - (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?, - _ => todo!() - }) - } - - #[command(Dec = "dec")] - pub fn dec (&mut self, axis: ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => None, - (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?, - _ => todo!() - }) - } - - #[command(Confirm = "confirm")] - pub fn confirm (&mut self) -> Perhaps { - Ok(match &self.dialog { - Dialog::Menu(index, items) => { - let callback = items.0[*index].1.clone(); - callback(self)?; - None - }, - _ => todo!(), - }) - } - - /// Swap currently active modal dialog. - /// - /// ``` - /// let _ = tek::App::default().set_dialog(tek::Dialog::welcome()); - /// ``` - #[command(SetDialog = "dialog")] - pub fn set_dialog (&mut self, dialog: Dialog) -> Perhaps { - let mut dialog = dialog.clone(); - std::mem::swap(&mut self.dialog, &mut dialog); - Ok(Some(AppCommand::SetDialog { dialog })) - } - } - - //impl<'a> Namespace<'a, AppCommand> for App { - //symbols!('a |app| -> AppCommand { - //"x/inc" => AppCommand::Inc { axis: ControlAxis::X }, - //"x/dec" => AppCommand::Dec { axis: ControlAxis::X }, - //"y/inc" => AppCommand::Inc { axis: ControlAxis::Y }, - //"y/dec" => AppCommand::Dec { axis: ControlAxis::Y }, - //"confirm" => AppCommand::Confirm, - //"cancel" => AppCommand::Cancel, - //}); - //} - /// A control axis. /// /// ``` @@ -1293,6 +1374,18 @@ mod device { } } + pub fn view_device (state: &App) -> impl Draw { + let selected = state.dialog.device_kind().unwrap(); + south( + bold(true, "Add device"), + iter_south(move||device_kinds().iter().enumerate().map(move|(i, _label)|{ + let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; + let l = if i == selected { "[ " } else { " " }; + let r = if i == selected { " ]" } else { " " }; + bg(b, east(l, west(r, "FIXME device name"))).full_w() + }))) + } + pub mod arrange; pub use self::arrange::*; pub mod browse; pub use self::browse::*; pub mod pool; pub use self::pool::*; @@ -1346,8 +1439,10 @@ mod draw { impl Keywords> for App { fn keywords () -> impl Iterator Perhaps>> { - [kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_full, - kw_tui_text, kw_tui_fg, kw_tui_bg] + [ + kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_full, + kw_tui_text, kw_tui_fg, kw_tui_bg + ] .into_iter() } } @@ -1521,73 +1616,6 @@ mod draw { Ok(Some(to.area().into())) }).min_w(w).exact_h(h) } - - pub fn view_device (state: &App) -> impl Draw { - let selected = state.dialog.device_kind().unwrap(); - south( - bold(true, "Add device"), - iter_south(move||device_kinds().iter().enumerate().map(move|(i, _label)|{ - let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; - let l = if i == selected { "[ " } else { " " }; - let r = if i == selected { " ]" } else { " " }; - bg(b, east(l, west(r, "FIXME device name"))).full_w() - }))) - } - - /// ``` - /// let x = ""; - /// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref()); - /// let _ = tek::view_transport(false, x.as_ref(), x.as_ref(), x.as_ref()); - /// ``` - pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw { - let theme = ItemTheme::G[96]; - bg(Black, east!(above( - button_play_pause(play, false).align_w(), - east!( - field_h(theme, "BPM", bpm), - field_h(theme, "Beat", beat), - field_h(theme, "Time", time), - ).align_e().full_wh() - ))) - } - - /// ``` - /// let x = ""; - /// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref()); - /// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref()); - /// ``` - pub fn view_status (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw { - let theme = ItemTheme::G[96]; - let sr = field_h(theme, "SR", sr); - let buf = field_h(theme, "Buf", buf); - let lat = field_h(theme, "Lat", lat); - bg(Black, east!(above( - sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(), - east!(sr, buf, lat).align_e().full_wh(), - ))) - } - - /// ``` - /// let _ = tek::button_play_pause(true, true); - /// let _ = tek::button_play_pause(true, false); - /// let _ = tek::button_play_pause(false, true); - /// let _ = tek::button_play_pause(false, false); - /// ``` - pub fn button_play_pause (playing: bool, compact: bool) -> impl Draw { - bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, - either(compact, - draw(move|to: &mut Tui|either(playing, - fg(Rgb(0, 255, 0), " PLAYING "), - fg(Rgb(255, 128, 0), " STOPPED "), - ).exact_w(9).draw(to)), - draw(move|to: &mut Tui|either(playing, - fg(Rgb(0, 255, 0), south(" ๐Ÿญ๐Ÿญ‘๐Ÿฌฝ ", " ๐Ÿญž๐Ÿญœ๐Ÿญ˜ ",)), - fg(Rgb(255, 128, 0), south(" โ–—โ–„โ–– ", " โ–โ–€โ–˜ ",)), - ).exact_w(5).draw(to)), - ) - ) - } - /// ``` /// let bg = tengri::ratatui::style::Color::Red; /// let fg = tengri::ratatui::style::Color::Green; diff --git a/tengri b/tengri index b7f4d55e..1f541407 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit b7f4d55e1d67d3481ecee14f693ca3e0a9426a6c +Subproject commit 1f541407597c7866cf1450d03967ab4711d28d29 From da2099c52ede8029b78964fbe74cc294aabcd8ba Mon Sep 17 00:00:00 2001 From: i do not exist Date: Wed, 12 Aug 2026 18:59:19 +0300 Subject: [PATCH 2/3] fix warnings, cleanup --- proc/src/lib.rs | 12 +- src/deps.rs | 39 +++ src/device/arrange/scene.rs | 5 +- src/device/arrange/track.rs | 10 +- src/device/browse.rs | 75 +++-- src/device/clock.rs | 12 - src/device/editor.rs | 1 - src/device/pool.rs | 84 +++--- src/device/sequence.rs | 12 +- src/tek.rs | 579 ++++++++++++++++-------------------- 10 files changed, 379 insertions(+), 450 deletions(-) create mode 100644 src/deps.rs diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 691fd5b3..bb971c63 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -353,7 +353,7 @@ attribute!(command { let mut variants_filtered = item.variants.clone(); variants_filtered.clear(); for variant in item.variants.iter_mut() { - let Variant { attrs, ident, fields, discriminant } = variant; + let Variant { attrs, ident, fields, discriminant: _ } = variant; let mut attrs_filtered = attrs.clone(); attrs_filtered.clear(); for attr in attrs.iter() { @@ -396,7 +396,7 @@ attribute!(command { panic!() }; match fields { - Fields::Named(fields) => todo!("named command fields"), + Fields::Named(_fields) => todo!("named command fields"), Fields::Unnamed(fields) => { let mut params = quote! {}; let mut values = quote! {}; @@ -455,9 +455,11 @@ attribute!(keyword { impl Parse for CustomAttributeItem { fn parse (input: ParseStream) -> Result { let mut item: ItemEnum = input.parse()?; - for Variant { attrs, ident, fields, discriminant } in item.variants.iter_mut() { + for Variant { + attrs, ident: _, fields: _, discriminant: _ + } in item.variants.iter_mut() { attrs.retain(|attr|if let syn::Meta::List(MetaList { - ref path, ref tokens, .. + ref path, tokens: ref _tokens, .. }) = attr.meta && path == &Path::from(Ident::new( "keyword", Span::call_site() )) { @@ -474,7 +476,7 @@ attribute!(keyword { fn to_tokens (&self, out: &mut TokenStream2) { let Self( CustomAttributeMeta(state), - CustomAttributeItem(item, variants) + CustomAttributeItem(item, _variants) ) = self; let ident = &item.ident; let body = quote! {}; diff --git a/src/deps.rs b/src/deps.rs new file mode 100644 index 00000000..540a4f68 --- /dev/null +++ b/src/deps.rs @@ -0,0 +1,39 @@ +pub extern crate atomic_float; +pub extern crate xdg; +pub extern crate tengri; +#[cfg(feature = "cli")] +pub(crate) use ::clap::{self, Parser, Subcommand}; +#[allow(unused)] +pub(crate) use ::{ + std::{ + cmp::Ord, + collections::BTreeMap, + error::Error, + ffi::OsString, + fmt::{Write, Debug, Formatter}, + fs::File, + ops::{Add, Sub, Mul, Div, Rem}, + path::{Path, PathBuf}, + sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}}, + time::Duration, + thread::{spawn, JoinHandle}, + }, + xdg::{ + BaseDirectories, + }, + tengri::{ + *, + dizzle::*, + midly::{ + Smf, TrackEventKind, MidiMessage, Error as MidiError, + num::*, + live::*, + }, + crossterm::event::{Event, KeyEvent}, + ratatui::{ + self, + prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, + widgets::{Widget, canvas::{Canvas, Line}}, + }, + }, +}; diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index ce45629e..3226a6f6 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -1,4 +1,3 @@ -use crate::*; use super::*; /// A scene consists of a set of clips to play together. @@ -85,13 +84,13 @@ pub trait SceneController: HasScene + Namespace { #[command(SetSize = "scene/size")] - fn scene_set_size (&mut self, size: usize) -> Perhaps + fn scene_set_size (&mut self, _size: usize) -> Perhaps where Self: Namespace { todo!() } #[command(SetZoom = "scene/zoom")] - fn scene_set_zoom (&mut self, size: usize) -> Perhaps + fn scene_set_zoom (&mut self, _size: usize) -> Perhaps where Self: Namespace { todo!() diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs index 78c27a72..4dbdd540 100644 --- a/src/device/arrange/track.rs +++ b/src/device/arrange/track.rs @@ -104,19 +104,19 @@ pub trait TrackController: HasTrack Ok(None) } #[command(SetMute = "track/mute")] - fn track_set_mute (&mut self, mute: Option) -> Perhaps { + fn track_set_mute (&mut self, _mute: Option) -> Perhaps { todo!() } #[command(SetSolo = "track/solo")] - fn track_set_solo (&mut self, solo: Option) -> Perhaps { + fn track_set_solo (&mut self, _solo: Option) -> Perhaps { todo!() } #[command(SetSize = "track/size")] - fn track_set_size (&mut self, size: usize) -> Perhaps { + fn track_set_size (&mut self, _size: usize) -> Perhaps { todo!() } #[command(SetZoom = "track/zoom")] - fn track_set_zoom (&mut self, zoom: usize) -> Perhaps { + fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps { todo!() } #[command(SetName = "track/name")] @@ -313,7 +313,7 @@ pub trait TracksController: HasTracks fn tracks_add (&mut self) -> Perhaps where Self: HasScenes + HasJack<'static> { - let (index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; + let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; Ok(None) } } diff --git a/src/device/browse.rs b/src/device/browse.rs index a723488e..be3bb3ec 100644 --- a/src/device/browse.rs +++ b/src/device/browse.rs @@ -60,13 +60,13 @@ pub trait BrowseController: pub size: Sizer, } -pub(crate) struct EntriesIterator<'a, S: Screen> { - pub browser: &'a Browse, - pub offset: usize, - pub length: usize, - pub index: usize, - _screen: std::marker::PhantomData -} +//pub(crate) struct EntriesIterator<'a, S: Screen> { + //pub browser: &'a Browse, + //pub offset: usize, + //pub length: usize, + //pub index: usize, + //_screen: std::marker::PhantomData +//} #[derive(Clone, Debug)] pub enum BrowseTarget { SaveProject, @@ -118,40 +118,37 @@ impl Browse { unreachable!() }) } - fn _todo_stub_path_buf (&self) -> PathBuf { todo!() } - fn _todo_stub_usize (&self) -> usize { todo!() } - fn _todo_stub_arc_str (&self) -> Arc { todo!() } - fn tui (&self) -> impl Draw { - iter_south_fixed(1, ||self.tui_entries(), |entry, _index|entry.origin_w().full_w()) - } - fn tui_entries (&self) -> EntriesIterator<'_, Tui> { - EntriesIterator { - offset: 0, - index: 0, - length: self.dirs.len() + self.files.len(), - browser: self, - _screen: Default::default(), - } - } + //fn tui (&self) -> impl Draw { + //iter_south_fixed(1, ||self.tui_entries(), |entry, _index|entry.origin_w().full_w()) + //} + //fn tui_entries (&self) -> EntriesIterator<'_, Tui> { + //EntriesIterator { + //offset: 0, + //index: 0, + //length: self.dirs.len() + self.files.len(), + //browser: self, + //_screen: Default::default(), + //} + //} } -impl<'a> Iterator for EntriesIterator<'a, Tui> { - type Item = impl Draw; - fn next (&mut self) -> Option { - let dirs = self.browser.dirs.len(); - let files = self.browser.files.len(); - let index = self.index; - if self.index < dirs { - self.index += 1; - Some(bold(true, self.browser.dirs[index].1.as_str())) - } else if self.index < dirs + files { - self.index += 1; - Some(bold(false, self.browser.files[index - dirs].1.as_str())) - } else { - None - } - } -} +//impl<'a> Iterator for EntriesIterator<'a, Tui> { + //type Item = impl Draw; + //fn next (&mut self) -> Option { + //let dirs = self.browser.dirs.len(); + //let files = self.browser.files.len(); + //let index = self.index; + //if self.index < dirs { + //self.index += 1; + //Some(bold(true, self.browser.dirs[index].1.as_str())) + //} else if self.index < dirs + files { + //self.index += 1; + //Some(bold(false, self.browser.files[index - dirs].1.as_str())) + //} else { + //None + //} + //} +//} impl PartialEq for BrowseTarget { fn eq (&self, other: &Self) -> bool { diff --git a/src/device/clock.rs b/src/device/clock.rs index e37779ea..fd7f811b 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -416,18 +416,6 @@ impl Clock { } } -impl Clock { - fn _todo_provide_u32 (&self) -> u32 { - todo!() - } - fn _todo_provide_opt_u32 (&self) -> Option { - todo!() - } - fn _todo_provide_f64 (&self) -> f64 { - todo!() - } -} - impl_has!(Clock: |self: Track|self.sequencer.clock); impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ)); diff --git a/src/device/editor.rs b/src/device/editor.rs index c8dbf86a..632e94fb 100644 --- a/src/device/editor.rs +++ b/src/device/editor.rs @@ -236,7 +236,6 @@ impl MidiEditor { self.mode.redraw(); } } - fn _todo_opt_clip_stub (&self) -> Option>> { todo!() } fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) } fn note_length (&self) -> usize { self.get_note_len() } fn note_pos (&self) -> usize { self.get_note_pos() } diff --git a/src/device/pool.rs b/src/device/pool.rs index 1c1a2057..93e18234 100644 --- a/src/device/pool.rs +++ b/src/device/pool.rs @@ -222,7 +222,7 @@ pub trait PoolController: HasPool } #[command(CropSet = "crop/set")] - fn crop_set (&mut self, length: usize) -> Perhaps { + fn crop_set (&mut self, _length: usize) -> Perhaps { if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) = self.pool_mut().mode_mut().clone() { @@ -233,7 +233,7 @@ pub trait PoolController: HasPool clip.write().unwrap().length = *length; } *self.pool_mut().mode_mut() = None; - return Ok(old_length.map(|length|PoolCommand::CropSet { length })) + return Ok(old_length.map(|l|PoolCommand::CropSet { _length: l })) } Ok(None) } @@ -429,12 +429,6 @@ impl ClipLength { } impl Pool { - fn _todo_usize_ (&self) -> usize { todo!() } - fn _todo_bool_ (&self) -> bool { todo!() } - fn _todo_clip_ (&self) -> MidiClip { todo!() } - fn _todo_path_ (&self) -> PathBuf { todo!() } - fn _todo_color_ (&self) -> ItemColor { todo!() } - fn _todo_str_ (&self) -> Arc { todo!() } fn _clip_new (&self) -> MidiClip { self.new_clip() } fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() } fn _clip_index_current (&self) -> usize { 0 } @@ -445,44 +439,44 @@ impl Pool { } impl<'a> PoolView<'a> { - fn tui (&self) -> impl Draw { - let Self(pool) = self; - //let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||g(32).into()); - //let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x)); - //let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x); - //let height = pool.clips.read().unwrap().len() as u16; - iter( - ||pool.clips().clone().into_iter(), - move|clip: Arc>, i: usize|{ - let MidiClip { ref name, color, length, .. } = *clip.read().unwrap(); - let item_height = 1; - let _item_offset = i as u16 * item_height; - let selected = i == pool.clip_index(); - let b = if selected { color.light.term } else { color.base.term }; - let f = color.lightest.term; - let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; - let length = if false { String::default() } else { format!("{length} ") }; - bg(b, below!( - fg(f, bold(selected, name)).origin_w().full_w(), - fg(f, bold(selected, length)).origin_e().full_w(), - when(selected, bold(true, fg(g(255), "โ–ถ"))).origin_w().full_w(), - when(selected, bold(true, fg(g(255), "โ—€"))).origin_e().full_w(), - )).exact_h(1) - }).origin_n().full_h().exact_w(20) - } + //fn tui (&self) -> impl Draw { + //let Self(pool) = self; + ////let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||g(32).into()); + ////let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x)); + ////let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x); + ////let height = pool.clips.read().unwrap().len() as u16; + //iter( + //||pool.clips().clone().into_iter(), + //move|clip: Arc>, i: usize|{ + //let MidiClip { ref name, color, length, .. } = *clip.read().unwrap(); + //let item_height = 1; + //let _item_offset = i as u16 * item_height; + //let selected = i == pool.clip_index(); + //let b = if selected { color.light.term } else { color.base.term }; + //let f = color.lightest.term; + //let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; + //let length = if false { String::default() } else { format!("{length} ") }; + //bg(b, below!( + //fg(f, bold(selected, name)).origin_w().full_w(), + //fg(f, bold(selected, length)).origin_e().full_w(), + //when(selected, bold(true, fg(g(255), "โ–ถ"))).origin_w().full_w(), + //when(selected, bold(true, fg(g(255), "โ—€"))).origin_e().full_w(), + //)).exact_h(1) + //}).origin_n().full_h().exact_w(20) + //} } impl ClipLength { - fn tui (&self) -> impl Draw { - use ClipLengthFocus::*; - let bars = format!("{}", self.bars()); - let beats = format!("{}", self.beats()); - let ticks = format!("{:>02}", self.ticks()); - match self.focus { - None => east!(" ", bars, ".", beats, ".", ticks), - Some(Bar) => east!("[", bars, "]", beats, ".", ticks), - Some(Beat) => east!(" ", bars, "[", beats, "]", ticks), - Some(Tick) => east!(" ", bars, ".", beats, "[", ticks), - } - } + //fn tui (&self) -> impl Draw { + //use ClipLengthFocus::*; + //let bars = format!("{}", self.bars()); + //let beats = format!("{}", self.beats()); + //let ticks = format!("{:>02}", self.ticks()); + //match self.focus { + //None => east!(" ", bars, ".", beats, ".", ticks), + //Some(Bar) => east!("[", bars, "]", beats, ".", ticks), + //Some(Beat) => east!(" ", bars, "[", beats, "]", ticks), + //Some(Tick) => east!(" ", bars, ".", beats, "[", ticks), + //} + //} } diff --git a/src/device/sequence.rs b/src/device/sequence.rs index bcc40d42..1ccc1d9d 100644 --- a/src/device/sequence.rs +++ b/src/device/sequence.rs @@ -263,7 +263,7 @@ pub trait MidiClipController: HasMidiClip { #[command(SetColor = "clip/color")] - fn clip_set_color (&mut self, color: Option) -> Perhaps { + fn clip_set_color (&mut self, _color: Option) -> Perhaps { //(SetColor [t: usize, s: usize, c: ItemTheme] //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) @@ -271,7 +271,7 @@ pub trait MidiClipController: HasMidiClip } #[command(SetLoop = "clip/loop")] - fn clip_toggle_loop (&mut self, looping: Option) -> Perhaps { + fn clip_toggle_loop (&mut self, _looping: Option) -> Perhaps { //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) todo!() @@ -362,14 +362,6 @@ impl PartialEq for MidiClip { impl Eq for MidiClip {} -impl MidiClip { - fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } - fn _todo_bool_stub_ (&self) -> bool { todo!() } - fn _todo_usize_stub_ (&self) -> usize { todo!() } - fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } - fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } - fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } -} impl_has!(Sequencer: |self: Track| self.sequencer); impl_has!(Clock: |self: Sequencer| self.clock); impl_has!(Vec: |self: Sequencer| self.midi_ins); diff --git a/src/tek.rs b/src/tek.rs index d969ab6a..52563b1c 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -1,44 +1,6 @@ #![allow(clippy::unit_arg)] -#![feature(impl_trait_in_assoc_type)] // Used by EntriesIterator; TODO remove -pub extern crate atomic_float; -pub extern crate xdg; -pub extern crate tengri; -#[cfg(feature = "cli")] -pub(crate) use ::clap::{self, Parser, Subcommand}; -#[allow(unused)] -pub(crate) use ::{ - std::{ - cmp::Ord, - collections::BTreeMap, - error::Error, - ffi::OsString, - fmt::{Write, Debug, Formatter}, - fs::File, - ops::{Add, Sub, Mul, Div, Rem}, - path::{Path, PathBuf}, - sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}}, - time::Duration, - thread::{spawn, JoinHandle}, - }, - xdg::{ - BaseDirectories, - }, - tengri::{ - *, - dizzle::*, - midly::{ - Smf, TrackEventKind, MidiMessage, Error as MidiError, - num::*, - live::*, - }, - crossterm::event::{Event, KeyEvent}, - ratatui::{ - self, - prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, - widgets::{Widget, canvas::{Canvas, Line}}, - }, - }, -}; +//#![feature(impl_trait_in_assoc_type)] // Used by EntriesIterator; TODO remove +mod deps; pub use self::deps::*; /// Banner. pub(crate) const HEADER: &'static str = r#" @@ -88,6 +50,27 @@ fn run_new_plain (config: Config) -> Usually<()> { })?) } + //pub fn tui ( + //app: Arc>, + //jack: Jack, + //sync_lead: &bool, + //sync_follow: &bool, + //) -> Usually<()> { + //// Run the [Tui] and [Jack] threads with the [App] state. + //Tui::run_main(&jack.run(move|jack|{ + //// Between jack init and app's first cycle: + ////jack.sync_lead(*sync_lead, |mut state|{ + ////let clock = app.write().unwrap().clock(); + ////clock.playhead.update_from_sample(state.position.frame() as f64); + ////state.position.bbt = Some(clock.bbt()); + ////state.position + ////})?; + ////jack.sync_follow(*sync_follow)?; + //// FIXME: They don't work properly. + //Ok(app) + //})?)? + //} + #[cfg(feature = "cli")] pub mod cli { use crate::*; @@ -174,7 +157,7 @@ fn run_new_plain (config: Config) -> Usually<()> { #[arg(short='n', long)] name: Option, /// Whether to attempt to become transport master #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, - /// Whether to sync to external transport master + /// Whether to sync to external transport master #[arg(short='y', long, default_value_t = true)] sync_follow: bool, /// Initial tempo in beats per minute #[arg(short='b', long, default_value = None)] bpm: Option, @@ -238,14 +221,8 @@ fn run_new_plain (config: Config) -> Usually<()> { .chain( connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() )); - //&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr)?; proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?; proj.scenes_add_many(scenes.unwrap_or(0))?; - //if matches!(self, Action::Status) { - //// Show status and exit - //tek_print_status(&proj); - //return Ok(()) - //} Ok(proj) } } @@ -325,11 +302,14 @@ mod config { } impl Config { + /// Default configuration directory. const CONFIG_DIR: &'static str = "tek"; + /// Default configuration subdirectory. const CONFIG_SUB: &'static str = "v0"; + /// Default configuration file name. const CONFIG: &'static str = "tek.edn"; + /// Default configuration contents. const DEFAULTS: &'static str = include_str!("tek.edn"); - /// Create a new app configuration from a set of XDG base directories, pub fn new (dirs: Option) -> Self { Self { @@ -340,20 +320,20 @@ mod config { ..Default::default() } } - + /// Create, initialize, and watch a new configuration. pub fn watched (callback: impl FnOnce(Arc)->T) -> Usually { let config = Self::init_new(None)?; Self::watch(config.clone(), None)?; let result = callback(config); Ok(result) } - + /// Create and initialize a new configuration. pub fn init_new (_dirs: Option) -> Usually> { let config = Arc::new(Self::new(None)); config.init()?; Ok(config) } - + /// Watch a config's file for changes. pub fn watch (config: Arc, poll: Option) -> Usually<()> { let handler = { let config = config.clone(); @@ -383,19 +363,18 @@ mod config { Err(format!("no config path").into()) } } - + /// Find the config file. fn find_file (&self) -> Option { self.dirs.find_config_file(Self::CONFIG) } - + /// Place config file in default location. fn place_file (&self) -> Result { self.dirs.place_config_file(Self::CONFIG) } - + /// Get path to config file. fn get_file (&self) -> Option { self.dirs.get_config_file(Self::CONFIG) } - /// Write initial contents of configuration. pub fn init (&self) -> Usually<()> { //println!("\r\ninit {}", quanta::Clock::new().raw()); @@ -405,13 +384,9 @@ mod config { Ok(()) }) } - /// Write initial contents of a configuration file. - pub fn load ( - &self, - path: &str, - defaults: &str, - mut each: impl FnMut(&Self, &str)->Usually<()> + pub fn load Usually<()>> ( + &self, path: &str, defaults: &str, mut each: F ) -> Usually<()> { self.stamp.store(quanta::Clock::new().raw(), Relaxed); if self.find_file().is_none() { @@ -426,19 +401,18 @@ mod config { return Err(format!("{path}: not found").into()) }) } - /// Add statements to configuration from [Dsl] source. pub fn add (&self, dsl: impl Language) -> Usually<&Self> { dsl.each(|item|self.add_one(item))?; Ok(self) } - + /// Make this configuration empty. fn clear (&self) { *self.modes.0.write().unwrap() = Default::default(); *self.views.write().unwrap() = Default::default(); *self.binds.write().unwrap() = Default::default(); } - + /// Add one entry to the configuration. fn add_one (&self, item: impl Language) -> Usually<()> { if let Some(expr) = item.expr()? { let head = expr.head()?; @@ -458,74 +432,35 @@ mod config { return Err(format!("Config::load: expected expr, got: {item:?}").into()) } } - + /// Get a mode by name. pub fn get_mode (&self, mode: impl AsRef) -> Option>>> { self.modes.get(mode) } - + /// Print the configuration. pub fn print (&self) { - use ::ansi_term::Color::*; - println!("{:?}", self.dirs); - for (k, v) in self.views.read().unwrap().iter() { - println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); - } - for (k, v) in self.binds.read().unwrap().iter() { - println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); - for (k, v) in v.0.iter() { - print!("{} ", &Yellow.paint(match &k.0 { - Event::Key(KeyEvent { modifiers, .. }) => - format!("{:>16}", format!("{modifiers}")), - _ => unimplemented!() - })); - print!("{}", &Yellow.bold().paint(match &k.0 { - Event::Key(KeyEvent { code, .. }) => - format!("{:<10}", format!("{code}")), - _ => unimplemented!() - })); - for v in v.iter() { - print!(" => {:?}", v.commands); - print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); - println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); - //println!(" {:?}", v.source); - } - } - } - self.modes.for_each(|k, v|{ - println!(); - for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } - for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } - print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); - print!("\n{}", Blue.paint("KEYS")); - for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - v.modes.for_each(|k, v|{ - print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); - print!( " INFO={:?}", v.info); - print!( " VIEW={:?}", v.view); - println!(" KEYS={:?}", v.keys); - }); - print!("{}", Blue.paint("VIEW")); - for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - }); + print_config(self) } } impl Modes { + /// Register a mode. pub fn add (&self, name: &impl AsRef, body: &impl Language) -> Usually<()> { let mut mode = Mode::default(); body.each(|item|mode.add(item))?; self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); Ok(()) } + /// Get a mode by name. pub fn get (&self, name: impl AsRef) -> Option>>> { self.0.read().unwrap().get(name.as_ref()).cloned() } + /// Run something for each mode. pub fn for_each (&self, mut ator: impl FnMut(&str, &Mode>)->T) { for (k, v) in self.0.read().unwrap().iter() { let _ = ator(k.as_ref(), v.as_ref()); } } + /// Count modes. pub fn len (&self) -> usize { self.0.read().unwrap().len() } @@ -562,34 +497,24 @@ mod config { } else { return Err(format!("Mode::add: unexpected: {dsl:?}").into()); }) - - //DslParse(dsl, ||Err(format!("Mode::add: unexpected: {dsl:?}").into())) - //.word(|word|self.add_view(word)) - //.expr(|expr|expr.head(|head|{ - ////println!("Mode::add: {head} {:?}", expr.tail()); - //let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); - //match head { - //"name" => self.add_name(tail), - //"info" => self.add_info(tail), - //"keys" => self.add_keys(tail)?, - //"mode" => self.add_mode(tail)?, - //_ => self.add_view(tail), - //}; - //})) } - + /// Add a name to the mode. fn add_name (&mut self, dsl: impl Language) -> Perhaps<()> { Ok(dsl.src()?.map(|src|self.name.push(src.into()))) } + /// Add a description to the mode. fn add_info (&mut self, dsl: impl Language) -> Perhaps<()> { Ok(dsl.src()?.map(|src|self.info.push(src.into()))) } + /// Add a view definition to the mode. fn add_view (&mut self, dsl: impl Language) -> Perhaps<()> { Ok(dsl.src()?.map(|src|self.view.push(src.into()))) } + /// Add a keyboard input bindin to the mode. fn add_keys (&mut self, dsl: impl Language) -> Perhaps<()> { Ok(Some(dsl.each(|expr|{ self.keys.push(expr.trim().into()); Ok(()) })?)) } + /// Add a submode to the mode. fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> { Ok(Some(if let Some(id) = dsl.head()? { self.modes.add(&id, &dsl.tail())?; @@ -639,30 +564,27 @@ mod app { /// let _ = app.project.h_scenes(); /// ``` #[derive(Default, Debug)] - #[namespace(Arc<[Connect]> App::get_arc_array_connect)] - #[namespace(Arc App::get_arc_str)] - #[namespace(Color App::get_color)] - #[namespace(ControlAxis App::get_axis)] - #[namespace(Dialog App::get_dialog)] #[namespace(MidiClip)] #[namespace(ItemColor)] #[namespace(ItemTheme)] - #[namespace(Option>> App::get_clip)] - #[namespace(Option> App::get_opt_arc_array_connect)] - #[namespace(Option> App::get_opt_arc_str)] - #[namespace(Option App::get_opt_itemtheme)] - #[namespace(Option>>>> App::get_opt_vec_opt_arc_rwlock_midiclip)] - #[namespace(Option App::get_opt_u16)] - #[namespace(Option App::get_opt_u7)] - #[namespace(Option App::get_opt_usize)] - #[namespace(Option App::get_opt_bool)] - #[namespace(Selection App::get_selection)] - #[namespace(bool App::get_bool)] - #[namespace(u16 App::get_u16)] - #[namespace(PathBuf App::get_path_buf)] #[namespace(isize)] #[namespace(u8)] - #[namespace(usize App::get_usize)] + #[namespace(Arc<[Connect]> get_arc_array_connect)] + #[namespace(Arc get_arc_str)] + #[namespace(Color get_color)] + #[namespace(ControlAxis get_axis)] + #[namespace(Option> get_opt_arc_array_connect)] + #[namespace(Option> get_opt_arc_str)] + #[namespace(Option get_opt_itemtheme)] + #[namespace(Option>>>> get_opt_vec_opt_arc_rwlock_midiclip)] + #[namespace(Option get_opt_u16)] + #[namespace(Option get_opt_usize)] + #[namespace(Option get_opt_bool)] + #[namespace(bool get_bool)] + #[namespace(u16 get_u16)] + #[namespace(PathBuf get_path_buf)] + #[namespace(usize get_usize)] + #[namespace(Dialog App::get_dialog)] pub struct App { /// Exit flag pub exit: Exit, @@ -701,10 +623,7 @@ mod app { /// let tek = tek::App::new(None, proj, conf, "hello"); /// ``` pub fn new ( - exit: Option, - project: Arrangement, - config: Arc, - mode: impl AsRef + exit: Option, project: Arrangement, config: Arc, mode: impl AsRef ) -> Self { App { exit: exit.unwrap_or_default(), @@ -717,157 +636,144 @@ mod app { ..Default::default() } } + } - fn get_path_buf (&self, src: impl Language) -> Perhaps { - todo!() - } - - fn get_arc_str (&self, src: impl Language) -> Perhaps> { - Ok(src.src()?.map(|x|x.into())) - } - - fn get_u16 (&self, src: impl Language) -> Perhaps { - Ok(Some(match src.word()? { - Some(":w/sidebar") => self.project.w_sidebar(self.editor().is_some()), - Some(":h/sample-detail") => 6.max(self.size.h() as u16 * 3 / 9), - _ => return try_to_u16(src) - })) - } - - fn get_usize (&self, src: impl Language) -> Perhaps { - Ok(Some(match src.word()? { - Some(":scene-count") => self.scenes().len(), - Some(":track-count") => self.tracks().len(), - Some(":device-kind") => self.dialog.device_kind().unwrap_or(0), - Some(":device-kind/next") => self.dialog.device_kind_next().unwrap_or(0), - Some(":device-kind/prev") => self.dialog.device_kind_prev().unwrap_or(0), - _ => return try_to_usize(src) - })) - } - - fn get_bool (&self, src: impl Language) -> Perhaps { - src.word()?.map(|word|Ok(match word { - "Y" => true, - "N" => false, - ":mode/editor" => self.project.editor.is_some(), - ":focused/dialog" => !matches!(self.dialog, Dialog::None), - ":focused/message" => matches!(self.dialog, Dialog::Message(..)), - ":focused/add_device" => matches!(self.dialog, Dialog::Device(..)), - ":focused/browser" => self.dialog.browser().is_some(), - ":focused/pool/import" => matches!(self.pool.mode, Some(PoolMode::Import(..))), - ":focused/pool/export" => matches!(self.pool.mode, Some(PoolMode::Export(..))), - ":focused/pool/rename" => matches!(self.pool.mode, Some(PoolMode::Rename(..))), - ":focused/pool/length" => matches!(self.pool.mode, Some(PoolMode::Length(..))), - ":focused/clip" => !self.editor_focused() && matches!(self.selection(), Selection::TrackClip{..}), - ":focused/track" => !self.editor_focused() && matches!(self.selection(), Selection::Track(..)), - ":focused/scene" => !self.editor_focused() && matches!(self.selection(), Selection::Scene(..)), - ":focused/mix" => !self.editor_focused() && matches!(self.selection(), Selection::Mix), - _ => return Err(format!("not bool: {word}").into()) - })).transpose() - } - - fn get_selection (&self, src: impl Language) -> Perhaps { - src.word()?.map(|word|Ok(match word { - ":select/scene" => self.selection().select_scene(self.tracks().len()), - ":select/scene/next" => self.selection().select_scene_next(self.scenes().len()), - ":select/scene/prev" => self.selection().select_scene_prev(), - ":select/track" => self.selection().select_track(self.tracks().len()), - ":select/track/next" => self.selection().select_track_next(self.tracks().len()), - ":select/track/prev" => self.selection().select_track_prev(), - _ => return Err(format!("not selection: {word}").into()) - })).transpose() - } - - fn get_color (&self, src: impl Language) -> Perhaps { - if let Some(expr) = src.expr()? { - match (expr.head()?, expr.tail()?) { - (Some("g"), Some(tail)) => { - let n = try_to_u8(tail.head().map_err(Into::into))? - .ok_or(LanguageError::Domain("not gray"))?; - Ok(Some(Color::Rgb(n, n, n))) - }, - (Some("rgb"), Some(tail)) => { - let r = try_to_u8(tail.head().map_err(Into::into))? - .ok_or(LanguageError::Domain("not red"))?; - let g = try_to_u8(tail.tail().head().map_err(Into::into))? - .ok_or(LanguageError::Domain("not green"))?; - let b = try_to_u8(tail.tail().tail().head().map_err(Into::into))? - .ok_or(LanguageError::Domain("not blue"))?; - Ok(Some(Color::Rgb(r, g, b))) - }, - (Some(_), _) => return Err(format!("not a color expression: {expr}").into()), - (None, _) => return Err(format!("not a color expression: {expr}").into()), - } - } else if let Ok(Some(sym)) = src.word() { - Ok(match sym { - ":color/bg" => Some(Color::Rgb(28, 32, 36)), - ":color/fg" => Some(Color::Rgb(98, 92, 96)), - _ => return Err(format!("not a color: {sym}").into()) - }) - } else { - return Err(format!("not a color: {:?}", src.src()?).into()) + fn get_arc_str (_: &App, src: impl Language) -> Perhaps> { + Ok(src.src()?.map(|x|x.into())) + } + fn get_u16 (state: &App, src: impl Language) -> Perhaps { + Ok(Some(match src.word()? { + Some(":w/sidebar") => state.project.w_sidebar(state.editor().is_some()), + Some(":h/sample-detail") => 6.max(state.size.h() as u16 * 3 / 9), + _ => return try_to_u16(src) + })) + } + fn get_usize (state: &App, src: impl Language) -> Perhaps { + Ok(Some(match src.word()? { + Some(":scene-count") => state.scenes().len(), + Some(":track-count") => state.tracks().len(), + Some(":device-kind") => state.dialog.device_kind().unwrap_or(0), + Some(":device-kind/next") => state.dialog.device_kind_next().unwrap_or(0), + Some(":device-kind/prev") => state.dialog.device_kind_prev().unwrap_or(0), + _ => return try_to_usize(src) + })) + } + fn get_bool (state: &App, src: impl Language) -> Perhaps { + src.word()?.map(|word|Ok(match word { + "Y" => true, + "N" => false, + ":mode/editor" => state.project.editor.is_some(), + ":focused/dialog" => !matches!(state.dialog, Dialog::None), + ":focused/message" => matches!(state.dialog, Dialog::Message(..)), + ":focused/add_device" => matches!(state.dialog, Dialog::Device(..)), + ":focused/browser" => state.dialog.browser().is_some(), + ":focused/pool/import" => matches!(state.pool.mode, Some(PoolMode::Import(..))), + ":focused/pool/export" => matches!(state.pool.mode, Some(PoolMode::Export(..))), + ":focused/pool/rename" => matches!(state.pool.mode, Some(PoolMode::Rename(..))), + ":focused/pool/length" => matches!(state.pool.mode, Some(PoolMode::Length(..))), + ":focused/clip" => !state.editor_focused() && matches!(state.selection(), Selection::TrackClip{..}), + ":focused/track" => !state.editor_focused() && matches!(state.selection(), Selection::Track(..)), + ":focused/scene" => !state.editor_focused() && matches!(state.selection(), Selection::Scene(..)), + ":focused/mix" => !state.editor_focused() && matches!(state.selection(), Selection::Mix), + _ => return Err(format!("not bool: {word}").into()) + })).transpose() + } + #[allow(unused)] + fn get_selection (state: &App, src: impl Language) -> Perhaps { + src.word()?.map(|word|Ok(match word { + ":select/scene" => state.selection().select_scene(state.tracks().len()), + ":select/scene/next" => state.selection().select_scene_next(state.scenes().len()), + ":select/scene/prev" => state.selection().select_scene_prev(), + ":select/track" => state.selection().select_track(state.tracks().len()), + ":select/track/next" => state.selection().select_track_next(state.tracks().len()), + ":select/track/prev" => state.selection().select_track_prev(), + _ => return Err(format!("not selection: {word}").into()) + })).transpose() + } + fn get_color (_: &App, src: impl Language) -> Perhaps { + if let Some(expr) = src.expr()? { + match (expr.head()?, expr.tail()?) { + (Some("g"), Some(tail)) => { + let n = try_to_u8(tail.head().map_err(Into::into))? + .ok_or(LanguageError::Domain("not gray"))?; + Ok(Some(Color::Rgb(n, n, n))) + }, + (Some("rgb"), Some(tail)) => { + let r = try_to_u8(tail.head().map_err(Into::into))? + .ok_or(LanguageError::Domain("not red"))?; + let g = try_to_u8(tail.tail().head().map_err(Into::into))? + .ok_or(LanguageError::Domain("not green"))?; + let b = try_to_u8(tail.tail().tail().head().map_err(Into::into))? + .ok_or(LanguageError::Domain("not blue"))?; + Ok(Some(Color::Rgb(r, g, b))) + }, + (Some(_), _) => return Err(format!("not a color expression: {expr}").into()), + (None, _) => return Err(format!("not a color expression: {expr}").into()), } + } else if let Ok(Some(sym)) = src.word() { + Ok(match sym { + ":color/bg" => Some(Color::Rgb(28, 32, 36)), + ":color/fg" => Some(Color::Rgb(98, 92, 96)), + _ => return Err(format!("not a color: {sym}").into()) + }) + } else { + return Err(format!("not a color: {:?}", src.src()?).into()) } - - fn get_opt_u7 (&self, src: impl Language) -> Perhaps> { - src.word()?.map(|word|Ok(match word { - ":editor/pitch" => Some(( - self.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8 - ).into()), - _ => return Err(format!("unknown midi note: {word}").into()) - })).transpose() - } - - fn get_opt_u16 (&self, _src: impl Language) -> Perhaps> { - Ok(None) - } - - fn get_opt_usize (&self, src: impl Language) -> Perhaps> { - src.word()?.map(|word|Ok(match word { - ":selected/scene" => self.selection().scene(), - ":selected/track" => self.selection().track(), - _ => return Err(format!("unknown opt: {word}").into()) - })).transpose() - } - - fn get_clip (&self, src: impl Language) -> Perhaps>>> { - src.word()?.map(|word|Ok(match word { - ":selected/clip" if let Selection::TrackClip { track, scene } = self.selection() => - self.scenes()[*scene].clips[*track].clone(), - _ => return Err(format!("not a clip: {word}").into()) - })).transpose() - } - - fn get_axis (&self, src: impl Language) -> Perhaps { - Ok(src.word()?.map(|word|Ok(match word { - ":x" => ControlAxis::X, - ":y" => ControlAxis::Y, - _ => return Err(format!("unknown axis {word}")) - })).transpose()?) - } - - fn get_opt_itemtheme (&self, src: impl Language) -> Perhaps> { - todo!() - } - fn get_opt_vec_opt_arc_rwlock_midiclip (&self, src: impl Language) -> Perhaps>>>>> { - todo!() - } - fn get_opt_arc_array_connect (&self, src: impl Language) -> Perhaps>> { - todo!() - } - - fn get_arc_array_connect (&self, src: impl Language) -> Perhaps> { - todo!() - } - - fn get_opt_arc_str (&self, src: impl Language) -> Perhaps>> { - todo!() - } - - fn get_opt_bool (&self, src: impl Language) -> Perhaps> { - todo!() - } - + } + #[allow(unused)] + fn get_opt_u7 (state: &App, src: impl Language) -> Perhaps> { + src.word()?.map(|word|Ok(match word { + ":editor/pitch" => Some(( + state.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8 + ).into()), + _ => return Err(format!("unknown midi note: {word}").into()) + })).transpose() + } + fn get_opt_usize (state: &App, src: impl Language) -> Perhaps> { + src.word()?.map(|word|Ok(match word { + ":selected/scene" => state.selection().scene(), + ":selected/track" => state.selection().track(), + _ => return Err(format!("unknown opt: {word}").into()) + })).transpose() + } + #[allow(unused)] + fn get_clip (state: &App, src: impl Language) -> Perhaps>>> { + src.word()?.map(|word|Ok(match word { + ":selected/clip" if let Selection::TrackClip { track, scene } = state.selection() => + state.scenes()[*scene].clips[*track].clone(), + _ => return Err(format!("not a clip: {word}").into()) + })).transpose() + } + fn get_axis (_: &App, src: impl Language) -> Perhaps { + Ok(src.word()?.map(|word|Ok(match word { + ":x" => ControlAxis::X, + ":y" => ControlAxis::Y, + _ => return Err(format!("unknown axis {word}")) + })).transpose()?) + } + fn get_opt_u16 (_: &App, _src: impl Language) -> Perhaps> { + Ok(None) + } + fn get_opt_itemtheme (_: &App, _: impl Language) -> Perhaps> { + todo!() + } + fn get_opt_vec_opt_arc_rwlock_midiclip (_: &App, _: impl Language) -> Perhaps>>>>> { + todo!() + } + fn get_opt_arc_array_connect (_: &App, _: impl Language) -> Perhaps>> { + todo!() + } + fn get_arc_array_connect (_: &App, _: impl Language) -> Perhaps> { + todo!() + } + fn get_opt_arc_str (_: &App, _: impl Language) -> Perhaps>> { + todo!() + } + fn get_opt_bool (_: &App, _: impl Language) -> Perhaps> { + todo!() + } + fn get_path_buf (_: &App, _: impl Language) -> Perhaps { + todo!() } } @@ -959,19 +865,6 @@ mod bind { } } - //#[derive(Clone, Debug)] - //pub enum AppCommand { - //Axis(AxisCommand), - //Browse(BrowseCommand), - //Dialog(DialogCommand), - //MidiClip(MidiClipCommand), - //Pool(PoolCommand), - //Scene(SceneCommand), - //Scenes(ScenesCommand), - //Track(TrackCommand), - //Tracks(TracksCommand), - //} - impl_from!(AppCommand: |x: AxisCommand| AppCommand::Axis { command: x }); impl_from!(AppCommand: |x: BrowseCommand| AppCommand::Browse { command: x }); impl_from!(AppCommand: |x: DialogCommand| AppCommand::Dialog { command: x }); @@ -1401,27 +1294,6 @@ mod device { #[cfg(feature = "plugin")] pub use self::plugin::*; } - //pub fn tui ( - //app: Arc>, - //jack: Jack, - //sync_lead: &bool, - //sync_follow: &bool, - //) -> Usually<()> { - //// Run the [Tui] and [Jack] threads with the [App] state. - //Tui::run_main(&jack.run(move|jack|{ - //// Between jack init and app's first cycle: - ////jack.sync_lead(*sync_lead, |mut state|{ - ////let clock = app.write().unwrap().clock(); - ////clock.playhead.update_from_sample(state.position.frame() as f64); - ////state.position.bbt = Some(clock.bbt()); - ////state.position - ////})?; - ////jack.sync_follow(*sync_follow)?; - //// FIXME: They don't work properly. - //Ok(app) - //})?)? - //} - pub use self::draw::*; mod draw { use crate::*; @@ -1698,6 +1570,53 @@ mod draw { } +pub fn print_config (config: &Config) { + use ::ansi_term::Color::*; + println!("{:?}", config.dirs); + for (k, v) in config.views.read().unwrap().iter() { + println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); + } + for (k, v) in config.binds.read().unwrap().iter() { + println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); + for (k, v) in v.0.iter() { + print!("{} ", &Yellow.paint(match &k.0 { + Event::Key(KeyEvent { modifiers, .. }) => + format!("{:>16}", format!("{modifiers}")), + _ => unimplemented!() + })); + print!("{}", &Yellow.bold().paint(match &k.0 { + Event::Key(KeyEvent { code, .. }) => + format!("{:<10}", format!("{code}")), + _ => unimplemented!() + })); + for v in v.iter() { + print!(" => {:?}", v.commands); + print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); + println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); + //println!(" {:?}", v.source); + } + } + } + config.modes.for_each(|k, v|{ + println!(); + for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } + for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } + print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); + print!("\n{}", Blue.paint("KEYS")); + for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + v.modes.for_each(|k, v|{ + print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); + print!( " INFO={:?}", v.info); + print!( " VIEW={:?}", v.view); + println!(" KEYS={:?}", v.keys); + }); + print!("{}", Blue.paint("VIEW")); + for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + }); +} + pub fn print_status (project: &Arrangement) { println!("Name: {:?}", &project.name); println!("JACK: {:?}", &project.jack); From d9e17cd3489664a456b1cd84640e21398375a1c6 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Wed, 12 Aug 2026 20:58:59 +0300 Subject: [PATCH 3/3] allow adding scenes --- src/device/arrange/scene.rs | 69 ++++++++++++++++++++++--------------- src/tek.edn | 16 ++++----- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index 3226a6f6..cff99a43 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -83,19 +83,19 @@ pub trait SceneController: HasScene + Namespace> + Namespace { - #[command(SetSize = "scene/size")] + #[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps where Self: Namespace { todo!() } - #[command(SetZoom = "scene/zoom")] + #[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps where Self: Namespace { todo!() } - #[command(SetName = "scene/name")] + #[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc) -> Perhaps where Self: Namespace> { @@ -105,7 +105,7 @@ pub trait SceneController: HasScene |name|SceneCommand::SetName { name } )).transpose()?.flatten()) } - #[command(SetColor = "scene/color")] + #[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps where Self: Namespace { @@ -171,6 +171,9 @@ pub trait HasScenes: AsRef> + AsMut> { } impl + Namespace + Namespace> + Namespace>> @@ -178,10 +181,20 @@ impl + Namespace + Namespace> + Namespace>> { + #[command(Add = "add")] + fn scenes_add (&mut self) -> Perhaps + where Self: HasScenes + HasJack<'static> + { + let (_index, _) = self.scenes_add_one(None, None)?; + Ok(None) + } // TODO } @@ -262,27 +275,27 @@ impl HasSceneScroll for App { } } - pub fn view_scene_name ( - select: &Selection, - editor: Option<&MidiEditor>, - index: usize, - scene: &Scene, - editing: bool - ) -> impl Draw { - let h = if select.scene() == Some(index) && let Some(_editor) = editor { - 7 - } else { - Scene::DEFAULT_HEIGHT as u16 - }; - let a = east(format!("ยทs{index:02} "), - fg(g(255), bold(true, &scene.name))).align_w().full_w(); - let b = when(select.scene() == Some(index) && editing, south( - editor.as_ref().map(|e|e.clip_status()), - editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); - let c = if select.scene() == Some(index) { - scene.color.light.term - } else { - scene.color.base.term - }; - bg(c, south(a, b).align_nw()).exact_wh(20, h) - } +pub fn view_scene_name ( + select: &Selection, + editor: Option<&MidiEditor>, + index: usize, + scene: &Scene, + editing: bool +) -> impl Draw { + let h = if select.scene() == Some(index) && let Some(_editor) = editor { + 7 + } else { + Scene::DEFAULT_HEIGHT as u16 + }; + let a = east(format!("ยทs{index:02} "), + fg(g(255), bold(true, &scene.name))).align_w().full_w(); + let b = when(select.scene() == Some(index) && editing, south( + editor.as_ref().map(|e|e.clip_status()), + editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); + let c = if select.scene() == Some(index) { + scene.color.light.term + } else { + scene.color.base.term + }; + bg(c, south(a, b).align_nw()).exact_wh(20, h) +} diff --git a/src/tek.edn b/src/tek.edn index 93e217ba..7c2f2e99 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -56,15 +56,15 @@ (keys :launch (@q launch)) -(keys :scenes (@s (select :select/scene)) - (@shift/S (scenes add)) - (@up (select :select/scene/dec)) - (@down (select :select/scene/inc))) +(keys :scenes (@shift/S (scenes add)) + (@s (select scene)) + (@up (select scene-dec)) + (@down (select scene-inc))) -(keys :tracks (@t (select :select/track)) - (@shift/T (tracks add)) - (@left (select :select/track/dec)) - (@right (select :select/track/inc))) +(keys :tracks (@shift/T (tracks add)) + (@t (select track)) + (@left (select track-dec)) + (@right (select track-inc))) (keys :global (see :history :saveload) (@f8 dialog :options)