mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-09-18 13:26:42 +02:00
Compare commits
5 commits
eb028c85fc
...
ac9fd7dfba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac9fd7dfba | ||
|
|
7f9b7091e2 | ||
|
|
083ce8ba76 | ||
|
|
4c25dcc702 | ||
|
|
cc4a428143 |
22 changed files with 2578 additions and 2817 deletions
|
|
@ -37,7 +37,6 @@ impl_keywords!(Tui, XYWH<u16>, State [
|
||||||
kw_split,
|
kw_split,
|
||||||
kw_align,
|
kw_align,
|
||||||
kw_exact,
|
kw_exact,
|
||||||
kw_fixed,
|
|
||||||
kw_min,
|
kw_min,
|
||||||
kw_max,
|
kw_max,
|
||||||
kw_push,
|
kw_push,
|
||||||
|
|
|
||||||
103
src/layout.rs
Normal file
103
src/layout.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<S: Screen, T: Draw<S>> Layout<S> for T {}
|
||||||
|
|
||||||
|
pub trait Layout<S: Screen>: Draw<S> + Sized {
|
||||||
|
fn full_w (self) -> impl Draw<S> {
|
||||||
|
Full::W(self)
|
||||||
|
}
|
||||||
|
fn full_h (self) -> impl Draw<S> {
|
||||||
|
Full::H(self)
|
||||||
|
}
|
||||||
|
fn full_wh (self) -> impl Draw<S> {
|
||||||
|
Full::WH(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn origin (self, azimuth: impl Into<Option<Azimuth>>) -> impl Draw<S> {
|
||||||
|
Origin(azimuth.into(), self)
|
||||||
|
}
|
||||||
|
fn origin_c (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::C), self)
|
||||||
|
}
|
||||||
|
fn origin_x (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::X), self)
|
||||||
|
}
|
||||||
|
fn origin_y (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::Y), self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn origin_n (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::N), self)
|
||||||
|
}
|
||||||
|
fn origin_s (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::S), self)
|
||||||
|
}
|
||||||
|
fn origin_e (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::E), self)
|
||||||
|
}
|
||||||
|
fn origin_w (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::W), self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn origin_ne (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::NE), self)
|
||||||
|
}
|
||||||
|
fn origin_se (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::SE), self)
|
||||||
|
}
|
||||||
|
fn origin_nw (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::NW), self)
|
||||||
|
}
|
||||||
|
fn origin_sw (self) -> impl Draw<S> {
|
||||||
|
Origin(Some(Azimuth::SW), self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uses [AtomicUsize] to measure size during\
|
||||||
|
/// rendering (which is normally read-only).
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Sizer(
|
||||||
|
/// Width
|
||||||
|
pub Arc<AtomicUsize>,
|
||||||
|
/// Height
|
||||||
|
pub Arc<AtomicUsize>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Xy<u16> for Sizer {
|
||||||
|
fn x (&self) -> u16 {
|
||||||
|
self.0.load(Relaxed) as u16
|
||||||
|
}
|
||||||
|
fn y (&self) -> u16 {
|
||||||
|
self.1.load(Relaxed) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Wide<u16> for Sizer {
|
||||||
|
fn w (&self) -> u16 {
|
||||||
|
self.0.load(Relaxed) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Tall<u16> for Sizer {
|
||||||
|
fn h (&self) -> u16 {
|
||||||
|
self.1.load(Relaxed) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialEq for Sizer {
|
||||||
|
fn eq (&self, _: &Self) -> bool { todo!() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sizer {
|
||||||
|
pub const fn of <T: Screen> (&self, of: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
draw(move|to: &mut T|{
|
||||||
|
let area = of.draw(to)?;
|
||||||
|
self.0.store(area.map(|a|a.w()).unwrap_or(T::Unit::zero()).into(), Relaxed);
|
||||||
|
self.1.store(area.map(|a|a.h()).unwrap_or(T::Unit::zero()).into(), Relaxed);
|
||||||
|
Ok(area)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod area; pub use self::area::*;
|
||||||
|
mod axis; pub use self::axis::*;
|
||||||
|
mod azimuth; pub use self::azimuth::*;
|
||||||
|
mod cond; pub use self::cond::*;
|
||||||
|
mod iter; pub use self::iter::*;
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use Azimuth::*;
|
|
||||||
|
|
||||||
pub struct Align<T>(
|
|
||||||
pub(crate) Option<Azimuth>,
|
|
||||||
pub(crate) T,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
|
|
||||||
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
|
|
||||||
Ok(align::<S>(area, self.1.layout(area)?, self.0))
|
|
||||||
}
|
|
||||||
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
|
||||||
Ok(self.layout(to.area())?
|
|
||||||
.map(|area|to.clip(area, |to|self.1.draw(to)))
|
|
||||||
.transpose()?
|
|
||||||
.flatten())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn align <S: Screen> (
|
|
||||||
area0: XYWH<S::Unit>, area: Option<XYWH<S::Unit>>, azimuth: Option<Azimuth>
|
|
||||||
) -> Option<XYWH<S::Unit>> {
|
|
||||||
area.map(|XYWH(x, y, w, h)|{
|
|
||||||
let XYWH(x0, y0, w0, h0) = area0;
|
|
||||||
match azimuth {
|
|
||||||
Some(NW) => XYWH(x0, y0, w, h),
|
|
||||||
Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h),
|
|
||||||
Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h),
|
|
||||||
Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h),
|
|
||||||
Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h),
|
|
||||||
Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h),
|
|
||||||
Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h),
|
|
||||||
Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h),
|
|
||||||
Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h),
|
|
||||||
Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h),
|
|
||||||
Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h),
|
|
||||||
None => XYWH(x, y, w, h)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -16,6 +16,7 @@ pub struct Area<S: Screen, T: Draw<S>>(
|
||||||
pub Option<XYWH<S::Unit>>,
|
pub Option<XYWH<S::Unit>>,
|
||||||
pub T
|
pub T
|
||||||
);
|
);
|
||||||
|
|
||||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Area<S, T>, to: S|{
|
impl_draw!(<S: Screen, T: Draw<S>,>|self: Area<S, T>, to: S|{
|
||||||
to.clip(self.0, |to|self.1.draw(to))
|
to.draw(self.0, &self.1)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
248
src/layout/axis.rs
Normal file
248
src/layout/axis.rs
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
macro_rules! def_layout_modifier {
|
||||||
|
(
|
||||||
|
$Trait:ident ($fn_x:ident $fn_y:ident $fn_xy:ident),
|
||||||
|
$Struct:ident { $X:ident $Y:ident $XY:ident } $kw_name:ident $name:literal
|
||||||
|
|$self:ident, $to:ident| $body:block
|
||||||
|
) => {
|
||||||
|
impl<S: Screen, T: Draw<S>> $Trait<S> for T {}
|
||||||
|
|
||||||
|
pub trait $Trait<S: Screen>: Draw<S> + Sized {
|
||||||
|
fn $fn_x <N: Into<Option<S::Unit>> + Copy> (self, x: N)
|
||||||
|
-> $Struct<S, Self, N> { $Struct::$X(self, x) }
|
||||||
|
fn $fn_y <N: Into<Option<S::Unit>> + Copy> (self, y: N)
|
||||||
|
-> $Struct<S, Self, N> { $Struct::$Y(self, y) }
|
||||||
|
fn $fn_xy <N: Into<Option<S::Unit>> + Copy> (self, x: N, y: N)
|
||||||
|
-> $Struct<S, Self, N> { $Struct::$XY(self, x, y) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum $Struct<S: Screen, I: Draw<S>, X: Into<Option<S::Unit>>> {
|
||||||
|
__(PhantomData<S>), $X(I, X), $Y(I, X), $XY(I, X, X),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for $Struct<S, I, X> {
|
||||||
|
fn draw (&$self, $to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_kw_layout!($kw_name |state, output, expr| {
|
||||||
|
let head = expr.head()?;
|
||||||
|
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||||
|
Ok(matches!(expr.head()?, Some("exact")).then(||{
|
||||||
|
let args = expr.tail();
|
||||||
|
let arg0 = args.head();
|
||||||
|
let tail0 = args.tail();
|
||||||
|
let arg1 = tail0.head();
|
||||||
|
let tail1 = tail0.tail();
|
||||||
|
let arg2 = tail1.head();
|
||||||
|
eval_xy!(
|
||||||
|
$name => expr, head, output, state, frags.next(),
|
||||||
|
$fn_xy, $fn_x, $fn_y,
|
||||||
|
arg0, arg1, arg2,
|
||||||
|
)
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
CanExact (exact_w exact_h exact_wh),
|
||||||
|
Exact { W H WH } kw_exact "exact" |self, to| {
|
||||||
|
let XYWH(x, y, w0, h0) = to.area();
|
||||||
|
let (item, w, h) = match self {
|
||||||
|
Self::W(item, w) => (item, (*w).into().unwrap_or(w0), h0),
|
||||||
|
Self::H(item, h) => (item, w0, (*h).into().unwrap_or(h0)),
|
||||||
|
Self::WH(item, w, h) => (item, (*w).into().unwrap_or(h0), (*h).into().unwrap_or(h0)),
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
to.draw(XYWH(x, y, w, h), item)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
CanMin (min_w min_h min_wh),
|
||||||
|
Min { W H WH } kw_min "min" |self, to| {
|
||||||
|
match self {
|
||||||
|
Self::__(_) => unreachable!(),
|
||||||
|
Self::W(item, w1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
|
||||||
|
let w: S::Unit = (*w1).into().map(|w1|w.max(w1)).unwrap_or(w);
|
||||||
|
to.draw(XYWH(x, y, w, h), item)
|
||||||
|
},
|
||||||
|
Self::H(item, h1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
|
||||||
|
let h: S::Unit = (*h1).into().map(|h1|h.max(h1)).unwrap_or(h);
|
||||||
|
to.draw(XYWH(x, y, w, h), item)
|
||||||
|
},
|
||||||
|
Self::WH(item, w1, h1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
|
||||||
|
let w: S::Unit = (*w1).into().map(|w1|w.max(w1)).unwrap_or(w);
|
||||||
|
let h: S::Unit = (*h1).into().map(|h1|h.max(h1)).unwrap_or(h);
|
||||||
|
to.draw(XYWH(x, y, w, h), item)
|
||||||
|
},
|
||||||
|
_ => Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
CanMax (max_w max_h max_wh),
|
||||||
|
Max { W H WH } kw_max "max" |self, to| {
|
||||||
|
let area: XYWH<S::Unit> = to.area();
|
||||||
|
let (item, area) = match self {
|
||||||
|
Self::W(item, max_w) => (item, XYWH(
|
||||||
|
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||||
|
area.3
|
||||||
|
)),
|
||||||
|
Self::H(item, max_h) => (item, XYWH(
|
||||||
|
area.0, area.1, area.2,
|
||||||
|
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)
|
||||||
|
)),
|
||||||
|
Self::WH(item, max_w, max_h) => (item, XYWH(
|
||||||
|
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||||
|
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3),
|
||||||
|
)),
|
||||||
|
_ => return Ok(None)
|
||||||
|
};
|
||||||
|
to.draw(area, item)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
CanPad (pad_w pad_h pad_wh),
|
||||||
|
Pad { X Y XY } kw_pad "pad" |self, to| {
|
||||||
|
let area = to.area();
|
||||||
|
let (item, area) = match self {
|
||||||
|
Self::X(item, w1) => {
|
||||||
|
let w1: S::Unit = (*w1).into().unwrap_or_default();
|
||||||
|
(item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3))
|
||||||
|
},
|
||||||
|
Self::Y(item, h1) => {
|
||||||
|
let h1: S::Unit = (*h1).into().unwrap_or_default();
|
||||||
|
(item, XYWH(area.0, area.1 + h1, area.2, area.3.minus(h1 + h1)))
|
||||||
|
},
|
||||||
|
Self::XY(item, w1, h1) => {
|
||||||
|
let w1: S::Unit = (*w1).into().unwrap_or_default();
|
||||||
|
let h1: S::Unit = (*h1).into().unwrap_or_default();
|
||||||
|
(item, XYWH(area.0 + w1, area.1 + h1, area.2.minus(w1 + w1), area.3.minus(h1 + h1)))
|
||||||
|
},
|
||||||
|
_ => return Ok(None)
|
||||||
|
};
|
||||||
|
item.draw(to)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
CanPush (push_x push_y push_xy),
|
||||||
|
Push { X Y XY } kw_push "push" |self, to| {
|
||||||
|
match self {
|
||||||
|
Self::__(_) => unreachable!(),
|
||||||
|
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
|
||||||
|
to.draw(XYWH(x + (*x1).into().unwrap_or_default(), y, w, h), item)
|
||||||
|
},
|
||||||
|
Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
|
||||||
|
to.draw(XYWH(x, y + (*y1).into().unwrap_or_default(), w, h), item)
|
||||||
|
},
|
||||||
|
Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
|
||||||
|
to.draw(XYWH(x + (*x1).into().unwrap_or_default(), y + (*y1).into().unwrap_or_default(), w, h), item)
|
||||||
|
},
|
||||||
|
_ => Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
CanPull (pull_x pull_y pull_xy),
|
||||||
|
Pull { X Y XY } kw_pull "pull" |self, to| {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Use whole drawing area along one or both axes.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn doctest_layout_full () -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// use tengri::{Layout, Draw, XYWH, Tui, Perhaps, Screen};
|
||||||
|
/// let area = XYWH(0u16, 0, 80, 25);
|
||||||
|
/// assert_eq!(layout("1")?, Some(XYWH(0u16, 0, 1, 1)));
|
||||||
|
/// assert_eq!(layout("1".full_w())?, Some(XYWH(0u16, 0, 80, 1)));
|
||||||
|
/// assert_eq!(layout("1".full_h())?, Some(XYWH(0u16, 0, 1, 25)));
|
||||||
|
/// assert_eq!(layout("1".full_wh())?, Some(XYWH(0u16, 0, 80, 25)));
|
||||||
|
/// fn layout (t: impl Draw<Tui>) -> Perhaps<XYWH<u16>> {
|
||||||
|
/// Tui::Layout(XYWH(0u16, 0, 80, 25)).size(None, t)
|
||||||
|
/// }
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
pub enum Full<T: Screen, I: Draw<T>> {
|
||||||
|
__(PhantomData<T>),
|
||||||
|
W(I),
|
||||||
|
H(I),
|
||||||
|
WH(I),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
|
||||||
|
let XYWH(x0, y0, w0, h0) = to.area();
|
||||||
|
match self {
|
||||||
|
Self::W(item) => if let Some(XYWH(_, y, _, h)) = to.size(None, item)? {
|
||||||
|
to.draw(XYWH(x0, y, w0, h), item)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
Self::H(item) => if let Some(XYWH(x, _, w, _)) = to.size(None, item)? {
|
||||||
|
to.draw(XYWH(x, y0, w, h0), item)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
Self::WH(item) => if let Some(XYWH(..)) = to.size(None, item)? {
|
||||||
|
to.draw(XYWH(x0, y0, w0, h0), item)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Only draw content if area is above a certain size.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn doctest_layout_min () -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// use tengri::{Layout, Draw, XYWH};
|
||||||
|
/// let area = XYWH(1u16, 1, 80, 25);
|
||||||
|
/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
|
||||||
|
/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5)));
|
||||||
|
/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5)));
|
||||||
|
/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1)));
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
/// Move content in the negative direction of one or both axes.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn doctest_layout_pull () -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// use tengri::{Layout, Draw, XYWH};
|
||||||
|
/// let area = XYWH(1u16, 1, 80, 25);
|
||||||
|
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||||
|
/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
||||||
|
/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
||||||
|
/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
/// Move content in the positive direction of one or both axes.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn doctest_layout_push () -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// use tengri::{Layout, Draw, XYWH};
|
||||||
|
/// let area = XYWH(0u16, 0, 80, 25);
|
||||||
|
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||||
|
/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
||||||
|
/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
||||||
|
/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
#[cfg(test)] #[test] fn test_exact () -> Usually<()> {
|
||||||
|
let mut screen = Tui::Layout(XYWH(0, 0, 80, 25));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".exact_w(3).draw(&mut screen)?, Some(XYWH(0, 0, 3, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".exact_h(1).draw(&mut screen)?, Some(XYWH(0, 0, 6, 1)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".exact_wh(2, 1).draw(&mut screen)?, Some(XYWH(0, 0, 2, 1)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
455
src/layout/azimuth.rs
Normal file
455
src/layout/azimuth.rs
Normal file
|
|
@ -0,0 +1,455 @@
|
||||||
|
use crate::*;
|
||||||
|
use Azimuth::*;
|
||||||
|
|
||||||
|
/// Where is [0, 0] located?
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tengri::*;
|
||||||
|
/// use Azimuth::*;
|
||||||
|
/// let _ = "".align(NW);
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(test, derive(Arbitrary))]
|
||||||
|
#[derive(Debug, Copy, Clone, Default)] pub enum Azimuth {
|
||||||
|
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Origin<T>(
|
||||||
|
pub(crate) Option<Azimuth>,
|
||||||
|
pub(crate) T
|
||||||
|
);
|
||||||
|
|
||||||
|
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, _to: S|{
|
||||||
|
todo!()
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Something that has `[0, 0]` at a particular point.
|
||||||
|
pub trait HasOrigin {
|
||||||
|
fn origin (&self) -> Azimuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<Azimuth>> HasOrigin for T {
|
||||||
|
fn origin (&self) -> Azimuth {
|
||||||
|
*self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_kw_layout!(kw_align |state, output, expr| {
|
||||||
|
Ok(matches!(expr.head()?, Some("align")).then(||{
|
||||||
|
draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align(
|
||||||
|
eval_enum!("align", output, state,
|
||||||
|
expr.head().src()?.unwrap_or_default().split("/").skip(1).next(),
|
||||||
|
expr.tail().head(),
|
||||||
|
Azimuth {
|
||||||
|
"c" => C, "x" => X, "y" => Y,
|
||||||
|
"n" => N, "s" => S, "e" => E, "w" => W,
|
||||||
|
"nw" => NW, "sw" => SW, "ne" => NE, "se" => SE,
|
||||||
|
})
|
||||||
|
).draw(output)
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
|
||||||
|
impl<S: Screen, T: Draw<S>> CanAlign<S> for T {}
|
||||||
|
|
||||||
|
pub trait CanAlign<S: Screen>: Draw<S> + Sized {
|
||||||
|
fn align (self, azimuth: impl Into<Option<Azimuth>>) -> Align<Self> {
|
||||||
|
Align(azimuth.into(), self)
|
||||||
|
}
|
||||||
|
fn align_c (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::C), self)
|
||||||
|
}
|
||||||
|
fn align_x (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::X), self)
|
||||||
|
}
|
||||||
|
fn align_y (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::Y), self)
|
||||||
|
}
|
||||||
|
fn align_n (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::N), self)
|
||||||
|
}
|
||||||
|
fn align_s (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::S), self)
|
||||||
|
}
|
||||||
|
fn align_e (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::E), self)
|
||||||
|
}
|
||||||
|
fn align_w (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::W), self)
|
||||||
|
}
|
||||||
|
fn align_ne (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::NE), self)
|
||||||
|
}
|
||||||
|
fn align_se (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::SE), self)
|
||||||
|
}
|
||||||
|
fn align_nw (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::NW), self)
|
||||||
|
}
|
||||||
|
fn align_sw (self) -> Align<Self> {
|
||||||
|
Align(Some(Azimuth::SW), self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Align<T>(
|
||||||
|
pub(crate) Option<Azimuth>,
|
||||||
|
pub(crate) T,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
|
||||||
|
fn draw (&self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
||||||
|
let area = to.area();
|
||||||
|
Ok(if let Some(area) = align::<S>(area, to.size(area, &self.1)?, self.0) {
|
||||||
|
to.draw(area, &self.1)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align <S: Screen> (
|
||||||
|
area0: XYWH<S::Unit>, area: Option<XYWH<S::Unit>>, azimuth: Option<Azimuth>
|
||||||
|
) -> Option<XYWH<S::Unit>> {
|
||||||
|
area.map(|XYWH(x, y, w, h)|{
|
||||||
|
let XYWH(x0, y0, w0, h0) = area0;
|
||||||
|
match azimuth {
|
||||||
|
Some(NW) => XYWH(x0, y0, w, h),
|
||||||
|
Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h),
|
||||||
|
Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h),
|
||||||
|
Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h),
|
||||||
|
Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h),
|
||||||
|
Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h),
|
||||||
|
Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h),
|
||||||
|
Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h),
|
||||||
|
Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h),
|
||||||
|
Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h),
|
||||||
|
Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h),
|
||||||
|
None => XYWH(x, y, w, h)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_kw_layout!(kw_split |state, output, expr| {
|
||||||
|
let head = expr.head();
|
||||||
|
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||||
|
Ok(matches!(frags.next(), Some("bsp")).then(||{
|
||||||
|
eval_enum!("bsp", output, state, frags.next(), expr.tail().head()?, Split {
|
||||||
|
"n" => North,
|
||||||
|
"s" => South,
|
||||||
|
"e" => East,
|
||||||
|
"w" => West,
|
||||||
|
"a" => Above,
|
||||||
|
"b" => Below
|
||||||
|
}).stack(
|
||||||
|
draw(move|output: &mut O|{
|
||||||
|
state.interpret(output, &expr.tail().tail().head()?)
|
||||||
|
}),
|
||||||
|
draw(move|output: &mut O|{
|
||||||
|
state.interpret(output, &expr.tail().tail().tail().head()?)
|
||||||
|
}),
|
||||||
|
).draw(output)
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Split along an axis. Direction determines order.
|
||||||
|
#[cfg_attr(test, derive(Arbitrary))]
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
|
||||||
|
North,
|
||||||
|
South,
|
||||||
|
East,
|
||||||
|
West,
|
||||||
|
Above,
|
||||||
|
#[default] Below
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pair<S: Screen, A: Draw<S>, B: Draw<S>>(Split, A, B, PhantomData<S>);
|
||||||
|
|
||||||
|
pub fn split <S: Screen, A: Draw<S>, B: Draw<S>> (
|
||||||
|
split: Split, a: A, b: B
|
||||||
|
) -> Pair<S, A, B> {
|
||||||
|
Pair(split, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Screen, A: Draw<S>, B: Draw<S>> Draw<S> for Pair<S, A, B> {
|
||||||
|
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
|
||||||
|
let Self(split, a, b, ..) = self;
|
||||||
|
let (area_a, area_b) = stack_areas(split, to, a, b)?;
|
||||||
|
let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?;
|
||||||
|
Ok(stack_drawn(split, drawn_a, drawn_b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Split {
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// use tengri::*;
|
||||||
|
/// let _ = Split::Above.stack(&"", &"");
|
||||||
|
/// let _ = Split::Below.stack(&"", &"");
|
||||||
|
/// let _ = Split::North.stack(&"", &"");
|
||||||
|
/// let _ = Split::South.stack(&"", &"");
|
||||||
|
/// let _ = Split::East.stack(&"", &"");
|
||||||
|
/// let _ = Split::West.stack(&"", &"");
|
||||||
|
/// ```
|
||||||
|
pub const fn stack <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
|
||||||
|
Pair(*self, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// use tengri::*;
|
||||||
|
/// let _ = Split::Above.half(&"", &"");
|
||||||
|
/// let _ = Split::Below.half(&"", &"");
|
||||||
|
/// let _ = Split::North.half(&"", &"");
|
||||||
|
/// let _ = Split::South.half(&"", &"");
|
||||||
|
/// let _ = Split::East.half(&"", &"");
|
||||||
|
/// let _ = Split::West.half(&"", &"");
|
||||||
|
/// ```
|
||||||
|
pub const fn half <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: &A, b: &B) -> impl Draw<S> {
|
||||||
|
draw(move|to: &mut S|{
|
||||||
|
let (area_a, area_b) = to.xywh().split_half(self);
|
||||||
|
let (origin_a, origin_b) = self.origins();
|
||||||
|
let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, origin_a, b, area_b, origin_b)?;
|
||||||
|
Ok(stack_drawn(self, drawn_a, drawn_b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Newly split areas begin at the center of the split
|
||||||
|
/// to maintain centeredness in the user's field of view.
|
||||||
|
///
|
||||||
|
/// Use [align] to override that and always start
|
||||||
|
/// at the top, bottom, etc.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// /*
|
||||||
|
///
|
||||||
|
/// Split east: Split south:
|
||||||
|
/// | | | | A |
|
||||||
|
/// | <-A|B-> | |---------|
|
||||||
|
/// | | | | B |
|
||||||
|
///
|
||||||
|
/// */
|
||||||
|
/// ```
|
||||||
|
const fn origins (&self) -> (Azimuth, Azimuth) {
|
||||||
|
use Azimuth::*;
|
||||||
|
match self {
|
||||||
|
Self::South => (S, N),
|
||||||
|
Self::East => (E, W),
|
||||||
|
Self::North => (N, S),
|
||||||
|
Self::West => (W, E),
|
||||||
|
Self::Above => (C, C),
|
||||||
|
Self::Below => (C, C),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///// ```
|
||||||
|
///// use tengri::*;
|
||||||
|
///// let _ = Split::Below.iter([
|
||||||
|
///// "Leftbar"
|
||||||
|
///// .min_w(10).max_w(15).align(Azimuth::NW),
|
||||||
|
///// "Rightbar"
|
||||||
|
///// .min_w(10).max_w(12).align(Azimuth::NE),
|
||||||
|
///// "Center"
|
||||||
|
///// .min_w(20).max_w(40).align(Azimuth::C),
|
||||||
|
///// ].iter());
|
||||||
|
///// ```
|
||||||
|
//pub fn iter <S: Screen, T: Draw<S>> (&self, _: impl Iterator<Item = T>) {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_stacks <S: Screen> (
|
||||||
|
split: &Split,
|
||||||
|
to: &mut S,
|
||||||
|
a: impl Draw<S>,
|
||||||
|
area_a: impl Into<Option<XYWH<S::Unit>>>,
|
||||||
|
origin_a: impl Into<Option<Azimuth>>,
|
||||||
|
b: impl Draw<S>,
|
||||||
|
area_b: impl Into<Option<XYWH<S::Unit>>>,
|
||||||
|
origin_b: impl Into<Option<Azimuth>>,
|
||||||
|
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
|
||||||
|
let draw_a = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_a) = origin_a.into() {
|
||||||
|
to.draw(area_a.into(), a.align(origin_a))?
|
||||||
|
} else {
|
||||||
|
to.draw(area_a.into(), a)?
|
||||||
|
});
|
||||||
|
let draw_b = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_b) = origin_b.into() {
|
||||||
|
to.draw(area_b.into(), b.align(origin_b))?
|
||||||
|
} else {
|
||||||
|
to.draw(area_b.into(), b)?
|
||||||
|
});
|
||||||
|
Ok(if matches!(split, Split::Below) {
|
||||||
|
let drawn_b = draw_b(to)?;
|
||||||
|
let drawn_a = draw_a(to)?;
|
||||||
|
(drawn_a, drawn_b)
|
||||||
|
} else {
|
||||||
|
(draw_a(to)?, draw_b(to)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stack_areas <S: Screen> (
|
||||||
|
split: &Split,
|
||||||
|
to: &mut S,
|
||||||
|
a: impl Draw<S>,
|
||||||
|
b: impl Draw<S>,
|
||||||
|
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
|
||||||
|
let area_a = to.draw(None, a)?;
|
||||||
|
Ok(match split {
|
||||||
|
Split::South => (
|
||||||
|
area_a,
|
||||||
|
area_a
|
||||||
|
.map(|used|to.size(XYWH(to.x(), to.y() + used.h(), to.w(), to.h().minus(used.h())), b))
|
||||||
|
.transpose()?
|
||||||
|
.flatten()
|
||||||
|
),
|
||||||
|
Split::East => (
|
||||||
|
area_a,
|
||||||
|
area_a.map(|used|to.size(XYWH(to.x() + used.w(), to.y(), to.w().minus(used.w()), to.h()), b))
|
||||||
|
.transpose()?
|
||||||
|
.flatten()
|
||||||
|
),
|
||||||
|
Split::North => (
|
||||||
|
area_a.map(|used|XYWH(used.x(), (to.y() + to.h()).minus(used.h()), used.w(), used.h())),
|
||||||
|
if let Some(used) = area_a {
|
||||||
|
to.size(XYWH(to.x(), to.y(), to.w(), to.h().minus(used.h())), b)?
|
||||||
|
} else {
|
||||||
|
to.size(None, b)?
|
||||||
|
.map(|area_b|XYWH(area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h()))
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Split::West => (
|
||||||
|
area_a.map(|used|XYWH((to.x() + to.w()).minus(used.w()), used.y(), used.w(), used.h())),
|
||||||
|
if let Some(used) = area_a {
|
||||||
|
to.size(XYWH(to.x(), to.y(), to.w().minus(used.w()), to.h()), b)?
|
||||||
|
} else {
|
||||||
|
to.size(None, b)?
|
||||||
|
.map(|area_b|XYWH(area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h()))
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Split::Above | Split::Below => (
|
||||||
|
area_a,
|
||||||
|
to.size(None, b)?,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stack_drawn <S: Coord> (
|
||||||
|
split: &Split,
|
||||||
|
drawn_a: Option<XYWH<S>>,
|
||||||
|
drawn_b: Option<XYWH<S>>,
|
||||||
|
) -> Option<XYWH<S>> {
|
||||||
|
if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) {
|
||||||
|
match split {
|
||||||
|
Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)),
|
||||||
|
Split::East => Some(XYWH(xa, ya.min(yb), wa + wb, ha.max(hb))),
|
||||||
|
Split::North => Some(XYWH(xa.min(xb), yb, wa.max(wb), ha + hb)),
|
||||||
|
Split::West => Some(XYWH(xb, ya.min(yb), wa + wb, ha.max(hb))),
|
||||||
|
Split::Above | Split::Below =>
|
||||||
|
Some(XYWH(xa.min(xb), ya.min(yb), wa.max(wb), ha.max(hb))),
|
||||||
|
}
|
||||||
|
} else if let Some(a) = drawn_a {
|
||||||
|
Some(a)
|
||||||
|
} else if let Some(b) = drawn_b {
|
||||||
|
Some(b)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! north {
|
||||||
|
($head:expr $(,)?) => { $head };
|
||||||
|
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! south {
|
||||||
|
($head:expr $(,)?) => { $head };
|
||||||
|
($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! east {
|
||||||
|
($head:expr $(,)?) => { $head };
|
||||||
|
($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! west {
|
||||||
|
($head:expr $(,)?) => { $head };
|
||||||
|
($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! above {
|
||||||
|
($head:expr $(,)?) => { $head };
|
||||||
|
($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! below {
|
||||||
|
($head:expr $(,)?) => { $head };
|
||||||
|
($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn east <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
||||||
|
Pair(Split::East, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn north <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
||||||
|
Pair(Split::North, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn west <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
||||||
|
Pair(Split::West, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn south <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
||||||
|
Pair(Split::South, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn above <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
||||||
|
Pair(Split::Above, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn below <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
||||||
|
Pair(Split::Below, a, b, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_stack_areas () -> Usually<()> {
|
||||||
|
let area = XYWH(0u16, 0, 80, 25);
|
||||||
|
|
||||||
|
assert_eq!(stack_areas(&Split::East, &mut Tui::Layout(area), &"foo", &"bar")?, (
|
||||||
|
Some(XYWH(0u16, 0, 3, 1)),
|
||||||
|
Some(XYWH(3u16, 0, 3, 1)),
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(stack_areas(&Split::South, &mut Tui::Layout(area), &"foo", &"bar")?, (
|
||||||
|
Some(XYWH(0u16, 0, 3, 1)),
|
||||||
|
Some(XYWH(0u16, 1, 3, 1)),
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_split_stack () -> Usually<()> {
|
||||||
|
use Split::*;
|
||||||
|
fn size_of <A: Draw<Tui>, B: Draw<Tui>> (stack: &Pair<Tui, A, B>) -> Perhaps<XYWH<u16>> {
|
||||||
|
Tui::Layout(XYWH(0, 0, 80, 25)).size(None, stack)
|
||||||
|
}
|
||||||
|
assert_eq!(size_of(&split(East, "foo", "bar"))?,
|
||||||
|
Some(XYWH(0, 0, 6, 1)));
|
||||||
|
assert_eq!(size_of(&split(South, "foo", "bar"))?,
|
||||||
|
Some(XYWH(0, 0, 3, 2)));
|
||||||
|
assert_eq!(size_of(&split(South, split(East, "foo", "bar"), "baz"))?,
|
||||||
|
Some(XYWH(0, 0, 6, 2)));
|
||||||
|
assert_eq!(size_of(&split(East, split(South, "foo", "bar"), "baz"))?,
|
||||||
|
Some(XYWH(0, 0, 6, 2)));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_align () -> Usually<()> {
|
||||||
|
let mut screen = Tui::Layout(XYWH(0, 0, 80, 25));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_nw().draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_n().draw(&mut screen)?, Some(XYWH(37, 0, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_ne().draw(&mut screen)?, Some(XYWH(74, 0, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_w().draw(&mut screen)?, Some(XYWH(0, 11, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_c().draw(&mut screen)?, Some(XYWH(37, 11, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_e().draw(&mut screen)?, Some(XYWH(74, 11, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_sw().draw(&mut screen)?, Some(XYWH(0, 23, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_s().draw(&mut screen)?, Some(XYWH(37, 23, 6, 2)));
|
||||||
|
assert_eq!("FOOBAR\nKILROY".align_se().draw(&mut screen)?, Some(XYWH(74, 23, 6, 2)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
46
src/layout/cond.rs
Normal file
46
src/layout/cond.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
fn_kw_layout!(kw_when |state, output, expr| {
|
||||||
|
Ok(matches!(expr.head()?, Some("when")).then(||{
|
||||||
|
when(state.namespace(expr.tail().head())?.unwrap(),
|
||||||
|
draw(move|output: &mut O|{state.interpret(output, &expr.tail().tail().head())})
|
||||||
|
).draw(output)
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Only render when condition is true.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use tengri::*;
|
||||||
|
/// # fn test () -> impl Draw<Tui> {
|
||||||
|
/// when(true, "Yes")
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub const fn when <T: Screen> (condition: bool, item: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
draw(move|to: &mut T|if condition { item.draw(to) } else { Ok(Default::default()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_kw_layout!(kw_either |state, output, expr| {
|
||||||
|
Ok(matches!(expr.head()?, Some("either")).then(||{
|
||||||
|
either(state.namespace(expr.tail().head()?)?.unwrap(),
|
||||||
|
draw(move|output: &mut O|{
|
||||||
|
state.interpret(output, &expr.tail().tail().head()?)
|
||||||
|
}),
|
||||||
|
draw(move|output: &mut O|{
|
||||||
|
state.interpret(output, &expr.tail().tail().tail().head()?)
|
||||||
|
}),
|
||||||
|
).draw(output)
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Render one thing if a condition is true and another false.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use tengri::*;
|
||||||
|
/// # fn test () -> impl Draw<Tui> {
|
||||||
|
/// either(true, "Yes", "No")
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
draw(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
|
||||||
|
}
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Set size of of drawing area.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::Layout;
|
|
||||||
/// let _ = "".exact_w(1);
|
|
||||||
/// let _ = "".exact_h(1);
|
|
||||||
/// let _ = "".exact_wh(1, 1);
|
|
||||||
/// ```
|
|
||||||
pub enum Exact<S: Screen, I: Draw<S>, X: Into<Option<S::Unit>>> {
|
|
||||||
__(PhantomData<S>),
|
|
||||||
W(I, X),
|
|
||||||
H(I, X),
|
|
||||||
WH(I, X, X),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Exact<S, I, X> {
|
|
||||||
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
|
|
||||||
Ok(Some(layout_exact(self, area)))
|
|
||||||
}
|
|
||||||
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
|
||||||
let area = layout_exact(&self, to.area());
|
|
||||||
let item = match self { Self::W(i, ..) => i, Self::H(i, ..) => i, Self::WH(i, ..) => i, _ => unreachable!() };
|
|
||||||
to.clip(area, |to|item.draw(to))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout_exact <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> (
|
|
||||||
exact: &Exact<S, I, X>, area: XYWH<S::Unit>
|
|
||||||
) -> XYWH<S::Unit> {
|
|
||||||
let (w, h): (S::Unit, S::Unit) = match exact {
|
|
||||||
Exact::W(_, w) => {
|
|
||||||
let w: Option<S::Unit> = (*w).into();
|
|
||||||
(w.unwrap_or(area.2), area.3)
|
|
||||||
},
|
|
||||||
Exact::H(_, h) => {
|
|
||||||
let h: Option<S::Unit> = (*h).into();
|
|
||||||
(area.2, h.unwrap_or(area.3))
|
|
||||||
},
|
|
||||||
Exact::WH(_, w, h) => {
|
|
||||||
let w: Option<S::Unit> = (*w).into();
|
|
||||||
let h: Option<S::Unit> = (*h).into();
|
|
||||||
(w.unwrap_or(area.2), w.unwrap_or(area.3))
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
XYWH(area.0, area.1, w, h)
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Use whole drawing area along one or both axes.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # fn doctest_layout_full () -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// use tengri::{Layout, Draw, XYWH};
|
|
||||||
/// let area = XYWH(0u16, 0, 80, 25);
|
|
||||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
|
||||||
/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1)));
|
|
||||||
/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25)));
|
|
||||||
/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25)));
|
|
||||||
/// # Ok(()) }
|
|
||||||
/// ```
|
|
||||||
pub enum Full<T: Screen, I: Draw<T>> {
|
|
||||||
__(PhantomData<T>),
|
|
||||||
W(I),
|
|
||||||
H(I),
|
|
||||||
WH(I),
|
|
||||||
}
|
|
||||||
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
|
|
||||||
let XYWH(x0, y0, w0, h0) = to.area();
|
|
||||||
match self {
|
|
||||||
Self::W(item) => if let Some(XYWH(_, y, _, h)) = item.layout(to.area())? {
|
|
||||||
to.clip(XYWH(x0, y, w0, h), |to|item.draw(to))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Self::H(item) => if let Some(XYWH(x, _, w, _)) = item.layout(to.area())? {
|
|
||||||
to.clip(XYWH(x, y0, w, h0), |to|item.draw(to))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Self::WH(item) => if let Some(XYWH(..)) = item.layout(to.area())? {
|
|
||||||
to.clip(XYWH(x0, y0, w0, h0), |to|item.draw(to))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Set maximum size of of drawing area.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # fn doctest_layout_max () -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// use tengri::{Layout, Draw, XYWH};
|
|
||||||
/// let area = XYWH(1u16, 1, 80, 25);
|
|
||||||
/// assert_eq!("12345".max_w(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
|
||||||
/// assert_eq!("12345".max_h(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
|
||||||
/// assert_eq!("12345".max_wh(1, 1).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
|
|
||||||
/// # Ok(()) }
|
|
||||||
/// ```
|
|
||||||
pub enum Max<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> {
|
|
||||||
__(PhantomData<T>),
|
|
||||||
W(I, X),
|
|
||||||
H(I, X),
|
|
||||||
WH(I, X, X),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> Draw<T> for Max<T, I, X> {
|
|
||||||
fn layout (&self, area: XYWH<T::Unit>) -> Perhaps<XYWH<T::Unit>> {
|
|
||||||
Ok(Some(match self {
|
|
||||||
Self::W(_, max_w) => XYWH(
|
|
||||||
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
|
||||||
area.3),
|
|
||||||
Self::H(_, max_h) => XYWH(
|
|
||||||
area.0, area.1, area.2,
|
|
||||||
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)),
|
|
||||||
Self::WH(_, max_w, max_h) => XYWH(
|
|
||||||
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
|
||||||
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)),
|
|
||||||
_ => return Ok(None)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
|
|
||||||
let area: XYWH<T::Unit> = to.area();
|
|
||||||
let (item, area) = match self {
|
|
||||||
Self::W(item, max_w) => (item, XYWH(
|
|
||||||
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
|
||||||
area.3
|
|
||||||
)),
|
|
||||||
Self::H(item, max_h) => (item, XYWH(
|
|
||||||
area.0, area.1, area.2,
|
|
||||||
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3)
|
|
||||||
)),
|
|
||||||
Self::WH(item, max_w, max_h) => (item, XYWH(
|
|
||||||
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
|
||||||
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3),
|
|
||||||
)),
|
|
||||||
_ => return Ok(None)
|
|
||||||
};
|
|
||||||
to.clip(area, |to|item.draw(to))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Only draw content if area is above a certain size.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # fn doctest_layout_min () -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// use tengri::{Layout, Draw, XYWH};
|
|
||||||
/// let area = XYWH(1u16, 1, 80, 25);
|
|
||||||
/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
|
|
||||||
/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5)));
|
|
||||||
/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5)));
|
|
||||||
/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1)));
|
|
||||||
/// # Ok(()) }
|
|
||||||
/// ```
|
|
||||||
pub enum Min<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
|
||||||
__(PhantomData<T>),
|
|
||||||
W(I, X),
|
|
||||||
H(I, X),
|
|
||||||
WH(I, X, X),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Min<T, I, X>, to: T|{
|
|
||||||
match self {
|
|
||||||
Self::__(_) => unreachable!(),
|
|
||||||
Self::W(item, w1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
|
||||||
let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w);
|
|
||||||
to.clip(XYWH(x, y, w, h), |to|item.draw(to))
|
|
||||||
},
|
|
||||||
Self::H(item, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
|
||||||
let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h);
|
|
||||||
to.clip(XYWH(x, y, w, h), |to|item.draw(to))
|
|
||||||
},
|
|
||||||
Self::WH(item, w1, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
|
||||||
let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w);
|
|
||||||
let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h);
|
|
||||||
to.clip(XYWH(x, y, w, h), |to|item.draw(to))
|
|
||||||
},
|
|
||||||
_ => Ok(None)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub struct Origin<T>(
|
|
||||||
pub(crate) Option<Azimuth>,
|
|
||||||
pub(crate) T
|
|
||||||
);
|
|
||||||
|
|
||||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, _to: S|{
|
|
||||||
todo!()
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Something that has `[0, 0]` at a particular point.
|
|
||||||
pub trait HasOrigin {
|
|
||||||
fn origin (&self) -> Azimuth;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<Azimuth>> HasOrigin for T {
|
|
||||||
fn origin (&self) -> Azimuth {
|
|
||||||
*self.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Define inner drawing area.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::Layout;
|
|
||||||
/// let _ = "".pad_w(1);
|
|
||||||
/// let _ = "".pad_h(1);
|
|
||||||
/// let _ = "".pad_wh(1, 1);
|
|
||||||
/// ```
|
|
||||||
pub enum Pad<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
|
||||||
__(PhantomData<T>),
|
|
||||||
W(I, X),
|
|
||||||
H(I, X),
|
|
||||||
WH(I, X, X),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pad<T, I, X>, to: T|{
|
|
||||||
let area = to.area();
|
|
||||||
let (item, area) = match self {
|
|
||||||
Self::W(item, w1) => {
|
|
||||||
let w1 = w1.into().unwrap_or(T::Unit::zero());
|
|
||||||
(item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3))
|
|
||||||
},
|
|
||||||
Self::H(item, h1) => {
|
|
||||||
let h1 = h1.into().unwrap_or(T::Unit::zero());
|
|
||||||
(item, XYWH(area.0, area.1 + h1, area.2, area.3.minus(h1 + h1)))
|
|
||||||
},
|
|
||||||
Self::WH(item, w1, h1) => {
|
|
||||||
let w1 = w1.into().unwrap_or(T::Unit::zero());
|
|
||||||
let h1 = h1.into().unwrap_or(T::Unit::zero());
|
|
||||||
(item, XYWH(area.0 + w1, area.1 + h1, area.2.minus(w1 + w1), area.3.minus(h1 + h1)))
|
|
||||||
},
|
|
||||||
_ => return Ok(None)
|
|
||||||
};
|
|
||||||
item.draw(to)
|
|
||||||
});
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Move content in the negative direction of one or both axes.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # fn doctest_layout_pull () -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// use tengri::{Layout, Draw, XYWH};
|
|
||||||
/// let area = XYWH(1u16, 1, 80, 25);
|
|
||||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
|
||||||
/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
|
||||||
/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
|
||||||
/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
|
||||||
/// # Ok(()) }
|
|
||||||
/// ```
|
|
||||||
pub enum Pull<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
|
||||||
__(PhantomData<T>),
|
|
||||||
X(I, X),
|
|
||||||
Y(I, X),
|
|
||||||
XY(I, X, X),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pull<T, I, X>, _to: T|{
|
|
||||||
todo!()
|
|
||||||
});
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Move content in the positive direction of one or both axes.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # fn doctest_layout_push () -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// use tengri::{Layout, Draw, XYWH};
|
|
||||||
/// let area = XYWH(0u16, 0, 80, 25);
|
|
||||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
|
||||||
/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
|
||||||
/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
|
||||||
/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
|
||||||
/// # Ok(()) }
|
|
||||||
/// ```
|
|
||||||
pub enum Push<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
|
||||||
__(PhantomData<T>),
|
|
||||||
X(I, X),
|
|
||||||
Y(I, X),
|
|
||||||
XY(I, X, X),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Push<T, I, X>, to: T|{
|
|
||||||
match self {
|
|
||||||
Self::__(_) => unreachable!(),
|
|
||||||
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
|
||||||
to.clip(XYWH(
|
|
||||||
x + x1.into().unwrap_or_default(), y, w, h
|
|
||||||
), |to|item.draw(to))
|
|
||||||
},
|
|
||||||
Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
|
||||||
to.clip(XYWH(
|
|
||||||
x, y + y1.into().unwrap_or_default(), w, h
|
|
||||||
), |to|item.draw(to))
|
|
||||||
},
|
|
||||||
Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
|
||||||
to.clip(XYWH(
|
|
||||||
x + x1.into().unwrap_or_default(), y + y1.into().unwrap_or_default(), w, h
|
|
||||||
), |to|item.draw(to))
|
|
||||||
},
|
|
||||||
_ => Ok(None)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,315 +0,0 @@
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Split along an axis. Direction determines order.
|
|
||||||
#[cfg_attr(test, derive(Arbitrary))]
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
|
|
||||||
North,
|
|
||||||
South,
|
|
||||||
East,
|
|
||||||
West,
|
|
||||||
Above,
|
|
||||||
#[default] Below
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Pair<S: Screen, A: Draw<S>, B: Draw<S>>(Split, A, B, PhantomData<S>);
|
|
||||||
|
|
||||||
pub fn split <S: Screen, A: Draw<S>, B: Draw<S>> (
|
|
||||||
split: Split, a: A, b: B
|
|
||||||
) -> Pair<S, A, B> {
|
|
||||||
Pair(split, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Screen, A: Draw<S>, B: Draw<S>> Draw<S> for Pair<S, A, B> {
|
|
||||||
fn layout (&self, to: XYWH<S::Unit>) -> Drawn<S::Unit> {
|
|
||||||
let Self(split, a, b, ..) = self;
|
|
||||||
let (area_a, area_b) = stack_areas(split, to, a, b)?;
|
|
||||||
Ok(stack_drawn(split, area_a, area_b))
|
|
||||||
}
|
|
||||||
fn draw (self, to: &mut S) -> Drawn<S::Unit> {
|
|
||||||
let Self(ref split, a, b, ..) = self;
|
|
||||||
let (area_a, area_b) = stack_areas(split, to.area(), &a, &b)?;
|
|
||||||
let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?;
|
|
||||||
Ok(stack_drawn(split, drawn_a, drawn_b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Split {
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// use tengri::*;
|
|
||||||
/// let _ = Split::Above.stack("", "");
|
|
||||||
/// let _ = Split::Below.stack("", "");
|
|
||||||
/// let _ = Split::North.stack("", "");
|
|
||||||
/// let _ = Split::South.stack("", "");
|
|
||||||
/// let _ = Split::East.stack("", "");
|
|
||||||
/// let _ = Split::West.stack("", "");
|
|
||||||
/// ```
|
|
||||||
pub const fn stack <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
|
|
||||||
Pair(*self, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// use tengri::*;
|
|
||||||
/// let _ = Split::Above.half("", "");
|
|
||||||
/// let _ = Split::Below.half("", "");
|
|
||||||
/// let _ = Split::North.half("", "");
|
|
||||||
/// let _ = Split::South.half("", "");
|
|
||||||
/// let _ = Split::East.half("", "");
|
|
||||||
/// let _ = Split::West.half("", "");
|
|
||||||
/// ```
|
|
||||||
pub const fn half <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
|
|
||||||
draw(move|to: &mut S|{
|
|
||||||
let (area_a, area_b) = to.xywh().split_half(self);
|
|
||||||
let (origin_a, origin_b) = self.origins();
|
|
||||||
let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, origin_a, b, area_b, origin_b)?;
|
|
||||||
Ok(stack_drawn(self, drawn_a, drawn_b))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Newly split areas begin at the center of the split
|
|
||||||
/// to maintain centeredness in the user's field of view.
|
|
||||||
///
|
|
||||||
/// Use [align] to override that and always start
|
|
||||||
/// at the top, bottom, etc.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// /*
|
|
||||||
///
|
|
||||||
/// Split east: Split south:
|
|
||||||
/// | | | | A |
|
|
||||||
/// | <-A|B-> | |---------|
|
|
||||||
/// | | | | B |
|
|
||||||
///
|
|
||||||
/// */
|
|
||||||
/// ```
|
|
||||||
const fn origins (&self) -> (Azimuth, Azimuth) {
|
|
||||||
use Azimuth::*;
|
|
||||||
match self {
|
|
||||||
Self::South => (S, N),
|
|
||||||
Self::East => (E, W),
|
|
||||||
Self::North => (N, S),
|
|
||||||
Self::West => (W, E),
|
|
||||||
Self::Above => (C, C),
|
|
||||||
Self::Below => (C, C),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///// ```
|
|
||||||
///// use tengri::*;
|
|
||||||
///// let _ = Split::Below.iter([
|
|
||||||
///// "Leftbar"
|
|
||||||
///// .min_w(10).max_w(15).align(Azimuth::NW),
|
|
||||||
///// "Rightbar"
|
|
||||||
///// .min_w(10).max_w(12).align(Azimuth::NE),
|
|
||||||
///// "Center"
|
|
||||||
///// .min_w(20).max_w(40).align(Azimuth::C),
|
|
||||||
///// ].iter());
|
|
||||||
///// ```
|
|
||||||
//pub fn iter <S: Screen, T: Draw<S>> (&self, _: impl Iterator<Item = T>) {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_stacks <S: Screen> (
|
|
||||||
split: &Split,
|
|
||||||
to: &mut S,
|
|
||||||
a: impl Draw<S>,
|
|
||||||
area_a: impl Into<Option<XYWH<S::Unit>>>,
|
|
||||||
origin_a: impl Into<Option<Azimuth>>,
|
|
||||||
b: impl Draw<S>,
|
|
||||||
area_b: impl Into<Option<XYWH<S::Unit>>>,
|
|
||||||
origin_b: impl Into<Option<Azimuth>>,
|
|
||||||
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
|
|
||||||
let draw_a = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_a) = origin_a.into() {
|
|
||||||
to.clip(area_a.into(), |to|a.align(origin_a).draw(to))?
|
|
||||||
} else {
|
|
||||||
to.clip(area_a.into(), |to|a.draw(to))?
|
|
||||||
});
|
|
||||||
let draw_b = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_b) = origin_b.into() {
|
|
||||||
to.clip(area_b.into(), |to|b.align(origin_b).draw(to))?
|
|
||||||
} else {
|
|
||||||
to.clip(area_b.into(), |to|b.draw(to))?
|
|
||||||
});
|
|
||||||
Ok(if matches!(split, Split::Below) {
|
|
||||||
let drawn_b = draw_b(to)?;
|
|
||||||
let drawn_a = draw_a(to)?;
|
|
||||||
(drawn_a, drawn_b)
|
|
||||||
} else {
|
|
||||||
(draw_a(to)?, draw_b(to)?)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stack_areas <S: Screen> (
|
|
||||||
split: &Split,
|
|
||||||
area: XYWH<S::Unit>,
|
|
||||||
a: &impl Draw<S>,
|
|
||||||
b: &impl Draw<S>,
|
|
||||||
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
|
|
||||||
let area_a = a.layout(area)?;
|
|
||||||
Ok(match split {
|
|
||||||
Split::South => (
|
|
||||||
area_a,
|
|
||||||
if let Some(used) = area_a {
|
|
||||||
b.layout(XYWH(area.x(), area.y() + used.h(), area.w(), area.h().minus(used.h())))?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Split::East => (
|
|
||||||
area_a,
|
|
||||||
if let Some(used) = area_a {
|
|
||||||
b.layout(XYWH(area.x() + used.w(), area.y(), area.w().minus(used.w()), area.h()))?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Split::North => (
|
|
||||||
if let Some(used) = area_a {
|
|
||||||
Some(XYWH(used.x(), (area.y() + area.h()).minus(used.h()), used.w(), used.h()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
if let Some(used) = area_a {
|
|
||||||
b.layout(XYWH(area.x(), area.y(), area.w(), area.h().minus(used.h())))?
|
|
||||||
} else {
|
|
||||||
b.layout(area)?.map(|area_b|XYWH(
|
|
||||||
area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Split::West => (
|
|
||||||
if let Some(used) = area_a {
|
|
||||||
Some(XYWH((area.x() + area.w()).minus(used.w()), used.y(), used.w(), used.h()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
if let Some(used) = area_a {
|
|
||||||
b.layout(XYWH(area.x(), area.y(), area.w().minus(used.w()), area.h()))?
|
|
||||||
} else {
|
|
||||||
b.layout(area)?.map(|area_b|XYWH(
|
|
||||||
area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Split::Above | Split::Below => (
|
|
||||||
area_a,
|
|
||||||
b.layout(area)?,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stack_drawn <S: Coord> (
|
|
||||||
split: &Split,
|
|
||||||
drawn_a: Option<XYWH<S>>,
|
|
||||||
drawn_b: Option<XYWH<S>>,
|
|
||||||
) -> Option<XYWH<S>> {
|
|
||||||
if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) {
|
|
||||||
match split {
|
|
||||||
Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)),
|
|
||||||
Split::East => Some(XYWH(xa, ya.min(yb), wa + wb, ha.max(hb))),
|
|
||||||
Split::North => Some(XYWH(xa.min(xb), yb, wa.max(wb), ha + hb)),
|
|
||||||
Split::West => Some(XYWH(xb, ya.min(yb), wa + wb, ha.max(hb))),
|
|
||||||
Split::Above | Split::Below =>
|
|
||||||
Some(XYWH(xa.min(xb), ya.min(yb), wa.max(wb), ha.max(hb))),
|
|
||||||
}
|
|
||||||
} else if let Some(a) = drawn_a {
|
|
||||||
Some(a)
|
|
||||||
} else if let Some(b) = drawn_b {
|
|
||||||
Some(b)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! north {
|
|
||||||
($head:expr $(,)?) => { $head };
|
|
||||||
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! south {
|
|
||||||
($head:expr $(,)?) => { $head };
|
|
||||||
($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! east {
|
|
||||||
($head:expr $(,)?) => { $head };
|
|
||||||
($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! west {
|
|
||||||
($head:expr $(,)?) => { $head };
|
|
||||||
($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! above {
|
|
||||||
($head:expr $(,)?) => { $head };
|
|
||||||
($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! below {
|
|
||||||
($head:expr $(,)?) => { $head };
|
|
||||||
($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn east <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
||||||
Pair(Split::East, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn north <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
||||||
Pair(Split::North, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn west <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
||||||
Pair(Split::West, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn south <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
||||||
Pair(Split::South, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn above <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
||||||
Pair(Split::Above, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn below <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
||||||
Pair(Split::Below, a, b, PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)] mod test {
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[test] fn test_stack_areas () -> Usually<()> {
|
|
||||||
let area = XYWH(0u16, 0, 80, 25);
|
|
||||||
|
|
||||||
assert_eq!(stack_areas(&Split::East, area, &"foo", &"bar")?, (
|
|
||||||
Some(XYWH(0u16, 0, 3, 1)),
|
|
||||||
Some(XYWH(3u16, 0, 3, 1)),
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(stack_areas(&Split::South, area, &"foo", &"bar")?, (
|
|
||||||
Some(XYWH(0u16, 0, 3, 1)),
|
|
||||||
Some(XYWH(0u16, 1, 3, 1)),
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test] fn test_split_stack () -> Usually<()> {
|
|
||||||
use tengri::{*, Split::*};
|
|
||||||
|
|
||||||
let stack = split(East, "foo", "bar");
|
|
||||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 1)));
|
|
||||||
|
|
||||||
let stack = split(South, "foo", "bar");
|
|
||||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 3, 2)));
|
|
||||||
|
|
||||||
let stack = split(South, split(East, "foo", "bar"), "baz");
|
|
||||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2)));
|
|
||||||
|
|
||||||
let stack = split(East, split(South, "foo", "bar"), "baz");
|
|
||||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2)));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2210
src/lib.rs
2210
src/lib.rs
File diff suppressed because it is too large
Load diff
924
src/sing.rs
924
src/sing.rs
|
|
@ -1,924 +0,0 @@
|
||||||
use crate::{*, time::PerfModel};
|
|
||||||
pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
|
||||||
pub use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
|
||||||
use ConnectName::*;
|
|
||||||
use ConnectScope::*;
|
|
||||||
use ConnectStatus::*;
|
|
||||||
use JackState::*;
|
|
||||||
|
|
||||||
/// Wraps [JackState], and through it [jack::Client] when connected.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let jack = tengri::Jack::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug, Default)] pub struct Jack<'j> (
|
|
||||||
pub(crate) Arc<RwLock<JackState<'j>>>
|
|
||||||
);
|
|
||||||
|
|
||||||
/// This is a connection which may be [Inactive], [Activating], or [Active].
|
|
||||||
/// In the [Active] and [Inactive] states, [JackState::client] returns a
|
|
||||||
/// [jack::Client], which you can use to talk to the JACK API.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let state = tengri::JackState::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub enum JackState<'j> {
|
|
||||||
/// Unused
|
|
||||||
#[default] Inert,
|
|
||||||
/// Before activation.
|
|
||||||
Inactive(Client),
|
|
||||||
/// During activation.
|
|
||||||
Activating,
|
|
||||||
/// After activation. Must not be dropped for JACK thread to persist.
|
|
||||||
Active(DynamicAsyncClient<'j>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement [Jack] constructor and methods
|
|
||||||
impl<'j> Jack<'j> {
|
|
||||||
/// Register new [Client] and wrap it for shared use.
|
|
||||||
pub fn new_run <T: HasJack<'j> + Audio + Send + Sync + 'static> (
|
|
||||||
name: impl AsRef<str>,
|
|
||||||
init: impl FnOnce(Jack<'j>)->Usually<T>
|
|
||||||
) -> Usually<Arc<RwLock<T>>> {
|
|
||||||
Jack::new(name)?.run(init)
|
|
||||||
}
|
|
||||||
pub fn new (name: impl AsRef<str>) -> Usually<Self> {
|
|
||||||
let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0;
|
|
||||||
Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client)))))
|
|
||||||
}
|
|
||||||
/// Run something with the client.
|
|
||||||
pub fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
|
||||||
match &*self.0.read().unwrap() {
|
|
||||||
Inert => panic!("jack client not activated"),
|
|
||||||
Inactive(client) => op(client),
|
|
||||||
Activating => panic!("jack client has not finished activation"),
|
|
||||||
Active(client) => op(client.as_client()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn run <T: HasJack<'j> + Audio + Send + Sync + 'static>
|
|
||||||
(self, init: impl FnOnce(Self)->Usually<T>) -> Usually<Arc<RwLock<T>>>
|
|
||||||
{
|
|
||||||
let client_state = self.0.clone();
|
|
||||||
let app: Arc<RwLock<T>> = Arc::new(RwLock::new(init(self)?));
|
|
||||||
let mut state = Activating;
|
|
||||||
std::mem::swap(&mut*client_state.write().unwrap(), &mut state);
|
|
||||||
if let Inactive(client) = state {
|
|
||||||
// This is the misc notifications handler. It's a struct that wraps a [Box]
|
|
||||||
// which performs type erasure on a callback that takes [JackEvent], which is
|
|
||||||
// one of the available misc notifications.
|
|
||||||
let notify = JackNotify(Box::new({
|
|
||||||
let app = app.clone();
|
|
||||||
move|event|(&mut*app.write().unwrap()).handle(event)
|
|
||||||
}) as BoxedJackEventHandler);
|
|
||||||
// This is the main processing handler. It's a struct that wraps a [Box]
|
|
||||||
// which performs type erasure on a callback that takes [Client] and [ProcessScope]
|
|
||||||
// and passes them down to the `app`'s `process` callback, which in turn
|
|
||||||
// implements audio and MIDI input and output on a realtime basis.
|
|
||||||
let process = ::jack::contrib::ClosureProcessHandler::new(Box::new({
|
|
||||||
let app = app.clone();
|
|
||||||
move|c: &_, s: &_|if let Ok(mut app) = app.write() {
|
|
||||||
app.process(c, s)
|
|
||||||
} else {
|
|
||||||
Control::Quit
|
|
||||||
}
|
|
||||||
}) as BoxedAudioHandler);
|
|
||||||
// Launch a client with the two handlers.
|
|
||||||
*client_state.write().unwrap() = Active(
|
|
||||||
client.activate_async(notify, process)?
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
Ok(app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'j> HasJack<'j> for Jack<'j> {
|
|
||||||
fn jack (&self) -> &Jack<'j> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'j> HasJack<'j> for &Jack<'j> {
|
|
||||||
fn jack (&self) -> &Jack<'j> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'j, T: HasJack<'j>> HasJack<'j> for Arc<T> {
|
|
||||||
fn jack (&self) -> &Jack<'j> {
|
|
||||||
(&**self).jack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Event enum for JACK events.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let event = tengri::JackEvent::XRun; // kerpop
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
|
|
||||||
ThreadInit,
|
|
||||||
Shutdown(ClientStatus, Arc<str>),
|
|
||||||
Freewheel(bool),
|
|
||||||
SampleRate(Frames),
|
|
||||||
ClientRegistration(Arc<str>, bool),
|
|
||||||
PortRegistration(PortId, bool),
|
|
||||||
PortRename(PortId, Arc<str>, Arc<str>),
|
|
||||||
PortsConnected(PortId, PortId, bool),
|
|
||||||
GraphReorder,
|
|
||||||
XRun,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic notification handler that emits [JackEvent]
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let notify = tengri::JackNotify(|_|{});
|
|
||||||
/// ```
|
|
||||||
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
|
|
||||||
|
|
||||||
/// Notification handler wrapper for [BoxedJackEventHandler].
|
|
||||||
pub type DynamicNotifications<'j> =
|
|
||||||
JackNotify<BoxedJackEventHandler<'j>>;
|
|
||||||
|
|
||||||
/// Boxed [JackEvent] callback.
|
|
||||||
pub type BoxedJackEventHandler<'j> =
|
|
||||||
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
|
|
||||||
|
|
||||||
impl<T: Fn(JackEvent) + Send> NotificationHandler for JackNotify<T> {
|
|
||||||
fn thread_init(&self, _: &Client) {
|
|
||||||
self.0(JackEvent::ThreadInit);
|
|
||||||
}
|
|
||||||
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
|
|
||||||
self.0(JackEvent::Shutdown(status, reason.into()));
|
|
||||||
}
|
|
||||||
fn freewheel(&mut self, _: &Client, enabled: bool) {
|
|
||||||
self.0(JackEvent::Freewheel(enabled));
|
|
||||||
}
|
|
||||||
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
|
|
||||||
self.0(JackEvent::SampleRate(frames));
|
|
||||||
Control::Quit
|
|
||||||
}
|
|
||||||
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
|
|
||||||
self.0(JackEvent::ClientRegistration(name.into(), reg));
|
|
||||||
}
|
|
||||||
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
|
|
||||||
self.0(JackEvent::PortRegistration(id, reg));
|
|
||||||
}
|
|
||||||
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
|
||||||
self.0(JackEvent::PortRename(id, old.into(), new.into()));
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
|
|
||||||
self.0(JackEvent::PortsConnected(a, b, are));
|
|
||||||
}
|
|
||||||
fn graph_reorder(&mut self, _: &Client) -> Control {
|
|
||||||
self.0(JackEvent::GraphReorder);
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
fn xrun(&mut self, _: &Client) -> Control {
|
|
||||||
self.0(JackEvent::XRun);
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait JackPerfModel {
|
|
||||||
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JackPerfModel for PerfModel {
|
|
||||||
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope) {
|
|
||||||
if let Some(t0) = t0 {
|
|
||||||
let t1 = self.clock.raw();
|
|
||||||
self.used.store(
|
|
||||||
self.clock.delta_as_nanos(t0, t1) as f64,
|
|
||||||
Relaxed,
|
|
||||||
);
|
|
||||||
self.window.store(
|
|
||||||
scope.cycle_times().unwrap().period_usecs as f64,
|
|
||||||
Relaxed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for thing that has a JACK process callback.
|
|
||||||
pub trait Audio {
|
|
||||||
/// Handle a JACK event.
|
|
||||||
fn handle (&mut self, _event: JackEvent) {}
|
|
||||||
/// Projecss a JACK chunk.
|
|
||||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
/// The JACK process callback function passed to the server.
|
|
||||||
fn callback (
|
|
||||||
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
|
||||||
) -> Control where Self: Sized {
|
|
||||||
if let Ok(mut state) = state.write() {
|
|
||||||
state.process(client, scope)
|
|
||||||
} else {
|
|
||||||
Control::Quit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Running JACK [AsyncClient] with maximum type erasure.
|
|
||||||
///
|
|
||||||
/// One [Box] contains function that handles [JackEvent]s.
|
|
||||||
///
|
|
||||||
/// Another [Box] containing a function that handles realtime IO.
|
|
||||||
///
|
|
||||||
/// That's all it knows about them.
|
|
||||||
pub type DynamicAsyncClient<'j>
|
|
||||||
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
|
|
||||||
|
|
||||||
/// Notification handler wrapper for [BoxedAudioHandler].
|
|
||||||
pub type DynamicAudioHandler<'j> =
|
|
||||||
::jack::contrib::ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
|
|
||||||
|
|
||||||
/// Boxed realtime callback.
|
|
||||||
pub type BoxedAudioHandler<'j> =
|
|
||||||
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + Sync + 'j>;
|
|
||||||
|
|
||||||
/// Things that can provide a [jack::Client] reference.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::*;
|
|
||||||
///
|
|
||||||
/// let jack: &Jack = Jacked::default().jack();
|
|
||||||
///
|
|
||||||
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
|
|
||||||
///
|
|
||||||
/// impl<'j> HasJack<'j> for Jacked<'j> {
|
|
||||||
/// fn jack (&self) -> &Jack<'j> { &self.0 }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub trait HasJack<'j>: Send + Sync {
|
|
||||||
/// Return the internal [jack::Client] handle
|
|
||||||
/// that lets you call the JACK API.
|
|
||||||
fn jack (&self) -> &Jack<'j>;
|
|
||||||
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
|
||||||
self.jack().with_client(op)
|
|
||||||
}
|
|
||||||
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|client|client.port_by_name(name))
|
|
||||||
}
|
|
||||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_id(id))
|
|
||||||
}
|
|
||||||
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
|
||||||
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
|
||||||
}
|
|
||||||
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->jack::contrib::Position)
|
|
||||||
-> Usually<()>
|
|
||||||
{
|
|
||||||
if enable {
|
|
||||||
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => Err(e)
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
|
||||||
// TODO: sync follow
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement [Audio]: provide JACK callbacks.
|
|
||||||
#[macro_export] macro_rules! impl_audio {
|
|
||||||
(|
|
|
||||||
$self1:ident:
|
|
||||||
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
|
||||||
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
|
||||||
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
($Struct:ident: $process:ident, $handle:ident) => {
|
|
||||||
impl Audio for $Struct {
|
|
||||||
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
|
||||||
$process(self, c, s)
|
|
||||||
}
|
|
||||||
#[inline] fn handle (&mut self, e: JackEvent) {
|
|
||||||
$handle(self, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
($Struct:ident: $process:ident) => {
|
|
||||||
impl Audio for $Struct {
|
|
||||||
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
|
||||||
$process(self, c, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait JackPorts: HasJack<'static> {
|
|
||||||
/// Register a MIDI input port.
|
|
||||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput>;
|
|
||||||
/// Register a MIDI output port.
|
|
||||||
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput>;
|
|
||||||
/// Register an audio input port.
|
|
||||||
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput>;
|
|
||||||
/// Register an audio output port.
|
|
||||||
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<J: HasJack<'static>> JackPorts for J {
|
|
||||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput> {
|
|
||||||
MidiInput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput> {
|
|
||||||
MidiOutput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput> {
|
|
||||||
AudioInput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput> {
|
|
||||||
AudioOutput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait JackPort: HasJack<'static> {
|
|
||||||
const KIND: &'static str = "Port";
|
|
||||||
type Port: PortSpec + Default;
|
|
||||||
type Pair: PortSpec + Default;
|
|
||||||
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized;
|
|
||||||
|
|
||||||
fn register (jack: &Jack<'static>, name: &impl AsRef<str>) -> Usually<Port<Self::Port>> {
|
|
||||||
jack.with_client(|c|c.register_port::<Self::Port>(name.as_ref(), Default::default()))
|
|
||||||
.map_err(|e|e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close (self) -> Usually<()> where Self: Sized {
|
|
||||||
let jack = self.jack().clone();
|
|
||||||
Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_port (self) -> Port<Self::Port> where Self: Sized;
|
|
||||||
fn port_name (&self) -> &Arc<str>;
|
|
||||||
fn port (&self) -> &Port<Self::Port>;
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port>;
|
|
||||||
fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec<String> {
|
|
||||||
self.with_client(|c|c.ports(re_name, re_type, flags))
|
|
||||||
}
|
|
||||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_id(id))
|
|
||||||
}
|
|
||||||
fn port_by_name (&self, name: impl AsRef<str>) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_name(name.as_ref()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connections (&self) -> &[Connect];
|
|
||||||
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
|
|
||||||
for connect in self.connections().iter() {
|
|
||||||
match &connect.name {
|
|
||||||
Some(Exact(name)) => {
|
|
||||||
*connect.status.write().unwrap() = self.connect_exact(name)?;
|
|
||||||
},
|
|
||||||
Some(RegExp(re)) => {
|
|
||||||
*connect.status.write().unwrap() = self.connect_regexp(re, connect.scope)?;
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn connect_exact <'k> (&'k self, name: &str) ->
|
|
||||||
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
|
||||||
{
|
|
||||||
self.with_client(move|c|{
|
|
||||||
let mut status = vec![];
|
|
||||||
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
|
||||||
if port.as_str() == &*name {
|
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
|
||||||
let port_status = self.connect_to_unowned(&port)?;
|
|
||||||
let name = port.name()?.into();
|
|
||||||
status.push((port, name, port_status));
|
|
||||||
if port_status == Connected {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn connect_regexp <'k> (
|
|
||||||
&'k self, re: &str, scope: Option<ConnectScope>
|
|
||||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
|
||||||
self.with_client(move|c|{
|
|
||||||
let mut status = vec![];
|
|
||||||
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
|
||||||
for port in ports.iter() {
|
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
|
||||||
let port_status = self.connect_to_unowned(&port)?;
|
|
||||||
let name = port.name()?.into();
|
|
||||||
status.push((port, name, port_status));
|
|
||||||
if port_status == Connected && scope == Some(One) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/** Connect to a matching port by name. */
|
|
||||||
fn connect_to_name (&self, name: impl AsRef<str>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) {
|
|
||||||
self.connect_to_unowned(port)
|
|
||||||
} else {
|
|
||||||
Ok(Missing)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/** Connect to a matching port by reference. */
|
|
||||||
fn connect_to_unowned (&self, port: &Port<Unowned>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
|
||||||
Connected
|
|
||||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
|
||||||
Connected
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/** Connect to an owned matching port by reference. */
|
|
||||||
fn connect_to_owned (&self, port: &Port<Self::Pair>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
|
||||||
Connected
|
|
||||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
|
||||||
Connected
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Audio input port.
|
|
||||||
#[derive(Debug)] pub struct AudioInput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<AudioIn>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Audio output port.
|
|
||||||
#[derive(Debug)] pub struct AudioOutput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<AudioOut>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MIDI input port.
|
|
||||||
#[derive(Debug)] pub struct MidiInput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<MidiIn>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
pub held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MIDI output port.
|
|
||||||
#[derive(Debug)] pub struct MidiOutput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<MidiOut>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
pub held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
/// Buffer
|
|
||||||
pub note_buffer: Vec<u8>,
|
|
||||||
/// Buffer
|
|
||||||
pub output_buffer: Vec<Vec<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! jack_port {
|
|
||||||
($($Struct:ty = ($Port:ty => $Pair:ty) $({ $($tt:tt)* })?),*) => {
|
|
||||||
$(
|
|
||||||
impl HasJack<'static> for $Struct {
|
|
||||||
fn jack (&self) -> &Jack<'static> { &self.jack }
|
|
||||||
}
|
|
||||||
impl JackPort for $Struct {
|
|
||||||
type Port = $Port;
|
|
||||||
type Pair = $Pair;
|
|
||||||
fn port_name (&self) -> &Arc<str> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
fn port (&self) -> &Port<Self::Port> {
|
|
||||||
&self.port
|
|
||||||
}
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port> {
|
|
||||||
&mut self.port
|
|
||||||
}
|
|
||||||
fn into_port (self) -> Port<Self::Port> {
|
|
||||||
self.port
|
|
||||||
}
|
|
||||||
fn connections (&self) -> &[Connect] {
|
|
||||||
self.connections.as_slice()
|
|
||||||
}
|
|
||||||
$($($tt)*)?
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_port!(
|
|
||||||
AudioInput = (AudioIn => AudioOut) {
|
|
||||||
const KIND: &'static str = "Audio In";
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self {
|
|
||||||
port: Self::register(jack, name)?,
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
connections: connect.to_vec(),
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
AudioOutput = (AudioOut => AudioIn) {
|
|
||||||
const KIND: &'static str = "Audio Out";
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self {
|
|
||||||
port: Self::register(jack, name)?,
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
connections: connect.to_vec(),
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
MidiInput = (MidiIn => MidiOut) {
|
|
||||||
const KIND: &'static str = "MIDI In";
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self {
|
|
||||||
port: Self::register(jack, name)?,
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
connections: connect.to_vec(),
|
|
||||||
held: Arc::new(RwLock::new([false;128]))
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
MidiOutput = (MidiOut => MidiIn) {
|
|
||||||
const KIND: &'static str = "MIDI Out";
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized
|
|
||||||
{
|
|
||||||
let port = Self::register(jack, name)?;
|
|
||||||
let jack = jack.clone();
|
|
||||||
let name = name.as_ref().into();
|
|
||||||
let connections = connect.to_vec();
|
|
||||||
let port = Self {
|
|
||||||
jack,
|
|
||||||
port,
|
|
||||||
name,
|
|
||||||
connections,
|
|
||||||
held: Arc::new([false;128].into()),
|
|
||||||
note_buffer: vec![0;8],
|
|
||||||
output_buffer: vec![vec![];65536],
|
|
||||||
};
|
|
||||||
port.connect_to_matching()?;
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
|
||||||
|
|
||||||
/// Trait for thing that may receive MIDI.
|
|
||||||
pub trait HasMidiIns {
|
|
||||||
fn midi_ins (&self) -> &Vec<MidiInput>;
|
|
||||||
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput>;
|
|
||||||
/// Collect MIDI input from app ports (TODO preallocate large buffers)
|
|
||||||
fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> {
|
|
||||||
self.midi_ins().iter()
|
|
||||||
.map(|port|port.port().iter(scope)
|
|
||||||
.map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes)))
|
|
||||||
.collect::<Vec<_>>())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
fn midi_ins_with_sizes <'a> (&'a self) ->
|
|
||||||
impl Iterator<Item=(usize, &'a Arc<str>, &'a [Connect], usize, usize)> + Send + Sync + 'a
|
|
||||||
{
|
|
||||||
let mut y = 0;
|
|
||||||
self.midi_ins().iter().enumerate().map(move|(i, input)|{
|
|
||||||
let height = 1 + input.connections().len();
|
|
||||||
let data = (i, input.port_name(), input.connections(), y, y + height);
|
|
||||||
y += height;
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Trait for thing that may output MIDI.
|
|
||||||
pub trait HasMidiOuts {
|
|
||||||
fn midi_outs (&self) -> &Vec<MidiOutput>;
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput>;
|
|
||||||
fn midi_outs_with_sizes <'a> (&'a self) ->
|
|
||||||
impl Iterator<Item=(usize, &'a Arc<str>, &'a [Connect], usize, usize)> + Send + Sync + 'a
|
|
||||||
{
|
|
||||||
let mut y = 0;
|
|
||||||
self.midi_outs().iter().enumerate().map(move|(i, output)|{
|
|
||||||
let height = 1 + output.connections().len();
|
|
||||||
let data = (i, output.port_name(), output.connections(), y, y + height);
|
|
||||||
y += height;
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn midi_outs_emit (&mut self, scope: &ProcessScope) {
|
|
||||||
for port in self.midi_outs_mut().iter_mut() {
|
|
||||||
port.buffer_emit(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiOutput {
|
|
||||||
/// Clear the section of the output buffer that we will be using,
|
|
||||||
/// emitting "all notes off" at start of buffer if requested.
|
|
||||||
pub fn buffer_clear (&mut self, scope: &ProcessScope, reset: bool) {
|
|
||||||
let n_frames = (scope.n_frames() as usize).min(self.output_buffer.len());
|
|
||||||
for frame in &mut self.output_buffer[0..n_frames] {
|
|
||||||
frame.clear();
|
|
||||||
}
|
|
||||||
if reset {
|
|
||||||
all_notes_off(&mut self.output_buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Write a note to the output buffer
|
|
||||||
pub fn buffer_write <'a> (
|
|
||||||
&'a mut self,
|
|
||||||
sample: usize,
|
|
||||||
event: LiveEvent,
|
|
||||||
) {
|
|
||||||
self.note_buffer.fill(0);
|
|
||||||
event.write(&mut self.note_buffer).expect("failed to serialize MIDI event");
|
|
||||||
self.output_buffer[sample].push(self.note_buffer.clone());
|
|
||||||
// Update the list of currently held notes.
|
|
||||||
if let LiveEvent::Midi { ref message, .. } = event {
|
|
||||||
update_keys(&mut*self.held.write().unwrap(), message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Write a chunk of MIDI data from the output buffer to the output port.
|
|
||||||
pub fn buffer_emit (&mut self, scope: &ProcessScope) {
|
|
||||||
let samples = scope.n_frames() as usize;
|
|
||||||
let mut writer = self.port.writer(scope);
|
|
||||||
for (time, events) in self.output_buffer.iter().enumerate().take(samples) {
|
|
||||||
for bytes in events.iter() {
|
|
||||||
writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{
|
|
||||||
panic!("Failed to write MIDI data: {bytes:?}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiInput {
|
|
||||||
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
|
|
||||||
parse_midi_input(self.port().iter(scope))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return boxed iterator of MIDI events
|
|
||||||
pub fn parse_midi_input <'a> (input: ::jack::MidiIter<'a>)
|
|
||||||
-> Box<dyn Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> + 'a>
|
|
||||||
{
|
|
||||||
Box::new(input.map(|::jack::RawMidi { time, bytes }|(
|
|
||||||
time as usize,
|
|
||||||
LiveEvent::parse(bytes).unwrap(),
|
|
||||||
bytes
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
|
||||||
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
|
||||||
let mut buf = vec![];
|
|
||||||
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
|
|
||||||
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
|
|
||||||
evt.write(&mut buf).unwrap();
|
|
||||||
output[0].push(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update notes_in array
|
|
||||||
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
|
|
||||||
match message {
|
|
||||||
MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; }
|
|
||||||
MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; },
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<Vec<MidiInput>> + AsMut<Vec<MidiInput>>> HasMidiIns for T {
|
|
||||||
fn midi_ins (&self) -> &Vec<MidiInput> { self.as_ref() }
|
|
||||||
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput> { self.as_mut() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<Vec<MidiOutput>> + AsMut<Vec<MidiOutput>>> HasMidiOuts for T {
|
|
||||||
fn midi_outs (&self) -> &Vec<MidiOutput> { self.as_ref() }
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput> { self.as_mut() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: HasMidiIns + HasJack<'static>> AddMidiIn for T {
|
|
||||||
fn midi_in_add (&mut self) -> Usually<()> {
|
|
||||||
let index = self.midi_ins().len();
|
|
||||||
let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?;
|
|
||||||
self.midi_ins_mut().push(port);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trail for thing that may gain new MIDI ports.
|
|
||||||
impl<T: HasMidiOuts + HasJack<'static>> AddMidiOut for T {
|
|
||||||
fn midi_out_add (&mut self) -> Usually<()> {
|
|
||||||
let index = self.midi_outs().len();
|
|
||||||
let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?;
|
|
||||||
self.midi_outs_mut().push(port);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// May create new MIDI input ports.
|
|
||||||
pub trait AddMidiIn {
|
|
||||||
fn midi_in_add (&mut self) -> Usually<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// May create new MIDI output ports.
|
|
||||||
pub trait AddMidiOut {
|
|
||||||
fn midi_out_add (&mut self) -> Usually<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)] pub enum ConnectName {
|
|
||||||
/** Exact match */
|
|
||||||
Exact(Arc<str>),
|
|
||||||
/** Match regular expression */
|
|
||||||
RegExp(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
|
||||||
One,
|
|
||||||
All
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
|
||||||
Missing,
|
|
||||||
Disconnected,
|
|
||||||
Connected,
|
|
||||||
Mismatch,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Port connection manager.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let connect = tengri::Connect::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Connect {
|
|
||||||
pub name: Option<ConnectName>,
|
|
||||||
pub scope: Option<ConnectScope>,
|
|
||||||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
|
||||||
pub info: Arc<str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connect {
|
|
||||||
pub fn new <T: AsRef<str>> (
|
|
||||||
exact: Option<impl Iterator<Item = T>>,
|
|
||||||
re: Option<impl Iterator<Item = T>>,
|
|
||||||
re_all: Option<impl Iterator<Item = T>>,
|
|
||||||
) -> Vec<Self> {
|
|
||||||
let mut connections = vec![];
|
|
||||||
if let Some(exact ) = exact { for port in exact { connections.push(Self::exact(port)) } }
|
|
||||||
if let Some(regexp) = re { for port in regexp { connections.push(Self::regexp(port)) } }
|
|
||||||
if let Some(re_all) = re_all { for port in re_all { connections.push(Self::regexp_all(port)) } }
|
|
||||||
connections
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connect to this exact port
|
|
||||||
pub fn exact (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("=:{}", name.as_ref()).into();
|
|
||||||
let name = Some(Exact(name.as_ref().into()));
|
|
||||||
Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn regexp (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("~:{}", name.as_ref()).into();
|
|
||||||
let name = Some(RegExp(name.as_ref().into()));
|
|
||||||
Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn regexp_all (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("+:{}", name.as_ref()).into();
|
|
||||||
let name = Some(RegExp(name.as_ref().into()));
|
|
||||||
Self { name, scope: Some(All), status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info (&self) -> Arc<str> {
|
|
||||||
format!(" ({}) {} {}", {
|
|
||||||
let status = self.status.read().unwrap();
|
|
||||||
let mut ok = 0;
|
|
||||||
for (_, _, state) in status.iter() {
|
|
||||||
if *state == Connected {
|
|
||||||
ok += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
format!("{ok}/{}", status.len())
|
|
||||||
}, match self.scope {
|
|
||||||
None => "x",
|
|
||||||
Some(One) => " ",
|
|
||||||
Some(All) => "*",
|
|
||||||
}, match &self.name {
|
|
||||||
None => format!("x"),
|
|
||||||
Some(Exact(name)) => format!("= {name}"),
|
|
||||||
Some(RegExp(name)) => format!("~ {name}"),
|
|
||||||
}).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect_midi_ins <T: AsRef<str>> (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
name: &T,
|
|
||||||
midi_from: &[T],
|
|
||||||
midi_from_re: Option<&[T]>,
|
|
||||||
) -> Usually<Vec<MidiInput>> {
|
|
||||||
Ok(Connect::new(
|
|
||||||
Some(midi_from.into_iter()),
|
|
||||||
Some([].into_iter()),
|
|
||||||
midi_from_re.map(|x|x.into_iter())).iter().enumerate()
|
|
||||||
.map(|(index, connect)|jack.midi_in(&format!("{}/{index}", name.as_ref()), &[connect.clone()]))
|
|
||||||
.collect::<Result<_, _>>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect_midi_outs <T: AsRef<str>> (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
name: &T,
|
|
||||||
midi_to: &[T],
|
|
||||||
midi_to_re: Option<&[T]>,
|
|
||||||
) -> Usually<Vec<MidiOutput>> {
|
|
||||||
Ok(Connect::new(
|
|
||||||
Some(midi_to.into_iter()),
|
|
||||||
Some([].into_iter()),
|
|
||||||
midi_to_re.map(|x|x.into_iter())).iter().enumerate()
|
|
||||||
.map(|(index, connect)|jack.midi_out(&format!("{index}/{}", name.as_ref()), &[connect.clone()]))
|
|
||||||
.collect::<Result<_, _>>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect_audio_ins <T: AsRef<str>> (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
name: &T,
|
|
||||||
audio_from: &[T],
|
|
||||||
audio_from_re: Option<&[T]>,
|
|
||||||
) -> Usually<Vec<AudioInput>> {
|
|
||||||
Ok(Connect::new(
|
|
||||||
Some(audio_from.into_iter()),
|
|
||||||
Some([].into_iter()),
|
|
||||||
audio_from_re.map(|x|x.into_iter())).iter().enumerate()
|
|
||||||
.map(|(index, connect)|jack.audio_in(&format!("{}/{index}", name.as_ref()), &[connect.clone()]))
|
|
||||||
.collect::<Result<_, _>>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect_audio_outs <T: AsRef<str>> (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
name: &T,
|
|
||||||
audio_to: &[T],
|
|
||||||
audio_to_re: Option<&[T]>,
|
|
||||||
) -> Usually<Vec<AudioOutput>> {
|
|
||||||
Ok(Connect::new(
|
|
||||||
Some(audio_to.into_iter()),
|
|
||||||
Some([].into_iter()),
|
|
||||||
audio_to_re.map(|x|x.into_iter())).iter().enumerate()
|
|
||||||
.map(|(index, connect)|jack.audio_out(&format!("{index}/{}", name.as_ref()), &[connect.clone()]))
|
|
||||||
.collect::<Result<_, _>>()?)
|
|
||||||
}
|
|
||||||
517
src/term.rs
Normal file
517
src/term.rs
Normal file
|
|
@ -0,0 +1,517 @@
|
||||||
|
use crate::*;
|
||||||
|
use Color::*;
|
||||||
|
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||||
|
//use rand::distributions::uniform::UniformSampler;
|
||||||
|
pub(crate) use ::{
|
||||||
|
std::{
|
||||||
|
io::{stdout, Write},
|
||||||
|
time::Duration,
|
||||||
|
},
|
||||||
|
ratatui::{
|
||||||
|
prelude::{Style, Position, Backend, Color},
|
||||||
|
style::{Modifier},
|
||||||
|
backend::{CrosstermBackend, ClearType},
|
||||||
|
layout::{Size, Rect},
|
||||||
|
buffer::{Buffer, Cell},
|
||||||
|
crossterm::{
|
||||||
|
ExecutableCommand,
|
||||||
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||||
|
//event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crossterm::event::read,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod border; pub use self::border::*;
|
||||||
|
mod buffer; pub use self::buffer::*;
|
||||||
|
mod button; pub use self::button::*;
|
||||||
|
mod event; pub use self::event::*;
|
||||||
|
mod keys; pub use self::keys::*;
|
||||||
|
mod modify; pub use self::modify::*;
|
||||||
|
mod phat; pub use self::phat::*;
|
||||||
|
mod repeat; pub use self::repeat::*;
|
||||||
|
mod scroll; pub use self::scroll::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "term")]
|
||||||
|
#[macro_export] macro_rules! tui_app {
|
||||||
|
($Struct:ident { $($fields:tt)* }) => {
|
||||||
|
#[dizzle::namespace(bool)]
|
||||||
|
#[dizzle::namespace(u8)]
|
||||||
|
#[dizzle::namespace(u16)]
|
||||||
|
#[dizzle::namespace(Option<u16>)]
|
||||||
|
#[dizzle::namespace(Color Tui::eval_color_expr)]
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct $Struct { $($fields)* }
|
||||||
|
tui_main!($Struct { ..Default::default() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement standard [main] entrypoint for TUI apps.
|
||||||
|
#[cfg(feature = "term")]
|
||||||
|
#[macro_export] macro_rules! tui_main {
|
||||||
|
($state:expr) => {
|
||||||
|
pub fn main () -> Usually<()> {
|
||||||
|
tengri::Tui::setup_panic();
|
||||||
|
tengri::Tui::run_main(::std::sync::Arc::new(::std::sync::RwLock::new($state)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable TUI output for state struct.
|
||||||
|
#[cfg(feature = "term")]
|
||||||
|
#[macro_export] macro_rules! tui_view {
|
||||||
|
($self:ident: $State:ty $body:block) => {
|
||||||
|
impl tengri::View<Tui> for $State {
|
||||||
|
fn view (&$self) -> impl tengri::Draw<tengri::Tui> $body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable TUI keyboard input for main state struct.
|
||||||
|
#[cfg(feature = "term")]
|
||||||
|
#[macro_export] macro_rules! tui_keys {
|
||||||
|
($self:ident:$State:ty,$input:ident $($body:tt)+) => {
|
||||||
|
impl dizzle::Apply<TuiEvent, Usually<()>> for $State {
|
||||||
|
fn apply (&mut $self, $input: &tengri::TuiEvent) -> Usually<()> $($body)+
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Terminal output.
|
||||||
|
pub enum Tui {
|
||||||
|
Draw (
|
||||||
|
/// Ratatui buffer; area is screen size
|
||||||
|
Buffer,
|
||||||
|
/// Current draw area
|
||||||
|
XYWH<u16>
|
||||||
|
),
|
||||||
|
Layout (
|
||||||
|
/// Draw area; no buffer
|
||||||
|
XYWH<u16>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen for Tui {
|
||||||
|
type Unit = u16;
|
||||||
|
fn area (&self) -> XYWH<Self::Unit> {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
fn clip <T> (
|
||||||
|
&mut self, area: impl Into<Option<XYWH<u16>>>, draw: &impl Fn(&mut Self)->T,
|
||||||
|
) -> T {
|
||||||
|
let prev = self.area();
|
||||||
|
if let Some(area) = area.into() {
|
||||||
|
*self.area_mut() = area.into();
|
||||||
|
}
|
||||||
|
let result = draw(self);
|
||||||
|
*self.area_mut() = prev;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
fn size (
|
||||||
|
&mut self, area: impl Into<Option<XYWH<u16>>>, draw: impl Draw<Self>,
|
||||||
|
) -> Perhaps<XYWH<Self::Unit>> {
|
||||||
|
Self::Layout(self.area()).clip(area, &|to|draw.draw(to))
|
||||||
|
}
|
||||||
|
fn draw (
|
||||||
|
&mut self, area: impl Into<Option<XYWH<u16>>>, draw: impl Draw<Tui>
|
||||||
|
) -> Perhaps<XYWH<Self::Unit>> {
|
||||||
|
self.clip(area, &|to|draw.draw(to))
|
||||||
|
}
|
||||||
|
|
||||||
|
//fn draw (
|
||||||
|
//&mut self,
|
||||||
|
//area: impl Into<Option<XYWH<u16>>>,
|
||||||
|
//draw: impl Draw<Self>
|
||||||
|
//) -> Perhaps<XYWH<Self::Unit>> {
|
||||||
|
//let prev = self.area();
|
||||||
|
//if let Some(area) = area.into() {
|
||||||
|
//*self.area_mut() = area.into();
|
||||||
|
//}
|
||||||
|
//let result = draw.draw(self);
|
||||||
|
//*self.area_mut() = prev;
|
||||||
|
//result
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
pub fn setup_panic () {
|
||||||
|
use ::std::panic::{set_hook, PanicHookInfo};
|
||||||
|
use ::better_panic::{Settings, Verbosity};
|
||||||
|
let panic = Settings::auto()
|
||||||
|
.verbosity(Verbosity::Full)
|
||||||
|
.create_panic_handler();
|
||||||
|
set_hook(Box::new(move |info: &PanicHookInfo|{
|
||||||
|
let _ = Tui::teardown(&mut stdout());
|
||||||
|
panic(info);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
pub fn run_main <T> (state: Arc<RwLock<T>>) -> Usually<()> where
|
||||||
|
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
Exit::run(|exit|{
|
||||||
|
let scan = Duration::from_millis(100);
|
||||||
|
let frame = Duration::from_millis(10);
|
||||||
|
let (_input, output) = Tui::io(exit.as_ref(), &state, scan, frame, std::io::stdout())?;
|
||||||
|
let _ = output.join();
|
||||||
|
Tui::teardown(&mut stdout())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Spawn the TUI input and output threadsl.
|
||||||
|
pub fn io <
|
||||||
|
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static,
|
||||||
|
W: Write + Send + Sync + 'static,
|
||||||
|
> (
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
state: &Arc<RwLock<T>>,
|
||||||
|
poll: Duration,
|
||||||
|
sleep: Duration,
|
||||||
|
output: W,
|
||||||
|
) -> Result<(Task, Task), Box<dyn std::error::Error>> {
|
||||||
|
Ok((
|
||||||
|
Tui::input(exited, state, poll)?,
|
||||||
|
Tui::output(exited, state, sleep, output)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||||
|
pub fn input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
|
||||||
|
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||||
|
) -> Result<Task, std::io::Error> {
|
||||||
|
let exited = exited.clone();
|
||||||
|
let state = state.clone();
|
||||||
|
Task::new_poll(exited.clone(), poll, move |_| {
|
||||||
|
let event = read().unwrap();
|
||||||
|
if Exit::is(&event) {
|
||||||
|
exited.store(true, Relaxed);
|
||||||
|
} else if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
|
||||||
|
panic!("{e}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn teardown <W: Write> (backend: &mut W) -> Usually<()> {
|
||||||
|
use ::ratatui::backend::Backend;
|
||||||
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
CrosstermBackend::new(backend).show_cursor()?;
|
||||||
|
disable_raw_mode().map_err(Into::into)
|
||||||
|
}
|
||||||
|
pub fn new (width: u16, height: u16) -> Self {
|
||||||
|
Self::Draw(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
|
||||||
|
}
|
||||||
|
pub fn resize <W: Write> (&mut self, back: &mut CrosstermBackend<W>, width: u16, height: u16) {
|
||||||
|
let size = Rect { x: 0, y: 0, width, height };
|
||||||
|
if let Self::Draw(buffer, ..) = self {
|
||||||
|
if buffer.area != size {
|
||||||
|
back.clear_region(ClearType::All).unwrap();
|
||||||
|
buffer.resize(size);
|
||||||
|
buffer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn redraw <'b, W: Write> (
|
||||||
|
&'b mut self,
|
||||||
|
back: &mut CrosstermBackend<W>,
|
||||||
|
next: &'b mut Self
|
||||||
|
) {
|
||||||
|
if let Self::Draw(prev, ..) = self && let Self::Draw(next, ..) = next {
|
||||||
|
let updates = prev.diff(&next);
|
||||||
|
back.draw(updates.into_iter()).expect("failed to render");
|
||||||
|
Backend::flush(back).expect("failed to flush output new");
|
||||||
|
std::mem::swap(prev, next);
|
||||||
|
next.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
|
||||||
|
let XYWH(x0, y0, w, h) = self.area();
|
||||||
|
if let Self::Draw(buffer, ..) = self {
|
||||||
|
for row in 0..h {
|
||||||
|
let y = y0 + row;
|
||||||
|
for col in 0..w {
|
||||||
|
let x = x0 + col;
|
||||||
|
if x < buffer.area.width && y < buffer.area.height {
|
||||||
|
if let Some(cell) = buffer.cell_mut(Position { x, y }) {
|
||||||
|
callback(cell, col, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.xywh()
|
||||||
|
}
|
||||||
|
pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
||||||
|
if let Self::Draw(buffer, ..) = self {
|
||||||
|
let text = text.as_ref();
|
||||||
|
let style = style.unwrap_or(Style::default());
|
||||||
|
if x < buffer.area.width && y < buffer.area.height {
|
||||||
|
buffer.set_string(x, y, text, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
||||||
|
if let Self::Draw(buffer, ..) = self {
|
||||||
|
for cell in buffer.content.iter_mut() {
|
||||||
|
cell.fg = fg;
|
||||||
|
cell.bg = bg;
|
||||||
|
cell.modifier = modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let state = std::sync::Arc::new(std::sync::RwLock::new(()));
|
||||||
|
/// let _ = tengri::Exit::run(|exit|{
|
||||||
|
/// tengri::Tui::output(
|
||||||
|
/// exit.as_ref(),
|
||||||
|
/// &state,
|
||||||
|
/// std::time::Duration::from_millis(10),
|
||||||
|
/// std::io::stdout()
|
||||||
|
/// )
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn output <
|
||||||
|
W: Write + Send + Sync + 'static, T: View<Tui> + Send + Sync + 'static
|
||||||
|
> (
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
state: &Arc<RwLock<T>>,
|
||||||
|
sleep: Duration,
|
||||||
|
output: W,
|
||||||
|
) -> Usually<Task> {
|
||||||
|
let state = state.clone();
|
||||||
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
CrosstermBackend::new(stdout()).hide_cursor()?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut backend = CrosstermBackend::new(output);
|
||||||
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
|
let mut prev = Tui::new(width, height);
|
||||||
|
let mut next = Tui::new(width, height);
|
||||||
|
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
||||||
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
|
if let Ok(state) = state.try_read() {
|
||||||
|
prev.resize(&mut backend, width, height);
|
||||||
|
state.view().draw(&mut next).expect("draw failed"); // TODO draw error
|
||||||
|
prev.redraw(&mut backend, &mut next);
|
||||||
|
}
|
||||||
|
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||||
|
prev.blit(&timer, 0, 0, Some(Style::default()));
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
/// Draw TUI content or its error message.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// for variant in [
|
||||||
|
/// Ok(Some("hello")),
|
||||||
|
/// Ok(None),
|
||||||
|
/// Err("fail".into()),
|
||||||
|
/// ] {
|
||||||
|
/// let _ = tengri::Tui::catcher(&variant);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn catcher <T: Draw<Tui>> (result: &Usually<T>) -> impl Draw<Tui> {
|
||||||
|
draw(move|to: &mut Tui|match result {
|
||||||
|
Ok(content) => content.draw(to),
|
||||||
|
Err(e) => {
|
||||||
|
let err_fg = Color::Rgb(255,224,244);
|
||||||
|
let err_bg = Color::Rgb(96, 24, 24);
|
||||||
|
let title = east(bold(true, "upsi daisy. "), "rendering failed.");
|
||||||
|
let error = east("\"why?\" ", bold(true, format!("{e}")));
|
||||||
|
fg(err_fg, bg(err_bg, south(title, error))).draw(to)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "text")]
|
||||||
|
/// Write a line of text
|
||||||
|
///
|
||||||
|
/// TODO: do a paragraph (handle newlines)
|
||||||
|
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16)
|
||||||
|
-> Perhaps<XYWH<u16>>
|
||||||
|
{
|
||||||
|
let text = text.as_ref();
|
||||||
|
let mut string_width: u16 = 0;
|
||||||
|
for character in text.chars() {
|
||||||
|
let x = x0 + string_width;
|
||||||
|
let character_width = character.width().unwrap_or(0) as u16;
|
||||||
|
string_width += character_width;
|
||||||
|
if string_width > max_width {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let Self::Draw(buffer, ..) = self {
|
||||||
|
if let Some(cell) = buffer.cell_mut(ratatui::prelude::Position { x, y }) {
|
||||||
|
cell.set_char(character);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x0, y, string_width, 1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer (&mut self) -> Option<&mut Buffer> {
|
||||||
|
if let Tui::Draw(buffer, _) = self {
|
||||||
|
Some(buffer)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_char (&mut self, x: u16, y: u16, c: char) {
|
||||||
|
self.buffer()
|
||||||
|
.and_then(|buff|buff.cell_mut(Position { x, y }))
|
||||||
|
.map(|cell|cell.set_char(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get mutable reference to current clipping area
|
||||||
|
fn area_mut (&mut self) -> &mut XYWH<u16> {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
|
||||||
|
//impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
||||||
|
//impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
||||||
|
impl AsRef<XYWH<u16>> for Tui {
|
||||||
|
fn as_ref (&self) -> &XYWH<u16> {
|
||||||
|
match self {
|
||||||
|
Self::Draw(_, area) => area,
|
||||||
|
Self::Layout(area) => area
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wide<u16> for Tui {
|
||||||
|
fn w (&self) -> u16 { self.as_ref().2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tall<u16> for Tui {
|
||||||
|
fn h (&self) -> u16 { self.as_ref().3 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Xy<u16> for Tui {
|
||||||
|
fn x (&self) -> u16 { self.as_ref().0 }
|
||||||
|
fn y (&self) -> u16 { self.as_ref().1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasOrigin for Tui {
|
||||||
|
fn origin (&self) -> Azimuth { Azimuth::NW }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Tui> for XYWH<u16> {
|
||||||
|
fn from (screen: &Tui) -> XYWH<u16> {
|
||||||
|
match screen {
|
||||||
|
Tui::Draw(_, area) => *area,
|
||||||
|
Tui::Layout(area) => *area,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a: 'b, 'b> From<&'a mut Tui> for &'b mut XYWH<u16> {
|
||||||
|
fn from (screen: &'a mut Tui) -> &'b mut XYWH<u16> {
|
||||||
|
match screen {
|
||||||
|
Tui::Draw(_, area) => area,
|
||||||
|
Tui::Layout(area) => area,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||||
|
draw(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
|
||||||
|
cell.set_char(c);
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "text")] impl_draw!(|self: String, to: Tui|{
|
||||||
|
self.as_str().draw(to)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "text")] impl_draw!(|self: std::sync::Arc<str>, to: Tui|{
|
||||||
|
self.as_ref().draw(to)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "text")] impl_draw!(<T: AsRef<str>,>|self: TrimString<T>, to: Tui|{
|
||||||
|
self.as_ref().draw(to)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "text")]
|
||||||
|
impl Draw<Tui> for &str {
|
||||||
|
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||||
|
let XYWH(x, y, w, h) = to.area();
|
||||||
|
let mut max_w = 0u16;
|
||||||
|
let mut max_h = 0u16;
|
||||||
|
for (index, line) in self.split("\n").enumerate() {
|
||||||
|
max_h += 1;
|
||||||
|
max_w = max_w.max(line.len() as u16);
|
||||||
|
let _ = to.text(&line, x, y + index as u16, width_chars_max(w, line) as u16)?;
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, w.min(max_w), h.min(max_h))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "text")]
|
||||||
|
impl<'t, T: AsRef<str>> Draw<Tui> for TrimStr<'_, T> {
|
||||||
|
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||||
|
let text = self.1.as_ref();
|
||||||
|
let area = layout_text_u16(text, to.area())?.unwrap();
|
||||||
|
let XYWH(x, y, w, ..) = to.area();
|
||||||
|
let mut width: u16 = 1;
|
||||||
|
let mut chars = text.chars();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if width > self.0 || width > w {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
to.set_char(x + width - 1, y, c);
|
||||||
|
width += c.width().unwrap_or(0) as u16;
|
||||||
|
}
|
||||||
|
let XYWH(x, y, w, ..) = XYWH(
|
||||||
|
to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()
|
||||||
|
);
|
||||||
|
to.text(&self.as_ref(), x, y, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "text")]
|
||||||
|
fn layout_text_u16 (text: &str, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
|
||||||
|
let XYWH(x, y, w, h) = area;
|
||||||
|
let mut max_w = 0u16;
|
||||||
|
let mut max_h = 0u16;
|
||||||
|
for line in text.split("\n") {
|
||||||
|
max_h += 1;
|
||||||
|
max_w = max_w.max(line.len() as u16);
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, w.min(max_w), h.min(max_h))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShowSize;
|
||||||
|
|
||||||
|
impl Draw<Tui> for ShowSize {
|
||||||
|
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||||
|
let area = to.area();
|
||||||
|
let info = format!("{area:?}");
|
||||||
|
to.text(&info, area.0, area.1, info.len() as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShowSizeOf<T>(pub T);
|
||||||
|
|
||||||
|
impl<T: Draw<Tui>> Draw<Tui> for ShowSizeOf<T> {
|
||||||
|
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||||
|
Ok(self.0.draw(to)?.map(|used|{
|
||||||
|
let _ = to.text(&format!("{used:?}"), used.0, used.1, u16::MAX);
|
||||||
|
used
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "eval")] fn_kw_layout_tui!(kw_tui_text |state, output, expr| {
|
||||||
|
Ok(matches!(expr.head()?, Some("text")).then(||{
|
||||||
|
if let Some(src) = expr.tail().src()? {
|
||||||
|
src.draw(output)
|
||||||
|
} else {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "text")] #[cfg(test)] #[test] fn test_layout_text_u16 () -> Usually<()> {
|
||||||
|
assert_eq!(layout_text_u16("foo", XYWH(5, 6, 10, 10))?, Some(XYWH(5, 6, 3, 1)));
|
||||||
|
assert_eq!(layout_text_u16("foo\nbarz", XYWH(5, 6, 10, 10))?, Some(XYWH(5, 6, 4, 2)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -48,8 +48,8 @@ macro_rules! border {
|
||||||
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
||||||
//impl Layout<Tui> for $T {}
|
//impl Layout<Tui> for $T {}
|
||||||
impl Draw<Tui> for $T {
|
impl Draw<Tui> for $T {
|
||||||
fn draw (self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
|
fn draw (&self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
|
||||||
when(self.enabled(), draw(|to: &mut Tui|BorderStyle::draw(self, to).map(Some))).draw(to)
|
when(self.enabled(), draw(|to: &mut Tui|BorderStyle::draw(*self, to).map(Some))).draw(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)+}
|
)+}
|
||||||
|
|
|
||||||
156
src/term/modify.rs
Normal file
156
src/term/modify.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
use crate::*;
|
||||||
|
use Color::*;
|
||||||
|
|
||||||
|
fn_kw_layout_tui!(kw_tui_fg |state, output, expr| {
|
||||||
|
Ok(matches!(expr.head()?, Some("fg")).then(||{
|
||||||
|
let args = expr.tail();
|
||||||
|
let arg0 = args.head();
|
||||||
|
let tail0 = args.tail();
|
||||||
|
let arg1 = tail0.head();
|
||||||
|
if let Some(color) = state.namespace(arg0?.expect("fg: expected arg 0 (color)"))? {
|
||||||
|
fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(output)
|
||||||
|
} else {
|
||||||
|
return Err(format!("fg: {arg0:?}: not a color").into())
|
||||||
|
}
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
|
||||||
|
fn_kw_layout_tui!(kw_tui_bg |state, output, expr| {
|
||||||
|
Ok(matches!(expr.head()?, Some("bg")).then(||{
|
||||||
|
let args = expr.tail();
|
||||||
|
let arg0 = args.head();
|
||||||
|
let tail0 = args.tail();
|
||||||
|
let arg1 = tail0.head();
|
||||||
|
if let Some(color) = state.namespace(arg0?.expect("bg: expected arg 0 (color)"))? {
|
||||||
|
bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(output)
|
||||||
|
} else {
|
||||||
|
return Err(format!("bg: {arg0:?}: not a color").into())
|
||||||
|
}
|
||||||
|
}).transpose()?.flatten())
|
||||||
|
});
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
pub fn eval_color_expr (state: &impl for<'a> Namespace<'a, u8>, src: impl Language)
|
||||||
|
-> Perhaps<Color>
|
||||||
|
{
|
||||||
|
if let Some(expr) = src.expr()? {
|
||||||
|
match (expr.head()?, expr.tail()?) {
|
||||||
|
(Some("g"), Some(tail)) => {
|
||||||
|
let n: u8 = state.namespace(tail.head().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?;
|
||||||
|
Ok(Some(Color::Rgb(n, n, n)))
|
||||||
|
},
|
||||||
|
(Some("rgb"), Some(tail)) => {
|
||||||
|
let r: u8 = state.namespace(tail.head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::Domain("not red"))?;
|
||||||
|
let g: u8 = state.namespace(tail.tail().head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::Domain("not green"))?;
|
||||||
|
let b: u8 = state.namespace(tail.tail().tail().head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::Domain("not blue"))?;
|
||||||
|
Ok(Some(Color::Rgb(r, g, b)))
|
||||||
|
},
|
||||||
|
(Some(_), _) => return Err(
|
||||||
|
format!("not a color expression: {expr}").into()
|
||||||
|
),
|
||||||
|
(None, _) => return Err(
|
||||||
|
format!("not a color expression: {expr}").into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply foreground color.
|
||||||
|
pub const fn fg (fg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
draw(move|to: &mut Tui|{
|
||||||
|
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
||||||
|
item.draw(to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply background color.
|
||||||
|
pub const fn bg (bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
Background(bg, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Background<T>(Color, T);
|
||||||
|
|
||||||
|
impl<T: Draw<Tui>> Draw<Tui> for Background<T> {
|
||||||
|
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||||
|
if let Some(size) = to.size(None, &self.1)? {
|
||||||
|
to.draw(Some(size), draw(|to: &mut Tui|{
|
||||||
|
to.update(&|cell,_,_|{ cell.set_bg(self.0); });
|
||||||
|
self.1.draw(to)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fg_bg (fg: Color, bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
draw(move|to: &mut Tui|{
|
||||||
|
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
||||||
|
item.draw(to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw contents with modifier applied.
|
||||||
|
pub const fn modify (on: bool, modifier: Modifier, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
draw(move|to: &mut Tui|{
|
||||||
|
fill_mod(on, modifier).draw(to)?;
|
||||||
|
item.draw(to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
||||||
|
draw(move|to: &mut Tui|Ok(Some({
|
||||||
|
if on {
|
||||||
|
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
||||||
|
} else {
|
||||||
|
to.update(&|cell,_,_|cell.modifier.remove(modifier))
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw contents with bold modifier applied.
|
||||||
|
pub const fn bold (on: bool, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
modify(on, Modifier::BOLD, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
|
||||||
|
draw(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
|
||||||
|
to.update(&|cell,_,_|{
|
||||||
|
cell.modifier.insert(Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = color;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
to.update(&|cell,_,_|{
|
||||||
|
cell.modifier.remove(Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = Reset;
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn tui_color_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||||
|
pub const fn tui_bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
||||||
|
pub const fn tui_bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
||||||
|
pub const fn tui_bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
||||||
|
pub const fn tui_border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
||||||
|
pub const fn tui_border_fg (f: bool) -> Color { if f { tui_bo1() } else { tui_bo2() } }
|
||||||
|
pub const fn tui_brown () -> Color { Color::Rgb(128,255,0) }
|
||||||
|
pub const fn tui_electric () -> Color { Color::Rgb(0,255,128) }
|
||||||
|
pub const fn tui_g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
||||||
|
pub const fn tui_green () -> Color { Color::Rgb(0,255,0) }
|
||||||
|
pub const fn tui_mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
||||||
|
pub const fn tui_mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
||||||
|
pub const fn tui_null () -> Color { Color::Reset }
|
||||||
|
pub const fn tui_orange () -> Color { Color::Rgb(255,128,0) }
|
||||||
|
pub const fn tui_red () -> Color { Color::Rgb(255,0, 0) }
|
||||||
|
pub const fn tui_separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
||||||
|
pub const fn tui_status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||||
|
pub const fn tui_ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
||||||
|
pub const fn tui_ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
||||||
|
pub const fn tui_title_fg (f: bool) -> Color { if f { tui_ti1() } else { tui_ti2() } }
|
||||||
|
pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) }
|
||||||
63
src/time.rs
63
src/time.rs
|
|
@ -1,63 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Performance counter
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PerfModel {
|
|
||||||
pub clock: quanta::Clock,
|
|
||||||
/// Measurement has a small cost. Disable it here.
|
|
||||||
pub enabled: bool,
|
|
||||||
// In nanoseconds. Time used by last iteration.
|
|
||||||
pub used: AtomicF64,
|
|
||||||
// In microseconds. Max prescribed time for iteration (frame, chunk...).
|
|
||||||
pub window: AtomicF64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_default!(PerfModel: Self {
|
|
||||||
enabled: true,
|
|
||||||
clock: quanta::Clock::new(),
|
|
||||||
used: Default::default(),
|
|
||||||
window: Default::default(),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl PerfModel {
|
|
||||||
pub fn get_t0 (&self) -> Option<u64> {
|
|
||||||
if self.enabled {
|
|
||||||
Some(self.clock.raw())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
|
||||||
if let Some(t0) = t0 {
|
|
||||||
if self.enabled {
|
|
||||||
Some(self.clock.delta(t0, self.clock.raw()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
|
|
||||||
if let Some(t0) = t0 {
|
|
||||||
let t1 = self.clock.raw();
|
|
||||||
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
|
|
||||||
self.window.store(microseconds, Relaxed,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn percentage (&self) -> Option<f64> {
|
|
||||||
let window = self.window.load(Relaxed) * 1000.0;
|
|
||||||
if window > 0.0 {
|
|
||||||
let used = self.used.load(Relaxed);
|
|
||||||
Some(100.0 * used / window)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn cycle <F: FnMut(&Self)->T, T> (&self, call: &mut F) -> T {
|
|
||||||
let t0 = self.get_t0();
|
|
||||||
let result = call(self);
|
|
||||||
let _t1 = self.get_t1(t0).unwrap();
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue