update layout macro invocations

This commit is contained in:
🪞👃🪞 2024-12-31 04:37:45 +01:00
parent e677d1d7d4
commit 83eb9dd2fa
19 changed files with 153 additions and 169 deletions

View file

@ -5,6 +5,8 @@ mod stack; pub use self::stack::*;
use crate::*; use crate::*;
use std::sync::RwLock;
/// Conditional rendering, in unary and binary forms. /// Conditional rendering, in unary and binary forms.
pub struct Cond; pub struct Cond;
@ -17,8 +19,14 @@ impl Cond {
pub fn either <E: Engine, A: Content<E>, B: Content<E>> (cond: bool, a: A, b: B) -> Either<E, A, B> { pub fn either <E: Engine, A: Content<E>, B: Content<E>> (cond: bool, a: A, b: B) -> Either<E, A, B> {
Either(cond, a, b, Default::default()) Either(cond, a, b, Default::default())
} }
pub fn opt <E: Engine, A, F: Fn(A)->R, R: Content<E>> (option: Option<A>, cb: F) -> Opt<E, A, F, R> {
Opt(option, cb, Default::default())
}
} }
pub struct Opt<E: Engine, A, F: Fn(A)->R, R: Content<E>>(Option<A>, F, PhantomData<E>);
/// Contents `self.1` when `self.0` is true. /// Contents `self.1` when `self.0` is true.
pub struct When<E: Engine, A>(bool, A, PhantomData<E>); pub struct When<E: Engine, A>(bool, A, PhantomData<E>);
@ -62,7 +70,6 @@ pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)
impl<E, F> Collector<E> for F impl<E, F> Collector<E> for F
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {} where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {}
/*
/// Rendering of iterable collections, one-to-one and many-to one. /// Rendering of iterable collections, one-to-one and many-to one.
pub struct Coll; pub struct Coll;
@ -73,24 +80,25 @@ impl Coll {
R: Content<E>, R: Content<E>,
F: Fn(T, usize)->R + Send + Sync F: Fn(T, usize)->R + Send + Sync
{ {
Map(Default::default(), iterator, callback) Map(Default::default(), RwLock::new(iterator), callback)
}
pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
F: Fn(R, T, usize) -> R + Send + Sync
{
Reduce(Default::default(), iterator, callback)
} }
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
//E: Engine,
//I: Iterator<Item = T> + Send + Sync,
//R: Content<E>,
//F: Fn(R, T, usize) -> R + Send + Sync
//{
//Reduce(Default::default(), iterator, callback)
//}
} }
pub struct Map<'a, E, T, I, R, F>(PhantomData<E>, &'a mut I, F) where pub struct Map<E, T, I, R, F>(PhantomData<E>, RwLock<I>, F) where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, I: Iterator<Item = T> + Send + Sync,
R: Content<E>, R: Content<E>,
F: Fn(T, usize)->R + Send + Sync; F: Fn(T, usize)->R + Send + Sync;
impl<'a, E, T, I, R, F> Content<E> for Map<'a, E, T, I, R, F> where
impl<E, T, I, R, F> Content<E> for Map<E, T, I, R, F> where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, I: Iterator<Item = T> + Send + Sync,
R: Content<E>, R: Content<E>,
@ -98,13 +106,14 @@ impl<'a, E, T, I, R, F> Content<E> for Map<'a, E, T, I, R, F> where
{ {
fn render (&self, to: &mut E::Output) { fn render (&self, to: &mut E::Output) {
let mut index = 0; let mut index = 0;
for item in self.1 { for item in &mut*self.1.write().unwrap() {
(self.2)(item, index).render(to); (self.2)(item, index).render(to);
index += 1; index += 1;
} }
} }
} }
/*
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, I: Iterator<Item = T> + Send + Sync,

View file

@ -102,23 +102,23 @@ impl ArrangerTui {
render!(Tui: (self: ArrangerTui) => { render!(Tui: (self: ArrangerTui) => {
let play = PlayPause(self.clock.is_rolling()); let play = PlayPause(self.clock.is_rolling());
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true); let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
let with_transport = |x|col!([row!(![&play, &transport]), &x]); let with_transport = |x|col!(row!(&play, &transport), &x);
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x); let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self); let status = ArrangerStatus::from(self);
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x); let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
let with_status = |x|Split::n(false, 2, status, x); let with_status = |x|Split::n(false, 2, status, x);
let with_size = |x|lay!([&self.size, x]); let with_size = |x|lay!(&self.size, x);
let arranger = ||lay!(|add|{ let arranger = ||lay!(|add|{
let color = self.color; let color = self.color;
add(&Fill::xy(Tui::bg(color.darkest.rgb, "")))?; add(&Fill::xy(Tui::bg(color.darkest.rgb, "")))?;
add(&Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?; add(&Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
add(&Self::render_mode(self)) add(&Self::render_mode(self))
}); });
with_size(with_status(with_editbar(with_pool(with_transport(col!([ with_size(with_status(with_editbar(with_pool(with_transport(col!(
Fill::x(Fixed::y(20, arranger())), Fill::x(Fixed::y(20, arranger())),
Fill::xy(&self.editor), Fill::xy(&self.editor),
])))))) ))))))
}); });
audio!(|self: ArrangerTui, client, scope|{ audio!(|self: ArrangerTui, client, scope|{
// Start profiling cycle // Start profiling cycle

View file

@ -4,20 +4,21 @@ mod v_cursor; pub(crate) use self::v_cursor::*;
mod v_head; pub(crate) use self::v_head::*; mod v_head; pub(crate) use self::v_head::*;
mod v_io; pub(crate) use self::v_io::*; mod v_io; pub(crate) use self::v_io::*;
mod v_sep; pub(crate) use self::v_sep::*; mod v_sep; pub(crate) use self::v_sep::*;
// egyptian snakes den
const HEADER_H: u16 = 5; const HEADER_H: u16 = 5;
const SCENES_W_OFFSET: u16 = 3; const SCENES_W_OFFSET: u16 = 3;
impl ArrangerTui { impl ArrangerTui {
pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Render<Tui> + use<'_> { pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Content<Tui> + use<'_> {
lay!([ lay!(
ArrangerVColSep::from(state), ArrangerVColSep::from(state),
ArrangerVRowSep::from((state, factor)), ArrangerVRowSep::from((state, factor)),
col!([ col!(
ArrangerVHead::from(state), ArrangerVHead::from(state),
ArrangerVIns::from(state), ArrangerVIns::from(state),
ArrangerVClips::from((state, factor)), ArrangerVClips::new(state, factor),
ArrangerVOuts::from(state), ArrangerVOuts::from(state),
]), ),
ArrangerVCursor::from((state, factor)), ArrangerVCursor::from((state, factor)),
]) )
} }
} }

View file

@ -20,7 +20,8 @@ impl<'a> ArrangerVClips<'a> {
impl<'a, E: Engine> Content<E> for ArrangerVClips<'a> { impl<'a, E: Engine> Content<E> for ArrangerVClips<'a> {
fn content (&self) -> Option<impl Content<E>> { fn content (&self) -> Option<impl Content<E>> {
let iter = self.scenes.iter().zip(self.rows.iter().map(|row|row.0)); let iter = self.scenes.iter().zip(self.rows.iter().map(|row|row.0));
let col = col_iter!(iter => |(scene, pulses)|Self::format_scene(self.tracks, scene, pulses)); let col = Coll::map(self.scenes.iter().zip(self.rows.iter().map(|row|row.0)), |(scene, pulses), i|
Self::format_scene(self.tracks, scene, pulses));
Some(Fill::xy(col)) Some(Fill::xy(col))
} }
} }
@ -36,9 +37,8 @@ impl<'a> ArrangerVClips<'a> {
if playing { "" } else { " " }), if playing { "" } else { " " }),
Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), Expand::x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))),
row_iter!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { Coll::map(ArrangerTrack::with_widths(tracks), |(index, track, x1, x2), _|
Self::format_clip(scene, index, track, (x2 - x1) as u16, height) Push::x(Self::format_clip(scene, index, track, (x2 - x1) as u16, height)))))}
})))}
fn format_clip ( fn format_clip (
scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16

View file

@ -15,29 +15,27 @@ from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16, scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16,
}); });
impl Content<Tui> for ArrangerVCursor { render!(Tui: (self: ArrangerVHead<'a>) => {
fn content (&self) -> Option<impl Content<Tui>> {
fn row <T: Content<Tui>> (color: ItemPalette, field: &T) -> impl Content<Tui> + use<'_, T> {
row!([Tui::fg(color.light.rgb, ""), Tui::fg(color.lightest.rgb, field)])
}
Some(Push::x(self.scenes_w, row_iter!(
(_0, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color();
Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::xy(w as u16, 5, col!([
row(color, &Self::format_name(track, w)),
row(color, &Self::format_input(track)?),
row(color, &Self::format_output(track)?),
row(color, &Self::format_elapsed(track, self.timebase)),
row(color, &Self::format_until_next(track, self.current)),
]))))
}
)))
fn row <T: Content<Tui>> (color: ItemPalette, field: &T) -> impl Content<Tui> + use<'_, T> {
row!(Tui::fg(color.light.rgb, ""), Tui::fg(color.lightest.rgb, field))
} }
}
Some(Push::x(self.scenes_w,
Coll::map(ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color();
Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::xy(w as u16, 5, col!(
row(color, &Self::format_name(track, w)),
row(color, &Self::format_input(track)?),
row(color, &Self::format_output(track)?),
row(color, &Self::format_elapsed(track, self.timebase)),
row(color, &Self::format_until_next(track, self.current)),
))))
})
))
});
impl ArrangerVHead<'_> { impl ArrangerVHead<'_> {
/// name and width of track /// name and width of track

View file

@ -3,7 +3,7 @@ use crate::*;
pub struct Bordered<S: BorderStyle, W: Content<Tui>>(pub S, pub W); pub struct Bordered<S: BorderStyle, W: Content<Tui>>(pub S, pub W);
render!(Tui: (self: Bordered<S: BorderStyle, W: Content<Tui>>) => { render!(Tui: (self: Bordered<S: BorderStyle, W: Content<Tui>>) => {
Fill::xy(lay!([Border(self.0), Padding::xy(1, 1, &self.1)])) Fill::xy(lay!(Border(self.0), Padding::xy(1, 1, &self.1)))
}); });
pub struct Border<S: BorderStyle>(pub S); pub struct Border<S: BorderStyle>(pub S);
@ -130,8 +130,7 @@ macro_rules! border {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct $T(pub Style); pub struct $T(pub Style);
impl Content<Tui> for $T { impl Content<Tui> for $T {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) } fn render (&self, to: &mut TuiOutput) { self.draw(to) }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { self.draw(to) }
} }
)+} )+}
} }

View file

@ -136,29 +136,29 @@ render!(Tui: (self: Groovebox) => {
GrooveboxSamples(self) GrooveboxSamples(self)
)), x); )), x);
let status = EditStatus(&self.sampler, &self.editor, note_pt, pool(sampler(&self.editor))); let status = EditStatus(&self.sampler, &self.editor, note_pt, pool(sampler(&self.editor)));
Fill::xy(lay!([ Fill::xy(lay!(
&self.size, &self.size,
Fill::xy(Align::s(Fixed::y(2, GrooveboxStatus::from(self)))), Fill::xy(Align::s(Fixed::y(2, GrooveboxStatus::from(self)))),
Shrink::y(2, col!(![ Shrink::y(2, col!(
transport, transport,
selector, selector,
status status
])) ))
])) ))
}); });
struct EditStatus<'a, T: Content<Tui>>(&'a Sampler, &'a MidiEditor, usize, T); struct EditStatus<'a, T: Content<Tui>>(&'a Sampler, &'a MidiEditor, usize, T);
impl<'a, T: Content<Tui>> Content<Tui> for EditStatus<'a, T> { impl<'a, T: Content<Tui>> Content<Tui> for EditStatus<'a, T> {
fn content (&self) -> Option<impl Content<Tui>> { fn content (&self) -> Option<impl Content<Tui>> {
Some(Split::n(false, 9, col!([ Some(Split::n(false, 9, col!(
row!(|add|{ row!(
if let Some(sample) = &self.0.mapped[self.2] { Cond::opt(
add(&format!("Sample {}", sample.read().unwrap().end))?; &self.0.mapped[self.2],
} |sample|format!("Sample {}", sample.read().unwrap().end)
add(&MidiEditStatus(&self.1))?; ),
Ok(()) MidiEditStatus(&self.1),
}), ),
lay!([ lay!(
Outer(Style::default().fg(TuiTheme::g(128))), Outer(Style::default().fg(TuiTheme::g(128))),
Fill::x(Fixed::y(8, if let Some((_, sample)) = &self.0.recording { Fill::x(Fixed::y(8, if let Some((_, sample)) = &self.0.recording {
SampleViewer(Some(sample.clone())) SampleViewer(Some(sample.clone()))
@ -167,16 +167,8 @@ impl<'a, T: Content<Tui>> Content<Tui> for EditStatus<'a, T> {
} else { } else {
SampleViewer(None) SampleViewer(None)
})), })),
]), ),
]), &self.3)) ), &self.3))
}
}
impl<'a, T: Content<Tui>> Content<Tui> for EditStatus<'a, T> {
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
self.content().unwrap().min_size(to)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
self.content().unwrap().render(to)
} }
} }
@ -185,7 +177,7 @@ render!(Tui: (self: GrooveboxSamples<'a>) => {
let note_lo = self.0.editor.note_lo().load(Relaxed); let note_lo = self.0.editor.note_lo().load(Relaxed);
let note_pt = self.0.editor.note_point(); let note_pt = self.0.editor.note_point();
let note_hi = self.0.editor.note_hi(); let note_hi = self.0.editor.note_hi();
Fill::xy(col_iter!((note_lo..=note_hi).rev() => |note| { Fill::xy(Coll::map((note_lo..=note_hi).rev(), |note, i| {
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset }; let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
let mut fg = TuiTheme::g(160); let mut fg = TuiTheme::g(160);
if let Some((index, _)) = self.0.sampler.recording { if let Some((index, _)) = self.0.sampler.recording {
@ -196,11 +188,11 @@ render!(Tui: (self: GrooveboxSamples<'a>) => {
} else if self.0.sampler.mapped[note].is_some() { } else if self.0.sampler.mapped[note].is_some() {
fg = TuiTheme::g(224); fg = TuiTheme::g(224);
} }
Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] { Push::y(i, Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] {
Tui::fg(fg, format!("{note:3} ?????? ")) Tui::fg(fg, format!("{note:3} ?????? "))
} else { } else {
Tui::fg(fg, format!("{note:3} (none) ")) Tui::fg(fg, format!("{note:3} (none) "))
}) }))
})) }))
}); });

View file

@ -2,7 +2,7 @@ use crate::*;
pub struct Meters<'a>(pub &'a[f32]); pub struct Meters<'a>(pub &'a[f32]);
render!(Tui: (self: Meters<'a>) => col!([ render!(Tui: (self: Meters<'a>) => col!(
&format!("L/{:>+9.3}", self.0[0]), &format!("L/{:>+9.3}", self.0[0]),
&format!("R/{:>+9.3}", self.0[1]), &format!("R/{:>+9.3}", self.0[1]),
])); ));

View file

@ -96,10 +96,7 @@ pub struct TrackView<'a> {
pub entered: bool, pub entered: bool,
} }
impl<'a> Render<Tui> for TrackView<'a> { impl<'a> Content<Tui> for TrackView<'a> {
fn min_size (&self, area: [u16;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> { fn render (&self, to: &mut TuiOutput) -> Usually<()> {
todo!(); todo!();
//let mut area = to.area(); //let mut area = to.area();
@ -131,7 +128,7 @@ impl<'a> Render<Tui> for TrackView<'a> {
} }
//impl Content<Tui> for Mixer<Tui> { //impl Content<Tui> for Mixer<Tui> {
//fn content (&self) -> impl Render<Tui> { //fn content (&self) -> impl Content<Tui> {
//Stack::right(|add| { //Stack::right(|add| {
//for channel in self.tracks.iter() { //for channel in self.tracks.iter() {
//add(channel)?; //add(channel)?;
@ -142,7 +139,7 @@ impl<'a> Render<Tui> for TrackView<'a> {
//} //}
//impl Content<Tui> for Track<Tui> { //impl Content<Tui> for Track<Tui> {
//fn content (&self) -> impl Render<Tui> { //fn content (&self) -> impl Content<Tui> {
//TrackView { //TrackView {
//chain: Some(&self), //chain: Some(&self),
//direction: tek_core::Direction::Right, //direction: tek_core::Direction::Right,

View file

@ -69,7 +69,7 @@ render!(Tui: (self: PianoHorizontal) => {
let notes = move||PianoHorizontalNotes(self); let notes = move||PianoHorizontalNotes(self);
let cursor = move||PianoHorizontalCursor(self); let cursor = move||PianoHorizontalCursor(self);
let border = Fill::xy(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); let border = Fill::xy(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb)));
let with_border = |x|lay!([border, Padding::xy(0, 0, &x)]); let with_border = |x|lay!(border, Padding::xy(0, 0, &x));
with_border(lay!( with_border(lay!(
Push::x(0, row!( Push::x(0, row!(
//" ", //" ",
@ -84,13 +84,13 @@ render!(Tui: (self: PianoHorizontal) => {
)), )),
Bsp::e( Bsp::e(
Fixed::x(self.keys_width, keys()), Fixed::x(self.keys_width, keys()),
Fill::xy(lay!([ Fill::xy(lay!(
&self.size, &self.size,
Fill::xy(lay!( Fill::xy(lay!(
Fill::xy(notes()), Fill::xy(notes()),
Fill::xy(cursor()), Fill::xy(cursor()),
)) ))
])), )),
), ),
))) )))
)) ))

View file

@ -150,11 +150,8 @@ impl Plugin {
}) })
} }
} }
impl Render<Tui> for Plugin { impl Content<Tui> for Plugin {
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { fn render (&self, to: &mut TuiOutput) {
Ok(Some(to))
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let area = to.area(); let area = to.area();
let [x, y, _, height] = area; let [x, y, _, height] = area;
let mut width = 20u16; let mut width = 20u16;
@ -186,12 +183,11 @@ impl Render<Tui> for Plugin {
}, },
_ => {} _ => {}
}; };
draw_header(self, to, x, y, width)?; draw_header(self, to, x, y, width);
Ok(())
} }
} }
fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> Usually<()> { fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) {
let style = Style::default().gray(); let style = Style::default().gray();
let label1 = format!(" {}", state.name); let label1 = format!(" {}", state.name);
to.blit(&label1, x + 1, y, Some(style.white().bold())); to.blit(&label1, x + 1, y, Some(style.white().bold()));
@ -199,7 +195,6 @@ fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> U
let label2 = format!("{}", &path[..((w as usize - 10).min(path.len()))]); let label2 = format!("{}", &path[..((w as usize - 10).min(path.len()))]);
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim())); to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
} }
Ok(())
//Ok(Rect { x, y, width: w, height: 1 }) //Ok(Rect { x, y, width: w, height: 1 })
} }

View file

@ -223,7 +223,7 @@ render!(Tui: (self: PoolView<'a>) => {
length.focus = Some(*focus); length.focus = Some(*focus);
} }
} }
add(&Tui::bg(color.base.rgb, Fill::x(col!([ add(&Tui::bg(color.base.rgb, Fill::x(col!(
Fill::x(lay!(|add|{ Fill::x(lay!(|add|{
add(&Fill::x(Align::w(format!(" {i}"))))?; add(&Fill::x(Align::w(format!(" {i}"))))?;
add(&Fill::x(Align::e(Pull::x(1, length.clone())))) add(&Fill::x(Align::e(Pull::x(1, length.clone()))))
@ -237,7 +237,7 @@ render!(Tui: (self: PoolView<'a>) => {
}; };
row2 row2
}), }),
]))))?; ))))?;
if i == self.0.phrase_index() { if i == self.0.phrase_index() {
add(&CORNERS)?; add(&CORNERS)?;
} }

View file

@ -8,13 +8,13 @@ pub struct PhraseSelector {
} }
// TODO: Display phrases always in order of appearance // TODO: Display phrases always in order of appearance
render!(Tui: (self: PhraseSelector) => Fixed::xy(24, 1, row!([ render!(Tui: (self: PhraseSelector) => Fixed::xy(24, 1, row!(
Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)), Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)),
Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([ Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!(
format!("{:8}", &self.name[0..8.min(self.name.len())]), format!("{:8}", &self.name[0..8.min(self.name.len())]),
Tui::bg(self.color.dark.rgb, &self.time), Tui::bg(self.color.dark.rgb, &self.time),
])), )),
]))); )));
impl PhraseSelector { impl PhraseSelector {

View file

@ -171,10 +171,6 @@ fn draw_sample (
} }
impl Content<Tui> for AddSampleModal { impl Content<Tui> for AddSampleModal {
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
todo!()
//Align::Center(()).layout(to)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> { fn render (&self, to: &mut TuiOutput) -> Usually<()> {
todo!() todo!()
//let area = to.area(); //let area = to.area();

View file

@ -50,8 +50,8 @@ render!(Tui: (self: SamplerTui) => {
let fg = self.color.base.rgb; let fg = self.color.base.rgb;
let bg = self.color.darkest.rgb; let bg = self.color.darkest.rgb;
let border = Fill::xy(Outer(Style::default().fg(fg).bg(bg))); let border = Fill::xy(Outer(Style::default().fg(fg).bg(bg)));
let with_border = |x|lay!([border, Fill::xy(&x)]); let with_border = |x|lay!(border, Fill::xy(&x));
let with_size = |x|lay!([self.size, x]); let with_size = |x|lay!(self.size, x);
Tui::bg(bg, Fill::xy(with_border(Bsp::s( Tui::bg(bg, Fill::xy(with_border(Bsp::s(
Tui::fg(self.color.light.rgb, Tui::bold(true, &"Sampler")), Tui::fg(self.color.light.rgb, Tui::bold(true, &"Sampler")),
with_size(Shrink::y(1, Bsp::e( with_size(Shrink::y(1, Bsp::e(

View file

@ -49,28 +49,28 @@ render!(Tui: (self: SequencerTui) => {
let status = SequencerStatus::from(self); let status = SequencerStatus::from(self);
let with_status = |x|Split::n(false, if self.status { 2 } else { 0 }, status, x); let with_status = |x|Split::n(false, if self.status { 2 } else { 0 }, status, x);
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x); let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
let with_size = |x|lay!([self.size, x]); let with_size = |x|lay!(self.size, x);
let editor = with_editbar(with_pool(Fill::xy(&self.editor))); let editor = with_editbar(with_pool(Fill::xy(&self.editor)));
let color = self.player.play_phrase().as_ref().map(|(_,p)| let color = self.player.play_phrase().as_ref().map(|(_,p)|
p.as_ref().map(|p|p.read().unwrap().color) p.as_ref().map(|p|p.read().unwrap().color)
).flatten().clone(); ).flatten().clone();
let toolbar = Cond::when(self.transport, row!([ let toolbar = Cond::when(self.transport, row!(
PlayPause(self.clock.is_rolling()), PlayPause(self.clock.is_rolling()),
TransportView::new(self, color, true), TransportView::new(self, color, true),
])); ));
let play_queue = Cond::when(self.selectors, row!([ let play_queue = Cond::when(self.selectors, row!(
PhraseSelector::play_phrase(&self.player), PhraseSelector::play_phrase(&self.player),
PhraseSelector::next_phrase(&self.player), PhraseSelector::next_phrase(&self.player),
])); ));
Min::y(15, with_size(with_status(col!([ Min::y(15, with_size(with_status(col!(
toolbar, toolbar,
play_queue, play_queue,
editor, editor,
])))) ))))
}); });
audio!(|self:SequencerTui, client, scope|{ audio!(|self:SequencerTui, client, scope|{
// Start profiling cycle // Start profiling cycle

View file

@ -2,22 +2,19 @@ use crate::*;
pub struct MidiEditStatus<'a>(pub &'a MidiEditor); pub struct MidiEditStatus<'a>(pub &'a MidiEditor);
render!(Tui: (self:MidiEditStatus<'a>) => { render!(Tui: (self:MidiEditStatus<'a>) => {
let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) {
(phrase.color, phrase.name.clone(), phrase.length, phrase.looped) (phrase.color, phrase.name.clone(), phrase.length, phrase.looped)
} else { } else {
(ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false)
}; };
let field = move|x, y|row!(
let field = move|x, y|row!([
Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)),
Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "")), Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "")),
Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y), Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y),
]); );
let bg = color.darkest.rgb; let bg = color.darkest.rgb;
let fg = color.lightest.rgb; let fg = color.lightest.rgb;
Tui::bg(bg, Fill::x(Tui::fg(fg, row!([ Tui::bg(bg, Fill::x(Tui::fg(fg, row!(
field(" Time", format!("{}/{}-{} ({}*{}) {}", field(" Time", format!("{}/{}-{} ({}*{}) {}",
self.0.time_point(), self.0.time_start().get(), self.0.time_end(), self.0.time_point(), self.0.time_start().get(), self.0.time_end(),
self.0.time_axis().get(), self.0.time_zoom().get(), self.0.time_axis().get(), self.0.time_zoom().get(),
@ -27,7 +24,7 @@ render!(Tui: (self:MidiEditStatus<'a>) => {
self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(),
to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()),
self.0.note_axis().get())) self.0.note_axis().get()))
])))) ))))
}); });
/// Status bar for sequencer app /// Status bar for sequencer app
@ -50,21 +47,21 @@ from!(|state:&SequencerTui|SequencerStatus = {
size: format!("{}x{}│", width, state.size.h()), size: format!("{}x{}│", width, state.size.h()),
} }
}); });
render!(Tui: (self: SequencerStatus) => Fixed::y(2, lay!([ render!(Tui: (self: SequencerStatus) => Fixed::y(2, lay!(
Self::help(), Self::help(),
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
]))); )));
impl SequencerStatus { impl SequencerStatus {
fn help () -> impl Content<Tui> { fn help () -> impl Content<Tui> {
let single = |binding, command|row!([" ", col!([ let single = |binding, command|row!(" ", col!(
Tui::fg(TuiTheme::yellow(), binding), Tui::fg(TuiTheme::yellow(), binding),
command command
])]); ));
let double = |(b1, c1), (b2, c2)|col!([ let double = |(b1, c1), (b2, c2)|col!(
row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
]); );
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
single("SPACE", "play/pause"), single("SPACE", "play/pause"),
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
double(("a", "append"), ("s", "set note"),), double(("a", "append"), ("s", "set note"),),
@ -72,10 +69,10 @@ impl SequencerStatus {
double(("[]", "phrase"), ("{}", "order"), ), double(("[]", "phrase"), ("{}", "order"), ),
double(("q", "enqueue"), ("e", "edit"), ), double(("q", "enqueue"), ("e", "edit"), ),
double(("c", "color"), ("", ""),), double(("c", "color"), ("", ""),),
])) ))
} }
fn stats (&self) -> impl Content<Tui> + use<'_> { fn stats (&self) -> impl Content<Tui> + use<'_> {
row!([&self.cpu, &self.size]) row!(&self.cpu, &self.size)
} }
} }
@ -99,10 +96,10 @@ from!(|state: &Groovebox|GrooveboxStatus = {
size: format!("{}x{}│", width, state.size.h()), size: format!("{}x{}│", width, state.size.h()),
} }
}); });
render!(Tui: (self: GrooveboxStatus) => Fixed::y(2, lay!([ render!(Tui: (self: GrooveboxStatus) => Fixed::y(2, lay!(
Self::help(), Self::help(),
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
]))); )));
impl GrooveboxStatus { impl GrooveboxStatus {
fn help () -> impl Content<Tui> { fn help () -> impl Content<Tui> {
let single = |binding, command|row!(" ", col!( let single = |binding, command|row!(" ", col!(
@ -148,21 +145,21 @@ from!(|state:&ArrangerTui|ArrangerStatus = {
size: format!("{}x{}│", width, state.size.h()), size: format!("{}x{}│", width, state.size.h()),
} }
}); });
render!(Tui: (self: ArrangerStatus) => Fixed::y(2, lay!([ render!(Tui: (self: ArrangerStatus) => Fixed::y(2, lay!(
Self::help(), Self::help(),
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
]))); )));
impl ArrangerStatus { impl ArrangerStatus {
fn help () -> impl Content<Tui> { fn help () -> impl Content<Tui> {
let single = |binding, command|row!([" ", col!([ let single = |binding, command|row!(" ", col!(
Tui::fg(TuiTheme::yellow(), binding), Tui::fg(TuiTheme::yellow(), binding),
command command
])]); ));
let double = |(b1, c1), (b2, c2)|col!([ let double = |(b1, c1), (b2, c2)|col!(
row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
]); );
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
single("SPACE", "play/pause"), single("SPACE", "play/pause"),
single(" Ctrl", " scroll"), single(" Ctrl", " scroll"),
single(" ▲▼▶◀", " cell"), single(" ▲▼▶◀", " cell"),
@ -172,9 +169,9 @@ impl ArrangerStatus {
double(("a", "append"), ("s", "set"),), double(("a", "append"), ("s", "set"),),
double((",.", "length"), ("<>", "triplet"),), double((",.", "length"), ("<>", "triplet"),),
double(("[]", "phrase"), ("{}", "order"),), double(("[]", "phrase"), ("{}", "order"),),
])) ))
} }
fn stats (&self) -> impl Content<Tui> + use<'_> { fn stats (&self) -> impl Content<Tui> + use<'_> {
row!([&self.cpu, &self.size]) row!(&self.cpu, &self.size)
} }
} }

View file

@ -26,7 +26,7 @@ impl<W: Content<Tui>> Content<Tui> for Bold<W> {
fn content (&self) -> Option<impl Content<Tui>> { fn content (&self) -> Option<impl Content<Tui>> {
Some(&self.1) Some(&self.1)
} }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { fn render (&self, to: &mut TuiOutput) {
to.fill_bold(to.area(), self.0); to.fill_bold(to.area(), self.0);
self.1.render(to) self.1.render(to)
} }
@ -38,7 +38,7 @@ impl<W: Content<Tui>> Content<Tui> for Foreground<W> {
fn content (&self) -> Option<impl Content<Tui>> { fn content (&self) -> Option<impl Content<Tui>> {
Some(&self.1) Some(&self.1)
} }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { fn render (&self, to: &mut TuiOutput) {
to.fill_fg(to.area(), self.0); to.fill_fg(to.area(), self.0);
self.1.render(to) self.1.render(to)
} }
@ -50,7 +50,7 @@ impl<W: Content<Tui>> Content<Tui> for Background<W> {
fn content (&self) -> Option<impl Content<Tui>> { fn content (&self) -> Option<impl Content<Tui>> {
Some(&self.1) Some(&self.1)
} }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { fn render (&self, to: &mut TuiOutput) {
to.fill_bg(to.area(), self.0); to.fill_bg(to.area(), self.0);
self.1.render(to) self.1.render(to)
} }
@ -62,7 +62,7 @@ impl Content<Tui> for Styled<&str> {
fn content (&self) -> Option<impl Content<Tui>> { fn content (&self) -> Option<impl Content<Tui>> {
Some(&self.1) Some(&self.1)
} }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { fn render (&self, to: &mut TuiOutput) {
// FIXME // FIXME
let [x, y, ..] = to.area(); let [x, y, ..] = to.area();
//let [w, h] = self.min_size(to.area().wh())?.unwrap(); //let [w, h] = self.min_size(to.area().wh())?.unwrap();

View file

@ -23,10 +23,10 @@ from_jack!(|jack|TransportTui Self {
has_clock!(|self: TransportTui|&self.clock); has_clock!(|self: TransportTui|&self.clock);
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
handle!(<Tui>|self: TransportTui, from|TransportCommand::execute_with_state(self, from)); handle!(<Tui>|self: TransportTui, from|TransportCommand::execute_with_state(self, from));
render!(Tui: (self: TransportTui) => Fixed::y(3, row!([ render!(Tui: (self: TransportTui) => Fixed::y(3, row!(
" ", Fixed::x(5, PlayPause(false)), " ", Fixed::x(5, PlayPause(false)),
" ", Shrink::x(1, TransportView::new(self, Some(self.color), true)), " ", Shrink::x(1, TransportView::new(self, Some(self.color), true)),
]))); )));
impl std::fmt::Debug for TransportTui { impl std::fmt::Debug for TransportTui {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("TransportTui") f.debug_struct("TransportTui")
@ -89,42 +89,42 @@ impl TransportView {
} }
render!(Tui: (self: TransportView) => { render!(Tui: (self: TransportView) => {
let color = self.color; let color = self.color;
Fixed::y(3, Tui::bg(color.base.rgb, Fill::x(row!([ Fixed::y(3, Tui::bg(color.base.rgb, Fill::x(row!(
col!([ col!(
TransportField(" Beat", self.beat.as_str(), &color), TransportField(" Beat", self.beat.as_str(), &color),
TransportField(" Time", format!("{:.1}s", self.current_second).as_str(), &color), TransportField(" Time", format!("{:.1}s", self.current_second).as_str(), &color),
TransportField(" BPM", self.bpm.as_str(), &color), TransportField(" BPM", self.bpm.as_str(), &color),
]), ),
col!([ col!(
TransportField(" Rate", format!("{}", self.sr).as_str(), &color), TransportField(" Rate", format!("{}", self.sr).as_str(), &color),
TransportField(" Chunk", format!("{}", self.chunk).as_str(), &color), TransportField(" Chunk", format!("{}", self.chunk).as_str(), &color),
TransportField(" Lag", format!("{:.3}ms", self.latency).as_str(), &color), TransportField(" Lag", format!("{:.3}ms", self.latency).as_str(), &color),
]), ),
col!([ col!(
//Field(" CPU%", format!("{:.1}ms", self.perf).as_str(), &color), //Field(" CPU%", format!("{:.1}ms", self.perf).as_str(), &color),
]), ),
])))) ))))
}); });
struct TransportField<'a>(&'a str, &'a str, &'a ItemPalette); struct TransportField<'a>(&'a str, &'a str, &'a ItemPalette);
render!(Tui: (self: TransportField<'a>) => row!([ render!(Tui: (self: TransportField<'a>) => row!(
Tui::fg_bg(self.2.lightest.rgb, self.2.base.rgb, Tui::bold(true, self.0)), Tui::fg_bg(self.2.lightest.rgb, self.2.base.rgb, Tui::bold(true, self.0)),
Tui::fg_bg(self.2.base.rgb, self.2.darkest.rgb, ""), Tui::fg_bg(self.2.base.rgb, self.2.darkest.rgb, ""),
Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, format!("{:>10}", self.1)), Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, format!("{:>10}", self.1)),
Tui::fg_bg(self.2.darkest.rgb, self.2.base.rgb, ""), Tui::fg_bg(self.2.darkest.rgb, self.2.base.rgb, ""),
])); ));
pub struct PlayPause(pub bool); pub struct PlayPause(pub bool);
render!(Tui: (self: PlayPause) => Tui::bg( render!(Tui: (self: PlayPause) => Tui::bg(
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
Fixed::x(5, col!(|add|if self.0 { Fixed::x(5, col!(|add|if self.0 {
add(&Tui::fg(Color::Rgb(0, 255, 0), col!([ add(&Tui::fg(Color::Rgb(0, 255, 0), col!(
" 🭍🭑🬽 ", " 🭍🭑🬽 ",
" 🭞🭜🭘 ", " 🭞🭜🭘 ",
]))) )))
} else { } else {
add(&Tui::fg(Color::Rgb(255, 128, 0), col!([ add(&Tui::fg(Color::Rgb(255, 128, 0), col!(
" ▗▄▖ ", " ▗▄▖ ",
" ▝▀▘ ", " ▝▀▘ ",
]))) )))
})) }))
)); ));
impl HasFocus for TransportTui { impl HasFocus for TransportTui {
@ -152,7 +152,7 @@ impl FocusWrap<TransportFocus> for TransportFocus {
let focused = focus == self; let focused = focus == self;
let corners = focused.then_some(CORNERS); let corners = focused.then_some(CORNERS);
//let highlight = focused.then_some(Tui::bg(Color::Rgb(60, 70, 50))); //let highlight = focused.then_some(Tui::bg(Color::Rgb(60, 70, 50)));
lay!([corners, /*highlight,*/ *content]) lay!(corners, /*highlight,*/ *content)
} }
} }
impl FocusWrap<TransportFocus> for Option<TransportFocus> { impl FocusWrap<TransportFocus> for Option<TransportFocus> {
@ -162,7 +162,7 @@ impl FocusWrap<TransportFocus> for Option<TransportFocus> {
let focused = Some(focus) == self; let focused = Some(focus) == self;
let corners = focused.then_some(CORNERS); let corners = focused.then_some(CORNERS);
//let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50))); //let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
lay!([corners, /*highlight,*/ *content]) lay!(corners, /*highlight,*/ *content)
} }
} }
pub trait TransportControl<T>: HasClock + { pub trait TransportControl<T>: HasClock + {