wip3 (25e): woohoohoo

This commit is contained in:
🪞👃🪞 2024-12-09 16:34:45 +01:00
parent 3de89bf4fd
commit 36280ce9b7
40 changed files with 1607 additions and 1412 deletions

View file

@ -58,7 +58,8 @@ impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> {
Collect::Array(array) => {
if let Some(item) = array.get(self.0) {
self.0 += 1;
Some(item)
//Some(item)
None
} else {
None
}
@ -66,7 +67,8 @@ impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> {
Collect::Slice(slice) => {
if let Some(item) = slice.get(self.0) {
self.0 += 1;
Some(item)
//Some(item)
None
} else {
None
}

View file

@ -15,6 +15,39 @@ pub fn widget <E: Engine, T: Render<E>> (w: &T) -> &dyn Render<E> {
w as &dyn Render<E>
}
/// A [Render] that contains other [Render]s
pub trait Content<E: Engine>: Send + Sync {
fn content (&self) -> impl Render<E>;
}
//impl<E: Engine, C: Content<E>> Render<E> for &C {
//fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
//self.content().min_size(to)
//}
//fn render (&self, to: &mut E::Output) -> Usually<()> {
//match self.min_size(to.area().wh().into())? {
//Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()),
//None => Ok(())
//}
//}
//}
/*
/// Every struct that has [Content] is a renderable [Render].
impl<E: Engine, C: Content<E>> Render<E> for C {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.content().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
match self.min_size(to.area().wh().into())? {
Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()),
None => Ok(())
}
}
}
*/
/// A renderable component
pub trait Render<E: Engine>: Send + Sync {
/// Minimum size to use
@ -25,7 +58,7 @@ pub trait Render<E: Engine>: Send + Sync {
fn render (&self, to: &mut E::Output) -> Usually<()>;
}
impl<E: Engine> Render<E> for &dyn Render<E> {
impl<E: Engine, R: Render<E>> Render<E> for &R {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
(*self).min_size(to)
}
@ -34,12 +67,21 @@ impl<E: Engine> Render<E> for &dyn Render<E> {
}
}
//impl<E: Engine> Render<E> for &dyn Render<E> {
//fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
//(*self).min_size(to)
//}
//fn render (&self, to: &mut E::Output) -> Usually<()> {
//(*self).render(to)
//}
//}
impl<E: Engine> Render<E> for &mut dyn Render<E> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
(**self).min_size(to)
(*self).min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
(**self).render(to)
(*self).render(to)
}
}
@ -88,24 +130,6 @@ impl<E: Engine, W: Render<E>> Render<E> for Option<W> {
}
}
/// A [Render] that contains other [Render]s
pub trait Content<E: Engine>: Send + Sync {
fn content (&self) -> impl Render<E>;
}
/// Every struct that has [Content] is a renderable [Render].
impl<E: Engine, W: Content<E>> Render<E> for &W {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.content().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
match self.min_size(to.area().wh().into())? {
Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()),
None => Ok(())
}
}
}
/// A custom [Render] defined by passing layout and render closures in place.
pub struct Widget<
E: Engine,

View file

@ -0,0 +1 @@
manja s grozde i ikebana s chiaroscuro

View file

@ -1,19 +1,19 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutAlign<E> for W {}
impl<E: Engine> LayoutAlign<E> for E {}
pub trait LayoutAlign<E: Engine>: Render<E> + Sized {
fn align_x (self) -> Align<Self> { Align::X(self) }
fn align_y (self) -> Align<Self> { Align::Y(self) }
fn align_center (self) -> Align<Self> { Align::Center(self) }
fn align_n (self) -> Align<Self> { Align::N(self) }
fn align_s (self) -> Align<Self> { Align::S(self) }
fn align_e (self) -> Align<Self> { Align::E(self) }
fn align_w (self) -> Align<Self> { Align::W(self) }
fn align_nw (self) -> Align<Self> { Align::NW(self) }
fn align_sw (self) -> Align<Self> { Align::SW(self) }
fn align_ne (self) -> Align<Self> { Align::NE(self) }
fn align_se (self) -> Align<Self> { Align::SE(self) }
pub trait LayoutAlign<E: Engine> {
fn center_x <W: Render<E>> (w: W) -> Align<W> { Align::X(w) }
fn center_y <W: Render<E>> (w: W) -> Align<W> { Align::Y(w) }
fn center <W: Render<E>> (w: W) -> Align<W> { Align::Center(w) }
fn at_n <W: Render<E>> (w: W) -> Align<W> { Align::N(w) }
fn at_s <W: Render<E>> (w: W) -> Align<W> { Align::S(w) }
fn at_e <W: Render<E>> (w: W) -> Align<W> { Align::E(w) }
fn at_w <W: Render<E>> (w: W) -> Align<W> { Align::W(w) }
fn at_nw <W: Render<E>> (w: W) -> Align<W> { Align::NW(w) }
fn at_sw <W: Render<E>> (w: W) -> Align<W> { Align::SW(w) }
fn at_ne <W: Render<E>> (w: W) -> Align<W> { Align::NE(w) }
fn at_se <W: Render<E>> (w: W) -> Align<W> { Align::SE(w) }
}
/// Override X and Y coordinates, aligning to corner, side, or center of area

View file

@ -1,54 +1,78 @@
use crate::*;
impl<E: Engine, R: Render<E>> LayoutBsp<E> for R {}
impl<E: Engine> LayoutBspStatic<E> for E {}
pub trait LayoutBsp<E: Engine>: Render<E> + Sized {
fn when (self, cond: bool) -> If<E, Self> {
If(Default::default(), cond, self)
pub trait LayoutBspStatic<E: Engine>: {
fn over <A: Render<E>, B: Render<E>> (a: A, b: B) -> Over<E, A, B> {
Over(Default::default(), a, b)
}
fn or <B: Render<E>> (self, cond: bool, other: B) -> Either<E, Self, B> {
Either(Default::default(), cond, self, other)
fn under <A: Render<E>, B: Render<E>> (a: A, b: B) -> Under<E, A, B> {
Under(Default::default(), a, b)
}
fn over <B: Render<E>> (self, other: B) -> Over<E, Self, B> {
Over(Default::default(), self, other)
fn to_north <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToNorth<E, A, B> {
ToNorth(None, a, b)
}
fn under <B: Render<E>> (self, other: B) -> Under<E, Self, B> {
Under(Default::default(), self, other)
fn to_south <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToSouth<E, A, B> {
ToSouth(None, a, b)
}
fn north_of <B: Render<E>> (self, other: B) -> North<E, Self, B> {
North(Default::default(), self, other)
fn to_east <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToEast<E, A, B> {
ToEast(None, a, b)
}
fn south_of <B: Render<E>> (self, other: B) -> South<E, Self, B> {
South(Default::default(), self, other)
}
fn east_of <B: Render<E>> (self, other: B) -> East<E, Self, B> {
East(Default::default(), self, other)
}
fn west_of <B: Render<E>> (self, other: B) -> West<E, Self, B> {
West(Default::default(), self, other)
fn to_west <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToWest<E, A, B> {
ToWest(None, a, b)
}
}
/// Render widget if predicate is true
pub struct If<E: Engine, A: Render<E>>(PhantomData<E>, bool, A);
impl<E: Engine, A: Render<E>> Content<E> for If<E, A> {
fn content (&self) -> impl Render<E> {
if self.1 { Some(widget(&self.2)) } else { None }
pub trait LayoutBspFixedStatic<E: Engine>: {
fn to_north <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToNorth<E, A, B> {
ToNorth(Some(n), a, b)
}
fn to_south <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToSouth<E, A, B> {
ToSouth(Some(n), a, b)
}
fn to_east <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToEast<E, A, B> {
ToEast(Some(n), a, b)
}
fn to_west <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToWest<E, A, B> {
ToWest(Some(n), a, b)
}
}
/// Render widget A if predicate is true, otherwise widget B
pub struct Either<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, bool, A, B);
pub struct Over<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct Under<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct North<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct ToNorth<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
pub struct South<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct ToSouth<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
pub struct East<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct ToEast<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
pub struct West<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct ToWest<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToNorth<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToSouth<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToEast<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}

View file

@ -0,0 +1,67 @@
use crate::*;
impl<E: Engine, R: Render<E>> LayoutCond<E> for R {}
pub trait LayoutCond<E: Engine>: Render<E> + Sized {
fn when (self, cond: bool) -> If<E, Self> {
If(Default::default(), cond, self)
}
fn or <B: Render<E>> (self, cond: bool, other: B) -> Either<E, Self, B> {
Either(Default::default(), cond, self, other)
}
}
impl<E: Engine> LayoutCondStatic<E> for E {}
pub trait LayoutCondStatic<E: Engine> {
fn either <A: Render<E>, B: Render<E>> (
condition: bool,
a: A,
b: B,
) -> Either<E, A, B> {
Either(Default::default(), condition, a, b)
}
}
/// Render widget if predicate is true
pub struct If<E: Engine, A: Render<E>>(PhantomData<E>, bool, A);
impl<E: Engine, A: Render<E>> Render<E> for If<E, A> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
if self.1 {
return self.2.min_size(to)
}
Ok(None)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
if self.1 {
return self.2.render(to)
}
Ok(())
}
}
/// Render widget A if predicate is true, otherwise widget B
pub struct Either<E: Engine, A: Render<E>, B: Render<E>>(
PhantomData<E>,
bool,
A,
B,
);
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Either<E, A, B> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
if self.1 {
return self.2.min_size(to)
} else {
return self.3.min_size(to)
}
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
if self.1 {
return self.2.render(to)
} else {
return self.3.render(to)
}
}
}

View file

@ -1,16 +1,16 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutFill<E> for W {}
impl<E: Engine> LayoutFill<E> for E {}
pub trait LayoutFill<E: Engine>: Render<E> + Sized {
fn fill_x (self) -> Fill<E, Self> {
Fill::X(self)
pub trait LayoutFill<E: Engine> {
fn fill_x <W: Render<E>> (fill: W) -> Fill<E, W> {
Fill::X(fill)
}
fn fill_y (self) -> Fill<E, Self> {
Fill::Y(self)
fn fill_y <W: Render<E>> (fill: W) -> Fill<E, W> {
Fill::Y(fill)
}
fn fill_xy (self) -> Fill<E, Self> {
Fill::XY(self)
fn fill_xy <W: Render<E>> (fill: W) -> Fill<E, W> {
Fill::XY(fill)
}
}

View file

@ -1,46 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutGrow<E> for W {}
pub trait LayoutGrow<E: Engine>: Render<E> + Sized {
fn grow_x (self, x: E::Unit) -> Grow<E::Unit, Self> {
Grow::X(x, self)
}
fn grow_y (self, y: E::Unit) -> Grow<E::Unit, Self> {
Grow::Y(y, self)
}
fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow<E::Unit, Self> {
Grow::XY(x, y, self)
}
}
/// Expand drawing area
pub enum Grow<N: Coordinate, T> {
/// Increase width
X(N, T),
/// Increase height
Y(N, T),
/// Increase width and height
XY(N, N, T)
}
impl<N: Coordinate, T> Grow<N, T> {
fn inner (&self) -> &T {
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
}
}
impl<E: Engine, T: Render<E>> Render<E> for Grow<E::Unit, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [to.w() + w, to.h()],
Self::Y(h, _) => [to.w(), to.h() + h],
Self::XY(w, h, _) => [to.w() + w, to.h() + h],
}.into()))
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}

View file

@ -1,48 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutInset<E> for W {}
pub trait LayoutInset<E: Engine>: Render<E> + Sized {
fn inset_x (self, x: E::Unit) -> Inset<E, Self> {
Inset::X(x, self)
}
fn inset_y (self, y: E::Unit) -> Inset<E, Self> {
Inset::Y(y, self)
}
fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset<E, Self> {
Inset::XY(x, y, self)
}
}
/// Shrink from each side
pub enum Inset<E: Engine, T> {
_Unused(PhantomData<E>),
/// Decrease width
X(E::Unit, T),
/// Decrease height
Y(E::Unit, T),
/// Decrease width and height
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Inset<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Inset<E, T> {
fn render (&self, to: &mut E::Output) -> Usually<()> {
match *self {
Self::X(x, ref inner) => (inner as &dyn Render<E>).shrink_x(x).push_x(x),
Self::Y(y, ref inner) => (inner as &dyn Render<E>).shrink_y(y).push_y(y),
Self::XY(x, y, ref inner) => (inner as &dyn Render<E>).shrink_xy(x, y).push_xy(x, y),
_ => unreachable!(),
}.render(to)
}
}

View file

@ -0,0 +1,99 @@
use crate::*;
impl<E: Engine + LayoutPushPull<E> + LayoutShrinkGrow<E>> LayoutInsetOutset<E> for E {}
pub trait LayoutInsetOutset<E: Engine>: LayoutPushPull<E> + LayoutShrinkGrow<E> {
fn inset_x <W: Render<E>> (x: E::Unit, w: W) -> Inset<E, W> {
Inset::X(x, w)
}
fn inset_y <W: Render<E>> (y: E::Unit, w: W) -> Inset<E, W> {
Inset::Y(y, w)
}
fn inset_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Inset<E, W> {
Inset::XY(x, y, w)
}
fn outset_x <W: Render<E>> (x: E::Unit, w: W) -> Outset<E, W> {
Outset::X(x, w)
}
fn outset_y <W: Render<E>> (y: E::Unit, w: W) -> Outset<E, W> {
Outset::Y(y, w)
}
fn outset_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Outset<E, W> {
Outset::XY(x, y, w)
}
}
/// Shrink from each side
pub enum Inset<E: Engine, T> {
_Unused(PhantomData<E>),
/// Decrease width
X(E::Unit, T),
/// Decrease height
Y(E::Unit, T),
/// Decrease width and height
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Inset<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Inset<E, T> {
fn render (&self, to: &mut E::Output) -> Usually<()> {
match self {
Self::X(x, inner) => E::push_x(*x, E::shrink_x(*x, inner)),
Self::Y(y, inner) => E::push_y(*y, E::shrink_y(*y, inner)),
Self::XY(x, y, inner) => E::push_xy(*x, *y, E::shrink_xy(*x, *y, inner)),
_ => unreachable!(),
}.render(to)
}
}
/// Grow on each side
pub enum Outset<E: Engine, T: Render<E>> {
_Unused(PhantomData<E>),
/// Increase width
X(E::Unit, T),
/// Increase height
Y(E::Unit, T),
/// Increase width and height
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Outset<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Outset<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
match *self {
Self::X(x, ref inner) => E::grow_x(x + x, inner),
Self::Y(y, ref inner) => E::grow_y(y + y, inner),
Self::XY(x, y, ref inner) => E::grow_xy(x + x, y + y, inner),
_ => unreachable!(),
}.min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
match *self {
Self::X(x, ref inner) => E::push_x(x, inner),
Self::Y(y, ref inner) => E::push_y(y, inner),
Self::XY(x, y, ref inner) => E::push_xy(x, y, inner),
_ => unreachable!(),
}.render(to)
}
}

View file

@ -33,7 +33,7 @@ where
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
if let Some(size) = self.min_size(to.area().wh().into())? {
(self.0)(&mut |layer|to.render_in(to.area().clip(size).into(), &layer))
(self.0)(&mut |layer|to.render_in(to.area().clip(size).into(), layer))
} else {
Ok(())
}

View file

@ -3,52 +3,22 @@ pub(crate) use tek_core::*;
submod! {
align
bsp
cond
debug
fill
fixed
grow
inset
inset_outset
layers
max
map_reduce
measure
min
outset
pull
push
min_max
push_pull
scroll
shrink
shrink_grow
split
stack
//stack
}
#[macro_export] macro_rules! lay {
($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) }
}
#[macro_export] macro_rules! col {
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
($pat:pat in $collection:expr => $item:expr) => {
Stack::down(move |add|{
for $pat in $collection { add(&$item)?; }
Ok(())
})
}
}
#[macro_export] macro_rules! col_up {
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
($pat:pat in $collection:expr => $item:expr) => {
Stack::up(move |add|{
for $pat in $collection { add(&$item)?; }
Ok(())
})
}
}
#[macro_export] macro_rules! row {
($($expr:expr),* $(,)?) => { Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
($pat:pat in $collection:expr => $item:expr) => {
Stack::right(move |add|{
for $pat in $collection { add(&$item)?; }
Ok(())
})
}
}

View file

@ -0,0 +1,34 @@
use crate::*;
impl<E: Engine> LayoutMapReduce<E> for E {}
pub trait LayoutMapReduce<E: Engine> {
fn map <T, I, R, F> (iterator: I, callback: F) -> Map<E, T, I, R, F>
where
I: Iterator<Item=T>,
R: Render<E>,
F: Fn(T)->R
{
Map(Default::default(), iterator, callback)
}
fn reduce <T, I, R, F> (iterator: I, callback: F) -> Reduce<E, T, I, R, F>
where
I: Iterator<Item=T>,
R: Render<E>,
F: Fn(R, T)->R
{
Reduce(Default::default(), iterator, callback)
}
}
pub struct Map<E: Engine, T, I: Iterator<Item=T>, R: Render<E>, F: Fn(T)->R>(
PhantomData<E>,
I,
F
);
pub struct Reduce<E: Engine, T, I: Iterator<Item=T>, R: Render<E>, F: Fn(R, T)->R>(
PhantomData<(E, R)>,
I,
F
);

View file

@ -1,40 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutMax<E> for W {}
pub trait LayoutMax<E: Engine>: Render<E> + Sized {
fn max_x (self, x: E::Unit) -> Max<E::Unit, Self> { Max::X(x, self) }
fn max_y (self, y: E::Unit) -> Max<E::Unit, Self> { Max::Y(y, self) }
fn max_xy (self, x: E::Unit, y: E::Unit) -> Max<E::Unit, Self> { Max::XY(x, y, self) }
}
/// Enforce maximum size of drawing area
pub enum Max<U: Coordinate, T> {
/// Enforce maximum width
X(U, T),
/// Enforce maximum height
Y(U, T),
/// Enforce maximum width and height
XY(U, U, T),
}
impl<N: Coordinate, T> Max<N, T> {
fn inner (&self) -> &T {
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
}
}
impl<E: Engine, T: Render<E>> Render<E> for Max<E:: Unit, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [to.w().min(w), to.h()],
Self::Y(h, _) => [to.w(), to.h().min(h)],
Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)],
}.into()))
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}

View file

@ -1,39 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutMin<E> for W {}
pub trait LayoutMin<E: Engine>: Render<E> + Sized {
fn min_x (self, x: E::Unit) -> Min<E::Unit, Self> { Min::X(x, self) }
fn min_y (self, y: E::Unit) -> Min<E::Unit, Self> { Min::Y(y, self) }
fn min_xy (self, x: E::Unit, y: E::Unit) -> Min<E::Unit, Self> { Min::XY(x, y, self) }
}
/// Enforce minimum size of drawing area
pub enum Min<U: Coordinate, T> {
/// Enforce minimum width
X(U, T),
/// Enforce minimum height
Y(U, T),
/// Enforce minimum width and height
XY(U, U, T),
}
impl<N: Coordinate, T> Min<N, T> {
pub fn inner (&self) -> &T {
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
}
}
impl<E: Engine, T: Render<E>> Render<E> for Min<E::Unit, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [to.w().max(w), to.h()],
Self::Y(h, _) => [to.w(), to.h().max(h)],
Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)],
}.into()))
}
// TODO: 🡘 🡙 ←🡙→
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}

View file

@ -0,0 +1,85 @@
use crate::*;
impl<E: Engine> LayoutMinMax<E> for E {}
pub trait LayoutMinMax<E: Engine> {
fn min_x <W: Render<E>> (x: E::Unit, w: W) -> Min<E::Unit, W> {
Min::X(x, w)
}
fn min_y <W: Render<E>> (y: E::Unit, w: W) -> Min<E::Unit, W> {
Min::Y(y, w)
}
fn min_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Min<E::Unit, W> {
Min::XY(x, y, w)
}
fn max_x <W: Render<E>> (x: E::Unit, w: W) -> Max<E::Unit, W> {
Max::X(x, w)
}
fn max_y <W: Render<E>> (y: E::Unit, w: W) -> Max<E::Unit, W> {
Max::Y(y, w)
}
fn max_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Max<E::Unit, W> {
Max::XY(x, y, w)
}
}
/// Enforce minimum size of drawing area
pub enum Min<U: Coordinate, T> {
/// Enforce minimum width
X(U, T),
/// Enforce minimum height
Y(U, T),
/// Enforce minimum width and height
XY(U, U, T),
}
impl<N: Coordinate, T> Min<N, T> {
pub fn inner (&self) -> &T {
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
}
}
impl<E: Engine, T: Render<E>> Render<E> for Min<E::Unit, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [to.w().max(w), to.h()],
Self::Y(h, _) => [to.w(), to.h().max(h)],
Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)],
}.into()))
}
// TODO: 🡘 🡙 ←🡙→
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}
/// Enforce maximum size of drawing area
pub enum Max<U: Coordinate, T> {
/// Enforce maximum width
X(U, T),
/// Enforce maximum height
Y(U, T),
/// Enforce maximum width and height
XY(U, U, T),
}
impl<N: Coordinate, T> Max<N, T> {
fn inner (&self) -> &T {
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
}
}
impl<E: Engine, T: Render<E>> Render<E> for Max<E:: Unit, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [to.w().min(w), to.h()],
Self::Y(h, _) => [to.w(), to.h().min(h)],
Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)],
}.into()))
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}

View file

@ -1,57 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutOutset<E> for W {}
pub trait LayoutOutset<E: Engine>: Render<E> + Sized {
fn outset_x (self, x: E::Unit) -> Outset<E, Self> {
Outset::X(x, self)
}
fn outset_y (self, y: E::Unit) -> Outset<E, Self> {
Outset::Y(y, self)
}
fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset<E, Self> {
Outset::XY(x, y, self)
}
}
/// Grow on each side
pub enum Outset<E: Engine, T: Render<E>> {
_Unused(PhantomData<E>),
/// Increase width
X(E::Unit, T),
/// Increase height
Y(E::Unit, T),
/// Increase width and height
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Outset<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Outset<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
match *self {
Self::X(x, ref inner) => (inner as &dyn Render<E>).grow_x(x + x),
Self::Y(y, ref inner) => (inner as &dyn Render<E>).grow_y(y + y),
Self::XY(x, y, ref inner) => (inner as &dyn Render<E>).grow_xy(x + x, y + y),
_ => unreachable!(),
}.min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
match *self {
Self::X(x, ref inner) => (inner as &dyn Render<E>).push_x(x),
Self::Y(y, ref inner) => (inner as &dyn Render<E>).push_y(y),
Self::XY(x, y, ref inner) => (inner as &dyn Render<E>).push_xy(x, y),
_ => unreachable!(),
}.render(to)
}
}

View file

@ -1,69 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutPull<E> for W {}
pub trait LayoutPull<E: Engine>: Render<E> + Sized {
fn pull_x (self, x: E::Unit) -> Pull<E, Self> {
Pull::X(x, self)
}
fn pull_y (self, y: E::Unit) -> Pull<E, Self> {
Pull::Y(y, self)
}
fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull<E, Self> {
Pull::XY(x, y, self)
}
}
/// Move origin point of drawing area
pub enum Pull<E: Engine, T: Render<E>> {
_Unused(PhantomData<E>),
/// Move origin to the right
X(E::Unit, T),
/// Move origin downwards
Y(E::Unit, T),
/// Move origin to the right and downwards
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Pull<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
pub fn x (&self) -> E::Unit {
match self {
Self::X(x, _) => *x,
Self::Y(_, _) => E::Unit::default(),
Self::XY(x, _, _) => *x,
_ => unreachable!(),
}
}
pub fn y (&self) -> E::Unit {
match self {
Self::X(_, _) => E::Unit::default(),
Self::Y(y, _) => *y,
Self::XY(_, y, _) => *y,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Pull<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.inner().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
let area = to.area();
Ok(self.min_size(area.wh().into())?
.map(|size|to.render_in(match *self {
Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()],
Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()],
Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()],
_ => unreachable!(),
}.into(), self.inner())).transpose()?.unwrap_or(()))
}
}

View file

@ -1,68 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutPush<E> for W {}
pub trait LayoutPush<E: Engine>: Render<E> + Sized {
fn push_x (self, x: E::Unit) -> Push<E, Self> {
Push::X(x, self)
}
fn push_y (self, y: E::Unit) -> Push<E, Self> {
Push::Y(y, self)
}
fn push_xy (self, x: E::Unit, y: E::Unit) -> Push<E, Self> {
Push::XY(x, y, self)
}
}
/// Move origin point of drawing area
pub enum Push<E: Engine, T: Render<E>> {
/// Move origin to the right
X(E::Unit, T),
/// Move origin downwards
Y(E::Unit, T),
/// Move origin to the right and downwards
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Push<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
pub fn x (&self) -> E::Unit {
match self {
Self::X(x, _) => *x,
Self::Y(_, _) => E::Unit::default(),
Self::XY(x, _, _) => *x,
_ => unreachable!(),
}
}
pub fn y (&self) -> E::Unit {
match self {
Self::X(_, _) => E::Unit::default(),
Self::Y(y, _) => *y,
Self::XY(_, y, _) => *y,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Push<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.inner().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
let area = to.area();
Ok(self.min_size(area.wh().into())?
.map(|size|to.render_in(match *self {
Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()],
Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()],
Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()],
_ => unreachable!(),
}.into(), self.inner())).transpose()?.unwrap_or(()))
}
}

View file

@ -0,0 +1,132 @@
use crate::*;
impl<E: Engine> LayoutPushPull<E> for E {}
pub trait LayoutPushPull<E: Engine> {
fn push_x <W: Render<E>> (x: E::Unit, w: W) -> Push<E, W> {
Push::X(x, w)
}
fn push_y <W: Render<E>> (y: E::Unit, w: W) -> Push<E, W> {
Push::Y(y, w)
}
fn push_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Push<E, W> {
Push::XY(x, y, w)
}
fn pull_x <W: Render<E>> (x: E::Unit, w: W) -> Pull<E, W> {
Pull::X(x, w)
}
fn pull_y <W: Render<E>> (y: E::Unit, w: W) -> Pull<E, W> {
Pull::Y(y, w)
}
fn pull_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Pull<E, W> {
Pull::XY(x, y, w)
}
}
/// Increment origin point of drawing area
pub enum Push<E: Engine, T: Render<E>> {
/// Move origin to the right
X(E::Unit, T),
/// Move origin downwards
Y(E::Unit, T),
/// Move origin to the right and downwards
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Push<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
pub fn x (&self) -> E::Unit {
match self {
Self::X(x, _) => *x,
Self::Y(_, _) => E::Unit::default(),
Self::XY(x, _, _) => *x,
_ => unreachable!(),
}
}
pub fn y (&self) -> E::Unit {
match self {
Self::X(_, _) => E::Unit::default(),
Self::Y(y, _) => *y,
Self::XY(_, y, _) => *y,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Push<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.inner().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
let area = to.area();
Ok(self.min_size(area.wh().into())?
.map(|size|to.render_in(match *self {
Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()],
Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()],
Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()],
_ => unreachable!(),
}.into(), self.inner())).transpose()?.unwrap_or(()))
}
}
/// Decrement origin point of drawing area
pub enum Pull<E: Engine, T: Render<E>> {
_Unused(PhantomData<E>),
/// Move origin to the right
X(E::Unit, T),
/// Move origin downwards
Y(E::Unit, T),
/// Move origin to the right and downwards
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Pull<E, T> {
pub fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
pub fn x (&self) -> E::Unit {
match self {
Self::X(x, _) => *x,
Self::Y(_, _) => E::Unit::default(),
Self::XY(x, _, _) => *x,
_ => unreachable!(),
}
}
pub fn y (&self) -> E::Unit {
match self {
Self::X(_, _) => E::Unit::default(),
Self::Y(y, _) => *y,
Self::XY(_, y, _) => *y,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Pull<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.inner().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
let area = to.area();
Ok(self.min_size(area.wh().into())?
.map(|size|to.render_in(match *self {
Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()],
Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()],
Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()],
_ => unreachable!(),
}.into(), self.inner())).transpose()?.unwrap_or(()))
}
}

View file

@ -1,62 +0,0 @@
use crate::*;
impl<E: Engine, W: Render<E>> LayoutShrink<E> for W {}
pub trait LayoutShrink<E: Engine>: Render<E> + Sized {
fn shrink_x (self, x: E::Unit) -> Shrink<E, Self> {
Shrink::X(x, self)
}
fn shrink_y (self, y: E::Unit) -> Shrink<E, Self> {
Shrink::Y(y, self)
}
fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink<E, Self> {
Shrink::XY(x, y, self)
}
}
/// Shrink drawing area
pub enum Shrink<E: Engine, T> {
_Unused(PhantomData<E>),
/// Decrease width
X(E::Unit, T),
/// Decrease height
Y(E::Unit, T),
/// Decrease width and height
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Shrink<E, T> {
fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Shrink<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [
if to.w() > w { to.w() - w } else { 0.into() },
to.h()
],
Self::Y(h, _) => [
to.w(),
if to.h() > h { to.h() - h } else { 0.into() }
],
Self::XY(w, h, _) => [
if to.w() > w { to.w() - w } else { 0.into() },
if to.h() > h { to.h() - h } else { 0.into() }
],
_ => unreachable!(),
}.into()))
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}

View file

@ -0,0 +1,102 @@
use crate::*;
impl<E: Engine> LayoutShrinkGrow<E> for E {}
pub trait LayoutShrinkGrow<E: Engine> {
fn shrink_x <W: Render<E>> (x: E::Unit, w: W) -> Shrink<E, W> {
Shrink::X(x, w)
}
fn shrink_y <W: Render<E>> (y: E::Unit, w: W) -> Shrink<E, W> {
Shrink::Y(y, w)
}
fn shrink_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Shrink<E, W> {
Shrink::XY(x, y, w)
}
fn grow_x <W: Render<E>> (x: E::Unit, w: W) -> Grow<E::Unit, W> {
Grow::X(x, w)
}
fn grow_y <W: Render<E>> (y: E::Unit, w: W) -> Grow<E::Unit, W> {
Grow::Y(y, w)
}
fn grow_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Grow<E::Unit, W> {
Grow::XY(x, y, w)
}
}
/// Shrink drawing area
pub enum Shrink<E: Engine, T> {
_Unused(PhantomData<E>),
/// Decrease width
X(E::Unit, T),
/// Decrease height
Y(E::Unit, T),
/// Decrease width and height
XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> Shrink<E, T> {
fn inner (&self) -> &T {
match self {
Self::X(_, i) => i,
Self::Y(_, i) => i,
Self::XY(_, _, i) => i,
_ => unreachable!(),
}
}
}
impl<E: Engine, T: Render<E>> Render<E> for Shrink<E, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [
if to.w() > w { to.w() - w } else { 0.into() },
to.h()
],
Self::Y(h, _) => [
to.w(),
if to.h() > h { to.h() - h } else { 0.into() }
],
Self::XY(w, h, _) => [
if to.w() > w { to.w() - w } else { 0.into() },
if to.h() > h { to.h() - h } else { 0.into() }
],
_ => unreachable!(),
}.into()))
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}
/// Expand drawing area
pub enum Grow<N: Coordinate, T> {
/// Increase width
X(N, T),
/// Increase height
Y(N, T),
/// Increase width and height
XY(N, N, T)
}
impl<N: Coordinate, T> Grow<N, T> {
fn inner (&self) -> &T {
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
}
}
impl<E: Engine, T: Render<E>> Render<E> for Grow<E::Unit, T> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.inner().min_size(to)?.map(|to|match *self {
Self::X(w, _) => [to.w() + w, to.h()],
Self::Y(h, _) => [to.w(), to.h() + h],
Self::XY(w, h, _) => [to.w() + w, to.h() + h],
}.into()))
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
Ok(self.min_size(to.area().wh().into())?
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
.transpose()?.unwrap_or(()))
}
}

View file

@ -1,5 +1,33 @@
use crate::*;
#[macro_export] macro_rules! col {
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
($pat:pat in $collection:expr => $item:expr) => {
Stack::down(move |add|{
for $pat in $collection { add(&$item)?; }
Ok(())
})
}
}
#[macro_export] macro_rules! col_up {
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
($pat:pat in $collection:expr => $item:expr) => {
Stack::up(move |add|{
for $pat in $collection { add(&$item)?; }
Ok(())
})
}
}
#[macro_export] macro_rules! row {
($($expr:expr),* $(,)?) => { Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
($pat:pat in $collection:expr => $item:expr) => {
Stack::right(move |add|{
for $pat in $collection { add(&$item)?; }
Ok(())
})
}
}
pub struct Stack<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
@ -36,7 +64,7 @@ where
(self.0)(&mut |component: &dyn Render<E>| {
let max = to.h().minus(h);
if max > E::Unit::ZERO() {
let item = component.push_y(h).max_y(max);
let item = E::max_y(max, E::push_y(h, component));
let size = item.min_size(to)?.map(|size|size.wh());
if let Some([width, height]) = size {
h = h + height.into();
@ -54,7 +82,7 @@ where
(self.0)(&mut |component: &dyn Render<E>| {
let max = to.w().minus(w);
if max > E::Unit::ZERO() {
let item = component.push_x(w).max_x(max);
let item = E::max_x(max, E::push_x(h, component));
let size = item.min_size(to)?.map(|size|size.wh());
if let Some([width, height]) = size {
w = w + width.into();

View file

@ -3,6 +3,9 @@ name = "tek_tui"
edition = "2021"
version = "0.1.0"
[lib]
path = "src/tui.rs"
[dependencies]
tek_core = { path = "../tek_core" }
tek_layout = { path = "../tek_layout" }

View file

@ -1,214 +0,0 @@
pub(crate) use crossterm::event::{KeyCode, KeyModifiers};
pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage};
pub(crate) use tek_core::{*, jack::*};
pub(crate) use tek_layout::*;
pub(crate) use tek_api::*;
pub(crate) use std::collections::BTreeMap;
pub(crate) use std::sync::{Arc, Mutex, RwLock};
pub(crate) use std::path::PathBuf;
pub(crate) use std::ffi::OsString;
pub(crate) use std::fs::read_dir;
pub(crate) use better_panic::{Settings, Verbosity};
submod! {
tui_engine
tui_engine_input
tui_engine_output
tui_engine_style
tui_app_arranger
tui_app_sequencer
tui_app_transport
tui_jack_transport
tui_jack_sequencer
tui_jack_arranger
tui_control_arranger
tui_control_file_browser
tui_control_phrase_editor
tui_control_phrase_length
tui_control_phrase_list
tui_control_phrase_rename
tui_control_sequencer
tui_control_transport
tui_model_arranger
tui_model_clock
tui_model_file_browser
tui_model_phrase_editor
tui_model_phrase_length
tui_model_phrase_list
tui_model_phrase_player
tui_view_arranger
tui_view_file_browser
tui_view_phrase_editor
tui_view_phrase_length
tui_view_phrase_list
tui_view_phrase_selector
tui_view_sequencer
tui_view_transport
}
pub struct TuiTheme;
impl TuiTheme {
pub fn border_bg () -> Color {
Color::Rgb(40, 50, 30)
}
pub fn border_fg (focused: bool) -> Color {
if focused { Color::Rgb(100, 110, 40) } else { Color::Rgb(70, 80, 50) }
}
pub fn title_fg (focused: bool) -> Color {
if focused { Color::Rgb(150, 160, 90) } else { Color::Rgb(120, 130, 100) }
}
pub fn separator_fg (_: bool) -> Color {
Color::Rgb(0, 0, 0)
}
pub const fn hotkey_fg () -> Color {
Color::Rgb(255, 255, 0)
}
pub fn mode_bg () -> Color {
Color::Rgb(150, 160, 90)
}
pub fn mode_fg () -> Color {
Color::Rgb(255, 255, 255)
}
pub fn status_bar_bg () -> Color {
Color::Rgb(28, 35, 25)
}
}
pub trait FocusWrap<T> {
fn wrap <'a, W: Render<Tui>> (self, focus: T, content: &'a W)
-> impl Render<Tui> + 'a;
}
pub fn to_focus_command (input: &TuiInput) -> Option<FocusCommand> {
use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc};
Some(match input.event() {
key!(Tab) => FocusCommand::Next,
key!(Shift-Tab) => FocusCommand::Prev,
key!(BackTab) => FocusCommand::Prev,
key!(Shift-BackTab) => FocusCommand::Prev,
key!(Up) => FocusCommand::Up,
key!(Down) => FocusCommand::Down,
key!(Left) => FocusCommand::Left,
key!(Right) => FocusCommand::Right,
key!(Enter) => FocusCommand::Enter,
key!(Esc) => FocusCommand::Exit,
_ => return None
})
}
#[macro_export] macro_rules! impl_focus {
($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => {
impl HasFocus for $Struct {
type Item = $Focus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus.inner()
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus.set_inner(to)
}
$(fn focus_updated (&mut $self) { $update_focus })?
}
impl HasEnter for $Struct {
/// Get the currently focused item.
fn entered (&self) -> bool {
self.focus.is_entered()
}
/// Get the currently focused item.
fn set_entered (&mut self, entered: bool) {
if entered {
self.focus.to_entered()
} else {
self.focus.to_focused()
}
}
}
impl FocusGrid for $Struct {
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[$Focus]] {
use $Focus::*;
&$Grid
}
}
}
}
pub trait StatusBar: Render<Tui> {
type State: Send + Sync;
fn hotkey_fg () -> Color where Self: Sized;
fn update (&mut self, state: &Self::State) where Self: Sized;
fn command (commands: &[[impl Render<Tui>;3]])
-> impl Render<Tui> + '_
where
Self: Sized
{
let hotkey_fg = Self::hotkey_fg();
Stack::right(move |add|{
Ok(for [a, b, c] in commands.iter() {
add(&row!(
" ",
widget(a),
widget(b).bold(true).fg(hotkey_fg),
widget(c),
))?;
})
})
}
fn with <'a> (state: &'a Self::State, content: impl Render<Tui>) -> impl Render<Tui>
where Self: Sized, &'a Self::State: Into<Self>
{
Stack::up(move |add|{
add(&state.into())?;
add(&content)
})
}
}
fn content_with_menu_and_status <'a, A, S, C> (
content: &'a A,
menu_bar: &'a Option<MenuBar<Tui, S, C>>,
status_bar: &'a Option<impl StatusBar>
) -> impl Render<Tui> + 'a
where
A: Render<Tui>,
S: Send + Sync + 'a,
C: Command<S>
{
let menus = menu_bar.as_ref().map_or_else(
||&[] as &[Menu<_, _, _>],
|m|m.menus.as_slice()
);
Either(
menu_bar.is_none(),
Either(
status_bar.is_none(),
widget(content),
Split::up(
1,
widget(status_bar.as_ref().unwrap()),
widget(content)
),
),
Split::down(
1,
row!(menu in menus.iter() => {
row!(" ", menu.title.as_str(), " ")
}),
widget(content)
)
)
}

View file

@ -1,18 +1,76 @@
use crate::*;
pub(crate) use std::io::{stdout};
pub(crate) use crossterm::{ExecutableCommand};
pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode};
pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState};
pub(crate) use tek_core::midly::{num::u7, MidiMessage};
pub(crate) use tek_core::{*, jack::*};
pub(crate) use tek_layout::*;
pub(crate) use tek_api::*;
pub(crate) use std::sync::{Arc, RwLock};
pub(crate) use std::path::PathBuf;
pub(crate) use std::ffi::OsString;
pub(crate) use better_panic::{Settings, Verbosity};
pub(crate) use std::thread::{spawn, JoinHandle};
pub(crate) use std::time::Duration;
pub(crate) use ratatui::buffer::Cell;
pub(crate) use crossterm::{ExecutableCommand};
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState};
pub use ratatui::prelude::{Rect, Style, Color, Buffer};
pub use ratatui::style::{Stylize, Modifier};
use ratatui::backend::{Backend, CrosstermBackend, ClearType};
use std::io::Stdout;
use crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen,
enable_raw_mode, disable_raw_mode
};
pub(crate) use ratatui::prelude::{Style, Color, Buffer};
pub(crate) use ratatui::style::{Stylize, Modifier};
pub(crate) use ratatui::backend::{Backend, CrosstermBackend, ClearType};
pub(crate) use std::io::{Stdout, stdout};
submod! {
tui_engine_focus
tui_engine_input
tui_engine_layout
tui_engine_output
tui_engine_style
tui_app_arranger
tui_app_sequencer
tui_app_transport
tui_jack_transport
tui_jack_sequencer
tui_jack_arranger
tui_control_arranger
tui_control_file_browser
tui_control_phrase_editor
tui_control_phrase_length
tui_control_phrase_list
tui_control_phrase_rename
tui_control_sequencer
tui_control_transport
tui_model_arranger
tui_model_clock
tui_model_file_browser
tui_model_phrase_editor
tui_model_phrase_length
tui_model_phrase_list
tui_model_phrase_player
tui_view_arranger
tui_view_file_browser
tui_view_phrase_editor
tui_view_phrase_length
tui_view_phrase_list
tui_view_phrase_selector
tui_view_sequencer
tui_view_status_bar
tui_view_transport
}
#[macro_export] macro_rules! render {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Render<Tui> for $Struct $(<$($L),*$($T),*>)? {
fn min_size (&$self, to: [u16;2]) -> Perhaps<[u16;2]> {
$cb.min_size(to)
}
fn render (&$self, to: &mut TuiOutput) -> Usually<()> {
$cb.render(to)
}
}
}
}
pub struct Tui {
pub exited: Arc<AtomicBool>,
@ -20,6 +78,7 @@ pub struct Tui {
pub backend: CrosstermBackend<Stdout>,
pub area: [u16;4], // FIXME auto resize
}
impl Engine for Tui {
type Unit = u16;
type Size = [Self::Unit;2];
@ -48,6 +107,7 @@ impl Engine for Tui {
disable_raw_mode().map_err(Into::into)
}
}
impl Tui {
/// Run the main loop.
pub fn run <R: Component<Tui> + Sized + 'static> (

View file

@ -152,8 +152,8 @@ impl StatusBar for ArrangerStatus {
}
}
impl Content<Tui> for ArrangerStatus {
fn content (&self) -> impl Render<Tui> {
render!(|self: ArrangerStatus|{
let label = match self {
Self::Transport => "TRANSPORT",
Self::ArrangerMix => "PROJECT",
@ -164,10 +164,13 @@ impl Content<Tui> for ArrangerStatus {
Self::PhraseView => "VIEW SEQ",
Self::PhraseEdit => "EDIT SEQ",
};
let status_bar_bg = TuiTheme::status_bar_bg();
let mode_bg = TuiTheme::mode_bg();
let mode_fg = TuiTheme::mode_fg();
let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg);
let commands = match self {
Self::ArrangerMix => Self::command(&[
["", "c", "olor"],
@ -229,7 +232,8 @@ impl Content<Tui> for ArrangerStatus {
]),
_ => Self::command(&[])
};
//let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}"));
row!(mode, commands).fill_x().bg(status_bar_bg)
}
}
Tui::bg(status_bar_bg, Tui::fill_x(Tui::to_east(mode, commands)))
});

View file

@ -55,8 +55,8 @@ impl FocusWrap<TransportFocus> for TransportFocus {
{
let focused = focus == self;
let corners = focused.then_some(CORNERS);
let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
lay!(corners, highlight, *content)
//let highlight = focused.then_some(Tui::bg(Color::Rgb(60, 70, 50)));
lay!(corners, /*highlight,*/ *content)
}
}
@ -66,8 +66,8 @@ 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)
//let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
lay!(corners, /*highlight,*/ *content)
}
}
@ -95,9 +95,7 @@ impl StatusBar for TransportStatusBar {
}
}
impl Content<Tui> for TransportStatusBar {
fn content (&self) -> impl Render<Tui> {
render!(|self: TransportStatusBar|{
todo!();
""
}
}
});

View file

@ -0,0 +1,66 @@
use crate::*;
pub trait FocusWrap<T> {
fn wrap <'a, W: Render<Tui>> (self, focus: T, content: &'a W)
-> impl Render<Tui> + 'a;
}
pub fn to_focus_command (input: &TuiInput) -> Option<FocusCommand> {
use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc};
Some(match input.event() {
key!(Tab) => FocusCommand::Next,
key!(Shift-Tab) => FocusCommand::Prev,
key!(BackTab) => FocusCommand::Prev,
key!(Shift-BackTab) => FocusCommand::Prev,
key!(Up) => FocusCommand::Up,
key!(Down) => FocusCommand::Down,
key!(Left) => FocusCommand::Left,
key!(Right) => FocusCommand::Right,
key!(Enter) => FocusCommand::Enter,
key!(Esc) => FocusCommand::Exit,
_ => return None
})
}
#[macro_export] macro_rules! impl_focus {
($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => {
impl HasFocus for $Struct {
type Item = $Focus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus.inner()
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus.set_inner(to)
}
$(fn focus_updated (&mut $self) { $update_focus })?
}
impl HasEnter for $Struct {
/// Get the currently focused item.
fn entered (&self) -> bool {
self.focus.is_entered()
}
/// Get the currently focused item.
fn set_entered (&mut self, entered: bool) {
if entered {
self.focus.to_entered()
} else {
self.focus.to_focused()
}
}
}
impl FocusGrid for $Struct {
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[$Focus]] {
use $Focus::*;
&$Grid
}
}
}
}

View file

@ -0,0 +1 @@
use crate::*;

View file

@ -1,6 +1,19 @@
use crate::*;
use ratatui::buffer::Cell;
/// Every struct that has [Content]<[Tui]> is a renderable [Render]<[Tui]>.
//impl<C: Content<Tui>> Render<Tui> for C {
//fn min_size (&self, to: [u16;2]) -> Perhaps<E::Size> {
//self.content().min_size(to)
//}
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
//match self.min_size(to.area().wh().into())? {
//Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()),
//None => Ok(())
//}
//}
//}
pub struct TuiOutput {
pub buffer: Buffer,
pub area: [u16;4]
@ -124,38 +137,39 @@ pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut C
}
}
}
//impl Render<Tui> for &str {
//type Tui;
//fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
//// TODO: line breaks
//Ok(Some([self.chars().count() as u16, 1]))
//}
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
//let [x, y, ..] = to.area();
////let [w, h] = self.min_size(to.area().wh())?.unwrap();
//Ok(to.blit(&self, x, y, None))
//}
//}
//impl Render<Tui> for String {
//type Tui;
//fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
//// TODO: line breaks
//Ok(Some([self.chars().count() as u16, 1]))
//}
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
//let [x, y, ..] = to.area();
////let [w, h] = self.min_size(to.area().wh())?.unwrap();
//Ok(to.blit(&self, x, y, None))
//}
//}
//impl<T: Render<Tui>> Render<Tui> for DebugOverlay<Tui, T> {
//type Tui;
//fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
//self.0.min_size(to)
//}
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
//let [x, y, w, h] = to.area();
//self.0.render(to)?;
//Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
//}
//}
impl Render<Tui> for &str {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
// TODO: line breaks
Ok(Some([self.chars().count() as u16, 1]))
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let [x, y, ..] = to.area();
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
Ok(to.blit(&self, x, y, None))
}
}
impl Render<Tui> for String {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
// TODO: line breaks
Ok(Some([self.chars().count() as u16, 1]))
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let [x, y, ..] = to.area();
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
Ok(to.blit(&self, x, y, None))
}
}
impl<T: Render<Tui>> Render<Tui> for DebugOverlay<Tui, T> {
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
self.1.min_size(to)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let [x, y, w, h] = to.area();
self.1.render(to)?;
Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
}
}

View file

@ -1,57 +1,47 @@
use crate::*;
pub struct Styled<T: Render<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 Tui {
pub(crate) fn fg <W: Render<Tui>> (color: Color, w: W) -> Foreground<W> {
Foreground(color, w)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
// FIXME
let [x, y, ..] = to.area();
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
Ok(to.blit(&self.1, x, y, None))
pub(crate) fn bg <W: Render<Tui>> (color: Color, w: W) -> Background<W> {
Background(color, w)
}
pub(crate) fn bold <W: Render<Tui>> (on: bool, w: W) -> Bold<W> {
Bold(on, w)
}
pub(crate) fn border <W: Render<Tui>, S: BorderStyle> (style: S, w: W) -> Bordered<S, W> {
Bordered(style, w)
}
}
pub trait TuiStyle: Render<Tui> + Sized {
fn fg (self, color: Color) -> impl Render<Tui> {
Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
}
fn bg (self, color: Color) -> impl Render<Tui> {
Layers::new(move |add|{ add(&Background(color))?; add(&self) })
}
fn bold (self, on: bool) -> impl Render<Tui> {
Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
}
fn border (self, style: impl BorderStyle) -> impl Render<Tui> {
Bordered(style, self)
}
}
pub struct Bold<W: Render<Tui>>(pub bool, W);
impl<W: Render<Tui>> TuiStyle for W {}
pub struct Bold(pub bool);
impl Render<Tui> for Bold {
impl<W: Render<Tui>> Render<Tui> for Bold<W> {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_bold(to.area(), self.0)) }
}
pub struct Foreground(pub Color);
pub struct Foreground<W: Render<Tui>>(pub Color, W);
impl Render<Tui> for Foreground {
impl<W: Render<Tui>> Render<Tui> for Foreground<W> {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_fg(to.area(), self.0)) }
}
pub struct Background(pub Color);
pub struct Background<W: Render<Tui>>(pub Color, W);
impl Render<Tui> for Background {
impl<W: Render<Tui>> Render<Tui> for Background<W> {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_bg(to.area(), self.0)) }
}
pub struct Bordered<S: BorderStyle, W: Render<Tui>>(pub S, pub W);
render!(|self: Bordered<S: BorderStyle, W: Render<Tui>>|{
Tui::fill_xy(Tui::under(Tui::inset_xy(1, 1, widget(&self.1)), Border(self.0)))
});
pub struct Border<S: BorderStyle>(pub S);
impl<S: BorderStyle> Render<Tui> for Border<S> {
@ -78,12 +68,46 @@ impl<S: BorderStyle> Render<Tui> for Border<S> {
}
}
pub struct Bordered<S: BorderStyle, W: Render<Tui>>(pub S, pub W);
pub struct TuiTheme;
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;
lay! { content.inset_xy(1, 1), Border(self.0) }.fill_xy()
impl TuiTheme {
pub fn border_bg () -> Color {
Color::Rgb(40, 50, 30)
}
pub fn border_fg (focused: bool) -> Color {
if focused { Color::Rgb(100, 110, 40) } else { Color::Rgb(70, 80, 50) }
}
pub fn title_fg (focused: bool) -> Color {
if focused { Color::Rgb(150, 160, 90) } else { Color::Rgb(120, 130, 100) }
}
pub fn separator_fg (_: bool) -> Color {
Color::Rgb(0, 0, 0)
}
pub const fn hotkey_fg () -> Color {
Color::Rgb(255, 255, 0)
}
pub fn mode_bg () -> Color {
Color::Rgb(150, 160, 90)
}
pub fn mode_fg () -> Color {
Color::Rgb(255, 255, 255)
}
pub fn status_bar_bg () -> Color {
Color::Rgb(28, 35, 25)
}
}
pub struct Styled<T: Render<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]))
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
// FIXME
let [x, y, ..] = to.area();
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
Ok(to.blit(&self.1, x, y, None))
}
}
@ -256,3 +280,30 @@ pub const CORNERS: CornersTall = CornersTall(Style {
add_modifier: Modifier::empty(),
sub_modifier: Modifier::DIM
});
//pub trait TuiStyle: Render<Tui> + Sized {
//fn fg (self, color: Color) -> impl Render<Tui> {
//Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
//}
//fn bg (self, color: Color) -> impl Render<Tui> {
//Layers::new(move |add|{ add(&Background(color))?; add(&self) })
//}
//fn bold (self, on: bool) -> impl Render<Tui> {
//Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
//}
//fn border <S: BorderStyle> (self, style: S) -> impl Render<Tui> {
//Bordered(style, self)
//}
//}
//impl<W: Render<Tui>> TuiStyle for W {}
//impl<S: BorderStyle> Render<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;
//lay! { content.inset_xy(1, 1), Border(self.0) }.fill_xy()
//}
//}

View file

@ -1,14 +1,13 @@
use crate::*;
/// Layout for standalone arranger app.
impl Content<Tui> for ArrangerTui {
fn content (&self) -> impl Render<Tui> {
// Layout for standalone arranger app.
render!(|self: ArrangerTui|{
let arranger_focused = self.arranger_focused();
Split::down(
1,
Tui::to_south(
TransportView::from(self),
Split::down(
Tui::to_south(
self.splits[0],
Tui::to_south(
lay!(
Layers::new(move |add|{
match self.mode {
@ -39,8 +38,8 @@ impl Content<Tui> for ArrangerTui {
)
)
)
}
}
)
});
/// Display mode of arranger
#[derive(Clone, PartialEq)]
@ -101,10 +100,11 @@ pub fn arranger_content_vertical (
let sep_fg = TuiTheme::separator_fg(false);
let header_h = 3u16;//5u16;
let scenes_w = 3 + ArrangerScene::longest_name(scenes) as u16; // x of 1st track
let arrangement = Layers::new(move |add|{
let rows: &[(usize, usize)] = rows.as_ref();
let cols: &[(usize, usize)] = cols.as_ref();
let any_size = |_|Ok(Some([0,0]));
let arrangement = Layers::new(move |add|{
// column separators
add(&Widget::new(any_size, move|to: &mut TuiOutput|{
let style = Some(Style::default().fg(sep_fg));
@ -113,6 +113,7 @@ pub fn arranger_content_vertical (
for y in to.area().y()..to.area().y2() { to.blit(&"", x, y, style); }
})
}))?;
// row separators
add(&Widget::new(any_size, move|to: &mut TuiOutput|{
Ok(for y in rows.iter().map(|row|row.1) {
@ -127,13 +128,14 @@ pub fn arranger_content_vertical (
}
})
}))?;
// track titles
let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{
let header = Tui::reduce(tracks.iter().zip(cols.iter().map(|col|col.0)), |prev, (track, w)|{
// name and width of track
let name = track.name().read().unwrap();
let max_w = w.saturating_sub(1).min(name.len()).max(2);
let name = format!("{}", &name[0..max_w]);
let name = TuiStyle::bold(name, true);
let name = Tui::bold(true, name);
// beats elapsed
let elapsed = if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
let length = phrase.read().unwrap().length;
@ -166,26 +168,30 @@ pub fn arranger_content_vertical (
.map(|port|port.short_name())
.transpose()?
.unwrap_or("(none)".into()));
col!(name, /*input, output,*/ until_next, elapsed)
.min_xy(w as u16, header_h)
.bg(track.color().rgb)
.push_x(scenes_w)
let current =
Tui::push_x(scenes_w,
Tui::bg(track.color().rgb,
Tui::min_xy(w as u16, header_h,
Tui::to_south(name, Tui::to_south(until_next, elapsed)))));
Tui::to_east(prev, current)
});
// tracks and scenes
let content = col!(
// scenes:
(scene, pulses) in scenes.iter().zip(rows.iter().map(|row|row.0)) => {
let content = Tui::fixed_y((view.size.h() as u16).saturating_sub(header_h), Tui::reduce(
scenes.iter().zip(rows.iter().map(|row|row.0)),
|(scene, pulses)| {
let height = 1.max((pulses / PPQ) as u16);
let playing = scene.is_playing(tracks);
Stack::right(move |add| {
// scene title:
add(&row!(
Tui::fixed_y(
height,
Tui::to_east(
Tui::to_east(
if playing { "" } else { " " },
TuiStyle::bold(scene.name.read().unwrap().as_str(), true),
).fixed_xy(scenes_w, height).bg(scene.color.rgb))?;
// clip per track:
Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() {
add(&Layers::new(move |add|{
Tui::bold(true, scene.name.read().unwrap().as_str())
),
Tui::iter(
cols.iter().map(|col|col.0).enumerate(),
|(track, w)|Tui::fixed_xy(w as u16, height, Layers::new(move |add|{
let mut bg = clip_bg;
match (tracks.get(track), scene.clips.get(track)) {
(Some(track), Some(Some(phrase))) => {
@ -204,13 +210,15 @@ pub fn arranger_content_vertical (
_ => {}
};
add(&Background(bg))
}).fixed_xy(w as u16, height))?;
})
}).fixed_y(height)
}))
)
)
)
}
).fixed_y((view.size.h() as u16).saturating_sub(header_h));
));
// full grid with header and footer
add(&col!(header, content))?;
add(&Tui::to_south(header, content))?;
// cursor
add(&Widget::new(any_size, move|to: &mut TuiOutput|{
let area = to.area();
@ -269,199 +277,200 @@ pub fn arranger_content_vertical (
}).bg(bg.rgb);
let color = TuiTheme::title_fg(view.arranger_focused());
let size = format!("{}x{}", view.size.w(), view.size.h());
let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy();
let lower_right = Tui::at_se(Tui::fill_xy(Tui::pull_x(1, Tui::fg(color, size))));
lay!(arrangement, lower_right)
}
pub fn arranger_content_horizontal (
view: &ArrangerTui,
) -> impl Render<Tui> + use<'_> {
let focused = view.arranger_focused();
let _tracks = view.tracks();
lay!(
focused.then_some(Background(TuiTheme::border_bg())),
row!(
// name
Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks, selected) = self;
//let yellow = Some(Style::default().yellow().bold().not_dim());
//let white = Some(Style::default().white().bold().not_dim());
//let area = to.area();
//let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
//let offset = 0; // track scroll offset
//for y in 0..area.h() {
//if y == 0 {
//to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2 + offset;
//if let Some(track) = tracks.get(index) {
//let selected = selected.track() == Some(index);
//let style = if selected { yellow } else { white };
//to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
//to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
//}
//}
//}
//Ok(Some(area))
}),
// monitor
Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks) = self;
//let mut area = to.area();
//let on = Some(Style::default().not_dim().green().bold());
//let off = Some(DIM);
//area.x += 1;
//for y in 0..area.h() {
//if y == 0 {
////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(track) = tracks.get(index) {
//let style = if track.monitoring { on } else { off };
//to.blit(&" MON ", area.x(), area.y() + y, style)?;
//let focused = view.arranger_focused();
//let _tracks = view.tracks();
//lay!(
//focused.then_some(Background(TuiTheme::border_bg())),
//row!(
//// name
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//todo!()
////let Self(tracks, selected) = self;
////let yellow = Some(Style::default().yellow().bold().not_dim());
////let white = Some(Style::default().white().bold().not_dim());
////let area = to.area();
////let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
////let offset = 0; // track scroll offset
////for y in 0..area.h() {
////if y == 0 {
////to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
////} else if y % 2 == 0 {
////let index = (y as usize - 2) / 2 + offset;
////if let Some(track) = tracks.get(index) {
////let selected = selected.track() == Some(index);
////let style = if selected { yellow } else { white };
////to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
////to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
////}
////}
////}
////Ok(Some(area))
//}),
//// monitor
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//todo!()
////let Self(tracks) = self;
////let mut area = to.area();
////let on = Some(Style::default().not_dim().green().bold());
////let off = Some(DIM);
////area.x += 1;
////for y in 0..area.h() {
////if y == 0 {
//////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
////} else if y % 2 == 0 {
////let index = (y as usize - 2) / 2;
////if let Some(track) = tracks.get(index) {
////let style = if track.monitoring { on } else { off };
////to.blit(&" MON ", area.x(), area.y() + y, style)?;
////} else {
////area.height = y;
////break
////}
////}
////}
////area.width = 4;
////Ok(Some(area))
//}),
//// record
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//todo!()
////let Self(tracks) = self;
////let mut area = to.area();
////let on = Some(Style::default().not_dim().red().bold());
////let off = Some(Style::default().dim());
////area.x += 1;
////for y in 0..area.h() {
////if y == 0 {
//////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
////} else if y % 2 == 0 {
////let index = (y as usize - 2) / 2;
////if let Some(track) = tracks.get(index) {
////let style = if track.recording { on } else { off };
////to.blit(&" REC ", area.x(), area.y() + y, style)?;
////} else {
////area.height = y;
////break
////}
////}
////}
////area.width = 4;
////Ok(Some(area))
//}),
//// overdub
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//todo!()
////let Self(tracks) = self;
////let mut area = to.area();
////let on = Some(Style::default().not_dim().yellow().bold());
////let off = Some(Style::default().dim());
////area.x = area.x + 1;
////for y in 0..area.h() {
////if y == 0 {
//////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
////} else if y % 2 == 0 {
////let index = (y as usize - 2) / 2;
////if let Some(track) = tracks.get(index) {
////to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
////on
////} else {
////off
////})?;
////} else {
////area.height = y;
////break
////}
////}
////}
////area.width = 4;
////Ok(Some(area))
//}),
//// erase
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//todo!()
////let Self(tracks) = self;
////let mut area = to.area();
////let off = Some(Style::default().dim());
////area.x = area.x + 1;
////for y in 0..area.h() {
////if y == 0 {
//////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
////} else if y % 2 == 0 {
////let index = (y as usize - 2) / 2;
////if let Some(_) = tracks.get(index) {
////to.blit(&" DEL ", area.x(), area.y() + y, off)?;
////} else {
////area.height = y;
////break
////}
////}
////}
////area.width = 4;
////Ok(Some(area))
//}),
//// gain
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//todo!()
////let Self(tracks) = self;
////let mut area = to.area();
////let off = Some(Style::default().dim());
////area.x = area.x() + 1;
////for y in 0..area.h() {
////if y == 0 {
//////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
////} else if y % 2 == 0 {
////let index = (y as usize - 2) / 2;
////if let Some(_) = tracks.get(index) {
////to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
////} else {
////area.height = y;
////break
////}
////}
////}
////area.width = 7;
////Ok(Some(area))
//}),
//// scenes
//Widget::new(|_|{todo!()}, |to: &mut TuiOutput|{
//let [x, y, _, height] = to.area();
//let mut x2 = 0;
//Ok(for (scene_index, scene) in view.scenes().iter().enumerate() {
//let active_scene = view.selected.scene() == Some(scene_index);
//let sep = Some(if active_scene {
//Style::default().yellow().not_dim()
//} else {
//area.height = y;
//break
//Style::default().dim()
//});
//for y in y+1..y+height {
//to.blit(&"│", x + x2, y, sep);
//}
//}
//}
//area.width = 4;
//Ok(Some(area))
}),
// record
Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks) = self;
//let mut area = to.area();
//let on = Some(Style::default().not_dim().red().bold());
//let off = Some(Style::default().dim());
//area.x += 1;
//for y in 0..area.h() {
//if y == 0 {
////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(track) = tracks.get(index) {
//let style = if track.recording { on } else { off };
//to.blit(&" REC ", area.x(), area.y() + y, style)?;
//let name = scene.name.read().unwrap();
//let mut x3 = name.len() as u16;
//to.blit(&*name, x + x2, y, sep);
//for (i, clip) in scene.clips.iter().enumerate() {
//let active_track = view.selected.track() == Some(i);
//if let Some(clip) = clip {
//let y2 = y + 2 + i as u16 * 2;
//let label = format!("{}", clip.read().unwrap().name);
//to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
//Style::default().not_dim().yellow().bold()
//} else {
//area.height = y;
//break
//Style::default().not_dim()
//}));
//x3 = x3.max(label.len() as u16)
//}
//}
//}
//area.width = 4;
//Ok(Some(area))
}),
// overdub
Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks) = self;
//let mut area = to.area();
//let on = Some(Style::default().not_dim().yellow().bold());
//let off = Some(Style::default().dim());
//area.x = area.x + 1;
//for y in 0..area.h() {
//if y == 0 {
////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(track) = tracks.get(index) {
//to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
//on
//} else {
//off
//})?;
//} else {
//area.height = y;
//break
//}
//}
//}
//area.width = 4;
//Ok(Some(area))
}),
// erase
Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks) = self;
//let mut area = to.area();
//let off = Some(Style::default().dim());
//area.x = area.x + 1;
//for y in 0..area.h() {
//if y == 0 {
////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(_) = tracks.get(index) {
//to.blit(&" DEL ", area.x(), area.y() + y, off)?;
//} else {
//area.height = y;
//break
//}
//}
//}
//area.width = 4;
//Ok(Some(area))
}),
// gain
Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks) = self;
//let mut area = to.area();
//let off = Some(Style::default().dim());
//area.x = area.x() + 1;
//for y in 0..area.h() {
//if y == 0 {
////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(_) = tracks.get(index) {
//to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
//} else {
//area.height = y;
//break
//}
//}
//}
//area.width = 7;
//Ok(Some(area))
}),
// scenes
Widget::new(|_|{todo!()}, |to: &mut TuiOutput|{
let [x, y, _, height] = to.area();
let mut x2 = 0;
Ok(for (scene_index, scene) in view.scenes().iter().enumerate() {
let active_scene = view.selected.scene() == Some(scene_index);
let sep = Some(if active_scene {
Style::default().yellow().not_dim()
} else {
Style::default().dim()
});
for y in y+1..y+height {
to.blit(&"", x + x2, y, sep);
}
let name = scene.name.read().unwrap();
let mut x3 = name.len() as u16;
to.blit(&*name, x + x2, y, sep);
for (i, clip) in scene.clips.iter().enumerate() {
let active_track = view.selected.track() == Some(i);
if let Some(clip) = clip {
let y2 = y + 2 + i as u16 * 2;
let label = format!("{}", clip.read().unwrap().name);
to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
Style::default().not_dim().yellow().bold()
} else {
Style::default().not_dim()
}));
x3 = x3.max(label.len() as u16)
}
}
x2 = x2 + x3 + 1;
})
}),
)
)
//x2 = x2 + x3 + 1;
//})
//}),
//)
//)
}

View file

@ -125,23 +125,21 @@ impl<'a> Content<Tui> for PhraseView<'a> {
////}
//Ok(())
//};
lay!(
row!(
Widget::new(|to:[u16;2]|Ok(Some(to.clip_w(2))), keys).fill_y().push_y(1),
lay!(
Widget::new(|to|Ok(Some(to)), notes).fill_x().push_y(1),
Widget::new(|to|Ok(Some(to)), cursor).push_y(1)
).fill_x()
)
.fill_x()
.bg(Color::Rgb(40, 50, 30)),
//Widget::new(|to:[u16;2]|Ok(Some(to.clip_h(1))), playhead).push_x(6).align_sw(),
TuiStyle::fg(upper_left.to_string(), title_color).align_nw(),
TuiStyle::fg(lower_left.to_string(), title_color).align_sw(),
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
TuiStyle::fg(lower_right.to_string(), title_color).pull_x(1).align_se().fill_xy(),
)
Tui::layers()
.add(Tui::layers()
.add(Tui::at_nw(Tui::fg(title_color, upper_left)))
.add(Tui::at_sw(Tui::fg(title_color, lower_left)))
.add(Tui::fill_xy(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right)))))
.add(Tui::fill_xy(Tui::at_se(Tui::pull_x(1, Tui::fg(title_color, lower_right))))))
.add(Tui::bg(
Color::Rgb(40, 50, 30),
Tui::fill_x(Tui::to_east(
Tui::push_y(1, Tui::fill_y(Widget::new(|to:[u16;2]|Ok(Some(to.clip_w(2))), keys))),
Tui::fill_x(lay!(
Tui::push_y(1, Tui::fill_x(Widget::new(|to|Ok(Some(to)), notes))),
Tui::push_y(1, Widget::new(|to|Ok(Some(to)), cursor))
)),
))))
}
}

View file

@ -1,34 +1,45 @@
use crate::*;
impl Content<Tui> for PhraseLength {
fn content (&self) -> impl Render<Tui> {
Layers::new(move|add|{
match self.focus {
None => add(&row!(
" ", self.bars_string(),
".", self.beats_string(),
".", self.ticks_string(),
" "
)),
Some(PhraseLengthFocus::Bar) => add(&row!(
"[", self.bars_string(),
"]", self.beats_string(),
".", self.ticks_string(),
" "
)),
Some(PhraseLengthFocus::Beat) => add(&row!(
" ", self.bars_string(),
"[", self.beats_string(),
"]", self.ticks_string(),
" "
)),
Some(PhraseLengthFocus::Tick) => add(&row!(
" ", self.bars_string(),
".", self.beats_string(),
"[", self.ticks_string(),
"]"
)),
}
})
}
}
render!(|self:PhraseLength|{
let bars = self.bars_string().as_str();
let beats = self.beats_string().as_str();
let ticks = self.ticks_string().as_str();
Tui::reduce(match self.focus {
None =>
[" ", bars, "B", beats, "b", ticks, "T"],
Some(PhraseLengthFocus::Bar) =>
["[", bars, "]", beats, "b", ticks, "T"],
Some(PhraseLengthFocus::Beat) =>
[" ", bars, "[", beats, "]", ticks, "T"],
Some(PhraseLengthFocus::Tick) =>
[" ", bars, "B", beats, "[", ticks, "]"],
}.iter(), Tui::to_east)
//Layers::new(move|add|{
//match self.focus {
//None => add(&row!(
//" ", self.bars_string(),
//".", self.beats_string(),
//".", self.ticks_string(),
//" "
//)),
//Some(PhraseLengthFocus::Bar) => add(&row!(
//"[", self.bars_string(),
//"]", self.beats_string(),
//".", self.ticks_string(),
//" "
//)),
//Some(PhraseLengthFocus::Beat) => add(&row!(
//" ", self.bars_string(),
//"[", self.beats_string(),
//"]", self.ticks_string(),
//" "
//)),
//Some(PhraseLengthFocus::Tick) => add(&row!(
//" ", self.bars_string(),
//".", self.beats_string(),
//"[", self.ticks_string(),
//"]"
//)),
//}
//})
});

View file

@ -23,8 +23,7 @@ impl<'a, T: HasPhraseList> From<&'a T> for PhraseListView<'a> {
}
// TODO: Display phrases always in order of appearance
impl<'a> Content<Tui> for PhraseListView<'a> {
fn content (&self) -> impl Render<Tui> {
render!(|self: PhraseListView<'a>|{
let Self { title, focused, entered, phrases, index, mode } = self;
let content = Stack::down(move|add|match mode {
Some(PhrasesMode::Import(_, ref browser)) => {
@ -53,7 +52,7 @@ impl<'a> Content<Tui> for PhraseListView<'a> {
}
};
let row2 = TuiStyle::bold(row2, true);
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?;
add(&Tui::bg(color.base.rgb, Tui::fill_x(Tui::to_south(row1, row2))))?;
if *entered && i == *index {
add(&CORNERS)?;
}
@ -74,5 +73,4 @@ impl<'a> Content<Tui> for PhraseListView<'a> {
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
)
}
}
});

View file

@ -46,7 +46,7 @@ impl<'a> Content<Tui> for PhraseSelector<'a> {
let row1 = lay!(format!(" ").align_w().fill_x(), length).fill_x();
let row2 = format!(" {name}");
let row2 = TuiStyle::bold(row2, true);
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?;
add(&Tui::bg(color.base.rgb, Tui::fill_x(Tui::to_south(row1, row2))))?;
}
Ok(())
});

View file

@ -1,8 +1,7 @@
//]))
use crate::*;
impl Content<Tui> for SequencerTui {
fn content (&self) -> impl Render<Tui> {
render!(|self: SequencerTui|{
let play = PhraseSelector::play_phrase(
&self.player,
self.focused() == SequencerFocus::PhrasePlay,
@ -13,47 +12,69 @@ impl Content<Tui> for SequencerTui {
self.focused() == SequencerFocus::PhraseNext,
self.entered()
);
Stack::Up(
Tui::to_north(
SequencerStatusBar::from(self),
Stack::Down(
Tui::to_south(
TransportView::from(self),
Min::Y(20, Split::Right(20,
Stack::Down(
play.fixed_y(4),
Stack::Down(
next.fixed_y(4),
Tui::min_y(
20,
Tui::split_right(
20,
Tui::to_south(
Tui::fixed_y(4, play),
Tui::to_south(
Tui::fixed_y(4, next),
PhraseListView::from(self)
)
),
PhraseView::from(self),
))
),
PhraseView::from(self)
)
}
}
)
)
)
});
impl Content<Tui> for SequencerStatusBar {
fn content (&self) -> impl Render<Tui> {
render!(|self: SequencerStatusBar|{
let orange = Color::Rgb(255,128,0);
let yellow = Color::Rgb(255,255,0);
let light = Color::Rgb(100,100,100);
let dark = Color::Rgb(100,100,100);
let black = Color::Rgb(0,0,0);
let modeline =
//Stack::Right(
widget(&self.mode).bg(orange).fg(black).bold(true)
//row!((prefix, hotkey, suffix) in self.help => {
//row!(" ", *prefix, TuiStyle::fg(*hotkey, yellow), *suffix)
//})
//)
.bg(Color::Rgb(100,100,100));
let statusbar =
Stack::Right(
widget(&self.cpu).fg(orange),
Fill::X(Align::SE(Both(
Background(Color::Rgb(50,50,50)),
Stack::Right(
widget(&self.res).fg(orange),
widget(&self.size).fg(orange)
)))));
let modeline = Tui::to_east(
Tui::bg(
orange,
Tui::fg(
black,
Tui::bold(
true,
Tui::text(self.mode)))),
Tui::bg(
light,
Tui::reduce(
|(prefix, hotkey, suffix)|Tui::reduce(
Tui::to_east,
[
widget(&" "),
widget(*prefix),
widget(TuiStyle::fg(*hotkey, yellow)),
widget(*suffix)
]
),
self.help
)
)
);
let statusbar = Tui::bg(
dark,
Tui::reduce(
Tui::to_east,
[
Tui::fg(orange, widget(&self.cpu)),
Tui::fg(orange, widget(&self.res)),
Tui::fg(orange, widget(&self.size)),
]
)
);
if self.width > 60 {
return Stack::Right(modeline, statusbar);
}
@ -61,5 +82,4 @@ impl Content<Tui> for SequencerStatusBar {
return Stack::Down(modeline, statusbar);
}
return Stack::None
}
}
});

View file

@ -0,0 +1,25 @@
use crate::*;
pub trait StatusBar: Render<Tui> {
type State: Send + Sync;
fn hotkey_fg () -> Color where Self: Sized;
fn update (&mut self, state: &Self::State) where Self: Sized;
fn command (commands: &[[impl Render<Tui>;3]])
-> impl Render<Tui> + '_
where
Self: Sized
{
let hotkey_fg = Self::hotkey_fg();
Tui::reduce(commands.iter(), |prev, [a, b, c]|
Tui::to_east(prev,
Tui::to_east(a,
Tui::to_east(Tui::fg(hotkey_fg, Tui::bold(true, b)),
c))))
}
fn with <'a> (state: &'a Self::State, content: impl Render<Tui>) -> impl Render<Tui>
where Self: Sized, &'a Self::State: Into<Self>
{
Tui::to_north(state.into(), content)
}
}

View file

@ -20,75 +20,53 @@ pub struct TransportView {
pub(crate) msu: String,
}
impl Content<Tui> for TransportView {
fn content (&self) -> impl Render<Tui> {
render!(|self: TransportView|{
let Self { state, selected, focused, bpm, sync, quant, beat, msu, } = self;
Stack::down(move|add|{
add(&row!("│World ", row!(
format!("│0 (0)"), //sample(chunk)
format!("│00m00s000u"), //msu
format!("│00B 0b 00/00"), //bbt
)))?;
match *state {
Some(TransportState::Rolling) => {
add(&row!(
"",
TuiStyle::fg("▶ PLAYING", Color::Rgb(0, 255, 0)),
format!("│0 (0)"),
format!("│00m00s000u"),
format!("│00B 0b 00/00")
))?;
add(&row!("│Now ", row!(
format!("│0 (0)"), //sample(chunk)
format!("│00m00s000u"), //msu
format!("│00B 0b 00/00"), //bbt
)))?;
},
_ => {
add(&row!("", TuiStyle::fg("⏹ STOPPED", Color::Rgb(255, 128, 0))))?;
add(&"")?;
}
}
Ok(())
}).fill_x().bg(Color::Rgb(40, 50, 30))
//row!(
////selected.wrap(TransportFocus::PlayPause, &play_pause.fixed_xy(10, 3)),
//row!(
//col!(
//Field("SR ", format!("192000")),
//Field("BUF ", format!("1024")),
//Field("LEN ", format!("21300")),
//Field("CPU ", format!("00.0%"))
//),
//col!(
//Field("PUL ", format!("000000000")),
//Field("PPQ ", format!("96")),
//Field("BBT ", format!("00B0b00p"))
//),
//col!(
//Field("SEC ", format!("000000.000")),
//Field("BPM ", format!("000.000")),
//Field("MSU ", format!("00m00s00u"))
//),
//),
//selected.wrap(TransportFocus::Bpm, &Outset::X(1u16, {
//row! {
//"BPM ",
//format!("{}.{:03}", *bpm as usize, (bpm * 1000.0) % 1000.0)
let world = Tui::to_east("│World ", Tui::to_east(format!("│0 (0)"), //sample(chunk)
Tui::to_east(format!("│00m00s000u"), /*msu*/ format!("│00B 0b 00/00"), /*bbt*/)));
let timer = Tui::either(
*state == Some(TransportState::Rolling),
Tui::to_south(
Tui::to_east("",
Tui::to_east(TuiStyle::fg("▶ PLAYING", Color::Rgb(0, 255, 0)),
Tui::to_east("│0 (0)",
Tui::to_east("│00m00s000u", "│00B 0b 00/00")))),
Tui::to_east("│Now ",
Tui::to_east("│0 (0)",
Tui::to_east("│00m00s000u", "│00B 0b 00/00")))
),
Tui::to_south(
Tui::to_east("", Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED")),
""
)
);
Tui::bg(Color::Rgb(40, 50, 30), Tui::fill_x(Tui::to_south(world, timer)))
});
//)?;
//match *state {
//Some(TransportState::Rolling) => {
//add(&row!(
//"│",
//TuiStyle::fg("▶ PLAYING", Color::Rgb(0, 255, 0)),
//format!("│0 (0)"),
//format!("│00m00s000u"),
//format!("│00B 0b 00/00")
//))?;
//add(&row!("│Now ", row!(
//format!("│0 (0)"), //sample(chunk)
//format!("│00m00s000u"), //msu
//format!("│00B 0b 00/00"), //bbt
//)))?;
//},
//_ => {
//add(&row!("│", TuiStyle::fg("⏹ STOPPED", Color::Rgb(255, 128, 0))))?;
//add(&"")?;
//}
//})),
//selected.wrap(TransportFocus::Sync, &Outset::X(1u16, row! {
//"SYNC ", pulses_to_name(*sync as usize)
//})),
//selected.wrap(TransportFocus::Quant, &Outset::X(1u16, row! {
//"QUANT ", pulses_to_name(*quant as usize)
//})),
//selected.wrap(TransportFocus::Clock, &{
//row!("B" , beat.as_str(), " T", msu.as_str()).outset_x(1)
//}).align_e().fill_x(),
//).fill_x().bg(Color::Rgb(40, 50, 30))
}
}
//}
//Ok(())
//}).fill_x().bg(Color::Rgb(40, 50, 30))
//});
impl<'a, T: HasClock> From<&'a T> for TransportView where Option<TransportFocus>: From<&'a T> {
fn from (state: &'a T) -> Self {
@ -132,12 +110,46 @@ impl From<&ArrangerTui> for Option<TransportFocus> {
struct Field(&'static str, String);
impl Content<Tui> for Field {
fn content (&self) -> impl Render<Tui> {
row!(
"",
TuiStyle::bold(self.0, true),
TuiStyle::bg(self.1.as_str(), Color::Rgb(0, 0, 0)),
)
}
}
render!(|self: Field|{
Tui::to_east("", Tui::to_east(
Tui::bold(true, self.0),
Tui::bg(Color::Rgb(0, 0, 0), self.1.as_str()),
))
});
//row!(
////selected.wrap(TransportFocus::PlayPause, &play_pause.fixed_xy(10, 3)),
//row!(
//col!(
//Field("SR ", format!("192000")),
//Field("BUF ", format!("1024")),
//Field("LEN ", format!("21300")),
//Field("CPU ", format!("00.0%"))
//),
//col!(
//Field("PUL ", format!("000000000")),
//Field("PPQ ", format!("96")),
//Field("BBT ", format!("00B0b00p"))
//),
//col!(
//Field("SEC ", format!("000000.000")),
//Field("BPM ", format!("000.000")),
//Field("MSU ", format!("00m00s00u"))
//),
//),
//selected.wrap(TransportFocus::Bpm, &Outset::X(1u16, {
//row! {
//"BPM ",
//format!("{}.{:03}", *bpm as usize, (bpm * 1000.0) % 1000.0)
//}
//})),
//selected.wrap(TransportFocus::Sync, &Outset::X(1u16, row! {
//"SYNC ", pulses_to_name(*sync as usize)
//})),
//selected.wrap(TransportFocus::Quant, &Outset::X(1u16, row! {
//"QUANT ", pulses_to_name(*quant as usize)
//})),
//selected.wrap(TransportFocus::Clock, &{
//row!("B" , beat.as_str(), " T", msu.as_str()).outset_x(1)
//}).align_e().fill_x(),
//).fill_x().bg(Color::Rgb(40, 50, 30))