From 701a651fbd96cf2ffad00f060a8d3a1b8e475aa5 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Thu, 2 Jul 2026 09:21:47 +0300 Subject: [PATCH] refactor: Mode, View, Bind --- src/app.rs | 2 - src/app/bind.rs | 86 ++--- src/app/cli.rs | 22 +- src/app/config.rs | 16 +- src/app/{ => config}/mode.rs | 36 +- src/app/config/modes.rs | 27 ++ src/app/config/views.rs | 10 + src/app/draw.rs | 615 ++++++++++++++++++++++++++++++++++- src/app/size.rs | 32 -- src/app/view.rs | 614 ---------------------------------- src/deps.rs | 33 ++ src/device/arrange/clip.rs | 6 +- src/device/arrange/scene.rs | 40 ++- src/device/arrange/track.rs | 40 ++- src/device/clock.rs | 21 ++ src/device/dialog.rs | 2 +- src/device/plugin.rs | 8 + src/device/port.rs | 35 +- src/device/port/register.rs | 27 ++ src/device/sampler.rs | 8 + src/tek.rs | 101 +----- 21 files changed, 901 insertions(+), 880 deletions(-) rename src/app/{ => config}/mode.rs (87%) create mode 100644 src/app/config/modes.rs create mode 100644 src/app/config/views.rs delete mode 100644 src/app/view.rs create mode 100644 src/deps.rs create mode 100644 src/device/port/register.rs diff --git a/src/app.rs b/src/app.rs index 77158692..0be37c36 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,9 +3,7 @@ pub mod audio; #[allow(unused)] pub use self::audio::*; pub mod bind; pub use self::bind::*; pub mod cli; pub use self::cli::*; pub mod config; pub use self::config::*; -pub mod view; pub use self::view::*; pub mod draw; pub use self::draw::*; -pub mod mode; pub use self::mode::*; pub mod size; pub use self::size::*; /// Total application state. diff --git a/src/app/bind.rs b/src/app/bind.rs index 2f683fcc..a1814ace 100644 --- a/src/app/bind.rs +++ b/src/app/bind.rs @@ -1,16 +1,41 @@ use crate::*; tui_keys!(|self: App, input| { - todo!() + let commands = tek_commands_collect(self, input)?; + let results = tek_commands_execute(self, commands)?; + self.history.extend(results.into_iter()); + Ok(()) }); -/// A control axis. -/// -/// ``` -/// let axis = tek::ControlAxis::X; -/// ``` -#[derive(Debug, Copy, Clone)] pub enum ControlAxis { - X, Y, Z, I +fn tek_commands_collect (app: &App, input: &TuiEvent) + -> Usually> +{ + let mut commands = vec![]; + for id in app.mode.keys.iter() { + if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) + && let Some(bindings) = event_map.query(input) { + for binding in bindings { + for command in binding.commands.iter() { + if let Some(command) = app.namespace(command)? as Option { + commands.push(command) + } + } + } + } + } + Ok(commands) +} + +fn tek_commands_execute (app: &mut App, commands: Vec) + -> Usually)>> +{ + let mut history = vec![]; + for command in commands.into_iter() { + let result = command.act(app); + match result { Err(err) => { history.push((command, None)); return Err(err) } + Ok(undo) => { history.push((command, undo)); } }; + } + Ok(history) } /// Collection of input bindings. @@ -91,6 +116,7 @@ impl Bind> { impl Default for Bind { fn default () -> Self { Self(Default::default()) } } + impl Default for Binding { fn default () -> Self { Self { @@ -158,38 +184,14 @@ impl<'a> Namespace<'a, AppCommand> for App { }); } -//impl_handle!(TuiIn: |self: App, input|{ - //let commands = tek_collect_commands(self, input)?; - //let history = tek_execute_commands(self, commands)?; - //self.history.extend(history.into_iter()); - //Ok(None) -//}); +/// A control axis. +/// +/// ``` +/// let axis = tek::ControlAxis::X; +/// ``` +#[derive(Debug, Copy, Clone)] pub enum ControlAxis { + X, Y, Z, I +} -//fn tek_collect_commands (app: &App, input: &TuiIn) -> Usually> { - //let mut commands = vec![]; - //for id in app.mode.keys.iter() { - //if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) - //&& let Some(bindings) = event_map.query(input.event()) { - //for binding in bindings { - //for command in binding.commands.iter() { - //if let Some(command) = app.namespace(command)? as Option { - //commands.push(command) - //} - //} - //} - //} - //} - //Ok(commands) -//} - -//fn tek_execute_commands ( - //app: &mut App, commands: Vec -//) -> Usually)>> { - //let mut history = vec![]; - //for command in commands.into_iter() { - //let result = command.act(app); - //match result { Err(err) => { history.push((command, None)); return Err(err) } - //Ok(undo) => { history.push((command, undo)); } }; - //} - //Ok(history) -//} +//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); diff --git a/src/app/cli.rs b/src/app/cli.rs index b0c0c0c7..61250d14 100644 --- a/src/app/cli.rs +++ b/src/app/cli.rs @@ -208,7 +208,7 @@ pub fn tek_print_config (config: &Config) { } } } - for (k, v) in config.modes.read().unwrap().iter() { + config.modes.for_each(|k, v|{ println!(); for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } @@ -216,22 +216,16 @@ pub fn tek_print_config (config: &Config) { print!("\n{}", Blue.paint("KEYS")); for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } println!(); - for (k, v) in v.modes.read().unwrap().iter() { - print!("{} {} {:?}", - Blue.paint("MODE"), - Green.bold().paint(format!("{k:<16}")), - v.name); - print!(" INFO={:?}", - v.info); - print!(" VIEW={:?}", - v.view); - println!(" KEYS={:?}", - v.keys); - } + v.modes.for_each(|k, v|{ + print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); + print!( " INFO={:?}", v.info); + print!( " VIEW={:?}", v.view); + println!(" KEYS={:?}", v.keys); + }); print!("{}", Blue.paint("VIEW")); for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } println!(); - } + }); } pub fn tek_print_status (project: &Arrangement) { diff --git a/src/app/config.rs b/src/app/config.rs index 3ec4f94a..fab190fa 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -1,4 +1,4 @@ -use crate::{*, bind::*, view::*}; +use crate::*; /// Configuration: mode, view, and bind definitions. /// @@ -48,12 +48,14 @@ impl Config { config.init()?; Ok(config) } + /// Create a new app configuration from a set of XDG base directories, pub fn new (dirs: Option) -> Self { let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB); let dirs = dirs.unwrap_or_else(default); Self { dirs, ..Default::default() } } + /// Write initial contents of configuration. pub fn init (&mut self) -> Usually<()> { self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{ @@ -62,6 +64,7 @@ impl Config { })?; Ok(()) } + /// Write initial contents of a configuration file. pub fn init_one ( &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> @@ -78,11 +81,13 @@ impl Config { return Err(format!("{path}: not found").into()) }) } + /// Add statements to configuration from [Dsl] source. pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> { dsl.each(|item|self.add_one(item))?; Ok(self) } + fn add_one (&self, item: impl Language) -> Usually<()> { if let Some(expr) = item.expr()? { let head = expr.head()?; @@ -91,7 +96,7 @@ impl Config { let body = tail.tail()?; //println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); match head { - Some("mode") if let Some(name) = name => load_mode(&self.modes, &name, &body)?, + Some("mode") if let Some(name) = name => self.modes.add(&name, &body)?, Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?, Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?, _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) @@ -101,7 +106,12 @@ impl Config { return Err(format!("Config::load: expected expr, got: {item:?}").into()) } } + pub fn get_mode (&self, mode: impl AsRef) -> Option>>> { - self.modes.clone().read().unwrap().get(mode.as_ref()).cloned() + self.modes.get(mode) } } + +mod views; pub use self::views::*; +mod modes; pub use self::modes::*; +mod mode; pub use self::mode::*; diff --git a/src/app/mode.rs b/src/app/config/mode.rs similarity index 87% rename from src/app/mode.rs rename to src/app/config/mode.rs index e5c5f8ea..83d25ba9 100644 --- a/src/app/mode.rs +++ b/src/app/config/mode.rs @@ -1,15 +1,19 @@ use crate::*; -pub(crate) fn load_mode (modes: &Modes, name: &impl AsRef, body: &impl Language) -> Usually<()> { - let mut mode = Mode::default(); - body.each(|item|mode.add(item))?; - modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); - Ok(()) +/// Group of view and keys definitions. +/// +/// ``` +/// let mode = tek::Mode::>::default(); +/// ``` +#[derive(Default, Debug)] pub struct Mode { + pub path: PathBuf, + pub name: Vec, + pub info: Vec, + pub view: Vec, + pub keys: Vec, + pub modes: Modes, } -/// Collection of interaction modes. -pub type Modes = Arc, Arc>>>>>; - impl Mode> { /// Add a definition to the mode. /// @@ -71,23 +75,9 @@ impl Mode> { } fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> { Ok(Some(if let Some(id) = dsl.head()? { - load_mode(&self.modes, &id, &dsl.tail())?; + self.modes.add(&id, &dsl.tail())?; } else { return Err(format!("Mode::add: self: incomplete: {dsl:?}").into()); })) } } - -/// Group of view and keys definitions. -/// -/// ``` -/// let mode = tek::Mode::>::default(); -/// ``` -#[derive(Default, Debug)] pub struct Mode { - pub path: PathBuf, - pub name: Vec, - pub info: Vec, - pub view: Vec, - pub keys: Vec, - pub modes: Modes, -} diff --git a/src/app/config/modes.rs b/src/app/config/modes.rs new file mode 100644 index 00000000..9ff83991 --- /dev/null +++ b/src/app/config/modes.rs @@ -0,0 +1,27 @@ +use crate::*; + +/// Collection of UI modes. +#[derive(Default, Debug, Clone)] +pub struct Modes( + Arc, Arc>>>>> +); + +impl Modes { + pub fn add (&self, name: &impl AsRef, body: &impl Language) -> Usually<()> { + let mut mode = Mode::default(); + body.each(|item|mode.add(item))?; + self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); + Ok(()) + } + pub fn get (&self, name: impl AsRef) -> Option>>> { + self.0.read().unwrap().get(name.as_ref()).cloned() + } + pub fn for_each (&self, mut ator: impl FnMut(&str, &Mode>)->T) { + for (k, v) in self.0.read().unwrap().iter() { + let _ = ator(k.as_ref(), v.as_ref()); + } + } + pub fn len (&self) -> usize { + self.0.read().unwrap().len() + } +} diff --git a/src/app/config/views.rs b/src/app/config/views.rs new file mode 100644 index 00000000..79560813 --- /dev/null +++ b/src/app/config/views.rs @@ -0,0 +1,10 @@ +use crate::*; + +/// Collection of custom view definitions. +pub type Views = Arc, Arc>>>; + +/// Load custom view definition. +pub(crate) fn load_view (views: &Views, name: &impl AsRef, body: &impl Language) -> Usually<()> { + views.write().unwrap().insert(name.as_ref().into(), body.src()?.unwrap_or_default().into()); + Ok(()) +} diff --git a/src/app/draw.rs b/src/app/draw.rs index 4fcb06d1..0c8578b0 100644 --- a/src/app/draw.rs +++ b/src/app/draw.rs @@ -1,5 +1,9 @@ use crate::*; +tui_view!(|self: App| { + "" +}); + /// The [Draw] implementation for [App] handles the loaded view, /// which is defined in terms of [dizzle] DSL. /// @@ -137,10 +141,10 @@ pub fn draw_dialog ( pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App) -> Usually> { - let modes = state.config.modes.clone(); - let height = (modes.read().unwrap().len() * 2) as u16; + let height = (state.config.modes.len() * 2) as u16; h_exact(height, w_min(Some(30), thunk(move |to: &mut Tui|{ - for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { + let mut index = 0; + state.config.modes.for_each(|id, profile| { let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) }; 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(""); @@ -149,12 +153,613 @@ pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App) let field_name = w_full(origin_w(fg(fg1, name))); let field_id = w_full(origin_e(fg(fg2, id))); let field_info = w_full(origin_w(info)); - y_push((2 * index) as u16, + let _ = y_push((2 * index) as u16, h_exact(2, w_full(bg(b, south( above(field_name, field_id), field_info ))))).draw(to); - } + index += 1; + }); Ok(to.area().into()) }))).draw(to) } + +/// ``` +/// let _ = tek::view_logo(); +/// ``` +pub fn view_logo () -> impl Draw { + wh_exact(Some(32), Some(7), bold(true, fg(Rgb(240, 200, 180), south!{ + h_exact(1, ""), + h_exact(1, ""), + h_exact(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"), + h_exact(1, east("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", east(fg(Rgb(230,100,40), "v0.3.0"), " ~~"))), + h_exact(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"), + }))) +} + +/// ``` +/// let x = ""; +/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref()); +/// let _ = tek::view_transport(false, x.as_ref(), x.as_ref(), x.as_ref()); +/// ``` +pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw { + let theme = ItemTheme::G[96]; + bg(Black, east!(above( + wh_full(origin_w(button_play_pause(play, false))), + wh_full(origin_e(east!( + field_h(theme, "BPM", bpm), + field_h(theme, "Beat", beat), + field_h(theme, "Time", time), + ))) + ))) +} + +/// ``` +/// let x = ""; +/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref()); +/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref()); +/// ``` +pub fn view_status (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw { + let theme = ItemTheme::G[96]; + let sr = field_h(theme, "SR", sr); + let buf = field_h(theme, "Buf", buf); + let lat = field_h(theme, "Lat", lat); + bg(Black, east!(above( + wh_full(origin_w(sel.map(|sel|field_h(theme, "Selected", sel)))), + wh_full(origin_e(east!(sr, buf, lat))), + ))) +} + +/// ``` +/// let _ = tek::button_play_pause(true, true); +/// let _ = tek::button_play_pause(true, false); +/// let _ = tek::button_play_pause(false, true); +/// let _ = tek::button_play_pause(false, false); +/// ``` +pub fn button_play_pause (playing: bool, compact: bool) -> impl Draw { + bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, + either(compact, + thunk(move|to: &mut Tui|w_exact(9, either(playing, + fg(Rgb(0, 255, 0), " PLAYING "), + fg(Rgb(255, 128, 0), " STOPPED ")) + ).draw(to)), + thunk(move|to: &mut Tui|w_exact(5, either(playing, + fg(Rgb(0, 255, 0), south(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), + fg(Rgb(255, 128, 0), south(" ▗▄▖ ", " ▝▀▘ ",))) + ).draw(to)), + ) + ) +} + +#[cfg(feature = "track")] pub fn view_track_row_section ( + _theme: ItemTheme, + button: impl Draw, + button_add: impl Draw, + content: impl Draw, +) -> impl Draw { + west(h_full(w_exact(4, origin_nw(button_add))), + east(w_exact(20, h_full(origin_nw(button))), wh_full(origin_c(content)))) +} + +/// ``` +/// let bg = tengri::ratatui::style::Color::Red; +/// let fg = tengri::ratatui::style::Color::Green; +/// let _ = tek::view_wrap(bg, fg, "and then blue, too!"); +/// ``` +pub fn view_wrap (bg: Color, fg: Color, content: impl Draw) -> impl Draw { + let left = fg_bg(bg, Reset, w_exact(1, y_repeat("▐"))); + let right = fg_bg(bg, Reset, w_exact(1, y_repeat("▌"))); + east(left, west(right, fg_bg(fg, bg, content))) +} + +/// ``` +/// let _ = tek::view_meter("", 0.0); +/// let _ = tek::view_meters(&[0.0, 0.0]); +/// ``` +pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Draw + 'a { + let f = field_h(ItemTheme::G[128], label, format!("{:>+9.3}", value)); + let w = if value >= 0.0 { 13 } + else if value >= -1.0 { 12 } + else if value >= -2.0 { 11 } + else if value >= -3.0 { 10 } + else if value >= -4.0 { 9 } + else if value >= -6.0 { 8 } + else if value >= -9.0 { 7 } + else if value >= -12.0 { 6 } + else if value >= -15.0 { 5 } + else if value >= -20.0 { 4 } + else if value >= -25.0 { 3 } + else if value >= -30.0 { 2 } + else if value >= -40.0 { 1 } + else { 0 }; + let c = if value >= 0.0 { Red } + else if value >= -3.0 { Yellow } + else { Green }; + south!(f, wh_exact(Some(w), Some(1), bg(c, ()))) +} + +pub fn view_meters (values: &[f32;2]) -> impl Draw + use<'_> { + let left = format!("L/{:>+9.3}", values[0]); + let right = format!("R/{:>+9.3}", values[1]); + south(left, right) +} + +pub fn view_sample_info (sample: Option<&Arc>>) -> impl Draw + use<'_> { + when(sample.is_some(), thunk(move|to: &mut Tui|{ + let sample = sample.unwrap().read().unwrap(); + let theme = sample.color; + east!( + field_h(theme, "Name", format!("{:<10}", sample.name.clone())), + field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())), + field_h(theme, "Start", format!("{:<8}", sample.start)), + field_h(theme, "End", format!("{:<8}", sample.end)), + field_h(theme, "Trans", "0"), + field_h(theme, "Gain", format!("{}", sample.gain)), + ).draw(to) + })) +} + +pub fn view_sample_info_v (sample: Option<&Arc>>) -> impl Draw + use<'_> { + let a = thunk(move|to: &mut Tui|{ + let sample = sample.unwrap().read().unwrap(); + let theme = sample.color; + w_exact(20, south!( + w_full(origin_w(field_h(theme, "Name ", format!("{:<10}", sample.name.clone())))), + w_full(origin_w(field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())))), + w_full(origin_w(field_h(theme, "Start ", format!("{:<8}", sample.start)))), + w_full(origin_w(field_h(theme, "End ", format!("{:<8}", sample.end)))), + w_full(origin_w(field_h(theme, "Trans ", "0"))), + w_full(origin_w(field_h(theme, "Gain ", format!("{}", sample.gain)))), + )).draw(to) + }); + + let b = thunk(|to: &mut Tui|fg(Red, south!( + bold(true, "× No sample."), + "[r] record", + "[Shift-F9] import", + )).draw(to)); + + either(sample.is_some(), a, b) +} + +pub fn view_sample_status (sample: Option<&Arc>>) -> impl Draw { + bold(true, fg(g(224), sample + .map(|sample|{ + let sample = sample.read().unwrap(); + format!("Sample {}-{}", sample.start, sample.end) + }) + .unwrap_or_else(||"No sample".to_string()))) +} + +pub fn view_track_header (theme: ItemTheme, content: impl Draw) -> impl Draw { + w_exact(12, bg(theme.darker.term, w_full(origin_e(content)))) +} + +pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) + -> impl Draw + use<'a, T> +{ + let ins = ports.len() as u16; + let frame = Outer(true, Style::default().fg(g(96))); + let iter = move||ports.iter(); + let names = iter_south(iter, move|port, index|h_full(origin_w(format!(" {index} {}", port.port_name())))); + let field = field_v(theme, title, names); + wh_exact(Some(20), Some(1 + ins), border(true, frame, wh_exact(Some(20), Some(1 + ins), field))) +} + +pub fn view_io_ports <'a, T: PortsSizes<'a>> ( + fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a +) -> impl Draw + 'a { + type Item<'a> = (usize, &'a Arc, &'a [Connect], usize, usize); + iter(items, move|(_index, name, connections, y, y2): Item<'a>, _| { + y_push(y as u16, h_exact((y2-y) as u16, + south(h_full(bold(true, fg_bg(fg, bg, origin_w(east(" 󰣲 ", name))))), + iter(||connections.iter(), move|connect: &'a Connect, index|y_push( + index as u16, h_exact(1, origin_w(bold(false, fg_bg(fg, bg, &connect.info)))) + ))))) + }) +} + +pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> ( + scenes: impl Fn()->S, + tracks: impl TracksSizes<'a>, + select: &Selection, + editor: Option<&MidiEditor>, + size: &Size, + editing: bool, +) -> impl Draw { + let status = wh_full(origin_se(fg(Green, format!("{}x{}", size.w(), size.h())))); + let tracks = iter_once(tracks, move|(track_index, track, _, _), _| { + let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| { + let (name, theme): (Arc, ItemTheme) = scene_name_theme(scene, track_index); + let f = theme.lightest.term; + let (b, o) = scene_bg(theme, select, track_index, scene_index); + let w = scene_w(track, select, track_index, editor); + let y = scene_y(select, scene_index, editor); + let is_selected = scene_sel(select, track_index, scene_index, editing); + wh_exact(Some(w), Some(y), below( + wh_full(Outer(true, Style::default().fg(o))), + wh_full(below( + below( + fg_bg(o, b, wh_full("")), + wh_full(origin_nw(fg_bg(f, b, bold(true, name)))), + ), + wh_full(when(is_selected, editor.map(|e|e.view()))))))) + }); + w_exact(track.width as u16, h_full(scenes)) + }); + + return size.of(wh_full(above(status, tracks))); + + fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemTheme) { + if let Some(Some(clip)) = &scene.clips.get(track_index) { + let clip = clip.read().unwrap(); + (format!(" ⏹ {}", &clip.name).into(), clip.color) + } else { + (" ⏹ -- ".into(), ItemTheme::G[32]) + } + } + + fn scene_bg ( + theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize + ) -> (Color, Color) { + let mut outline = theme.base.term; + (if select.track() == Some(track_index) && select.scene() == Some(scene_index) { + outline = theme.lighter.term; + theme.light.term + } else if select.track() == Some(track_index) || select.scene() == Some(scene_index) { + outline = theme.darkest.term; + theme.base.term + } else { + theme.dark.term + }, outline) + } + + fn scene_w ( + track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor> + ) -> u16 { + if select.track() == Some(track_index) && let Some(editor) = editor { + (editor.size.w() as usize).max(24).max(track.width) as u16 + } else { + track.width as u16 + } + } + + fn scene_y ( + select: &Selection, scene_index: usize, editor: Option<&MidiEditor> + ) -> u16 { + if select.scene() == Some(scene_index) && let Some(editor) = editor { + editor.size.h().max(12) + } else { + H_SCENE as u16 + } + } + + fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool { + editing && select.track() == Some(track_index) && select.scene() == Some(scene_index) + } +} + +pub fn view_track_names ( + theme: ItemTheme, + tracks: impl TracksSizes<'_>, + track_count: usize, + scene_count: usize, + selected: &Selection, +) -> impl Draw { + let button = south( + button_3("t", "rack ", format!("{}{track_count}", selected.track() + .map(|track|format!("{track}/")).unwrap_or_default()), false), + button_3("s", "cene ", format!("{}{scene_count}", selected.scene() + .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); + let button_2 = south( + button_2("T", "+", false), + button_2("S", "+", false)); + view_track_row_section(theme, button, button_2, bg(theme.darker.term, + h_exact(2, thunk(|to: &mut Tui|{ + for (index, track, x1, _x2) in tracks { + x_push(x1 as u16, w_exact(track_width(index, track), + bg(if selected.track() == Some(index) { + track.color.light.term + } else { + track.color.base.term + }, south(w_full(origin_nw(east( + format!("·t{index:02} "), + fg(Rgb(255, 255, 255), bold(true, &track.name)) + ))), ""))) ).draw(to); + } + Ok(XYWH(0, 0, 0, 0)) + })))) +} + +pub fn view_track_outputs ( + theme: ItemTheme, tracks: impl TracksSizes<'_>, midi_outs: impl Iterator, +) -> impl Draw { + view_track_row_section(theme, + south(w_full(origin_w(button_2("o", "utput", false))), + thunk(|to: &mut Tui|{ + for port in midi_outs { + w_full(origin_w(port.port_name())).draw(to); + } + Ok(XYWH(0, 0, 0, 0)) + })), + button_2("O", "+", false), + bg(theme.darker.term, origin_w(thunk(|to: &mut Tui|{ + for (index, track, _x1, _x2) in tracks { + let iter = ||track.sequencer.midi_outs.iter(); + let draw = |port: &MidiOutput, _|fg(Rgb(255, 255, 255), + h_exact(1, bg(track.color.dark.term, w_full(origin_w( + format!("·o{index:02} {}", port.port_name())))))); + w_exact(track_width(index, track), + origin_nw(h_full(iter_south(iter, draw)))).draw(to); + } + Ok(XYWH(0, 0, 0, 0)) + })))) +} + +pub fn view_track_inputs ( + theme: ItemTheme, tracks: impl TracksSizes<'_>, height: u16, +) -> impl Draw { + view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), + bg(theme.darker.term, origin_w(thunk(move|to: &mut Tui|{ + for (index, track, _x1, _x2) in tracks { + wh_exact(Some(track_width(index, track)), Some(height + 1), + origin_nw(south( + bg(track.color.base.term, + w_full(origin_w(east!( + either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "), + either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), + either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), + )))), + iter_south(||track.sequencer.midi_ins.iter(), + |port, _|fg_bg(Rgb(255, 255, 255), track.color.dark.term, + w_full(origin_w(format!("·i{index:02} {}", port.port_name())))))))) + .draw(to); + } + Ok(XYWH(0, 0, 0, 0)) + })))) +} + +pub fn view_scenes_names ( + scenes: impl ScenesSizes<'_>, + select: &Selection, + editor: Option<&MidiEditor>, + editing: bool, +) -> impl Draw { + w_exact(20, thunk(move |to: &mut Tui|{ + for (index, scene, ..) in scenes { + view_scene_name(select, editor, index, scene, editing).draw(to); + } + Ok(XYWH(1, 1, 1, 1)) + })) +} + +pub fn view_scene_name ( + select: &Selection, + editor: Option<&MidiEditor>, + index: usize, + scene: &Scene, + editing: bool +) -> impl Draw { + let h = if select.scene() == Some(index) && let Some(_editor) = editor { + 7 + } else { + H_SCENE as u16 + }; + let a = w_full(origin_w(east(format!("·s{index:02} "), + fg(g(255), bold(true, &scene.name))))); + let b = when(select.scene() == Some(index) && editing, + wh_full(origin_nw(south( + editor.as_ref().map(|e|e.clip_status()), + editor.as_ref().map(|e|e.edit_status()))))); + let c = if select.scene() == Some(index) { + scene.color.light.term + } else { + scene.color.base.term + }; + wh_exact(Some(20), Some(h), bg(c, origin_nw(south(a, b)))) +} + +pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins)) +} + +pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs)) +} + +pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins())) +} + +pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) +} + +pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a +) -> impl Draw { + iter_east(tracks, move|(index, track, x1, x2): (usize, &Track, usize, usize), _|{ + w_exact((x2 - x1) as u16, fg_bg( + track.color.lightest.term, + track.color.base.term, + callback(index, track))) + }) +} + +pub fn view_per_track () -> impl Draw {} + +pub fn view_per_track_top () -> impl Draw {} + +pub fn view_inputs (tracks: impl TracksSizes<'_>, midi_ins: &[MidiInput]) -> impl Draw { + let title_1 = wh_exact(Some(20), Some(1), origin_w(button_3("i", "nput ", format!("{}", midi_ins.len()), false))); + let title_2 = wh_exact(Some(4), Some(1), w_exact(4, button_2("I", "+", false))); + east(title_1, west(title_2, thunk(move|to: &mut Tui|{ + for (_index, track, x1, _x2) in tracks { + south( + x_push(x1 as u16, bg(track.color.dark.term, origin_w(w_exact(track.width as u16, east!( + either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), + either(track.sequencer.recording, fg(Red, "rec "), "rec "), + either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "), + ))))), + thunk(move |to: &mut Tui|{ + for (index, port) in midi_ins.iter().enumerate() { + x_push(index as u16 * 10, h_exact(1, east(w_exact(20, origin_w(east(" ● ", bold(true, fg(Rgb(255,255,255), port.port_name()))))), + west(w_exact(4, ()), thunk(move|to: &mut Tui|{ + bg(track.color.darker.term, origin_w(w_exact(track.width as u16, east!( + either(track.sequencer.monitoring, fg(Green, " ● "), " · "), + either(track.sequencer.recording, fg(Red, " ● "), " · "), + either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), + )))).draw(to); + todo!() + }))))).draw(to); + } + todo!() + }) + ).draw(to); + } + todo!() + }))) +} + +pub fn view_outputs ( + theme: ItemTheme, + tracks: impl TracksSizes<'_>, + midi_outs: &[MidiOutput], + height: u16, +) -> impl Draw { + + let list = south( + h_exact(1, w_full(origin_w(button_3( + "o", "utput", format!("{}", midi_outs.len()), false + )))), + h_exact(height - 1, wh_full(origin_nw(thunk(|to: &mut Tui|{ + for (_index, port) in midi_outs.iter().enumerate() { + h_exact(1,w_full(east( + origin_w(east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name())))), + w_full(origin_e(format!("{}/{} ", + port.port().get_connections().len(), + port.connections.len())))))).draw(to); + for (index, conn) in port.connections.iter().enumerate() { + h_exact(1, w_full(origin_w(format!(" c{index:02}{}", conn.info())))) + .draw(to); + } + } + todo!(); + })))) + ); + + h_exact(height, view_track_row_section(theme, list, button_2("O", "+", false), + bg(theme.darker.term, origin_w(w_full( + thunk(|to: &mut Tui|{ + for (index, track, _x1, _x2) in tracks { + w_exact(track_width(index, track), + thunk(|to: &mut Tui|{ + h_exact(1, origin_w(east( + either(true, fg(Green, "play "), "play "), + either(false, fg(Yellow, "solo "), "solo "), + ))).draw(to); + for (_index, port) in midi_outs.iter().enumerate() { + h_exact(1, origin_w(east( + either(true, fg(Green, " ● "), " · "), + either(false, fg(Yellow, " ● "), " · "), + ))).draw(to); + for (_index, _conn) in port.connections.iter().enumerate() { + h_exact(1, w_full("")).draw(to); + } + } + todo!() + }) + ).draw(to); + } + todo!() + }) + ))) + )) +} + +pub fn view_track_devices ( + theme: ItemTheme, + tracks: impl TracksSizes<'_>, + track: Option<&Track>, + h: u16, +) -> impl Draw { + view_track_row_section(theme, + button_3("d", "evice", format!("{}", track.map(|t|t.devices.len()).unwrap_or(0)), false), + button_2("D", "+", false), + iter_once(tracks, move|(_, track, _x1, _x2), index| wh_exact( + Some(track_width(index, track)), + Some(h + 1), + bg(track.color.dark.term, origin_nw(iter_south(move||0..h, + |_, _index|wh_exact(Some(track.width as u16), Some(2), + fg_bg( + ItemTheme::G[32].lightest.term, + ItemTheme::G[32].dark.term, + origin_nw(format!(" · {}", "--")) + ) + ))))))) +} + +pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a +) -> impl Draw + 'a { + per_track_top(tracks, move|index, track|h_full(origin_y(callback(index, track)))) +} + +pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a +) -> impl Draw + 'a { + origin_x(bg(Reset, iter_east(tracks, + move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ + w_exact((x2 - x1) as u16, fg_bg( + track.color.lightest.term, + track.color.base.term, + callback(index, track))) }))) +} + +pub fn field_h (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { +} + +pub fn field_v (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { +} + +pub fn view_sessions () -> impl Draw { + let h = 6; + let w = Some(30); + let f = Rgb(224, 192, 128); + h_exact(h, w_min(w, thunk(move |to: &mut Tui|{ + for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { + let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) }; + let y = (2 * index) as u16; + let h = 2; + y_push(y, &h_exact(h, w_full(bg(b, origin_w(fg(f, name)))))).draw(to); + } + Ok(to.area().into()) + }))) +} + +pub fn view_browse_title (state: &App) -> impl Draw { + w_full(origin_w(field_v(ItemTheme::default(), + match state.dialog.browser_target().unwrap() { + BrowseTarget::SaveProject => "Save project:", + BrowseTarget::LoadProject => "Load project:", + BrowseTarget::ImportSample(_) => "Import sample:", + BrowseTarget::ExportSample(_) => "Export sample:", + BrowseTarget::ImportClip(_) => "Import clip:", + BrowseTarget::ExportClip(_) => "Export clip:", + }, h_exact(1, fg(g(96), x_repeat("🭻"))) + ))) +} + +pub fn view_device (state: &App) -> impl Draw { + let selected = state.dialog.device_kind().unwrap(); + south(bold(true, "Add device"), iter_south( + move||device_kinds().iter(), + move|_label: &&'static str, i|{ + let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; + let l = if i == selected { "[ " } else { " " }; + let r = if i == selected { " ]" } else { " " }; + w_full(bg(b, east(l, west(r, "FIXME device name")))) })) +} diff --git a/src/app/size.rs b/src/app/size.rs index 843328c8..2d336b34 100644 --- a/src/app/size.rs +++ b/src/app/size.rs @@ -16,10 +16,6 @@ def_sizes_iter!(PortsSizes => Arc, [Connect]); def_sizes_iter!(ScenesSizes => Scene); def_sizes_iter!(TracksSizes => Track); -pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { - track.width as u16 -} - pub trait HasWidth { const MIN_WIDTH: usize; /// Increment track width. @@ -27,31 +23,3 @@ pub trait HasWidth { /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. fn width_dec (&mut self); } - -impl HasClipsSize for App { - fn clips_size (&self) -> &Size { &self.project.size_inner } -} - -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 ScenesView for App { - fn w_mid (&self) -> u16 { - (self.size.w() as u16).saturating_sub(self.w_side()) - } - fn w_side (&self) -> u16 { - 20 - } - fn h_scenes (&self) -> u16 { - (self.size.h() as u16).saturating_sub(20) - } -} diff --git a/src/app/view.rs b/src/app/view.rs deleted file mode 100644 index 27b9313a..00000000 --- a/src/app/view.rs +++ /dev/null @@ -1,614 +0,0 @@ -use crate::*; - -tui_view!(|self: App| { - "" -}); - -/// Collection of custom view definitions. -pub type Views = Arc, Arc>>>; - -/// Load custom view definition. -pub(crate) fn load_view (views: &Views, name: &impl AsRef, body: &impl Language) -> Usually<()> { - views.write().unwrap().insert(name.as_ref().into(), body.src()?.unwrap_or_default().into()); - Ok(()) -} - -/// ``` -/// let _ = tek::view_logo(); -/// ``` -pub fn view_logo () -> impl Draw { - wh_exact(Some(32), Some(7), bold(true, fg(Rgb(240, 200, 180), south!{ - h_exact(1, ""), - h_exact(1, ""), - h_exact(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"), - h_exact(1, east("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", east(fg(Rgb(230,100,40), "v0.3.0"), " ~~"))), - h_exact(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"), - }))) -} - -/// ``` -/// let x = ""; -/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref()); -/// let _ = tek::view_transport(false, x.as_ref(), x.as_ref(), x.as_ref()); -/// ``` -pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw { - let theme = ItemTheme::G[96]; - bg(Black, east!(above( - wh_full(origin_w(button_play_pause(play, false))), - wh_full(origin_e(east!( - field_h(theme, "BPM", bpm), - field_h(theme, "Beat", beat), - field_h(theme, "Time", time), - ))) - ))) -} - -/// ``` -/// let x = ""; -/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref()); -/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref()); -/// ``` -pub fn view_status (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw { - let theme = ItemTheme::G[96]; - let sr = field_h(theme, "SR", sr); - let buf = field_h(theme, "Buf", buf); - let lat = field_h(theme, "Lat", lat); - bg(Black, east!(above( - wh_full(origin_w(sel.map(|sel|field_h(theme, "Selected", sel)))), - wh_full(origin_e(east!(sr, buf, lat))), - ))) -} - -/// ``` -/// let _ = tek::button_play_pause(true, true); -/// let _ = tek::button_play_pause(true, false); -/// let _ = tek::button_play_pause(false, true); -/// let _ = tek::button_play_pause(false, false); -/// ``` -pub fn button_play_pause (playing: bool, compact: bool) -> impl Draw { - bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, - either(compact, - thunk(move|to: &mut Tui|w_exact(9, either(playing, - fg(Rgb(0, 255, 0), " PLAYING "), - fg(Rgb(255, 128, 0), " STOPPED ")) - ).draw(to)), - thunk(move|to: &mut Tui|w_exact(5, either(playing, - fg(Rgb(0, 255, 0), south(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), - fg(Rgb(255, 128, 0), south(" ▗▄▖ ", " ▝▀▘ ",))) - ).draw(to)), - ) - ) -} - -#[cfg(feature = "track")] pub fn view_track_row_section ( - _theme: ItemTheme, - button: impl Draw, - button_add: impl Draw, - content: impl Draw, -) -> impl Draw { - west(h_full(w_exact(4, origin_nw(button_add))), - east(w_exact(20, h_full(origin_nw(button))), wh_full(origin_c(content)))) -} - -/// ``` -/// let bg = tengri::ratatui::style::Color::Red; -/// let fg = tengri::ratatui::style::Color::Green; -/// let _ = tek::view_wrap(bg, fg, "and then blue, too!"); -/// ``` -pub fn view_wrap (bg: Color, fg: Color, content: impl Draw) -> impl Draw { - let left = fg_bg(bg, Reset, w_exact(1, y_repeat("▐"))); - let right = fg_bg(bg, Reset, w_exact(1, y_repeat("▌"))); - east(left, west(right, fg_bg(fg, bg, content))) -} - -/// ``` -/// let _ = tek::view_meter("", 0.0); -/// let _ = tek::view_meters(&[0.0, 0.0]); -/// ``` -pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Draw + 'a { - let f = field_h(ItemTheme::G[128], label, format!("{:>+9.3}", value)); - let w = if value >= 0.0 { 13 } - else if value >= -1.0 { 12 } - else if value >= -2.0 { 11 } - else if value >= -3.0 { 10 } - else if value >= -4.0 { 9 } - else if value >= -6.0 { 8 } - else if value >= -9.0 { 7 } - else if value >= -12.0 { 6 } - else if value >= -15.0 { 5 } - else if value >= -20.0 { 4 } - else if value >= -25.0 { 3 } - else if value >= -30.0 { 2 } - else if value >= -40.0 { 1 } - else { 0 }; - let c = if value >= 0.0 { Red } - else if value >= -3.0 { Yellow } - else { Green }; - south!(f, wh_exact(Some(w), Some(1), bg(c, ()))) -} - -pub fn view_meters (values: &[f32;2]) -> impl Draw + use<'_> { - let left = format!("L/{:>+9.3}", values[0]); - let right = format!("R/{:>+9.3}", values[1]); - south(left, right) -} - -pub fn view_sample_info (sample: Option<&Arc>>) -> impl Draw + use<'_> { - when(sample.is_some(), thunk(move|to: &mut Tui|{ - let sample = sample.unwrap().read().unwrap(); - let theme = sample.color; - east!( - field_h(theme, "Name", format!("{:<10}", sample.name.clone())), - field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())), - field_h(theme, "Start", format!("{:<8}", sample.start)), - field_h(theme, "End", format!("{:<8}", sample.end)), - field_h(theme, "Trans", "0"), - field_h(theme, "Gain", format!("{}", sample.gain)), - ).draw(to) - })) -} - -pub fn view_sample_info_v (sample: Option<&Arc>>) -> impl Draw + use<'_> { - let a = thunk(move|to: &mut Tui|{ - let sample = sample.unwrap().read().unwrap(); - let theme = sample.color; - w_exact(20, south!( - w_full(origin_w(field_h(theme, "Name ", format!("{:<10}", sample.name.clone())))), - w_full(origin_w(field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())))), - w_full(origin_w(field_h(theme, "Start ", format!("{:<8}", sample.start)))), - w_full(origin_w(field_h(theme, "End ", format!("{:<8}", sample.end)))), - w_full(origin_w(field_h(theme, "Trans ", "0"))), - w_full(origin_w(field_h(theme, "Gain ", format!("{}", sample.gain)))), - )).draw(to) - }); - - let b = thunk(|to: &mut Tui|fg(Red, south!( - bold(true, "× No sample."), - "[r] record", - "[Shift-F9] import", - )).draw(to)); - - either(sample.is_some(), a, b) -} - -pub fn view_sample_status (sample: Option<&Arc>>) -> impl Draw { - bold(true, fg(g(224), sample - .map(|sample|{ - let sample = sample.read().unwrap(); - format!("Sample {}-{}", sample.start, sample.end) - }) - .unwrap_or_else(||"No sample".to_string()))) -} - -pub fn view_track_header (theme: ItemTheme, content: impl Draw) -> impl Draw { - w_exact(12, bg(theme.darker.term, w_full(origin_e(content)))) -} - -pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) - -> impl Draw + use<'a, T> -{ - let ins = ports.len() as u16; - let frame = Outer(true, Style::default().fg(g(96))); - let iter = move||ports.iter(); - let names = iter_south(iter, move|port, index|h_full(origin_w(format!(" {index} {}", port.port_name())))); - let field = field_v(theme, title, names); - wh_exact(Some(20), Some(1 + ins), border(true, frame, wh_exact(Some(20), Some(1 + ins), field))) -} - -pub fn view_io_ports <'a, T: PortsSizes<'a>> ( - fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a -) -> impl Draw + 'a { - type Item<'a> = (usize, &'a Arc, &'a [Connect], usize, usize); - iter(items, move|(_index, name, connections, y, y2): Item<'a>, _| { - y_push(y as u16, h_exact((y2-y) as u16, - south(h_full(bold(true, fg_bg(fg, bg, origin_w(east(" 󰣲 ", name))))), - iter(||connections.iter(), move|connect: &'a Connect, index|y_push( - index as u16, h_exact(1, origin_w(bold(false, fg_bg(fg, bg, &connect.info)))) - ))))) - }) -} - -pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> ( - scenes: impl Fn()->S, - tracks: impl TracksSizes<'a>, - select: &Selection, - editor: Option<&MidiEditor>, - size: &Size, - editing: bool, -) -> impl Draw { - let status = wh_full(origin_se(fg(Green, format!("{}x{}", size.w(), size.h())))); - let tracks = iter_once(tracks, move|(track_index, track, _, _), _| { - let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| { - let (name, theme): (Arc, ItemTheme) = scene_name_theme(scene, track_index); - let f = theme.lightest.term; - let (b, o) = scene_bg(theme, select, track_index, scene_index); - let w = scene_w(track, select, track_index, editor); - let y = scene_y(select, scene_index, editor); - let is_selected = scene_sel(select, track_index, scene_index, editing); - wh_exact(Some(w), Some(y), below( - wh_full(Outer(true, Style::default().fg(o))), - wh_full(below( - below( - fg_bg(o, b, wh_full("")), - wh_full(origin_nw(fg_bg(f, b, bold(true, name)))), - ), - wh_full(when(is_selected, editor.map(|e|e.view()))))))) - }); - w_exact(track.width as u16, h_full(scenes)) - }); - - return size.of(wh_full(above(status, tracks))); - - fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemTheme) { - if let Some(Some(clip)) = &scene.clips.get(track_index) { - let clip = clip.read().unwrap(); - (format!(" ⏹ {}", &clip.name).into(), clip.color) - } else { - (" ⏹ -- ".into(), ItemTheme::G[32]) - } - } - - fn scene_bg ( - theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize - ) -> (Color, Color) { - let mut outline = theme.base.term; - (if select.track() == Some(track_index) && select.scene() == Some(scene_index) { - outline = theme.lighter.term; - theme.light.term - } else if select.track() == Some(track_index) || select.scene() == Some(scene_index) { - outline = theme.darkest.term; - theme.base.term - } else { - theme.dark.term - }, outline) - } - - fn scene_w ( - track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor> - ) -> u16 { - if select.track() == Some(track_index) && let Some(editor) = editor { - (editor.size.w() as usize).max(24).max(track.width) as u16 - } else { - track.width as u16 - } - } - - fn scene_y ( - select: &Selection, scene_index: usize, editor: Option<&MidiEditor> - ) -> u16 { - if select.scene() == Some(scene_index) && let Some(editor) = editor { - editor.size.h().max(12) - } else { - H_SCENE as u16 - } - } - - fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool { - editing && select.track() == Some(track_index) && select.scene() == Some(scene_index) - } -} - -pub fn view_track_names ( - theme: ItemTheme, - tracks: impl TracksSizes<'_>, - track_count: usize, - scene_count: usize, - selected: &Selection, -) -> impl Draw { - let button = south( - button_3("t", "rack ", format!("{}{track_count}", selected.track() - .map(|track|format!("{track}/")).unwrap_or_default()), false), - button_3("s", "cene ", format!("{}{scene_count}", selected.scene() - .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); - let button_2 = south( - button_2("T", "+", false), - button_2("S", "+", false)); - view_track_row_section(theme, button, button_2, bg(theme.darker.term, - h_exact(2, thunk(|to: &mut Tui|{ - for (index, track, x1, _x2) in tracks { - x_push(x1 as u16, w_exact(track_width(index, track), - bg(if selected.track() == Some(index) { - track.color.light.term - } else { - track.color.base.term - }, south(w_full(origin_nw(east( - format!("·t{index:02} "), - fg(Rgb(255, 255, 255), bold(true, &track.name)) - ))), ""))) ).draw(to); - } - Ok(XYWH(0, 0, 0, 0)) - })))) -} - -pub fn view_track_outputs ( - theme: ItemTheme, tracks: impl TracksSizes<'_>, midi_outs: impl Iterator, -) -> impl Draw { - view_track_row_section(theme, - south(w_full(origin_w(button_2("o", "utput", false))), - thunk(|to: &mut Tui|{ - for port in midi_outs { - w_full(origin_w(port.port_name())).draw(to); - } - Ok(XYWH(0, 0, 0, 0)) - })), - button_2("O", "+", false), - bg(theme.darker.term, origin_w(thunk(|to: &mut Tui|{ - for (index, track, _x1, _x2) in tracks { - let iter = ||track.sequencer.midi_outs.iter(); - let draw = |port: &MidiOutput, _|fg(Rgb(255, 255, 255), - h_exact(1, bg(track.color.dark.term, w_full(origin_w( - format!("·o{index:02} {}", port.port_name())))))); - w_exact(track_width(index, track), - origin_nw(h_full(iter_south(iter, draw)))).draw(to); - } - Ok(XYWH(0, 0, 0, 0)) - })))) -} - -pub fn view_track_inputs ( - theme: ItemTheme, tracks: impl TracksSizes<'_>, height: u16, -) -> impl Draw { - view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), - bg(theme.darker.term, origin_w(thunk(move|to: &mut Tui|{ - for (index, track, _x1, _x2) in tracks { - wh_exact(Some(track_width(index, track)), Some(height + 1), - origin_nw(south( - bg(track.color.base.term, - w_full(origin_w(east!( - either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "), - either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), - either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), - )))), - iter_south(||track.sequencer.midi_ins.iter(), - |port, _|fg_bg(Rgb(255, 255, 255), track.color.dark.term, - w_full(origin_w(format!("·i{index:02} {}", port.port_name())))))))) - .draw(to); - } - Ok(XYWH(0, 0, 0, 0)) - })))) -} - -pub fn view_scenes_names ( - scenes: impl ScenesSizes<'_>, - select: &Selection, - editor: Option<&MidiEditor>, - editing: bool, -) -> impl Draw { - w_exact(20, thunk(move |to: &mut Tui|{ - for (index, scene, ..) in scenes { - view_scene_name(select, editor, index, scene, editing).draw(to); - } - Ok(XYWH(1, 1, 1, 1)) - })) -} - -pub fn view_scene_name ( - select: &Selection, - editor: Option<&MidiEditor>, - index: usize, - scene: &Scene, - editing: bool -) -> impl Draw { - let h = if select.scene() == Some(index) && let Some(_editor) = editor { - 7 - } else { - H_SCENE as u16 - }; - let a = w_full(origin_w(east(format!("·s{index:02} "), - fg(g(255), bold(true, &scene.name))))); - let b = when(select.scene() == Some(index) && editing, - wh_full(origin_nw(south( - editor.as_ref().map(|e|e.clip_status()), - editor.as_ref().map(|e|e.edit_status()))))); - let c = if select.scene() == Some(index) { - scene.color.light.term - } else { - scene.color.base.term - }; - wh_exact(Some(20), Some(h), bg(c, origin_nw(south(a, b)))) -} - -pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins)) -} - -pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs)) -} - -pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins())) -} - -pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) -} - -pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a -) -> impl Draw { - iter_east(tracks, move|(index, track, x1, x2): (usize, &Track, usize, usize), _|{ - w_exact((x2 - x1) as u16, fg_bg( - track.color.lightest.term, - track.color.base.term, - callback(index, track))) - }) -} - -pub fn view_per_track () -> impl Draw {} - -pub fn view_per_track_top () -> impl Draw {} - -pub fn view_inputs (tracks: impl TracksSizes<'_>, midi_ins: &[MidiInput]) -> impl Draw { - let title_1 = wh_exact(Some(20), Some(1), origin_w(button_3("i", "nput ", format!("{}", midi_ins.len()), false))); - let title_2 = wh_exact(Some(4), Some(1), w_exact(4, button_2("I", "+", false))); - east(title_1, west(title_2, thunk(move|to: &mut Tui|{ - for (_index, track, x1, _x2) in tracks { - south( - x_push(x1 as u16, bg(track.color.dark.term, origin_w(w_exact(track.width as u16, east!( - either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), - either(track.sequencer.recording, fg(Red, "rec "), "rec "), - either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "), - ))))), - thunk(move |to: &mut Tui|{ - for (index, port) in midi_ins.iter().enumerate() { - x_push(index as u16 * 10, h_exact(1, east(w_exact(20, origin_w(east(" ● ", bold(true, fg(Rgb(255,255,255), port.port_name()))))), - west(w_exact(4, ()), thunk(move|to: &mut Tui|{ - bg(track.color.darker.term, origin_w(w_exact(track.width as u16, east!( - either(track.sequencer.monitoring, fg(Green, " ● "), " · "), - either(track.sequencer.recording, fg(Red, " ● "), " · "), - either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), - )))).draw(to); - todo!() - }))))).draw(to); - } - todo!() - }) - ).draw(to); - } - todo!() - }))) -} - -pub fn view_outputs ( - theme: ItemTheme, - tracks: impl TracksSizes<'_>, - midi_outs: &[MidiOutput], - height: u16, -) -> impl Draw { - - let list = south( - h_exact(1, w_full(origin_w(button_3( - "o", "utput", format!("{}", midi_outs.len()), false - )))), - h_exact(height - 1, wh_full(origin_nw(thunk(|to: &mut Tui|{ - for (_index, port) in midi_outs.iter().enumerate() { - h_exact(1,w_full(east( - origin_w(east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name())))), - w_full(origin_e(format!("{}/{} ", - port.port().get_connections().len(), - port.connections.len())))))).draw(to); - for (index, conn) in port.connections.iter().enumerate() { - h_exact(1, w_full(origin_w(format!(" c{index:02}{}", conn.info())))) - .draw(to); - } - } - todo!(); - })))) - ); - - h_exact(height, view_track_row_section(theme, list, button_2("O", "+", false), - bg(theme.darker.term, origin_w(w_full( - thunk(|to: &mut Tui|{ - for (index, track, _x1, _x2) in tracks { - w_exact(track_width(index, track), - thunk(|to: &mut Tui|{ - h_exact(1, origin_w(east( - either(true, fg(Green, "play "), "play "), - either(false, fg(Yellow, "solo "), "solo "), - ))).draw(to); - for (_index, port) in midi_outs.iter().enumerate() { - h_exact(1, origin_w(east( - either(true, fg(Green, " ● "), " · "), - either(false, fg(Yellow, " ● "), " · "), - ))).draw(to); - for (_index, _conn) in port.connections.iter().enumerate() { - h_exact(1, w_full("")).draw(to); - } - } - todo!() - }) - ).draw(to); - } - todo!() - }) - ))) - )) -} - -pub fn view_track_devices ( - theme: ItemTheme, - tracks: impl TracksSizes<'_>, - track: Option<&Track>, - h: u16, -) -> impl Draw { - view_track_row_section(theme, - button_3("d", "evice", format!("{}", track.map(|t|t.devices.len()).unwrap_or(0)), false), - button_2("D", "+", false), - iter_once(tracks, move|(_, track, _x1, _x2), index| wh_exact( - Some(track_width(index, track)), - Some(h + 1), - bg(track.color.dark.term, origin_nw(iter_south(move||0..h, - |_, _index|wh_exact(Some(track.width as u16), Some(2), - fg_bg( - ItemTheme::G[32].lightest.term, - ItemTheme::G[32].dark.term, - origin_nw(format!(" · {}", "--")) - ) - ))))))) -} - -pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a -) -> impl Draw + 'a { - per_track_top(tracks, move|index, track|h_full(origin_y(callback(index, track)))) -} - -pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a -) -> impl Draw + 'a { - origin_x(bg(Reset, iter_east(tracks, - move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ - w_exact((x2 - x1) as u16, fg_bg( - track.color.lightest.term, - track.color.base.term, - callback(index, track))) }))) -} - -pub fn field_h (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { -} - -pub fn field_v (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { -} - -pub fn view_sessions () -> impl Draw { - let h = 6; - let w = Some(30); - let f = Rgb(224, 192, 128); - h_exact(h, w_min(w, thunk(move |to: &mut Tui|{ - for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { - let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) }; - let y = (2 * index) as u16; - let h = 2; - y_push(y, &h_exact(h, w_full(bg(b, origin_w(fg(f, name)))))).draw(to); - } - Ok(to.area().into()) - }))) -} - -pub fn view_browse_title (state: &App) -> impl Draw { - w_full(origin_w(field_v(ItemTheme::default(), - match state.dialog.browser_target().unwrap() { - BrowseTarget::SaveProject => "Save project:", - BrowseTarget::LoadProject => "Load project:", - BrowseTarget::ImportSample(_) => "Import sample:", - BrowseTarget::ExportSample(_) => "Export sample:", - BrowseTarget::ImportClip(_) => "Import clip:", - BrowseTarget::ExportClip(_) => "Export clip:", - }, h_exact(1, fg(g(96), x_repeat("🭻"))) - ))) -} - -pub fn view_device (state: &App) -> impl Draw { - let selected = state.dialog.device_kind().unwrap(); - south(bold(true, "Add device"), iter_south( - move||device_kinds().iter(), - move|_label: &&'static str, i|{ - let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; - let l = if i == selected { "[ " } else { " " }; - let r = if i == selected { " ]" } else { " " }; - w_full(bg(b, east(l, west(r, "FIXME device name")))) })) -} diff --git a/src/deps.rs b/src/deps.rs new file mode 100644 index 00000000..ad94c204 --- /dev/null +++ b/src/deps.rs @@ -0,0 +1,33 @@ +#[cfg(feature = "cli")] +pub(crate) use ::clap::{self, Parser, Subcommand}; + +pub(crate) use ::xdg::BaseDirectories; + +pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*}; + +pub(crate) use tengri::{ + *, lang::*, exit::*, eval::*, sing::*, time::*, draw::*, term::*, color::*, task::*, + crossterm::event::{Event, KeyEvent}, + ratatui::{ + self, + prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, + widgets::{Widget, canvas::{Canvas, Line}}, + }, +}; + +#[allow(unused)] +pub(crate) use ::{ + std::{ + cmp::Ord, + collections::BTreeMap, + error::Error, + ffi::OsString, + fmt::{Write, Debug, Formatter}, + fs::File, + ops::{Add, Sub, Mul, Div, Rem}, + path::{Path, PathBuf}, + sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}}, + time::Duration, + thread::{spawn, JoinHandle}, + }, +}; diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs index 05c7b9c5..06679935 100644 --- a/src/device/arrange/clip.rs +++ b/src/device/arrange/clip.rs @@ -26,7 +26,7 @@ use crate::*; pub trait ClipsView: TracksView + ScenesView { /// Draw clips per scene fn view_scenes_clips <'a> (&'a self) -> impl Draw + 'a { - crate::view::view_scenes_clips( + view_scenes_clips( ||self.scenes_with_sizes(), self.tracks_with_sizes(), self.selection(), @@ -87,3 +87,7 @@ impl Arrangement { } pub trait HasClipsSize { fn clips_size (&self) -> &Size; } + +impl HasClipsSize for App { + fn clips_size (&self) -> &Size { &self.project.size_inner } +} diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs index 2d1c284e..d7c57966 100644 --- a/src/device/arrange/scene.rs +++ b/src/device/arrange/scene.rs @@ -62,7 +62,7 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + fn w_mid (&self) -> u16; fn view_scenes_names (&self) -> impl Draw { - crate::view::view_scenes_names( + view_scenes_names( self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing() ) } @@ -130,16 +130,14 @@ impl HasSceneScroll for Arrangement { fn scene_scroll (&self) -> usize { self.scene_scroll } } +impl HasSceneScroll for App { + fn scene_scroll (&self) -> usize { self.project.scene_scroll() } +} + impl ScenesView for Arrangement { - fn h_scenes (&self) -> u16 { - (self.size.h() as u16).saturating_sub(20) - } - fn w_side (&self) -> u16 { - (self.size.w() as u16 * 2 / 10).max(20) - } - fn w_mid (&self) -> u16 { - (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) - } + fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } + fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) } + fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) } } pub type SceneWith<'a, T> = @@ -148,7 +146,7 @@ pub type SceneWith<'a, T> = def_command!(SceneCommand: |scene: Scene| { SetSize { size: usize } => { todo!() }, SetZoom { size: usize } => { todo!() }, - SetName { name: Arc } => + SetName { name: Arc } => swap_value(&mut scene.name, name, |name|Self::SetName{name}), SetColor { color: ItemTheme } => swap_value(&mut scene.color, color, |color|Self::SetColor{color}), @@ -158,7 +156,19 @@ def_command!(SceneCommand: |scene: Scene| { #[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); #[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene()); #[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut()); -impl ClipsView for T {} -impl_has!(Vec: |self: Arrangement| self.scenes); -impl>+AsMut>> HasScenes for T {} -impl+AsMutOpt+Send+Sync> HasScene for T {} +impl_has!(Vec: |self: Arrangement| self.scenes); +impl ClipsView for T {} +impl>+AsMut>> HasScenes for T {} +impl+AsMutOpt+Send+Sync> HasScene for T {} + +impl ScenesView for App { + fn w_mid (&self) -> u16 { + (self.size.w() as u16).saturating_sub(self.w_side()) + } + fn w_side (&self) -> u16 { + 20 + } + fn h_scenes (&self) -> u16 { + (self.size.h() as u16).saturating_sub(20) + } +} diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs index 5ea42948..c480f694 100644 --- a/src/device/arrange/track.rs +++ b/src/device/arrange/track.rs @@ -55,9 +55,13 @@ impl Track { ) -> impl Draw + 'a { view_track_per(tracks, callback) } +} + +#[cfg(feature = "sampler")] +impl Track { /// Create a new track connecting the [Sequencer] to a [Sampler]. - #[cfg(feature = "sampler")] pub fn new_with_sampler ( + pub fn new_with_sampler ( name: &impl AsRef, color: Option, jack: &Jack<'static>, @@ -78,7 +82,7 @@ impl Track { Ok(track) } - #[cfg(feature = "sampler")] pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { + pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { for device in self.devices.iter() { match device { Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, @@ -88,7 +92,7 @@ impl Track { None } - #[cfg(feature = "sampler")] pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { + pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { for device in self.devices.iter_mut() { match device { Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, @@ -153,22 +157,22 @@ pub trait HasTrack: AsRefOpt + AsMutOpt { #[cfg(feature = "port")] fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw + 'a { - crate::view::view_midi_ins_status(theme, self.track()) + view_midi_ins_status(theme, self.track()) } #[cfg(feature = "port")] fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw + '_ { - crate::view::view_midi_outs_status(theme, self.track()) + view_midi_outs_status(theme, self.track()) } #[cfg(feature = "port")] fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw { - crate::view::view_audio_ins_status(theme, self.track()) + view_audio_ins_status(theme, self.track()) } #[cfg(feature = "port")] fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw { - crate::view::view_audio_outs_status(theme, self.track()) + view_audio_outs_status(theme, self.track()) } } @@ -179,13 +183,13 @@ pub trait HasTrackScroll: HasTracks { pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { /// Draw name of each track fn view_track_names (&self, theme: ItemTheme) -> impl Draw { - crate::view::view_track_names( + view_track_names( theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection() ) } /// Draw outputs per track fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { - crate::view::view_track_outputs( + view_track_outputs( theme, self.tracks_with_sizes(), self.midi_outs().iter() ) } @@ -195,7 +199,7 @@ pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } - crate::view::view_track_inputs(theme, self.tracks_with_sizes(), h) + view_track_inputs(theme, self.tracks_with_sizes(), h) } /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { @@ -247,19 +251,19 @@ impl_as_mut!(Vec: |self: App| self.project.as_mut()); impl Arrangement { pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { - crate::view::view_inputs( + view_inputs( self.tracks_with_sizes(), self.midi_ins().as_slice() ) } pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw { - crate::view::view_outputs( + view_outputs( theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height() ) } pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { - crate::view::view_track_devices( + view_track_devices( theme, self.tracks_with_sizes(), self.track(), self.devices_height() ) } @@ -334,3 +338,13 @@ impl Arrangement { Ok((index, &mut self.tracks_mut()[index])) } } + +impl HasTrackScroll for App { + fn track_scroll (&self) -> usize { + self.project.track_scroll() + } +} + +pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { + track.width as u16 +} diff --git a/src/device/clock.rs b/src/device/clock.rs index 11a85607..ab53bbe5 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -145,6 +145,27 @@ impl Quantize { } } +/// Implement an arithmetic operation for a unit of time +#[macro_export] macro_rules! impl_op { + ($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => { + impl $Op for $T { + type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output { + let $a = self.get(); let $b = other.get(); Self($impl.into()) + } + } + impl $Op for $T { + type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output { + let $a = self.get(); let $b = other as f64; Self($impl.into()) + } + } + impl $Op for $T { + type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output { + let $a = self.get(); let $b = other; Self($impl.into()) + } + } + } +} + /// Define and implement a unit of time #[macro_export] macro_rules! impl_time_unit { ($T:ident) => { diff --git a/src/device/dialog.rs b/src/device/dialog.rs index 1d861d6d..25aa4fc5 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -43,7 +43,7 @@ impl Dialog { 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(); + app.mode = app.config.modes.get(":arranger").unwrap(); })))), MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))), ].into())) diff --git a/src/device/plugin.rs b/src/device/plugin.rs index 0c233240..e4d8c548 100644 --- a/src/device/plugin.rs +++ b/src/device/plugin.rs @@ -1,5 +1,13 @@ use crate::*; +#[cfg(feature = "lv2_gui")] use ::winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, + window::{Window, WindowId}, + platform::x11::EventLoopBuilderExtX11 +}; + /// A LV2 plugin. #[derive(Debug)] #[cfg(feature = "lv2")] pub struct Lv2 { /// JACK client handle (needs to not be dropped for standalone mode to work). diff --git a/src/device/port.rs b/src/device/port.rs index 1375822f..eb8cf92a 100644 --- a/src/device/port.rs +++ b/src/device/port.rs @@ -3,14 +3,13 @@ use ConnectName::*; use ConnectScope::*; use ConnectStatus::*; -pub mod audio; pub use self::audio::*; -pub mod midi; pub use self::midi::*; -pub mod connect; pub use self::connect::*; +mod audio; pub use self::audio::*; +mod midi; pub use self::midi::*; +mod connect; pub use self::connect::*; +mod register; pub use self::register::*; pub trait JackPort: HasJack<'static> { - type Port: PortSpec + Default; - type Pair: PortSpec + Default; fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) @@ -135,29 +134,3 @@ pub trait JackPort: HasJack<'static> { })) } } - -pub trait RegisterPorts: HasJack<'static> { - /// Register a MIDI input port. - fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; - /// Register a MIDI output port. - fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; - /// Register an audio input port. - fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; - /// Register an audio output port. - fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; -} - -impl> RegisterPorts for J { - fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - MidiInput::new(self.jack(), name, connect) - } - fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - MidiOutput::new(self.jack(), name, connect) - } - fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - AudioInput::new(self.jack(), name, connect) - } - fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { - AudioOutput::new(self.jack(), name, connect) - } -} diff --git a/src/device/port/register.rs b/src/device/port/register.rs new file mode 100644 index 00000000..0702a21a --- /dev/null +++ b/src/device/port/register.rs @@ -0,0 +1,27 @@ +use crate::*; + +pub trait RegisterPorts: HasJack<'static> { + /// Register a MIDI input port. + fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; + /// Register a MIDI output port. + fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; + /// Register an audio input port. + fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; + /// Register an audio output port. + fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; +} + +impl> RegisterPorts for J { + fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + MidiInput::new(self.jack(), name, connect) + } + fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + MidiOutput::new(self.jack(), name, connect) + } + fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + AudioInput::new(self.jack(), name, connect) + } + fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + AudioOutput::new(self.jack(), name, connect) + } +} diff --git a/src/device/sampler.rs b/src/device/sampler.rs index da02443b..a537a795 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -1,5 +1,13 @@ use crate::{*, device::*, browse::*, mix::*}; +pub(crate) use symphonia::{ + default::get_codecs, + core::{//errors::Error as SymphoniaError, + audio::SampleBuffer, formats::Packet, io::MediaSourceStream, probe::Hint, + codecs::{Decoder, CODEC_TYPE_NULL}, + }, +}; + mod voice; pub use self::voice::*; mod sample; pub use self::sample::*; mod sample_add; pub use self::sample_add::*; diff --git a/src/tek.rs b/src/tek.rs index 8535b8cc..26391210 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -1,92 +1,19 @@ #![allow(clippy::unit_arg)] -#![feature(adt_const_params, associated_type_defaults, anonymous_lifetime_in_impl_trait, - closure_lifetime_binder, impl_trait_in_assoc_type, trait_alias, type_alias_impl_trait, - type_changing_struct_update)] - -/// CLI banner. -pub(crate) const HEADER: &'static str = r#" - -~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ - █ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~ - ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; - -#[cfg(feature = "cli")] use clap::{self, Parser, Subcommand}; +#![feature( + adt_const_params, + anonymous_lifetime_in_impl_trait, + impl_trait_in_assoc_type, + trait_alias, + type_changing_struct_update +)] pub extern crate atomic_float; - pub extern crate xdg; -pub(crate) use ::xdg::BaseDirectories; - pub extern crate midly; -pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*}; - pub extern crate tengri; -pub(crate) use tengri::{ - *, lang::*, exit::*, eval::*, sing::*, time::*, draw::*, term::*, color::*, task::*, - crossterm::event::{Event, KeyEvent}, - ratatui::{ - self, - prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, - widgets::{Widget, canvas::{Canvas, Line}}, - }, -}; - -/// Implement an arithmetic operation for a unit of time -#[macro_export] macro_rules! impl_op { - ($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => { - impl $Op for $T { - type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output { - let $a = self.get(); let $b = other.get(); Self($impl.into()) - } - } - impl $Op for $T { - type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output { - let $a = self.get(); let $b = other as f64; Self($impl.into()) - } - } - impl $Op for $T { - type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output { - let $a = self.get(); let $b = other; Self($impl.into()) - } - } - } -} - -#[cfg(feature = "sampler")] pub(crate) use symphonia::{ - default::get_codecs, - core::{//errors::Error as SymphoniaError, - audio::SampleBuffer, formats::Packet, io::MediaSourceStream, probe::Hint, - codecs::{Decoder, CODEC_TYPE_NULL}, - }, -}; - -#[cfg(feature = "lv2_gui")] use ::winit::{ - application::ApplicationHandler, - event::WindowEvent, - event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, - window::{Window, WindowId}, - platform::x11::EventLoopBuilderExtX11 -}; - -#[allow(unused)] pub(crate) use ::{ - std::{ - cmp::Ord, - collections::BTreeMap, - error::Error, - ffi::OsString, - fmt::{Write, Debug, Formatter}, - fs::File, - ops::{Add, Sub, Mul, Div, Rem}, - path::{Path, PathBuf}, - sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}}, - time::Duration, - thread::{spawn, JoinHandle}, - }, -}; /// Command-line entrypoint. -#[cfg(feature = "cli")] -pub fn main () -> Usually<()> { +#[cfg(feature = "cli")] pub fn main () -> Usually<()> { tui_setup_panic(); Config::watch(|config|{ tui_run_main(Jack::new_run("tek", move|jack|{ @@ -98,8 +25,14 @@ pub fn main () -> Usually<()> { }).map(|_|()) } -//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); - +pub mod deps; pub use self::deps::*; pub mod device; pub use self::device::*; pub mod app; pub use self::app::*; + + +/// CLI banner. +pub(crate) const HEADER: &'static str = r#" + +~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ + █ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~ + ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#;