diff --git a/tek/src/lib.rs b/tek/src/lib.rs index b8d43861..7dd3f5a7 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -17,7 +17,7 @@ pub use ::tek_sampler::{self, *}; pub use ::tek_plugin::{self, *}; pub use ::tek_tui::{ *, tek_edn::*, tek_input::*, tek_output::*, - ratatui, ratatui::{prelude::{Color, Style, Stylize, Buffer, Modifier}, buffer::Cell}, + ratatui, ratatui::{prelude::{Color::{self, *}, Style, Stylize, Buffer, Modifier}, buffer::Cell}, crossterm, crossterm::event::{ Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyCode::{self, *}, }, @@ -157,6 +157,7 @@ impl TekCli { pub fmtd_sr: Arc>, pub fmtd_buf: Arc>, pub fmtd_lat: Arc>, + pub fmtd_stop: Arc, } has_size!(|self: Tek|&self.size); has_clock!(|self: Tek|self.clock); @@ -238,6 +239,7 @@ impl Tek { fmtd_sr: Arc::new(RwLock::new(String::with_capacity(16))), fmtd_buf: Arc::new(RwLock::new(String::with_capacity(16))), fmtd_lat: Arc::new(RwLock::new(String::with_capacity(16))), + fmtd_stop: "⏹".into(), ..Default::default() }; tek.sync_lead(sync_lead); @@ -452,9 +454,9 @@ impl Tek { else if value >= -25.0 { 3 } else if value >= -30.0 { 2 } else if value >= -40.0 { 1 } - else { 0 }, 1, Tui::bg(if value >= 0.0 { Color::Red } - else if value >= -3.0 { Color::Yellow } - else { Color::Green }, ()))) + else { 0 }, 1, Tui::bg(if value >= 0.0 { Red } + else if value >= -3.0 { Yellow } + else { Green }, ()))) } fn view_meters (&self, values: &[f32;2]) -> impl Content + use<'_> { col!( @@ -466,14 +468,14 @@ impl Tek { let playing = self.clock.is_rolling(); let compact = self.is_editing(); Tui::bg( - if playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, + if playing{Rgb(0,128,0)}else{Rgb(128,64,0)}, Either::new(compact, Thunk::new(move||Fixed::x(9, Either::new(playing, - Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "), - Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))), + Tui::fg(Rgb(0, 255, 0), " PLAYING "), + Tui::fg(Rgb(255, 128, 0), " STOPPED ")))), Thunk::new(move||Fixed::x(5, Either::new(playing, - Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), - Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))) + Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), + Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))) } fn view_editor (&self) -> impl Content + use<'_> { self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())) @@ -482,8 +484,8 @@ impl Tek { self.pool.as_ref().map(|pool|PoolView(self.is_editing(), pool)) } fn pool (&self) -> impl Content + use<'_> { - self.pool.as_ref() - .map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.is_editing(), pool)))) + let by_pool = |pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.is_editing(), pool))); + self.pool.as_ref().map(by_pool) } fn w (&self) -> u16 { self.tracks_sizes(self.is_editing(), self.editor_w()) @@ -530,40 +532,65 @@ impl Tek { move|(_, track, x1, x2), t| { let last_color = Arc::new(RwLock::new(ItemPalette::default())); let w = (x2 - x1) as u16; - let color: ItemPalette = track.color; map_east(x1 as u16, w, border(Map::new(scenes, move|(_, scene, y1, y2), s| { - let last_color = last_color.clone(); - let h = (1 + y2 - y1) as u16; - let color = scene.color; - let mut name = String::from("⏹ "); - let mut fg = Tui::g(64); - let mut bg = ItemPalette::G[32]; - if let Some(clip) = &scene.clips[t] { - let clip = clip.read().unwrap(); - name.clear(); - write!(&mut name, "{}", clip.name); - fg = clip.color.lightest.rgb; - bg = clip.color - }; let same_track = selected_track == Some(t+1); let selected = same_track && Some(s+1) == selected_scene; let neighbor = same_track && Some(s) == selected_scene; let active = editing && selected; - let label = move||Tui::fg(fg, Push::x(1, Tui::bold(true, name.clone()))); - let mid = if active { bg.light } else { bg.base }; - let top = if neighbor { None } else { Some(last_color.read().unwrap().base.rgb) }; - let mid = mid.rgb; - let low = Color::Rgb(0, 0, 0); + let last_color = last_color.clone(); + let mut fg = Tui::g(64); + let mut bg = ItemPalette::G[32]; + if let Some(clip) = &scene.clips[t] { + let clip = clip.read().unwrap(); + fg = clip.color.lightest.rgb; + bg = clip.color + }; + + //let top = if neighbor { None } else { Some(last_color.read().unwrap().base.rgb) }; + let top = if s == 0 { + Some(Reset) + } else if neighbor { + None + } else { + Some(last_color.read().unwrap().base.rgb) + }; + let mid = if selected { bg.light } else { bg.base }.rgb; + let low = Some(Reset); + let h = (1 + y2 - y1) as u16; *last_color.write().unwrap() = bg; + let tab = " Tab "; + let name = if active { + self.editor.as_ref() + .map(|e|e.clip().as_ref().map(|c|c.clone())) + .flatten() + .map(|c|c.read().unwrap().name.clone()) + .unwrap_or_else(||"".into()) + } else { + "edit".into() + }; + let label = move||{ + let stop = self.fmtd_stop.clone(); + let clip = scene.clips[t].clone(); + Tui::fg(fg, Push::x(1, Tui::bold(true, clip + .map(|c|c.read().unwrap().name.clone()) + .unwrap_or(stop)))) + }; + map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(active, - Thunk::new(||Bsp::a( - Fill::xy(Align::nw(button(" Tab ", ""))), + Thunk::new(move||Bsp::a( + Fill::xy(Align::nw(button(tab, Tui::fg_bg(fg, bg.base.rgb, name.clone())))), &self.editor)), Thunk::new(move||Bsp::a( - When::new(selected, Fill::y(Align::n(button(" Tab ", "edit")))), - phat_sel_3(selected, label(), label(), top, mid, low) + When::new(selected, Fill::y(Align::n(button(tab, "edit")))), + Fill::xy(phat_sel_3( + selected, + label(), + label(), + top, mid, low + )) )), )))) + }))).boxed() } })).boxed()).into() @@ -679,34 +706,34 @@ handle!(TuiIn: |self: Tek, input|Ok({ Zoom(Option), } atom_command!(TekCommand: |app: Tek| { - ("stop-all" [] Self::StopAll) - ("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize))) - ("redo" [d: usize] Self::History(d.unwrap_or(0) as isize)) - ("zoom" [z: usize] Self::Zoom(z)) - ("edit" [] Self::Edit(None)) - ("edit" [c: bool] Self::Edit(c)) - ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) + ("stop" [] Self::StopAll) + ("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize))) + ("redo" [d: usize] Self::History(d.unwrap_or(0) as isize)) + ("zoom" [z: usize] Self::Zoom(z)) + ("edit" [] Self::Edit(None)) + ("edit" [c: bool] Self::Edit(c)) + ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) ("enqueue" [c: Arc>] Self::Enqueue(c)) - ("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) { + ("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) { (0, 0) => Self::Select(Selection::Mix), (t, 0) => Self::Select(Selection::Track(t)), (0, s) => Self::Select(Selection::Scene(s)), (t, s) => Self::Select(Selection::Clip(t, s)), }) - ("clip" [,..a] Self::Clip(ClipCommand::try_from_expr(app, a) - .expect("invalid command"))) - ("clock" [,..a] Self::Clock(ClockCommand::try_from_expr(app.clock(), a) - .expect("invalid command"))) - ("editor" [,..a] Self::Editor(MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a) - .expect("invalid command"))) - ("pool" [,..a] Self::Pool(PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a) - .expect("invalid command"))) - ("sampler" [,..a] Self::Sampler(SamplerCommand::try_from_expr(app.sampler.as_ref().expect("no sampler"), a) - .expect("invalid command"))) - ("scene" [,..a] Self::Scene(SceneCommand::try_from_expr(app, a) - .expect("invalid command"))) - ("track" [,..a] Self::Track(TrackCommand::try_from_expr(app, a) - .expect("invalid command"))) + ("clip" [,..a] Self::Clip( + ClipCommand::try_from_expr(app, a).expect("invalid command"))) + ("clock" [,..a] Self::Clock( + ClockCommand::try_from_expr(app.clock(), a).expect("invalid command"))) + ("editor" [,..a] Self::Editor( + MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).expect("invalid command"))) + ("pool" [,..a] Self::Pool( + PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).expect("invalid command"))) + ("sampler" [,..a] Self::Sampler( + SamplerCommand::try_from_expr(app.sampler.as_ref().expect("no sampler"), a).expect("invalid command"))) + ("scene" [,..a] Self::Scene( + SceneCommand::try_from_expr(app, a).expect("invalid command"))) + ("track" [,..a] Self::Track( + TrackCommand::try_from_expr(app, a).expect("invalid command"))) }); command!(|self: TekCommand, app: Tek|match self { Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, @@ -741,7 +768,10 @@ command!(|self: TekCommand, app: Tek|match self { let (index, mut clip) = pool.add_new_clip(); // autocolor: new clip colors from scene and track color clip.write().unwrap().color = ItemColor::random_near( - app.tracks[t.saturating_sub(1)].color.base.mix(scene.color.base, 0.5), + app.tracks[t.saturating_sub(1)].color.base.mix( + scene.color.base, + 0.5 + ), 0.2 ).into(); if let Some(ref mut editor) = app.editor { @@ -759,7 +789,7 @@ command!(|self: TekCommand, app: Tek|match self { Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?, Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?, Self::Track(cmd) => cmd.delegate(app, Self::Track)?, - Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?, + Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?, Self::Editor(cmd) => app.editor.as_mut() .map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), Self::Sampler(cmd) => app.sampler.as_mut() @@ -978,7 +1008,7 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { let fg = color.lightest.rgb; let bg = color.base.rgb; let active = self.selected().track() == Some(i + 1); - let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) }; + let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) }; let border = Style::default().fg(bfg).bg(bg); Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16, Outer(false, border) @@ -998,11 +1028,11 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { } fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content { row!( - Tui::fg_bg(if rec { Color::Red } else { bg }, bg, "▐"), - Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"), - Tui::fg_bg(if rec { Color::White } else { bg }, bg, "▐"), - Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"), - Tui::fg_bg(if mon { Color::White } else { bg }, bg, "▌"), + Tui::fg_bg(if rec { Red } else { bg }, bg, "▐"), + Tui::fg_bg(if rec { White } else { Rgb(0,0,0) }, bg, "REC"), + Tui::fg_bg(if rec { White } else { bg }, bg, "▐"), + Tui::fg_bg(if mon { White } else { Rgb(0,0,0) }, bg, "MON"), + Tui::fg_bg(if mon { White } else { bg }, bg, "▌"), ) } fn output_cells <'a> (&'a self) -> ThunkBox<'a, TuiOut> { @@ -1017,16 +1047,16 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { } fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content { row!( - Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"), - Tui::fg_bg(if mute { Color::White } else { bg }, bg, "▐"), - Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"), + Tui::fg_bg(if mute { White } else { Rgb(0,0,0) }, bg, "MUT"), + Tui::fg_bg(if mute { White } else { bg }, bg, "▐"), + Tui::fg_bg(if solo { White } else { Rgb(0,0,0) }, bg, "SOL"), ) } fn cell > (color: ItemPalette, field: T) -> impl Content { Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) } } -trait Device: Send + Sync + std::fmt::Debug {} +pub trait Device: Send + Sync + std::fmt::Debug {} impl Device for Sampler {} impl Device for Plugin {} #[derive(Debug, Default)] pub struct Scene { @@ -1149,21 +1179,19 @@ trait HasScenes: HasSelection + HasEditor + Send + Sync { let selected = self.selected().scene(); let iter = ||self.scenes_sizes(self.is_editing(), 2, 15); Map::new(iter, move|(_, scene, y1, y2), i| { - let h = (1 + y2 - y1) as u16; - let name = format!("🭬{}", &scene.name); - let color = scene.color; - let active = selected == Some(i + 1); - let neighbor = selected == Some(i); - let mid = if active { color.light } else { color.base }; - let top = if neighbor { None } else { Some(last_color.read().unwrap().base.rgb) }; - let mid = mid.rgb; - let low = Color::Rgb(0, 0, 0); + let color = scene.color; + let top = if i == 0 { Some(Reset) } else if selected == Some(i+0) { None } else { Some(last_color.read().unwrap().base.rgb) }; + let mid = if selected == Some(i+1) { color.light } else { color.base }.rgb; + let low = Some(Reset); let cell = phat_sel_3( - active, - Tui::bold(true, name.clone()), - Tui::bold(true, name), - top, mid, low + selected == Some(i), + Tui::bold(true, Bsp::e("🭬", &scene.name)), + Tui::bold(true, Bsp::e("🭬", &scene.name)), + top, + mid, + low ); + let h = (1 + y2 - y1) as u16; *last_color.write().unwrap() = color; map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Outer(false, Style::default().fg(Tui::g(0))).enclose(cell)))) @@ -1275,7 +1303,10 @@ audio!(|self: Tek, client, scope|{ self.perf.update(t0, scope); Control::Continue }); -fn button (key: &'static str, label: &'static str) -> impl Content + 'static { +fn button <'a> ( + key: impl Content + 'a, + label: impl Content + 'a +) -> impl Content + 'a { Tui::bold(true, Bsp::e( Margin::x(1, Tui::fg_bg(Tui::g(0), Tui::orange(), key)), Margin::x(1, Tui::fg_bg(Tui::g(255), Tui::g(96), label)), diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 118650d4..7a582d84 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -158,17 +158,22 @@ pub fn phat_cell_3 > ( ) } pub fn phat_sel_3 > ( - selected: bool, field_1: T, field_2: T, top: Option, middle: Color, bottom: Color + selected: bool, field_1: T, field_2: T, + top: Option, + mid: Color, + low: Option, ) -> impl Content { - let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle); - Either::new(selected, - Tui::bg(middle, Outer(true, border) - .enclose(Align::w(Bsp::s("", Bsp::n("", Fill::y(field_1)))))), - Bsp::s(Fixed::y(1, top.map(|top|phat_lo(middle, top))), - Bsp::n(Fixed::y(1, phat_hi(middle, bottom)), - Fill::xy(Tui::bg(middle, field_2)), - ) - ) + let border = Style::default().fg(Color::Rgb(255,255,255)).bg(mid); + let top = top.map(|top|phat_lo(mid, top)); + let low = low.map(|low|phat_hi(mid, low)); + Either::new( + selected, + Tui::bg(mid, Outer(true, border).enclose( + Align::w(Bsp::s("", Bsp::n("", Fill::y(field_1)))) + )), + Bsp::s(Fixed::y(1, top), Bsp::n(Fixed::y(1, low), + Fill::xy(Tui::bg(mid, field_2)) + )), ) }