From 68236a72109a73eb9fb7e5cd307f24f25b887bca Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sat, 8 Aug 2026 23:37:25 +0300 Subject: [PATCH 1/2] reenable most arranger components --- src/device/arrange.rs | 21 ++++-- src/tek.edn | 108 ++++++++++++++++--------------- src/tek.rs | 146 +++++++++++++++++++++++------------------- tengri | 2 +- 4 files changed, 152 insertions(+), 125 deletions(-) diff --git a/src/device/arrange.rs b/src/device/arrange.rs index 5f871a38..e86227c6 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -609,9 +609,15 @@ impl Track { pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a) ) -> impl Draw + 'a { - view_track_per(tracks, callback) + iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{ + fg_bg( + track.color.lightest.term, + track.color.base.term, + callback(index, track) + ).exact_w((x2 - x1) as u16) + })) } } @@ -948,10 +954,11 @@ impl Arrangement { pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { let height = self.devices_height(); - view_track_row_section(theme, - button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), - button_2("D", "+", false), - iter_once(self.tracks_with_sizes(), move|(_, track, _x1, _x2), index|bg( + let btn1 = button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false); + let btn2 = button_2("D", "+", false); + view_track_row_section(theme, btn1, btn2, iter_east(move||self.tracks_with_sizes() + .enumerate() + .map(move|(index, (_, track, _x1, _x2))|bg( track.color.dark.term, iter_south(move||(0..height).map(|_|fg_bg( ItemTheme::G[32].lightest.term, @@ -963,7 +970,7 @@ impl Arrangement { .exact_wh( Some(track_width(index, track)), Some(height + 1), - ))) + )))) } fn devices_height (&self) -> u16 { diff --git a/src/tek.edn b/src/tek.edn index 7dac49a6..ef4b6566 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,7 +1,7 @@ -(view :logo (bsp/s (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ) - (bsp/s (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~ ) - (bsp/s (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ) - (text))))) +(view :logo (bsp/s (bg (rgb 100 70 40) (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ )) + (bsp/s (bg (rgb 90 70 50) (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~ )) + (bsp/s (bg (rgb 80 70 60) (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ )) + (align/x (text .)))))) (view :browse (bsp/s (padding 3 1 :browse-title) @@ -15,51 +15,8 @@ (mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm) (view (bsp/s - (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)))))) - -(view :ports/out - (bsp/s (align/w (text L-AUDIO-OUT)) - (bsp/e (text MIDI-OUT) - (align/e (text AUDIO-OUT-R))))) - -(view :ports/in - (bsp/s (align/w (text L-AUDIO-IN)) - (bsp/e (text MIDI-IN) - (align/e (text AUDIO-IN-R))))) - -(mode :sequencer (name Sequencer) (info MIDI sequencer.) - (keys :editor :clock :global) - (mode browse (keys :browse)) - (mode rename (keys :pool/rename)) - (mode length (keys :pool/length)) - (bsp/s (fixed/y 1 :transport) - (bsp/n (fixed/y 1 :status) - (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) - -(mode :sampler (name Sampler) (info Sample player.) - (keys :sampler/directions :sampler/record :sampler/play) - (bsp/s (fixed/y 1 :transport) - (bsp/n (fixed/y 1 :status) - (fill :samples/grid)))) - -(mode :groovebox (name Groovebox) (info Sequencer with sampler.) - (keys :clock :editor :sampler :global) - (mode browse (keys :browse)) - (mode rename (keys :pool-rename)) - (mode length (keys :pool-length)) - (bsp/w :meters/output (bsp/e :meters/input (bsp/w :groove/meta :groove/editor)))) - -(view :groove/meta (fill/y (align/n (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool)))) - -(view :groove/editor (bsp/n :groove/sample :groove/sequence)) - -(view :groove/sample (fixed/y :h-sample-detail (bsp/e (fill/y (fixed/x 20 (align/nw :sample-status))) :sample-viewer))) - -(view :groove/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor))) + (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)))))) (mode :arranger (name Arranger) (info Launch grid.) (keys (see :clock :color :launch :scenes :tracks :global) @@ -76,7 +33,58 @@ (mode :track (keys :track)) (mode :scene (keys :scene)) (mode :mix (keys :mix)) - (view (bg (g 64) (bsp/n :status (text test))))) + (view + (bsp/n (bg (g 10) (bsp/e :transport :status)) + (bsp/w (bg (g 20) (exact/x 4 (align/ne :meters/output))) + (bsp/e (bg (g 30) (exact/x 4 (align/nw :meters/input))) + (full/xy (align/c (max/xy 80 80 + (bsp/s (bg (g 40) (exact/y 4 :tracks/outputs)) + (bsp/s (bg (g 50) (exact/y 2 :tracks/names)) + (bsp/s (bg (g 60) (exact/y 4 :tracks/devices)) + (bsp/s (bg (g 70) (exact/y 4 :tracks/inputs)) + (either :mode/editor + (bg (g 80) (bsp/s :scenes/names :editor)) + (bg (g 90) :scenes)))))))))))))) + +(view :ports/out + (bsp/s (align/w (text L-AUDIO-OUT)) + (bsp/e (text MIDI-OUT) + (align/e (text AUDIO-OUT-R))))) + +(view :ports/in + (bsp/s (align/w (text L-AUDIO-IN)) + (bsp/e (text MIDI-IN) + (align/e (text AUDIO-IN-R))))) + +(mode :sequencer (name Sequencer) (info MIDI sequencer.) + (keys :editor :clock :global) + (mode browse (keys :browse)) + (mode rename (keys :pool/rename)) + (mode length (keys :pool/length)) + (bsp/s (exact/y 1 :transport) + (bsp/n (exact/y 1 :status) + (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) + +(mode :sampler (name Sampler) (info Sample player.) + (keys :sampler/directions :sampler/record :sampler/play) + (bsp/s (exact/y 1 :transport) + (bsp/n (exact/y 1 :status) + (fill :samples/grid)))) + +(mode :groovebox (name Groovebox) (info Sequencer with sampler.) + (keys :clock :editor :sampler :global) + (mode browse (keys :browse)) + (mode rename (keys :pool-rename)) + (mode length (keys :pool-length)) + (bsp/w :meters/output (bsp/e :meters/input (bsp/w :groove/meta :groove/editor)))) + +(view :groove/meta (fill/y (align/n (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool)))) + +(view :groove/editor (bsp/n :groove/sample :groove/sequence)) + +(view :groove/sample (exact/y :h-sample-detail (bsp/e (fill/y (exact/x 20 (align/nw :sample-status))) :sample-viewer))) + +(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)) diff --git a/src/tek.rs b/src/tek.rs index 85efad6a..520197a9 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -1380,9 +1380,7 @@ mod draw { /// Load custom view definition. pub(crate) fn load_view ( - views: &Views, - name: &impl AsRef, - body: &impl Language, + views: &Views, name: &impl AsRef, body: &impl Language, ) -> Usually<()> { views.write().unwrap().insert( name.as_ref().into(), @@ -1393,7 +1391,7 @@ 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_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() } @@ -1467,11 +1465,26 @@ mod draw { let mut frags = lang.src()?.unwrap().split("/"); match frags.next() { //Some(":logo") => view_logo().draw(to), - Some(":meters") => draw_meter_section(to, frags), - Some(":tracks") => draw_tracks(to, frags, self), - Some(":scenes") => draw_scenes(to, frags), + Some(":meters") => match frags.next() { + Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to), + Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to), + _ => panic!() + }, + Some(":tracks") => match frags.next() { + None => "TODO tracks".draw(to), + Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), + Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), + Some("devices") => self.project.view_track_devices(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to), + Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to), + _ => panic!() + }, + Some(":scenes") => match frags.next() { + None => "TODO Scenes".draw(to), + Some(":scenes/names") => "TODO Scene Names".draw(to), + _ => panic!() + }, Some(":dialog") => draw_dialog(to, frags, self, lang), - Some(":templates") => draw_templates(to, frags, self), + Some(":templates") => view_templates(frags, self).draw(to), Some(":sessions") => view_sessions().draw(to), Some(":browse/title") => view_browse_title(self).draw(to), Some(":device") => view_device(self).draw(to), @@ -1486,7 +1499,7 @@ mod draw { std::mem::drop(views); self.interpret(to, &lang) } else { - unimplemented!("{lang:?}"); + fg(Color::Rgb(128, 32, 32), format!("undefined: {lang:?}")).draw(to) } }, _ => unreachable!() @@ -1504,34 +1517,7 @@ mod draw { fn width_dec (&mut self); } - pub fn draw_meter_section (to: &mut Tui, mut frags: std::str::Split<&str>) -> Drawn { - match frags.next() { - Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to), - Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to), - _ => panic!() - } - } - - pub fn draw_tracks (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App) -> Drawn { - match frags.next() { - None => "TODO tracks".draw(to), - Some("names") => state.project.view_track_names(state.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), - Some("inputs") => bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), - Some("devices") => bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to), - Some("outputs") => bg(Rgb(40, 40, 40), "Track Outputs".align_w().full_w()).draw(to), - _ => panic!() - } - } - - pub fn draw_scenes (to: &mut Tui, mut frags: std::str::Split<&str>) -> Drawn { - match frags.next() { - None => "TODO Scenes".draw(to), - Some(":scenes/names") => "TODO Scene Names".draw(to), - _ => panic!() - } - } - - pub fn draw_templates (to: &mut Tui, _frags: std::str::Split<&str>, state: &App) -> Drawn { + pub fn view_templates (_frags: std::str::Split<&str>, state: &App) -> impl Draw { let height = (state.config.modes.len() * 2) as u16; draw(move |to: &mut Tui|{ let mut index = 0; @@ -1549,29 +1535,29 @@ mod draw { index += 1; }); Ok(Some(to.area().into())) - }).min_w(30).exact_h(height).draw(to) + }).min_w(30).exact_h(height) } - pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> impl Draw + 'a { - per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y()) - } + //pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + //tracks: impl Fn() -> U + Send + Sync + 'a, + //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + //) -> impl Draw + 'a { + //per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y()) + //} - pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> impl Draw + 'a { - bg(Reset, iter_east(tracks, - move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ - fg_bg( - track.color.lightest.term, - track.color.base.term, - callback(index, track) - ).exact_w((x2 - x1) as u16) - }).align_x()) - } + //pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + //tracks: impl Fn() -> U + Send + Sync + 'a, + //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + //) -> impl Draw + 'a { + //bg(Reset, iter_east(||tracks() + //.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{ + //fg_bg( + //track.color.lightest.term, + //track.color.base.term, + //callback(index, track) + //).exact_w((x2 - x1) as u16) + //})).align_x()) + //} pub fn field_h ( _theme: ItemTheme, _head: impl Draw, _body: impl Draw @@ -1932,17 +1918,43 @@ mod draw { track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) } - pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + //pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + //tracks: impl Fn() -> U + Send + Sync + 'a, + //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + //) -> impl Draw { + //} + + /// ``` + /// let _ = tek::button_2("", "", true); + /// let _ = tek::button_2("", "", false); + /// ``` + pub fn button_2 <'a> (key: impl Draw, label: impl Draw, hide: bool) -> impl Draw { + let c1 = tui_orange(); + let c2 = tui_g(0); + let c3 = tui_g(96); + let c4 = tui_g(255); + bold(true, fg_bg(c1, c2, + east!(fg(c2, "▐"), key, fg(c3, "▐"), when(!hide, fg_bg(c4, c3, label))))) + } + + /// ``` + /// let _ = tek::button_3("", "", "", true); + /// let _ = tek::button_3("", "", "", false); + /// ``` + pub fn button_3 <'a> ( + key: impl Draw, label: impl Draw, value: impl Draw, editing: bool, ) -> impl Draw { - iter_east(tracks, move|(index, track, x1, x2): (usize, &Track, usize, usize), _|{ - fg_bg( - track.color.lightest.term, - track.color.base.term, - callback(index, track) - ).exact_w((x2 - x1) as u16) - }) + let c1 = tui_orange(); + let c2 = tui_g(0); + let c3 = tui_g(96); + let c4 = tui_g(255); + let c5 = tui_g(128); + bold(true, east( + fg_bg(c1, c2, + east(fg(c2, "▐"), east(key, fg(if editing { c5 } else { c3 }, "▐")))), + east( + when(!editing, east(fg_bg(c4, c3, label), fg_bg(c5, c3, "▐"),)), + east(fg_bg(tui_g(224), c5, value), fg_bg(c5, Reset, "▌"), )))) } } diff --git a/tengri b/tengri index 799e4976..8a6eb19e 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 799e49762250f48778dadd92874a29495114a6af +Subproject commit 8a6eb19e279afefc126008c27b38cc66645e1c96 From ae496987c8bdc85e3a8880be1ec0139b55a99c26 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sun, 9 Aug 2026 09:23:24 +0300 Subject: [PATCH 2/2] implement passable #[command] --- Cargo.lock | 10 ++ Cargo.toml | 3 +- proc/Cargo.toml | 11 ++ proc/src/lib.rs | 189 ++++++++++++++++++++++ src/device/browse.rs | 30 +++- src/device/dialog.rs | 16 +- src/tek.rs | 371 ++++++++++++++++++++++++------------------- 7 files changed, 458 insertions(+), 172 deletions(-) create mode 100644 proc/Cargo.toml create mode 100644 proc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f49c67e0..10dda47d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3145,6 +3145,7 @@ dependencies = [ "rand 0.8.7", "symphonia", "tek", + "tek_proc", "tengri", "toml 0.9.12+spec-1.1.0", "uuid", @@ -3153,6 +3154,15 @@ dependencies = [ "xdg", ] +[[package]] +name = "tek_proc" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "tempfile" version = "3.27.0" diff --git a/Cargo.toml b/Cargo.toml index b398b732..fcc3c5d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ path = "src/tek.rs" rustflags = ["-C", "link-arg=-fuse-ld=mold"] [dependencies] -tengri = { path = "./tengri", features = [ "term", "lang" ] } +tek_proc = { path = "./proc" } +tengri = { path = "./tengri", features = [ "term", "lang" ] } ansi_term = { version = "0.12.1" } atomic_float = { version = "1.0.0" } diff --git a/proc/Cargo.toml b/proc/Cargo.toml new file mode 100644 index 00000000..bea4fa22 --- /dev/null +++ b/proc/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tek_proc" +description = "Shorthands for implementing Tek commands." +version = "0.1.0" +edition = "2024" +[lib] +proc-macro = true +[dependencies] +proc-macro2 = "1.0.106" +quote = "1.0.46" +syn = { version = "2.0.119", features = ["full", "extra-traits"] } diff --git a/proc/src/lib.rs b/proc/src/lib.rs new file mode 100644 index 00000000..9ef4169a --- /dev/null +++ b/proc/src/lib.rs @@ -0,0 +1,189 @@ +use proc_macro::{TokenStream, Literal}; +use proc_macro2::{TokenStream as TokenStream2, Span}; +use quote::{quote, ToTokens, TokenStreamExt}; +use std::collections::{HashMap, BTreeMap}; +use syn::{ + Path, ItemEnum, Ident, Type, Variant, MetaList, Expr, ExprPath, ExprBinary, Fields, BinOp, + parse::{ParseStream, Parse, Result} +}; + +macro_rules! attribute { + ($name:ident { $($body:tt)* }) => { + #[proc_macro_attribute] pub fn $name (meta: TokenStream, item: TokenStream) -> TokenStream { + write(self::$name::Def( + syn::parse_macro_input!(meta as self::$name::Meta), + syn::parse_macro_input!(item as self::$name::Item), + )) + } + mod $name { + use crate::*; + $($body)* + } + } +} + +attribute!(command { + #[derive(Debug, Clone)] pub struct Def( + pub Meta, pub Item + ); + #[derive(Debug, Clone)] pub struct Meta( + pub Path + ); + #[derive(Debug, Clone)] pub struct Item( + pub ItemEnum, pub HashMap + ); + impl Parse for Meta { + fn parse (input: ParseStream) -> Result { + Ok(Self(input.parse()?)) + } + } + impl Parse for Item { + fn parse (input: ParseStream) -> Result { + let mut item: ItemEnum = input.parse()?; + let mut branches: HashMap = Default::default(); + 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 mut attrs_filtered = attrs.clone(); + attrs_filtered.clear(); + for attr in attrs.iter() { + if let syn::Meta::List(MetaList { ref path, ref tokens, .. }) = attr.meta + && path == &Path::from(Ident::new("command", Span::call_site())) + && let Ok(handler) = syn::parse2::(tokens.clone()) + { + branches.insert(ident.clone(), (fields.clone(), handler)); + } else { + attrs_filtered.push(attr.clone()); + } + } + *attrs = attrs_filtered; + variants_filtered.push(variant.clone()) + } + item.variants = variants_filtered; + Ok(Self(item, branches)) + } + } + impl ToTokens for Def { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(Meta(state), Item(item, branches)) = self; + let ident = &item.ident; + let mut body = quote! {}; + for (variant, (fields, handler)) in branches.iter() { + let (handler, wrapper) = if let Expr::Path(ExprPath { path, .. }) = handler { + (path, None) + } else if let Expr::Binary(ExprBinary { + op: BinOp::BitOr(_), left, right, .. + }) = handler && let Expr::Path(ExprPath { + path: handler, .. + }) = &**left && let Expr::Path(ExprPath { + path: wrapper, .. + }) = &**right { + (handler, Some(wrapper)) + } else { + panic!() + }; + match fields { + Fields::Named(fields) => todo!("named command fields"), + Fields::Unnamed(fields) => { + let mut params = quote! {}; + let mut values = quote! {}; + for (index, _field) in fields.unnamed.iter().enumerate() { + let name = Ident::new(&format!("arg{index}"), Span::call_site()); + append(&mut params, quote! { #name, }); + append(&mut values, quote! { #name, }); + } + let invocation = if let Some(wrapper) = wrapper { + quote! { Ok(Some(#wrapper(#handler(state, #values)))) } + } else { + quote! { #handler(state, #values) } + }; + append(&mut body, quote! { + #ident::#variant (#params) => #invocation, + }); + }, + _ => { + append(&mut body, quote! { + #ident::#variant => #handler(state), + }); + } + } + } + append(out, quote! { + #item + + impl dizzle::Act<#state> for #ident { + fn act (&self, state: &mut #state) -> Perhaps { + match self { + #body + _ => unreachable!() + } + } + } + }) + } + } +}); + +attribute!(keyword { + #[derive(Debug, Clone)] pub struct Def( + pub Meta, pub Item + ); + #[derive(Debug, Clone)] pub struct Meta( + pub Path + ); + #[derive(Debug, Clone)] pub struct Item( + pub ItemEnum, pub BTreeMap)>> + ); + impl Parse for Meta { + fn parse (input: ParseStream) -> Result { + Ok(Self(input.parse()?)) + } + } + impl Parse for Item { + fn parse (input: ParseStream) -> Result { + let mut item: ItemEnum = input.parse()?; + for Variant { attrs, ident, fields, discriminant } in item.variants.iter_mut() { + attrs.retain(|attr|if let syn::Meta::List(MetaList { + ref path, ref tokens, .. + }) = attr.meta && path == &Path::from(Ident::new( + "keyword", Span::call_site() + )) { + // TODO + false + } else { + true + }); + } + Ok(Self(item, Default::default())) + } + } + impl ToTokens for Def { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(Meta(state), Item(item, variants)) = self; + let ident = &item.ident; + let body = quote! {}; + append(out, quote! { + #item + + impl<'a> Namespace<'a, #ident> for #state { + symbols!('a |stte| -> #ident { + #body, + }); + } + }) + } + } +}); + +fn write (t: T) -> TokenStream { + let mut out = TokenStream2::new(); + t.to_tokens(&mut out); + out.into() +} + +fn append (out: &mut TokenStream2, quote: TokenStream2) { + for token in quote { + out.append(token); + } +} diff --git a/src/device/browse.rs b/src/device/browse.rs index a5f61c7a..985a98a7 100644 --- a/src/device/browse.rs +++ b/src/device/browse.rs @@ -1,6 +1,17 @@ use crate::*; -def_command!(FileBrowserCommand: |sampler: Sampler|{ +impl App { + /// Return reference to content browser if open. + /// + /// ``` + /// assert_eq!(tek::App::default().browser(), None); + /// ``` + pub fn browser (&self) -> Option<&Browse> { + if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } + } +} + +def_command!(FileBrowserCommand: |_browse: Browse|{ //("begin" [] Some(Self::Begin)) //("cancel" [] Some(Self::Cancel)) //("confirm" [] Some(Self::Confirm)) @@ -539,3 +550,20 @@ def_command!(CropCommand: |pool: Pool| { Ok(None) } }); + +pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { + let (mut subdirs, mut files) = std::fs::read_dir(dir)? + .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ + let entry = entry.expect("failed to read drectory entry"); + let meta = entry.metadata().expect("failed to read entry metadata"); + if meta.is_file() { + files.push(entry.file_name()); + } else if meta.is_dir() { + subdirs.push(entry.file_name()); + } + (subdirs, files) + }); + subdirs.sort(); + files.sort(); + Ok((subdirs, files)) +} diff --git a/src/device/dialog.rs b/src/device/dialog.rs index 7b28cf07..c54e5dea 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -33,6 +33,7 @@ pub fn draw_dialog ( } impl App { + pub fn get_dialog (&self, src: impl Language) -> Perhaps { src.word()?.map(|word|Ok(match word { ":dialog/none" => Dialog::None, @@ -64,31 +65,36 @@ impl App { ) })).transpose() } - /// Set modal dialog. + + /// Set currently active modal dialog. /// /// ``` - /// let previous: tek::Dialog = tek::App::default().set_dialog(tek::Dialog::welcome()); + /// let previous: tek::Dialog = tek::App::default().set_dialog(&tek::Dialog::welcome()); /// ``` - pub fn set_dialog (&mut self, mut dialog: Dialog) -> Dialog { + pub fn set_dialog (&mut self, dialog: &Dialog) -> Dialog { + let mut dialog = dialog.clone(); std::mem::swap(&mut self.dialog, &mut dialog); dialog } + 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)?, + AppCommand::SetDialog(self.dialog.menu_next()).act(self)?, _ => todo!() }) } + 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)?, + AppCommand::SetDialog(self.dialog.menu_prev()).act(self)?, _ => todo!() }) } + pub fn confirm (&mut self) -> Perhaps { Ok(match &self.dialog { Dialog::Menu(index, items) => { diff --git a/src/tek.rs b/src/tek.rs index 520197a9..8dcc90c8 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -218,14 +218,26 @@ fn run_new_plain (config: Config) -> Usually<()> { Clock::new(&jack, *bpm)?, [].into_iter(), [].into_iter(), - connect_midi_ins(&jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re))?.into_iter(), - connect_midi_outs(&jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re))?.into_iter(), + connect_midi_ins( + &jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re) + )?.into_iter(), + connect_midi_outs( + &jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re) + )?.into_iter(), [].into_iter() - .chain(connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter()) - .chain(connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter()), + .chain( + connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter() + ) + .chain( + connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter() + ), [].into_iter() - .chain(connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter()) - .chain(connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter())); + .chain( + connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter() + ) + .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(tracks.unwrap_or(0), None, &[], &[])?; proj.scenes_add(scenes.unwrap_or(0))?; @@ -476,12 +488,13 @@ mod config { let tail = expr.tail()?; let name = tail.head()?; let body = tail.tail()?; - //println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); match head { Some("mode") if let Some(name) = name => self.modes.add(&name, &body)?, Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?, Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?, - _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) + _ => return Err( + format!("Config::load: expected view/keys/mode, got: {item:?}").into() + ) } Ok(()) } else { @@ -713,6 +726,7 @@ mod app { /// Error, if any pub error: Arc>>> } + impl App { /// Create a new application instance from a backend, project, config, and mode /// @@ -856,55 +870,32 @@ mod app { })).transpose() } - /// FIXME: generalize. Set picked device in device pick dialog. - /// - /// ``` - /// tek::App::default().device_pick(0); - /// ``` - pub fn device_pick (&mut self, index: usize) { - self.dialog = Dialog::Device(index); - } - - /// FIXME: generalize. Add device to current track. - pub fn add_device (&mut self, index: usize) -> Usually<()> { - match index { - 0 => { - let name = self.jack.with_client(|c|c.name().to_string()); - let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); - let track = self.track().expect("no active track"); - let port = format!("{}/Sampler", &track.name); - let connect = Connect::exact(format!("{name}:{midi}")); - let sampler = if let Ok(sampler) = Sampler::new( - &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] - ) { - self.dialog = Dialog::None; - Device::Sampler(sampler) - } else { - self.dialog = Dialog::Message("Failed to add device.".into()); - return Err("failed to add device".into()) - }; - let track = self.track_mut().expect("no active track"); - track.devices.push(sampler); - Ok(()) - }, - 1 => { - todo!(); - //Ok(()) - }, - _ => unreachable!(), - } - } - - /// Return reference to content browser if open. - /// - /// ``` - /// assert_eq!(tek::App::default().browser(), None); - /// ``` - pub fn browser (&self) -> Option<&Browse> { - if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } - } - } +} + +pub use self::bind::*; +mod bind { + use crate::*; + + /// Define an enum containing commands, and implement [Command] trait for over given `State`. + #[macro_export] macro_rules! def_command ( + ($Command:ident: |$state:ident: $State:ty| { + // FIXME: support attrs (docstrings) + $($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)? + })=>{ + #[derive(Debug)] pub enum $Command { + // FIXME: support attrs (docstrings) + $($Variant $({ $($arg: $Arg),* })?),* + } + impl ::tengri::dizzle::Act<$State> for $Command { + fn act (&self, $state: &mut $State) -> Perhaps { + match self { + $(Self::$Variant $({ $($arg),* })? => $body,)* + _ => unimplemented!("Act<{}>: {self:?}", stringify!($State)), + } + } + } + }); pub fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U @@ -930,23 +921,6 @@ mod app { } } - pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { - let (mut subdirs, mut files) = std::fs::read_dir(dir)? - .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ - let entry = entry.expect("failed to read drectory entry"); - let meta = entry.metadata().expect("failed to read entry metadata"); - if meta.is_file() { - files.push(entry.file_name()); - } else if meta.is_dir() { - subdirs.push(entry.file_name()); - } - (subdirs, files) - }); - subdirs.sort(); - files.sort(); - Ok((subdirs, files)) - } - tui_keys!(self: App, input { let commands = tek_commands_collect(self, input)?; let results = tek_commands_execute(self, commands)?; @@ -1072,30 +1046,58 @@ mod app { impl_debug!(Condition |self, w| { write!(w, "*") }); - impl_default!(AppCommand: Self::Nop); + #[tek_proc::command(App)] + #[tek_proc::keyword(App)] + #[derive(Debug, Default)] + pub enum AppCommand { + #[default] + #[command(App::nop)] + #[keyword("nop")] + Nop, - def_command!(AppCommand: |app: App| { - Nop => Ok(None), - Cancel => todo!(), // TODO delegate: - Confirm => app.confirm(), - Inc { axis: ControlAxis } => app.inc(axis), - Dec { axis: ControlAxis } => app.dec(axis), - SetDialog { dialog: Dialog } => { - swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }) - }, - }); + #[command(App::cancel)] + #[keyword("cancel")] + Cancel, - 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, - }); + #[command(App::confirm)] + #[keyword("confirm")] + Confirm, + + #[command(App::inc)] + #[keyword("x/inc", ControlAxis::X)] + #[keyword("y/inc", ControlAxis::Y)] + Inc(ControlAxis), + + #[command(App::dec)] + #[keyword("x/dec", ControlAxis::X)] + #[keyword("y/dec", ControlAxis::Y)] + Dec(ControlAxis), + + #[command(App::set_dialog | Self::SetDialog)] + #[keyword("dialog")] + SetDialog(Dialog), } + impl App { + fn nop (&mut self) -> Perhaps { + Ok(None) + } + fn cancel (&mut self) -> Perhaps { + todo!() + } + } + + //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. /// /// ``` @@ -1107,6 +1109,52 @@ mod app { //take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +} + +pub use self::device::*; +mod device { + use crate::*; + + impl App { + /// FIXME: generalize. Set picked device in device pick dialog. + /// + /// ``` + /// tek::App::default().device_pick(0); + /// ``` + pub fn device_pick (&mut self, index: usize) { + self.dialog = Dialog::Device(index); + } + + /// FIXME: generalize. Add device to current track. + pub fn add_device (&mut self, index: usize) -> Usually<()> { + match index { + 0 => { + let name = self.jack.with_client(|c|c.name().to_string()); + let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); + let track = self.track().expect("no active track"); + let port = format!("{}/Sampler", &track.name); + let connect = Connect::exact(format!("{name}:{midi}")); + let sampler = if let Ok(sampler) = Sampler::new( + &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] + ) { + self.dialog = Dialog::None; + Device::Sampler(sampler) + } else { + self.dialog = Dialog::Message("Failed to add device.".into()); + return Err("failed to add device".into()) + }; + let track = self.track_mut().expect("no active track"); + track.devices.push(sampler); + Ok(()) + }, + 1 => { + todo!(); + //Ok(()) + }, + _ => unreachable!(), + } + } + } impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } } @@ -1158,12 +1206,12 @@ mod app { _ => { panic!("{event:?}"); } } } -} -pub use self::device::*; -mod device { - use crate::*; - def_command!(DeviceCommand: |device: Device| {}); + #[tek_proc::command(Device)] + #[tek_proc::keyword(Device)] + #[derive(Debug)] + pub enum DeviceCommand {} + impl Device { pub fn name (&self) -> &str { match self { @@ -1196,6 +1244,7 @@ mod device { } } } + /// A device that can be plugged into the chain. /// /// ``` @@ -1274,26 +1323,6 @@ mod device { #[cfg(feature = "plugin")] pub mod plugin; #[cfg(feature = "plugin")] pub use self::plugin::*; - def_command!(AudioInputCommand: |port: AudioInput| { - Close => todo!(), - Connect { audio_out: Arc } => todo!(), - }); - - def_command!(AudioOutputCommand: |port: AudioOutput| { - Close => todo!(), - Connect { audio_in: Arc } => todo!(), - }); - - def_command!(MidiInputCommand: |port: MidiInput| { - Close => todo!(), - Connect { midi_out: Arc } => todo!(), - }); - - def_command!(MidiOutputCommand: |port: MidiOutput| { - Close => todo!(), - Connect { midi_in: Arc } => todo!(), - }); - pub struct Junction(T); impl View for Junction { @@ -1301,36 +1330,38 @@ mod device { T::KIND } } -} -pub fn print_status (project: &Arrangement) { - println!("Name: {:?}", &project.name); - println!("JACK: {:?}", &project.jack); - println!("Buffer: {:?}", &project.clock.chunk); - println!("Sample rate: {:?}", &project.clock.timebase.sr); - println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); - println!("Tempo: {:?}", &project.clock.timebase.bpm); - println!("Quantize: {:?}", &project.clock.quant); - println!("Launch: {:?}", &project.clock.sync); - println!("Playhead: {:?}us", &project.clock.playhead.usec); - println!("Playhead: {:?}s", &project.clock.playhead.sample); - println!("Playhead: {:?}p", &project.clock.playhead.pulse); - println!("Started: {:?}", &project.clock.started); - println!("Tracks:"); - for (i, t) in project.tracks.iter().enumerate() { - println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, - &t.sequencer.play_clip, &t.sequencer.next_clip); + #[tek_proc::command(AudioInput)] + #[tek_proc::keyword(AudioInput)] + #[derive(Debug)] + pub enum AudioInputCommand { + Close, + Connect(Arc), } - println!("Scenes:"); - for (i, t) in project.scenes.iter().enumerate() { - println!(" Scene {i}: {} {:?}", &t.name, &t.clips); + + #[tek_proc::command(AudioOutput)] + #[tek_proc::keyword(AudioOutput)] + #[derive(Debug)] + pub enum AudioOutputCommand { + Close, + Connect(Arc), + } + + #[tek_proc::command(MidiInput)] + #[tek_proc::keyword(MidiInput)] + #[derive(Debug)] + pub enum MidiInputCommand { + Close, + Connect(Arc), + } + + #[tek_proc::command(MidiOutput)] + #[tek_proc::keyword(MidiOutput)] + #[derive(Debug)] + pub enum MidiOutputCommand { + Close, + Connect(Arc), } - println!("MIDI Ins: {:?}", &project.midi_ins); - println!("MIDI Outs: {:?}", &project.midi_outs); - println!("Audio Ins: {:?}", &project.audio_ins); - println!("Audio Outs: {:?}", &project.audio_outs); - // TODO git integration - // TODO dawvert integration } //pub fn tui ( @@ -1354,26 +1385,6 @@ pub fn print_status (project: &Arrangement) { //})?)? //} -/// Define an enum containing commands, and implement [Command] trait for over given `State`. -#[macro_export] macro_rules! def_command ( - ($Command:ident: |$state:ident: $State:ty| { - // FIXME: support attrs (docstrings) - $($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)? - })=>{ - #[derive(Debug)] pub enum $Command { - // FIXME: support attrs (docstrings) - $($Variant $({ $($arg: $Arg),* })?),* - } - impl ::tengri::dizzle::Act<$State> for $Command { - fn act (&self, $state: &mut $State) -> Perhaps { - match self { - $(Self::$Variant $({ $($arg),* })? => $body,)* - _ => unimplemented!("Act<{}>: {self:?}", stringify!($State)), - } - } - } - }); - pub use self::draw::*; mod draw { use crate::*; @@ -1958,3 +1969,33 @@ mod draw { } } + +pub fn print_status (project: &Arrangement) { + println!("Name: {:?}", &project.name); + println!("JACK: {:?}", &project.jack); + println!("Buffer: {:?}", &project.clock.chunk); + println!("Sample rate: {:?}", &project.clock.timebase.sr); + println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); + println!("Tempo: {:?}", &project.clock.timebase.bpm); + println!("Quantize: {:?}", &project.clock.quant); + println!("Launch: {:?}", &project.clock.sync); + println!("Playhead: {:?}us", &project.clock.playhead.usec); + println!("Playhead: {:?}s", &project.clock.playhead.sample); + println!("Playhead: {:?}p", &project.clock.playhead.pulse); + println!("Started: {:?}", &project.clock.started); + println!("Tracks:"); + for (i, t) in project.tracks.iter().enumerate() { + println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, + &t.sequencer.play_clip, &t.sequencer.next_clip); + } + println!("Scenes:"); + for (i, t) in project.scenes.iter().enumerate() { + println!(" Scene {i}: {} {:?}", &t.name, &t.clips); + } + println!("MIDI Ins: {:?}", &project.midi_ins); + println!("MIDI Outs: {:?}", &project.midi_outs); + println!("Audio Ins: {:?}", &project.audio_ins); + println!("Audio Outs: {:?}", &project.audio_outs); + // TODO git integration + // TODO dawvert integration +}