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::*;
#[macro_export] macro_rules! edn_command {
($Command:ty : |$state:ident:$State:ty| {
$(($key:literal [$($arg:ident : $type:ty),*] $command:expr))*
}) => {
($Command:ty : |$state:ident:$State:ty| { $((
// 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 {
fn from_edn <'a> ($state: &$State, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
match (head, tail) {
$((EdnItem::Key($key), [$($arg),*]) => {
$(let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg);)*
$command
},)*
_ => panic!("no such command")
}
$(if let (EdnItem::Key($key), [ // if the identifier matches
// bind argument ids
$($arg),*
// bind rest parameters
$(, $rest @ ..)?
]) = (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.

View file

@ -23,3 +23,12 @@ pub trait EdnProvide<U> {
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(),
});
edn_command!(MidiEditCommand: |state: MidiEditor| {
("note/put" [a: bool] Self::PutNote)
("note/del" [a: bool] Self::PutNote)
("note/dur" [a: usize] Self::SetNoteCursor(a))
("note/put" [a: bool] Self::PutNote)
("note/del" [a: bool] Self::PutNote)
("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 {
//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> {
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(),
(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(b.to_ref())).boxed(),

View file

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

View file

@ -2,24 +2,16 @@ use crate::*;
use EdnItem::*;
use ClockCommand::{Play, Pause};
use KeyCode::{Tab, Char};
use SequencerCommand as SeqCmd;
use GrooveboxCommand as GrvCmd;
use ArrangerCommand as ArrCmd;
use SamplerCommand as SmplCmd;
use MidiEditCommand as EditCmd;
use PoolClipCommand as PoolCmd;
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 {
Clear,
Clip(ClipCommand),
Clock(ClockCommand),
Color(ItemPalette),
Compact(bool),
Compact(Option<bool>),
Editor(MidiEditCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>),
History(isize),
@ -29,46 +21,29 @@ handle!(TuiIn: |self: App, input| Ok(None));
Select(ArrangerSelection),
StopAll,
Track(TrackCommand),
Zoom(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!(),
}
}
Zoom(Option<usize>),
}
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 {
Self::Clear => { todo!() },
Self::Zoom(_) => { todo!(); },
Self::History(delta) => { todo!("undo/redo") },
Self::Select(s) => { state.selected = s; None },
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
Self::Scene(cmd) => match cmd {
@ -111,7 +86,6 @@ command!(|self: AppCommand, state: App|match self {
},
_ => None
}.map(Self::Clip),
Self::Editor(cmd) =>
state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
Self::Sampler(cmd) =>
@ -127,7 +101,6 @@ command!(|self: AppCommand, state: App|match self {
state.color = palette;
Some(Self::Color(old))
},
Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() {
let undo = cmd.clone().delegate(pool, Self::Pool)?;
if let Some(editor) = state.editor.as_mut() {
@ -143,45 +116,60 @@ command!(|self: AppCommand, state: App|match self {
} else {
None
},
Self::Compact(compact) => if state.compact != compact {
state.compact = compact;
Some(Self::Compact(!compact))
} else {
None
},
Self::Compact(compact) => match compact {
Some(compact) => {
if state.compact != compact {
state.compact = compact;
Some(Self::Compact(Some(!compact)))
} else {
None
}
},
None => {
state.compact = !state.compact;
Some(Self::Compact(Some(!state.compact)))
}
}
});
#[derive(Clone, Debug)] pub enum SequencerCommand {
Compact(bool),
History(isize),
Clock(ClockCommand),
Pool(PoolCommand),
Editor(MidiEditCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>),
}
#[derive(Clone, Debug)] pub enum GrooveboxCommand {
Compact(bool),
History(isize),
Clock(ClockCommand),
Pool(PoolCommand),
Editor(MidiEditCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>),
Sampler(SamplerCommand),
}
#[derive(Clone, Debug)] pub enum ArrangerCommand {
History(isize),
Color(ItemPalette),
Clock(ClockCommand),
Scene(SceneCommand),
Track(TrackCommand),
Clip(ClipCommand),
Select(ArrangerSelection),
Zoom(usize),
Pool(PoolCommand),
Editor(MidiEditCommand),
StopAll,
Clear,
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//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()));
//use SequencerCommand as SeqCmd;
//use GrooveboxCommand as GrvCmd;
//use ArrangerCommand as ArrCmd;
//#[derive(Clone, Debug)] pub enum SequencerCommand {
//Compact(bool),
//History(isize),
//Clock(ClockCommand),
//Pool(PoolCommand),
//Editor(MidiEditCommand),
//Enqueue(Option<Arc<RwLock<MidiClip>>>),
//}
//#[derive(Clone, Debug)] pub enum GrooveboxCommand {
//Compact(bool),
//History(isize),
//Clock(ClockCommand),
//Pool(PoolCommand),
//Editor(MidiEditCommand),
//Enqueue(Option<Arc<RwLock<MidiClip>>>),
//Sampler(SamplerCommand),
//}
//#[derive(Clone, Debug)] pub enum ArrangerCommand {
//History(isize),
//Color(ItemPalette),
//Clock(ClockCommand),
//Scene(SceneCommand),
//Track(TrackCommand),
//Clip(ClipCommand),
//Select(ArrangerSelection),
//Zoom(usize),
//Pool(PoolCommand),
//Editor(MidiEditCommand),
//StopAll,
//Clear,
//}
//command!(|self: SequencerCommand, state: Sequencer|match self {
//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_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
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 {
//pub jack: Arc<RwLock<JackConnection>>,

View file

@ -21,21 +21,11 @@ impl EdnViewData<TuiOut> for &App {
}
}
fn get_unit (&self, item: EdnItem<&str>) -> u16 {
use EdnItem::*;
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
}
EdnProvide::<u16>::get_or_fail(self, &item)
}
}
impl App {
fn compact (&self) -> bool { false }
pub fn compact (&self) -> bool { false }
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
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)));
//impl EdnViewData<TuiOut> for &Sequencer {
//fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {