diff --git a/Justfile b/Justfile index 475359ee..86e1bf36 100644 --- a/Justfile +++ b/Justfile @@ -1,4 +1,5 @@ export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace" +export RUST_BACKTRACE := "1" debug := "reset && cargo run --" release := "reset && cargo run --release --" @@ -51,6 +52,8 @@ ftpush: run: {{debug}} +run-init: + rm -rf ~/.config/tek && {{debug}} release: {{release}} build-release: diff --git a/crates/app/app.rs b/crates/app/app.rs index 5e2c5495..73b89907 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -63,7 +63,8 @@ pub struct App { pub struct Config { pub dirs: BaseDirectories, pub modes: Arc, Arc>>>>>, - pub binds: Arc, EventMap, Arc>>>>, + pub views: Arc, Arc>>>, + pub binds: Arc, EventMap>>>>, } #[derive(Default, Debug)] pub struct Mode { @@ -91,7 +92,7 @@ impl Config { pub fn init () -> Usually { let mut cfgs: Self = Default::default(); cfgs.dirs = BaseDirectories::with_profile("tek", "v0"); - cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load_defs(dsl))?; + cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?; Ok(cfgs) } pub fn init_file ( @@ -109,61 +110,76 @@ impl Config { return Err(format!("{path}: not found").into()) }) } - pub fn load_defs (&mut self, dsl: impl Dsl) -> Usually<()> { - dsl.each(|item|{ - match item.exp().head() { - Ok(Some("keys")) if let Some(id) = item.exp().tail().head()? => - self.load_bind(id.into(), item), - Ok(Some("mode")) if let Some(id) = item.exp().tail().head()? => - self.load_mode(id.into(), item), - _ => return Err(format!("load_defs: unexpected: {item:?}").into()) + pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> { + dsl.each(|item|if let Some(expr) = item.expr()? { + let head = expr.head()?; + let tail = expr.tail()?; + let name = tail.head()?; + let body = tail.tail()?; + println!("{} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); + match head { + Some("view") if let Some(name) = name => self.load_view(name.into(), body), + Some("keys") if let Some(name) = name => self.load_bind(name.into(), body), + Some("mode") if let Some(name) = name => self.load_mode(name.into(), body), + _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) } + } else { + return Err(format!("Config::load: expected expr, got: {item:?}").into()) }) } - pub fn load_bind (&mut self, id: Arc, item: impl Dsl) -> Usually<()> { + pub fn load_view (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { + self.views.write().unwrap().insert(id, dsl.src()?.unwrap_or_default().into()); + Ok(()) + } + pub fn load_bind (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { let mut map = EventMap::new(); - item.exp().tail().tail()?.each(|item|Self::load_bind_one(&mut map, item))?; + dsl.each(|item|if item.expr().head() == Ok(Some("see")) { + // TODO + Ok(()) + } else if let Ok(Some(word)) = item.expr().head().word() { + if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? { + map.add(key, Binding { + commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(), + condition: None, + description: None, + source: None + }); + Ok(()) + } else if Some(":char") == item.expr()?.head()? { + // TODO + return Ok(()) + } else { + return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into()) + } + } else { + return Err(format!("Config::load_bind: unexpected: {item:?}").into()) + })?; self.binds.write().unwrap().insert(id, map); Ok(()) } - fn load_bind_one (map: &mut EventMap, Arc>, item: impl Dsl) -> Usually<()> { - if let Ok(Some(sym)) = item.exp().head().sym() { - map.add(TuiEvent::from_dsl(item.exp()?.head()?)?, Binding { - command: item.exp()?.tail()?.unwrap_or_default().into(), - condition: None, - description: None, - source: None - }); - } else if item.exp().head() == Ok(Some("see")) { - // TODO - } else { - return Err(format!("load_defs: unexpected: {item:?}").into()) - } - Ok(()) - } - pub fn load_mode (&mut self, id: Arc, item: impl Dsl) -> Usually<()> { + pub fn load_mode (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { let mut mode = Mode::default(); - item.exp().tail().tail()?.each(|item|Self::load_mode_one(&mut mode, item))?; + dsl.each(|item|Self::load_mode_one(&mut mode, item))?; self.modes.write().unwrap().insert(id.into(), Arc::new(mode)); Ok(()) } pub fn load_mode_one (mode: &mut Mode>, item: impl Dsl) -> Usually<()> { - Ok(if let Ok(Some(key)) = item.exp().head() { + Ok(if let Ok(Some(key)) = item.expr().head() { match key { - "name" => mode.name.push(item.exp()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "info" => mode.info.push(item.exp()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "keys" => mode.keys.push(item.exp()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "mode" => if let Some(id) = item.exp()?.tail()?.head()? { + "name" => mode.name.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "info" => mode.info.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "keys" => mode.keys.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "mode" => if let Some(id) = item.expr()?.tail()?.head()? { let mut submode = Mode::default(); - Self::load_mode_one(&mut submode, item.exp()?.tail()?.tail()?)?; + Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?; mode.modes.insert(id.into(), submode); } else { return Err(format!("load_mode_one: incomplete: {item:?}").into()); }, - _ => mode.view.push(item.exp()?.unwrap().into()), + _ => mode.view.push(item.expr()?.unwrap().into()), } - } else if let Ok(Some(sym)) = item.sym() { - mode.view.push(sym.into()); + } else if let Ok(Some(word)) = item.word() { + mode.view.push(word.into()); } else { return Err(format!("load_mode_one: unexpected: {item:?}").into()); }) @@ -210,20 +226,24 @@ fn render_dsl <'t> ( state: &'t impl DslNs<'t, Box>>, src: &str ) -> Box> { - let sym_err: Option> = match state.from_sym(src) { + let err: Option> = match state.from(src) { Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), }; - let exp_err = match state.from_exp(src) { - Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), - }; - let (err_fg_1, err_bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0)); - let (err_fg_2, err_bg_2) = (Color::Rgb(250, 200, 120), Color::Rgb(32, 0, 0)); - Box::new(Fill::x(col! { - Fill::x(Margin::x(1, Align::w(Tui::bold(true, Tui::fg_bg(err_fg_1, err_bg_1, "Could not render:"))))), - Fill::x(Margin::x(1, Tui::fg_bg(err_fg_2, err_bg_2, format!("{src}")))), - Fill::x(Margin::x(1, Tui::fg_bg(err_fg_2, err_bg_2, format!("{sym_err:?}")))), - Fill::x(Margin::x(1, Tui::fg_bg(err_fg_2, err_bg_2, format!("{exp_err:?}")))), - })) + let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0)); + let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0)); + let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0)); + let bg = Color::Rgb(24, 0, 0); + Box::new(col! { + Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))), + Tui::bg(bg, col! { + Fill::x(Bsp::e( + Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")), + Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))), + )), + Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))), + }), + Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))), + }) } fn wrap_dialog (dialog: impl Content) -> impl Content { @@ -242,22 +262,19 @@ impl ScenesView for App { } } handle!(TuiIn:|self: App, input|{ - panic!("wat: {:?}", self.mode); - for keys in self.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:?}"); + for id in self.mode.keys.iter() { + if let Some(event_map) = self.config.binds.read().unwrap().get(id.as_ref()) { + if let Some(bindings) = event_map.query(input.event()) { + for binding in bindings { + panic!("{binding:?}"); + } + } } } Ok(None) }); #[derive(Debug)] pub enum AppCommand { /* TODO */ } -impl App { - pub fn editor_focused (&self) -> bool { - false - } -} impl Dialog { pub fn menu_selected (&self) -> Option { if let Self::Menu(selected) = self { Some(*selected) } else { None } @@ -293,6 +310,9 @@ impl Dialog { } impl App { + pub fn editor_focused (&self) -> bool { + false + } pub fn toggle_dialog (&mut self, mut dialog: Dialog) -> Dialog { std::mem::swap(&mut self.dialog, &mut dialog); dialog @@ -371,28 +391,144 @@ impl App { } } -dsl_ns!(|app: App| +macro_rules!dsl_words((|$state:ident|$({ + $($word:literal => $body:expr),* $(,)? +})?)=>{ + const WORDS: DslNsMap<'t, fn (&'t Self)->Perhaps>>> = DslNsMap::new(&[$( + $(($word, |$state: &Self|{Ok(Some($body))})),* + )? ]); +}); +macro_rules!dsl_exprs((|$state:ident|$({ + $($name:literal ($($arg:ident:$ty:ty),* $(,)?) => $body:expr),* $(,)? +})?)=>{ + const EXPRS: DslNsMap<'t, fn (&'t Self, &str)->Perhaps>>> = DslNsMap::new(&[$( + $(($name, |$state: &Self, tail: &str|{ + $( + let head = tail.head()?.unwrap_or_default(); + let tail = tail.tail()?.unwrap_or_default(); + let $arg: $ty = if let Some(arg) = $state.from(&head)? { + arg + } else { + return Err(format!("{}: arg \"{}\" ({}) got: {head}, remaining: {tail}", + $name, + stringify!($arg), + stringify!($ty), + ).into()) + }; + )* + Ok(Some($body)) + })),* + )? ]); +}); - u8; +impl<'t> DslNs<'t, Box>> for App { + dsl_exprs!(|app|{ + "fg" (color: Color, x: Box>) => Box::new(Tui::fg(color, x)), + "bg" (color: Color, x: Box>) => Box::new(Tui::bg(color, x)), + "fg/bg" (fg: Color, bg: Color, x: Box>) => Box::new(Tui::fg_bg(fg, bg, x)), - isize; + "bsp/n" (a: Box>, b: Box>) => Box::new(Bsp::n(a, b)), + "bsp/s" (a: Box>, b: Box>) => Box::new(Bsp::s(a, b)), + "bsp/e" (a: Box>, b: Box>) => Box::new(Bsp::e(a, b)), + "bsp/w" (a: Box>, b: Box>) => Box::new(Bsp::w(a, b)), + "bsp/a" (a: Box>, b: Box>) => Box::new(Bsp::a(a, b)), + "bsp/b" (a: Box>, b: Box>) => Box::new(Bsp::b(a, b)), - ItemTheme => { - ":_theme_stub" => Default::default() - }; + "align/n" (x: Box>) => Box::new(Align::n(x)), + "align/s" (x: Box>) => Box::new(Align::s(x)), + "align/e" (x: Box>) => Box::new(Align::e(x)), + "align/w" (x: Box>) => Box::new(Align::w(x)), + "align/x" (x: Box>) => Box::new(Align::x(x)), + "align/y" (x: Box>) => Box::new(Align::y(x)), + "align/c" (x: Box>) => Box::new(Align::c(x)), - u16 => { - ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), - ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), - }; + "fill/x" (x: Box>) => Box::new(Fill::x(x)), + "fill/y" (x: Box>) => Box::new(Fill::y(x)), + "fill/xy" (x: Box>) => Box::new(Fill::xy(x)), - usize => { - ":scene-count" => app.scenes().len(), - ":track-count" => app.tracks().len(), - ":device-kind" => app.dialog.device_kind().unwrap_or(0), - ":device-kind/next" => app.dialog.device_kind_next().unwrap_or(0), - ":device-kind/prev" => app.dialog.device_kind_prev().unwrap_or(0), - }; + "fixed/x" (x: u16, c: Box>) => Box::new(Fixed::x(x, c)), + "fixed/y" (y: u16, c: Box>) => Box::new(Fixed::y(y, c)), + "fixed/xy" (x: u16, y: u16, c: Box>) => Box::new(Fixed::xy(x, y, c)), + + "min/x" (x: u16, c: Box>) => Box::new(Min::x(x, c)), + "min/y" (y: u16, c: Box>) => Box::new(Min::y(y, c)), + "min/xy" (x: u16, y: u16, c: Box>) => Box::new(Min::xy(x, y, c)), + + "max/x" (x: u16, c: Box>) => Box::new(Max::x(x, c)), + "max/y" (y: u16, c: Box>) => Box::new(Max::y(y, c)), + "max/xy" (x: u16, y: u16, c: Box>) => Box::new(Max::xy(x, y, c)), + }); + /// Resolve an expression if known. + fn from_expr (&'t self, dsl: D) -> Perhaps>> { + if let Some(head) = dsl.expr().head()? { + for (key, value) in Self::EXPRS.0.iter() { + if head == *key { + return value(self, dsl.expr().tail()?.unwrap_or("")) + } + } + } + return Ok(None) + } + dsl_words!(|app|{ + ":templates" => Box::new({ + let modes = app.config.modes.clone(); + let height = (modes.read().unwrap().len() * 2) as u16; + Min::x(30, Fixed::y(height, Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { + let bg = if index == 0 { Rgb(48,64,32) } else { Rgb(16, 32, 24) }; + let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or(""); + let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); + let fg1 = Rgb(224, 192, 128); + let fg2 = Rgb(224, 128, 32); + add(&Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s( + Bsp::a(Fill::x(Align::w(Tui::fg(fg1, name))), + Fill::x(Align::e(Tui::fg(fg2, id)))), + Align::w(info)))))); + }})))}), + ":sessions" => Box::new(Min::x(30, Fixed::y(6, Stack::south( + move|add: &mut dyn FnMut(&dyn Render)|{ + let fg = Rgb(224, 192, 128); + for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { + let bg = if index == 0 { Rgb(48,64,32) } else { Rgb(16, 32, 24) }; + add(&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))); + } + } + )))), + ":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(), + match app.dialog.browser_target().unwrap() { + BrowserTarget::SaveProject => "Save project:", + BrowserTarget::LoadProject => "Load project:", + BrowserTarget::ImportSample(_) => "Import sample:", + BrowserTarget::ExportSample(_) => "Export sample:", + BrowserTarget::ImportClip(_) => "Import clip:", + BrowserTarget::ExportClip(_) => "Export clip:", + }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), + ":device" => { + let selected = app.dialog.device_kind().unwrap(); + Box::new(Bsp::s(Tui::bold(true, "Add device"), Map::south(1, + move||device_kinds().iter(), + move|label: &&'static str, i|{ + let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; + let lb = if i == selected { "[ " } else { " " }; + let rb = if i == selected { " ]" } else { " " }; + Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) }))) }, + }); + /// Resolve a symbol if known. + fn from_word (&'t self, dsl: D) -> Perhaps>> { + if let Some(dsl) = dsl.word()? { + let views = self.config.views.read().unwrap(); + if let Some(view) = views.get(dsl) { + let view = view.clone(); + std::mem::drop(views); + return Ok(Some(render_dsl(self, view.as_ref()))) + } + for (word, get) in Self::WORDS.0 { if dsl == *word { return get(self) } } + } + return Ok(None) + } +} + +dsl_ns! { |app: App| bool => { ":focused/editor" => app.project.editor.is_some(), @@ -414,6 +550,10 @@ dsl_ns!(|app: App| Selection::Mix), }; + ItemTheme => { + ":_theme_stub" => Default::default() + }; + Dialog => { ":dialog/none" => Dialog::None, ":dialog/options" => Dialog::Options, @@ -446,7 +586,9 @@ dsl_ns!(|app: App| }; Option => { - ":editor/pitch" => Some((app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into()) + ":editor/pitch" => Some( + (app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into() + ) }; Option => { @@ -490,99 +632,49 @@ dsl_ns!(|app: App| Color => { ("g", n: u8) => Color::Rgb(n, n, n), - ("rgb", r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), + ("rgb", red: u8, green: u8, blue: u8) => Color::Rgb(red, green, blue), + ":color/bg" => Color::Rgb(28, 32, 36), + }; - Box> => { - ("bold", value: bool, x: Box>) => Box::new(Tui::bold(value, x)), +} - ("fg", color: Color, x: Box>) => Box::new(Tui::fg(color, x)), - ("bg", color: Color, x: Box>) => Box::new(Tui::bg(color, x)), - ("fg/bg", fg: Color, bg: Color, x: Box>) => Box::new(Tui::fg_bg(fg, bg, x)), - - ("bsp/n", a: Box>, b: Box>) => Box::new(Bsp::n(a, b)), - ("bsp/s", a: Box>, b: Box>) => Box::new(Bsp::s(a, b)), - ("bsp/e", a: Box>, b: Box>) => Box::new(Bsp::e(a, b)), - ("bsp/w", a: Box>, b: Box>) => Box::new(Bsp::w(a, b)), - ("bsp/a", a: Box>, b: Box>) => Box::new(Bsp::a(a, b)), - ("bsp/b", a: Box>, b: Box>) => Box::new(Bsp::b(a, b)), - - ("align/n", x: Box>) => Box::new(Align::n(x)), - ("align/s", x: Box>) => Box::new(Align::s(x)), - ("align/e", x: Box>) => Box::new(Align::e(x)), - ("align/w", x: Box>) => Box::new(Align::w(x)), - ("align/x", x: Box>) => Box::new(Align::x(x)), - ("align/y", x: Box>) => Box::new(Align::y(x)), - ("align/c", x: Box>) => Box::new(Align::c(x)), - - ("fill/x", x: Box>) => Box::new(Fill::x(x)), - ("fill/y", x: Box>) => Box::new(Fill::y(x)), - ("fill/xy", x: Box>) => Box::new(Fill::xy(x)), - - ("fixed/x", x: u16, c: Box>) => Box::new(Fixed::x(x, c)), - ("fixed/y", y: u16, c: Box>) => Box::new(Fixed::y(y, c)), - ("fixed/xy", x: u16, y: u16, c: Box>) => Box::new(Fixed::xy(x, y, c)), - - ("min/x", x: u16, c: Box>) => Box::new(Min::x(x, c)), - ("min/y", y: u16, c: Box>) => Box::new(Min::y(y, c)), - ("min/xy", x: u16, y: u16, c: Box>) => Box::new(Min::xy(x, y, c)), - - ("max/x", x: u16, c: Box>) => Box::new(Max::x(x, c)), - ("max/y", y: u16, c: Box>) => Box::new(Max::y(y, c)), - ("max/xy", x: u16, y: u16, c: Box>) => Box::new(Max::xy(x, y, c)), - - ":view/menu" => - app.view(stringify!((bg (rgb 0 0 0) - (bsp/s :view/ports/outs (bsp/s (bg (rgb 33 33 33) (bold :true "tek 0.3.0-rc.0")) - (bsp/n :view/ports/ins (bsp/n - (bg (rgb 33 33 33) (bsp/e (fg (rgb 255 192 48) "[Enter]") " new session")) - (align/n (fill/xy :view/modes))))))))), - ":view/modes" => Box::new({ - let modes = app.config.modes.clone(); - Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { - let bg = if index == 0 { Rgb(64,64,64) } else { Rgb(32,32,32) }; - let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or(""); - let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); - //add(&app.view(stringify!((fixed/y 2 (bg #bg (bsp/s - //(fill/x (bsp/a (fill/x (align/w (fg (rgb 224 192 128) #name))) - //(fill/x (align/e (fg (rgb 224 128 32) #id))))) - //(fill/x (align/w #info)))))))); - } - }) }), - ":view/ports/outs" => - app.view(stringify!((fill/x (fixed/y 3 - (bsp/a (fill/x (align/w "L AUDIO OUT") - (bsp/a "MIDI OUT" (fill/x (align/e "AUDIO OUT R"))))))))), - ":view/ports/ins" => - app.view(stringify!(fill/x (fixed/y 3 - (bsp/a (fill/x (align/w "L AUDIO IN ") - (bsp/a "MIDI IN " (fill/x (align/e "AUDIO IN R")))))))), - ":view/browse" => - app.view(stringify!(bsp/s - (padding/xy 3 1 :view/browse-title) (enclose (fg (g 96)) :view/browser))), - ":view/browse/title" => - Box::new(Fill::x(Align::w(FieldV(Default::default(), - match app.dialog.browser_target().unwrap() { - BrowserTarget::SaveProject => "Save project:", - BrowserTarget::LoadProject => "Load project:", - BrowserTarget::ImportSample(_) => "Import sample:", - BrowserTarget::ExportSample(_) => "Export sample:", - BrowserTarget::ImportClip(_) => "Import clip:", - BrowserTarget::ExportClip(_) => "Export clip:", - }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), - ":view/device" => { - let selected = app.dialog.device_kind().unwrap(); - Box::new(Bsp::s(Tui::bold(true, "Add device"), Map::south(1, - move||device_kinds().iter(), - move|label: &&'static str, i|{ - let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; - let lb = if i == selected { "[ " } else { " " }; - let rb = if i == selected { " ]" } else { " " }; - Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) }))) }, - //(":view/options", view_options), +dsl_ns! { num |app: App| + u8; + u16 => { + ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), + ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), }; -); + usize => { + ":scene-count" => app.scenes().len(), + ":track-count" => app.tracks().len(), + ":device-kind" => app.dialog.device_kind().unwrap_or(0), + ":device-kind/next" => app.dialog.device_kind_next().unwrap_or(0), + ":device-kind/prev" => app.dialog.device_kind_prev().unwrap_or(0), + }; + isize; +} + +//#[dizzle::ns] +//#[dizzle::ns::include(TuiOut)] +//trait AppApi { + //#[dizzle::ns::word("sessions")] + //fn view_sessions (&self) -> Box> { + //Min::x(30, Fixed::y(6, Stack::south( + //move|add: &mut dyn FnMut(&dyn Render)|{ + //let fg = Rgb(224, 192, 128); + //for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { + //let bg = if index == 0 { Rgb(48,64,32) } else { Rgb(16, 32, 24) }; + //add(&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))); + //} + //} + //))).into() + //} + //#[dizzle::ns::expr("bold")] + //fn view_bold (&self, value: bool, x: Box>) -> Box> { + //Box::new(Tui::bold(value, x)) + //} +//} /////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 6c0af4a4..ba73533b 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -79,8 +79,8 @@ impl Cli { color: ItemTheme::random(), dialog: Dialog::Menu(0), mode: Mode { - view: vec![":view/menu".into()], - keys: vec![":keys/axis/y".into()], + view: vec![":menu".into()], + keys: vec![":y".into()], ..Default::default() }, project: Arrangement { diff --git a/deps/tengri b/deps/tengri index 4fc0db57..1ef898ac 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 4fc0db577737ad1ce2601aa99d45248dba9a2d5f +Subproject commit 1ef898ac32a8292d8c4ecf056f8d8c7d957f2991 diff --git a/tek.edn b/tek.edn index 42897107..27a3e216 100644 --- a/tek.edn +++ b/tek.edn @@ -1,146 +1,161 @@ +(view :menu (bg :color/bg (bsp/s :ports/out (bsp/n :ports/in (bsp/e :sessions :templates))))) +(view :ports/out (fill/x (fixed/y 3 (bsp/a (fill/x (align/w "L AUDIO OUT") + (bsp/a "MIDI OUT" (fill/x (align/e "AUDIO OUT R")))))))) +(view :ports/in (fill/x (fixed/y 3 (bsp/a (fill/x (align/w "L AUDIO IN ") + (bsp/a "MIDI IN " (fill/x (align/e "AUDIO IN R")))))))) +(view :browse (bsp/s (padding/xy 3 1 :browse-title) (enclose (fg (g 96)) browser))) + (keys :help (@f1 dialog :help)) (keys :back (@escape back)) (keys :confirm (@enter confirm)) (keys :page (@pgup page/up) (@pgdn page/down)) -(keys :axis/x (@left x/prev) (@right x/next)) -(keys :axis/x2 (@shift/left x2/prev) (@shift/right x2/next)) -(keys :axis/y (@up y/prev) (@down y/next)) -(keys :axis/y2 (@shift/up y2/prev) (@shift/down y2/next)) -(keys :axis/z (@minus z/prev) (@equal z/next)) -(keys :axis/z2 (@underscore z2/prev) (@plus z2/next)) -(keys :axis/i (@comma i/prev) (@period z/next)) -(keys :axis/i2 (@lt i2/prev) (@gt z2/next)) -(keys :axis/w (@openbracket w/prev) (@closebracket w/next)) -(keys :axis/w2 (@openbrace w2/prev) (@closebrace w2/next)) -(keys :delete (@delete delete)) -(keys :backspace (@backspace delete/back)) -(keys :input (see :delete :axis/x :backspace) (:char input)) -(keys :length (see :confirm :axis/y :axis/x)) -(keys :list (see :confirm :axis/y)) +(keys :x (@left x/prev) (@right x/next)) +(keys :x2 (@shift/left x2/prev) (@shift/right x2/next)) +(keys :y (@up y/prev) (@down y/next)) +(keys :y2 (@shift/up y2/prev) (@shift/down y2/next)) +(keys :z (@minus z/prev) (@equal z/next)) +(keys :z2 (@underscore z2/prev) (@plus z2/next)) +(keys :i (@comma i/prev) (@period z/next)) +(keys :i2 (@lt i2/prev) (@gt z2/next)) +(keys :w (@openbracket w/prev) (@closebracket w/next)) +(keys :w2 (@openbrace w2/prev) (@closebrace w2/next)) +(keys :delete (@delete delete) (@backspace delete/back)) +(keys :input (see :x :delete) (:char input)) +(keys :list (see :y :confirm)) +(keys :length (see :x :y :confirm)) (keys :browse (see :list :input :focus)) -(keys :focus) (keys :history (@u undo 1) (@r redo 1)) (keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0)) (keys :color (@c color)) (keys :launch (@q launch)) (keys :saveload (@f6 dialog :save) (@f9 dialog :load)) (keys :global (see :history :saveload) (@f8 dialog :options) (@f10 dialog :quit)) +(keys :focus) -(mode :transport - (name Transport) - (info A JACK transport controller.) - (keys :clock :global) - :transport) +(mode :transport (name Transport) + (info A JACK transport controller.) + (keys :clock :global) + (view :transport)) (mode :arranger - (name Arranger) - (info A grid of launchable clips arranged by track and scene.) - (when :mode/editor (keys :editor)) - (when :mode/dialog (keys :dialog)) - (when :mode/message (keys :message)) - (when :mode/add-device (keys :add-device)) - (when :mode/browse (keys :browse)) - (when :mode/rename (keys :pool/rename)) - (when :mode/length (keys :pool/length)) - (when :mode/clip (keys :clip)) - (when :mode/track (keys :track)) - (when :mode/scene (keys :scene)) - (when :mode/mix (keys :mix)) - (keys :clock :arranger :global) - (bsp/w :meters/output (bsp/e :meters/input (stack/n - (fixed/y 2 :status/h2) :tracks/inputs (stack/s - :tracks/devices :tracks/outputs :tracks/names - (fill/xy (either :mode/editor (bsp/e :scenes/names :editor) :scenes))))))) + (name Arranger) + (info A grid of launchable clips arranged by track and scene.) + (mode :editor (keys :editor)) + (mode :dialog (keys :dialog)) + (mode :message (keys :message)) + (mode :add-device (keys :add-device)) + (mode :browse (keys :browse)) + (mode :rename (keys :input)) + (mode :length (keys :rename)) + (mode :clip (keys :clip)) + (mode :track (keys :track)) + (mode :scene (keys :scene)) + (mode :mix (keys :mix)) + (keys :clock :arranger :global) + (view :arranger)) + +(view :arranger (bsp/w :meters/output (bsp/e :meters/input (stack/n + (fixed/y 2 :status/h2) :tracks/inputs (stack/s + :tracks/devices :tracks/outputs :tracks/names + (fill/xy (either :mode/editor (bsp/e :scenes/names :editor) :scenes))))))) (keys :arranger (see :color :launch :scenes :tracks) - (@tab project/edit) (@enter project/edit) - (@shift/I project/input/add) (@shift/O project/output/add) - (@shift/S project/scene/add) (@shift/T project/track/add) - (@shift/D dialog/show :dialog/device)) + (@tab project/edit) (@enter project/edit) + (@shift/I project/input/add) (@shift/O project/output/add) + (@shift/S project/scene/add) (@shift/T project/track/add) + (@shift/D dialog/show :dialog/device)) (keys :tracks - (@t select :select/track) - (@left select :select/track/prev) - (@right select :select/track/next)) - -(keys :track (see :color :launch :axis/z :axis/z2 :delete) - (@r toggle :rec) - (@m toggle :mon) - (@p toggle :play) - (@P toggle :solo)) + (@t select :select/track) + (@left select :select/track/prev) + (@right select :select/track/next)) (keys :scenes - (@s select :select/scene) - (@up select :select/scene/prev) - (@down select :select/scene/next)) + (@s select :select/scene) + (@up select :select/scene/prev) + (@down select :select/scene/next)) -(keys :scene (see :color :launch :axis/z :axis/z2 :delete)) +(keys :track (see :color :launch :z :z2 :delete) + (@r toggle :rec) + (@m toggle :mon) + (@p toggle :play) + (@P toggle :solo)) -(keys :clip (see :color :launch :axis/z :axis/z2 :delete) - (@l toggle :loop)) +(keys :scene (see :color :launch :z :z2 :delete)) + +(keys :clip (see :color :launch :z :z2 :delete) + (@l toggle :loop)) (mode :groovebox - (name Groovebox) - (info A sequencer with built-in sampler.) - (when :mode/browse (keys :browse)) - (when :mode/rename (keys :pool-rename)) - (when :mode/length (keys :pool-length)) - (keys :clock :editor :sampler :global) - (bsp/w :meters/output (bsp/e :meters/input (bsp/w - (fill/y (align/n - (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool))) - (bsp/n - (fixed/y :h-sample-detail (bsp/e - (fill/y (fixed/x 20 (align/nw :sample-status))) - :sample-viewer)) - (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) - (bsp/e :samples/keys :editor))))))) + (name Groovebox) + (info A sequencer with built-in sampler.) + (mode browse (keys :browse)) + (mode rename (keys :pool-rename)) + (mode length (keys :pool-length)) + (keys :clock :editor :sampler :global) + (view :groovebox)) + +(view :groovebox (bsp/w :meters/output (bsp/e :meters/input (bsp/w + (fill/y (align/n + (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool))) + (bsp/n + (fixed/y :h-sample-detail (bsp/e + (fill/y (fixed/x 20 (align/nw :sample-status))) + :sample-viewer)) + (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) + (bsp/e :samples/keys :editor))))))) (mode :sampler - (name Sampler) - (info A sampling soundboard.) - (keys :sampler :global) - (bsp/s (fixed/y 1 :transport) (bsp/n (fixed/y 1 :status) (fill/xy :samples/grid)))) + (name Sampler) + (info A sampling soundboard.) + (keys :sampler :global) + (view :sampler)) + +(view :sampler + (bsp/s (fixed/y 1 :transport) (bsp/n (fixed/y 1 :status) (fill/xy :samples/grid)))) (keys :sampler - (see :sampler/directions :sampler/record :sampler/play)) + (see :sampler/directions :sampler/record :sampler/play)) (keys :sampler/play - (@p sampler/play/sample :sample/selected) - (@P sampler/stop/sample :sample/selected)) + (@p sampler/play/sample :sample/selected) + (@P sampler/stop/sample :sample/selected)) (keys :sampler/record - (@r sampler/record/toggle :sample/selected) - (@shift/R sampler/record/back)) + (@r sampler/record/toggle :sample/selected) + (@shift/R sampler/record/back)) (keys :sampler/import-export - (@shift/f6 dialog :dialog/export/sample) - (@shift/f9 dialog :dialog/import/sample)) + (@shift/f6 dialog :dialog/export/sample) + (@shift/f9 dialog :dialog/import/sample)) (keys :sampler/directions - (@up sampler/select :sample/above) - (@down sampler/select :sample/below) - (@left sampler/select :sample/to/left) - (@right sampler/select :sample/to/right)) + (@up sampler/select :sample/above) + (@down sampler/select :sample/below) + (@left sampler/select :sample/to/left) + (@right sampler/select :sample/to/right)) (mode :sequencer - (name Sequencer) - (info A MIDI sequencer.) - (when :mode/browse (keys :browse)) - (when :mode/rename (keys :pool/rename)) - (when :mode/length (keys :pool/length)) - (keys :editor :clock :global) - (bsp/s (fixed/y 1 :transport) - (bsp/n (fixed/y 1 :status) - (fill/xy (bsp/a (fill/xy (align/e :pool)) - :editor))))) + (name Sequencer) + (info A MIDI sequencer.) + (mode browse (keys :browse)) + (mode rename (keys :pool/rename)) + (mode length (keys :pool/length)) + (keys :editor :clock :global) + (view :sequencer)) + +(view :sequencer (bsp/s + (fixed/y 1 :transport) + (bsp/n (fixed/y 1 :status) + (fill/xy (bsp/a (fill/xy (align/e :pool)) + :editor))))) (keys :sequencer (see :color :launch) - (@shift/I input/add) - (@shift/O output/add)) -(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete) - (@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin) - (@shift/A clip/add :after :new/clip) - (@shift/D clip/add :after :cloned/clip)) + (@shift/I input/add) + (@shift/O output/add)) +(keys :pool (see :axis-y :axis-w :z2 :color :delete) + (@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin) + (@shift/A clip/add :after :new/clip) + (@shift/D clip/add :after :cloned/clip)) (keys :editor (see :editor/view :editor/note)) -(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2) - (@z toggle :lock)) -(keys :editor/note (see :axis/i :axis/i2 :axis/y :axis/page) - (@a editor/append :true) (@enter editor/append :false) - (@del editor/delete/note) (@shift/del editor/delete/note)) +(keys :editor/view (see :x :x2 :z :z2) + (@z toggle :lock)) +(keys :editor/note (see :i :i2 :y :page) + (@a editor/append :true) (@enter editor/append :false) + (@del editor/delete/note) (@shift/del editor/delete/note))