// ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ //██Let me play the world's tiniest piano for you. ██ //█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█ //█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙██ //█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ //███████████████████████████████████████████████████ //█ ▀ ▀ ▀ █ #![allow(unused)] #![allow(clippy::unit_arg)] #![feature(adt_const_params)] #![feature(associated_type_defaults)] #![feature(if_let_guard)] #![feature(impl_trait_in_assoc_type)] #![feature(type_alias_impl_trait)] #![feature(trait_alias)] #![feature(type_changing_struct_update)] #![feature(let_chains)] #![feature(closure_lifetime_binder)] #![feature(generic_const_exprs)] #![feature(generic_arg_infer)] pub use ::tek_engine:: *; pub use ::tek_device::{self, *}; pub use ::tengri::{Usually, Perhaps, Has, MaybeHas}; pub use ::tengri::{has, maybe_has}; pub use ::tengri::dsl::*; pub use ::tengri::input::*; pub use ::tengri::output::*; pub use ::tengri::tui::*; pub use ::tengri::tui::ratatui; pub use ::tengri::tui::ratatui::prelude::buffer::Cell; pub use ::tengri::tui::ratatui::prelude::Color::{self, *}; pub use ::tengri::tui::ratatui::prelude::{Style, Stylize, Buffer, Modifier}; pub use ::tengri::tui::crossterm; pub use ::tengri::tui::crossterm::event::{Event, KeyCode::{self, *}}; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}; use std::error::Error; use std::collections::BTreeMap; use std::fmt::Write; use ::tengri::tui::ratatui::prelude::Position; use xdg::BaseDirectories; mod app_dsl; pub use self::app_dsl::*; mod app_view; pub use self::app_view::*; mod app_ctrl; pub use self::app_ctrl::*; mod app_jack; pub use self::app_jack::*; #[cfg(test)] mod app_test; /// Total state #[derive(Default, Debug)] pub struct App { /// Must not be dropped for the duration of the process pub jack: Jack<'static>, /// Display size pub size: Measure, /// Performance counter pub perf: PerfModel, /// Available view modes and input bindings pub config: Config, /// Currently selected mode pub mode: Mode>, /// Contains the currently edited musical arrangement pub project: Arrangement, /// Contains all recently created clips. pub pool: Pool, /// Undo history pub history: Vec<(AppCommand, Option)>, /// Dialog overlay pub dialog: Dialog, /// Base color. pub color: ItemTheme, } /// Configuration #[derive(Default, Debug)] pub struct Config { pub dirs: BaseDirectories, pub modes: Arc, Arc>>>>>, pub binds: Arc, EventMap, Arc>>>>, } #[derive(Default, Debug)] pub struct Mode { pub path: PathBuf, pub name: Vec, pub info: Vec, pub view: Vec, pub keys: Vec, pub modes: BTreeMap>, } impl Config { const CONFIG: &'static str = "tek.edn"; const DEFAULTS: &'static str = include_str!("../../tek.edn"); 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))?; Ok(cfgs) } pub fn init_file ( &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!("Creating {path:?}"); std::fs::write(self.dirs.place_config_file(path)?, defaults); } Ok(if let Some(path) = self.dirs.find_config_file(path) { println!("Loading {path:?}"); let src = std::fs::read_to_string(&path)?; src.as_str().each(move|item|each(self, item))?; } else { return Err(format!("{path}: not found").into()) }) } pub fn load_defs (&mut self, dsl: D) -> Usually<()> { dsl.each(|item|{ println!("{item:?}"); Ok(match item.exp().head() { Ok(Some("keys")) if let Some(id) = item.exp().tail().head()? => { self.binds.write().unwrap().insert(id.into(), { let mut map = EventMap::new(); item.exp().tail().tail()?.each(|item|Ok({ 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()) } }))?; map }); }, Ok(Some("mode")) if let Some(id) = item.exp().tail().head()? => { self.modes.write().unwrap().insert(id.into(), { let mut mode = Mode::default(); item.exp().tail().tail()?.each(|item|Ok(if let Ok(Some(exp)) = item.exp() { match exp.head()? { Some("name") => mode.name.push( exp.tail()?.map(|x|x.trim()).unwrap_or("").into() ), Some("info") => mode.info.push( exp.tail()?.map(|x|x.trim()).unwrap_or("").into() ), 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()) }, Some("mode") => if let (Some(name), Some(tail)) = ( exp.tail()?.head()?, exp.tail()?.tail()?, ) { let mut submode: 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()) }))?; mode.modes.insert(name.trim().into(), submode); } else { return Err(format!("load_view: empty mode: {exp}").into()) }, Some(_) => mode.view.push(exp.into()), None => return Err(format!("load_view: empty: {exp}").into()) } } else if let Ok(Some(sym)) = item.sym() { mode.view.push(sym.into()); } else { return Err(format!("load_view: unexpected: {dsl:?}").into()) }))?; mode.into() }); }, _ => return Err(format!("load_defs: unexpected: {item:?}").into()) }) }) } } fn load_modules (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usually<()> { //dsl!(dsl|module :id ..body|dsl!(body|@bind #info? ..commands|) if let Some(exp) = dsl.exp()? && Some("module") == exp.head().key()? && let Some(tail) = exp.tail()? && let Some(id) = tail.head().sym()? && let Some(body) = tail.tail()? { let _ = cb(id, body)?; Ok(()) } else { return Err("unexpected: {exp:?}".into()); } } /// Various possible dialog modes. impl App { pub fn update_clock (&self) { ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) } pub fn focused_editor (&self) -> bool { false } } has!(Jack<'static>: |self: App|self.jack); has!(Pool: |self: App|self.pool); has!(Dialog: |self: App|self.dialog); has!(Clock: |self: App|self.project.clock); has!(Option: |self: App|self.project.editor); has!(Selection: |self: App|self.project.selection); has!(Vec: |self: App|self.project.midi_ins); has!(Vec: |self: App|self.project.midi_outs); has!(Vec: |self: App|self.project.scenes); has!(Vec: |self: App|self.project.tracks); has!(Measure: |self: App|self.size); has_clips!( |self: App|self.pool.clips); maybe_has!(Track: |self: App| { MaybeHas::::get(&self.project) }; { MaybeHas::::get_mut(&mut self.project) }); maybe_has!(Scene: |self: App| { MaybeHas::::get(&self.project) }; { MaybeHas::::get_mut(&mut self.project) }); impl HasClipsSize for App { fn clips_size (&self) -> &Measure { &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() } } #[derive(Debug, Clone, Default)] pub enum Dialog { #[default] None, Help(usize), Menu(usize), Device(usize), Message(Arc), Browser(BrowserTarget, Arc), Options, } impl Dialog { fn menu_selected (&self) -> Option { if let Self::Menu(selected) = self { Some(*selected) } else { None } } fn device_kind (&self) -> Option { if let Self::Device(index) = self { Some(*index) } else { None } } fn device_kind_next (&self) -> Option { self.device_kind().map(|index|(index + 1) % device_kinds().len()) } fn device_kind_prev (&self) -> Option { self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1))) } fn message (&self) -> Option<&str> { todo!() } fn browser (&self) -> Option<&Arc> { todo!() } fn browser_target (&self) -> Option<&BrowserTarget> { todo!() } }