Compare commits

..

No commits in common. "04db6f4af549012a4ffab3f06af3d2d6694cf811" and "ce2eeaee7fc23c3f3683cee310a9117d764409cb" have entirely different histories.

6 changed files with 128 additions and 134 deletions

View file

@ -1,43 +1,36 @@
#[cfg(test)] use proptest_derive::Arbitrary;
use crate::*; use crate::*;
/// A point (X, Y). /// A point (X, Y).
/// ///
/// ``` /// ```
/// let xy = tengri::output::XY(0u16, 0); /// let xy: XY<u16> = XY(0, 0);
/// ``` /// ```
#[cfg_attr(test, derive(Arbitrary))] #[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XY<C: Coord>( #[derive(Copy, Clone, Debug, Default)] pub struct XY<C: Coord>(pub C, pub C);
pub C, pub C
);
/// A size (Width, Height). /// A size (Width, Height).
/// ///
/// ``` /// ```
/// let wh = tengri::output::WH(0u16, 0); /// let wh: WH<u16> = WH(0, 0);
/// ``` /// ```
#[cfg_attr(test, derive(Arbitrary))] #[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct WH<C: Coord>( #[derive(Copy, Clone, Debug, Default)] pub struct WH<C: Coord>(pub C, pub C);
pub C, pub C
);
/// Point with size. /// Point with size.
/// ///
/// ``` /// ```
/// let xywh = tengri::output::XYWH(0u16, 0, 0, 0); /// let xywh: XYWH<u16> = XYWH(0, 0, 0, 0);
/// ``` /// ```
/// ///
/// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0) /// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0)
/// ///
#[cfg_attr(test, derive(Arbitrary))] #[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH<C: Coord>( #[derive(Copy, Clone, Debug, Default)] pub struct XYWH<C: Coord>(pub C, pub C, pub C, pub C);
pub C, pub C, pub C, pub C
);
/// A cardinal direction. /// A cardinal direction.
/// ///
/// ``` /// ```
/// let direction = tengri::output::Direction::Above; /// let direction = Direction::Above;
/// ``` /// ```
#[cfg_attr(test, derive(Arbitrary))] #[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction { #[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
@ -47,7 +40,7 @@ use crate::*;
/// 9th of area to place. /// 9th of area to place.
/// ///
/// ``` /// ```
/// let alignment = tengri::output::Alignment::Center; /// let alignment = Align::Center;
/// ``` /// ```
#[cfg_attr(test, derive(Arbitrary))] #[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[derive(Debug, Copy, Clone, Default)] pub enum Alignment {
@ -57,7 +50,7 @@ use crate::*;
/// A widget that tracks its rendered width and height. /// A widget that tracks its rendered width and height.
/// ///
/// ``` /// ```
/// let measure = tengri::output::Measure::<tengri::tui::TuiOut>::default(); /// let measure = Measure::default();
/// ``` /// ```
#[derive(Default)] pub struct Measure<O: Out> { #[derive(Default)] pub struct Measure<O: Out> {
pub __: PhantomData<O>, pub __: PhantomData<O>,
@ -68,80 +61,70 @@ use crate::*;
/// Show an item only when a condition is true. /// Show an item only when a condition is true.
/// ///
/// ``` /// ```
/// fn test () -> impl tengri::output::Draw<tengri::tui::TuiOut> { /// let when = When(true, "Yes");
/// tengri::output::when(true, "Yes")
/// }
/// ``` /// ```
pub struct When<O, T>(pub bool, pub T, pub PhantomData<O>); pub struct When<O, T>(pub bool, pub T, pub PhantomData<O>);
pub const fn when<O, T>(condition: bool, content: T) -> When<O, T> {
When(condition, content, PhantomData)
}
/// Show one item if a condition is true and another if the condition is false. /// Show one item if a condition is true and another if the condition is false.
/// ///
/// ``` /// ```
/// fn test () -> impl tengri::output::Draw<tengri::tui::TuiOut> { /// let either = Either(true, "Yes", "No");
/// tengri::output::either(true, "Yes", "No")
/// }
/// ``` /// ```
pub struct Either<E, A, B>(pub bool, pub A, pub B, pub PhantomData<E>); pub struct Either<E: Out, A, B>(pub bool, pub A, pub B, pub PhantomData<E>);
pub const fn either<E, A, B>(condition: bool, content_a: A, content_b: B) -> Either<E, A, B> {
Either(condition, content_a, content_b, PhantomData)
}
/// Increment X and/or Y coordinate. /// Increment X and/or Y coordinate.
/// ///
/// ``` /// ```
/// let pushed = tengri::output::Push::XY(2, 2, "Hello"); /// let pushed = Push::XY(2, 2, "Hello");
/// ``` /// ```
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrement X and/or Y coordinate. /// Decrement X and/or Y coordinate.
/// ///
/// ``` /// ```
/// let pulled = tengri::output::Pull::XY(2, 2, "Hello"); /// let pulled = Pull::XY(2, 2, "Hello");
/// ``` /// ```
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the content to fill the container. /// Set the content to fill the container.
/// ///
/// ``` /// ```
/// let filled = tengri::output::Fill::XY("Hello"); /// let filled = Fill::XY("Hello");
/// ``` /// ```
pub enum Fill<A> { X(A), Y(A), XY(A) } pub enum Fill<A> { X(A), Y(A), XY(A) }
/// Set fixed size for content. /// Set fixed size for content.
/// ///
/// ``` /// ```
/// let fixed = tengri::output::Fixed::XY(3, 5, "Hello"); // 3x5 /// let fixed = Fixed::XY(3, 5, "Hello"); // 3x5
/// ``` /// ```
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the maximum width and/or height of the content. /// Set the maximum width and/or height of the content.
/// ///
/// ``` /// ```
/// let maximum = tengri::output::Min::XY(3, 5, "Hello"); // 3x1 /// let maximum = Min::XY(3, 5, "Hello"); // 3x1
/// ``` /// ```
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the minimum width and/or height of the content. /// Set the minimum width and/or height of the content.
/// ///
/// ``` /// ```
/// let minimam = tengri::output::Min::XY(3, 5, "Hello"); // 5x5 /// let minimam = Min::XY(3, 5, "Hello"); // 5x5
/// ``` /// ```
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrease the width and/or height of the content. /// Decrease the width and/or height of the content.
/// ///
/// ``` /// ```
/// let shrunk = tengri::output::Shrink::XY(2, 0, "Hello"); // 1x1 /// let shrunk = Shrink::XY(2, 0, "Hello"); // 1x1
/// ``` /// ```
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Increaase the width and/or height of the content. /// Increaase the width and/or height of the content.
/// ///
/// ``` /// ```
/// let expanded = tengri::output::Expand::XY(5, 3, "HELLO"); // 15x3 /// let expanded = Expand::XY(5, 3, "HELLO"); // 15x3
/// ``` /// ```
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), } pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
@ -151,9 +134,9 @@ pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// ``` /// ```
/// use ::tengri::{output::*, tui::*}; /// use ::tengri::{output::*, tui::*};
/// let area = XYWH(10u16, 10, 20, 20); /// let area = XYWH(10u16, 10, 20, 20);
/// fn test (area: XYWH<u16>, item: &impl Draw<TuiOut>, expected: [u16;4]) { /// fn test (area: XYWH<16>, item: &impl Draw<TuiOut>, expected: [u16;4]) {
/// //assert_eq!(Lay::layout(item, area), expected); /// assert_eq!(Content::layout(item, area), expected);
/// //assert_eq!(Draw::layout(item, area), expected); /// assert_eq!(Draw::layout(item, area), expected);
/// }; /// };
/// ///
/// let four = ||Fixed::XY(4, 4, ""); /// let four = ||Fixed::XY(4, 4, "");
@ -184,17 +167,14 @@ pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// TODO DOCUMENTME /// TODO DOCUMENTME
/// ///
/// ``` /// ```
/// use tengri::output::{Bounded, XYWH}; /// let bounded = Bounded(XYWH(0, 0, 0, 0), "");
/// let area = XYWH(0, 0, 0, 0);
/// let content = "";
/// let bounded: Bounded<tengri::tui::TuiOut, _> = Bounded(area, content);
/// ``` /// ```
pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D); pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D);
/// Draws items from an iterator. /// Draws items from an iterator.
/// ///
/// ``` /// ```
/// // FIXME let map = tengri::output::Map(||[].iter(), |_|{}); /// let map = Map(||[].iter(), |_|{});
/// ``` /// ```
pub struct Map<O, A, B, I, F, G> pub struct Map<O, A, B, I, F, G>
where where

View file

@ -3,7 +3,7 @@ use proptest_derive::Arbitrary;
use crate::*; use crate::*;
#[test] fn test_area () { #[test] fn test_area () {
assert_eq!(XYWH(10u16, 10, 20, 20).center(), XY(20, 20)); assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]);
} }
proptest! { proptest! {
@ -19,7 +19,7 @@ proptest! {
h in u16::MIN..u16::MAX, h in u16::MIN..u16::MAX,
a in u16::MIN..u16::MAX, a in u16::MIN..u16::MAX,
) { ) {
let _ = d.split_fixed(XYWH(x, y, w, h), a); let _ = d.split_fixed([x, y, w, h], a);
} }
} }
@ -32,27 +32,27 @@ proptest! {
a in u16::MIN..u16::MAX, a in u16::MIN..u16::MAX,
b in u16::MIN..u16::MAX, b in u16::MIN..u16::MAX,
) { ) {
let _: XYWH<u16> = XYWH::zero(); let _: [u16;4] = <[u16;4] as Area<u16>>::zero();
//let _: XYWH<u16> = XYWH::from_position([a, b]); let _: [u16;4] = <[u16;4] as Area<u16>>::from_position([a, b]);
//let _: XYWH<u16> = XYWH::from_size([a, b]); let _: [u16;4] = <[u16;4] as Area<u16>>::from_size([a, b]);
let area: XYWH<u16> = XYWH(x, y, w, h); let area: [u16;4] = [x, y, w, h];
//let _ = area.expect_min(a, b); let _ = area.expect_min(a, b);
let _ = area.xy(); let _ = area.xy();
let _ = area.wh(); let _ = area.wh();
//let _ = area.xywh(); let _ = area.xywh();
let _ = area.clipped_h(a); let _ = area.clip_h(a);
let _ = area.clipped_w(b); let _ = area.clip_w(b);
let _ = area.clipped(WH(a, b)); let _ = area.clip([a, b]);
//let _ = area.set_w(a); let _ = area.set_w(a);
//let _ = area.set_h(b); let _ = area.set_h(b);
let _ = area.x2(); let _ = area.x2();
let _ = area.y2(); let _ = area.y2();
let _ = area.lrtb(); let _ = area.lrtb();
let _ = area.center(); let _ = area.center();
let _ = area.center_x(a);
let _ = area.center_y(b);
let _ = area.center_xy([a, b]);
let _ = area.centered(); let _ = area.centered();
let _ = area.centered_x(a);
let _ = area.centered_y(b);
let _ = area.centered_xy([a, b]);
} }
} }
@ -63,15 +63,15 @@ proptest! {
a in u16::MIN..u16::MAX, a in u16::MIN..u16::MAX,
b in u16::MIN..u16::MAX, b in u16::MIN..u16::MAX,
) { ) {
let size = WH(x, y); let size = [x, y];
let _ = size.w(); let _ = size.w();
let _ = size.h(); let _ = size.h();
let _ = size.wh(); let _ = size.wh();
let _ = size.clip_w(a); let _ = size.clip_w(a);
let _ = size.clip_h(b); let _ = size.clip_h(b);
//let _ = size.expect_min(a, b); let _ = size.expect_min(a, b);
//let _ = size.to_area_pos(); let _ = size.to_area_pos();
//let _ = size.to_area_size(); let _ = size.to_area_size();
} }
} }

View file

@ -3,12 +3,18 @@ use crate::*;
/// Drawing target. /// Drawing target.
/// ///
/// ``` /// ```
/// use tengri::output::*; /// use crate::*;
/// struct TestOut(XYWH<u16>); /// struct TestOut([u16;4]);
/// impl Out for TestOut { /// impl Out for TestOut {
/// type Unit = u16; /// type Unit = u16;
/// fn area (&self) -> XYWH<u16> { self.0 } /// type Size = [u16;2];
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.0 } /// type Area = [u16;4];
/// fn area (&self) -> [u16;4] {
/// self.0
/// }
/// fn area_mut (&mut self) -> &mut [u16;4] {
/// &mut self.0
/// }
/// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, _: &T) { /// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, _: &T) {
/// println!("place_at: {area:?}"); /// println!("place_at: {area:?}");
/// () /// ()
@ -16,7 +22,7 @@ use crate::*;
/// } /// }
/// impl Draw<TestOut> for String { /// impl Draw<TestOut> for String {
/// fn draw (&self, to: &mut TestOut) { /// fn draw (&self, to: &mut TestOut) {
/// //to.area_mut().set_w(self.len() as u16); /// to.area_mut().set_w(self.len() as u16);
/// } /// }
/// } /// }
/// ``` /// ```

View file

@ -1,16 +1,23 @@
#![feature(type_changing_struct_update, trait_alias)] #![feature(type_changing_struct_update, trait_alias)]
pub extern crate dizzle;
pub extern crate tengri_input;
pub extern crate tengri_output;
pub extern crate ratatui;
pub extern crate crossterm;
pub extern crate palette;
pub extern crate better_panic;
pub use ::tengri_output::PerfModel;
use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write}; use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write};
use unicode_width::*; use unicode_width::*;
pub use ::{
dizzle,
tengri_input,
tengri_output::{self, PerfModel},
ratatui,
crossterm,
palette,
better_panic,
};
pub(crate) use ::{ pub(crate) use ::{
dizzle::*, tengri_input::*, tengri_output::*, dizzle::*,
tengri_input::*,
tengri_output::*,
atomic_float::AtomicF64,
std::{io::{stdout, Stdout}, sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}}, std::{io::{stdout, Stdout}, sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}},
better_panic::{Settings, Verbosity}, better_panic::{Settings, Verbosity},
palette::{*, convert::*, okhsl::*}, palette::{*, convert::*, okhsl::*},
@ -31,7 +38,8 @@ pub(crate) use ::{
#[macro_export] macro_rules! tui_main { #[macro_export] macro_rules! tui_main {
($expr:expr) => { ($expr:expr) => {
fn main () -> Usually<()> { fn main () -> Usually<()> {
tengri_tui::Tui::run(true, $expr)?; let state = Arc::new(RwLock::new($expr));
tengri_tui::Tui::new().unwrap().run(&state)?;
Ok(()) Ok(())
} }
}; };
@ -74,12 +82,10 @@ macro_rules! border {
mod tui_structs; pub use self::tui_structs::*; mod tui_structs; pub use self::tui_structs::*;
mod tui_traits; pub use self::tui_traits::*; mod tui_traits; pub use self::tui_traits::*;
mod tui_impls; mod tui_impls; pub use self::tui_impls::*;
#[cfg(test)] mod tui_test;
/// Run an app in the main loop. /// Run an app in the main loop.
pub fn tui_run <T: Send + Sync + Draw<TuiOut> + Handle<TuiIn> + 'static> ( pub fn tui_run <T: Send + Sync + Draw<TuiOut> + Handle<TuiIn> + 'static> (
join: bool,
state: &Arc<RwLock<T>> state: &Arc<RwLock<T>>
) -> Usually<Arc<RwLock<Tui>>> { ) -> Usually<Arc<RwLock<Tui>>> {
let backend = CrosstermBackend::new(stdout()); let backend = CrosstermBackend::new(stdout());
@ -94,7 +100,6 @@ pub fn tui_run <T: Send + Sync + Draw<TuiOut> + Handle<TuiIn> + 'static> (
let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100));
tui.write().unwrap().setup()?; tui.write().unwrap().setup()?;
let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?;
if join {
match render_thread.join() { match render_thread.join() {
Ok(result) => { Ok(result) => {
tui.write().unwrap().teardown()?; tui.write().unwrap().teardown()?;
@ -105,7 +110,6 @@ pub fn tui_run <T: Send + Sync + Draw<TuiOut> + Handle<TuiIn> + 'static> (
panic!("\n\rDraw thread failed: error={error:?}.\n\r") panic!("\n\rDraw thread failed: error={error:?}.\n\r")
}, },
} }
}
Ok(tui) Ok(tui)
} }
@ -488,3 +492,41 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
} }
return width return width
} }
#[cfg(test)] mod tui_test {
use crate::*;
#[test] fn test_tui_engine () -> Usually<()> {
//use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl HasContent<TuiOut> for TestComponent {
fn content (&self) -> impl Content<TuiOut> {
Some(self.0.as_str())
}
}
impl Handle<TuiIn> for TestComponent {
fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> {
Ok(None)
}
}
let engine = Tui::new()?;
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
let state = TestComponent("hello world".into());
let _state = std::sync::Arc::new(std::sync::RwLock::new(state));
//engine.run(&state)?;
Ok(())
}
//#[test] fn test_parse_key () {
////use KeyModifiers as Mods;
//let _test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));
////test(":x",
////KeyEvent::new(KeyCode::Char('x'), Mods::NONE));
////test(":ctrl-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL));
////test(":alt-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::ALT));
////test(":shift-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT));
////test(":ctrl-alt-shift-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
//}
}

View file

@ -4,10 +4,10 @@ use rand::{thread_rng, distributions::uniform::UniformSampler};
impl Tui { impl Tui {
/// Create and launch a terminal user interface. /// Create and launch a terminal user interface.
pub fn run <T> (join: bool, state: T) -> Usually<Arc<RwLock<Self>>> where pub fn run <T: Send + Sync + Handle<TuiIn> + Draw<TuiOut> + 'static> (
T: Handle<TuiIn> + Draw<TuiOut> + Send + Sync + 'static state: &Arc<RwLock<T>>
{ ) -> Usually<Arc<RwLock<Self>>> {
tui_run(join, &Arc::new(RwLock::new(state))) tui_run(state)
} }
/// True if done /// True if done
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }

View file

@ -1,34 +0,0 @@
use crate::*;
#[test] fn test_tui_engine () -> Usually<()> {
//use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl Draw<TuiOut> for TestComponent {
fn draw (&self, _to: &mut TuiOut) {
}
}
impl Handle<TuiIn> for TestComponent {
fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> {
Ok(None)
}
}
let engine = Tui::run(false, TestComponent("hello world".into()))?;
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
//engine.run(&state)?;
Ok(())
}
//#[test] fn test_parse_key () {
////use KeyModifiers as Mods;
//let _test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));
////test(":x",
////KeyEvent::new(KeyCode::Char('x'), Mods::NONE));
////test(":ctrl-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL));
////test(":alt-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::ALT));
////test(":shift-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT));
////test(":ctrl-alt-shift-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
//}