mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-08-07 22:17:07 +02:00
moar flat
This commit is contained in:
parent
fc01fd6ad3
commit
a27dacff93
29 changed files with 649 additions and 582 deletions
532
src/lib.rs
532
src/lib.rs
|
|
@ -46,7 +46,6 @@ macro_rules! features {
|
|||
|
||||
features! {
|
||||
"time": [ time ],
|
||||
"play": [ exit, task ],
|
||||
"sing": [ sing ]
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +191,7 @@ macro_rules! eval_xy (
|
|||
) => {{
|
||||
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
|
||||
let variant = $variant;
|
||||
let thunk = thunk(move|screen|$state.interpret(screen, &$arg));
|
||||
let thunk = draw(move|screen|$state.interpret(screen, &$arg));
|
||||
match variant {
|
||||
// X variant
|
||||
Some("x") => thunk.$x().draw($output),
|
||||
|
|
@ -215,7 +214,7 @@ macro_rules! eval_xy (
|
|||
) => {{
|
||||
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
|
||||
let variant = $variant;
|
||||
let thunk = thunk(move|screen|$state.interpret(screen, &match variant {
|
||||
let thunk = draw(move|screen|$state.interpret(screen, &match variant {
|
||||
Some("x") | Some("y") => $arg1,
|
||||
Some("xy") | None => $arg2,
|
||||
_ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name)
|
||||
|
|
@ -236,9 +235,109 @@ macro_rules! eval_xy (
|
|||
}};
|
||||
);
|
||||
|
||||
#[cfg(feature = "exit")] pub use self::exit::*;
|
||||
#[cfg(feature = "exit")] mod exit {
|
||||
use crate::*;
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
use crossterm::event::*;
|
||||
|
||||
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
||||
|
||||
impl Exit {
|
||||
pub fn run <T> (run: impl FnOnce(Self)->Usually<T>) -> Usually<T> {
|
||||
run(Self(Arc::new(AtomicBool::new(false))))
|
||||
}
|
||||
pub fn is (event: &Event) -> bool {
|
||||
matches!(event, Event::Key(KeyEvent {
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
code: KeyCode::Char('c'),
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Arc<AtomicBool>> for Exit {
|
||||
fn as_ref (&self) -> &Arc<AtomicBool> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "play")] pub use self::task::*;
|
||||
#[cfg(feature = "play")] mod task {
|
||||
use std::{
|
||||
time::Duration,
|
||||
sync::{Arc, atomic::{AtomicBool, Ordering::*}},
|
||||
thread::{Builder, JoinHandle, sleep},
|
||||
};
|
||||
#[cfg(feature = "term")] use ::crossterm::event::poll;
|
||||
use crate::time::PerfModel;
|
||||
|
||||
#[derive(Debug)] pub struct Task {
|
||||
/// Exit flag.
|
||||
pub exit: Arc<AtomicBool>,
|
||||
/// Performance counter.
|
||||
pub perf: Arc<PerfModel>,
|
||||
/// Use this to wait for the thread to finish.
|
||||
pub join: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
|
||||
pub fn new <F> (exit: Arc<AtomicBool>, mut call: F) -> Result<Self, std::io::Error>
|
||||
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
|
||||
{
|
||||
let perf = Arc::new(PerfModel::default());
|
||||
Ok(Self {
|
||||
exit: exit.clone(),
|
||||
perf: perf.clone(),
|
||||
join: Builder::new().name("tengri tui output".into()).spawn(move || {
|
||||
while !exit.fetch_and(true, Relaxed) {
|
||||
let _ = perf.cycle(&mut call);
|
||||
}
|
||||
})?.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a thread that runs `call` least one, then repeats
|
||||
/// until `exit`, sleeping for `time` msec after every iteration.
|
||||
pub fn new_sleep <F> (
|
||||
exit: Arc<AtomicBool>, time: Duration, mut call: F
|
||||
) -> Result<Self, std::io::Error>
|
||||
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
|
||||
{
|
||||
Self::new(exit, move |perf| {
|
||||
let _ = call(perf);
|
||||
sleep(time);
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a thread that uses [crossterm::event::poll]
|
||||
/// to run `call` every `time` msec.
|
||||
#[cfg(feature = "term")] pub fn new_poll <F> (
|
||||
exit: Arc<AtomicBool>, time: Duration, mut call: F
|
||||
) -> Result<Self, std::io::Error>
|
||||
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
|
||||
{
|
||||
Self::new(exit, move |perf| {
|
||||
if poll(time).is_ok() {
|
||||
let _ = call(perf);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn join (self) -> Result<(), Box<dyn std::any::Any + Send>> {
|
||||
self.join.join()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "draw")] pub use self::draw::*;
|
||||
#[cfg(feature = "draw")] mod draw {
|
||||
use crate::*;
|
||||
use Azimuth::*;
|
||||
use Split::*;
|
||||
|
||||
/// Output target.
|
||||
///
|
||||
|
|
@ -338,9 +437,82 @@ macro_rules! eval_xy (
|
|||
}
|
||||
}
|
||||
|
||||
/// The opposite of [thunk]?
|
||||
pub fn draw <S: Screen, T: Draw<S>> (item: T) -> impl FnOnce(&mut S) -> Perhaps<XYWH<S::Unit>> {
|
||||
move|to: &mut S|item.draw(to)
|
||||
/// Emit a [Draw]able.
|
||||
///
|
||||
/// Speculative. How to avoid conflicts with [Draw] proper?
|
||||
pub trait View<T: Screen> {
|
||||
fn view (&self) -> impl Draw<T>;
|
||||
}
|
||||
|
||||
impl<T: Screen> View<T> for () {
|
||||
fn view (&self) -> impl Draw<T> {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a [Draw]able.
|
||||
///
|
||||
/// ```
|
||||
/// # use tengri::*;
|
||||
/// let _ = view::<Tui, _, _>(||"drawable");
|
||||
/// let _ = view::<Tui, _, _>(||Some("drawable"));
|
||||
/// ```
|
||||
pub const fn view <S: Screen, T: Draw<S>, F: Fn()->T> (view: F) -> impl View<S> {
|
||||
ViewThunk(view, PhantomData)
|
||||
}
|
||||
|
||||
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
|
||||
pub struct ViewThunk<S: Screen, F>(pub F, std::marker::PhantomData<S>);
|
||||
|
||||
impl<S: Screen, T: Draw<S>, F: Fn()->T> View<S> for ViewThunk<S, F> {
|
||||
fn view (&self) -> impl Draw<S> {
|
||||
self.0()
|
||||
}
|
||||
}
|
||||
|
||||
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
|
||||
pub struct DrawThunk<S: Screen, F>(pub F, std::marker::PhantomData<S>);
|
||||
|
||||
impl<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> Draw<T> for DrawThunk<T, F> {
|
||||
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
|
||||
(self.0)(to)
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic [Draw]able closure.
|
||||
///
|
||||
/// ```
|
||||
/// # use tengri::*;
|
||||
/// let _ = draw(|to: &mut Tui|Ok(Some(to.1)));
|
||||
/// ```
|
||||
pub const fn draw <T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> (
|
||||
item: F
|
||||
) -> DrawThunk<T, F> {
|
||||
DrawThunk(item, std::marker::PhantomData)
|
||||
}
|
||||
|
||||
/// 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()) })
|
||||
}
|
||||
|
||||
/// 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) })
|
||||
}
|
||||
|
||||
pub type Drawn<U> = Perhaps<XYWH<U>>;
|
||||
|
|
@ -367,34 +539,168 @@ macro_rules! eval_xy (
|
|||
//}
|
||||
//}
|
||||
|
||||
/// Emit a [Draw]able.
|
||||
///
|
||||
/// Speculative. How to avoid conflicts with [Draw] proper?
|
||||
pub trait View<T: Screen> {
|
||||
fn view (&self) -> impl Draw<T>;
|
||||
}
|
||||
|
||||
impl<T: Screen> View<T> for () {
|
||||
fn view (&self) -> impl Draw<T> {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Screen, V: View<T>> Draw<T> for &V {
|
||||
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
|
||||
self.view().draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
features! {
|
||||
"draw": [
|
||||
iter,
|
||||
lrtb,
|
||||
sizer,
|
||||
split,
|
||||
thunk,
|
||||
xywh
|
||||
]
|
||||
pub trait Xy<N: Coord> {
|
||||
fn x (&self) -> N;
|
||||
fn y (&self) -> N;
|
||||
}
|
||||
|
||||
pub trait Wh<N: Coord>: Wide<N> + Tall<N> {
|
||||
fn wh (&self) -> [N;2];
|
||||
}
|
||||
|
||||
pub trait Xywh<N: Coord>: Xy<N> + Wh<N> {
|
||||
fn xywh (&self) -> XYWH<N> {
|
||||
XYWH(self.x(), self.y(), self.w(), self.h())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Wide<N: Coord>: Xy<N> {
|
||||
fn w (&self) -> N { N::zero() }
|
||||
fn w_min (&self) -> N { self.w() }
|
||||
fn w_max (&self) -> N { self.w() }
|
||||
}
|
||||
|
||||
pub trait Tall<N: Coord> {
|
||||
fn h (&self) -> N { N::zero() }
|
||||
fn h_min (&self) -> N { self.h() }
|
||||
fn h_max (&self) -> N { self.h() }
|
||||
}
|
||||
|
||||
/// Point with size.
|
||||
///
|
||||
/// ```
|
||||
/// # use tengri::*;
|
||||
/// let xywh = XYWH(0u16, 0, 0, 0);
|
||||
/// assert_eq!(XYWH(10u16, 10, 20, 20).center(), (20, 20));
|
||||
/// ```
|
||||
///
|
||||
/// * [ ] TODO: origin field (determines at which corner/side is X0 Y0)
|
||||
///
|
||||
#[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct XYWH<N: Coord>(pub N, pub N, pub N, pub N);
|
||||
|
||||
impl<N: Coord> Xy<N> for XYWH<N> {
|
||||
fn x (&self) -> N { self.0 }
|
||||
fn y (&self) -> N { self.1 }
|
||||
}
|
||||
|
||||
impl<N: Coord> Wide<N> for XYWH<N> { fn w (&self) -> N { self.2 } }
|
||||
|
||||
impl<N: Coord> Tall<N> for XYWH<N> { fn h (&self) -> N { self.3 } }
|
||||
|
||||
impl<N: Coord> XYWH<N> {
|
||||
|
||||
pub fn zero () -> Self {
|
||||
Self(0.into(), 0.into(), 0.into(), 0.into())
|
||||
}
|
||||
|
||||
pub fn center (&self) -> (N, N) {
|
||||
let Self(x, y, w, h) = *self;
|
||||
(x.plus(w/2.into()), y.plus(h/2.into()))
|
||||
}
|
||||
|
||||
pub fn centered (&self) -> (N, N) {
|
||||
let Self(x, y, w, h) = *self;
|
||||
(x.minus(w/2.into()), y.minus(h/2.into()))
|
||||
}
|
||||
|
||||
pub fn centered_x (&self, n: N) -> Self {
|
||||
let Self(x, y, w, h) = *self;
|
||||
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
|
||||
let y_center = y.plus(h / 2.into());
|
||||
XYWH(x_center, y_center, n, 1.into())
|
||||
}
|
||||
|
||||
pub fn centered_y (&self, n: N) -> Self {
|
||||
let Self(x, y, w, h) = *self;
|
||||
let x_center = x.plus(w / 2.into());
|
||||
let y_corner = (y.plus(h / 2.into())).minus(n / 2.into());
|
||||
XYWH(x_center, y_corner, 1.into(), n)
|
||||
}
|
||||
|
||||
pub fn centered_xy (&self, [n, m]: [N;2]) -> Self {
|
||||
let Self(x, y, w, h) = *self;
|
||||
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
|
||||
let y_corner = (y.plus(h / 2.into())).minus(m / 2.into());
|
||||
XYWH(x_center, y_corner, n, m)
|
||||
}
|
||||
|
||||
pub fn split_half (&self, direction: &Split) -> (Self, Self) {
|
||||
let XYWH(x, y, w, h) = self.xywh();
|
||||
match direction {
|
||||
South => (XYWH(x, y, w, h - h / 2.into()), XYWH(x, y + h / 2.into(), w, h / 2.into())),
|
||||
East => (XYWH(x, y, w - w / 2.into(), h), XYWH(x + w / 2.into(), y, w / 2.into(), h)),
|
||||
North => (XYWH(x, y + h / 2.into(), w, h - h / 2.into()), XYWH(x, y, w, h / 2.into())),
|
||||
West => (XYWH(x + w / 2.into(), y, w - w / 2.into(), h), XYWH(x, y, w / 2.into(), h)),
|
||||
Above | Below => (XYWH(x, y, w, h), XYWH(x, y, w, h))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl From<&ratatui::prelude::Rect> for XYWH<u16> {
|
||||
fn from (rect: &ratatui::prelude::Rect) -> Self {
|
||||
Self(rect.x, rect.y, rect.width, rect.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Coord, T: Wide<N> + Tall<N>> Wh<N> for T {
|
||||
fn wh (&self) -> [N;2] {
|
||||
[self.w(), self.h()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Coord, T: Xy<N> + Wh<N>> Xywh<N> for T {}
|
||||
|
||||
impl<N: Coord, T: Xywh<N>> Lrtb<N> for T {}
|
||||
|
||||
pub trait Lrtb<N: Coord>: Xywh<N> {
|
||||
fn lrtb (&self) -> [N;4] {
|
||||
// FIXME: factor origin
|
||||
[self.x(), self.y(), self.x()+self.w(), self.y()+self.h()]
|
||||
}
|
||||
fn iter_x (&self) -> std::ops::Range<N> where Self: HasOrigin {
|
||||
self.x_west()..self.x_east()
|
||||
}
|
||||
fn x_west (&self) -> N where Self: HasOrigin {
|
||||
let w = self.w();
|
||||
let a = self.origin();
|
||||
let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w };
|
||||
self.x().minus(d)
|
||||
}
|
||||
fn x_east (&self) -> N where Self: HasOrigin {
|
||||
let w = self.w();
|
||||
let a = self.origin();
|
||||
let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() };
|
||||
self.x().plus(d)
|
||||
}
|
||||
fn x_center (&self) -> N where Self: HasOrigin {
|
||||
todo!()
|
||||
}
|
||||
fn iter_y (&self) -> std::ops::Range<N> where Self: HasOrigin {
|
||||
self.y_north()..self.y_south()
|
||||
}
|
||||
fn y_north (&self) -> N where Self: HasOrigin {
|
||||
let a = self.origin();
|
||||
let h = self.h();
|
||||
let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h };
|
||||
self.y().minus(d)
|
||||
}
|
||||
fn y_south (&self) -> N where Self: HasOrigin {
|
||||
let a = self.origin();
|
||||
let h = self.h();
|
||||
let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() };
|
||||
self.y().plus(d)
|
||||
}
|
||||
fn y_center (&self) -> N where Self: HasOrigin {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -444,7 +750,7 @@ macro_rules! eval_xy (
|
|||
|
||||
#[cfg(feature = "draw")] pub use self::layout::*;
|
||||
#[cfg(feature = "draw")] mod layout {
|
||||
use crate::{*, draw::*};
|
||||
use crate::*;
|
||||
|
||||
impl<S: Screen, T: Draw<S>> Layout<S> for T {}
|
||||
|
||||
|
|
@ -613,21 +919,66 @@ macro_rules! eval_xy (
|
|||
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
|
||||
}
|
||||
|
||||
/// 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 align; pub use self::align::*;
|
||||
mod area; pub use self::area::*;
|
||||
mod exact; pub use self::exact::*;
|
||||
mod full; pub use self::full::*;
|
||||
mod iter; pub use self::iter::*;
|
||||
mod max; pub use self::max::*;
|
||||
mod min; pub use self::min::*;
|
||||
mod origin; pub use self::origin::*;
|
||||
mod pad; pub use self::pad::*;
|
||||
mod pull; pub use self::pull::*;
|
||||
mod push; pub use self::push::*;
|
||||
mod split; pub use self::split::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "draw")] pub use self::color::*;
|
||||
#[cfg(feature = "draw")] mod color {
|
||||
use crate::{*, draw::*};
|
||||
use crate::*;
|
||||
use dizzle::LanguageError::*;
|
||||
use ::ratatui::style::Color;
|
||||
use ::rand::distributions::uniform::UniformSampler;
|
||||
|
|
@ -814,8 +1165,10 @@ macro_rules! eval_xy (
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "text")]
|
||||
mod text {
|
||||
#[cfg(feature = "draw")] pub use self::text::*;
|
||||
#[cfg(feature = "text")] mod text {
|
||||
#![allow(unused)]
|
||||
|
||||
pub(crate) use ::unicode_width::*;
|
||||
|
||||
/// Displays an owned [str]-like with fixed maximum width.
|
||||
|
|
@ -878,10 +1231,7 @@ mod text {
|
|||
#[cfg(feature = "term")] pub use self::term::*;
|
||||
#[cfg(feature = "term")] mod term {
|
||||
use crate::*;
|
||||
use crate::draw::*;
|
||||
use crate::layout::*;
|
||||
use Color::*;
|
||||
#[cfg(feature = "text")] use crate::text::*;
|
||||
|
||||
#[macro_export] macro_rules! tui_app {
|
||||
($Struct:ident { $($fields:tt)* }) => {
|
||||
|
|
@ -982,6 +1332,7 @@ mod text {
|
|||
impl Tall<u16> for Tui { fn h (&self) -> u16 { self.1.3 } }
|
||||
impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } }
|
||||
impl Xy<u16> for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } }
|
||||
|
||||
/// Terminal output.
|
||||
pub struct Tui(
|
||||
/// Ratatui buffer; area is screen size
|
||||
|
|
@ -989,6 +1340,7 @@ mod text {
|
|||
/// Current draw area
|
||||
pub XYWH<u16>
|
||||
);
|
||||
|
||||
impl Tui {
|
||||
pub fn setup_panic () {
|
||||
use ::std::panic::{set_hook, PanicHookInfo};
|
||||
|
|
@ -1151,7 +1503,7 @@ mod text {
|
|||
/// }
|
||||
/// ```
|
||||
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|match result {
|
||||
draw(move|to: &mut Tui|match result {
|
||||
Ok(content) => content.draw(to),
|
||||
Err(e) => {
|
||||
let err_fg = Color::Rgb(255,224,244);
|
||||
|
|
@ -1166,7 +1518,7 @@ mod text {
|
|||
/// Interpret TUI-specific layout operation.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::{*, lang::*, ratatui::prelude::Color};
|
||||
/// use tengri::{*, dizzle::*, ratatui::prelude::Color};
|
||||
///
|
||||
/// #[namespace(bool)]
|
||||
/// #[namespace(u8)]
|
||||
|
|
@ -1233,10 +1585,7 @@ mod text {
|
|||
Some("fg") => {
|
||||
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
||||
if let Some(color) = Namespace::namespace(state, arg0)? {
|
||||
fg(color, thunk(move|to: &mut Tui|{
|
||||
state.interpret(to, &arg1)?;
|
||||
Ok(Some(to.area().into())) // FIXME?: don't max out the used area?
|
||||
})).draw(to)
|
||||
fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
|
||||
} else {
|
||||
return Err(format!("fg: {arg0:?}: not a color").into())
|
||||
}
|
||||
|
|
@ -1245,10 +1594,7 @@ mod text {
|
|||
Some("bg") => {
|
||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
||||
if let Some(color) = Namespace::namespace(state, arg0)? {
|
||||
bg(color, thunk(move|to: &mut Tui|{
|
||||
state.interpret(to, &arg1)?;
|
||||
Ok(Some(to.area().into())) // FIXME?: don't max out the used area?
|
||||
})).draw(to)
|
||||
bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
|
||||
} else {
|
||||
return Err(format!("bg: {arg0:?}: not a color").into())
|
||||
}
|
||||
|
|
@ -1310,6 +1656,7 @@ mod text {
|
|||
Ok(Some(XYWH(x0, y, string_width, 1)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Screen for Tui {
|
||||
type Unit = u16;
|
||||
/// Render drawable in subarea specified by `area`
|
||||
|
|
@ -1349,21 +1696,21 @@ mod text {
|
|||
}
|
||||
|
||||
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
|
||||
draw(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
|
||||
cell.set_char(c);
|
||||
}))))
|
||||
}
|
||||
|
||||
/// Draw contents with modifier applied.
|
||||
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
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)?;
|
||||
draw.draw(to)
|
||||
item.draw(to)
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|Ok(Some({
|
||||
draw(move|to: &mut Tui|Ok(Some({
|
||||
if on {
|
||||
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
||||
} else {
|
||||
|
|
@ -1373,8 +1720,8 @@ mod text {
|
|||
}
|
||||
|
||||
/// Draw contents with bold modifier applied.
|
||||
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
modify(on, Modifier::BOLD, draw)
|
||||
pub const fn bold (on: bool, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
modify(on, Modifier::BOLD, item)
|
||||
}
|
||||
|
||||
#[cfg(feature = "text")]
|
||||
|
|
@ -1421,7 +1768,7 @@ mod text {
|
|||
layout_text_u16(self.as_ref(), area)
|
||||
}
|
||||
fn draw (self, to: &mut Tui) -> Drawn<u16> {
|
||||
let XYWH(x, y, w, ..) = to.1;
|
||||
let XYWH(x, y, w, ..) = to.area();
|
||||
let mut width: u16 = 1;
|
||||
let mut chars = self.1.as_ref().chars();
|
||||
while let Some(c) = chars.next() {
|
||||
|
|
@ -1443,14 +1790,14 @@ mod text {
|
|||
|
||||
#[cfg(feature = "text")]
|
||||
fn layout_text_u16 (text: &str, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
|
||||
let XYWH(x, y, ..) = area;
|
||||
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, max_w, max_h)))
|
||||
Ok(Some(XYWH(x, y, w.min(max_w), h.min(max_h))))
|
||||
}
|
||||
|
||||
pub struct ShowSize;
|
||||
|
|
@ -1467,31 +1814,56 @@ mod text {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ShowSizeOf<T>(pub T);
|
||||
|
||||
impl<T: Draw<Tui>> Draw<Tui> for ShowSizeOf<T> {
|
||||
fn layout (&self, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
|
||||
self.0.layout(area)
|
||||
}
|
||||
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
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply foreground color.
|
||||
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
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); });
|
||||
draw.draw(to)
|
||||
item.draw(to)
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply background color.
|
||||
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
to.update(&|cell,_,_|{ cell.set_bg(bg); });
|
||||
draw.draw(to)
|
||||
})
|
||||
pub const fn bg (bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
Background(bg, item)
|
||||
}
|
||||
|
||||
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
pub struct Background<T>(Color, T);
|
||||
|
||||
impl<T: Draw<Tui>> Draw<Tui> for Background<T> {
|
||||
fn layout (&self, area: XYWH<u16>) -> Drawn<u16> {
|
||||
self.1.layout(area)
|
||||
}
|
||||
fn draw (self, to: &mut Tui) -> Drawn<u16> {
|
||||
self.layout(to.area()).map(|area|to.clip(area, |to|{
|
||||
to.update(&|cell,_,_|{ cell.set_bg(self.0); });
|
||||
self.1.draw(to)
|
||||
}))?
|
||||
}
|
||||
}
|
||||
|
||||
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); });
|
||||
draw.draw(to)
|
||||
item.draw(to)
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
|
||||
draw(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
|
||||
to.update(&|cell,_,_|{
|
||||
cell.modifier.insert(Modifier::UNDERLINED);
|
||||
cell.underline_color = color;
|
||||
|
|
@ -1532,7 +1904,6 @@ mod text {
|
|||
mod buffer; pub use self::buffer::*;
|
||||
mod repeat; pub use self::repeat::*;
|
||||
mod scroll; pub use self::scroll::*;
|
||||
mod colors; pub use self::colors::*;
|
||||
mod phat; pub use self::phat::*;
|
||||
mod button; pub use self::button::*;
|
||||
|
||||
|
|
@ -1553,7 +1924,7 @@ mod text {
|
|||
/// Interpret layout operation.
|
||||
///
|
||||
/// ```
|
||||
/// # use tengri::{*, lang::*};
|
||||
/// # use tengri::{*, dizzle::*};
|
||||
///
|
||||
/// struct State {/*app-specific*/}
|
||||
/// impl<'b> Namespace<'b, u16> for State {}
|
||||
|
|
@ -1598,29 +1969,28 @@ mod text {
|
|||
|
||||
Some("when") => when(
|
||||
state.namespace(arg0?)?.unwrap(),
|
||||
thunk(move|output: &mut O|{state.interpret(output, &arg1)})
|
||||
draw(move|output: &mut O|{state.interpret(output, &arg1)})
|
||||
).draw(output),
|
||||
|
||||
Some("either") => either(
|
||||
state.namespace(arg0?)?.unwrap(),
|
||||
thunk(move|output: &mut O|{state.interpret(output, &arg1)}),
|
||||
thunk(move|output: &mut O|{state.interpret(output, &arg2)}),
|
||||
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
|
||||
draw(move|output: &mut O|{state.interpret(output, &arg2)}),
|
||||
).draw(output),
|
||||
|
||||
Some("bsp") => eval_enum!("bsp", output, state, frags.next(), arg0, Split {
|
||||
"n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below
|
||||
}).stack(
|
||||
thunk(move|output: &mut O|{state.interpret(output, &arg0)}),
|
||||
thunk(move|output: &mut O|{state.interpret(output, &arg1)}),
|
||||
draw(move|output: &mut O|{state.interpret(output, &arg0)}),
|
||||
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
|
||||
).draw(output),
|
||||
|
||||
Some("align") => thunk(move|output: &mut O|{
|
||||
state.interpret(output, &arg0)
|
||||
}).align(eval_enum!("align", output, state, frags.next(), arg0, 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),
|
||||
Some("align") => draw(move|output: &mut O|{state.interpret(output, &arg0)})
|
||||
.align(eval_enum!("align", output, state, frags.next(), arg0, 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),
|
||||
|
||||
Some("exact") => eval_xy!(
|
||||
"exact" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h, arg0, arg1, arg2,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue