mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +01:00
wip3 (25e): woohoohoo
This commit is contained in:
parent
3de89bf4fd
commit
36280ce9b7
40 changed files with 1607 additions and 1412 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
1
crates/tek_layout/README.md
Normal file
1
crates/tek_layout/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
manja s grozde i ikebana s chiaroscuro
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
crates/tek_layout/src/cond.rs
Normal file
67
crates/tek_layout/src/cond.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
99
crates/tek_layout/src/inset_outset.rs
Normal file
99
crates/tek_layout/src/inset_outset.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
crates/tek_layout/src/map_reduce.rs
Normal file
34
crates/tek_layout/src/map_reduce.rs
Normal 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
|
||||
);
|
||||
|
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
|
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
85
crates/tek_layout/src/min_max.rs
Normal file
85
crates/tek_layout/src/min_max.rs
Normal 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(()))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
|
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
132
crates/tek_layout/src/push_pull.rs
Normal file
132
crates/tek_layout/src/push_pull.rs
Normal 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(()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
102
crates/tek_layout/src/shrink_grow.rs
Normal file
102
crates/tek_layout/src/shrink_grow.rs
Normal 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(()))
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -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> (
|
||||
|
|
@ -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)))
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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!();
|
||||
""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
66
crates/tek_tui/src/tui_engine_focus.rs
Normal file
66
crates/tek_tui/src/tui_engine_focus.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/tek_tui/src/tui_engine_layout.rs
Normal file
1
crates/tek_tui/src/tui_engine_layout.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
use crate::*;
|
||||
|
|
@ -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())))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
//})
|
||||
//}),
|
||||
//)
|
||||
//)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
)),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
//"]"
|
||||
//)),
|
||||
//}
|
||||
//})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
25
crates/tek_tui/src/tui_view_status_bar.rs
Normal file
25
crates/tek_tui/src/tui_view_status_bar.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue