mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-03 21:40:44 +02:00
Compare commits
4 commits
9dbf4fcab5
...
cf57f44933
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf57f44933 | ||
|
|
cdc513060d | ||
|
|
eb899906f9 | ||
|
|
5d627f7669 |
12 changed files with 1028 additions and 986 deletions
2
dizzle
2
dizzle
|
|
@ -1 +1 @@
|
||||||
Subproject commit 44b2be57ca8d1f69a95f2eb02f4b5474ec77ac0a
|
Subproject commit 192a1d8257a9f2ad43ebceacb7b5ca348c601471
|
||||||
|
|
@ -1439,25 +1439,4 @@ impl<E: Engine, S, C: Command<S>> MenuItem<E, S, C> {
|
||||||
|
|
||||||
///// TUI helper defs.
|
///// TUI helper defs.
|
||||||
//impl Tui {
|
//impl Tui {
|
||||||
////pub const fn null () -> Color { Color::Reset }
|
|
||||||
////pub const fn red () -> Color { Color::Rgb(255,0, 0) }
|
|
||||||
////pub const fn orange () -> Color { Color::Rgb(255,128,0) }
|
|
||||||
////pub const fn yellow () -> Color { Color::Rgb(255,255,0) }
|
|
||||||
////pub const fn brown () -> Color { Color::Rgb(128,255,0) }
|
|
||||||
////pub const fn green () -> Color { Color::Rgb(0,255,0) }
|
|
||||||
////pub const fn electric () -> Color { Color::Rgb(0,255,128) }
|
|
||||||
////pub const fn g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
|
||||||
////fn bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
|
||||||
////fn bg () -> Color { Color::Rgb(28, 35, 25) }
|
|
||||||
////fn border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
|
||||||
////fn border_fg (f: bool) -> Color { if f { Self::bo1() } else { Self::bo2() } }
|
|
||||||
////fn title_fg (f: bool) -> Color { if f { Self::ti1() } else { Self::ti2() } }
|
|
||||||
////fn separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
|
||||||
////fn mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
|
||||||
////fn mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
|
||||||
////fn status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
|
||||||
////fn bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
|
||||||
////fn bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
|
||||||
////fn ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
|
||||||
////fn ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
|
||||||
//}
|
//}
|
||||||
|
|
|
||||||
38
src/color.rs
38
src/color.rs
|
|
@ -1,6 +1,10 @@
|
||||||
use ::ratatui::style::Color;
|
use ::ratatui::style::Color;
|
||||||
use crate::lang::impl_from;
|
use crate::lang::impl_from;
|
||||||
pub(crate) use ::palette::{Okhsl, Srgb, OklabHue, okhsl::UniformOkhsl};
|
pub(crate) use ::palette::{
|
||||||
|
Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl,
|
||||||
|
convert::{FromColor, FromColorUnclamped}
|
||||||
|
};
|
||||||
|
use rand::distributions::uniform::UniformSampler;
|
||||||
|
|
||||||
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor { todo!(); }
|
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor { todo!(); }
|
||||||
|
|
||||||
|
|
@ -26,13 +30,17 @@ pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ItemColor {}
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
impl_from!(ItemColor: |rgb: Color| Self { rgb, okhsl: rgb_to_okhsl(rgb) });
|
pub struct ItemColor {
|
||||||
impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, rgb: okhsl_to_rgb(okhsl) });
|
term: Color,
|
||||||
|
okhsl: Okhsl<f32>
|
||||||
|
}
|
||||||
|
impl_from!(ItemColor: |term: Color| Self { term, okhsl: rgb_to_okhsl(term) });
|
||||||
|
impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, term: okhsl_to_rgb(okhsl) });
|
||||||
// A single color within item theme parameters, in OKHSL and RGB representations.
|
// A single color within item theme parameters, in OKHSL and RGB representations.
|
||||||
impl ItemColor {
|
impl ItemColor {
|
||||||
#[cfg(feature = "tui")] pub const fn from_tui (rgb: Color) -> Self {
|
#[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self {
|
||||||
Self { rgb, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) }
|
Self { term, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) }
|
||||||
}
|
}
|
||||||
pub fn random () -> Self {
|
pub fn random () -> Self {
|
||||||
let mut rng = ::rand::thread_rng();
|
let mut rng = ::rand::thread_rng();
|
||||||
|
|
@ -55,12 +63,20 @@ impl ItemColor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ItemTheme {}
|
pub struct ItemTheme {
|
||||||
|
pub base: ItemColor,
|
||||||
|
pub light: ItemColor,
|
||||||
|
pub lighter: ItemColor,
|
||||||
|
pub lightest: ItemColor,
|
||||||
|
pub dark: ItemColor,
|
||||||
|
pub darker: ItemColor,
|
||||||
|
pub darkest: ItemColor,
|
||||||
|
}
|
||||||
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
|
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
|
||||||
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
|
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
|
||||||
impl ItemTheme {
|
impl ItemTheme {
|
||||||
#[cfg(feature = "tui")] pub const G: [Self;256] = {
|
#[cfg(feature = "term")] pub const G: [Self;256] = {
|
||||||
let mut builder = konst::array::ArrayBuilder::new();
|
let mut builder = dizzle::konst::array::ArrayBuilder::new();
|
||||||
while !builder.is_full() {
|
while !builder.is_full() {
|
||||||
let index = builder.len() as u8;
|
let index = builder.len() as u8;
|
||||||
let light = (index as f64 * 1.15) as u8;
|
let light = (index as f64 * 1.15) as u8;
|
||||||
|
|
@ -88,7 +104,7 @@ impl ItemTheme {
|
||||||
pub const G00: Self = {
|
pub const G00: Self = {
|
||||||
let color: ItemColor = ItemColor {
|
let color: ItemColor = ItemColor {
|
||||||
okhsl: Okhsl { hue: OklabHue::new(0.0), lightness: 0.0, saturation: 0.0 },
|
okhsl: Okhsl { hue: OklabHue::new(0.0), lightness: 0.0, saturation: 0.0 },
|
||||||
rgb: Color::Rgb(0, 0, 0)
|
term: Color::Rgb(0, 0, 0)
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
base: color,
|
base: color,
|
||||||
|
|
@ -100,7 +116,7 @@ impl ItemTheme {
|
||||||
darkest: color,
|
darkest: color,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#[cfg(feature = "tui")] pub fn from_tui_color (base: Color) -> Self {
|
#[cfg(feature = "term")] pub fn from_tui_color (base: Color) -> Self {
|
||||||
Self::from_item_color(ItemColor::from_tui(base))
|
Self::from_item_color(ItemColor::from_tui(base))
|
||||||
}
|
}
|
||||||
pub fn from_item_color (base: ItemColor) -> Self {
|
pub fn from_item_color (base: ItemColor) -> Self {
|
||||||
|
|
|
||||||
541
src/draw.rs
541
src/draw.rs
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::{*, lang::*, color::*};
|
use crate::{*, lang::*, color::*, space::*};
|
||||||
|
|
||||||
/// Drawable that supports dynamic dispatch.
|
/// Drawable that supports dynamic dispatch.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tengri::draw::*;
|
/// use tengri::draw::*;
|
||||||
/// struct TestScreen(bool);
|
/// struct TestScreen(bool);
|
||||||
|
/// impl Screen for TestScreen { type Unit = u16; }
|
||||||
/// struct TestWidget(bool);
|
/// struct TestWidget(bool);
|
||||||
/// impl Draw<TestScreen> for TestWidget {
|
/// impl Draw<TestScreen> for TestWidget {
|
||||||
/// fn draw (&self, screen: &mut T) {
|
/// fn draw (&self, screen: &mut T) -> Usually<XYWH<u16>> {
|
||||||
/// screen.0 |= self.0;
|
/// screen.0 |= self.0;
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
|
@ -16,26 +17,32 @@ use crate::{*, lang::*, color::*};
|
||||||
/// TestWidget(true).draw(&mut screen);
|
/// TestWidget(true).draw(&mut screen);
|
||||||
/// TestWidget(false).draw(&mut screen);
|
/// TestWidget(false).draw(&mut screen);
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Draw<T> {
|
pub trait Draw<T: Screen> {
|
||||||
fn draw (&self, to: &mut T);
|
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>>;
|
||||||
}
|
}
|
||||||
impl<T> Draw<T> for () {
|
impl<T: Screen> Draw<T> for () {
|
||||||
fn draw (&self, __: &mut T) {}
|
fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> {
|
||||||
|
Ok(Default::default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<T, F: Fn(&mut T)> Draw<T> for F {
|
impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> {
|
||||||
fn draw (&self, to: &mut T) { self(to) }
|
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
|
||||||
|
Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//impl<T, D: Draw<T>> Draw<T> for &D {
|
|
||||||
//fn draw (&self, to: &mut T) { (*self).draw(to) }
|
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
|
||||||
//}
|
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>(
|
||||||
impl<T, D: Draw<T>> Draw<T> for Arc<D> {
|
pub F,
|
||||||
fn draw (&self, to: &mut T) { (**self).draw(to) }
|
std::marker::PhantomData<T>
|
||||||
|
);
|
||||||
|
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (draw: F) -> Thunk<T, F> {
|
||||||
|
Thunk(draw, std::marker::PhantomData)
|
||||||
}
|
}
|
||||||
impl<T, D: Draw<T>> Draw<T> for RwLock<D> {
|
impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
|
||||||
fn draw (&self, to: &mut T) { self.read().unwrap().draw(to) }
|
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
|
||||||
}
|
(self.0)(to)
|
||||||
impl<T, D: Draw<T>> Draw<T> for Option<D> {
|
}
|
||||||
fn draw (&self, to: &mut T) { if let Some(draw) = self { draw.draw(to) } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only render when condition is true.
|
/// Only render when condition is true.
|
||||||
|
|
@ -45,8 +52,8 @@ impl<T, D: Draw<T>> Draw<T> for Option<D> {
|
||||||
/// tengri::when(true, "Yes")
|
/// tengri::when(true, "Yes")
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn when <T> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
|
pub const fn when <T: Screen> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
|
||||||
move|to: &mut T|if condition { draw.draw(to); }
|
thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render one thing if a condition is true and another false.
|
/// Render one thing if a condition is true and another false.
|
||||||
|
|
@ -56,8 +63,8 @@ pub fn when <T> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
|
||||||
/// tengri::either(true, "Yes", "No")
|
/// tengri::either(true, "Yes", "No")
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn either <T> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) }
|
thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Output target.
|
/// Output target.
|
||||||
|
|
@ -69,497 +76,25 @@ pub fn either <T> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Dr
|
||||||
///
|
///
|
||||||
/// impl Screen for TestOut {
|
/// impl Screen for TestOut {
|
||||||
/// type Unit = u16;
|
/// type Unit = u16;
|
||||||
/// fn area (&self) -> impl TwoD<u16> { self.0 }
|
/// fn place <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) {
|
||||||
/// fn area_mut (&mut self) -> &mut impl TwoD<u16> { &mut self.0 }
|
/// println!("place: {area:?}");
|
||||||
/// fn show <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) {
|
|
||||||
/// println!("show: {area:?}");
|
|
||||||
/// ()
|
/// ()
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl Draw<Screen> for String {
|
/// impl Draw<Screen> for String {
|
||||||
/// fn draw (&self, to: &mut TestOut) {
|
/// fn draw (&self, to: &mut TestOut) -> Usually<XYWH<u16>> {
|
||||||
/// to.area_mut().set_w(self.len() as u16);
|
/// to.area_mut().set_w(self.len() as u16);
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Screen: Area<Self::Unit> + Aligned + Send + Sync + Sized {
|
pub trait Screen: Space<Self::Unit> + Send + Sync + Sized {
|
||||||
type Unit: Coord;
|
type Unit: Coord;
|
||||||
/// Render drawable in area specified by `area`
|
/// Render drawable in area specified by `area`
|
||||||
fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: impl Area<Self::Unit>, content: &'t T);
|
fn place <'t, T: Draw<Self> + ?Sized> (
|
||||||
|
&mut self, content: &'t T, area: Option<XYWH<Self::Unit>>
|
||||||
fn clipped_mut (&mut self, w: Option<Self::Unit>, h: Option<Self::Unit>)
|
) {
|
||||||
-> &mut Self;
|
let area = area.unwrap_or_else(||self.xywh());
|
||||||
fn padded_mut (&mut self, w: Self::Unit, h: Self::Unit)
|
unimplemented!()
|
||||||
-> &mut Self;
|
|
||||||
}
|
|
||||||
impl<O: Screen> X<O::Unit> for O { fn x (&self) -> O::Unit { self.x() } }
|
|
||||||
impl<O: Screen> Y<O::Unit> for O { fn y (&self) -> O::Unit { self.y() } }
|
|
||||||
impl<O: Screen> W<O::Unit> for O { fn w (&self) -> O::Unit { self.w() } }
|
|
||||||
impl<O: Screen> H<O::Unit> for O { fn h (&self) -> O::Unit { self.h() } }
|
|
||||||
|
|
||||||
/// Something that has area in 2D space.
|
|
||||||
pub trait Area<N: Coord>: W<N> + H<N> {
|
|
||||||
fn wh (&self) -> WH<N> {
|
|
||||||
WH(self.w(), self.h())
|
|
||||||
}
|
|
||||||
fn clip_w (&self, w: N) -> [N;2] {
|
|
||||||
[self.w().min(w), self.h()]
|
|
||||||
}
|
|
||||||
fn clip_h (&self, h: N) -> [N;2] {
|
|
||||||
[self.w(), self.h().min(h)]
|
|
||||||
}
|
|
||||||
fn at_least (&self, w: N, h: N) -> Usually<&Self> {
|
|
||||||
if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) }
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
fn clip_wh (&self, w: Option<N>, h: Option<N>) -> [N;2] {
|
|
||||||
let w = w.map(|w|self.w().min(w)).unwrap_or(self.w());
|
|
||||||
let h = h.map(|h|self.h().min(h)).unwrap_or(self.h());
|
|
||||||
[w, h]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Something that has length along horizontal axis.
|
|
||||||
pub trait W<N: Coord> {
|
|
||||||
fn w (&self) -> N { N::zero() }
|
|
||||||
fn w_min (&self) -> N { self.w() }
|
|
||||||
fn w_max (&self) -> N { self.w() }
|
|
||||||
}
|
|
||||||
fn w_exact <N: Coord, T: Screen<Unit = N>> (_w: N, _c: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
fn w_fill <N: Coord, T: Screen<Unit = N>> (_c: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
fn w_min <N: Coord, T: Screen<Unit = N>> (w: N, x: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
min(Some(w), None, x)
|
|
||||||
}
|
|
||||||
fn w_max <N: Coord, T> (w: N, x: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
max(Some(w), None, x)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Something that has length along vertical axis.
|
|
||||||
pub trait H<N: Coord> {
|
|
||||||
fn h (&self) -> N { N::zero() }
|
|
||||||
fn h_min (&self) -> N { self.h() }
|
|
||||||
fn h_max (&self) -> N { self.h() }
|
|
||||||
}
|
|
||||||
fn h_exact <N: Coord, T: Screen<Unit = N>> (_h: N, _c: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
fn h_fill <N: Coord, T: Screen<Unit = N>> (_c: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
fn h_min <N: Coord, T: Screen<Unit = N>> (h: N, x: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
min(None, Some(h), x)
|
|
||||||
}
|
|
||||||
fn h_max <N: Coord, T> (h: N, x: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
max(None, Some(h), x)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Something that has a [Measure] of its rendered size.
|
|
||||||
pub trait Measured <N: Coord> {}
|
|
||||||
|
|
||||||
/// Point along (X, Y).
|
|
||||||
pub trait Point<N: Coord>: X<N> + Y<N> {
|
|
||||||
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Something that has `[0, 0]` at a particular point.
|
|
||||||
pub trait Anchored { fn anchor (&self) -> Align; }
|
|
||||||
|
|
||||||
/// Something that has a bounding box
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::{Bounded, XYWH};
|
|
||||||
/// let bounded: Bounded<tengri::Tui, _> = Bounded(0, 0 ,0 ,0 ,"");
|
|
||||||
/// ```
|
|
||||||
pub trait Bounding <N: Coord>: Point<N> + Area<N> + Anchored {
|
|
||||||
/// Iterate over every covered X coordinate.
|
|
||||||
fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
|
||||||
self.x_left()..self.x_right()
|
|
||||||
}
|
|
||||||
/// Iterate over every covered Y coordinate.
|
|
||||||
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
|
||||||
self.y_top()..self.y_bottom()
|
|
||||||
}
|
|
||||||
fn x_left (&self) -> N {
|
|
||||||
use Align::*;
|
|
||||||
let w = self.w();
|
|
||||||
let a = self.anchor();
|
|
||||||
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_right (&self) -> N {
|
|
||||||
use Align::*;
|
|
||||||
let w = self.w();
|
|
||||||
let a = self.anchor();
|
|
||||||
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 y_top (&self) -> N {
|
|
||||||
use Align::*;
|
|
||||||
let a = self.anchor();
|
|
||||||
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_bottom (&self) -> N {
|
|
||||||
use Align::*;
|
|
||||||
let a = self.anchor();
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A cardinal direction.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let direction = tengri::Direction::Above;
|
|
||||||
/// ```
|
|
||||||
#[cfg_attr(test, derive(Arbitrary))]
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
|
|
||||||
North, South, East, West, Above, Below
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A numeric type that can be used as coordinate.
|
|
||||||
///
|
|
||||||
/// FIXME: Replace with `num` crate?
|
|
||||||
/// FIXME: Use AsRef/AsMut?
|
|
||||||
pub trait Coord: Send + Sync + Copy
|
|
||||||
+ Add<Self, Output=Self>
|
|
||||||
+ Sub<Self, Output=Self>
|
|
||||||
+ Mul<Self, Output=Self>
|
|
||||||
+ Div<Self, Output=Self>
|
|
||||||
+ Ord + PartialEq + Eq
|
|
||||||
+ Debug + Display + Default
|
|
||||||
+ From<u16> + Into<u16>
|
|
||||||
+ Into<usize>
|
|
||||||
+ Into<f64>
|
|
||||||
{
|
|
||||||
fn zero () -> Self { 0.into() }
|
|
||||||
fn plus (self, other: Self) -> Self;
|
|
||||||
/// Saturating subtraction
|
|
||||||
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
|
|
||||||
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Point along horizontal axis.
|
|
||||||
pub trait X<N: Coord> {
|
|
||||||
fn x (&self) -> N { N::zero() }
|
|
||||||
fn push <T> (a: impl Draw<T>) -> impl Draw<T> { a }
|
|
||||||
fn pull <T> (a: impl Draw<T>) -> impl Draw<T> { a }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Point along vertical axis.
|
|
||||||
pub trait Y<N: Coord> {
|
|
||||||
fn y (&self) -> N { N::zero() }
|
|
||||||
fn push <T> (a: impl Draw<T>) -> impl Draw<T> { a }
|
|
||||||
fn pull <T> (a: impl Draw<T>) -> impl Draw<T> { a }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//impl<O: Screen, T: Draw<O>> Draw<O> for Bounded<O, T> {
|
|
||||||
//fn draw (&self, to: &mut O) {
|
|
||||||
//let area = to.area();
|
|
||||||
//*to.area_mut() = self.0;
|
|
||||||
//self.1.draw(to);
|
|
||||||
//*to.area_mut() = area;
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// A point (X, Y).
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let xy = tengri::XY(0u16, 0);
|
|
||||||
/// ```
|
|
||||||
#[cfg_attr(test, derive(Arbitrary))]
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
pub struct XY<C: Coord>(pub C, pub C);
|
|
||||||
impl<N: Coord> X<N> for XY<N> { fn x (&self) -> N { self.0 } }
|
|
||||||
impl<N: Coord> Y<N> for XY<N> { fn y (&self) -> N { self.1 } }
|
|
||||||
|
|
||||||
/// A size (Width, Height).
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let wh = tengri::WH(0u16, 0);
|
|
||||||
/// ```
|
|
||||||
#[cfg_attr(test, derive(Arbitrary))]
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
pub struct WH<C: Coord>(pub C, pub C);
|
|
||||||
impl<N: Coord> W<N> for WH<N> { fn w (&self) -> N { self.0 } }
|
|
||||||
impl<N: Coord> H<N> for WH<N> { fn h (&self) -> N { self.1 } }
|
|
||||||
impl<N: Coord> WH<N> {
|
|
||||||
fn min <T: Screen<Unit = N>> (&self, x: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
min(Some(self.w()), Some(self.h()), x)
|
|
||||||
}
|
|
||||||
fn exact <T: Screen<Unit = N>> (&self, c: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
fn fill <T: Screen<Unit = N>> (c: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Point with size.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let xywh = tengri::XYWH(0u16, 0, 0, 0);
|
|
||||||
/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20));
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0)
|
|
||||||
///
|
|
||||||
#[cfg_attr(test, derive(Arbitrary))]
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH<C: Coord>(
|
|
||||||
pub C, pub C, pub C, pub C
|
|
||||||
);
|
|
||||||
impl<N: Coord> X<N> for XYWH<N> { fn x (&self) -> N { self.0 } }
|
|
||||||
impl<N: Coord> Y<N> for XYWH<N> { fn y (&self) -> N { self.1 } }
|
|
||||||
impl<N: Coord> W<N> for XYWH<N> { fn w (&self) -> N { self.0 } }
|
|
||||||
impl<N: Coord> H<N> for XYWH<N> { fn h (&self) -> N { self.1 } }
|
|
||||||
|
|
||||||
impl<N: Coord> XYWH<N> {
|
|
||||||
pub fn zero () -> Self {
|
|
||||||
Self(0.into(), 0.into(), 0.into(), 0.into())
|
|
||||||
}
|
|
||||||
pub fn clipped_w (&self, w: N) -> XYWH<N> {
|
|
||||||
Self(self.x(), self.y(), self.w().min(w), self.h())
|
|
||||||
}
|
|
||||||
pub fn clipped_h (&self, h: N) -> XYWH<N> {
|
|
||||||
Self(self.x(), self.y(), self.w(), self.h().min(h))
|
|
||||||
}
|
|
||||||
pub fn clipped (&self, wh: WH<N>) -> XYWH<N> {
|
|
||||||
Self(self.x(), self.y(), wh.w(), wh.h())
|
|
||||||
}
|
|
||||||
pub fn center (&self) -> XY<N> {
|
|
||||||
XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into()))
|
|
||||||
}
|
|
||||||
pub fn centered (&self) -> XY<N> {
|
|
||||||
let Self(x, y, w, h) = *self;
|
|
||||||
XY(x.minus(w/2.into()), y.minus(h/2.into()))
|
|
||||||
}
|
|
||||||
pub fn centered_x (&self, n: N) -> XYWH<N> {
|
|
||||||
let Self(x, y, w, h) = *self;
|
|
||||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into())
|
|
||||||
}
|
|
||||||
pub fn centered_y (&self, n: N) -> XYWH<N> {
|
|
||||||
let Self(x, y, w, h) = *self;
|
|
||||||
XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n)
|
|
||||||
}
|
|
||||||
pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
|
|
||||||
let Self(x, y, w, h) = *self;
|
|
||||||
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Set maximum width and/or height of drawing area.
|
|
||||||
pub fn clip <T: Screen<Unit = N>, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|to: &mut T|draw.draw(to.clipped_mut(w, h))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shrink drawing area symmetrically.
|
|
||||||
pub fn pad <T: Screen<Unit = N>, N: Coord> (w: N, h: N, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|to: &mut T|draw.draw(to.padded_mut(w, h))
|
|
||||||
}
|
|
||||||
pub fn pad_x <T: Screen<Unit = N>, N: Coord> (w: N, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
pad(w, N::zero(), draw)
|
|
||||||
}
|
|
||||||
pub fn pad_y <T: Screen<Unit = N>, N: Coord> (h: N, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
pad(N::zero(), h, draw)
|
|
||||||
}
|
|
||||||
pub fn pad_xy <T: Screen<Unit = N>, N: Coord> (p: N, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
pad(p, p, draw)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 9th of area to place.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let alignment = tengri::Align::C;
|
|
||||||
/// use ::tengri::*;
|
|
||||||
/// let area = XYWH(10u16, 10, 20, 20);
|
|
||||||
/// fn test (area: XYWH<u16>, item: &impl Draw<Tui>, expected: [u16;4]) {
|
|
||||||
/// //assert_eq!(Lay::layout(item, area), expected);
|
|
||||||
/// //assert_eq!(Draw::layout(item, area), expected);
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let four = exact_xy(4, 4, "foo");
|
|
||||||
/// test(area, &Align::nw(four()), [10, 10, 4, 4]);
|
|
||||||
/// test(area, &Align::n(four()), [18, 10, 4, 4]);
|
|
||||||
/// test(area, &Align::ne(four()), [26, 10, 4, 4]);
|
|
||||||
/// test(area, &Align::e(four()), [26, 18, 4, 4]);
|
|
||||||
/// test(area, &Align::se(four()), [26, 26, 4, 4]);
|
|
||||||
/// test(area, &Align::s(four()), [18, 26, 4, 4]);
|
|
||||||
/// test(area, &Align::sw(four()), [10, 26, 4, 4]);
|
|
||||||
/// test(area, &Align::w(four()), [10, 18, 4, 4]);
|
|
||||||
///
|
|
||||||
/// let two_by_four = exact_xy(4, 2, "foo");
|
|
||||||
/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
|
|
||||||
/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
|
|
||||||
/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
|
|
||||||
/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
|
|
||||||
/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
|
|
||||||
/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
|
|
||||||
/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
|
|
||||||
/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
|
|
||||||
/// pub struct Align<T>(pub Align, pub T);
|
|
||||||
/// ```
|
|
||||||
#[cfg_attr(test, derive(Arbitrary))]
|
|
||||||
#[derive(Debug, Copy, Clone, Default)] pub enum Align {
|
|
||||||
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
|
|
||||||
}
|
|
||||||
pub trait Aligned {
|
|
||||||
fn aligned (&mut self, align: Align) -> &mut Self;
|
|
||||||
}
|
|
||||||
/// Set 0, 0 of drawing subarea.
|
|
||||||
pub fn align <T: Aligned> (alignment: Align, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|to: &mut T|draw.draw(to.aligned(alignment))
|
|
||||||
}
|
|
||||||
impl Align {
|
|
||||||
pub fn n <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::N, x) }
|
|
||||||
pub fn s <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::S, x) }
|
|
||||||
pub fn e <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::E, x) }
|
|
||||||
pub fn w <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::W, x) }
|
|
||||||
pub fn nw <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::NW, x) }
|
|
||||||
pub fn ne <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::NE, x) }
|
|
||||||
pub fn sw <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::SW, x) }
|
|
||||||
pub fn se <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::SE, x) }
|
|
||||||
pub fn c <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::C, x) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Only draw content if area is above a certain size.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let minimum = tengri::min_wh(3, 5, "Hello"); // 5x5
|
|
||||||
/// ```
|
|
||||||
pub fn min <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|to: &mut T| { todo!() }//draw.draw(to.min(w, h))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum width and/or height of the content.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let maximum = tengri::max_wh(3, 5, "Hello"); // 3x1
|
|
||||||
/// ```
|
|
||||||
pub fn max <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|to: &mut T| { todo!() }// { todo!() }//draw.draw(to.max(w, h))
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn displace ...
|
|
||||||
|
|
||||||
pub fn iter <T, U: Draw<T>, F: Fn(U)->dyn Draw<T>> (
|
|
||||||
_items: impl Iterator<Item = U>, _cb: F
|
|
||||||
) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Split screen between two items, or layer them atop each other.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::Direction::*;
|
|
||||||
/// let _ = tengri::draw::bsp(Above, (), ());
|
|
||||||
/// let _ = tengri::draw::bsp(Below, (), ());
|
|
||||||
/// let _ = tengri::draw::bsp(North, (), ());
|
|
||||||
/// let _ = tengri::draw::bsp(South, (), ());
|
|
||||||
/// let _ = tengri::draw::bsp(East, (), ());
|
|
||||||
/// let _ = tengri::draw::bsp(West, (), ());
|
|
||||||
/// ```
|
|
||||||
pub fn bsp <T> (_dir: Direction, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
move|_to: &mut T|{ todo!() }
|
|
||||||
}
|
|
||||||
pub fn bsp_n <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
bsp(Direction::North, a, b)
|
|
||||||
}
|
|
||||||
pub fn bsp_s <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
bsp(Direction::South, a, b)
|
|
||||||
}
|
|
||||||
pub fn bsp_e <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
bsp(Direction::East, a, b)
|
|
||||||
}
|
|
||||||
pub fn bsp_w <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
bsp(Direction::West, a, b)
|
|
||||||
}
|
|
||||||
pub fn bsp_a <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
bsp(Direction::Above, a, b)
|
|
||||||
}
|
|
||||||
pub fn bsp_b <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
|
||||||
bsp(Direction::Below, a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear a pre-allocated buffer, then write into it.
|
|
||||||
#[macro_export] macro_rules! rewrite {
|
|
||||||
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: This macro should be some variant of `eval`, too.
|
|
||||||
/// But taking into account the different signatures (resolving them into 1?)
|
|
||||||
#[cfg(feature = "lang")] #[macro_export] macro_rules! draw {
|
|
||||||
($State:ident: $Output:ident: $layers:expr) => {
|
|
||||||
impl Draw<$Output> for $State {
|
|
||||||
fn draw (&self, to: &mut $Output) {
|
|
||||||
for layer in $layers { layer(self, to) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: This is generic: should be called `eval` and be part of [dizzle].
|
|
||||||
#[cfg(feature = "lang")] #[macro_export] macro_rules! view {
|
|
||||||
($State:ident: $Output:ident: $namespaces:expr) => {
|
|
||||||
impl Understand<$Output, ()> for $State {
|
|
||||||
fn understand_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> {
|
|
||||||
for namespace in $namespaces { if namespace(self, to, expr)? { return Ok(()) } }
|
|
||||||
Err(format!("{}::<{}, ()>::understand_expr: unexpected: {expr:?}",
|
|
||||||
stringify! { $State },
|
|
||||||
stringify! { $Output }).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stack things on top of each other,
|
|
||||||
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{
|
|
||||||
let bsp = (); $(let bsp = bsp_b(bsp, $expr);)* bsp
|
|
||||||
}});
|
|
||||||
|
|
||||||
/// Stack southward.
|
|
||||||
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{
|
|
||||||
let bsp = (); $(let bsp = bsp_s(bsp, $expr);)* bsp
|
|
||||||
}});
|
|
||||||
|
|
||||||
/// Stack northward.
|
|
||||||
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{
|
|
||||||
let bsp = (); $(let bsp = bsp_n(bsp, $expr);)* bsp
|
|
||||||
}});
|
|
||||||
|
|
||||||
/// Stack eastward.
|
|
||||||
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{
|
|
||||||
let bsp = (); $(let bsp = bsp_e(bsp, $expr);)* bsp
|
|
||||||
}});
|
|
||||||
|
|
||||||
/// Memoize a rendering.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tengri::Memo::new((), ());
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Memo<T, U> {
|
|
||||||
pub value: T,
|
|
||||||
pub view: Arc<RwLock<U>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq, U> Memo<T, U> {
|
|
||||||
pub fn new (value: T, view: U) -> Self {
|
|
||||||
Self { value, view: Arc::new(view.into()) }
|
|
||||||
}
|
|
||||||
pub fn update <R> (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
|
|
||||||
if newval != self.value {
|
|
||||||
let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value);
|
|
||||||
self.value = newval;
|
|
||||||
return Some(result);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
src/keys.rs
Normal file
110
src/keys.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
use crate::play::Thread;
|
||||||
|
|
||||||
|
use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
|
||||||
|
use ::std::time::Duration;
|
||||||
|
use ::dizzle::{Usually, Do, impl_from};
|
||||||
|
use ::crossterm::event::{
|
||||||
|
read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||||
|
pub fn tui_input <T: Do<TuiEvent, Usually<T>> + Send + Sync + 'static> (
|
||||||
|
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||||
|
) -> Result<Thread, std::io::Error> {
|
||||||
|
let exited = exited.clone();
|
||||||
|
let state = state.clone();
|
||||||
|
Thread::new_poll(exited.clone(), poll, move |_| {
|
||||||
|
let event = read().unwrap();
|
||||||
|
match event {
|
||||||
|
|
||||||
|
// Hardcoded exit.
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
code: KeyCode::Char('c'),
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
state: KeyEventState::NONE
|
||||||
|
}) => { exited.store(true, Relaxed); },
|
||||||
|
|
||||||
|
// Handle all other events by the state:
|
||||||
|
event => {
|
||||||
|
if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
|
||||||
|
panic!("{e}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TUI input loop event.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
pub struct TuiEvent(pub Event);
|
||||||
|
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
|
||||||
|
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
|
||||||
|
impl Ord for TuiEvent {
|
||||||
|
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.partial_cmp(other)
|
||||||
|
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TUI key spec.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
||||||
|
impl TuiKey {
|
||||||
|
const SPLIT: char = '/';
|
||||||
|
pub fn from_crossterm (event: KeyEvent) -> Self {
|
||||||
|
Self(Some(event.code), event.modifiers)
|
||||||
|
}
|
||||||
|
pub fn to_crossterm (&self) -> Option<Event> {
|
||||||
|
self.0.map(|code|Event::Key(KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers: self.1,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
state: KeyEventState::NONE,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
pub fn named (token: &str) -> Option<KeyCode> {
|
||||||
|
use KeyCode::*;
|
||||||
|
Some(match token {
|
||||||
|
"up" => Up,
|
||||||
|
"down" => Down,
|
||||||
|
"left" => Left,
|
||||||
|
"right" => Right,
|
||||||
|
"esc" | "escape" => Esc,
|
||||||
|
"enter" | "return" => Enter,
|
||||||
|
"delete" | "del" => Delete,
|
||||||
|
"backspace" => Backspace,
|
||||||
|
"tab" => Tab,
|
||||||
|
"space" => Char(' '),
|
||||||
|
"comma" => Char(','),
|
||||||
|
"period" => Char('.'),
|
||||||
|
"plus" => Char('+'),
|
||||||
|
"minus" | "dash" => Char('-'),
|
||||||
|
"equal" | "equals" => Char('='),
|
||||||
|
"underscore" => Char('_'),
|
||||||
|
"backtick" => Char('`'),
|
||||||
|
"lt" => Char('<'),
|
||||||
|
"gt" => Char('>'),
|
||||||
|
"cbopen" | "openbrace" => Char('{'),
|
||||||
|
"cbclose" | "closebrace" => Char('}'),
|
||||||
|
"bropen" | "openbracket" => Char('['),
|
||||||
|
"brclose" | "closebracket" => Char(']'),
|
||||||
|
"pgup" | "pageup" => PageUp,
|
||||||
|
"pgdn" | "pagedown" => PageDown,
|
||||||
|
"f1" => F(1),
|
||||||
|
"f2" => F(2),
|
||||||
|
"f3" => F(3),
|
||||||
|
"f4" => F(4),
|
||||||
|
"f5" => F(5),
|
||||||
|
"f6" => F(6),
|
||||||
|
"f7" => F(7),
|
||||||
|
"f8" => F(8),
|
||||||
|
"f9" => F(9),
|
||||||
|
"f10" => F(10),
|
||||||
|
"f11" => F(11),
|
||||||
|
"f12" => F(12),
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#[cfg(feature = "tui")] impl TuiKey {
|
#[cfg(feature = "term")]
|
||||||
|
impl crate::term::TuiKey {
|
||||||
#[cfg(feature = "lang")]
|
#[cfg(feature = "lang")]
|
||||||
pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
|
pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
|
||||||
if let Some(word) = dsl.word()? {
|
if let Some(word) = dsl.word()? {
|
||||||
|
|
@ -37,5 +37,4 @@
|
||||||
return Err(format!("TuiKey: unspecified").into())
|
return Err(format!("TuiKey: unspecified").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,11 @@ pub(crate) use ::{
|
||||||
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||||
|
|
||||||
#[cfg(feature = "draw")] pub mod draw;
|
#[cfg(feature = "draw")] pub mod draw;
|
||||||
|
#[cfg(feature = "draw")] pub mod space;
|
||||||
#[cfg(feature = "draw")] pub mod color;
|
#[cfg(feature = "draw")] pub mod color;
|
||||||
#[cfg(feature = "text")] pub mod text;
|
#[cfg(feature = "text")] pub mod text;
|
||||||
#[cfg(feature = "term")] pub mod term;
|
#[cfg(feature = "term")] pub mod term;
|
||||||
|
#[cfg(feature = "term")] pub mod keys;
|
||||||
#[cfg(feature = "term")] pub extern crate ratatui;
|
#[cfg(feature = "term")] pub extern crate ratatui;
|
||||||
#[cfg(feature = "term")] pub extern crate crossterm;
|
#[cfg(feature = "term")] pub extern crate crossterm;
|
||||||
|
|
||||||
|
|
|
||||||
18
src/play.rs
18
src/play.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{*, time::*, lang::*};
|
use crate::{*, time::*, lang::*};
|
||||||
use ::std::{thread::JoinHandle, time::Duration};
|
use ::std::{thread::JoinHandle, time::Duration};
|
||||||
#[cfg(feature = "tui")] use ::crossterm::event::poll;
|
#[cfg(feature = "term")] use ::crossterm::event::poll;
|
||||||
|
|
||||||
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
||||||
|
|
||||||
|
|
@ -21,8 +21,8 @@ impl Exit {
|
||||||
|
|
||||||
impl Thread {
|
impl Thread {
|
||||||
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
|
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
|
||||||
pub fn new <F> (exit: Arc<AtomicBool>, call: F) -> Result<Self, std::io::Error>
|
pub fn new <F> (exit: Arc<AtomicBool>, mut call: F) -> Result<Self, std::io::Error>
|
||||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
let perf = Arc::new(PerfModel::default());
|
let perf = Arc::new(PerfModel::default());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
@ -30,7 +30,7 @@ impl Thread {
|
||||||
perf: perf.clone(),
|
perf: perf.clone(),
|
||||||
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
|
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
|
||||||
while !exit.fetch_and(true, Relaxed) {
|
while !exit.fetch_and(true, Relaxed) {
|
||||||
let _ = perf.cycle(&call);
|
let _ = perf.cycle(&mut call);
|
||||||
}
|
}
|
||||||
})?.into()
|
})?.into()
|
||||||
})
|
})
|
||||||
|
|
@ -39,19 +39,19 @@ impl Thread {
|
||||||
/// Spawn a thread that runs `call` least one, then repeats
|
/// Spawn a thread that runs `call` least one, then repeats
|
||||||
/// until `exit`, sleeping for `time` msec after every iteration.
|
/// until `exit`, sleeping for `time` msec after every iteration.
|
||||||
pub fn new_sleep <F> (
|
pub fn new_sleep <F> (
|
||||||
exit: Arc<AtomicBool>, time: Duration, call: F
|
exit: Arc<AtomicBool>, time: Duration, mut call: F
|
||||||
) -> Result<Self, std::io::Error>
|
) -> Result<Self, std::io::Error>
|
||||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
|
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a thread that uses [crossterm::event::poll]
|
/// Spawn a thread that uses [crossterm::event::poll]
|
||||||
/// to run `call` every `time` msec.
|
/// to run `call` every `time` msec.
|
||||||
#[cfg(feature = "tui")]pub fn new_poll <F> (
|
#[cfg(feature = "term")] pub fn new_poll <F> (
|
||||||
exit: Arc<AtomicBool>, time: Duration, call: F
|
exit: Arc<AtomicBool>, time: Duration, mut call: F
|
||||||
) -> Result<Self, std::io::Error>
|
) -> Result<Self, std::io::Error>
|
||||||
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } })
|
Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
482
src/space.rs
Normal file
482
src/space.rs
Normal file
|
|
@ -0,0 +1,482 @@
|
||||||
|
use crate::{*, draw::*};
|
||||||
|
|
||||||
|
/// Point with size.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let xywh = tengri::XYWH(0u16, 0, 0, 0);
|
||||||
|
/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(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> X<N> for XYWH<N> { fn x (&self) -> N { self.0 } fn w (&self) -> N { self.2 } }
|
||||||
|
impl<N: Coord> Y<N> for XYWH<N> { fn y (&self) -> N { self.0 } fn h (&self) -> N { self.2 } }
|
||||||
|
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) {
|
||||||
|
use Split::*;
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Something that has `[0, 0]` at a particular point.
|
||||||
|
pub trait HasOrigin { fn origin (&self) -> Origin;
|
||||||
|
}
|
||||||
|
impl<T: AsRef<Origin>> HasOrigin for T { fn origin (&self) -> Origin { *self.as_ref() } }
|
||||||
|
pub struct Anchor<T>(Origin, T);
|
||||||
|
impl<T> AsRef<Origin> for Anchor<T> {
|
||||||
|
fn as_ref (&self) -> &Origin {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Screen, U: Draw<T>> Draw<T> for Anchor<U> {
|
||||||
|
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn origin_nw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::NW, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_n <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::N, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_ne <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::NE, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_w <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::W, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_c <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::C, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_e <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::E, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_sw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::SW, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_s <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::S, a)
|
||||||
|
}
|
||||||
|
pub const fn origin_se <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Anchor(Origin::SE, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where is [0, 0] located?
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tengri::draw::Origin;
|
||||||
|
/// let _ = Origin::NW.align(())
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(test, derive(Arbitrary))]
|
||||||
|
#[derive(Debug, Copy, Clone, Default)] pub enum Origin {
|
||||||
|
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
|
||||||
|
}
|
||||||
|
impl Origin {
|
||||||
|
pub fn align <T: Screen> (&self, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
align(*self, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// use tengri::draw::{align, Origin::*};
|
||||||
|
/// let _ = align(NW, "test");
|
||||||
|
/// let _ = align(SE, "test");
|
||||||
|
/// ```
|
||||||
|
pub fn align <T: Screen> (origin: Origin, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
thunk(move|to: &mut T| { todo!() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A numeric type that can be used as coordinate.
|
||||||
|
///
|
||||||
|
/// FIXME: Replace with `num` crate?
|
||||||
|
/// FIXME: Use AsRef/AsMut?
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tengri::draw::Coord;
|
||||||
|
/// let a: u16 = Coord::zero();
|
||||||
|
/// let b: u16 = a.plus(1);
|
||||||
|
/// let c: u16 = a.minus(2);
|
||||||
|
/// let d = a.atomic();
|
||||||
|
/// ```
|
||||||
|
pub trait Coord: Send + Sync + Copy
|
||||||
|
+ Add<Self, Output=Self>
|
||||||
|
+ Sub<Self, Output=Self>
|
||||||
|
+ Mul<Self, Output=Self>
|
||||||
|
+ Div<Self, Output=Self>
|
||||||
|
+ Ord + PartialEq + Eq
|
||||||
|
+ Debug + Display + Default
|
||||||
|
+ From<u16> + Into<u16>
|
||||||
|
+ Into<usize>
|
||||||
|
+ Into<f64>
|
||||||
|
+ std::iter::Step
|
||||||
|
{
|
||||||
|
/// Zero in own type.
|
||||||
|
fn zero () -> Self { 0.into() }
|
||||||
|
/// Addition.
|
||||||
|
fn plus (self, other: Self) -> Self;
|
||||||
|
/// Saturating subtraction.
|
||||||
|
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
|
||||||
|
/// Convert to [AtomicUsize].
|
||||||
|
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cardinal direction.
|
||||||
|
#[cfg_attr(test, derive(Arbitrary))]
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
|
||||||
|
North, South, East, West, Above, #[default] Below
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn east <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Split::East.half(a, b)
|
||||||
|
}
|
||||||
|
pub const fn north <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Split::North.half(a, b)
|
||||||
|
}
|
||||||
|
pub const fn west <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Split::West.half(a, b)
|
||||||
|
}
|
||||||
|
pub const fn south <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Split::South.half(a, b)
|
||||||
|
}
|
||||||
|
pub const fn above <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Split::Above.half(a, b)
|
||||||
|
}
|
||||||
|
pub const fn below <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
Split::Below.half(a, b)
|
||||||
|
}
|
||||||
|
impl Split {
|
||||||
|
/// ```
|
||||||
|
/// use tengri::draw::Split::*;
|
||||||
|
/// let _ = Above.bsp((), ());
|
||||||
|
/// let _ = Below.bsp((), ());
|
||||||
|
/// let _ = North.bsp((), ());
|
||||||
|
/// let _ = South.bsp((), ());
|
||||||
|
/// let _ = East.bsp((), ());
|
||||||
|
/// let _ = West.bsp((), ());
|
||||||
|
/// ```
|
||||||
|
pub const fn half <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
thunk(move|to: &mut T|{
|
||||||
|
let (area_a, area_b) = to.xywh().split_half(self);
|
||||||
|
let (origin_a, origin_b) = self.origins();
|
||||||
|
let a = origin_a.align(a);
|
||||||
|
let b = origin_b.align(b);
|
||||||
|
match self {
|
||||||
|
Self::Below => {
|
||||||
|
to.place(&b, Some(area_b));
|
||||||
|
to.place(&a, Some(area_b));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
to.place(&a, Some(area_a));
|
||||||
|
to.place(&b, Some(area_a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(to.xywh()) // FIXME: compute and return actually used area
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// 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) -> (Origin, Origin) {
|
||||||
|
use Origin::*;
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Horizontal axis.
|
||||||
|
pub trait X<N: Coord> {
|
||||||
|
fn x (&self) -> N;
|
||||||
|
fn w (&self) -> N { N::zero() }
|
||||||
|
fn w_min (&self) -> N { self.w() }
|
||||||
|
fn w_max (&self) -> N { self.w() }
|
||||||
|
fn iter_x (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
|
||||||
|
self.x_west()..self.x_east()
|
||||||
|
}
|
||||||
|
fn x_west (&self) -> N where Self: HasOrigin {
|
||||||
|
use Origin::*;
|
||||||
|
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 {
|
||||||
|
use Origin::*;
|
||||||
|
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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn x_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
pub const fn x_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
pub const fn w_max <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
wh_max(w, None, a)
|
||||||
|
}
|
||||||
|
pub const fn w_min <T: Screen> (w: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
wh_min(w, None, a)
|
||||||
|
}
|
||||||
|
pub const fn w_exact <T: Screen> (w: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
wh_exact(Some(w), None, c)
|
||||||
|
}
|
||||||
|
/// Shrink drawing area symmetrically.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let padded = tengri::W(3).pad("Hello");
|
||||||
|
/// ```
|
||||||
|
pub const fn w_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
thunk(move|to: &mut T|draw.draw(todo!()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Y<N: Coord> {
|
||||||
|
fn y (&self) -> N;
|
||||||
|
fn h (&self) -> N { N::zero() }
|
||||||
|
fn h_min (&self) -> N { self.h() }
|
||||||
|
fn h_max (&self) -> N { self.h() }
|
||||||
|
fn iter_y (&self) -> impl Iterator<Item = 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();
|
||||||
|
use Origin::*;
|
||||||
|
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();
|
||||||
|
use Origin::*;
|
||||||
|
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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn y_push <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
pub const fn y_pull <T: Screen> (x: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
pub const fn h_max <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
wh_max(None, h, a)
|
||||||
|
}
|
||||||
|
pub const fn h_min <T: Screen> (h: Option<T::Unit>, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
wh_min(None, h, a)
|
||||||
|
}
|
||||||
|
pub const fn h_exact <T: Screen> (h: T::Unit, c: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
wh_exact(None, Some(h), c)
|
||||||
|
}
|
||||||
|
/// Shrink drawing area symmetrically.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let padded = tengri::W::pad(3, "Hello");
|
||||||
|
/// ```
|
||||||
|
pub const fn h_pad <T: Screen> (x: T::Unit, draw: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
thunk(move|to: &mut T|draw.draw(todo!()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Space<N: Coord>: X<N> + Y<N> {
|
||||||
|
fn xywh (&self) -> XYWH<N> { XYWH(self.x(), self.y(), self.w(), self.h()) }
|
||||||
|
// FIXME: factor origin
|
||||||
|
fn lrtb (&self) -> [N;4] { [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] }
|
||||||
|
}
|
||||||
|
impl<N: Coord, T: X<N> + Y<N>> Space<N> for T {}
|
||||||
|
pub const fn xy_push <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
pub const fn xy_pull <T: Screen> (x: T::Unit, y: T::Unit, a: impl Draw<T>) -> impl Draw<T> {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
/// Shrink drawing area symmetrically.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let padded = tengri::WH(3, 5).pad("Hello");
|
||||||
|
/// ```
|
||||||
|
pub const fn wh_pad <T: Screen> (w: T::Unit, h: T::Unit, draw: impl Draw<T>)
|
||||||
|
-> impl Draw<T>
|
||||||
|
{
|
||||||
|
thunk(move|to: &mut T|draw.draw(todo!()))
|
||||||
|
}
|
||||||
|
/// Only draw content if area is above a certain size.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let min = tengri::wh_min(3, 5, "Hello"); // 5x5
|
||||||
|
/// ```
|
||||||
|
pub const fn wh_min <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
|
||||||
|
-> impl Draw<T>
|
||||||
|
{
|
||||||
|
thunk(move|to: &mut T|draw.draw(todo!()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the maximum width and/or height of the content.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let max = tengri::wh_max(Some(3), Some(5), "Hello");
|
||||||
|
/// ```
|
||||||
|
pub const fn wh_max <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
|
||||||
|
-> impl Draw<T>
|
||||||
|
{
|
||||||
|
thunk(move|to: &mut T|draw.draw(todo!()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the maximum width and/or height of the content.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let exact = tengri::wh_exact(Some(3), Some(5), "Hello");
|
||||||
|
/// ```
|
||||||
|
pub const fn wh_exact <T: Screen> (w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>)
|
||||||
|
-> impl Draw<T>
|
||||||
|
{
|
||||||
|
thunk(move|to: &mut T|draw.draw(todo!()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limit size of drawing area
|
||||||
|
/// ```
|
||||||
|
/// let clipped = tengri::wh_clip(Some(3), Some(5), "Hello");
|
||||||
|
/// ```
|
||||||
|
pub const fn wh_clip <T: Screen> (
|
||||||
|
w: Option<T::Unit>, h: Option<T::Unit>, draw: impl Draw<T>
|
||||||
|
) -> impl Draw<T> {
|
||||||
|
thunk(move|to: &mut T|draw.draw(todo!()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn wh_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
|
||||||
|
pub const fn w_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
|
||||||
|
pub const fn h_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! north {
|
||||||
|
($($tt:tt)*) => { unimplemented!() };
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! south {
|
||||||
|
($($tt:tt)*) => { unimplemented!() };
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! east {
|
||||||
|
($($tt:tt)*) => { unimplemented!() };
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! west {
|
||||||
|
($($tt:tt)*) => { unimplemented!() };
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! above {
|
||||||
|
($($tt:tt)*) => { unimplemented!() };
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! below {
|
||||||
|
($($tt:tt)*) => { unimplemented!() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over a collection of renderables:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tengri::draw::{Origin::*, Split::*};
|
||||||
|
/// let _ = Below.iter([
|
||||||
|
/// NW.align(W(15).max(W(10).min("Leftbar"))),
|
||||||
|
/// NE.align(W(12).max(W(10).min("Rightbar"))),
|
||||||
|
/// Center.align(W(40).max(H(20.max("Center"))))
|
||||||
|
/// ].iter(), |x|x);
|
||||||
|
/// ```
|
||||||
|
pub fn iter <
|
||||||
|
T: Screen,
|
||||||
|
V: Fn()->I,
|
||||||
|
I: Iterator<Item = dyn Draw<T>>,
|
||||||
|
F: Fn(&dyn Draw<T>)->dyn Draw<T>,
|
||||||
|
> (_items: V, _cb: F) -> impl Draw<T> {
|
||||||
|
thunk(move|_to: &mut T|{ todo!() })
|
||||||
|
}
|
||||||
|
pub fn iter_north <
|
||||||
|
T: Screen,
|
||||||
|
V: Fn()->I,
|
||||||
|
I: Iterator<Item = dyn Draw<T>>,
|
||||||
|
F: Fn(&dyn Draw<T>)->dyn Draw<T>,
|
||||||
|
> (_items: V, _cb: F) -> impl Draw<T> {
|
||||||
|
thunk(move|_to: &mut T|{ todo!() })
|
||||||
|
}
|
||||||
|
pub fn iter_east <
|
||||||
|
T: Screen,
|
||||||
|
V: Fn()->I,
|
||||||
|
I: Iterator<Item = dyn Draw<T>>,
|
||||||
|
F: Fn(&dyn Draw<T>)->dyn Draw<T>,
|
||||||
|
> (_items: V, _cb: F) -> impl Draw<T> {
|
||||||
|
thunk(move|_to: &mut T|{ todo!() })
|
||||||
|
}
|
||||||
|
pub fn iter_south <
|
||||||
|
T: Screen,
|
||||||
|
V: Fn()->I,
|
||||||
|
I: Iterator<Item = dyn Draw<T>>,
|
||||||
|
F: Fn(&dyn Draw<T>)->dyn Draw<T>,
|
||||||
|
> (_items: V, _cb: F) -> impl Draw<T> {
|
||||||
|
thunk(move|_to: &mut T|{ todo!() })
|
||||||
|
}
|
||||||
|
pub fn iter_west <
|
||||||
|
T: Screen,
|
||||||
|
V: Fn()->I,
|
||||||
|
I: Iterator<Item = dyn Draw<T>>,
|
||||||
|
F: Fn(&dyn Draw<T>)->dyn Draw<T>,
|
||||||
|
> (_items: V, _cb: F) -> impl Draw<T> {
|
||||||
|
thunk(move|_to: &mut T|{ todo!() })
|
||||||
|
}
|
||||||
657
src/term.rs
657
src/term.rs
|
|
@ -1,156 +1,140 @@
|
||||||
use crate::{*, lang::*, play::*, draw::*, color::*, text::*};
|
use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*};
|
||||||
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||||
use rand::distributions::uniform::UniformSampler;
|
use rand::distributions::uniform::UniformSampler;
|
||||||
use ::{
|
use ::{
|
||||||
std::{
|
std::{
|
||||||
io::{stdout, Write},
|
io::{stdout, Write},
|
||||||
time::Duration
|
time::Duration,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
},
|
},
|
||||||
better_panic::{Settings, Verbosity},
|
better_panic::{Settings, Verbosity},
|
||||||
ratatui::{
|
ratatui::{
|
||||||
prelude::{Color, Style, Buffer, Position, Backend},
|
prelude::{Style, Buffer as ScreenBuffer, Position, Backend, Color},
|
||||||
style::{Modifier, Color::*},
|
style::{Modifier, Color::*},
|
||||||
backend::{CrosstermBackend, ClearType},
|
backend::{CrosstermBackend, ClearType},
|
||||||
layout::{Size, Rect},
|
layout::{Size, Rect},
|
||||||
buffer::Cell
|
buffer::{Buffer, Cell},
|
||||||
},
|
},
|
||||||
crossterm::{
|
crossterm::{
|
||||||
|
ExecutableCommand,
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||||
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
pub struct Tui(pub Buffer, pub XYWH<u16>);
|
||||||
/// Marker trait for structs that may be root of TUI app.
|
impl Screen for Tui { type Unit = u16; }
|
||||||
pub trait Tui: Draw<Buffer> + Do<TuiEvent, Perhaps<TuiEvent>> {}
|
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 } }
|
||||||
/// `Tui` is automatically implemented.
|
impl HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } }
|
||||||
impl<T: Draw<Buffer> + Do<TuiEvent, Perhaps<TuiEvent>>> Tui for T {}
|
impl X<u16> for Tui {
|
||||||
|
fn x (&self) -> u16 { self.1.0 }
|
||||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
fn w (&self) -> u16 { self.1.2 }
|
||||||
pub fn tui_input <T: Act<TuiEvent, T> + Send + Sync + 'static> (
|
}
|
||||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
impl Y<u16> for Tui {
|
||||||
) -> Result<Thread, std::io::Error> {
|
fn y (&self) -> u16 { self.1.1 }
|
||||||
let exited = exited.clone();
|
fn h (&self) -> u16 { self.1.3 }
|
||||||
let state = state.clone();
|
}
|
||||||
Thread::new_poll(exited.clone(), poll, move |_| {
|
impl Tui {
|
||||||
let event = read().unwrap();
|
fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
|
||||||
match event {
|
for row in 0..self.h() {
|
||||||
|
let y = self.y() + row;
|
||||||
// Hardcoded exit.
|
for col in 0..self.w() {
|
||||||
Event::Key(KeyEvent {
|
let x = self.x() + col;
|
||||||
modifiers: KeyModifiers::CONTROL,
|
if x < self.0.area.width && y < self.0.area.height {
|
||||||
code: KeyCode::Char('c'),
|
if let Some(cell) = self.0.cell_mut(Position { x, y }) {
|
||||||
kind: KeyEventKind::Press,
|
callback(cell, col, row);
|
||||||
state: KeyEventState::NONE
|
}
|
||||||
}) => { exited.store(true, Relaxed); },
|
|
||||||
|
|
||||||
// Handle all other events by the state:
|
|
||||||
_ => {
|
|
||||||
let event = TuiEvent::from_crossterm(event);
|
|
||||||
if let Err(e) = state.write().unwrap().handle(&event) {
|
|
||||||
panic!("{e}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.xywh()
|
||||||
|
}
|
||||||
|
fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
||||||
|
for cell in self.0.content.iter_mut() {
|
||||||
|
cell.fg = fg;
|
||||||
|
cell.bg = bg;
|
||||||
|
cell.modifier = modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
||||||
|
let text = text.as_ref();
|
||||||
|
let style = style.unwrap_or(Style::default());
|
||||||
|
if x < self.0.area.width && y < self.0.area.height {
|
||||||
|
self.0.set_string(x, y, text, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Apply foreground color.
|
||||||
|
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
|
thunk(move|to: &mut Tui|{
|
||||||
|
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
||||||
|
draw.draw(to)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Apply background color.
|
||||||
/// TUI input loop event.
|
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
thunk(move|to: &mut Tui|{
|
||||||
pub struct TuiEvent(pub Event);
|
to.update(&|cell,_,_|{ cell.set_bg(bg); });
|
||||||
impl_from!(TuiEvent: |e: Event| TuiEvent(e));
|
draw.draw(to)
|
||||||
impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))));
|
})
|
||||||
impl TuiEvent {
|
|
||||||
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Perhaps<Self> {
|
|
||||||
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl Ord for TuiEvent {
|
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
|
thunk(move|to: &mut Tui|{
|
||||||
self.partial_cmp(other)
|
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
||||||
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
|
draw.draw(to)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||||
/// TUI key spec.
|
thunk(move|to: &mut Tui|Ok(to.update(&|cell,_,_|{
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
cell.set_char(c);
|
||||||
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
|
})))
|
||||||
|
}
|
||||||
impl TuiKey {
|
/// Draw contents with modifier applied.
|
||||||
const SPLIT: char = '/';
|
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
pub fn to_crossterm (&self) -> Option<Event> {
|
thunk(move|to: &mut Tui|{
|
||||||
self.0.map(|code|Event::Key(KeyEvent {
|
fill_mod(on, modifier).draw(to)?;
|
||||||
code,
|
draw.draw(to)
|
||||||
modifiers: self.1,
|
})
|
||||||
kind: KeyEventKind::Press,
|
}
|
||||||
state: KeyEventState::NONE,
|
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
||||||
}))
|
thunk(move|to: &mut Tui|Ok({
|
||||||
}
|
if on {
|
||||||
pub fn named (token: &str) -> Option<KeyCode> {
|
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
||||||
use KeyCode::*;
|
} else {
|
||||||
Some(match token {
|
to.update(&|cell,_,_|cell.modifier.remove(modifier))
|
||||||
"up" => Up,
|
}
|
||||||
"down" => Down,
|
}))
|
||||||
"left" => Left,
|
}
|
||||||
"right" => Right,
|
/// Draw contents with bold modifier applied.
|
||||||
"esc" | "escape" => Esc,
|
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
"enter" | "return" => Enter,
|
modify(on, Modifier::BOLD, draw)
|
||||||
"delete" | "del" => Delete,
|
}
|
||||||
"backspace" => Backspace,
|
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
|
||||||
"tab" => Tab,
|
thunk(move|to: &mut Tui|Ok(if let Some(color) = color {
|
||||||
"space" => Char(' '),
|
to.update(&|cell,_,_|{
|
||||||
"comma" => Char(','),
|
cell.modifier.insert(Modifier::UNDERLINED);
|
||||||
"period" => Char('.'),
|
cell.underline_color = color;
|
||||||
"plus" => Char('+'),
|
|
||||||
"minus" | "dash" => Char('-'),
|
|
||||||
"equal" | "equals" => Char('='),
|
|
||||||
"underscore" => Char('_'),
|
|
||||||
"backtick" => Char('`'),
|
|
||||||
"lt" => Char('<'),
|
|
||||||
"gt" => Char('>'),
|
|
||||||
"cbopen" | "openbrace" => Char('{'),
|
|
||||||
"cbclose" | "closebrace" => Char('}'),
|
|
||||||
"bropen" | "openbracket" => Char('['),
|
|
||||||
"brclose" | "closebracket" => Char(']'),
|
|
||||||
"pgup" | "pageup" => PageUp,
|
|
||||||
"pgdn" | "pagedown" => PageDown,
|
|
||||||
"f1" => F(1),
|
|
||||||
"f2" => F(2),
|
|
||||||
"f3" => F(3),
|
|
||||||
"f4" => F(4),
|
|
||||||
"f5" => F(5),
|
|
||||||
"f6" => F(6),
|
|
||||||
"f7" => F(7),
|
|
||||||
"f8" => F(8),
|
|
||||||
"f9" => F(9),
|
|
||||||
"f10" => F(10),
|
|
||||||
"f11" => F(11),
|
|
||||||
"f12" => F(12),
|
|
||||||
_ => return None,
|
|
||||||
})
|
})
|
||||||
}
|
} else {
|
||||||
|
to.update(&|cell,_,_|{
|
||||||
|
cell.modifier.remove(Modifier::UNDERLINED);
|
||||||
|
cell.underline_color = Reset;
|
||||||
|
})
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TUI works in u16 coordinates.
|
/// TUI works in u16 coordinates.
|
||||||
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
|
impl Coord for u16 {
|
||||||
|
fn plus (self, other: Self) -> Self {
|
||||||
impl Draw<Buffer> for u64 {
|
self.saturating_add(other)
|
||||||
fn draw (&self, _to: &mut Buffer) { todo!() }
|
|
||||||
}
|
|
||||||
impl Draw<Buffer> for f64 {
|
|
||||||
fn draw (&self, _to: &mut Buffer) { todo!() }
|
|
||||||
}
|
|
||||||
impl Draw<Buffer> for &str {
|
|
||||||
fn draw (&self, to: &mut Buffer) {
|
|
||||||
let XYWH(x, y, w, ..) = to.centered_xy([width_chars_max(to.w(), self), 1]);
|
|
||||||
to.text(&self, x, y, w)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Draw<Buffer> for String {
|
|
||||||
fn draw (&self, to: &mut Buffer) { self.as_str().draw(to) }
|
impl Draw<Tui> for u64 {
|
||||||
|
fn draw (self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
|
||||||
}
|
}
|
||||||
impl Draw<Buffer> for Arc<str> {
|
impl Draw<Tui> for f64 {
|
||||||
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
fn draw (self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
mod phat {
|
mod phat {
|
||||||
|
|
@ -158,12 +142,12 @@ mod phat {
|
||||||
pub const LO: &'static str = "▄";
|
pub const LO: &'static str = "▄";
|
||||||
pub const HI: &'static str = "▀";
|
pub const HI: &'static str = "▀";
|
||||||
/// A phat line
|
/// A phat line
|
||||||
pub fn lo (fg: Color, bg: Color) -> impl Draw<Buffer> {
|
pub fn lo (fg: Color, bg: Color) -> impl Draw<Tui> {
|
||||||
H::fixed(1, Tui::fg_bg(fg, bg, X::repeat(self::phat::LO)))
|
h_exact(1, fg_bg(fg, bg, x_repeat(self::phat::LO)))
|
||||||
}
|
}
|
||||||
/// A phat line
|
/// A phat line
|
||||||
pub fn hi (fg: Color, bg: Color) -> impl Draw<Buffer> {
|
pub fn hi (fg: Color, bg: Color) -> impl Draw<Tui> {
|
||||||
H::fixed(1, Tui::fg_bg(fg, bg, X::repeat(self::phat::HI)))
|
h_exact(1, fg_bg(fg, bg, x_repeat(self::phat::HI)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,66 +158,71 @@ mod scroll {
|
||||||
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn x_repeat (c: char) {
|
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
|
||||||
move|to: &mut Buffer|{
|
thunk(move|to: &mut Tui|{
|
||||||
let XYWH(x, y, w, h) = to.area();
|
let XYWH(x, y, w, h) = to.xywh();
|
||||||
for x in x..x+w {
|
for x in x..x+w {
|
||||||
if let Some(cell) = to.cell_mut(Position::from((x, y))) {
|
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||||
cell.set_symbol(&c);
|
cell.set_symbol(&c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(XYWH(x, y, w, 1))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn y_repeat (c: char) {
|
pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
|
||||||
move|to: &mut Buffer|{
|
thunk(move|to: &mut Tui|{
|
||||||
let XYWH(x, y, w, h) = to.area();
|
let XYWH(x, y, w, h) = to.xywh();
|
||||||
for y in y..y+h {
|
for y in y..y+h {
|
||||||
if let Some(cell) = to.cell_mut(Position::from((x, y))) {
|
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||||
cell.set_symbol(&c);
|
cell.set_symbol(&c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(XYWH(x, y, 1, h))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xy_repeat (c: char) {
|
pub const fn xy_repeat (c: &str) -> impl Draw<Tui> {
|
||||||
move|to: &mut Buffer|{
|
thunk(move|to: &mut Tui|{
|
||||||
let XYWH(x, y, w, h) = to.area();
|
let XYWH(x, y, w, h) = to.xywh();
|
||||||
let a = c.len();
|
let a = c.len();
|
||||||
for (_v, y) in (y..y+h).enumerate() {
|
for (_v, y) in (y..y+h).enumerate() {
|
||||||
for (u, x) in (x..x+w).enumerate() {
|
for (u, x) in (x..x+w).enumerate() {
|
||||||
if let Some(cell) = to.cell_mut(Position::from((x, y))) {
|
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
|
||||||
let u = u % a;
|
let u = u % a;
|
||||||
cell.set_symbol(&c[u..u+1]);
|
cell.set_symbol(&c[u..u+1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(XYWH(x, y, w, h))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ```
|
/// ```
|
||||||
/// let _ = tengri::button_2("", "", true);
|
/// let _ = tengri::button_2("", "", true);
|
||||||
/// let _ = tengri::button_2("", "", false);
|
/// let _ = tengri::button_2("", "", false);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn button_2 <'a> (key: impl Draw<Buffer>, label: impl Draw<Buffer>, editing: bool) -> impl Draw<Buffer> {
|
pub const fn button_2 <'a> (key: impl Draw<Tui>, label: impl Draw<Tui>, hide: bool) -> impl Draw<Tui> {
|
||||||
Tui::bold(true, bsp_e(
|
let c1 = tui_orange();
|
||||||
Tui::fg_bg(Tui::orange(), Tui::g(0), bsp_e(Tui::fg(Tui::g(0), &"▐"), bsp_e(key, Tui::fg(Tui::g(96), &"▐")))),
|
let c2 = tui_g(0);
|
||||||
when(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label))))
|
let c3 = tui_g(96);
|
||||||
|
let c4 = tui_g(255);
|
||||||
|
bold(true, fg_bg(c1, c2, east(fg(c2, east(key, fg(c3, "▐"))), when(!hide, fg_bg(c4, c3, label)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ```
|
/// ```
|
||||||
/// let _ = tengri::button_3("", "", "", true);
|
/// let _ = tengri::button_3("", "", "", true);
|
||||||
/// let _ = tengri::button_3("", "", "", false);
|
/// let _ = tengri::button_3("", "", "", false);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn button_3 <'a> (
|
pub const fn button_3 <'a> (
|
||||||
key: impl Draw<Buffer>, label: impl Draw<Buffer>, value: impl Draw<Buffer>, editing: bool,
|
key: impl Draw<Tui>, label: impl Draw<Tui>, value: impl Draw<Tui>, editing: bool,
|
||||||
) -> impl Draw<Buffer> {
|
) -> impl Draw<Tui> {
|
||||||
Tui::bold(true, bsp_e(
|
bold(true, east(
|
||||||
Tui::fg_bg(Tui::orange(), Tui::g(0),
|
fg_bg(tui_orange(), tui_g(0),
|
||||||
bsp_e(Tui::fg(Tui::g(0), &"▐"), bsp_e(key, Tui::fg(if editing { Tui::g(128) } else { Tui::g(96) }, "▐")))),
|
east(fg(tui_g(0), "▐"), east(key, fg(if editing { tui_g(128) } else { tui_g(96) }, "▐")))),
|
||||||
bsp_e(
|
east(
|
||||||
when(!editing, bsp_e(Tui::fg_bg(Tui::g(255), Tui::g(96), label), Tui::fg_bg(Tui::g(128), Tui::g(96), &"▐"),)),
|
when(!editing, east(fg_bg(tui_g(255), tui_g(96), label), fg_bg(tui_g(128), tui_g(96), "▐"),)),
|
||||||
bsp_e(Tui::fg_bg(Tui::g(224), Tui::g(128), value), Tui::fg_bg(Tui::g(128), Reset, &"▌"), ))))
|
east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, "▌"), ))))
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! border {
|
macro_rules! border {
|
||||||
|
|
@ -255,9 +244,9 @@ 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<Buffer> for $T {
|
impl Draw<Tui> for $T {
|
||||||
fn draw (&self, to: &mut Buffer) {
|
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
if self.enabled() { let _ = BorderStyle::draw(self, to); }
|
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)+}
|
)+}
|
||||||
|
|
@ -369,87 +358,7 @@ border! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TuiOut: Screen {
|
pub trait BorderStyle: Draw<Tui> + Copy {
|
||||||
fn tui_out (&mut self) -> &mut Buffer;
|
|
||||||
fn update (&mut self, area: impl Area<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
|
|
||||||
tui_update(self.buffer(), area, callback);
|
|
||||||
}
|
|
||||||
fn fill_char (&mut self, area: impl Area<u16>, c: char) {
|
|
||||||
self.update(area, &|cell,_,_|{cell.set_char(c);})
|
|
||||||
}
|
|
||||||
fn fill_bg (&mut self, area: impl Area<u16>, color: Color) {
|
|
||||||
self.update(area, &|cell,_,_|{cell.set_bg(color);})
|
|
||||||
}
|
|
||||||
fn fill_fg (&mut self, area: impl Area<u16>, color: Color) {
|
|
||||||
self.update(area, &|cell,_,_|{cell.set_fg(color);})
|
|
||||||
}
|
|
||||||
fn fill_mod (&mut self, area: impl Area<u16>, on: bool, modifier: Modifier) {
|
|
||||||
if on {
|
|
||||||
self.update(area, &|cell,_,_|cell.modifier.insert(modifier))
|
|
||||||
} else {
|
|
||||||
self.update(area, &|cell,_,_|cell.modifier.remove(modifier))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn fill_bold (&mut self, area: impl Area<u16>, on: bool) {
|
|
||||||
self.fill_mod(area, on, Modifier::BOLD)
|
|
||||||
}
|
|
||||||
fn fill_reversed (&mut self, area: impl Area<u16>, on: bool) {
|
|
||||||
self.fill_mod(area, on, Modifier::REVERSED)
|
|
||||||
}
|
|
||||||
fn fill_crossed_out (&mut self, area: impl Area<u16>, on: bool) {
|
|
||||||
self.fill_mod(area, on, Modifier::CROSSED_OUT)
|
|
||||||
}
|
|
||||||
fn fill_ul (&mut self, area: impl Area<u16>, color: Option<Color>) {
|
|
||||||
if let Some(color) = color {
|
|
||||||
self.update(area, &|cell,_,_|{
|
|
||||||
cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED);
|
|
||||||
cell.underline_color = color;
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.update(area, &|cell,_,_|{
|
|
||||||
cell.modifier.remove(ratatui::prelude::Modifier::UNDERLINED);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
|
||||||
for cell in self.buffer().content.iter_mut() {
|
|
||||||
cell.fg = fg;
|
|
||||||
cell.bg = bg;
|
|
||||||
cell.modifier = modifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
|
||||||
let text = text.as_ref();
|
|
||||||
let style = style.unwrap_or(Style::default());
|
|
||||||
let buf = self.buffer();
|
|
||||||
if x < buf.area.width && y < buf.area.height {
|
|
||||||
buf.set_string(x, y, text, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Write a line of text
|
|
||||||
///
|
|
||||||
/// TODO: do a paragraph (handle newlines)
|
|
||||||
fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) {
|
|
||||||
let text = text.as_ref();
|
|
||||||
let buf = self.buffer();
|
|
||||||
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 Some(cell) = buf.write().unwrap().cell_mut(ratatui::prelude::Position { x, y }) {
|
|
||||||
cell.set_char(character);
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait BorderStyle: Draw<Buffer> + Copy {
|
|
||||||
fn enabled (&self) -> bool;
|
fn enabled (&self) -> bool;
|
||||||
fn border_n (&self) -> &str { Self::N }
|
fn border_n (&self) -> &str { Self::N }
|
||||||
fn border_s (&self) -> &str { Self::S }
|
fn border_s (&self) -> &str { Self::S }
|
||||||
|
|
@ -460,36 +369,26 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
||||||
fn border_sw (&self) -> &str { Self::SW }
|
fn border_sw (&self) -> &str { Self::SW }
|
||||||
fn border_se (&self) -> &str { Self::SE }
|
fn border_se (&self) -> &str { Self::SE }
|
||||||
|
|
||||||
fn enclose (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
#[inline] fn draw <'a> (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
bsp_b(XY::fill(border(self.enabled(), self)), w)
|
|
||||||
}
|
|
||||||
fn enclose2 (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
|
||||||
bsp_b(XY::pad(1, 1, XY::fill(border(self.enabled(), self))), w)
|
|
||||||
}
|
|
||||||
fn enclose_bg (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
|
|
||||||
TuiOut::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
|
|
||||||
bsp_b(XY::fill(border(self.enabled(), self)), w))
|
|
||||||
}
|
|
||||||
#[inline] fn draw <'a> (&self, to: &mut impl TuiOut) -> Usually<()> {
|
|
||||||
if self.enabled() {
|
if self.enabled() {
|
||||||
self.draw_h(to, None)?;
|
self.draw_h(to, None)?;
|
||||||
self.draw_v(to, None)?;
|
self.draw_v(to, None)?;
|
||||||
self.draw_c(to, None)?;
|
self.draw_c(to, None)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(to.1)
|
||||||
}
|
}
|
||||||
#[inline] fn draw_h (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
#[inline] fn draw_h (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_horizontal());
|
let style = style.or_else(||self.style_horizontal());
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
let y1 = to.y_north();
|
||||||
for x in x..x2.saturating_sub(1) {
|
let y2 = to.y_south().saturating_sub(1);
|
||||||
to.blit(&Self::N, x, y, style);
|
for x in to.x_west()..to.x_east().saturating_sub(1) {
|
||||||
to.blit(&Self::S, x, y2.saturating_sub(1), style)
|
to.blit(&Self::N, x, y1, style);
|
||||||
|
to.blit(&Self::S, x, y2, style)
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(to.xywh())
|
||||||
}
|
}
|
||||||
#[inline] fn draw_v (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
#[inline] fn draw_v (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||||
let area = to.area();
|
let area = to.1;
|
||||||
let style = style.or_else(||self.style_vertical());
|
let style = style.or_else(||self.style_vertical());
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
let [x, x2, y, y2] = area.lrtb();
|
||||||
let h = y2 - y;
|
let h = y2 - y;
|
||||||
|
|
@ -504,8 +403,8 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
#[inline] fn draw_c (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl Area<u16>> {
|
#[inline] fn draw_c (self, to: &mut Tui, style: Option<Style>) -> Usually<XYWH<u16>> {
|
||||||
let area = to.area();
|
let area = to.1;
|
||||||
let style = style.or_else(||self.style_corners());
|
let style = style.or_else(||self.style_corners());
|
||||||
let XYWH(x, y, w, h) = area;
|
let XYWH(x, y, w, h) = area;
|
||||||
if w > 1 && h > 1 {
|
if w > 1 && h > 1 {
|
||||||
|
|
@ -541,28 +440,26 @@ pub trait BorderStyle: Draw<Buffer> + Copy {
|
||||||
/// ```
|
/// ```
|
||||||
/// /// TODO
|
/// /// TODO
|
||||||
/// ```
|
/// ```
|
||||||
pub fn phat <T, N: Coord> (w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Tui) -> impl Tui {
|
pub fn phat (w: u16, h: u16, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
let top = W::exact(1, self::phat::lo(bg, hi));
|
let top = w_exact(1, self::phat::lo(bg, hi));
|
||||||
let low = W::exact(1, self::phat::hi(bg, lo));
|
let low = w_exact(1, self::phat::hi(bg, lo));
|
||||||
let draw = Tui::fg_bg(fg, bg, draw);
|
let draw = fg_bg(fg, bg, draw);
|
||||||
WH::min(w, h, bsp_s(top, bsp_n(low, WH::fill(draw))))
|
wh_min(Some(w), Some(h), south(top, north(low, draw)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn x_scroll () {
|
fn x_scroll () -> impl Draw<Tui> {
|
||||||
|to: &mut Buffer|{
|
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
|
||||||
let XYWH(x1, y1, w, h) = to.area();
|
let x2 = *x1 + *w;
|
||||||
let mut buf = to.buffer.write().unwrap();
|
for (i, x) in (*x1..=x2).enumerate() {
|
||||||
let x2 = x1 + w;
|
if let Some(cell) = buf.cell_mut(Position::from((x, *y1))) {
|
||||||
for (i, x) in (x1..=x2).enumerate() {
|
|
||||||
if let Some(cell) = buf.cell_mut(Position::from((x, y1))) {
|
|
||||||
if i < (self::scroll::ICON_DEC_H.len()) {
|
if i < (self::scroll::ICON_DEC_H.len()) {
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
cell.set_char(self::scroll::ICON_DEC_H[i as usize]);
|
cell.set_char(self::scroll::ICON_DEC_H[i as usize]);
|
||||||
} else if i > (w as usize - self::scroll::ICON_INC_H.len()) {
|
} else if i > (*w as usize - self::scroll::ICON_INC_H.len()) {
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
cell.set_char(self::scroll::ICON_INC_H[w as usize - i]);
|
cell.set_char(self::scroll::ICON_INC_H[*w as usize - i]);
|
||||||
} else if false {
|
} else if false {
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
cell.set_bg(Reset);
|
cell.set_bg(Reset);
|
||||||
|
|
@ -574,24 +471,23 @@ fn x_scroll () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(XYWH(*x1, *y1, *w, 1))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn y_scroll () {
|
fn y_scroll () -> impl Draw<Tui> {
|
||||||
|to: &mut Buffer|{
|
thunk(|Tui(buf, XYWH(x1, y1, w, h)): &mut Tui|{
|
||||||
let XYWH(x1, y1, w, h) = to.area();
|
let y2 = *y1 + *h;
|
||||||
let mut buf = to.buffer.write().unwrap();
|
for (i, y) in (*y1..=y2).enumerate() {
|
||||||
let y2 = y1 + h;
|
if let Some(cell) = buf.cell_mut(Position::from((*x1, y))) {
|
||||||
for (i, y) in (y1..=y2).enumerate() {
|
|
||||||
if let Some(cell) = buf.cell_mut(Position::from((x1, y))) {
|
|
||||||
if (i as usize) < (self::scroll::ICON_DEC_V.len()) {
|
if (i as usize) < (self::scroll::ICON_DEC_V.len()) {
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
cell.set_char(self::scroll::ICON_DEC_V[i as usize]);
|
cell.set_char(self::scroll::ICON_DEC_V[i as usize]);
|
||||||
} else if (i as usize) > (h as usize - self::scroll::ICON_INC_V.len()) {
|
} else if (i as usize) > (*h as usize - self::scroll::ICON_INC_V.len()) {
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
cell.set_bg(Rgb(0, 0, 0));
|
cell.set_bg(Rgb(0, 0, 0));
|
||||||
cell.set_char(self::scroll::ICON_INC_V[h as usize - i]);
|
cell.set_char(self::scroll::ICON_INC_V[*h as usize - i]);
|
||||||
} else if false {
|
} else if false {
|
||||||
cell.set_fg(Rgb(255, 255, 255));
|
cell.set_fg(Rgb(255, 255, 255));
|
||||||
cell.set_bg(Reset);
|
cell.set_bg(Reset);
|
||||||
|
|
@ -603,56 +499,32 @@ fn y_scroll () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(XYWH(*x1, *y1, 1, *h))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||||
pub fn tui_output <W: Write, T: Draw<Buffer> + Send + Sync + 'static> (
|
pub fn tui_output <W: Write + Send + Sync + 'static, T: Draw<Tui> + Send + Sync + 'static> (
|
||||||
output: W,
|
output: W,
|
||||||
exited: &Arc<AtomicBool>,
|
exited: &Arc<AtomicBool>,
|
||||||
state: &Arc<RwLock<T>>,
|
state: &Arc<RwLock<T>>,
|
||||||
sleep: Duration
|
sleep: Duration
|
||||||
) -> Result<Thread, std::io::Error> {
|
) -> Usually<Thread> {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
tui_setup(&mut output)?;
|
tui_setup()?;
|
||||||
let mut backend = CrosstermBackend::new(output);
|
let mut backend = CrosstermBackend::new(output);
|
||||||
let WH(width, height) = tui_wh(&mut backend);
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
||||||
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
||||||
Thread::new_sleep(exited.clone(), sleep, move |perf| {
|
Ok(Thread::new_sleep(exited.clone(), sleep, move |perf| {
|
||||||
let size = tui_wh(&mut backend);
|
let Size { width, height } = backend.size().expect("get size failed");
|
||||||
if let Ok(state) = state.try_read() {
|
if let Ok(state) = state.try_read() {
|
||||||
tui_resize(&mut backend, &mut buffer_a, size);
|
tui_resize(&mut backend, &mut buffer_a, Rect { x: 0, y: 0, width, height });
|
||||||
buffer_a = tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
|
tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b);
|
||||||
}
|
}
|
||||||
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||||
buffer_a.set_string(0, 0, &timer, Style::default());
|
buffer_a.set_string(0, 0, &timer, Style::default());
|
||||||
})
|
})?)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_setup (output: &mut impl Write) -> Usually<()> {
|
|
||||||
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
|
||||||
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
|
||||||
output.execute(LeaveAlternateScreen).unwrap();
|
|
||||||
CrosstermBackend::new(output).show_cursor().unwrap();
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
better_panic_handler(info);
|
|
||||||
}));
|
|
||||||
output.execute(EnterAlternateScreen)?;
|
|
||||||
CrosstermBackend::new(output).hide_cursor()?;
|
|
||||||
enable_raw_mode().map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_resize <W: Write> (
|
|
||||||
backend: &mut CrosstermBackend<W>,
|
|
||||||
buffer: &mut Buffer,
|
|
||||||
size: WH<u16>
|
|
||||||
) {
|
|
||||||
if buffer.area != size {
|
|
||||||
backend.clear_region(ClearType::All).unwrap();
|
|
||||||
buffer.resize(size);
|
|
||||||
buffer.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tui_redraw <'b, W: Write> (
|
pub fn tui_redraw <'b, W: Write> (
|
||||||
|
|
@ -665,7 +537,31 @@ pub fn tui_redraw <'b, W: Write> (
|
||||||
Backend::flush(backend).expect("failed to flush output new_buffer");
|
Backend::flush(backend).expect("failed to flush output new_buffer");
|
||||||
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
||||||
next_buffer.reset();
|
next_buffer.reset();
|
||||||
next_buffer
|
}
|
||||||
|
|
||||||
|
pub fn tui_setup () -> Usually<()> {
|
||||||
|
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
||||||
|
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
||||||
|
stdout().execute(LeaveAlternateScreen).unwrap();
|
||||||
|
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
better_panic_handler(info);
|
||||||
|
}));
|
||||||
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
CrosstermBackend::new(stdout()).hide_cursor()?;
|
||||||
|
enable_raw_mode().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tui_resize <W: Write> (
|
||||||
|
backend: &mut CrosstermBackend<W>,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
size: Rect
|
||||||
|
) {
|
||||||
|
if buffer.area != size {
|
||||||
|
backend.clear_region(ClearType::All).unwrap();
|
||||||
|
buffer.resize(size);
|
||||||
|
buffer.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
||||||
|
|
@ -675,48 +571,8 @@ pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tui_update (
|
pub fn tui_update (
|
||||||
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
Tui(buf, ..): &mut Tui, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
||||||
) {
|
) {
|
||||||
for row in 0..area.h() {
|
|
||||||
let y = area.y() + row;
|
|
||||||
for col in 0..area.w() {
|
|
||||||
let x = area.x() + col;
|
|
||||||
if x < buf.area.width && y < buf.area.height {
|
|
||||||
if let Some(cell) = buf.cell_mut(ratatui::prelude::Position { x, y }) {
|
|
||||||
callback(cell, col, row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn tui_wh <W: Write> (backend: &mut CrosstermBackend<W>) -> WH<u16> {
|
|
||||||
let Size { width, height } = backend.size().expect("get size failed");
|
|
||||||
WH(width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw contents with foreground color applied.
|
|
||||||
pub const fn fg (enabled: bool, color: Color, item: impl Tui) -> impl Tui {
|
|
||||||
|to: &mut Buffer|item.draw(to.with_fg(enabled, color))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw contents with background color applied.
|
|
||||||
pub const fn bg (enabled: bool, color: Color, item: impl Tui) -> impl Tui {
|
|
||||||
|to: &mut Buffer|item.draw(to.with_bg(enabled, color))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw contents with modifier applied.
|
|
||||||
pub const fn modify (enabled: bool, modifier: Modifier, item: impl Tui) -> impl Tui {
|
|
||||||
|to: &mut Buffer|item.draw(to.with_modifier(enabled, modifier, item))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn bold (enabled: bool, item: impl Tui) -> impl Tui {
|
|
||||||
modify(enabled, Modifier::BOLD, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw contents with style applied.
|
|
||||||
pub const fn styled (enabled: bool, style: Style, item: impl Tui) -> impl Tui {
|
|
||||||
|to: &mut Buffer|item.draw(to.with_style(enabled, style, item))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw border around shrinked item.
|
/// Draw border around shrinked item.
|
||||||
|
|
@ -724,24 +580,27 @@ pub const fn styled (enabled: bool, style: Style, item: impl Tui) -> impl Tui {
|
||||||
/// ```
|
/// ```
|
||||||
/// /// TODO
|
/// /// TODO
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Tui) -> impl Tui {
|
pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||||
WH::fill(bsp_a(when(on, |to: &mut Area|{
|
let content = wh_pad(1, 1, draw);
|
||||||
let area = to.area();
|
let outline = when(on, thunk(move|to: &mut Tui|{
|
||||||
if area.w() > 0 && area.y() > 0 {
|
let XYWH(x, y, w, h) = to.1;
|
||||||
to.blit(&style.border_nw(), area.x(), area.y(), style.style());
|
if w > 0 && h > 0 {
|
||||||
to.blit(&style.border_ne(), area.x() + area.w() - 1, area.y(), style.style());
|
to.blit(&style.border_nw(), x, y, style.style());
|
||||||
to.blit(&style.border_sw(), area.x(), area.y() + area.h() - 1, style.style());
|
to.blit(&style.border_ne(), x + w - 1, y, style.style());
|
||||||
to.blit(&style.border_se(), area.x() + area.w() - 1, area.y() + area.h() - 1, style.style());
|
to.blit(&style.border_sw(), x, y + h - 1, style.style());
|
||||||
for x in area.x()+1..area.x()+area.w()-1 {
|
to.blit(&style.border_se(), x + w - 1, y + h - 1, style.style());
|
||||||
to.blit(&style.border_n(), x, area.y(), style.style());
|
for x in x+1..x+w-1 {
|
||||||
to.blit(&style.border_s(), x, area.y() + area.h() - 1, style.style());
|
to.blit(&style.border_n(), x, y, style.style());
|
||||||
|
to.blit(&style.border_s(), x, y + h - 1, style.style());
|
||||||
}
|
}
|
||||||
for y in area.y()+1..area.y()+area.h()-1 {
|
for y in y+1..y+h-1 {
|
||||||
to.blit(&style.border_w(), area.x(), y, style.style());
|
to.blit(&style.border_w(), x, y, style.style());
|
||||||
to.blit(&style.border_e(), area.x() + area.w() - 1, y, style.style());
|
to.blit(&style.border_e(), x + w - 1, y, style.style());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), pad(Some(1), Some(1), draw)))
|
Ok(XYWH(x, y, w, h))
|
||||||
|
}));
|
||||||
|
above(outline, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw TUI content or its error message.
|
/// Draw TUI content or its error message.
|
||||||
|
|
@ -751,18 +610,17 @@ pub const fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Tui) ->
|
||||||
/// let _ = tengri::tui::catcher(Ok(None));
|
/// let _ = tengri::tui::catcher(Ok(None));
|
||||||
/// let _ = tengri::tui::catcher(Err("draw fail".into()));
|
/// let _ = tengri::tui::catcher(Err("draw fail".into()));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn catcher <T, E> (error: Perhaps<E>, draw: impl Tui) -> impl Tui {
|
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
||||||
move|to: &mut Buffer|match error.as_ref() {
|
thunk(move|to: &mut Tui|match result {
|
||||||
Ok(Some(content)) => draw(to),
|
Ok(content) => content.draw(to),
|
||||||
Ok(None) => to.blit(&"<empty>", 0, 0, Some(Style::default().yellow())),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_fg = rgb(255,224,244);
|
let err_fg = Color::Rgb(255,224,244);
|
||||||
let err_bg = rgb(96, 24, 24);
|
let err_bg = Color::Rgb(96, 24, 24);
|
||||||
let title = bsp_e(bold(true, "upsi daisy. "), "rendering failed.");
|
let title = east(bold(true, "upsi daisy. "), "rendering failed.");
|
||||||
let error = bsp_e("\"why?\" ", bold(true, format!("{e}")));
|
let error = east("\"why?\" ", bold(true, format!("{e}")));
|
||||||
&fg(err_fg, bg(err_bg, bsp_s(title, error)))(to)
|
fg(err_fg, bg(err_bg, south(title, error))).draw(to)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TUI buffer sized by `usize` instead of `u16`.
|
/// TUI buffer sized by `usize` instead of `u16`.
|
||||||
|
|
@ -790,3 +648,28 @@ impl BigBuffer {
|
||||||
y * self.width + x
|
y * self.width + x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use self::colors::*; mod colors {
|
||||||
|
use ratatui::prelude::Color;
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
|
|
||||||
134
src/text.rs
134
src/text.rs
|
|
@ -1,18 +1,75 @@
|
||||||
use crate::draw::Draw;
|
use crate::{Usually, draw::Draw, space::*};
|
||||||
pub(crate) use ::unicode_width::*;
|
pub(crate) use ::unicode_width::*;
|
||||||
|
|
||||||
/// Displays an owned [str]-like with fixed maximum width.
|
#[cfg(feature = "term")] mod impl_term {
|
||||||
///
|
use super::*;
|
||||||
/// Width is computed using [unicode_width].
|
use crate::term::Tui;
|
||||||
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
use ratatui::prelude::Position;
|
||||||
|
|
||||||
/// Displays a borrowed [str]-like with fixed maximum width
|
impl Draw<Tui> for &str {
|
||||||
///
|
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
/// Width is computed using [unicode_width].
|
let XYWH(x, y, w, ..) = to.1.centered_xy([width_chars_max(to.w(), self), 1]);
|
||||||
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
to.text(&self, x, y, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Draw<Tui> for String {
|
||||||
|
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
|
self.as_str().draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Draw<Tui> for std::sync::Arc<str> {
|
||||||
|
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
|
self.as_ref().draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, T: AsRef<str>> TrimString<T> {
|
impl<T: AsRef<str>> Draw<Tui> for TrimString<T> {
|
||||||
fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
|
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { self.as_ref().draw(to) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
|
||||||
|
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
|
let XYWH(x, y, w, ..) = to.1;
|
||||||
|
let mut width: u16 = 1;
|
||||||
|
let mut chars = self.1.as_ref().chars();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if width > self.0 || width > w {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let pos = Position { x: x + width - 1, y };
|
||||||
|
if let Some(cell) = to.0.cell_mut(pos) {
|
||||||
|
cell.set_char(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) -> Usually<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 Some(cell) = self.0.cell_mut(ratatui::prelude::Position { x, y }) {
|
||||||
|
cell.set_char(character);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(XYWH(x0, y, string_width, 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trim string with [unicode_width].
|
/// Trim string with [unicode_width].
|
||||||
|
|
@ -31,6 +88,23 @@ pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||||
return output.into_iter().collect()
|
return output.into_iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Displays an owned [str]-like with fixed maximum width.
|
||||||
|
///
|
||||||
|
/// Width is computed using [unicode_width].
|
||||||
|
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
||||||
|
impl<T: AsRef<str>> AsRef<str> for TrimString<T> { fn as_ref (&self) -> &str { self.1.as_ref() } }
|
||||||
|
impl<'a, T: AsRef<str>> TrimString<T> {
|
||||||
|
fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a borrowed [str]-like with fixed maximum width
|
||||||
|
///
|
||||||
|
/// Width is computed using [unicode_width].
|
||||||
|
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
||||||
|
impl<T: AsRef<str>> AsRef<str> for TrimStringRef<'_, T> {
|
||||||
|
fn as_ref (&self) -> &str { self.1.as_ref() }
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
||||||
let mut width: u16 = 0;
|
let mut width: u16 = 0;
|
||||||
let mut chars = text.as_ref().chars();
|
let mut chars = text.as_ref().chars();
|
||||||
|
|
@ -42,41 +116,3 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
||||||
}
|
}
|
||||||
return width
|
return width
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "term")] mod impl_term {
|
|
||||||
use super::*;
|
|
||||||
use crate::draw::XYWH;
|
|
||||||
use ratatui::prelude::{Buffer, Position};
|
|
||||||
impl<'a, T> Draw<Buffer> for TrimStringRef<'a, T> {
|
|
||||||
fn draw (&self, to: &mut Buffer) {
|
|
||||||
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, x, y, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> Draw<Buffer> for TrimString<T> {
|
|
||||||
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
|
|
||||||
}
|
|
||||||
impl<'a, T: AsRef<str>> Draw<Buffer> for TrimString<T> {
|
|
||||||
fn draw (&self, to: &mut Buffer) { Draw::draw(&self.as_ref(), to) }
|
|
||||||
}
|
|
||||||
impl<T: AsRef<str>> Draw<Buffer> for TrimStringRef<'_, T> {
|
|
||||||
fn draw (&self, target: &mut Buffer) {
|
|
||||||
let area = target.area();
|
|
||||||
let mut buf = target.buffer.write().unwrap();
|
|
||||||
let mut width: u16 = 1;
|
|
||||||
let mut chars = self.1.as_ref().chars();
|
|
||||||
while let Some(c) = chars.next() {
|
|
||||||
if width > self.0 || width > area.w() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if let Some(cell) = buf.cell_mut(Position {
|
|
||||||
x: area.x() + width - 1,
|
|
||||||
y: area.y()
|
|
||||||
}) {
|
|
||||||
cell.set_char(c);
|
|
||||||
}
|
|
||||||
width += c.width().unwrap_or(0) as u16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ impl PerfModel {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
|
pub fn cycle <F: FnMut(&Self)->T, T> (&self, call: &mut F) -> T {
|
||||||
let t0 = self.get_t0();
|
let t0 = self.get_t0();
|
||||||
let result = call(self);
|
let result = call(self);
|
||||||
let _t1 = self.get_t1(t0).unwrap();
|
let _t1 = self.get_t1(t0).unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue