rework command/namespace dispatch

This commit is contained in:
i do not exist 2026-08-12 11:32:51 +03:00
parent def7a1b210
commit 406aabde6e
15 changed files with 586 additions and 374 deletions

View file

@ -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" }

View file

@ -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:

View file

@ -48,7 +48,7 @@ attribute!(commands {
impl Parse for CustomAttributeMeta {
/// Parse contents of `#[command(...)]` attribute tag.
fn parse (input: ParseStream) -> Result<Self> {
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<Self> {
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<Self> {
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<LitStr>,
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<Self> {
match self {
#disp
_ => unreachable!()
}
}
}
}
}
fn write_for_trait (
syms: TokenStream2,
exps: TokenStream2,
disp: TokenStream2,
state: &Path,
command: &Path,
) -> TokenStream2 {
quote! {
impl<T: #state> dizzle::Namespaced<T> for #command {
fn namespaced_symbol <L: Symbol> (state: &T, word: L) -> Perhaps<#command> {
if let Some(word) = word.word()? {
#syms
}
Ok(None)
}
fn namespaced_expression <L: 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<T: #state> dizzle::Dispatch<T> for #command {
fn dispatch (self, state: &mut T) -> Perhaps<Self> {
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, });
}
})
}

View file

@ -9,6 +9,7 @@
pkgs.grcov
pkgs.libclang
pkgs.mold
pkgs.perf
pkgs.pkg-config
pkgs.watchexec
];

View file

@ -73,32 +73,32 @@ pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
}
impl<T: HasScene
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Arc<str>>
+ for<'a> Namespace<'a, ItemTheme>
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
> SceneController for T {}
#[tek_proc::commands(SceneCommand)]
pub trait SceneController: HasScene
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Arc<str>>
+ for<'a> Namespace<'a, ItemTheme>
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
{
#[command(SetSize = "scene/size")]
fn scene_set_size (&mut self, size: usize) -> Perhaps<SceneCommand>
where Self: for<'a> Namespace<'a, usize>
where Self: Namespace<usize>
{
todo!()
}
#[command(SetZoom = "scene/zoom")]
fn scene_set_zoom (&mut self, size: usize) -> Perhaps<SceneCommand>
where Self: for<'a> Namespace<'a, usize>
where Self: Namespace<usize>
{
todo!()
}
#[command(SetName = "scene/name")]
fn scene_set_name (&mut self, name: Arc<str>) -> Perhaps<SceneCommand>
where Self: for<'a> Namespace<'a, Arc<str>>
where Self: Namespace<Arc<str>>
{
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<SceneCommand>
where Self: for<'a> Namespace<'a, ItemTheme>
where Self: Namespace<ItemTheme>
{
Ok(self.scene_mut().map(|scene|swap_value(
&mut scene.color,
@ -172,16 +172,16 @@ pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
}
impl<T: HasScenes
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Option<ItemTheme>>
+ for<'a> Namespace<'a, Option<Arc<str>>>
+ Namespace<usize>
+ Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<str>>>
> ScenesController for T {}
#[tek_proc::commands(ScenesCommand)]
pub trait ScenesController: HasScenes
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Option<ItemTheme>>
+ for<'a> Namespace<'a, Option<Arc<str>>>
+ Namespace<usize>
+ Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<str>>>
{
// TODO
}

View file

@ -85,18 +85,18 @@ pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
}
impl<T: HasTrack
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Arc<str>>
+ for<'a> Namespace<'a, ItemTheme>
+ for<'a> Namespace<'a, Option<bool>>
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
+ Namespace<Option<bool>>
> TrackController for T {}
#[tek_proc::commands(TrackCommand)]
pub trait TrackController: HasTrack
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Arc<str>>
+ for<'a> Namespace<'a, ItemTheme>
+ for<'a> Namespace<'a, Option<bool>>
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
+ Namespace<Option<bool>>
{
#[command(Stop = "track/stop")]
fn track_stop (&mut self) -> Perhaps<TrackCommand> {
@ -259,27 +259,31 @@ pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrack
}
impl<T: HasTracks
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Arc<[Connect]>>
+ for<'a> Namespace<'a, Option<usize>>
+ for<'a> Namespace<'a, Option<Arc<str>>>
+ for<'a> Namespace<'a, Option<ItemTheme>>
+ for<'a> Namespace<'a, Option<Arc<[Connect]>>>
+ for<'a> Namespace<'a, Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
+ HasScenes
+ HasJack<'static>
+ Namespace<usize>
+ Namespace<Arc<[Connect]>>
+ Namespace<Option<usize>>
+ Namespace<Option<Arc<str>>>
+ Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<[Connect]>>>
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
> 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<usize>>
+ for<'a> Namespace<'a, Option<Arc<str>>>
+ for<'a> Namespace<'a, Option<ItemTheme>>
+ for<'a> Namespace<'a, Option<Arc<[Connect]>>>
+ for<'a> Namespace<'a, Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
+ HasScenes
+ HasJack<'static>
+ Namespace<usize>
+ Namespace<Arc<[Connect]>>
+ Namespace<Option<usize>>
+ Namespace<Option<Arc<str>>>
+ Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<[Connect]>>>
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
{
#[command(Stop = "tracks/stop")]
#[command(Stop = "stop")]
/// Stop all playing clips
fn tracks_stop_all (&mut self) -> Perhaps<TracksCommand> {
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<Vec<Option<Arc<RwLock<MidiClip>>>>>
@ -304,6 +308,14 @@ pub trait TracksController: HasTracks
}
Ok(None)
}
#[command(Add = "add")]
fn tracks_add (&mut self) -> Perhaps<TracksCommand>
where Self: HasScenes + HasJack<'static>
{
let (index, _) = self.tracks_add_one(None, None, [].into(), [].into())?;
Ok(None)
}
}
impl<

View file

@ -11,11 +11,17 @@ impl App {
}
}
impl<T:
Namespace<usize> +
Namespace<PathBuf> +
Namespace<Arc<str>>
> 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<str>>
Namespace<usize> +
Namespace<PathBuf> +
Namespace<Arc<str>>
{
/// Toggle visibility of browser
#[command(Show = "show")]

View file

@ -66,16 +66,16 @@ pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
}
impl<T: HasClock
+ for<'a> Namespace<'a, u32>
+ for<'a> Namespace<'a, f64>
+ for<'a> Namespace<'a, Option<u32>>
+ Namespace<u32>
+ Namespace<f64>
+ Namespace<Option<u32>>
> 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<u32>>
+ Namespace<u32>
+ Namespace<f64>
+ Namespace<Option<u32>>
{
#[command(SeekUsec = "usec")]
fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCommand> {
@ -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<Tui> {
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<Tui> {
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<Tui> {
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)),
)
)
}

View file

@ -67,22 +67,22 @@ pub trait HasEditor: AsRefOpt<MidiEditor> + AsMutOpt<MidiEditor> {
impl<T: AsRefOpt<MidiEditor>+AsMutOpt<MidiEditor>> HasEditor for T {}
impl<T: 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<u32>>
+ for<'a> Namespace<'a, Option<Arc<RwLock<MidiClip>>>>
+ Namespace<u32>
+ Namespace<f64>
+ Namespace<bool>
+ Namespace<usize>
+ Namespace<Option<u32>>
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
> 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<u32>>
+ for<'a> Namespace<'a, Option<Arc<RwLock<MidiClip>>>>
+ Namespace<u32>
+ Namespace<f64>
+ Namespace<bool>
+ Namespace<usize>
+ Namespace<Option<u32>>
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
{
#[command(Show = "show")]
fn show (&mut self, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<MidiEditCommand> {

View file

@ -70,28 +70,24 @@ pub trait HasPool: AsRef<Pool> + AsMut<Pool> {
}
impl<T: HasPool
+ for<'a> Namespace<'a, bool>
+ for<'a> Namespace<'a, usize>
+ for<'a> Namespace<'a, Arc<str>>
+ 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<bool>
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<PathBuf>
+ Namespace<MidiClip>
+ Namespace<ItemColor>
+ Namespace<BrowseCommand>
> 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<str>>
+ 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<bool>
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<PathBuf>
+ Namespace<MidiClip>
+ Namespace<ItemColor>
+ Namespace<BrowseCommand>
{
#[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

View file

@ -21,7 +21,7 @@ impl<T: AsRef<Sampler> + AsMut<Sampler>> HasSampler for T {}
#[tek_proc::commands(SamplerCommand = "sampler")]
pub trait SamplerController: HasSampler
+ for<'a> Namespace<'a, usize>
+ Namespace<usize>
{
#[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)
}

View file

@ -245,15 +245,21 @@ pub trait HasMidiClip {
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
}
impl HasMidiClip for App {
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
None
}
}
impl<T: HasMidiClip
+ for<'a> Namespace<'a, Option<bool>>
+ for<'a> Namespace<'a, Option<ItemTheme>>
+ Namespace<Option<bool>>
+ Namespace<Option<ItemTheme>>
> MidiClipController for T {}
#[tek_proc::commands(MidiClipCommand)]
pub trait MidiClipController: HasMidiClip
+ for<'a> Namespace<'a, Option<bool>>
+ for<'a> Namespace<'a, Option<ItemTheme>>
+ Namespace<Option<bool>>
+ Namespace<Option<ItemTheme>>
{
#[command(SetColor = "clip/color")]

View file

@ -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)

View file

@ -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<str> 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<u7> App::get_opt_u7)]
#[namespace(Option<u16> App::get_opt_u16)]
#[namespace(Option<usize> App::get_opt_usize)]
#[namespace(Option<Arc<RwLock<MidiClip>>> 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<Arc<RwLock<MidiClip>>> App::get_clip)]
#[namespace(Option<Arc<[Connect]>> App::get_opt_arc_array_connect)]
#[namespace(Option<Arc<str>> App::get_opt_arc_str)]
#[namespace(Option<ItemTheme> App::get_opt_itemtheme)]
#[namespace(Option<Vec<Option<Arc<RwLock<MidiClip>>>>> App::get_opt_vec_opt_arc_rwlock_midiclip)]
#[namespace(Option<u16> App::get_opt_u16)]
#[namespace(Option<u7> App::get_opt_u7)]
#[namespace(Option<usize> App::get_opt_usize)]
#[namespace(Option<bool> 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<PathBuf> {
todo!()
}
fn get_arc_str (&self, src: impl Language) -> Perhaps<Arc<str>> {
Ok(src.src()?.map(|x|x.into()))
}
@ -827,12 +840,34 @@ mod app {
fn get_axis (&self, src: impl Language) -> Perhaps<ControlAxis> {
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<Option<ItemTheme>> {
todo!()
}
fn get_opt_vec_opt_arc_rwlock_midiclip (&self, src: impl Language) -> Perhaps<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>> {
todo!()
}
fn get_opt_arc_array_connect (&self, src: impl Language) -> Perhaps<Option<Arc<[Connect]>>> {
todo!()
}
fn get_arc_array_connect (&self, src: impl Language) -> Perhaps<Arc<[Connect]>> {
todo!()
}
fn get_opt_arc_str (&self, src: impl Language) -> Perhaps<Option<Arc<str>>> {
todo!()
}
fn get_opt_bool (&self, src: impl Language) -> Perhaps<Option<bool>> {
todo!()
}
}
}
@ -879,56 +914,171 @@ mod bind {
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)]
pub struct Condition(
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
);
pub struct Condition(pub Arc<Box<dyn Fn()->bool + 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<Vec<AppCommand>>
{
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<AppCommand> {
commands.push(command)
if Self::command::<AppCommand>(self, command)? {
break
}
}
}
}
}
Ok::<_, Box<dyn Error>>(())
}).transpose()?;
Ok(commands)
}
Ok(())
});
impl App {
fn command <
T: Dispatch<Self> + Clone + Into<AppCommand>
> (state: &mut Self, src: &str) -> Usually<bool> where
Self: Namespace<T>,
{
if let Some(command) = Namespace::<T>::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<AppCommand>)
-> Usually<Vec<(AppCommand, Option<AppCommand>)>>
{
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<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Browse = "browse")]
pub fn command_browse (&mut self, command: BrowseCommand) -> Perhaps<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Dialog = "dialog")]
pub fn command_dialog (&mut self, command: DialogCommand) -> Perhaps<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(MidiClip = "clip")]
pub fn command_midi_clip (&mut self, command: MidiClipCommand) -> Perhaps<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Pool = "pool")]
pub fn command_pool (&mut self, command: PoolCommand) -> Perhaps<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Scene = "scene")]
pub fn command_scene (&mut self, command: SceneCommand) -> Perhaps<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Scenes = "scenes")]
pub fn command_scenes (&mut self, command: ScenesCommand) -> Perhaps<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Track = "track")]
pub fn command_track (&mut self, command: TrackCommand) -> Perhaps<AppCommand> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Tracks = "tracks")]
pub fn command_tracks (&mut self, command: TracksCommand) -> Perhaps<AppCommand> {
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<DialogCommand> {
todo!()
}
/// Confirm current dialog selection.
#[command(Confirm = "confirm")]
pub fn confirm (&mut self) -> Perhaps<DialogCommand> {
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<DialogCommand> {
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<AxisCommand> {
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<AxisCommand> {
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<str>, 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<AppCommand> {
Ok(None)
}
#[command(Cancel = "cancel")]
pub fn cancel (&mut self) -> Perhaps<AppCommand> {
todo!()
}
#[command(Inc = "inc")]
pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AppCommand> {
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<AppCommand> {
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<AppCommand> {
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<AppCommand> {
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<Tui> {
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<Tui, XYWH<u16>> for App {
fn keywords () -> impl Iterator<Item = fn(&Self, &mut Tui, &str) -> Perhaps<XYWH<u16>>> {
[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<Tui> {
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<Tui> {
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<Tui> {
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<Tui> {
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;

2
tengri

@ -1 +1 @@
Subproject commit b7f4d55e1d67d3481ecee14f693ca3e0a9426a6c
Subproject commit 1f541407597c7866cf1450d03967ab4711d28d29