load up to modes

the stacked modal music editor. lol
This commit is contained in:
🪞👃🪞 2025-08-10 14:22:22 +03:00
parent b991a49ad7
commit 4d4c470a81
7 changed files with 220 additions and 205 deletions

View file

@ -69,16 +69,36 @@ pub struct App {
/// Base color.
pub color: ItemTheme,
}
type ModuleMap<T> = Arc<RwLock<BTreeMap<Arc<str>, T>>>;
/// Configuration
#[derive(Default, Debug)]
pub struct Config {
/// XDG basedirs
pub dirs: BaseDirectories,
pub dirs: BaseDirectories,
/// Available view profiles
pub views: ModuleMap<Profile>,
pub views: Arc<RwLock<BTreeMap<Arc<str>, Profile>>>,
/// Available input bindings
pub binds: ModuleMap<EventMap<Option<TuiEvent>, Arc<str>>>,
pub binds: Arc<RwLock<BTreeMap<Arc<str>, EventMap<Option<TuiEvent>, Arc<str>>>>>,
}
/// Profile
#[derive(Default, Debug)]
pub struct Profile {
/// Path of configuration entrypoint
pub path: PathBuf,
/// Default mode
pub mode: Mode,
/// Optional modes
pub modes: BTreeMap<Arc<str>, Mode>,
}
#[derive(Default, Debug)]
pub struct Mode {
/// Name of configuration
pub name: Vec<Arc<str>>,
/// Description of configuration
pub info: Vec<Arc<str>>,
/// View definition
pub view: Vec<Arc<str>>,
// Input keymap
pub keys: Vec<Arc<str>>,
}
impl Config {
const VIEWS: &'static str = "views.edn";
@ -88,16 +108,14 @@ impl Config {
pub fn init () -> Usually<Self> {
let mut cfgs: Self = Default::default();
cfgs.dirs = BaseDirectories::with_profile("tek", "v0");
cfgs.load(Self::VIEWS, Self::DEFAULT_VIEWS, |cfgs, dsl|cfgs.load_view(dsl))?;
cfgs.load(Self::BINDS, Self::DEFAULT_BINDS, |cfgs, dsl|cfgs.load_bind(dsl))?;
println!("{cfgs:#?}");
cfgs.load(Self::VIEWS, Self::DEFAULT_VIEWS, |cfgs, dsl|cfgs.load_view(dsl))?;
Ok(cfgs)
}
pub fn load (
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
) -> Usually<()> {
if self.dirs.find_config_file(path).is_none() {
println!("init: {path}");
std::fs::write(self.dirs.place_config_file(path)?, defaults);
}
Ok(if let Some(path) = self.dirs.find_config_file(path) {
@ -107,15 +125,10 @@ impl Config {
return Err(format!("{path}: not found").into())
})
}
pub fn load_view <D: Dsl> (&mut self, dsl: D) -> Usually<()> {
load_mod(dsl, |id, tail|Ok(self.views.write().unwrap().insert(id.into(),
Profile::from_dsl(tail)?)))
}
pub fn load_bind <D: Dsl> (&mut self, dsl: D) -> Usually<()> {
load_mod(dsl, |id, tail|Ok(self.binds.write().unwrap().insert(id.into(), {
load_modules(dsl, |id, tail|Ok(self.binds.write().unwrap().insert(id.into(), {
let mut map = EventMap::new();
tail.each(|item|{
println!("{item:?}");
map.add(TuiEvent::from_dsl(item.exp()?.head()?)?, Binding {
command: item.exp()?.tail()?.unwrap_or_default().into(),
condition: None,
@ -127,8 +140,59 @@ impl Config {
map
})))
}
pub fn load_view <D: Dsl> (&mut self, dsl: D) -> Usually<()> {
load_modules(&dsl, |id, tail|Ok(self.views.write().unwrap().insert(id.into(), {
let mut profile = Profile::default();
tail.each(|item|Ok(if let Ok(Some(exp)) = item.exp() {
match exp.head()? {
Some("name") => profile.mode.name.push(
exp.tail()?.map(|x|x.trim()).unwrap_or("").into()
),
Some("info") => profile.mode.info.push(
exp.tail()?.map(|x|x.trim()).unwrap_or("").into()
),
Some("keys") => if let Some(tail) = exp.tail()? {
tail.each(|keys|Ok(profile.mode.keys.push(keys.trim().into())))?;
} else {
return Err(format!("load_view: empty keys: {exp}").into())
},
Some("mode") => if let (Some(name), Some(tail)) = (
exp.tail()?.head()?, exp.tail()?.tail()?,
) {
let mut mode: Mode = Default::default();
tail.each(|item|Ok(if let Ok(Some(exp)) = item.exp() {
match exp.head()? {
Some("keys") => if let Some(tail) = exp.tail()? {
tail.each(|keys|Ok(mode.keys.push(keys.trim().into())))?;
} else {
return Err(format!("load_view: empty keys: {exp}").into())
},
_ => {
return Err(format!("load_view: unexpected in mode {name}: {item:?}").into())
}
}
} else if let Ok(Some(sym)) = item.sym() {
// TODO
} else {
return Err(format!("load_view: unexpected in mode {name}: {item:?}").into())
}))?;
profile.modes.insert(name.trim().into(), mode);
} else {
return Err(format!("load_view: empty mode: {exp}").into())
},
Some(_) => profile.mode.view.push(exp.into()),
None => return Err(format!("load_view: empty: {exp}").into())
}
} else if let Ok(Some(sym)) = item.sym() {
profile.mode.view.push(sym.into());
} else {
return Err(format!("load_view: unexpected: {dsl:?}").into())
}))?;
profile
})))
}
}
fn load_mod <D: Dsl, U> (dsl: D, cb: impl Fn(&str, &str)->Usually<U>) -> Usually<()> {
fn load_modules <D: Dsl, U> (dsl: D, cb: impl Fn(&str, &str)->Usually<U>) -> Usually<()> {
//dsl!(dsl|module :id ..body|dsl!(body|@bind #info? ..commands|)
if let Some(exp) = dsl.exp()?
&& Some("module") == exp.head().key()?
@ -142,66 +206,6 @@ fn load_mod <D: Dsl, U> (dsl: D, cb: impl Fn(&str, &str)->Usually<U>) -> Usually
return Err("unexpected: {exp:?}".into());
}
}
/// Profile
#[derive(Default, Debug)]
pub struct Profile {
/// Path of configuration entrypoint
pub path: PathBuf,
/// Name of configuration
pub name: Option<Arc<str>>,
/// Description of configuration
pub info: Option<Arc<str>>,
/// View definition
pub view: Arc<str>,
// Input keymap
pub keys: EventMap<TuiEvent, AppCommand>,
}
impl Profile {
fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
let mut profile = Self { ..Default::default() };
dsl.each(|dsl|{
let head = dsl.head();
let exp = dsl.exp();
Ok(if exp.head().key() == Ok(Some("name")) {
profile.name = Some(exp.tail()?.unwrap_or_default().into());
} else if exp.head().key() == Ok(Some("info")) {
profile.info = Some(exp.tail()?.unwrap_or_default().into());
})
})?;
Ok(profile)
}
}
impl Profile {
fn load_template (&mut self, dsl: impl Dsl) -> Usually<&mut Self> {
//dsl.src()?.unwrap_or_default().each(|item|Ok(match () {
//_ if let Some(exp) = dsl.exp()? => match exp.head()?.key()? {
//Some("name") => match exp.tail()?.text()? {
//Some(name) => self.name = Some(name.into()),
//_ => return Err(format!("missing name definition").into())
//},
//Some("info") => match exp.tail()?.text()? {
//Some(info) => self.info = Some(info.into()),
//_ => return Err(format!("missing info definition").into())
//},
//Some("bind") => match exp.tail()? {
//Some(keys) => self.keys = EventMap::from_dsl(&mut &keys)?,
//_ => return Err(format!("missing keys definition").into())
//},
//Some("view") => match exp.tail()? {
//Some(tail) => self.view = tail.src()?.unwrap_or_default().into(),
//_ => return Err(format!("missing view definition").into())
//},
//dsl => return Err(format!("unexpected: {dsl:?}").into())
//},
//_ => return Err(format!("unexpected: {dsl:?}").into())
//}));
Ok(self)
}
fn load_binding (&mut self, dsl: impl Dsl) -> Usually<&mut Self> {
todo!();
Ok(self)
}
}
/// Various possible dialog modes.
impl App {
pub fn update_clock (&self) {
@ -230,12 +234,6 @@ maybe_has!(Scene: |self: App| { MaybeHas::<Scene>::get(&self.project) };
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
fn unquote (x: &str) -> &str {
let mut chars = x.chars();
chars.next();
//chars.next_back();
chars.as_str()
}
#[derive(Debug, Clone, Default)]
pub enum Dialog {

View file

@ -2,7 +2,14 @@ use crate::*;
#[derive(Debug)]
pub enum AppCommand {}
handle!(TuiIn:|self: App, input|{
panic!("{input:?}");
panic!("wat: {:?}", self.profile);
for keys in self.profile.mode.keys.iter() {
panic!("{keys} {:?}", self.config.binds.read().unwrap());
if let Some(binding) = self.config.binds.read().unwrap().get(keys.as_ref()) {
panic!("{binding:?}");
}
}
Ok(None)
//Ok(if let Some(binding) = self.profile.as_ref()
//.map(|c|c.keys.dispatch(input.event())).flatten()
//{
@ -95,12 +102,14 @@ dsl_exp!(|app: App| -> AppCommand {
["clock" / command: ClockCommand] => todo!(),
["sampler" / command: SamplerCommand] => todo!(),
["pool" / command: PoolCommand] => todo!(),
["pool" / editor: MidiEditCommand] => todo!(),
});
dsl_exp!(|app: App| -> DialogCommand {});
dsl_exp!(|app: App| -> ArrangementCommand {});
dsl_exp!(|app: App| -> ClockCommand {});
dsl_exp!(|app: App| -> SamplerCommand {});
dsl_exp!(|app: App| -> PoolCommand {});
dsl_exp!(|app: App| -> MidiEditCommand {});
//dsl_bind!(AppCommand: App {
//enqueue = |app, clip: Option<Arc<RwLock<MidiClip>>>| { todo!() };
//history = |app, delta: isize| { todo!() };

View file

@ -1,8 +1,23 @@
use crate::*;
/// Namespace. Currently linearly searched.
pub struct DslNs<'t, T: 't>(pub &'t [(&'t str, T)]);
pub trait DslSymNs<'t, T: 't>: 't { const NS: DslNs<'t, fn (&'t Self)->T>; }
/// Namespace where keys are symbols.
pub trait DslSymNs<'t, T: 't>: 't {
const NS: DslNs<'t, fn (&'t Self)->T>;
fn from_sym <D: Dsl> (&'t self, dsl: D) -> Usually<T> {
if let Some(dsl) = dsl.sym()? {
for (sym, get) in Self::NS.0 {
if dsl == *sym {
return Ok(get(self))
}
}
}
return Err(format!("not found: sym: {dsl:?}").into())
}
}
#[macro_export] macro_rules! dsl_sym (
(|$state:ident:$State:ty| -> $type:ty {$($lit:literal => $exp:expr),* $(,)?})=>{
impl<'t> DslSymNs<'t, $type> for $State {

View file

@ -20,8 +20,8 @@ pub const VIEW: DslNs<'static, DslCb> = DslNs(&[
Stack::south(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
for (index, (id, profile)) in views.read().unwrap().iter().enumerate() {
let bg = if index == 0 { Rgb(64,64,64) } else { Rgb(32,32,32) };
let name = profile.name.as_ref().map(|x|unquote(unquote(x.as_ref())));
let info = profile.info.as_ref().map(|x|unquote(unquote(x.as_ref())));
let name = profile.mode.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
let info = profile.mode.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
add(&Fixed::y(3, Tui::bg(bg, Bsp::s(
Fill::x(Bsp::a(
Fill::x(Align::w(Tui::fg(Rgb(224,192,128), name))),