mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-08-07 14:16:56 +02:00
This commit is contained in:
parent
cf67185ffd
commit
799e497622
10 changed files with 456 additions and 460 deletions
2
Justfile
2
Justfile
|
|
@ -13,7 +13,7 @@ cov:
|
||||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' \
|
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' \
|
||||||
time cargo test -j4 --workspace --profile coverage
|
time cargo test -j4 --workspace --profile coverage
|
||||||
rm -rf target/coverage/html || true
|
rm -rf target/coverage/html || true
|
||||||
time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t html -o target/coverage/html
|
time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t html -o target/coverage/html && reset
|
||||||
|
|
||||||
cov-md:
|
cov-md:
|
||||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' \
|
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' \
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,7 @@ impl_keywords!(Tui, XYWH<u16>, State [
|
||||||
kw_push,
|
kw_push,
|
||||||
kw_tui_text,
|
kw_tui_text,
|
||||||
kw_tui_fg,
|
kw_tui_fg,
|
||||||
kw_tui_bg,
|
kw_tui_bg
|
||||||
kw_color_g,
|
|
||||||
kw_color_rgb,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
impl Interpret<Tui, Option<XYWH<u16>>> for State {
|
impl Interpret<Tui, Option<XYWH<u16>>> for State {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ in pkgs.mkShell.override {
|
||||||
pkgs.jack2
|
pkgs.jack2
|
||||||
];
|
];
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
|
pkgs.grcov
|
||||||
pkgs.pkg-config
|
pkgs.pkg-config
|
||||||
pkgs.clang
|
pkgs.clang
|
||||||
pkgs.libclang
|
pkgs.libclang
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,53 @@ impl Sizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod area; pub use self::area::*;
|
mod area; pub use self::area::*;
|
||||||
mod axis; pub use self::axis::*;
|
mod axis; pub use self::axis::*;
|
||||||
mod azimuth; pub use self::azimuth::*;
|
mod azimuth; pub use self::azimuth::*;
|
||||||
|
|
|
||||||
|
|
@ -1,138 +1,125 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
macro_rules! def_layout_modifier {
|
/// Use whole drawing area along one or both axes.
|
||||||
(
|
pub enum Full<T: Screen, I: Draw<T>> {
|
||||||
$Trait:ident ($fn_x:ident $fn_y:ident $fn_xy:ident),
|
__(PhantomData<T>),
|
||||||
$Struct:ident { $X:ident $Y:ident $XY:ident } $kw_name:ident $name:literal
|
W(I),
|
||||||
|$self:ident, $to:ident| $body:block
|
H(I),
|
||||||
) => {
|
WH(I),
|
||||||
impl<S: Screen, T: Draw<S>> $Trait<S> for T {}
|
}
|
||||||
|
|
||||||
pub trait $Trait<S: Screen>: Draw<S> + Sized {
|
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
|
||||||
fn $fn_x <N: Into<Option<S::Unit>> + Copy> (self, x: N)
|
let XYWH(x0, y0, w0, h0) = to.area();
|
||||||
-> $Struct<S, Self, N> { $Struct::$X(self, x) }
|
let item = match self {
|
||||||
fn $fn_y <N: Into<Option<S::Unit>> + Copy> (self, y: N)
|
Self::W(i) => i, Self::H(i) => i, Self::WH(i) => i, _ => unreachable!()
|
||||||
-> $Struct<S, Self, N> { $Struct::$Y(self, y) }
|
};
|
||||||
fn $fn_xy <N: Into<Option<S::Unit>> + Copy> (self, x: N, y: N)
|
let (x1, y1, w1, h1) = match self {
|
||||||
-> $Struct<S, Self, N> { $Struct::$XY(self, x, y) }
|
Self::W(..) => (Some(x0), None, Some(w0), None),
|
||||||
}
|
Self::H(..) => (None, Some(y0), None, Some(h0)),
|
||||||
|
Self::WH(..) => (Some(x0), Some(y0), Some(w0), Some(h0)),
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
Ok(to.draw(to.area(), item)?.map(|XYWH(x2, y2, w2, h2)|XYWH(
|
||||||
|
x1.unwrap_or(x2), y1.unwrap_or(y2), w1.unwrap_or(w2), h1.unwrap_or(h2),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
|
||||||
pub enum $Struct<S: Screen, I: Draw<S>, X: Into<Option<S::Unit>>> {
|
#[cfg(test)] #[test] fn test_layout_full () -> Usually<()> {
|
||||||
__(PhantomData<S>), $X(I, X), $Y(I, X), $XY(I, X, X),
|
assert_eq!(check_layout("1")?, Some(XYWH(1u16, 1, 1, 1)));
|
||||||
}
|
assert_eq!(check_layout("1".full_w())?, Some(XYWH(1u16, 1, 80, 1)));
|
||||||
|
assert_eq!(check_layout("1".full_h())?, Some(XYWH(1u16, 1, 1, 25)));
|
||||||
impl <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for $Struct<S, I, X> {
|
assert_eq!(check_layout("1".full_wh())?, Some(XYWH(1u16, 1, 80, 25)));
|
||||||
fn draw (&$self, $to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
Ok(())
|
||||||
$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!(
|
def_layout_modifier!(
|
||||||
CanExact (exact_w exact_h exact_wh),
|
LayoutExact (exact_w exact_h exact_wh),
|
||||||
Exact { W H WH } kw_exact "exact" |self, to| {
|
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(), None),
|
||||||
|
Self::H(item, h) => (item, None, (*h).into()),
|
||||||
|
Self::WH(item, w, h) => (item, (*w).into(), (*h).into()),
|
||||||
|
_ => return Ok(None)
|
||||||
|
};
|
||||||
|
to.draw(XYWH(x, y, w.unwrap_or(w0), h.unwrap_or(h0)), item)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_layout_exact () -> Usually<()> {
|
||||||
|
assert_eq!(check_layout("FOOBAR\nKILROY")?, Some(XYWH(1, 1, 6, 2)));
|
||||||
|
assert_eq!(check_layout("FOOBAR\nKILROY".exact_w(3))?, Some(XYWH(1, 1, 3, 2)));
|
||||||
|
assert_eq!(check_layout("FOOBAR\nKILROY".exact_h(1))?, Some(XYWH(1, 1, 6, 1)));
|
||||||
|
assert_eq!(check_layout("FOOBAR\nKILROY".exact_wh(2, 1))?, Some(XYWH(1, 1, 2, 1)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
LayoutMin (min_w min_h min_wh),
|
||||||
|
Min { W H WH } kw_min "min" |self, to| {
|
||||||
|
let XYWH(x0, y0, w0, h0) = to.area();
|
||||||
|
let (item, w1, h1) = match self {
|
||||||
|
Self::W(item, w1) => (item, (*w1).into(), None),
|
||||||
|
Self::H(item, h1) => (item, None, (*h1).into()),
|
||||||
|
Self::WH(item, w1, h1) => (item, (*w1).into(), (*h1).into()),
|
||||||
|
_ => return Ok(None)
|
||||||
|
};
|
||||||
|
Ok(to.draw(XYWH(
|
||||||
|
x0, y0,
|
||||||
|
w1.map(|w1|w1.max(w0)).unwrap_or(w0),
|
||||||
|
h1.map(|h1|h1.max(h0)).unwrap_or(h0)
|
||||||
|
), item)?.map(|XYWH(x2, y2, w2, h2)|XYWH(
|
||||||
|
x2, y2,
|
||||||
|
w1.map(|w1|w1.max(w2)).unwrap_or(w2),
|
||||||
|
h1.map(|h1|h1.max(h2)).unwrap_or(h2)
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_layout_min () -> Usually<()> {
|
||||||
|
assert_eq!(check_layout("1".min_w(5))?, Some(XYWH(1u16, 1, 5, 1)));
|
||||||
|
assert_eq!(check_layout("1".min_h(5))?, Some(XYWH(1u16, 1, 1, 5)));
|
||||||
|
assert_eq!(check_layout("1".min_wh(5, 5))?, Some(XYWH(1u16, 1, 5, 5)));
|
||||||
|
assert_eq!(check_layout("123456".min_w(5))?, Some(XYWH(1u16, 1, 6, 1)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
LayoutMax (max_w max_h max_wh),
|
||||||
|
Max { W H WH } kw_max "max" |self, to| {
|
||||||
let XYWH(x, y, w0, h0) = to.area();
|
let XYWH(x, y, w0, h0) = to.area();
|
||||||
let (item, w, h) = match self {
|
let (item, w, h) = match self {
|
||||||
Self::W(item, w) => (item, (*w).into().unwrap_or(w0), h0),
|
Self::W(item, w) => (item, (*w).into().unwrap_or(w0), h0),
|
||||||
Self::H(item, h) => (item, w0, (*h).into().unwrap_or(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)),
|
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)
|
_ => return Ok(None)
|
||||||
};
|
};
|
||||||
to.draw(area, item)
|
to.draw(XYWH(x, y, if w0 > w { w } else { w0 }, if h0 > h { h } else { h0 }), item)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
def_layout_modifier!(
|
def_layout_modifier!(
|
||||||
CanPad (pad_w pad_h pad_wh),
|
LayoutPad (pad_w pad_h pad_wh),
|
||||||
Pad { X Y XY } kw_pad "pad" |self, to| {
|
Pad { X Y XY } kw_pad "pad" |self, to| {
|
||||||
let area = to.area();
|
let XYWH(x, y, w0, h0) = to.area();
|
||||||
let (item, area) = match self {
|
let (item, w, h) = match self {
|
||||||
Self::X(item, w1) => {
|
Self::X(item, w) => (item, (*w).into().unwrap_or_default(), Default::default()),
|
||||||
let w1: S::Unit = (*w1).into().unwrap_or_default();
|
Self::Y(item, h) => (item, Default::default(), (*h).into().unwrap_or_default()),
|
||||||
(item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3))
|
Self::XY(item, w, h) => (item, (*w).into().unwrap_or_default(),
|
||||||
},
|
(*h).into().unwrap_or_default()),
|
||||||
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)
|
_ => return Ok(None)
|
||||||
};
|
};
|
||||||
item.draw(to)
|
to.draw(XYWH(
|
||||||
|
x.plus(w),
|
||||||
|
y.plus(h),
|
||||||
|
w0.minus(w).minus(w),
|
||||||
|
h0.minus(h).minus(h),
|
||||||
|
), item)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
def_layout_modifier!(
|
def_layout_modifier!(
|
||||||
CanPush (push_x push_y push_xy),
|
LayoutPush (push_x push_y push_xy),
|
||||||
Push { X Y XY } kw_push "push" |self, to| {
|
Push { X Y XY } kw_push "push" |self, to| {
|
||||||
match self {
|
match self {
|
||||||
Self::__(_) => unreachable!(),
|
Self::__(_) => unreachable!(),
|
||||||
|
|
@ -150,99 +137,47 @@ def_layout_modifier!(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
def_layout_modifier!(
|
#[cfg(test)] #[test] fn test_layout_push () -> Usually<()> {
|
||||||
CanPull (pull_x pull_y pull_xy),
|
assert_eq!(check_layout("1")?, Some(XYWH(1u16, 1, 1, 1)));
|
||||||
Pull { X Y XY } kw_pull "pull" |self, to| {
|
assert_eq!(check_layout("1".push_x(1))?, Some(XYWH(2u16, 1, 1, 1)));
|
||||||
todo!()
|
assert_eq!(check_layout("1".push_y(1))?, Some(XYWH(1u16, 2, 1, 1)));
|
||||||
}
|
assert_eq!(check_layout("1".push_xy(1, 1))?, Some(XYWH(2u16, 2, 1, 1)));
|
||||||
);
|
|
||||||
|
|
||||||
/// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_layout_modifier!(
|
||||||
|
LayoutPull (pull_x pull_y pull_xy),
|
||||||
|
Pull { X Y XY } kw_pull "pull" |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.minus((*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.minus((*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.minus((*x1).into().unwrap_or_default()),
|
||||||
|
y.minus((*y1).into().unwrap_or_default()),
|
||||||
|
w, h), item)
|
||||||
|
},
|
||||||
|
_ => Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_layout_pull () -> Usually<()> {
|
||||||
|
assert_eq!(check_layout("1")?, Some(XYWH(1u16, 1, 1, 1)));
|
||||||
|
assert_eq!(check_layout("1".pull_x(1))?, Some(XYWH(0u16, 1, 1, 1)));
|
||||||
|
assert_eq!(check_layout("1".pull_y(1))?, Some(XYWH(1u16, 0, 1, 1)));
|
||||||
|
assert_eq!(check_layout("1".pull_xy(1, 1))?, Some(XYWH(0u16, 0, 1, 1)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] fn check_layout (t: impl Draw<Tui>) -> Perhaps<XYWH<u16>> {
|
||||||
|
Tui::Layout(XYWH(1u16, 1, 80, 25)).size(None, t)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ impl<T: AsRef<Azimuth>> HasOrigin for T {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn_kw_layout!(kw_align |state, output, expr| {
|
fn_kw_layout!(kw_align |state, output, expr| {
|
||||||
Ok(matches!(expr.head()?, Some("align")).then(||{
|
let head = expr.head();
|
||||||
|
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||||
|
Ok(matches!(frags.next(), Some("align")).then(||{
|
||||||
draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align(
|
draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align(
|
||||||
eval_enum!("align", output, state,
|
eval_enum!("align", output, state,
|
||||||
expr.head().src()?.unwrap_or_default().split("/").skip(1).next(),
|
expr.head().src()?.unwrap_or_default().split("/").skip(1).next(),
|
||||||
|
|
@ -95,9 +97,13 @@ pub struct Align<T>(
|
||||||
|
|
||||||
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
|
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
|
||||||
fn draw (&self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
fn draw (&self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
||||||
let area = to.area();
|
let Self(azimuth, item) = self;
|
||||||
Ok(if let Some(area) = align::<S>(area, to.size(area, &self.1)?, self.0) {
|
let area0 = to.area();
|
||||||
to.draw(area, &self.1)?
|
let size = to.size(area0, item)?;
|
||||||
|
let area1 = align::<S>(area0, size, *azimuth);
|
||||||
|
//println!("\n\r{azimuth:?} {area0:?} {size:?}=>{area1:?}");
|
||||||
|
Ok(if let Some(area) = area1 {
|
||||||
|
to.draw(area, &item)?
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
|
|
@ -176,84 +182,6 @@ impl<S: Screen, A: Draw<S>, B: Draw<S>> Draw<S> for Pair<S, A, 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> (
|
fn draw_stacks <S: Screen> (
|
||||||
split: &Split,
|
split: &Split,
|
||||||
to: &mut S,
|
to: &mut S,
|
||||||
|
|
@ -352,6 +280,108 @@ fn stack_drawn <S: Coord> (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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!()
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! north {
|
#[macro_export] macro_rules! north {
|
||||||
($head:expr $(,)?) => { $head };
|
($head:expr $(,)?) => { $head };
|
||||||
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
|
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
|
||||||
|
|
@ -382,30 +412,6 @@ fn stack_drawn <S: Coord> (
|
||||||
($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) };
|
($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<()> {
|
#[cfg(test)] #[test] fn test_stack_areas () -> Usually<()> {
|
||||||
let area = XYWH(0u16, 0, 80, 25);
|
let area = XYWH(0u16, 0, 80, 25);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,18 +47,21 @@ pub fn iter_south <'a, S: Screen, D: Draw<S>, I: Iterator<Item = D>> (
|
||||||
iter: impl Fn()->I
|
iter: impl Fn()->I
|
||||||
) -> impl Draw<S> {
|
) -> impl Draw<S> {
|
||||||
draw(move|to: &mut S|{
|
draw(move|to: &mut S|{
|
||||||
let XYWH(x, mut y, w, mut h) = to.area();
|
let XYWH(x, y, w, h) = to.area();
|
||||||
let h0 = h;
|
let mut y_next = y;
|
||||||
|
let mut h_used = S::Unit::zero();
|
||||||
for item in iter() {
|
for item in iter() {
|
||||||
if let Some(used) = to.draw(Some(XYWH(x, y, w, h)), item)? {
|
let area = XYWH(x, y_next, w, h.minus(h_used));
|
||||||
y = y.add(used.y());
|
if let Some(used) = to.draw(area, item)? {
|
||||||
h = h.minus(used.y());
|
let used_h = used.h();
|
||||||
if h == S::Unit::zero() {
|
y_next = y_next.plus(used_h);
|
||||||
|
h_used = h_used.plus(used_h);
|
||||||
|
if h_used >= h {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Some(XYWH(x, y, w, h)))
|
Ok(Some(XYWH(x, y, w, h_used)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1949,7 +1949,7 @@ macro_rules! eval_xy (
|
||||||
let mut chars = text.as_ref().chars();
|
let mut chars = text.as_ref().chars();
|
||||||
while let Some(c) = chars.next() {
|
while let Some(c) = chars.next() {
|
||||||
width += c.width().unwrap_or(0) as u16;
|
width += c.width().unwrap_or(0) as u16;
|
||||||
if width > max {
|
if width >= max {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
240
src/term.rs
240
src/term.rs
|
|
@ -32,7 +32,6 @@ mod phat; pub use self::phat::*;
|
||||||
mod repeat; pub use self::repeat::*;
|
mod repeat; pub use self::repeat::*;
|
||||||
mod scroll; pub use self::scroll::*;
|
mod scroll; pub use self::scroll::*;
|
||||||
|
|
||||||
#[cfg(feature = "term")]
|
|
||||||
#[macro_export] macro_rules! tui_app {
|
#[macro_export] macro_rules! tui_app {
|
||||||
($Struct:ident { $($fields:tt)* }) => {
|
($Struct:ident { $($fields:tt)* }) => {
|
||||||
#[dizzle::namespace(bool)]
|
#[dizzle::namespace(bool)]
|
||||||
|
|
@ -47,18 +46,19 @@ mod scroll; pub use self::scroll::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement standard [main] entrypoint for TUI apps.
|
/// Implement standard [main] entrypoint for TUI apps.
|
||||||
#[cfg(feature = "term")]
|
|
||||||
#[macro_export] macro_rules! tui_main {
|
#[macro_export] macro_rules! tui_main {
|
||||||
($state:expr) => {
|
($state:expr) => {
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
tengri::Tui::setup_panic();
|
tengri::Tui::setup_panic();
|
||||||
tengri::Tui::run_main(::std::sync::Arc::new(::std::sync::RwLock::new($state)))
|
Exit::run(|exit|tengri::Tui::run_main(
|
||||||
|
exit,
|
||||||
|
::std::sync::Arc::new(::std::sync::RwLock::new($state))
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable TUI output for state struct.
|
/// Enable TUI output for state struct.
|
||||||
#[cfg(feature = "term")]
|
|
||||||
#[macro_export] macro_rules! tui_view {
|
#[macro_export] macro_rules! tui_view {
|
||||||
($self:ident: $State:ty $body:block) => {
|
($self:ident: $State:ty $body:block) => {
|
||||||
impl tengri::View<Tui> for $State {
|
impl tengri::View<Tui> for $State {
|
||||||
|
|
@ -68,7 +68,6 @@ mod scroll; pub use self::scroll::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable TUI keyboard input for main state struct.
|
/// Enable TUI keyboard input for main state struct.
|
||||||
#[cfg(feature = "term")]
|
|
||||||
#[macro_export] macro_rules! tui_keys {
|
#[macro_export] macro_rules! tui_keys {
|
||||||
($self:ident:$State:ty,$input:ident $($body:tt)+) => {
|
($self:ident:$State:ty,$input:ident $($body:tt)+) => {
|
||||||
impl dizzle::Apply<TuiEvent, Usually<()>> for $State {
|
impl dizzle::Apply<TuiEvent, Usually<()>> for $State {
|
||||||
|
|
@ -79,12 +78,14 @@ mod scroll; pub use self::scroll::*;
|
||||||
|
|
||||||
/// Terminal output.
|
/// Terminal output.
|
||||||
pub enum Tui {
|
pub enum Tui {
|
||||||
|
/// Draw mode: updates contained [Buffer].
|
||||||
Draw (
|
Draw (
|
||||||
/// Ratatui buffer; area is screen size
|
/// Ratatui buffer; area is screen size
|
||||||
Buffer,
|
Buffer,
|
||||||
/// Current draw area
|
/// Current draw area
|
||||||
XYWH<u16>
|
XYWH<u16>
|
||||||
),
|
),
|
||||||
|
/// Discard mode: only computes sizes
|
||||||
Layout (
|
Layout (
|
||||||
/// Draw area; no buffer
|
/// Draw area; no buffer
|
||||||
XYWH<u16>
|
XYWH<u16>
|
||||||
|
|
@ -96,6 +97,16 @@ impl Screen for Tui {
|
||||||
fn area (&self) -> XYWH<Self::Unit> {
|
fn area (&self) -> XYWH<Self::Unit> {
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
|
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 clip <T> (
|
fn clip <T> (
|
||||||
&mut self, area: impl Into<Option<XYWH<u16>>>, draw: &impl Fn(&mut Self)->T,
|
&mut self, area: impl Into<Option<XYWH<u16>>>, draw: &impl Fn(&mut Self)->T,
|
||||||
) -> T {
|
) -> T {
|
||||||
|
|
@ -107,30 +118,6 @@ impl Screen for Tui {
|
||||||
*self.area_mut() = prev;
|
*self.area_mut() = prev;
|
||||||
result
|
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 {
|
impl Tui {
|
||||||
|
|
@ -316,33 +303,6 @@ impl Tui {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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> {
|
pub fn buffer (&mut self) -> Option<&mut Buffer> {
|
||||||
if let Tui::Draw(buffer, _) = self {
|
if let Tui::Draw(buffer, _) = self {
|
||||||
Some(buffer)
|
Some(buffer)
|
||||||
|
|
@ -416,67 +376,7 @@ pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||||
}))))
|
}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "text")] impl_draw!(|self: String, to: Tui|{
|
/// FIXME: Don't use `format!` but implement number blitting
|
||||||
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;
|
pub struct ShowSize;
|
||||||
|
|
||||||
impl Draw<Tui> for ShowSize {
|
impl Draw<Tui> for ShowSize {
|
||||||
|
|
@ -500,16 +400,116 @@ impl<T: Draw<Tui>> Draw<Tui> for ShowSizeOf<T> {
|
||||||
|
|
||||||
#[cfg(feature = "eval")] fn_kw_layout_tui!(kw_tui_text |_state, output, expr| {
|
#[cfg(feature = "eval")] fn_kw_layout_tui!(kw_tui_text |_state, output, expr| {
|
||||||
Ok(matches!(expr.head()?, Some("text")).then(||{
|
Ok(matches!(expr.head()?, Some("text")).then(||{
|
||||||
if let Some(src) = expr.tail().src()? {
|
if let Some(src) = expr.tail().src()? && src.len() > 0 {
|
||||||
src.draw(output)
|
(&src[1..]).draw(output)
|
||||||
} else {
|
} else {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
}).transpose()?.flatten())
|
}).transpose()?.flatten())
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "text")] #[cfg(test)] #[test] fn test_layout_text_u16 () -> Usually<()> {
|
#[cfg(feature = "text")] pub use self::tui_text::*;
|
||||||
|
#[cfg(feature = "text")] mod tui_text {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
/// 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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_draw!(|self: String, to: Tui|{
|
||||||
|
self.as_str().draw(to)
|
||||||
|
});
|
||||||
|
|
||||||
|
impl_draw!(|self: std::sync::Arc<str>, to: Tui|{
|
||||||
|
self.as_ref().draw(to)
|
||||||
|
});
|
||||||
|
|
||||||
|
impl_draw!(<T: AsRef<str>,>|self: TrimString<T>, to: Tui|{
|
||||||
|
self.as_ref().draw(to)
|
||||||
|
});
|
||||||
|
|
||||||
|
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() {
|
||||||
|
let length = width_chars_max(w, line) as u16;
|
||||||
|
max_w = max_w.max(length);
|
||||||
|
max_h += 1;
|
||||||
|
if let Tui::Draw(..) = to {
|
||||||
|
let _ = to.text(&line, x, y + index as u16, length)?;
|
||||||
|
}
|
||||||
|
if max_h >= h {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(XYWH(x, y, max_w, max_h)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if let Tui::Draw(..) = to {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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", 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)));
|
assert_eq!(layout_text_u16("foo\nbarz", XYWH(5, 6, 10, 10))?, Some(XYWH(5, 6, 4, 2)));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,35 +65,41 @@ impl Tui {
|
||||||
/// Apply foreground color.
|
/// Apply foreground color.
|
||||||
pub const fn fg (fg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn fg (fg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
draw(move|to: &mut Tui|{
|
draw(move|to: &mut Tui|{
|
||||||
|
if let Some(size) = to.size(None, &item)? {
|
||||||
|
to.draw(Some(size), draw(|to: &mut Tui|{
|
||||||
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
||||||
item.draw(to)
|
item.draw(to)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply background color.
|
/// Apply background color.
|
||||||
pub const fn bg (bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn bg (bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
Background(bg, item)
|
draw(move|to: &mut Tui|{
|
||||||
}
|
if let Some(size) = to.size(None, &item)? {
|
||||||
|
let _ = to.draw(Some(size), draw(|to: &mut Tui|{
|
||||||
pub struct Background<T>(Color, T);
|
Ok(Some(to.update(&|cell,_,_|{ cell.set_bg(bg); })))
|
||||||
|
}))?;
|
||||||
impl<T: Draw<Tui>> Draw<Tui> for Background<T> {
|
to.draw(Some(size), &item)
|
||||||
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 {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn fg_bg (fg: Color, bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
pub const fn fg_bg (fg: Color, bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
draw(move|to: &mut Tui|{
|
draw(move|to: &mut Tui|{
|
||||||
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
if let Some(size) = to.size(None, &item)? {
|
||||||
item.draw(to)
|
let _ = to.draw(Some(size), draw(|to: &mut Tui|{
|
||||||
|
Ok(Some(to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); })))
|
||||||
|
}))?;
|
||||||
|
to.draw(Some(size), &item)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue