flatten more fns

This commit is contained in:
🪞👃🪞 2025-02-09 16:06:36 +01:00
parent fe9d5a309e
commit e0f4ec9a15
5 changed files with 203 additions and 133 deletions

View file

@ -6,8 +6,8 @@ mod keymap; pub use self::keymap::*;
pub(crate) use ::tek_edn::*;
/// Standard error trait.
pub(crate) use std::error::Error;
///// Standard result type.
//pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard result type.
#[cfg(test)] pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type.
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
#[cfg(test)] #[test] fn test_stub_input () -> Usually<()> {

View file

@ -47,68 +47,50 @@ impl Tek {
.map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool)))
}
pub(crate) fn w (&self) -> u16 { self.size.w() as u16 }
pub(crate) fn h (&self) -> u16 { self.size.h() as u16 }
pub(crate) fn w_sidebar (&self) -> u16 { self.w() / if self.is_editing() { 16 } else { 8 } as u16 }
pub(crate) fn row <'a> (
&'a self,
w: u16,
h: u16,
a: impl Content<TuiOut> + 'a,
b: impl Content<TuiOut> + 'a,
c: impl Content<TuiOut> + 'a,
) -> impl Content<TuiOut> + 'a {
Fixed::y(h, Bsp::a(
Fill::xy(Align::c(Fixed::x(w, Align::x(b)))),
Bsp::a(
Fill::xy(Align::w(Fixed::x(self.w_sidebar() as u16, a))),
Fill::xy(Align::e(Fixed::x(self.w_sidebar() as u16, c))),
),
))
}
pub(crate) fn row_top <'a> (
&'a self,
w: u16,
h: u16,
a: impl Content<TuiOut> + 'a,
b: impl Content<TuiOut> + 'a,
c: impl Content<TuiOut> + 'a,
) -> impl Content<TuiOut> + 'a {
Fixed::y(h, Bsp::a(
Fill::x(Align::n(Fixed::x(w, Align::x(Tui::bg(Reset, b))))),
Bsp::a(
Fill::x(Align::nw(Fixed::x(self.w_sidebar() as u16, Tui::bg(Reset, a)))),
Fill::x(Align::ne(Fixed::x(self.w_sidebar() as u16, Tui::bg(Reset, c)))),
),
))
}
pub(crate) fn io_ports <'a, T: PortsSizes<'a>> (
&'a self, fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Map::new(iter,
move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))),
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
&connect.info)))))))))}
pub(crate) fn io_connections <'a, T: PortsSizes<'a>> (
&'a self, fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Map::new(iter,
move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, Self::wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))),
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, Self::wrap(bg, fg, Fill::x(""))))))))))}
fn heading <'a> (
&'a self, key: &'a str, label: &'a str, count: usize,
content: impl Content<TuiOut> + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
let count = format!("{count}");
Fill::xy(Align::w(Bsp::s(Fill::x(Align::w(button_3(key, label, count, self.is_editing()))), content)))
}
pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
Bsp::e(Tui::fg_bg(bg, Reset, ""), Bsp::w(Tui::fg_bg(bg, Reset, ""),
Tui::fg_bg(fg, bg, content))) }
}
pub(crate) fn row <'a> (
w: u16,
h: u16,
s: u16,
a: impl Content<TuiOut> + 'a,
b: impl Content<TuiOut> + 'a,
c: impl Content<TuiOut> + 'a,
) -> impl Content<TuiOut> + 'a {
Fixed::y(h, Bsp::a(
Fill::xy(Align::c(Fixed::x(w, Align::x(b)))),
Bsp::a(
Fill::xy(Align::w(Fixed::x(s, a))),
Fill::xy(Align::e(Fixed::x(s, c))),
),
))
}
pub(crate) fn row_top <'a> (
w: u16,
h: u16,
s: u16,
a: impl Content<TuiOut> + 'a,
b: impl Content<TuiOut> + 'a,
c: impl Content<TuiOut> + 'a,
) -> impl Content<TuiOut> + 'a {
Fixed::y(h, Bsp::a(
Fill::x(Align::n(Fixed::x(w, Align::x(Tui::bg(Reset, b))))),
Bsp::a(
Fill::x(Align::nw(Fixed::x(s, Tui::bg(Reset, a)))),
Fill::x(Align::ne(Fixed::x(s, Tui::bg(Reset, c)))),
),
))
}
pub(crate) fn wrap (
bg: Color,
fg: Color,
content: impl Content<TuiOut>
) -> impl Content<TuiOut> {
Bsp::e(Tui::fg_bg(bg, Reset, ""),
Bsp::w(Tui::fg_bg(bg, Reset, ""),
Tui::fg_bg(fg, bg, content)))
}
pub(crate) fn button_2 <'a, K, L> (
@ -154,26 +136,27 @@ pub(crate) fn button_3 <'a, K, L, V> (
));
Tui::bold(true, Bsp::e(key, label))
}
fn heading <'a> (
key: &'a str,
label: &'a str,
count: usize,
content: impl Content<TuiOut> + Send + Sync + 'a,
editing: bool,
) -> impl Content<TuiOut> + 'a {
let count = format!("{count}");
Fill::xy(Align::w(Bsp::s(Fill::x(Align::w(button_3(key, label, count, editing))), content)))
}
#[cfg(test)] mod test {
use super::*;
#[test] fn test_view () {
let app = Tek::default();
let _ = app.view_editor();
let _ = app.view_pool();
let _ = app.w();
let _ = app.w_sidebar();
let _ = app.w_tracks_area();
let _ = app.h();
let _ = app.h_tracks_area();
let _ = app.row(0, 0, "", "", "");
let _ = app.row_top(0, 0, "", "", "");
//let _ = app.io_ports(Reset, Reset, ||[].iter());
//let _ = app.io_connections(Reset, Reset, ||[].iter());
let _ = app.button_2("", "", true);
let _ = app.button_2("", "", false);
let _ = app.button_3("", "", "", true);
let _ = app.button_3("", "", "", false);
let _ = app.heading("", "", 0, "");
let _ = Tek::wrap(Reset, Reset, "");
let _ = button_2("", "", true);
let _ = button_2("", "", false);
let _ = button_3("", "", "", true);
let _ = button_3("", "", "", false);
let _ = heading("", "", 0, "", true);
let _ = heading("", "", 0, "", false);
let _ = wrap(Reset, Reset, "");
}
}

View file

@ -1,14 +1,14 @@
use crate::*;
impl Tek {
const TAB: &str = " Tab";
/// Blit the currently visible section of the arranger to the output.
///
/// If the arranger is larger than the available display area,
/// the scrollbars determine the portion that will be shown.
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
()
() // TODO
}
/// Draw the full arranger to the arranger view buffer.
///
/// This should happen on changes to the arrangement view
@ -19,27 +19,25 @@ impl Tek {
let height = self.h_scenes() + self.h_inputs() + self.h_outputs();
let buffer = Buffer::empty(ratatui::prelude::Rect { x: 0, y: 0, width, height });
let mut output = TuiOut { buffer, area: [0, 0, width, height] };
let content = Bsp::s(self.view_inputs(),
Bsp::s(self.view_tracks(),
Bsp::n(self.view_outputs(), self.view_scenes())));
Content::render(&content, &mut output);
Content::render(&Bsp::s(self.view_inputs(), Bsp::s(self.view_tracks(),
Bsp::n(self.view_outputs(), self.view_scenes()))), &mut output);
*self.arranger.write().unwrap() = output.buffer;
}
/// Display the current scene scroll state.
fn scene_scrollbar (&self) -> impl Content<TuiOut> + use<'_> {
Fill::y(Fixed::x(1, ScrollbarV {
offset: self.scene_scroll,
length: self.h_tracks_area() as usize,
total: self.h_scenes() as usize,
}))
let offset = self.scene_scroll;
let length = self.h_tracks_area() as usize;
let total = self.h_scenes() as usize;
Fill::y(Fixed::x(1, ScrollbarV { offset, length, total }))
}
/// Display the current track scroll state.
fn track_scrollbar (&self) -> impl Content<TuiOut> + use<'_> {
Fill::x(Fixed::y(1, ScrollbarH {
offset: self.track_scroll,
length: self.w_tracks_area() as usize,
total: self.w_tracks() as usize,
}))
let offset = self.track_scroll;
let length = self.w_tracks_area() as usize;
let total = self.w_tracks() as usize;
Fill::x(Fixed::y(1, ScrollbarH { offset, length, total }))
}
fn per_track <'a, T: Content<TuiOut> + 'a> (
@ -47,11 +45,16 @@ impl Tek {
) -> impl Content<TuiOut> + 'a {
self.per_track_top(move|index, track|Fill::y(Align::y(f(index, track))))
}
fn per_track_top <'a, T: Content<TuiOut> + 'a> (
&'a self, f: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
let width = self.w_tracks_area();
let filter = move|(t, track, x1, x2)|if x2 as u16 >= width {None} else {Some((t, track, x1, x2))};
let filter = move|(t, track, x1, x2)|if x2 as u16 >= width {
None
} else {
Some((t, track, x1, x2))
};
let tracks = move||self.tracks_sizes().map_while(filter);
Align::x(Tui::bg(Reset, Map::new(tracks, move|(index, track, x1, x2), _|{
let width = (x2 - x1) as u16;
@ -64,26 +67,18 @@ impl Tek {
pub fn view_tracks (&self) -> impl Content<TuiOut> + use<'_> {
let w = (self.size.w() as u16).saturating_sub(2 * self.w_sidebar());
let s = self.w_sidebar() as u16;
let data = (self.selected.track().unwrap_or(0), self.tracks().len());
let editing = self.is_editing();
self.fmtd.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1));
self.row(w, 1, button_3("t", "track", self.fmtd.read().unwrap().trks.view.clone(), editing),
self.per_track(|t, track|self.view_track_header(t, track)),
button_2("T", "add track", editing))
}
fn view_track_header <'a> (&self, t: usize, track: &'a Track) -> impl Content<TuiOut> + use<'a> {
let active = self.selected().track() == Some(t);
let name = &track.name;
let fg = track.color.lightest.rgb;
let bg = if active { track.color.light.rgb } else { track.color.base.rgb };
let bg2 = Reset;//if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset };
let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) };
Self::wrap(bg, fg, Tui::bold(true, Fill::x(Align::nw(name))))
row(w, 1, s, button_3("t", "track", self.fmtd.read().unwrap().trks.view.clone(), editing),
self.per_track(|t, track|track_header(t, track, self.selected().track() == Some(t))),
button_2("T", "add track", editing))
}
pub fn view_scenes (&self) -> impl Content<TuiOut> + use<'_> {
let editing = self.is_editing();
let s = self.w_sidebar() as u16;
let w = self.w_tracks_area();
let w_full = self.w();
let h = self.h_scenes();
@ -91,7 +86,7 @@ impl Tek {
let selected_track = self.selected().track();
let selected_scene = self.selected().scene();
Tui::bg(Reset, Bsp::s(self.track_scrollbar(), Bsp::e(self.scene_scrollbar(),
Fixed::y(self.h_tracks_area(), self.row(self.w_tracks_area(), h,
Fixed::y(self.h_tracks_area(), row(self.w_tracks_area(), h, s,
Map::new(
move||self.scenes_with_colors(editing, h_area),
move|(s, scene, y1, y2, prev): SceneColor, _|self.view_scene_name(
@ -105,8 +100,13 @@ impl Tek {
}
fn view_scene_name (
&self, width: u16, height: u16, offset: u16,
s: usize, scene: &Scene, prev: Option<ItemPalette>,
&self,
width: u16,
height: u16,
offset: u16,
s: usize,
scene: &Scene,
prev: Option<ItemPalette>,
) -> impl Content<TuiOut> + use<'_> {
let bg = scene.color;
let fg = scene.color.lightest.rgb;
@ -168,9 +168,11 @@ impl Tek {
self.fmtd.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1));
button_3("S", "add scene", self.fmtd.read().unwrap().scns.view.clone(), editing)
}
pub fn view_outputs (&self) -> impl Content<TuiOut> + use<'_> {
let editing = self.is_editing();
let w = self.w_tracks_area();
let s = self.w_sidebar() as u16;
let fg = Tui::g(224);
let nexts = self.per_track_top(|t, track|Either(
track.player.next_clip.is_some(),
@ -180,41 +182,46 @@ impl Tek {
.map(|clip|clip.read().unwrap().name.clone()))
.flatten().as_ref()))),
Thunk::new(||Tui::bg(Reset, " ------ "))));
let nexts = self.row_top(w, 2, Align::ne("Next:"), nexts, ());
let nexts = row_top(w, 2, s, Align::ne("Next:"), nexts, ());
let froms = self.per_track_top(|_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default(),))));
let froms = self.row_top(w, 2, Align::ne("From:"), froms, ());
let ports = self.row_top(w, 1,
let froms = row_top(w, 2, s, Align::ne("From:"), froms, ());
let ports = row_top(w, 1, s,
button_3("o", "midi outs", format!("{}", self.midi_outs.len()), editing),
self.per_track_top(move|t, track|{
let mute = false;
let solo = false;
let mute = if mute { White } else { track.color.darkest.rgb };
let solo = if solo { White } else { track.color.darkest.rgb };
let bg = if self.selected().track() == Some(t) { track.color.light.rgb } else { track.color.base.rgb };
let bg2 = if t > 0 { self.tracks()[t].color.base.rgb } else { Reset };
Self::wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e(
let bg = if self.selected().track() == Some(t) {
track.color.light.rgb
} else {
track.color.base.rgb
};
let bg2 = if t > 0 { self.tracks()[t].color.base.rgb } else { Reset };
wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e(
Tui::fg_bg(mute, bg, "Play "),
Tui::fg_bg(solo, bg, "Solo ")))))}),
button_2("O", "add midi out", editing));
let routes = self.row_top(w, self.h_outputs() - 1,
self.io_ports(fg, Tui::g(32), ||self.outputs_sizes()),
self.per_track_top(move|t, track|self.io_connections(
let routes = row_top(w, self.h_outputs() - 1, s,
io_ports(fg, Tui::g(32), ||self.outputs_sizes()),
self.per_track_top(move|t, track|io_conns(
track.color.dark.rgb, track.color.darker.rgb, ||self.outputs_sizes())), ());
Align::n(Bsp::s(Bsp::s(nexts, froms), Bsp::s(ports, routes)))
}
pub fn view_inputs (&self) -> impl Content<TuiOut> + use<'_> {
let editing = self.is_editing();
let w = (self.size.w() as u16).saturating_sub(2 * self.w_sidebar());
let s = self.w_sidebar() as u16;
let fg = Tui::g(224);
let routes = self.row_top(w, self.h_inputs() - 1,
self.io_ports(fg, Tui::g(32), ||self.inputs_sizes()),
self.per_track_top(move|t, track|self.io_connections(
let routes = row_top(w, self.h_inputs() - 1, s,
io_ports(fg, Tui::g(32), ||self.inputs_sizes()),
self.per_track_top(move|t, track|io_conns(
track.color.dark.rgb,
track.color.darker.rgb,
||self.inputs_sizes()
)), ());
let ports = self.row_top(w, 1,
let ports = row_top(w, 1, s,
button_3("i", "midi ins", format!("{}", self.midi_ins.len()), editing),
self.per_track_top(move|t, track|{
let rec = track.player.recording;
@ -227,18 +234,71 @@ impl Tek {
track.color.base.rgb
};
let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset };
Self::wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e(
wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e(
Tui::fg_bg(rec, bg, "Rec "),
Tui::fg_bg(mon, bg, "Mon ")))))
}),
button_2("I", "add midi in", editing));
Bsp::s(
Bsp::s(routes, ports),
self.row_top(w, 2,
row_top(w, 2, s,
Bsp::s(Align::e("Input:"), Align::e("Into:")),
self.per_track_top(|_, _|Tui::bg(Reset, Align::c(Bsp::s(
OctaveVertical::default(),
" ------ ")))), ())
)
}
}
fn track_header <'a> (t: usize, track: &'a Track, active: bool) -> impl Content<TuiOut> + use<'a> {
let name = &track.name;
let fg = track.color.lightest.rgb;
let bg = if active { track.color.light.rgb } else { track.color.base.rgb };
let bg2 = Reset;//if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset };
let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) };
wrap(bg, fg, Tui::bold(true, Fill::x(Align::nw(name))))
}
fn io_ports <'a, T: PortsSizes<'a>> (
fg: Color,
bg: Color,
iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Map::new(iter,
move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))),
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
&connect.info)))))))))
}
fn io_conns <'a, T: PortsSizes<'a>> (
fg: Color,
bg: Color,
iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Map::new(iter,
move|(index, name, connections, y, y2), _|map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))),
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x(""))))))))))
}
#[cfg(test)] mod test {
use super::*;
#[test] fn test_view_arranger () {
let app = Tek::default();
let _ = io_ports(Reset, Reset, ||app.inputs_sizes());
let _ = io_conns(Reset, Reset, ||app.outputs_sizes());
let _ = app.per_track(|_, _|());
let _ = app.per_track_top(|_, _|());
let _ = app.redraw_arranger();
//let _ = app.view_editor();
//let _ = app.view_pool();
//let _ = app.row(0, 0, "", "", "");
//let _ = app.row_top(0, 0, "", "", "");
//let _ = app.io_ports(Reset, Reset, ||[].iter());
//let _ = app.io_conns(Reset, Reset, ||[].iter());
}
}

View file

@ -6,6 +6,14 @@ impl Tek {
pub(crate) const H_SCENE: usize = 2;
/// Default editor height.
pub(crate) const H_EDITOR: usize = 15;
/// Width of display
pub(crate) fn w (&self) -> u16 {
self.size.w() as u16
}
pub(crate) fn w_sidebar (&self) -> u16 {
self.w() / if self.is_editing() { 16 } else { 8 } as u16
}
/// Width taken by all tracks.
pub(crate) fn w_tracks (&self) -> u16 {
self.tracks_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
@ -14,6 +22,10 @@ impl Tek {
pub(crate) fn w_tracks_area (&self) -> u16 {
self.w().saturating_sub(2 * self.w_sidebar())
}
/// Height of display
pub(crate) fn h (&self) -> u16 {
self.size.h() as u16
}
/// Height available to display tracks.
pub(crate) fn h_tracks_area (&self) -> u16 {
self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 10)
@ -28,6 +40,21 @@ impl Tek {
}
/// Height taken by all scenes.
pub(crate) fn h_scenes (&self) -> u16 {
self.scenes_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last().map(|(_, _, _, y)|y as u16).unwrap_or(0)
self.scenes_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last()
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
}
}
#[cfg(test)] mod test {
use super::*;
#[test] fn test_view_size () {
let app = Tek::default();
let _ = app.w();
let _ = app.w_sidebar();
let _ = app.w_tracks_area();
let _ = app.h();
let _ = app.h_tracks_area();
let _ = app.h_inputs();
let _ = app.h_outputs();
let _ = app.h_scenes();
}
}

View file

@ -39,7 +39,7 @@ pub(crate) use std::ffi::OsString;
use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl Content<TuiOut> for TestComponent {
fn content (&self) -> Option<impl Content<TuiOut>> {
fn content (&self) -> impl Render<TuiOut> {
Some(self.0.as_str())
}
}