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 std::sync::RwLock;
/// Conditional rendering, in unary and binary forms.
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> {
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.
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
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {}
/*
/// Rendering of iterable collections, one-to-one and many-to one.
pub struct Coll;
@ -73,24 +80,25 @@ impl Coll {
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync
{
Map(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)
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 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,
I: Iterator<Item = T> + Send + Sync,
R: Content<E>,
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,
I: Iterator<Item = T> + Send + Sync,
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) {
let mut index = 0;
for item in self.1 {
for item in &mut*self.1.write().unwrap() {
(self.2)(item, index).render(to);
index += 1;
}
}
}
/*
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where
E: Engine,
I: Iterator<Item = T> + Send + Sync,

View file

@ -102,23 +102,23 @@ impl ArrangerTui {
render!(Tui: (self: ArrangerTui) => {
let play = PlayPause(self.clock.is_rolling());
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 with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self);
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), 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 color = self.color;
add(&Fill::xy(Tui::bg(color.darkest.rgb, "")))?;
add(&Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
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::xy(&self.editor),
]))))))
))))))
});
audio!(|self: ArrangerTui, client, scope|{
// 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_io; pub(crate) use self::v_io::*;
mod v_sep; pub(crate) use self::v_sep::*;
// egyptian snakes den
const HEADER_H: u16 = 5;
const SCENES_W_OFFSET: u16 = 3;
impl ArrangerTui {
pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Render<Tui> + use<'_> {
lay!([
pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Content<Tui> + use<'_> {
lay!(
ArrangerVColSep::from(state),
ArrangerVRowSep::from((state, factor)),
col!([
col!(
ArrangerVHead::from(state),
ArrangerVIns::from(state),
ArrangerVClips::from((state, factor)),
ArrangerVClips::new(state, factor),
ArrangerVOuts::from(state),
]),
),
ArrangerVCursor::from((state, factor)),
])
)
}
}

View file

@ -20,7 +20,8 @@ impl<'a> ArrangerVClips<'a> {
impl<'a, E: Engine> Content<E> for ArrangerVClips<'a> {
fn content (&self) -> Option<impl Content<E>> {
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))
}
}
@ -36,9 +37,8 @@ impl<'a> ArrangerVClips<'a> {
if playing { "" } else { " " }),
Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))),
row_iter!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => {
Self::format_clip(scene, index, track, (x2 - x1) as u16, height)
})))}
Coll::map(ArrangerTrack::with_widths(tracks), |(index, track, x1, x2), _|
Push::x(Self::format_clip(scene, index, track, (x2 - x1) as u16, height)))))}
fn format_clip (
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,
});
impl Content<Tui> for ArrangerVCursor {
fn content (&self) -> Option<impl Content<Tui>> {
render!(Tui: (self: ArrangerVHead<'a>) => {
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)])
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) => {
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!([
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<'_> {
/// 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);
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);
@ -130,8 +130,7 @@ macro_rules! border {
#[derive(Copy, Clone)]
pub struct $T(pub Style);
impl Content<Tui> for $T {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { self.draw(to) }
fn render (&self, to: &mut TuiOutput) { self.draw(to) }
}
)+}
}

View file

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

View file

@ -2,7 +2,7 @@ use crate::*;
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!("R/{:>+9.3}", self.0[1]),
]));
));

View file

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

View file

@ -69,7 +69,7 @@ render!(Tui: (self: PianoHorizontal) => {
let notes = move||PianoHorizontalNotes(self);
let cursor = move||PianoHorizontalCursor(self);
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!(
Push::x(0, row!(
//" ",
@ -84,13 +84,13 @@ render!(Tui: (self: PianoHorizontal) => {
)),
Bsp::e(
Fixed::x(self.keys_width, keys()),
Fill::xy(lay!([
Fill::xy(lay!(
&self.size,
Fill::xy(lay!(
Fill::xy(notes()),
Fill::xy(cursor()),
))
])),
)),
),
)))
))

View file

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

View file

@ -223,7 +223,7 @@ render!(Tui: (self: PoolView<'a>) => {
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|{
add(&Fill::x(Align::w(format!(" {i}"))))?;
add(&Fill::x(Align::e(Pull::x(1, length.clone()))))
@ -237,7 +237,7 @@ render!(Tui: (self: PoolView<'a>) => {
};
row2
}),
]))))?;
))))?;
if i == self.0.phrase_index() {
add(&CORNERS)?;
}

View file

@ -8,13 +8,13 @@ pub struct PhraseSelector {
}
// 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_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())]),
Tui::bg(self.color.dark.rgb, &self.time),
])),
])));
)),
)));
impl PhraseSelector {

View file

@ -171,10 +171,6 @@ fn draw_sample (
}
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<()> {
todo!()
//let area = to.area();

View file

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

View file

@ -49,28 +49,28 @@ render!(Tui: (self: SequencerTui) => {
let status = SequencerStatus::from(self);
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_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 color = self.player.play_phrase().as_ref().map(|(_,p)|
p.as_ref().map(|p|p.read().unwrap().color)
).flatten().clone();
let toolbar = Cond::when(self.transport, row!([
let toolbar = Cond::when(self.transport, row!(
PlayPause(self.clock.is_rolling()),
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::next_phrase(&self.player),
]));
));
Min::y(15, with_size(with_status(col!([
Min::y(15, with_size(with_status(col!(
toolbar,
play_queue,
editor,
]))))
))))
});
audio!(|self:SequencerTui, client, scope|{
// Start profiling cycle

View file

@ -2,22 +2,19 @@ use crate::*;
pub struct MidiEditStatus<'a>(pub &'a MidiEditor);
render!(Tui: (self:MidiEditStatus<'a>) => {
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)
} else {
(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.light.rgb, color.darker.rgb, Tui::bold(true, "")),
Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y),
]);
);
let bg = color.darkest.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!("{}/{}-{} ({}*{}) {}",
self.0.time_point(), self.0.time_start().get(), self.0.time_end(),
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(),
to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()),
self.0.note_axis().get()))
]))))
))))
});
/// Status bar for sequencer app
@ -50,21 +47,21 @@ from!(|state:&SequencerTui|SequencerStatus = {
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(),
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
])));
)));
impl SequencerStatus {
fn help () -> impl Content<Tui> {
let single = |binding, command|row!([" ", col!([
let single = |binding, command|row!(" ", col!(
Tui::fg(TuiTheme::yellow(), binding),
command
])]);
let double = |(b1, c1), (b2, c2)|col!([
row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]),
row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]),
]);
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([
));
let double = |(b1, c1), (b2, c2)|col!(
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
);
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
single("SPACE", "play/pause"),
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
double(("a", "append"), ("s", "set note"),),
@ -72,10 +69,10 @@ impl SequencerStatus {
double(("[]", "phrase"), ("{}", "order"), ),
double(("q", "enqueue"), ("e", "edit"), ),
double(("c", "color"), ("", ""),),
]))
))
}
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()),
}
});
render!(Tui: (self: GrooveboxStatus) => Fixed::y(2, lay!([
render!(Tui: (self: GrooveboxStatus) => Fixed::y(2, lay!(
Self::help(),
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
])));
)));
impl GrooveboxStatus {
fn help () -> impl Content<Tui> {
let single = |binding, command|row!(" ", col!(
@ -148,21 +145,21 @@ from!(|state:&ArrangerTui|ArrangerStatus = {
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(),
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
])));
)));
impl ArrangerStatus {
fn help () -> impl Content<Tui> {
let single = |binding, command|row!([" ", col!([
let single = |binding, command|row!(" ", col!(
Tui::fg(TuiTheme::yellow(), binding),
command
])]);
let double = |(b1, c1), (b2, c2)|col!([
row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]),
row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]),
]);
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([
));
let double = |(b1, c1), (b2, c2)|col!(
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
);
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
single("SPACE", "play/pause"),
single(" Ctrl", " scroll"),
single(" ▲▼▶◀", " cell"),
@ -172,9 +169,9 @@ impl ArrangerStatus {
double(("a", "append"), ("s", "set"),),
double((",.", "length"), ("<>", "triplet"),),
double(("[]", "phrase"), ("{}", "order"),),
]))
))
}
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>> {
Some(&self.1)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
fn render (&self, to: &mut TuiOutput) {
to.fill_bold(to.area(), self.0);
self.1.render(to)
}
@ -38,7 +38,7 @@ impl<W: Content<Tui>> Content<Tui> for Foreground<W> {
fn content (&self) -> Option<impl Content<Tui>> {
Some(&self.1)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
fn render (&self, to: &mut TuiOutput) {
to.fill_fg(to.area(), self.0);
self.1.render(to)
}
@ -50,7 +50,7 @@ impl<W: Content<Tui>> Content<Tui> for Background<W> {
fn content (&self) -> Option<impl Content<Tui>> {
Some(&self.1)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
fn render (&self, to: &mut TuiOutput) {
to.fill_bg(to.area(), self.0);
self.1.render(to)
}
@ -62,7 +62,7 @@ impl Content<Tui> for Styled<&str> {
fn content (&self) -> Option<impl Content<Tui>> {
Some(&self.1)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
fn render (&self, to: &mut TuiOutput) {
// FIXME
let [x, y, ..] = to.area();
//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);
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
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)),
" ", Shrink::x(1, TransportView::new(self, Some(self.color), true)),
])));
)));
impl std::fmt::Debug for TransportTui {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("TransportTui")
@ -89,42 +89,42 @@ impl TransportView {
}
render!(Tui: (self: TransportView) => {
let color = self.color;
Fixed::y(3, Tui::bg(color.base.rgb, Fill::x(row!([
col!([
Fixed::y(3, Tui::bg(color.base.rgb, Fill::x(row!(
col!(
TransportField(" Beat", self.beat.as_str(), &color),
TransportField(" Time", format!("{:.1}s", self.current_second).as_str(), &color),
TransportField(" BPM", self.bpm.as_str(), &color),
]),
col!([
),
col!(
TransportField(" Rate", format!("{}", self.sr).as_str(), &color),
TransportField(" Chunk", format!("{}", self.chunk).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),
]),
]))))
),
))))
});
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.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.darkest.rgb, self.2.base.rgb, ""),
]));
));
pub struct PlayPause(pub bool);
render!(Tui: (self: PlayPause) => Tui::bg(
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,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 {
add(&Tui::fg(Color::Rgb(255, 128, 0), col!([
add(&Tui::fg(Color::Rgb(255, 128, 0), col!(
" ▗▄▖ ",
" ▝▀▘ ",
])))
)))
}))
));
impl HasFocus for TransportTui {
@ -152,7 +152,7 @@ impl FocusWrap<TransportFocus> for TransportFocus {
let focused = focus == self;
let corners = focused.then_some(CORNERS);
//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> {
@ -162,7 +162,7 @@ impl FocusWrap<TransportFocus> for Option<TransportFocus> {
let focused = Some(focus) == self;
let corners = focused.then_some(CORNERS);
//let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
lay!([corners, /*highlight,*/ *content])
lay!(corners, /*highlight,*/ *content)
}
}
pub trait TransportControl<T>: HasClock + {