From 2cd56e73911086d68db7a11a9b5ce9feed1fec89 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 11 Jan 2025 21:09:21 +0100 Subject: [PATCH] wip: provide more components to app --- jack/src/jack_port.rs | 20 ++++---- tek/src/app.rs | 117 +++++++++++++++++++++++++++++++++++++++--- tek/src/arranger.rs | 10 ++-- 3 files changed, 126 insertions(+), 21 deletions(-) diff --git a/jack/src/jack_port.rs b/jack/src/jack_port.rs index c73ce730..def59bb5 100644 --- a/jack/src/jack_port.rs +++ b/jack/src/jack_port.rs @@ -3,7 +3,7 @@ use crate::*; #[derive(Debug)] pub struct JackPort { /// Port name - pub name: String, + pub name: Arc, /// Handle to JACK client, for receiving reconnect events. pub jack: Arc>, /// Port handle. @@ -23,7 +23,7 @@ impl JackPort { if port.as_str() == &**name { if let Some(port) = self.jack.port_by_name(port.as_str()) { let port_status = Self::try_both_ways(&self.jack, &port, &self.port); - let name = port.name()?; + let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected { break @@ -34,7 +34,7 @@ impl JackPort { RegExp(re) => for port in self.jack.ports(Some(&re), None, PortFlags::empty()).iter() { if let Some(port) = self.jack.port_by_name(port.as_str()) { let port_status = Self::try_both_ways(&self.jack, &port, &self.port); - let name = port.name()?; + let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected && connect.scope == One { break @@ -64,7 +64,7 @@ impl JackPort { pub struct PortConnection { pub name: PortConnectionName, pub scope: PortConnectionScope, - pub status: Vec<(Port, String, PortConnectionStatus)>, + pub status: Vec<(Port, Arc, PortConnectionStatus)>, } impl PortConnection { pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) @@ -89,14 +89,14 @@ impl PortConnection { let name = PortConnectionName::RegExp(name.as_ref().into()); Self { name, scope: PortConnectionScope::All, status: vec![] } } - pub fn info (&self) -> String { + pub fn info (&self) -> Arc { format!("{} {} {}", match self.scope { PortConnectionScope::One => " ", PortConnectionScope::All => "*", }, match &self.name { PortConnectionName::Exact(name) => format!("= {name}"), PortConnectionName::RegExp(name) => format!("~ {name}"), - }, self.status.len()) + }, self.status.len()).into() } } #[derive(Clone, Debug, PartialEq)] @@ -123,7 +123,7 @@ impl JackPort { let mut port = JackPort { jack: jack.clone(), port: jack.midi_in(name.as_ref())?, - name: name.as_ref().to_string(), + name: name.as_ref().into(), connect: connect.to_vec() }; port.connect_to_matching()?; @@ -137,7 +137,7 @@ impl JackPort { let mut port = Self { jack: jack.clone(), port: jack.midi_out(name.as_ref())?, - name: name.as_ref().to_string(), + name: name.as_ref().into(), connect: connect.to_vec() }; port.connect_to_matching()?; @@ -151,7 +151,7 @@ impl JackPort { let mut port = Self { jack: jack.clone(), port: jack.audio_in(name.as_ref())?, - name: name.as_ref().to_string(), + name: name.as_ref().into(), connect: connect.to_vec() }; port.connect_to_matching()?; @@ -165,7 +165,7 @@ impl JackPort { let mut port = Self { jack: jack.clone(), port: jack.audio_out(name.as_ref())?, - name: name.as_ref().to_string(), + name: name.as_ref().into(), connect: connect.to_vec() }; port.connect_to_matching()?; diff --git a/tek/src/app.rs b/tek/src/app.rs index 9d19b5cc..a0b9e6f8 100644 --- a/tek/src/app.rs +++ b/tek/src/app.rs @@ -98,15 +98,15 @@ impl EdnViewData for &App { Nil => Box::new(()), Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), Sym(":editor") => (&self.editor).boxed(), - Sym(":inputs") => self.input_row(w).boxed(), - Sym(":outputs") => self.output_row(w).boxed(), + Sym(":inputs") => self.input_row(w, 3).boxed(), + Sym(":outputs") => self.output_row(w, 3).boxed(), Sym(":pool") => self.pool().boxed(), Sym(":sample") => self.sample().boxed(), Sym(":sampler") => self.sampler().boxed(), Sym(":scenes") => self.scene_row(w).boxed(), Sym(":status") => self.status(0).boxed(), Sym(":toolbar") => self.toolbar().boxed(), - Sym(":tracks") => self.track_row(w).boxed(), + Sym(":tracks") => self.track_row(w, 3).boxed(), _ => panic!("no content for {item:?}") } } @@ -162,10 +162,25 @@ impl App { } None } - fn track_row (&self, w: u16) -> impl Content + '_ { "" } - fn input_row (&self, w: u16) -> impl Content + '_ { "" } + + fn row <'a> ( + &'a self, w: u16, h: u16, a: impl Content + 'a, b: impl Content + 'a + ) -> impl Content + 'a { + Fixed::y(h, Bsp::e( + Fixed::xy(self.sidebar_w() as u16, h, a), + Fill::x(Align::c(Fixed::xy(w, h, b))) + )) + } + fn track_row (&self, w: u16, h: u16) -> impl Content + '_ { + self.row(w, h, track_header(&self), track_cells(&self)) + } + fn input_row (&self, w: u16, h: u16) -> impl Content + '_ { + self.row(w, h, input_header(&self), input_cells(&self)) + } + fn output_row (&self, w: u16, h: u16) -> impl Content + '_ { + self.row(w, h, output_header(&self), output_cells(&self)) + } fn scene_row (&self, w: u16) -> impl Content + '_ { "" } - fn output_row (&self, w: u16) -> impl Content + '_ { "" } pub fn tracks_with_sizes (&self) -> impl Iterator @@ -205,3 +220,93 @@ pub fn tracks_with_sizes <'a> ( data }) } +pub fn track_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + (||Tui::bg(TuiTheme::g(32), Tui::bold(true, Bsp::s( + row!( + Tui::fg(TuiTheme::g(128), "add "), + Tui::fg(TuiTheme::orange(), "t"), + Tui::fg(TuiTheme::g(128), "rack"), + ), + row!( + Tui::fg(TuiTheme::orange(), "a"), + Tui::fg(TuiTheme::g(128), "dd scene"), + ), + ))).boxed()).into() +} +pub fn track_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + let iter = ||state.tracks_with_sizes(); + (move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { + let name = Push::x(1, &track.name); + let color = track.color(); + let fg = color.lightest.rgb; + let bg = color.base.rgb; + let active = state.selected.track() == Some(i); + let bfg = if active { Color::Rgb(255,255,255) } else { Color::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(border).enclose(Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::x(name))))) + )) + })).boxed()).into() +} +fn help_tag <'a>(before: &'a str, key: &'a str, after: &'a str) -> impl Content + 'a { + let lo = TuiTheme::g(128); + let hi = TuiTheme::orange(); + Tui::bold(true, row!(Tui::fg(lo, before), Tui::fg(hi, key), Tui::fg(lo, after))) +} +fn input_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + let fg = TuiTheme::g(224); + let bg = TuiTheme::g(64); + (move||Bsp::s(help_tag("midi ", "I", "ns"), state.midi_ins.get(0).map(|inp|Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))), + inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, + Tui::fg_bg(fg, bg, connect.info()))))), + ))).boxed()).into() +} +fn input_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + (move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| { + let w = (x2 - x1) as u16; + let color: ItemPalette = track.color().dark.into(); + map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n( + rec_mon(color.base.rgb, false, false), + phat_hi(color.base.rgb, color.dark.rgb) + )))) + })).boxed()).into() +} +fn output_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + let fg = TuiTheme::g(224); + let bg = TuiTheme::g(64); + (move||Bsp::s(help_tag("midi ", "O", "uts"), state.midi_outs.get(0).map(|out|Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))), + out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, + Tui::fg_bg(fg, bg, connect.info()))))), + ))).boxed()).into() +} +fn output_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + (move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| { + let w = (x2 - x1) as u16; + let color: ItemPalette = track.color().dark.into(); + map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n( + mute_solo(color.base.rgb, false, false), + phat_hi(color.dark.rgb, color.darker.rgb) + )))) + })).boxed()).into() +} +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, "▌"), + ) +} +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"), + ) +} +fn cell > (color: ItemPalette, field: T) -> impl Content { + Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) +} diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index cf680f4a..f2b477ff 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -520,15 +520,15 @@ impl Arranger { } } /// A phat line -fn phat_lo (fg: Color, bg: Color) -> impl Content { +pub fn phat_lo (fg: Color, bg: Color) -> impl Content { Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"▄"))) } /// A phat line -fn phat_hi (fg: Color, bg: Color) -> impl Content { +pub fn phat_hi (fg: Color, bg: Color) -> impl Content { Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"▀"))) } /// A cell that is 3-row on its own, but stacks, giving (N+1)*2 rows per N cells. -fn phat_cell > ( +pub fn phat_cell > ( color: ItemPalette, last: ItemPalette, field: T ) -> impl Content { Bsp::s(phat_lo(color.base.rgb, last.base.rgb), @@ -537,7 +537,7 @@ fn phat_cell > ( ) ) } -fn phat_cell_3 > ( +pub fn phat_cell_3 > ( field: T, top: Color, middle: Color, bottom: Color ) -> impl Content { Bsp::s(phat_lo(middle, top), @@ -546,7 +546,7 @@ fn phat_cell_3 > ( ) ) } -fn phat_sel_3 > ( +pub fn phat_sel_3 > ( selected: bool, field_1: T, field_2: T, top: Option, middle: Color, bottom: Color ) -> impl Content { let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle);