diff --git a/crates/app/app.rs b/crates/app/app.rs index 60f387f0..ad4e0a47 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -2,11 +2,10 @@ #![feature(adt_const_params, associated_type_defaults, if_let_guard, impl_trait_in_assoc_type, type_alias_impl_trait, trait_alias, type_changing_struct_update, closure_lifetime_binder)] #[cfg(test)] mod app_test; -mod app_bind; pub use self::app_bind::*; -mod app_data; pub use self::app_data::*; mod app_deps; pub use self::app_deps::*; mod app_jack; pub use self::app_jack::*; -mod app_menu; pub use self::app_menu::*; +mod app_bind; pub use self::app_bind::*; +mod app_data; pub use self::app_data::*; mod app_view; pub use self::app_view::*; /// Total state #[derive(Default, Debug)] @@ -32,6 +31,23 @@ pub struct App { /// Contains the currently edited musical arrangement pub project: Arrangement, } +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Axis { + min: usize, + max: usize, + step: usize, +} +/// Various possible dialog modes. +#[derive(Debug, Clone, Default, PartialEq)] +pub enum Dialog { + #[default] None, + Help(usize), + Menu(usize, Arc<[Arc]>), + Device(usize), + Message(Arc), + Browse(BrowseTarget, Arc), + Options, +} has!(Jack<'static>: |self: App|self.jack); has!(Pool: |self: App|self.pool); has!(Dialog: |self: App|self.dialog); @@ -51,6 +67,48 @@ maybe_has!(Scene: |self: App| { MaybeHas::::get(&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() } } +impl Dialog { + pub fn welcome () -> Self { + Self::Menu(0, [ + "Continue session".into(), + "Load old session".into(), + "Begin new session".into(), + ].into()) + } + pub fn menu_next (&self) -> Self { + match self { + Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.len()), items.clone()), + _ => Self::None + } + } + pub fn menu_prev (&self) -> Self { + match self { + Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.len()), items.clone()), + _ => Self::None + } + } + pub fn menu_selected (&self) -> Option { + if let Self::Menu(selected, _) = self { Some(*selected) } else { None } + } + pub fn device_kind (&self) -> Option { + if let Self::Device(index) = self { Some(*index) } else { None } + } + pub fn device_kind_next (&self) -> Option { + self.device_kind().map(|index|(index + 1) % device_kinds().len()) + } + pub fn device_kind_prev (&self) -> Option { + self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1))) + } + pub fn message (&self) -> Option<&str> { + todo!() + } + pub fn browser (&self) -> Option<&Arc> { + todo!() + } + pub fn browser_target (&self) -> Option<&BrowseTarget> { + todo!() + } +} impl App { pub fn editor_focused (&self) -> bool { false diff --git a/crates/app/app_bind.rs b/crates/app/app_bind.rs index c0c4a7a7..6abdd449 100644 --- a/crates/app/app_bind.rs +++ b/crates/app/app_bind.rs @@ -49,15 +49,8 @@ impl Default for AppCommand { fn default () -> Self { Self::Nop } } def_command!(AppCommand: |app: App| { Nop => Ok(None), - Confirm => Ok(match &app.dialog { - Dialog::Menu(index, items) => { - let callback = items.0[*index].1.clone(); - callback(app)?; - None - }, - _ => todo!(), - }), - Cancel => todo!(), // TODO delegate: + Confirm => todo!(), + Cancel => todo!(), // TODO delegate: Inc { axis: Axis } => Ok(match (&app.dialog, axis) { (Dialog::None, _) => todo!(), (Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() } diff --git a/crates/app/app_data.rs b/crates/app/app_data.rs index 2b27fbb8..cfb8feb3 100644 --- a/crates/app/app_data.rs +++ b/crates/app/app_data.rs @@ -10,8 +10,7 @@ impl<'t> DslNs<'t, Arc> for App { impl<'t> DslNs<'t, bool> for App { dsl_words!(|app| -> bool { - ":mode/editor" => app.project.editor.is_some(), - + ":focused/editor" => app.project.editor.is_some(), ":focused/dialog" => !matches!(app.dialog, Dialog::None), ":focused/message" => matches!(app.dialog, Dialog::Message(..)), ":focused/add_device" => matches!(app.dialog, Dialog::Device(..)), diff --git a/crates/app/app_deps.rs b/crates/app/app_deps.rs index 8084c703..40bdf08f 100644 --- a/crates/app/app_deps.rs +++ b/crates/app/app_deps.rs @@ -3,17 +3,14 @@ pub use ::{ tek_config::*, tek_device::{self, *}, tengri::{ - Usually, Perhaps, Has, MaybeHas, has, maybe_has, impl_debug, + Usually, Perhaps, Has, MaybeHas, has, maybe_has, dsl::*, input::*, output::*, tui::*, - tui::{ - ratatui::{ - self, prelude::{Style, Stylize, Buffer, Modifier, buffer::Cell, Color::{self, *}} - }, - crossterm::{ - self, - event::{Event, KeyCode::{self, *}}, - }, - } + tui::ratatui, + tui::ratatui::prelude::buffer::Cell, + tui::ratatui::prelude::Color::{self, *}, + tui::ratatui::prelude::{Style, Stylize, Buffer, Modifier}, + tui::crossterm, + tui::crossterm::event::{Event, KeyCode::{self, *}}, }, std::{ path::{Path, PathBuf}, diff --git a/crates/app/app_menu.rs b/crates/app/app_menu.rs deleted file mode 100644 index b4bd5374..00000000 --- a/crates/app/app_menu.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::*; - -/// Various possible dialog modes. -#[derive(Debug, Clone, Default, PartialEq)] -pub enum Dialog { - #[default] None, - Help(usize), - Menu(usize, MenuItems), - Device(usize), - Message(Arc), - Browse(BrowseTarget, Arc), - Options, -} - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct MenuItems(pub Arc<[MenuItem]>); - -impl AsRef> for MenuItems { - fn as_ref (&self) -> &Arc<[MenuItem]> { - &self.0 - } -} - -#[derive(Clone)] -pub struct MenuItem( - /// Label - pub Arc, - /// Callback - pub ArcUsually<()> + Send + Sync>> -); - -impl Default for MenuItem { - fn default () -> Self { Self("".into(), Arc::new(Box::new(|_|Ok(())))) } -} - -impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) }); - -impl PartialEq for MenuItem { - fn eq (&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Dialog { - pub fn welcome () -> Self { - Self::Menu(1, MenuItems([ - MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))), - MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({ - app.dialog = Dialog::None; - app.mode = app.config.modes.clone().read().unwrap().get(":arranger").cloned().unwrap(); - })))), - MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))), - ].into())) - } - pub fn menu_next (&self) -> Self { - match self { - Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.0.len()), items.clone()), - _ => Self::None - } - } - pub fn menu_prev (&self) -> Self { - match self { - Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.0.len()), items.clone()), - _ => Self::None - } - } - pub fn menu_selected (&self) -> Option { - if let Self::Menu(selected, _) = self { Some(*selected) } else { None } - } - pub fn device_kind (&self) -> Option { - if let Self::Device(index) = self { Some(*index) } else { None } - } - pub fn device_kind_next (&self) -> Option { - self.device_kind().map(|index|(index + 1) % device_kinds().len()) - } - pub fn device_kind_prev (&self) -> Option { - self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1))) - } - pub fn message (&self) -> Option<&str> { - todo!() - } - pub fn browser (&self) -> Option<&Arc> { - todo!() - } - pub fn browser_target (&self) -> Option<&BrowseTarget> { - todo!() - } -} diff --git a/crates/app/app_view.rs b/crates/app/app_view.rs index ebf1db9b..43ac442f 100644 --- a/crates/app/app_view.rs +++ b/crates/app/app_view.rs @@ -49,9 +49,6 @@ impl<'t> DslNs<'t, Box>> for App { "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)), - "either" (cond: bool, a: Box>, b: Box>) => - Box::new(Either(cond, a, b)), - "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)), @@ -86,35 +83,18 @@ impl<'t> DslNs<'t, Box>> for App { "max/xy" (x: u16, y: u16, c: Box>) => Box::new(Max::xy(x, y, c)), }); dsl_words!(|app| -> Box> { - ":logo" => Box::new(Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), Stack::south(|add|{ - add(&Fixed::y(1, "")); - add(&Fixed::y(1, "")); - add(&Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~")); - add(&Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~")))); - add(&Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~")); - }))))), - ":meters/input" => Box::new("Input Meters"), - ":meters/output" => Box::new("Output Meters"), - ":status" => Box::new("Status Bar"), - ":tracks/names" => Box::new("Track Names"), - ":tracks/inputs" => Box::new("Track Inputs"), - ":tracks/devices" => Box::new("Track Devices"), - ":tracks/outputs" => Box::new("Track Outputs"), - ":scenes/names" => Box::new("Scene Names"), - ":editor" => Box::new("Editor"), - ":scenes" => Box::new("Editor"), ":dialog/menu" => Box::new(if let Dialog::Menu(selected, items) = &app.dialog { let items = items.clone(); let selected = *selected; - Some(Fill::x(Stack::south(move|add|{ - for (index, MenuItem(item, _)) in items.0.iter().enumerate() { + Some(Fill::xy(Align::c(Tui::bg(Red, Fill::x(Stack::south(move|add|{ + for (index, item) in items.iter().enumerate() { add(&Tui::fg_bg( if selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) }, if selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) }, Fixed::y(2, Align::n(Fill::x(item))) )); } - }))) + })))))) } else { None }), @@ -238,6 +218,14 @@ pub fn view_nil (_: &App) -> Box> { ////.enclose(Fill::xy(browser))) //}, // + //pub fn view_meters_input (&self) -> impl Content + use<'_> { + //self.project.sampler().map(|s| + //s.view_meters_input()) + //} + //pub fn view_meters_output (&self) -> impl Content + use<'_> { + //self.project.sampler().map(|s| + //s.view_meters_output()) + //} //pub fn view_history (&self) -> impl Content { //Fixed::y(1, Fill::x(Align::w(FieldH(self.color, //format!("History ({})", self.history.len()), diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 3562ecab..255a8d8c 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -115,15 +115,12 @@ impl Cli { /// CLI header const HEADER: &'static str = r#" -~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ ~~~~~~ ~ ~~~ - ~~ ║ ~ ╟─╌ ~╟─< ~ v0.3.0, 2025 sum(m)er @ the nose of the cat. ~ -~~~ ╨ ~ ╙──╜ ╨ ╜ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ ~~ ~~ ~ ~~ - On first run, Tek will create configuration and state dirs: - * [x] ~/.config/tek - config - * [ ] ~/.local/share/tek - projects - * [ ] ~/.local/lib/tek - plugins - * [ ] ~/.cache/tek - cache -~"#; +~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~~~~ +~ ~ ║ ~ ╟─╌ ~╟─< ~ v0.3.0-rc.0 "no, i insist that i am not a dj ~ +~ ~ ╨ ~ ╙──╜ ╨ ╜ ~ 2025, summer, the nose of the cat. J ~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + On first run, Tek will create configuration and state dirs. ~ + On subsequent runs, Tek should resume from where you left off. ~"#; #[cfg(test)] #[test] fn test_cli () { use clap::CommandFactory; diff --git a/crates/config/config.rs b/crates/config/config.rs index 3b23f99d..d48482bb 100644 --- a/crates/config/config.rs +++ b/crates/config/config.rs @@ -125,10 +125,10 @@ impl Mode> { Ok(()) } pub fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> { - Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { - println!("Mode::load_one: {head} {:?}", expr.tail()); + Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(key)) = expr.head() { + println!("Mode::load_one: {key} {:?}", expr.tail()?); let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); - match head { + match key { "name" => self.name.push(tail.into()), "info" => self.info.push(tail.into()), "view" => self.view.push(tail.into()), @@ -139,7 +139,7 @@ impl Mode> { return Err(format!("Mode::load_one: self: incomplete: {expr:?}").into()); }, _ => { - return Err(format!("Mode::load_one: unexpected expr: {head:?} {tail:?}").into()) + return Err(format!("Mode::load_one: unexpected expr: {key:?} {tail:?}").into()) }, }; } else if let Ok(Some(word)) = dsl.word() { diff --git a/deps/tengri b/deps/tengri index d9081087..446ec7a7 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit d9081087ecc41ad8514c17614daab4f34d3e876d +Subproject commit 446ec7a71477e1b9ca117b7a23759d6318eb2cf0 diff --git a/tek.edn b/tek.edn index 63466c6f..5066d1b1 100644 --- a/tek.edn +++ b/tek.edn @@ -12,9 +12,9 @@ (mode :menu (keys :axis/y :confirm) :menu) (keys :confirm (@enter confirm)) (view :menu (bg (g 40) (bsp/s :ports/out (bsp/n :ports/in - (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill/xy :dialog/menu))))))) + (bg (g 30) (fill/xy (align/c :dialog/menu))))))) (view :ports/out (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-OUT))) - (bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R)))))))) + (bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT R)))))))) (view :ports/in (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-IN))) (bsp/a (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R)))))))) (view :browse (bsp/s (padding/xy 3 1 :browse-title) (enclose (fg (g 96)) browser))) @@ -55,16 +55,12 @@ (mode :scene (keys :scene)) (mode :mix (keys :mix)) (keys :clock :arranger :global) - :arranger) + (view :arranger)) -(view :arranger (bsp/w :meters/output (bsp/e :meters/input - (bsp/n (fixed/y 2 :status) (bsp/n :tracks/inputs - (bsp/s :tracks/devices (bsp/s :tracks/outputs (bsp/s :tracks/names - (fill/xy (either :mode/editor (bsp/e :scenes/names :editor) :scenes)))))))))) -(view :arranger (bsp/w :meters/output (bsp/e :meters/input - (bsp/n (fixed/y 2 :status) (bsp/n :tracks/inputs - (bsp/s :tracks/devices (bsp/s :tracks/outputs (bsp/s :tracks/names - (fill/xy (either :mode/editor (bsp/e :scenes/names :editor) :scenes)))))))))) +(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)