mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
sweeeeping sweep
This commit is contained in:
parent
c9b09b7dea
commit
e677d1d7d4
38 changed files with 766 additions and 691 deletions
|
|
@ -39,6 +39,60 @@ impl<E: Engine> Content<E> for () {}
|
|||
|
||||
impl<E: Engine, T: Content<E>> Content<E> for &T {}
|
||||
|
||||
impl<E: Engine, T: Content<E>> Content<E> for Option<T> {}
|
||||
|
||||
#[macro_export] macro_rules! render {
|
||||
(($self:ident:$Struct:ty) => $content:expr) => {
|
||||
impl <E: Engine> Content<E> for $Struct {
|
||||
fn content (&$self) -> Option<impl Content<E>> {
|
||||
Some($content)
|
||||
}
|
||||
}
|
||||
};
|
||||
(|$self:ident:$Struct:ident $(<
|
||||
$($L:lifetime),*
|
||||
$($T:ident $(:$Trait:path)?),*
|
||||
>)?, $to:ident | $render:expr) => {
|
||||
impl <$($($L),*)? E: Engine, $($T$(:$Trait)?),*> Content<E>
|
||||
for $Struct $(<$($L),* $($T),*>>)? {
|
||||
fn render (&$self, $to: &mut E::Output) {
|
||||
Some($render)
|
||||
}
|
||||
}
|
||||
};
|
||||
($Engine:ty:
|
||||
($self:ident:$Struct:ident $(<$(
|
||||
$($L:lifetime)?
|
||||
$($T:ident)?
|
||||
$(:$Trait:path)?
|
||||
),+>)?) => $content:expr
|
||||
) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Engine>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)?
|
||||
{
|
||||
fn content (&$self) -> Option<impl Content<$Engine>> {
|
||||
Some($content)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($Engine:ty:
|
||||
|$self:ident : $Struct:ident $(<$(
|
||||
$($L:lifetime)?
|
||||
$($T:ident)?
|
||||
$(:$Trait:path)?
|
||||
),+>)?, $to:ident| $render:expr
|
||||
) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Engine>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)?
|
||||
{
|
||||
fn render (&$self, $to: &mut <$Engine as Engine>::Output) {
|
||||
Some($render)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//impl<E: Engine, R: Render<E>> Render<E> for &R {}
|
||||
|
||||
//pub trait Render<E: Engine>: Send + Sync {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,10 @@
|
|||
//! Groupings of elements.
|
||||
|
||||
mod bsp; pub use self::bsp::*;
|
||||
mod split; pub use self::split::*;
|
||||
mod stack; pub use self::stack::*;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// A function or closure that emits renderables.
|
||||
pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {}
|
||||
|
||||
/// Any function or closure that emits renderables for the given engine matches [CollectCallback].
|
||||
impl<E, F> Collector<E> for F
|
||||
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {}
|
||||
|
||||
pub struct Map<E, T, I, R, F>(PhantomData<E>, I, F)
|
||||
where E: Engine, I: Iterator<Item = T>, R: Content<E>, F: Fn(T)->R;
|
||||
|
||||
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F)
|
||||
where E: Engine, I: Iterator<Item = T>, R: Content<E>, F: Fn(&dyn Content<E>, T)->R;
|
||||
|
||||
impl<E: Engine, T, I: Iterator<Item=T>+Send+Sync, R: Content<E>, F: Fn(&dyn Content<E>, T)->R+Send+Sync> Content<E> for Reduce<E, T, I, R, F> {
|
||||
fn render (&self, _to: &mut E::Output) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Conditional rendering, in unary and binary forms.
|
||||
pub struct Cond;
|
||||
|
||||
|
|
@ -74,3 +54,70 @@ impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Either<E, A, B> {
|
|||
if *cond { a.render(to) } else { b.render(to) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A function or closure that emits renderables.
|
||||
pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {}
|
||||
|
||||
/// Any function or closure that emits renderables for the given engine matches [CollectCallback].
|
||||
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;
|
||||
|
||||
impl Coll {
|
||||
pub fn map <E, T, I, R, F>(iterator: I, callback: F) -> Map<E, T, I, R, F> where
|
||||
E: Engine,
|
||||
I: Iterator<Item = T> + Send + Sync,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Map<'a, E, T, I, R, F>(PhantomData<E>, &'a mut 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
|
||||
E: Engine,
|
||||
I: Iterator<Item = T> + Send + Sync,
|
||||
R: Content<E>,
|
||||
F: Fn(T, usize)->R + Send + Sync
|
||||
{
|
||||
fn render (&self, to: &mut E::Output) {
|
||||
let mut index = 0;
|
||||
for item in self.1 {
|
||||
(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,
|
||||
R: Content<E>,
|
||||
F: Fn(R, T, usize) -> R + Send + Sync;
|
||||
impl<E, T, I, R, F> Content<E> for 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
|
||||
{
|
||||
fn render (&self, to: &mut E::Output) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Renders multiple things on top of each other,
|
||||
macro_rules! lay {
|
||||
($($expr:expr),*) => {{
|
||||
let layers = ();
|
||||
$(let layers = Bsp::b(layers, $expr);)*;
|
||||
layers
|
||||
}}
|
||||
}
|
||||
|
||||
/// Renders multiple things on top of each other,
|
||||
macro_rules! col {
|
||||
($($expr:expr),*) => {{
|
||||
let layers = ();
|
||||
$(let layers = Bsp::s(layers, $expr);)*;
|
||||
layers
|
||||
}}
|
||||
}
|
||||
|
||||
pub enum Bsp<E: Engine, X: Content<E>, Y: Content<E>> {
|
||||
/// X is north of Y
|
||||
N(f64, Option<X>, Option<Y>),
|
||||
/// X is south of Y
|
||||
S(f64, Option<X>, Option<Y>),
|
||||
/// X is east of Y
|
||||
E(f64, Option<X>, Option<Y>),
|
||||
/// X is west of Y
|
||||
W(f64, Option<X>, Option<Y>),
|
||||
/// X is above Y
|
||||
A(Option<X>, Option<Y>),
|
||||
/// X is below Y
|
||||
B(Option<X>, Option<Y>),
|
||||
/// Should be avoided.
|
||||
Null(PhantomData<E>),
|
||||
}
|
||||
|
||||
impl<E: Engine, X: Content<E>, Y: Content<E>> Bsp<E, X, Y> {
|
||||
pub fn n (p: f64, x: X, y: Y) -> Self { Self::N(p, Some(x), Some(y)) }
|
||||
pub fn s (p: f64, x: X, y: Y) -> Self { Self::S(p, Some(x), Some(y)) }
|
||||
pub fn e (p: f64, x: X, y: Y) -> Self { Self::E(p, Some(x), Some(y)) }
|
||||
pub fn w (p: f64, x: X, y: Y) -> Self { Self::W(p, Some(x), Some(y)) }
|
||||
pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) }
|
||||
pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) }
|
||||
}
|
||||
|
||||
impl<E: Engine, X: Content<E>, Y: Content<E>> Default for Bsp<E, X, Y> {
|
||||
fn default () -> Self {
|
||||
Self::Null(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, X: Content<E>, Y: Content<E>> Content<E> for Bsp<E, X, Y> {
|
||||
fn render (&self, to: &mut E::Output) {
|
||||
let n = E::Size::zero();
|
||||
let s = to.wh();
|
||||
match self {
|
||||
Self::Null(_) => {},
|
||||
//Self::S(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let h = s_a.h().into();
|
||||
//to.render_in(to.area().clip_h(h).into(), a);
|
||||
//to.render_in(to.area().shrink_y(h).push_y(h).into(), b);
|
||||
//},
|
||||
//Self::E(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let w = s_a.w().into();
|
||||
//to.render_in(to.area().clip_w(w).into(), a);
|
||||
//to.render_in(to.area().push_x(w).shrink_x(w).into(), b);
|
||||
//},
|
||||
//Self::W(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let w = (to.area().w() - s_a.w()).into();
|
||||
//to.render_in(to.area().push_x(w).into(), a);
|
||||
//to.render_in(to.area().shrink_x(w).into(), b);
|
||||
//},
|
||||
//Self::N(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let h = to.area().h() - s_a.h();
|
||||
//to.render_in(to.area().push_y(h).into(), a);
|
||||
//to.render_in(to.area().shrink_y(h).into(), b);
|
||||
//},
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,3 +50,110 @@ impl Direction {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders multiple things on top of each other,
|
||||
#[macro_export] macro_rules! lay {
|
||||
($($expr:expr),* $(,)?) => {{
|
||||
let bsp = ();
|
||||
$(let bsp = Bsp::b(bsp, $expr);)*;
|
||||
bsp
|
||||
}}
|
||||
}
|
||||
|
||||
/// Stack southward.
|
||||
#[macro_export] macro_rules! col {
|
||||
($($expr:expr),* $(,)?) => {{
|
||||
let bsp = ();
|
||||
$(let bsp = Bsp::s(bsp, $expr);)*;
|
||||
bsp
|
||||
}};
|
||||
}
|
||||
|
||||
/// Stack northward.
|
||||
#[macro_export] macro_rules! col_up {
|
||||
($($expr:expr),* $(,)?) => {{
|
||||
let bsp = ();
|
||||
$(let bsp = Bsp::n(bsp, $expr);)*;
|
||||
bsp
|
||||
}}
|
||||
}
|
||||
|
||||
/// Stack eastward.
|
||||
#[macro_export] macro_rules! row {
|
||||
($($expr:expr),* $(,)?) => {{
|
||||
let bsp = ();
|
||||
$(let bsp = Bsp::e(bsp, $expr);)*;
|
||||
bsp
|
||||
}};
|
||||
}
|
||||
|
||||
pub enum Bsp<E: Engine, X: Content<E>, Y: Content<E>> {
|
||||
/// X is north of Y
|
||||
N(Option<f64>, Option<X>, Option<Y>),
|
||||
/// X is south of Y
|
||||
S(Option<f64>, Option<X>, Option<Y>),
|
||||
/// X is east of Y
|
||||
E(Option<f64>, Option<X>, Option<Y>),
|
||||
/// X is west of Y
|
||||
W(Option<f64>, Option<X>, Option<Y>),
|
||||
/// X is above Y
|
||||
A(Option<X>, Option<Y>),
|
||||
/// X is below Y
|
||||
B(Option<X>, Option<Y>),
|
||||
/// Should be avoided.
|
||||
Null(PhantomData<E>),
|
||||
}
|
||||
|
||||
impl<E: Engine, X: Content<E>, Y: Content<E>> Bsp<E, X, Y> {
|
||||
pub fn n (x: X, y: Y) -> Self { Self::N(None, Some(x), Some(y)) }
|
||||
pub fn s (x: X, y: Y) -> Self { Self::S(None, Some(x), Some(y)) }
|
||||
pub fn e (x: X, y: Y) -> Self { Self::E(None, Some(x), Some(y)) }
|
||||
pub fn w (x: X, y: Y) -> Self { Self::W(None, Some(x), Some(y)) }
|
||||
pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) }
|
||||
pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) }
|
||||
}
|
||||
|
||||
impl<E: Engine, X: Content<E>, Y: Content<E>> Default for Bsp<E, X, Y> {
|
||||
fn default () -> Self {
|
||||
Self::Null(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, X: Content<E>, Y: Content<E>> Content<E> for Bsp<E, X, Y> {
|
||||
fn render (&self, to: &mut E::Output) {
|
||||
let n = E::Size::zero();
|
||||
let s = to.wh();
|
||||
match self {
|
||||
Self::Null(_) => {},
|
||||
//Self::S(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let h = s_a.h().into();
|
||||
//to.render_in(to.area().clip_h(h).into(), a);
|
||||
//to.render_in(to.area().shrink_y(h).push_y(h).into(), b);
|
||||
//},
|
||||
//Self::E(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let w = s_a.w().into();
|
||||
//to.render_in(to.area().clip_w(w).into(), a);
|
||||
//to.render_in(to.area().push_x(w).shrink_x(w).into(), b);
|
||||
//},
|
||||
//Self::W(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let w = (to.area().w() - s_a.w()).into();
|
||||
//to.render_in(to.area().push_x(w).into(), a);
|
||||
//to.render_in(to.area().shrink_x(w).into(), b);
|
||||
//},
|
||||
//Self::N(p, a, b) => {
|
||||
//let s_a = a.min_size(s)?.unwrap_or(n);
|
||||
//let _ = b.min_size(s)?.unwrap_or(n);
|
||||
//let h = to.area().h() - s_a.h();
|
||||
//to.render_in(to.area().push_y(h).into(), a);
|
||||
//to.render_in(to.area().shrink_y(h).into(), b);
|
||||
//},
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ macro_rules! transform_xy {
|
|||
}
|
||||
impl<E: Engine, T: Content<E>> Content<E> for $Enum<E, T> {
|
||||
fn content (&self) -> Option<impl Content<E>> {
|
||||
match self {
|
||||
Self::_Unused(_) => None,
|
||||
Self::X(item) => Some(item),
|
||||
Self::Y(item) => Some(item),
|
||||
Self::XY(item) => Some(item),
|
||||
}
|
||||
Some(match self {
|
||||
Self::X(item) => item,
|
||||
Self::Y(item) => item,
|
||||
Self::XY(item) => item,
|
||||
_ => unreachable!()
|
||||
})
|
||||
}
|
||||
fn area (&$self, $to: <E as Engine>::Area) -> <E as Engine>::Area {
|
||||
$area
|
||||
|
|
@ -42,16 +42,13 @@ macro_rules! transform_xy {
|
|||
|
||||
transform_xy!(self: Fill |to|{
|
||||
let [x0, y0, wmax, hmax] = to.xywh();
|
||||
if let Some(content) = self.content() {
|
||||
let [x, y, w, h] = content.area(to).xywh();
|
||||
return match self {
|
||||
Self::X(_) => [x0, y, wmax, h],
|
||||
Self::Y(_) => [x, y0, w, hmax],
|
||||
Self::XY(_) => [x0, y0, wmax, hmax],
|
||||
_ => unreachable!()
|
||||
}.into()
|
||||
}
|
||||
return [0.into(), 0.into(), 0.into(), 0.into(),].into()
|
||||
let [x, y, w, h] = self.content().unwrap().area(to).xywh();
|
||||
return match self {
|
||||
Self::X(_) => [x0, y, wmax, h],
|
||||
Self::Y(_) => [x, y0, w, hmax],
|
||||
Self::XY(_) => [x0, y0, wmax, hmax],
|
||||
_ => unreachable!()
|
||||
}.into()
|
||||
});
|
||||
|
||||
/// Defines an enum that transforms its content parametrically
|
||||
|
|
|
|||
|
|
@ -92,14 +92,14 @@ from_jack!(|jack| ArrangerTui {
|
|||
}
|
||||
});
|
||||
impl ArrangerTui {
|
||||
fn render_mode (state: &Self) -> impl Render<Tui> + use<'_> {
|
||||
fn render_mode (state: &Self) -> impl Content<Tui> + use<'_> {
|
||||
match state.mode {
|
||||
ArrangerMode::H => todo!("horizontal arranger"),
|
||||
ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
render!(<Tui>|self: 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]);
|
||||
|
|
@ -115,10 +115,10 @@ render!(<Tui>|self: ArrangerTui|{
|
|||
add(&Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
|
||||
add(&Self::render_mode(self))
|
||||
});
|
||||
Some(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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub enum ArrangerMode {
|
|||
/// Tracks are rows
|
||||
H,
|
||||
}
|
||||
render!(<Tui>|self: ArrangerMode|"");
|
||||
impl<E: Engine> Content<E> for ArrangerMode {}
|
||||
/// Arranger display mode can be cycled
|
||||
impl ArrangerMode {
|
||||
/// Cycle arranger display mode
|
||||
|
|
|
|||
|
|
@ -7,39 +7,42 @@ pub struct ArrangerVClips<'a> {
|
|||
tracks: &'a Vec<ArrangerTrack>,
|
||||
rows: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVClips<'a> = Self {
|
||||
size: &args.0.size,
|
||||
scenes: &args.0.scenes,
|
||||
tracks: &args.0.tracks,
|
||||
rows: ArrangerScene::ppqs(&args.0.scenes, args.1),
|
||||
});
|
||||
|
||||
render!(<Tui>|self: ArrangerVClips<'a>|Fill::xy(
|
||||
col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => {
|
||||
Self::format_scene(self.tracks, scene, pulses)
|
||||
})
|
||||
));
|
||||
|
||||
impl<'a> ArrangerVClips<'a> {
|
||||
pub fn new (state: &'a ArrangerTui, zoom: usize) -> Self {
|
||||
Self {
|
||||
size: &state.size,
|
||||
tracks: &state.tracks,
|
||||
scenes: &state.scenes,
|
||||
rows: ArrangerScene::ppqs(&state.scenes, zoom),
|
||||
}
|
||||
}
|
||||
}
|
||||
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));
|
||||
Some(Fill::xy(col))
|
||||
}
|
||||
}
|
||||
impl<'a> ArrangerVClips<'a> {
|
||||
|
||||
fn format_scene (
|
||||
tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize
|
||||
) -> impl Render<Tui> + use<'a> {
|
||||
) -> impl Content<Tui> + use<'a> {
|
||||
let height = 1.max((pulses / PPQ) as u16);
|
||||
let playing = scene.is_playing(tracks);
|
||||
Fixed::y(height, row!([
|
||||
Fixed::y(height, row!(
|
||||
Tui::bg(scene.color.base.rgb,
|
||||
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!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => {
|
||||
row_iter!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => {
|
||||
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
|
||||
) -> impl Render<Tui> + use<'a> {
|
||||
) -> impl Content<Tui> + use<'a> {
|
||||
Fixed::xy(w, h, Layers::new(move |add|{
|
||||
if let Some(Some(phrase)) = scene.clips.get(index) {
|
||||
let mut bg = TuiTheme::border_bg();
|
||||
|
|
|
|||
|
|
@ -24,11 +24,9 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self {
|
|||
sub_modifier: Modifier::DIM
|
||||
}),
|
||||
});
|
||||
|
||||
render!(<Tui>(self: ArrangerVCursor)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|{
|
||||
let area = render.area();
|
||||
impl Content<Tui> for ArrangerVCursor {
|
||||
fn render (&self, to: &mut TuiOutput) {
|
||||
let area = to.area();
|
||||
let focused = true;
|
||||
let selected = self.selected;
|
||||
let get_track_area = |t: usize| [
|
||||
|
|
@ -67,17 +65,18 @@ render!(<Tui>(self: ArrangerVCursor)
|
|||
};
|
||||
let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0);
|
||||
if let Some([x, y, width, height]) = track_area {
|
||||
render.fill_fg([x, y, 1, height], bg);
|
||||
render.fill_fg([x + width, y, 1, height], bg);
|
||||
to.fill_fg([x, y, 1, height], bg);
|
||||
to.fill_fg([x + width, y, 1, height], bg);
|
||||
}
|
||||
if let Some([_, y, _, height]) = scene_area {
|
||||
render.fill_ul([area.x(), y - 1, area.w(), 1], bg);
|
||||
render.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
|
||||
to.fill_ul([area.x(), y - 1, area.w(), 1], bg);
|
||||
to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
|
||||
}
|
||||
Ok(if focused {
|
||||
render.render_in(if let Some(clip_area) = clip_area { clip_area }
|
||||
to.render_in(if let Some(clip_area) = clip_area { clip_area }
|
||||
else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) }
|
||||
else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) }
|
||||
else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)?
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,44 +15,48 @@ from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A
|
|||
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16,
|
||||
});
|
||||
|
||||
render!(<Tui>|self: ArrangerVHead<'a>|Push::x(self.scenes_w, row!(
|
||||
(_, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => {
|
||||
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
|
||||
let color = track.color();
|
||||
fn row <T: Render<Tui>> (color: ItemPalette, field: &T) -> impl Render<Tui> + use<'_, T> {
|
||||
row!([
|
||||
Tui::fg(color.light.rgb, "▎"),
|
||||
Tui::fg(color.lightest.rgb, field)
|
||||
])
|
||||
impl Content<Tui> for ArrangerVCursor {
|
||||
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)])
|
||||
}
|
||||
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)),
|
||||
]))))
|
||||
|
||||
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)),
|
||||
]))))
|
||||
}
|
||||
)))
|
||||
|
||||
}
|
||||
)));
|
||||
}
|
||||
|
||||
impl ArrangerVHead<'_> {
|
||||
/// name and width of track
|
||||
fn format_name (track: &ArrangerTrack, _w: usize) -> impl Render<Tui> {
|
||||
fn format_name (track: &ArrangerTrack, _w: usize) -> impl Content<Tui> {
|
||||
let name = track.name().read().unwrap().clone();
|
||||
Tui::bold(true, Tui::fg(track.color.lightest.rgb, name))
|
||||
}
|
||||
/// input port
|
||||
fn format_input (track: &ArrangerTrack) -> Usually<impl Render<Tui>> {
|
||||
fn format_input (track: &ArrangerTrack) -> Usually<impl Content<Tui>> {
|
||||
Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name())
|
||||
.transpose()?.unwrap_or("?".into())))
|
||||
}
|
||||
/// output port
|
||||
fn format_output (track: &ArrangerTrack) -> Usually<impl Render<Tui>> {
|
||||
fn format_output (track: &ArrangerTrack) -> Usually<impl Content<Tui>> {
|
||||
Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name())
|
||||
.transpose()?.unwrap_or("?".into())))
|
||||
}
|
||||
/// beats elapsed
|
||||
fn format_elapsed (track: &ArrangerTrack, timebase: &Arc<Timebase>) -> impl Render<Tui> {
|
||||
fn format_elapsed (track: &ArrangerTrack, timebase: &Arc<Timebase>) -> impl Content<Tui> {
|
||||
let mut result = String::new();
|
||||
if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
||||
let length = phrase.read().unwrap().length;
|
||||
|
|
@ -66,7 +70,7 @@ impl ArrangerVHead<'_> {
|
|||
}
|
||||
/// beats until switchover
|
||||
fn format_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
|
||||
-> Option<impl Render<Tui>>
|
||||
-> Option<impl Content<Tui>>
|
||||
{
|
||||
let timebase = ¤t.timebase;
|
||||
let mut result = String::new();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from!(<'a>|args: &'a ArrangerTui|ArrangerVIns<'a> = Self {
|
|||
tracks: &args.tracks,
|
||||
});
|
||||
|
||||
render!(<Tui>|self: ArrangerVIns<'a>|"");
|
||||
render!(Tui: (self: ArrangerVIns<'a>) => "");
|
||||
|
||||
pub struct ArrangerVOuts<'a> {
|
||||
size: &'a Measure<Tui>,
|
||||
|
|
@ -23,4 +23,4 @@ from!(<'a>|args: &'a ArrangerTui|ArrangerVOuts<'a> = Self {
|
|||
tracks: &args.tracks,
|
||||
});
|
||||
|
||||
render!(<Tui>|self: ArrangerVOuts<'a>|"");
|
||||
render!(Tui: (self: ArrangerVOuts<'a>) => "");
|
||||
|
|
|
|||
|
|
@ -11,17 +11,15 @@ from!(|state:&ArrangerTui|ArrangerVColSep = Self {
|
|||
cols: ArrangerTrack::widths(&state.tracks),
|
||||
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16,
|
||||
});
|
||||
render!(<Tui>(self: ArrangerVColSep)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|{
|
||||
let style = Some(Style::default().fg(self.fg));
|
||||
Ok(for x in self.cols.iter().map(|col|col.1) {
|
||||
let x = self.scenes_w + render.area().x() + x as u16;
|
||||
for y in render.area().y()..render.area().y2() {
|
||||
render.blit(&"▎", x, y, style);
|
||||
}
|
||||
})
|
||||
});
|
||||
render!(Tui: |self: ArrangerVColSep, to| {
|
||||
let style = Some(Style::default().fg(self.fg));
|
||||
Ok(for x in self.cols.iter().map(|col|col.1) {
|
||||
let x = self.scenes_w + to.area().x() + x as u16;
|
||||
for y in to.area().y()..to.area().y2() {
|
||||
to.blit(&"▎", x, y, style);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
pub struct ArrangerVRowSep {
|
||||
fg: Color,
|
||||
|
|
@ -31,16 +29,14 @@ from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self {
|
|||
fg: TuiTheme::separator_fg(false),
|
||||
rows: ArrangerScene::ppqs(&args.0.scenes, args.1),
|
||||
});
|
||||
render!(<Tui>(self: ArrangerVRowSep)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|Ok(for y in self.rows.iter().map(|row|row.1) {
|
||||
let y = render.area().y() + (y / PPQ) as u16 + 1;
|
||||
if y >= render.buffer.area.height { break }
|
||||
for x in render.area().x()..render.area().x2().saturating_sub(2) {
|
||||
if x < render.buffer.area.x && y < render.buffer.area.y {
|
||||
let cell = render.buffer.get_mut(x, y);
|
||||
cell.modifier = Modifier::UNDERLINED;
|
||||
cell.underline_color = self.fg;
|
||||
}
|
||||
render!(Tui: |self: ArrangerVRowSep, to|for y in self.rows.iter().map(|row|row.1) {
|
||||
let y = to.area().y() + (y / PPQ) as u16 + 1;
|
||||
if y >= to.buffer.area.height { break }
|
||||
for x in to.area().x()..to.area().x2().saturating_sub(2) {
|
||||
if x < to.buffer.area.x && y < to.buffer.area.y {
|
||||
let cell = to.buffer.get_mut(x, y);
|
||||
cell.modifier = Modifier::UNDERLINED;
|
||||
cell.underline_color = self.fg;
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,39 +1,34 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct Bordered<S: BorderStyle, W: Render<Tui>>(pub S, pub W);
|
||||
pub struct Bordered<S: BorderStyle, W: Content<Tui>>(pub S, pub W);
|
||||
|
||||
render!(<Tui>|self: Bordered<S: BorderStyle, W: Render<Tui>>|{
|
||||
render!(Tui: (self: Bordered<S: BorderStyle, W: Content<Tui>>) => {
|
||||
Fill::xy(lay!([Border(self.0), Padding::xy(1, 1, &self.1)]))
|
||||
});
|
||||
|
||||
pub struct Border<S: BorderStyle>(pub S);
|
||||
|
||||
impl<S: BorderStyle> Render<Tui> for Border<S> {
|
||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
Ok(Some([0, 0]))
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
let area = to.area();
|
||||
if area.w() > 0 && area.y() > 0 {
|
||||
to.blit(&self.0.nw(), area.x(), area.y(), self.0.style());
|
||||
to.blit(&self.0.ne(), area.x() + area.w() - 1, area.y(), self.0.style());
|
||||
to.blit(&self.0.sw(), area.x(), area.y() + area.h() - 1, self.0.style());
|
||||
to.blit(&self.0.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.0.style());
|
||||
for x in area.x()+1..area.x()+area.w()-1 {
|
||||
to.blit(&self.0.n(), x, area.y(), self.0.style());
|
||||
to.blit(&self.0.s(), x, area.y() + area.h() - 1, self.0.style());
|
||||
}
|
||||
for y in area.y()+1..area.y()+area.h()-1 {
|
||||
to.blit(&self.0.w(), area.x(), y, self.0.style());
|
||||
to.blit(&self.0.e(), area.x() + area.w() - 1, y, self.0.style());
|
||||
}
|
||||
render!(Tui: |self: Border<S: BorderStyle>, to| {
|
||||
let area = to.area();
|
||||
if area.w() > 0 && area.y() > 0 {
|
||||
to.blit(&self.0.nw(), area.x(), area.y(), self.0.style());
|
||||
to.blit(&self.0.ne(), area.x() + area.w() - 1, area.y(), self.0.style());
|
||||
to.blit(&self.0.sw(), area.x(), area.y() + area.h() - 1, self.0.style());
|
||||
to.blit(&self.0.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.0.style());
|
||||
for x in area.x()+1..area.x()+area.w()-1 {
|
||||
to.blit(&self.0.n(), x, area.y(), self.0.style());
|
||||
to.blit(&self.0.s(), x, area.y() + area.h() - 1, self.0.style());
|
||||
}
|
||||
for y in area.y()+1..area.y()+area.h()-1 {
|
||||
to.blit(&self.0.w(), area.x(), y, self.0.style());
|
||||
to.blit(&self.0.e(), area.x() + area.w() - 1, y, self.0.style());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
pub trait BorderStyle: Send + Sync + Copy {
|
||||
fn wrap <W: Render<Tui>> (self, w: W) -> Bordered<Self, W> {
|
||||
fn wrap <W: Content<Tui>> (self, w: W) -> Bordered<Self, W> {
|
||||
Bordered(self, w)
|
||||
}
|
||||
const NW: &'static str = "";
|
||||
|
|
@ -134,7 +129,7 @@ macro_rules! border {
|
|||
}
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct $T(pub Style);
|
||||
impl Render<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) -> Usually<()> { self.draw(to) }
|
||||
}
|
||||
|
|
|
|||
32
src/file.rs
32
src/file.rs
|
|
@ -20,25 +20,23 @@ pub enum FileBrowserCommand {
|
|||
Chdir(PathBuf),
|
||||
Filter(String),
|
||||
}
|
||||
render!(<Tui>|self: FileBrowser|{
|
||||
Stack::down(|add|{
|
||||
let mut i = 0;
|
||||
for (_, name) in self.dirs.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
render!(Tui: (self: FileBrowser) => Stack::down(|add|{
|
||||
let mut i = 0;
|
||||
for (_, name) in self.dirs.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
for (_, name) in self.files.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
i += 1;
|
||||
}
|
||||
for (_, name) in self.files.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
add(&format!("{}/{i}", self.index))?;
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
i += 1;
|
||||
}
|
||||
add(&format!("{}/{i}", self.index))?;
|
||||
Ok(())
|
||||
}));
|
||||
impl FileBrowser {
|
||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ impl<T: FocusGrid + HasEnter> FocusOrder for T {
|
|||
}
|
||||
|
||||
pub trait FocusWrap<T> {
|
||||
fn wrap <W: Render<Tui>> (self, focus: T, content: &'_ W) -> impl Render<Tui> + '_;
|
||||
fn wrap <W: Content<Tui>> (self, focus: T, content: &'_ W) -> impl Content<Tui> + '_;
|
||||
}
|
||||
|
||||
pub fn to_focus_command <T: Send + Sync> (input: &TuiInput) -> Option<FocusCommand<T>> {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ audio!(|self: Groovebox, client, scope|{
|
|||
self.perf.update(t0, scope);
|
||||
Control::Continue
|
||||
});
|
||||
render!(<Tui>|self:Groovebox|{
|
||||
render!(Tui: (self: Groovebox) => {
|
||||
let w = self.size.w();
|
||||
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
let pool_w = if self.pool.visible { phrase_w } else { 0 };
|
||||
|
|
@ -120,19 +120,21 @@ render!(<Tui>|self:Groovebox|{
|
|||
let color = self.player.play_phrase().as_ref()
|
||||
.and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color))
|
||||
.clone();
|
||||
let transport = Fixed::y(3, row!([
|
||||
let transport = Fixed::y(3, row!(
|
||||
PlayPause(self.clock().is_rolling()),
|
||||
TransportView::new(self, color, true),
|
||||
]));
|
||||
let selector = Push::x(sampler_w, Fixed::y(1, row!(![
|
||||
));
|
||||
let selector = Push::x(sampler_w, Fixed::y(1, row!(
|
||||
PhraseSelector::play_phrase(&self.player),
|
||||
PhraseSelector::next_phrase(&self.player),
|
||||
])));
|
||||
let pool = move|x|Split::w(false, pool_w, Pull::y(1, Fill::y(Align::e(PoolView(&self.pool)))), x);
|
||||
let sampler = move|x|Split::e(false, sampler_w, Fill::xy(col!([
|
||||
)));
|
||||
let pool = move|x|Split::w(false, pool_w,
|
||||
Pull::y(1, Fill::y(Align::e(PoolView(&self.pool)))),
|
||||
x);
|
||||
let sampler = move|x|Split::e(false, sampler_w, Fill::xy(col!(
|
||||
Meters(self.sampler.input_meter.as_ref()),
|
||||
GrooveboxSamples(self)
|
||||
])), x);
|
||||
)), x);
|
||||
let status = EditStatus(&self.sampler, &self.editor, note_pt, pool(sampler(&self.editor)));
|
||||
Fill::xy(lay!([
|
||||
&self.size,
|
||||
|
|
@ -145,9 +147,9 @@ render!(<Tui>|self:Groovebox|{
|
|||
]))
|
||||
});
|
||||
|
||||
struct EditStatus<'a, T: Render<Tui>>(&'a Sampler, &'a MidiEditor, usize, T);
|
||||
impl<'a, T: Render<Tui>> Content<Tui> for EditStatus<'a, T> {
|
||||
fn content (&self) -> Option<impl Render<Tui>> {
|
||||
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] {
|
||||
|
|
@ -169,7 +171,7 @@ impl<'a, T: Render<Tui>> Content<Tui> for EditStatus<'a, T> {
|
|||
]), &self.3))
|
||||
}
|
||||
}
|
||||
impl<'a, T: Render<Tui>> Render<Tui> for EditStatus<'a, T> {
|
||||
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)
|
||||
}
|
||||
|
|
@ -179,11 +181,11 @@ impl<'a, T: Render<Tui>> Render<Tui> for EditStatus<'a, T> {
|
|||
}
|
||||
|
||||
struct GrooveboxSamples<'a>(&'a Groovebox);
|
||||
render!(<Tui>|self: GrooveboxSamples<'a>|{
|
||||
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!(note in (note_lo..=note_hi).rev() => {
|
||||
Fill::xy(col_iter!((note_lo..=note_hi).rev() => |note| {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ pub(crate) use ::tek_layout::{
|
|||
tek_engine::{
|
||||
Usually, Perhaps,
|
||||
Engine, Size, Area,
|
||||
Output, Content, Render, render,
|
||||
Input, Handle, handle, kexp, key_pat, key_event_pat, key_event_expr,
|
||||
Output, Content, render,
|
||||
Input, Handle, handle,
|
||||
kexp, key_pat, key_event_pat, key_event_expr,
|
||||
tui::{
|
||||
Tui, TuiInput, TuiOutput,
|
||||
crossterm::{
|
||||
|
|
|
|||
|
|
@ -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]),
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ impl Command<MidiEditor> for PhraseCommand {
|
|||
|
||||
/// Contains state for viewing and editing a phrase
|
||||
pub struct MidiEditor {
|
||||
/// Renders the phrase
|
||||
/// Contents the phrase
|
||||
pub mode: Box<dyn PhraseViewMode>,
|
||||
pub size: Measure<Tui>
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ impl Default for MidiEditor {
|
|||
|
||||
has_size!(<Tui>|self: MidiEditor|&self.size);
|
||||
|
||||
render!(<Tui>|self: MidiEditor|{
|
||||
render!(Tui: (self: MidiEditor) => {
|
||||
self.autoscroll();
|
||||
self.autozoom();
|
||||
&self.mode
|
||||
|
|
@ -119,7 +119,7 @@ render!(<Tui>|self: MidiEditor|{
|
|||
|
||||
//render!(<Tui>|self: MidiEditor|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
|
||||
|
||||
pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
|
||||
pub trait PhraseViewMode: Content<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
|
||||
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize);
|
||||
fn redraw (&mut self);
|
||||
fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>>;
|
||||
|
|
@ -131,18 +131,10 @@ pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + D
|
|||
}
|
||||
|
||||
impl Content<Tui> for Box<dyn PhraseViewMode> {
|
||||
fn content (&self) -> Option<impl Render<Tui>> {
|
||||
fn content (&self) -> Option<impl Content<Tui>> {
|
||||
Some(&(*self))
|
||||
}
|
||||
}
|
||||
impl Render<Tui> for Box<dyn PhraseViewMode> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl MidiView<Tui> for MidiEditor {}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use self::piano_h_cursor::*;
|
|||
|
||||
mod piano_h_keys;
|
||||
pub(crate) use self::piano_h_keys::*;
|
||||
pub use self::piano_h_keys::render_keys_v;
|
||||
|
||||
mod piano_h_notes; use self::piano_h_notes::*;
|
||||
|
||||
|
|
@ -54,38 +53,30 @@ impl PianoHorizontal {
|
|||
}
|
||||
}
|
||||
|
||||
render!(<Tui>|self: PianoHorizontal|{
|
||||
|
||||
render!(Tui: (self: PianoHorizontal) => {
|
||||
let (color, name, length, looped) = if let Some(phrase) = self.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.lighter.rgb, color.dark.rgb, &y),
|
||||
]);
|
||||
|
||||
);
|
||||
let keys_width = 5;
|
||||
let keys = move||PianoHorizontalKeys(self);
|
||||
|
||||
let timeline = move||PianoHorizontalTimeline(self);
|
||||
|
||||
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)]);
|
||||
|
||||
with_border(lay!([
|
||||
Push::x(0, row!(![
|
||||
with_border(lay!(
|
||||
Push::x(0, row!(
|
||||
//" ",
|
||||
field("Edit:", name.to_string()), " ",
|
||||
field("Length:", length.to_string()), " ",
|
||||
field("Loop:", looped.to_string())
|
||||
])),
|
||||
)),
|
||||
Padding::xy(0, 1, Fill::xy(Bsp::s(
|
||||
Fixed::y(1, Bsp::e(
|
||||
Fixed::x(self.keys_width, ""),
|
||||
|
|
@ -95,14 +86,14 @@ render!(<Tui>|self: PianoHorizontal|{
|
|||
Fixed::x(self.keys_width, keys()),
|
||||
Fill::xy(lay!([
|
||||
&self.size,
|
||||
Fill::xy(lay!([
|
||||
Fill::xy(lay!(
|
||||
Fill::xy(notes()),
|
||||
Fill::xy(cursor()),
|
||||
]))
|
||||
))
|
||||
])),
|
||||
),
|
||||
)))
|
||||
]))
|
||||
))
|
||||
});
|
||||
|
||||
impl PianoHorizontal {
|
||||
|
|
|
|||
|
|
@ -2,34 +2,32 @@ use crate::*;
|
|||
use super::note_y_iter;
|
||||
|
||||
pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal);
|
||||
render!(<Tui>(self: PianoHorizontalCursor<'a>)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|Ok({
|
||||
let style = Some(Style::default().fg(self.0.color.lightest.rgb));
|
||||
let note_hi = self.0.note_hi();
|
||||
let note_len = self.0.note_len();
|
||||
let note_lo = self.0.note_lo().get();
|
||||
let note_point = self.0.note_point();
|
||||
let time_point = self.0.time_point();
|
||||
let time_start = self.0.time_start().get();
|
||||
let time_zoom = self.0.time_zoom().get();
|
||||
let [x0, y0, w, _] = render.area().xywh();
|
||||
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||
if note == note_point {
|
||||
for x in 0..w {
|
||||
let screen_x = x0 + x;
|
||||
let time_1 = time_start + x as usize * time_zoom;
|
||||
let time_2 = time_1 + time_zoom;
|
||||
if time_1 <= time_point && time_point < time_2 {
|
||||
render.blit(&"█", screen_x, screen_y, style);
|
||||
let tail = note_len as u16 / time_zoom as u16;
|
||||
for x_tail in (screen_x + 1)..(screen_x + tail) {
|
||||
render.blit(&"▂", x_tail, screen_y, style);
|
||||
}
|
||||
break
|
||||
render!(Tui: |self: PianoHorizontalCursor<'a>, render|{
|
||||
let style = Some(Style::default().fg(self.0.color.lightest.rgb));
|
||||
let note_hi = self.0.note_hi();
|
||||
let note_len = self.0.note_len();
|
||||
let note_lo = self.0.note_lo().get();
|
||||
let note_point = self.0.note_point();
|
||||
let time_point = self.0.time_point();
|
||||
let time_start = self.0.time_start().get();
|
||||
let time_zoom = self.0.time_zoom().get();
|
||||
let [x0, y0, w, _] = render.area().xywh();
|
||||
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||
if note == note_point {
|
||||
for x in 0..w {
|
||||
let screen_x = x0 + x;
|
||||
let time_1 = time_start + x as usize * time_zoom;
|
||||
let time_2 = time_1 + time_zoom;
|
||||
if time_1 <= time_point && time_point < time_2 {
|
||||
render.blit(&"█", screen_x, screen_y, style);
|
||||
let tail = note_len as u16 / time_zoom as u16;
|
||||
for x_tail in (screen_x + 1)..(screen_x + tail) {
|
||||
render.blit(&"▂", x_tail, screen_y, style);
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,9 +3,28 @@ use super::note_y_iter;
|
|||
|
||||
pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal);
|
||||
|
||||
render!(<Tui>(self: PianoHorizontalKeys<'a>)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|Ok(render_keys_v(render, self)));
|
||||
render!(Tui: |self: PianoHorizontalKeys<'a>, to|{
|
||||
let state = self.0;
|
||||
let color = state.color();
|
||||
let note_lo = state.note_lo().get();
|
||||
let note_hi = state.note_hi();
|
||||
let note_point = state.note_point();
|
||||
let [x, y0, w, h] = to.area().xywh();
|
||||
let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0)));
|
||||
let off_style = Some(Style::default().fg(TuiTheme::g(160)));
|
||||
let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold());
|
||||
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||
to.blit(&to_key(note), x, screen_y, key_style);
|
||||
if note > 127 {
|
||||
continue
|
||||
}
|
||||
if note == note_point {
|
||||
to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style)
|
||||
} else {
|
||||
to.blit(&to_note_name(note), x, screen_y, off_style)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base);
|
||||
|
||||
|
|
@ -21,28 +40,6 @@ impl<'a> NotePoint for PianoHorizontalKeys<'a> {
|
|||
fn set_note_point (&self, x: usize) { self.0.set_note_point(x) }
|
||||
}
|
||||
|
||||
pub fn render_keys_v <T: HasColor + NoteRange + NotePoint> (to: &mut TuiOutput, state: &T) {
|
||||
let color = state.color();
|
||||
let note_lo = state.note_lo().get();
|
||||
let note_hi = state.note_hi();
|
||||
let note_point = state.note_point();
|
||||
let [x, y0, w, h] = to.area().xywh();
|
||||
let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0)));
|
||||
let off_style = Some(Style::default().fg(TuiTheme::g(160)));
|
||||
let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold());
|
||||
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||
to.blit(&to_key(note), x, screen_y, key_style);
|
||||
if note > 127 {
|
||||
continue
|
||||
}
|
||||
if note == note_point {
|
||||
to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style)
|
||||
} else {
|
||||
to.blit(&to_note_name(note), x, screen_y, off_style)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn to_key (note: usize) -> &'static str {
|
||||
match note % 12 {
|
||||
11 => "████▌",
|
||||
|
|
|
|||
|
|
@ -3,32 +3,30 @@ use super::note_y_iter;
|
|||
|
||||
pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal);
|
||||
|
||||
render!(<Tui>(self: PianoHorizontalNotes<'a>)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|Ok({
|
||||
let time_start = self.0.time_start().get();
|
||||
let note_lo = self.0.note_lo().get();
|
||||
let note_hi = self.0.note_hi();
|
||||
let note_point = self.0.note_point();
|
||||
let source = &self.0.buffer;
|
||||
let [x0, y0, w, h] = render.area().xywh();
|
||||
if h as usize != self.0.note_axis().get() {
|
||||
panic!("area height mismatch");
|
||||
}
|
||||
for (area_x, screen_x) in (x0..x0+w).enumerate() {
|
||||
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||
let source_x = time_start + area_x;
|
||||
let source_y = note_hi - area_y;
|
||||
// TODO: enable loop rollover:
|
||||
//let source_x = (time_start + area_x) % source.width.max(1);
|
||||
//let source_y = (note_hi - area_y) % source.height.max(1);
|
||||
let is_in_x = source_x < source.width;
|
||||
let is_in_y = source_y < source.height;
|
||||
if is_in_x && is_in_y {
|
||||
if let Some(source_cell) = source.get(source_x, source_y) {
|
||||
*render.buffer.get_mut(screen_x, screen_y) = source_cell.clone();
|
||||
}
|
||||
render!(Tui: |self: PianoHorizontalNotes<'a>, render|{
|
||||
let time_start = self.0.time_start().get();
|
||||
let note_lo = self.0.note_lo().get();
|
||||
let note_hi = self.0.note_hi();
|
||||
let note_point = self.0.note_point();
|
||||
let source = &self.0.buffer;
|
||||
let [x0, y0, w, h] = render.area().xywh();
|
||||
if h as usize != self.0.note_axis().get() {
|
||||
panic!("area height mismatch");
|
||||
}
|
||||
for (area_x, screen_x) in (x0..x0+w).enumerate() {
|
||||
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||
let source_x = time_start + area_x;
|
||||
let source_y = note_hi - area_y;
|
||||
// TODO: enable loop rollover:
|
||||
//let source_x = (time_start + area_x) % source.width.max(1);
|
||||
//let source_y = (note_hi - area_y) % source.height.max(1);
|
||||
let is_in_x = source_x < source.width;
|
||||
let is_in_y = source_y < source.height;
|
||||
if is_in_x && is_in_y {
|
||||
if let Some(source_cell) = source.get(source_x, source_y) {
|
||||
*render.buffer.get_mut(screen_x, screen_y) = source_cell.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal);
|
||||
render!(<Tui>(self: PianoHorizontalTimeline<'a>)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|{
|
||||
let [x, y, w, h] = render.area();
|
||||
let style = Some(Style::default().dim());
|
||||
let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
|
||||
let t = area_x as usize * self.0.time_zoom().get();
|
||||
if t < length {
|
||||
render.blit(&"|", screen_x, y, style);
|
||||
}
|
||||
render!(Tui: |self: PianoHorizontalTimeline<'a>, render|{
|
||||
let [x, y, w, h] = render.area();
|
||||
let style = Some(Style::default().dim());
|
||||
let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
|
||||
let t = area_x as usize * self.0.time_zoom().get();
|
||||
if t < length {
|
||||
render.blit(&"|", screen_x, y, style);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//Tui::fg_bg(
|
||||
//self.0.color.lightest.rgb,
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ impl PoolModel {
|
|||
}
|
||||
pub struct PoolView<'a>(pub(crate) &'a PoolModel);
|
||||
// TODO: Display phrases always in order of appearance
|
||||
render!(<Tui>|self: PoolView<'a>|{
|
||||
render!(Tui: (self: PoolView<'a>) => {
|
||||
let PoolModel { phrases, mode, .. } = self.0;
|
||||
let bg = TuiTheme::g(32);
|
||||
let title_color = TuiTheme::ti1();
|
||||
|
|
|
|||
|
|
@ -69,20 +69,20 @@ impl PhraseLengthFocus {
|
|||
}
|
||||
}
|
||||
|
||||
render!(<Tui>|self: PhraseLength|{
|
||||
render!(Tui: (self: PhraseLength) => {
|
||||
let bars = ||self.bars_string();
|
||||
let beats = ||self.beats_string();
|
||||
let ticks = ||self.ticks_string();
|
||||
row!(move|add|match self.focus {
|
||||
match self.focus {
|
||||
None =>
|
||||
add(&row!([" ", bars(), ".", beats(), ".", ticks()])),
|
||||
row!(" ", bars(), ".", beats(), ".", ticks()),
|
||||
Some(PhraseLengthFocus::Bar) =>
|
||||
add(&row!(["[", bars(), "]", beats(), ".", ticks()])),
|
||||
row!("[", bars(), "]", beats(), ".", ticks()),
|
||||
Some(PhraseLengthFocus::Beat) =>
|
||||
add(&row!([" ", bars(), "[", beats(), "]", ticks()])),
|
||||
row!(" ", bars(), "[", beats(), "]", ticks()),
|
||||
Some(PhraseLengthFocus::Tick) =>
|
||||
add(&row!([" ", bars(), ".", beats(), "[", ticks()])),
|
||||
})
|
||||
row!(" ", bars(), ".", beats(), "[", ticks()),
|
||||
}
|
||||
});
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ 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!([
|
||||
format!("{:8}", &self.name[0..8.min(self.name.len())]),
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ fn draw_sample (
|
|||
Ok(label1.len() + label2.len() + 4)
|
||||
}
|
||||
|
||||
impl Render<Tui> for AddSampleModal {
|
||||
impl Content<Tui> for AddSampleModal {
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
todo!()
|
||||
//Align::Center(()).layout(to)
|
||||
|
|
|
|||
|
|
@ -5,55 +5,53 @@ use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points, Line}}};
|
|||
const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
|
||||
|
||||
pub struct SampleViewer(pub Option<Arc<RwLock<Sample>>>);
|
||||
render!(<Tui>(self: SampleViewer)
|
||||
|layout|Ok(Some([0, 0])),
|
||||
|render|{
|
||||
render!(Tui: |self: SampleViewer, to|{
|
||||
|
||||
let [x, y, width, height] = render.area();
|
||||
let [x, y, width, height] = to.area();
|
||||
|
||||
let area = Rect { x, y, width, height };
|
||||
let min_db = -40.0;
|
||||
let area = Rect { x, y, width, height };
|
||||
let min_db = -40.0;
|
||||
|
||||
let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec<Line>) =
|
||||
if let Some(sample) = &self.0 {
|
||||
let sample = sample.read().unwrap();
|
||||
let start = sample.start as f64;
|
||||
let end = sample.end as f64;
|
||||
let length = end - start;
|
||||
let step = length / width as f64;
|
||||
let mut t = start;
|
||||
let mut lines = vec![];
|
||||
while t < end {
|
||||
let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)];
|
||||
let total: f32 = chunk.iter().map(|x|x.abs()).sum();
|
||||
let count = chunk.len() as f32;
|
||||
let meter = 10. * (total / count).log10();
|
||||
let x = t as f64;
|
||||
let y = meter as f64;
|
||||
lines.push(Line::new(x, min_db, x, y, Color::Green));
|
||||
t += step / 2.;
|
||||
}
|
||||
(
|
||||
[sample.start as f64, sample.end as f64],
|
||||
[min_db, 0.],
|
||||
lines
|
||||
)
|
||||
} else {
|
||||
(
|
||||
[0.0, width as f64],
|
||||
[0.0, height as f64],
|
||||
vec![
|
||||
Line::new(0.0, 0.0, width as f64, height as f64, Color::Red),
|
||||
Line::new(width as f64, 0.0, 0.0, height as f64, Color::Red),
|
||||
]
|
||||
)
|
||||
};
|
||||
let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec<Line>) =
|
||||
if let Some(sample) = &self.0 {
|
||||
let sample = sample.read().unwrap();
|
||||
let start = sample.start as f64;
|
||||
let end = sample.end as f64;
|
||||
let length = end - start;
|
||||
let step = length / width as f64;
|
||||
let mut t = start;
|
||||
let mut lines = vec![];
|
||||
while t < end {
|
||||
let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)];
|
||||
let total: f32 = chunk.iter().map(|x|x.abs()).sum();
|
||||
let count = chunk.len() as f32;
|
||||
let meter = 10. * (total / count).log10();
|
||||
let x = t as f64;
|
||||
let y = meter as f64;
|
||||
lines.push(Line::new(x, min_db, x, y, Color::Green));
|
||||
t += step / 2.;
|
||||
}
|
||||
(
|
||||
[sample.start as f64, sample.end as f64],
|
||||
[min_db, 0.],
|
||||
lines
|
||||
)
|
||||
} else {
|
||||
(
|
||||
[0.0, width as f64],
|
||||
[0.0, height as f64],
|
||||
vec![
|
||||
Line::new(0.0, 0.0, width as f64, height as f64, Color::Red),
|
||||
Line::new(width as f64, 0.0, 0.0, height as f64, Color::Red),
|
||||
]
|
||||
)
|
||||
};
|
||||
|
||||
Canvas::default()
|
||||
.x_bounds(x_bounds)
|
||||
.y_bounds(y_bounds)
|
||||
.paint(|ctx| { for line in lines.iter() { ctx.draw(line) } })
|
||||
.render(area, &mut render.buffer);
|
||||
Canvas::default()
|
||||
.x_bounds(x_bounds)
|
||||
.y_bounds(y_bounds)
|
||||
.paint(|ctx| { for line in lines.iter() { ctx.draw(line) } })
|
||||
.render(area, &mut to.buffer);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
Ok(())
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ impl SamplerTui {
|
|||
//}
|
||||
//});
|
||||
|
||||
render!(<Tui>|self: SamplerTui|{
|
||||
render!(Tui: (self: SamplerTui) => {
|
||||
let keys_width = 5;
|
||||
let keys = move||"";//SamplerKeys(self);
|
||||
let fg = self.color.base.rgb;
|
||||
|
|
@ -72,21 +72,19 @@ struct SamplesTui {
|
|||
note_pt: usize,
|
||||
height: usize,
|
||||
}
|
||||
render!(<Tui>(self: SamplesTui)
|
||||
|area|Ok(Some([15, 1])),
|
||||
|render|Ok({
|
||||
let x = render.area.x();
|
||||
let bg_base = self.color.darkest.rgb;
|
||||
let bg_selected = self.color.darker.rgb;
|
||||
let style_empty = Style::default().fg(self.color.base.rgb);
|
||||
let style_full = Style::default().fg(self.color.lighter.rgb);
|
||||
for y in 0..self.height {
|
||||
let note = self.note_hi - y as usize;
|
||||
let bg = if note == self.note_pt { bg_selected } else { bg_base };
|
||||
let style = Some(style_empty.bg(bg));
|
||||
render.blit(&" (no sample) ", x, render.area.y() + y as u16, style)
|
||||
}
|
||||
}));
|
||||
render!(Tui: |self: SamplesTui, render|{
|
||||
let x = render.area.x();
|
||||
let bg_base = self.color.darkest.rgb;
|
||||
let bg_selected = self.color.darker.rgb;
|
||||
let style_empty = Style::default().fg(self.color.base.rgb);
|
||||
let style_full = Style::default().fg(self.color.lighter.rgb);
|
||||
for y in 0..self.height {
|
||||
let note = self.note_hi - y as usize;
|
||||
let bg = if note == self.note_pt { bg_selected } else { bg_base };
|
||||
let style = Some(style_empty.bg(bg));
|
||||
render.blit(&" (no sample) ", x, render.area.y() + y as u16, style)
|
||||
}
|
||||
});
|
||||
|
||||
impl NoteRange for SamplerTui {
|
||||
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ from_jack!(|jack|SequencerTui {
|
|||
clock,
|
||||
}
|
||||
});
|
||||
render!(<Tui>|self: SequencerTui|{
|
||||
render!(Tui: (self: SequencerTui) => {
|
||||
let w = self.size.w();
|
||||
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
let pool_w = if self.phrases.visible { phrase_w } else { 0 };
|
||||
|
|
|
|||
184
src/status.rs
184
src/status.rs
|
|
@ -1,4 +1,180 @@
|
|||
mod status_arranger; pub(crate) use self::status_arranger::*;
|
||||
mod status_edit; pub(crate) use self::status_edit::*;
|
||||
mod status_sequencer; pub(crate) use self::status_sequencer::*;
|
||||
mod status_groovebox; pub(crate) use self::status_groovebox::*;
|
||||
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!([
|
||||
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!([
|
||||
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(),
|
||||
if self.0.time_lock().get() { "[lock]" } else { " " })),
|
||||
" ",
|
||||
field(" Note", format!("{} ({}) {} | {}-{} ({})",
|
||||
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
|
||||
#[derive(Clone)]
|
||||
pub struct SequencerStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state:&SequencerTui|SequencerStatus = {
|
||||
let samples = state.clock.chunk.load(Relaxed);
|
||||
let rate = state.clock.timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock.is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
}
|
||||
});
|
||||
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!([
|
||||
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!([
|
||||
single("SPACE", "play/pause"),
|
||||
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||
double(("a", "append"), ("s", "set note"),),
|
||||
double((",.", "length"), ("<>", "triplet"), ),
|
||||
double(("[]", "phrase"), ("{}", "order"), ),
|
||||
double(("q", "enqueue"), ("e", "edit"), ),
|
||||
double(("c", "color"), ("", ""),),
|
||||
]))
|
||||
}
|
||||
fn stats (&self) -> impl Content<Tui> + use<'_> {
|
||||
row!([&self.cpu, &self.size])
|
||||
}
|
||||
}
|
||||
|
||||
/// Status bar for sequencer app
|
||||
#[derive(Clone)]
|
||||
pub struct GrooveboxStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state: &Groovebox|GrooveboxStatus = {
|
||||
let samples = state.clock().chunk.load(Relaxed);
|
||||
let rate = state.clock().timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock().is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
}
|
||||
});
|
||||
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!(
|
||||
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!(
|
||||
single("SPACE", "play/pause"),
|
||||
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||
double(("a", "append"), ("s", "set note"),),
|
||||
double((",.", "length"), ("<>", "triplet"), ),
|
||||
double(("[]", "phrase"), ("{}", "order"), ),
|
||||
double(("q", "enqueue"), ("e", "edit"), ),
|
||||
double(("c", "color"), ("", ""),),
|
||||
))
|
||||
}
|
||||
fn stats (&self) -> impl Content<Tui> + use<'_> {
|
||||
row!(&self.cpu, &self.size)
|
||||
}
|
||||
}
|
||||
|
||||
/// Status bar for arranger app
|
||||
#[derive(Clone)]
|
||||
pub struct ArrangerStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state:&ArrangerTui|ArrangerStatus = {
|
||||
let samples = state.clock.chunk.load(Relaxed);
|
||||
let rate = state.clock.timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock.is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
}
|
||||
});
|
||||
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!([
|
||||
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!([
|
||||
single("SPACE", "play/pause"),
|
||||
single(" Ctrl", " scroll"),
|
||||
single(" ▲▼▶◀", " cell"),
|
||||
double(("p", "put"), ("g", "get")),
|
||||
double(("q", "enqueue"), ("e", "edit")),
|
||||
single(" wsad", " note"),
|
||||
double(("a", "append"), ("s", "set"),),
|
||||
double((",.", "length"), ("<>", "triplet"),),
|
||||
double(("[]", "phrase"), ("{}", "order"),),
|
||||
]))
|
||||
}
|
||||
fn stats (&self) -> impl Content<Tui> + use<'_> {
|
||||
row!([&self.cpu, &self.size])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Status bar for arranger app
|
||||
#[derive(Clone)]
|
||||
pub struct ArrangerStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state:&ArrangerTui|ArrangerStatus = {
|
||||
let samples = state.clock.chunk.load(Relaxed);
|
||||
let rate = state.clock.timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock.is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
}
|
||||
});
|
||||
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 Render<Tui> {
|
||||
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!([
|
||||
single("SPACE", "play/pause"),
|
||||
single(" Ctrl", " scroll"),
|
||||
single(" ▲▼▶◀", " cell"),
|
||||
double(("p", "put"), ("g", "get")),
|
||||
double(("q", "enqueue"), ("e", "edit")),
|
||||
single(" wsad", " note"),
|
||||
double(("a", "append"), ("s", "set"),),
|
||||
double((",.", "length"), ("<>", "triplet"),),
|
||||
double(("[]", "phrase"), ("{}", "order"),),
|
||||
]))
|
||||
}
|
||||
fn stats (&self) -> impl Render<Tui> + use<'_> {
|
||||
row!([&self.cpu, &self.size])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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!([
|
||||
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!([
|
||||
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(),
|
||||
if self.0.time_lock().get() { "[lock]" } else { " " })),
|
||||
" ",
|
||||
field(" Note", format!("{} ({}) {} | {}-{} ({})",
|
||||
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()))
|
||||
]))))
|
||||
});
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Status bar for sequencer app
|
||||
#[derive(Clone)]
|
||||
pub struct GrooveboxStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state: &Groovebox|GrooveboxStatus = {
|
||||
let samples = state.clock().chunk.load(Relaxed);
|
||||
let rate = state.clock().timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock().is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
}
|
||||
});
|
||||
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 Render<Tui> {
|
||||
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!([
|
||||
single("SPACE", "play/pause"),
|
||||
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||
double(("a", "append"), ("s", "set note"),),
|
||||
double((",.", "length"), ("<>", "triplet"), ),
|
||||
double(("[]", "phrase"), ("{}", "order"), ),
|
||||
double(("q", "enqueue"), ("e", "edit"), ),
|
||||
double(("c", "color"), ("", ""),),
|
||||
]))
|
||||
}
|
||||
fn stats (&self) -> impl Render<Tui> + use<'_> {
|
||||
row!([&self.cpu, &self.size])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Status bar for sequencer app
|
||||
#[derive(Clone)]
|
||||
pub struct SequencerStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state:&SequencerTui|SequencerStatus = {
|
||||
let samples = state.clock.chunk.load(Relaxed);
|
||||
let rate = state.clock.timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock.is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
}
|
||||
});
|
||||
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 Render<Tui> {
|
||||
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!([
|
||||
single("SPACE", "play/pause"),
|
||||
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||
double(("a", "append"), ("s", "set note"),),
|
||||
double((",.", "length"), ("<>", "triplet"), ),
|
||||
double(("[]", "phrase"), ("{}", "order"), ),
|
||||
double(("q", "enqueue"), ("e", "edit"), ),
|
||||
double(("c", "color"), ("", ""),),
|
||||
]))
|
||||
}
|
||||
fn stats (&self) -> impl Render<Tui> + use<'_> {
|
||||
row!([&self.cpu, &self.size])
|
||||
}
|
||||
}
|
||||
62
src/style.rs
62
src/style.rs
|
|
@ -1,30 +1,30 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait TuiStyle {
|
||||
fn fg <W: Render<Tui>> (color: Color, w: W) -> Foreground<W> {
|
||||
fn fg <W: Content<Tui>> (color: Color, w: W) -> Foreground<W> {
|
||||
Foreground(color, w)
|
||||
}
|
||||
fn bg <W: Render<Tui>> (color: Color, w: W) -> Background<W> {
|
||||
fn bg <W: Content<Tui>> (color: Color, w: W) -> Background<W> {
|
||||
Background(color, w)
|
||||
}
|
||||
fn fg_bg <W: Render<Tui>> (fg: Color, bg: Color, w: W) -> Background<Foreground<W>> {
|
||||
fn fg_bg <W: Content<Tui>> (fg: Color, bg: Color, w: W) -> Background<Foreground<W>> {
|
||||
Background(bg, Foreground(fg, w))
|
||||
}
|
||||
fn bold <W: Render<Tui>> (on: bool, w: W) -> Bold<W> {
|
||||
fn bold <W: Content<Tui>> (on: bool, w: W) -> Bold<W> {
|
||||
Bold(on, w)
|
||||
}
|
||||
fn border <W: Render<Tui>, S: BorderStyle> (style: S, w: W) -> Bordered<S, W> {
|
||||
fn border <W: Content<Tui>, S: BorderStyle> (style: S, w: W) -> Bordered<S, W> {
|
||||
Bordered(style, w)
|
||||
}
|
||||
}
|
||||
|
||||
impl TuiStyle for Tui {}
|
||||
|
||||
pub struct Bold<W: Render<Tui>>(pub bool, W);
|
||||
pub struct Bold<W: Content<Tui>>(pub bool, W);
|
||||
|
||||
impl<W: Render<Tui>> Render<Tui> for Bold<W> {
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
self.1.min_size(to)
|
||||
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<()> {
|
||||
to.fill_bold(to.area(), self.0);
|
||||
|
|
@ -32,11 +32,11 @@ impl<W: Render<Tui>> Render<Tui> for Bold<W> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Foreground<W: Render<Tui>>(pub Color, W);
|
||||
pub struct Foreground<W: Content<Tui>>(pub Color, W);
|
||||
|
||||
impl<W: Render<Tui>> Render<Tui> for Foreground<W> {
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
self.1.min_size(to)
|
||||
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<()> {
|
||||
to.fill_fg(to.area(), self.0);
|
||||
|
|
@ -44,11 +44,11 @@ impl<W: Render<Tui>> Render<Tui> for Foreground<W> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Background<W: Render<Tui>>(pub Color, W);
|
||||
pub struct Background<W: Content<Tui>>(pub Color, W);
|
||||
|
||||
impl<W: Render<Tui>> Render<Tui> for Background<W> {
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
self.1.min_size(to)
|
||||
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<()> {
|
||||
to.fill_bg(to.area(), self.0);
|
||||
|
|
@ -56,11 +56,11 @@ impl<W: Render<Tui>> Render<Tui> for Background<W> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Styled<T: Render<Tui>>(pub Option<Style>, pub T);
|
||||
pub struct Styled<T: Content<Tui>>(pub Option<Style>, pub T);
|
||||
|
||||
impl Render<Tui> for Styled<&str> {
|
||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
Ok(Some([self.1.chars().count() as u16, 1]))
|
||||
impl Content<Tui> for Styled<&str> {
|
||||
fn content (&self) -> Option<impl Content<Tui>> {
|
||||
Some(&self.1)
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
// FIXME
|
||||
|
|
@ -70,29 +70,29 @@ impl Render<Tui> for Styled<&str> {
|
|||
}
|
||||
}
|
||||
|
||||
//pub trait TuiStyle: Render<Tui> + Sized {
|
||||
//fn fg (self, color: Color) -> impl Render<Tui> {
|
||||
//pub trait TuiStyle: Content<Tui> + Sized {
|
||||
//fn fg (self, color: Color) -> impl Content<Tui> {
|
||||
//Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
|
||||
//}
|
||||
//fn bg (self, color: Color) -> impl Render<Tui> {
|
||||
//fn bg (self, color: Color) -> impl Content<Tui> {
|
||||
//Layers::new(move |add|{ add(&Background(color))?; add(&self) })
|
||||
//}
|
||||
//fn bold (self, on: bool) -> impl Render<Tui> {
|
||||
//fn bold (self, on: bool) -> impl Content<Tui> {
|
||||
//Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
|
||||
//}
|
||||
//fn border <S: BorderStyle> (self, style: S) -> impl Render<Tui> {
|
||||
//fn border <S: BorderStyle> (self, style: S) -> impl Content<Tui> {
|
||||
//Bordered(style, self)
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<W: Render<Tui>> TuiStyle for W {}
|
||||
//impl<W: Content<Tui>> TuiStyle for W {}
|
||||
|
||||
//impl<S: BorderStyle> Render<Tui> for Border<S> {
|
||||
//impl<S: BorderStyle> Content<Tui> for Border<S> {
|
||||
//}
|
||||
|
||||
//impl<S: BorderStyle, W: Render<Tui>> Content<Tui> for Bordered<S, W> {
|
||||
//fn content (&self) -> impl Render<Tui> {
|
||||
//let content: &dyn Render<Tui> = &self.1;
|
||||
//impl<S: BorderStyle, W: Content<Tui>> Content<Tui> for Bordered<S, W> {
|
||||
//fn content (&self) -> impl Content<Tui> {
|
||||
//let content: &dyn Content<Tui> = &self.1;
|
||||
//lay! { content.padding_xy(1, 1), Border(self.0) }.fill_xy()
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ 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)),
|
||||
])));
|
||||
|
|
@ -87,7 +87,7 @@ impl TransportView {
|
|||
}
|
||||
}
|
||||
}
|
||||
render!(<Tui>|self: TransportView|{
|
||||
render!(Tui: (self: TransportView) => {
|
||||
let color = self.color;
|
||||
Fixed::y(3, Tui::bg(color.base.rgb, Fill::x(row!([
|
||||
col!([
|
||||
|
|
@ -106,14 +106,14 @@ render!(<Tui>|self: TransportView|{
|
|||
]))))
|
||||
});
|
||||
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(
|
||||
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!([
|
||||
|
|
@ -146,8 +146,8 @@ pub enum TransportFocus {
|
|||
Quant,
|
||||
}
|
||||
impl FocusWrap<TransportFocus> for TransportFocus {
|
||||
fn wrap <'a, W: Render<Tui>> (self, focus: TransportFocus, content: &'a W)
|
||||
-> impl Render<Tui> + 'a
|
||||
fn wrap <'a, W: Content<Tui>> (self, focus: TransportFocus, content: &'a W)
|
||||
-> impl Content<Tui> + 'a
|
||||
{
|
||||
let focused = focus == self;
|
||||
let corners = focused.then_some(CORNERS);
|
||||
|
|
@ -156,8 +156,8 @@ impl FocusWrap<TransportFocus> for TransportFocus {
|
|||
}
|
||||
}
|
||||
impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
||||
fn wrap <'a, W: Render<Tui>> (self, focus: TransportFocus, content: &'a W)
|
||||
-> impl Render<Tui> + 'a
|
||||
fn wrap <'a, W: Content<Tui>> (self, focus: TransportFocus, content: &'a W)
|
||||
-> impl Content<Tui> + 'a
|
||||
{
|
||||
let focused = Some(focus) == self;
|
||||
let corners = focused.then_some(CORNERS);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue