wip: provide more components to app

This commit is contained in:
🪞👃🪞 2025-01-11 21:09:21 +01:00
parent ed462cd0f6
commit 2cd56e7391
3 changed files with 126 additions and 21 deletions

View file

@ -3,7 +3,7 @@ use crate::*;
#[derive(Debug)]
pub struct JackPort<T: PortSpec> {
/// Port name
pub name: String,
pub name: Arc<str>,
/// Handle to JACK client, for receiving reconnect events.
pub jack: Arc<RwLock<JackConnection>>,
/// Port handle.
@ -23,7 +23,7 @@ impl<T: PortSpec> JackPort<T> {
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<T: PortSpec> JackPort<T> {
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<T: PortSpec> JackPort<T> {
pub struct PortConnection {
pub name: PortConnectionName,
pub scope: PortConnectionScope,
pub status: Vec<(Port<Unowned>, String, PortConnectionStatus)>,
pub status: Vec<(Port<Unowned>, Arc<str>, PortConnectionStatus)>,
}
impl PortConnection {
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
@ -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<str> {
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<MidiIn> {
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<MidiOut> {
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<AudioIn> {
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<AudioOut> {
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()?;

View file

@ -98,15 +98,15 @@ impl EdnViewData<TuiOut> 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<TuiOut> + '_ { "" }
fn input_row (&self, w: u16) -> impl Content<TuiOut> + '_ { "" }
fn row <'a> (
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
) -> impl Content<TuiOut> + '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<TuiOut> + '_ {
self.row(w, h, track_header(&self), track_cells(&self))
}
fn input_row (&self, w: u16, h: u16) -> impl Content<TuiOut> + '_ {
self.row(w, h, input_header(&self), input_cells(&self))
}
fn output_row (&self, w: u16, h: u16) -> impl Content<TuiOut> + '_ {
self.row(w, h, output_header(&self), output_cells(&self))
}
fn scene_row (&self, w: u16) -> impl Content<TuiOut> + '_ { "" }
fn output_row (&self, w: u16) -> impl Content<TuiOut> + '_ { "" }
pub fn tracks_with_sizes (&self)
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
@ -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<TuiOut> + '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<TuiOut> {
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<TuiOut> {
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 <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> {
Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field))
}

View file

@ -520,15 +520,15 @@ impl Arranger {
}
}
/// A phat line
fn phat_lo (fg: Color, bg: Color) -> impl Content<TuiOut> {
pub fn phat_lo (fg: Color, bg: Color) -> impl Content<TuiOut> {
Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"")))
}
/// A phat line
fn phat_hi (fg: Color, bg: Color) -> impl Content<TuiOut> {
pub fn phat_hi (fg: Color, bg: Color) -> impl Content<TuiOut> {
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 <T: Content<TuiOut>> (
pub fn phat_cell <T: Content<TuiOut>> (
color: ItemPalette, last: ItemPalette, field: T
) -> impl Content<TuiOut> {
Bsp::s(phat_lo(color.base.rgb, last.base.rgb),
@ -537,7 +537,7 @@ fn phat_cell <T: Content<TuiOut>> (
)
)
}
fn phat_cell_3 <T: Content<TuiOut>> (
pub fn phat_cell_3 <T: Content<TuiOut>> (
field: T, top: Color, middle: Color, bottom: Color
) -> impl Content<TuiOut> {
Bsp::s(phat_lo(middle, top),
@ -546,7 +546,7 @@ fn phat_cell_3 <T: Content<TuiOut>> (
)
)
}
fn phat_sel_3 <T: Content<TuiOut>> (
pub fn phat_sel_3 <T: Content<TuiOut>> (
selected: bool, field_1: T, field_2: T, top: Option<Color>, middle: Color, bottom: Color
) -> impl Content<TuiOut> {
let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle);