wip: start replacing EdnViewData with EdnProvide

This commit is contained in:
🪞👃🪞 2025-01-12 15:26:37 +01:00
parent fc06fb863b
commit 1ff35baea9
8 changed files with 151 additions and 113 deletions

View file

@ -2,21 +2,51 @@ use crate::*;
use EdnItem::*; use EdnItem::*;
#[macro_export] macro_rules! edn_command { #[macro_export] macro_rules! edn_command {
($Command:ty : |$state:ident:$State:ty| { ($Command:ty : |$state:ident:$State:ty| { $((
$(($key:literal [$($arg:ident : $type:ty),*] $command:expr))* // identifier
}) => { $key:literal [
// named parameters
$(
// argument name
$arg:ident
// if type is not provided defaults to EdnItem
$(
// type:name separator
:
// argument type
$type:ty
)?
),*
// rest of parameters
$(, ..$rest:ident)?
]
// bound command:
$command:expr
))* }) => {
impl EdnCommand<$State> for $Command { impl EdnCommand<$State> for $Command {
fn from_edn <'a> ($state: &$State, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self { fn from_edn <'a> ($state: &$State, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
match (head, tail) { $(if let (EdnItem::Key($key), [ // if the identifier matches
$((EdnItem::Key($key), [$($arg),*]) => { // bind argument ids
$(let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg);)* $($arg),*
$command // bind rest parameters
},)* $(, $rest @ ..)?
_ => panic!("no such command") ]) = (head, tail) {
} $(
$(let $arg: Option<$type> = EdnProvide::<$type>::get($state, $arg);)?
)*
//$(edn_command!(@bind $state => $arg $(?)? : $type);)*
return $command
})*
panic!("no such command")
} }
} }
} };
(@bind $state:ident =>$arg:ident ? : $type:ty) => {
let $arg: Option<$type> = EdnProvide::<$type>::get($state, $arg);
};
(@bind $state:ident => $arg:ident : $type:ty) => {
let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg);
};
} }
/// Turns an EDN symbol sequence into a command enum variant. /// Turns an EDN symbol sequence into a command enum variant.

View file

@ -23,3 +23,12 @@ pub trait EdnProvide<U> {
self.get(edn).expect("no value") self.get(edn).expect("no value")
} }
} }
impl<T: EdnProvide<U>, U> EdnProvide<U> for &T {
fn get <S: AsRef<str>> (&self, edn: &EdnItem<S>) -> Option<U> {
(*self).get(edn)
}
fn get_or_fail <S: AsRef<str>> (&self, edn: &EdnItem<S>) -> U {
(*self).get_or_fail(edn)
}
}

View file

@ -12,9 +12,13 @@ edn_provide!(usize: |self: MidiEditor|{
":time-zoom" => self.time_zoom().get(), ":time-zoom" => self.time_zoom().get(),
}); });
edn_command!(MidiEditCommand: |state: MidiEditor| { edn_command!(MidiEditCommand: |state: MidiEditor| {
("note/put" [a: bool] Self::PutNote) ("note/put" [a: bool] Self::PutNote)
("note/del" [a: bool] Self::PutNote) ("note/del" [a: bool] Self::PutNote)
("note/dur" [a: usize] Self::SetNoteCursor(a)) ("note/pos" [a: usize] Self::SetNoteCursor(a.expect("no note cursor")))
("note/len" [a: usize] Self::SetNoteLength(a.expect("no note length")))
("time/pos" [a: usize] Self::SetTimeCursor(a.expect("no time cursor")))
("time/zoom" [a: usize] Self::SetTimeZoom(a.expect("no time zoom")))
("time/lock" [a: bool] Self::SetTimeLock(a.expect("no time lock")))
}); });
//impl EdnCommand<MidiEditor> for MidiEditCommand { //impl EdnCommand<MidiEditor> for MidiEditCommand {
//fn from_edn <'a> (state: &MidiEditor, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self { //fn from_edn <'a> (state: &MidiEditor, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {

View file

@ -78,10 +78,12 @@ fn match_exp <'a, E: Output + 'a, State: EdnViewData<E>> (
) -> Box<dyn Render<E> + 'a> { ) -> Box<dyn Render<E> + 'a> {
match (head, tail) { match (head, tail) {
(Key("when"), [c, a]) => When(s.get_bool(c.to_ref()), (Key("when"), [c, a]) => When(
s.get_bool(c.to_ref()),
s.get_content(a.to_ref())).boxed(), s.get_content(a.to_ref())).boxed(),
(Key("either"),[c, a, b]) => Either(s.get_bool(c.to_ref()), (Key("either"),[c, a, b]) => Either(
s.get_bool(c.to_ref()),
s.get_content(a.to_ref()), s.get_content(a.to_ref()),
s.get_content(b.to_ref())).boxed(), s.get_content(b.to_ref())).boxed(),

View file

@ -1,7 +1,6 @@
use crate::*; use crate::*;
use EdnItem::*; use EdnItem::*;
use ClockCommand::{Play, Pause}; use ClockCommand::{Play, Pause};
use self::ArrangerCommand as Cmd;
pub const TRACK_MIN_WIDTH: usize = 9; pub const TRACK_MIN_WIDTH: usize = 9;
command!(|self: ClipCommand, state: App|match self { _ => todo!("clip command") }); command!(|self: ClipCommand, state: App|match self { _ => todo!("clip command") });
command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") }); command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") });

View file

@ -2,24 +2,16 @@ use crate::*;
use EdnItem::*; use EdnItem::*;
use ClockCommand::{Play, Pause}; use ClockCommand::{Play, Pause};
use KeyCode::{Tab, Char}; use KeyCode::{Tab, Char};
use SequencerCommand as SeqCmd;
use GrooveboxCommand as GrvCmd;
use ArrangerCommand as ArrCmd;
use SamplerCommand as SmplCmd; use SamplerCommand as SmplCmd;
use MidiEditCommand as EditCmd; use MidiEditCommand as EditCmd;
use PoolClipCommand as PoolCmd; use PoolClipCommand as PoolCmd;
handle!(TuiIn: |self: App, input| Ok(None)); handle!(TuiIn: |self: App, input| Ok(None));
//handle!(TuiIn: |self: Sequencer, input|SequencerCommand::execute_with_state(self, input.event()));
//handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event()));
//handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));
#[derive(Clone, Debug)] pub enum AppCommand { #[derive(Clone, Debug)] pub enum AppCommand {
Clear, Clear,
Clip(ClipCommand), Clip(ClipCommand),
Clock(ClockCommand), Clock(ClockCommand),
Color(ItemPalette), Color(ItemPalette),
Compact(bool), Compact(Option<bool>),
Editor(MidiEditCommand), Editor(MidiEditCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>), Enqueue(Option<Arc<RwLock<MidiClip>>>),
History(isize), History(isize),
@ -29,46 +21,29 @@ handle!(TuiIn: |self: App, input| Ok(None));
Select(ArrangerSelection), Select(ArrangerSelection),
StopAll, StopAll,
Track(TrackCommand), Track(TrackCommand),
Zoom(usize), Zoom(Option<usize>),
}
impl EdnCommand<App> for AppCommand {
fn from_edn <'a> (state: &App, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
match (head, tail) {
(Key("clear"), []) => Self::Clear,
(Key("stop-all"), []) => Self::StopAll,
(Key("color"), [a]) => Self::Color(ItemPalette::default()),
(Key("compact"), [a]) => Self::Compact(true),
(Key("enqueue"), [a]) => Self::Enqueue(None),
(Key("history"), [a]) => Self::History(0),
(Key("select"), [a]) => Self::Select(ArrangerSelection::Mix),
(Key("zoom"), [a]) => Self::Zoom(0),
(Key("clock"), [a, b @ ..]) => Self::Clock(
ClockCommand::from_edn(state, &a.to_ref(), b)),
(Key("track"), [a, b @ ..]) => Self::Track(
TrackCommand::from_edn(state, &a.to_ref(), b)),
(Key("scene"), [a, b @ ..]) => Self::Scene(
SceneCommand::from_edn(state, &a.to_ref(), b)),
(Key("clip"), [a, b @ ..]) => Self::Clip(
ClipCommand::from_edn(state, &a.to_ref(), b)),
(Key("pool"), [a, b @ ..]) if let Some(pool) = state.pool.as_ref() =>
Self::Pool(PoolCommand::from_edn(pool, &a.to_ref(), b)),
(Key("editor"), [a, b @ ..]) if let Some(editor) = state.editor.as_ref() =>
Self::Editor(MidiEditCommand::from_edn(editor, &a.to_ref(), b)),
(Key("sampler"), [a, b @ ..]) if let Some(sampler) = state.sampler.as_ref() =>
Self::Sampler(SamplerCommand::from_edn(sampler, &a.to_ref(), b)),
_ => panic!(),
}
}
} }
edn_command!(AppCommand: |state: App| {
("clear" [] Self::Clear)
("stop-all" [] Self::StopAll)
("compact" [c: bool ] Self::Compact(c))
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
("history" [d: isize] Self::History(d.unwrap_or(0)))
("zoom" [z: usize] Self::Zoom(z))
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b)))
("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b)))
("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b)))
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b)))
("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b)))
("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b)))
("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)))
("select" [s: ArrangerSelection] Self::Select(s.expect("no selection")))
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
});
command!(|self: AppCommand, state: App|match self { command!(|self: AppCommand, state: App|match self {
Self::Clear => { todo!() }, Self::Clear => { todo!() },
Self::Zoom(_) => { todo!(); }, Self::Zoom(_) => { todo!(); },
Self::History(delta) => { todo!("undo/redo") }, Self::History(delta) => { todo!("undo/redo") },
Self::Select(s) => { state.selected = s; None }, Self::Select(s) => { state.selected = s; None },
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
Self::Scene(cmd) => match cmd { Self::Scene(cmd) => match cmd {
@ -111,7 +86,6 @@ command!(|self: AppCommand, state: App|match self {
}, },
_ => None _ => None
}.map(Self::Clip), }.map(Self::Clip),
Self::Editor(cmd) => Self::Editor(cmd) =>
state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
Self::Sampler(cmd) => Self::Sampler(cmd) =>
@ -127,7 +101,6 @@ command!(|self: AppCommand, state: App|match self {
state.color = palette; state.color = palette;
Some(Self::Color(old)) Some(Self::Color(old))
}, },
Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() { Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() {
let undo = cmd.clone().delegate(pool, Self::Pool)?; let undo = cmd.clone().delegate(pool, Self::Pool)?;
if let Some(editor) = state.editor.as_mut() { if let Some(editor) = state.editor.as_mut() {
@ -143,45 +116,60 @@ command!(|self: AppCommand, state: App|match self {
} else { } else {
None None
}, },
Self::Compact(compact) => match compact {
Self::Compact(compact) => if state.compact != compact { Some(compact) => {
state.compact = compact; if state.compact != compact {
Some(Self::Compact(!compact)) state.compact = compact;
} else { Some(Self::Compact(Some(!compact)))
None } else {
}, None
}
},
None => {
state.compact = !state.compact;
Some(Self::Compact(Some(!state.compact)))
}
}
}); });
#[derive(Clone, Debug)] pub enum SequencerCommand {
Compact(bool), ///////////////////////////////////////////////////////////////////////////////////////////////////
History(isize), //handle!(TuiIn: |self: Sequencer, input|SequencerCommand::execute_with_state(self, input.event()));
Clock(ClockCommand), //handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event()));
Pool(PoolCommand), //handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));
Editor(MidiEditCommand), //use SequencerCommand as SeqCmd;
Enqueue(Option<Arc<RwLock<MidiClip>>>), //use GrooveboxCommand as GrvCmd;
} //use ArrangerCommand as ArrCmd;
#[derive(Clone, Debug)] pub enum GrooveboxCommand { //#[derive(Clone, Debug)] pub enum SequencerCommand {
Compact(bool), //Compact(bool),
History(isize), //History(isize),
Clock(ClockCommand), //Clock(ClockCommand),
Pool(PoolCommand), //Pool(PoolCommand),
Editor(MidiEditCommand), //Editor(MidiEditCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>), //Enqueue(Option<Arc<RwLock<MidiClip>>>),
Sampler(SamplerCommand), //}
} //#[derive(Clone, Debug)] pub enum GrooveboxCommand {
#[derive(Clone, Debug)] pub enum ArrangerCommand { //Compact(bool),
History(isize), //History(isize),
Color(ItemPalette), //Clock(ClockCommand),
Clock(ClockCommand), //Pool(PoolCommand),
Scene(SceneCommand), //Editor(MidiEditCommand),
Track(TrackCommand), //Enqueue(Option<Arc<RwLock<MidiClip>>>),
Clip(ClipCommand), //Sampler(SamplerCommand),
Select(ArrangerSelection), //}
Zoom(usize), //#[derive(Clone, Debug)] pub enum ArrangerCommand {
Pool(PoolCommand), //History(isize),
Editor(MidiEditCommand), //Color(ItemPalette),
StopAll, //Clock(ClockCommand),
Clear, //Scene(SceneCommand),
} //Track(TrackCommand),
//Clip(ClipCommand),
//Select(ArrangerSelection),
//Zoom(usize),
//Pool(PoolCommand),
//Editor(MidiEditCommand),
//StopAll,
//Clear,
//}
//command!(|self: SequencerCommand, state: Sequencer|match self { //command!(|self: SequencerCommand, state: Sequencer|match self {
//Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,

View file

@ -94,6 +94,21 @@ has_size!(<TuiOut>|self: App|&self.size);
has_clock!(|self: App|&self.clock); has_clock!(|self: App|&self.clock);
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
has_editor!(|self: App|self.editor.as_ref().expect("no editor")); has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
edn_provide!(u16: |self: App|{
":sample-h" => if self.compact() { 0 } else { 5 },
":samples-w" => if self.compact() { 4 } else { 11 },
":samples-y" => if self.compact() { 1 } else { 0 },
":pool-w" => if self.compact() { 5 } else {
let w = self.size.w();
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
}
});
edn_provide!(Color: |self: App| { _ => return None });
edn_provide!(usize: |self: App| { _ => return None });
edn_provide!(isize: |self: App| { _ => return None });
edn_provide!(bool: |self: App| { _ => return None });
edn_provide!(ArrangerSelection: |self: App| { _ => return None });
edn_provide!(Arc<RwLock<MidiClip>>: |self: App| { _ => return None });
//#[derive(Default)] pub struct Sequencer { //#[derive(Default)] pub struct Sequencer {
//pub jack: Arc<RwLock<JackConnection>>, //pub jack: Arc<RwLock<JackConnection>>,

View file

@ -21,21 +21,11 @@ impl EdnViewData<TuiOut> for &App {
} }
} }
fn get_unit (&self, item: EdnItem<&str>) -> u16 { fn get_unit (&self, item: EdnItem<&str>) -> u16 {
use EdnItem::*; EdnProvide::<u16>::get_or_fail(self, &item)
match item.to_str() {
":sample-h" => if self.compact() { 0 } else { 5 },
":samples-w" => if self.compact() { 4 } else { 11 },
":samples-y" => if self.compact() { 1 } else { 0 },
":pool-w" => if self.compact() { 5 } else {
let w = self.size.w();
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
},
_ => 0
}
} }
} }
impl App { impl App {
fn compact (&self) -> bool { false } pub fn compact (&self) -> bool { false }
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> { fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))) Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock))))
} }
@ -337,6 +327,7 @@ fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiO
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
//render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN))); //render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN)));
//impl EdnViewData<TuiOut> for &Sequencer { //impl EdnViewData<TuiOut> for &Sequencer {
//fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { //fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {