break down engine modules

This commit is contained in:
🪞👃🪞 2025-01-05 08:16:15 +01:00
parent f6c603bf73
commit 905486edbd
16 changed files with 376 additions and 352 deletions

View file

@ -55,52 +55,8 @@ impl<T: AsRef<str> + PartialEq + Default + Clone + std::fmt::Debug> EdnItem<T> {
EdnIterator::new(&self) EdnIterator::new(&self)
} }
} }
enum EdnIterator<'a, T>{
Nil,
Sym(&'a T),
Exp(Vec<EdnIterator<'a, T>>)
}
impl<'a, T: AsRef<str>> EdnIterator<'a, T> {
fn new (item: &'a EdnItem<T>) -> Self {
use EdnItem::*;
match item {
Sym(t) => Self::Sym(t),
Exp(i) => Self::Exp(i.iter().map(EdnIterator::new).collect()),
_ => Self::Nil,
}
}
}
impl<'a, T: AsRef<str>> Iterator for EdnIterator<'a, T> {
type Item = &'a T;
fn next (&mut self) -> Option<Self::Item> {
use EdnIterator::*;
match self {
Sym(t) => {
let t = *t;
*self = Nil;
Some(t)
},
Exp(v) => match v.as_mut_slice() {
[a] => if let Some(next) = a.next() {
Some(next)
} else {
*self = Exp(v.split_off(1));
self.next()
},
_ => {
*self = Nil;
None
}
},
_ => {
*self = Nil;
None
}
}
}
}
impl EdnItem<String> { impl EdnItem<String> {
fn from (other: EdnItem<impl AsRef<str>>) -> EdnItem<String> { pub fn from (other: EdnItem<impl AsRef<str>>) -> EdnItem<String> {
use EdnItem::*; use EdnItem::*;
match other { match other {
Nil => Nil, Nil => Nil,

45
edn/src/edn_iter.rs Normal file
View file

@ -0,0 +1,45 @@
use crate::*;
pub enum EdnIterator<'a, T>{
Nil,
Sym(&'a T),
Exp(Vec<EdnIterator<'a, T>>)
}
impl<'a, T: AsRef<str>> EdnIterator<'a, T> {
pub fn new (item: &'a EdnItem<T>) -> Self {
use EdnItem::*;
match item {
Sym(t) => Self::Sym(t),
Exp(i) => Self::Exp(i.iter().map(EdnIterator::new).collect()),
_ => Self::Nil,
}
}
}
impl<'a, T: AsRef<str>> Iterator for EdnIterator<'a, T> {
type Item = &'a T;
fn next (&mut self) -> Option<Self::Item> {
use EdnIterator::*;
match self {
Sym(t) => {
let t = *t;
*self = Nil;
Some(t)
},
Exp(v) => match v.as_mut_slice() {
[a] => if let Some(next) = a.next() {
Some(next)
} else {
*self = Exp(v.split_off(1));
self.next()
},
_ => {
*self = Nil;
None
}
},
_ => {
*self = Nil;
None
}
}
}
}

View file

@ -1,5 +1,3 @@
use crate::*;
#[macro_export] macro_rules! run_tek_edn { #[macro_export] macro_rules! run_tek_edn {
($source:expr) => { ($source:expr) => {
struct EdnRunner; struct EdnRunner;

View file

@ -5,16 +5,13 @@ pub(crate) use std::{
fmt::{Debug, Formatter, Error as FormatError} fmt::{Debug, Formatter, Error as FormatError}
}; };
pub use ::tek_layout; pub use ::tek_layout;
pub(crate) use ::tek_layout::{
*,
tek_engine::{Usually, Content, Render, Engine, Thunk}
};
mod edn_error; pub use self::edn_error::*; mod edn_error; pub use self::edn_error::*;
mod edn_item; pub use self::edn_item::*; mod edn_item; pub use self::edn_item::*;
mod edn_iter; pub use self::edn_iter::*;
mod edn_main; pub use self::edn_main::*;
mod edn_token; pub use self::edn_token::*; mod edn_token; pub use self::edn_token::*;
mod edn_view; pub use self::edn_view::*; mod edn_view; pub use self::edn_view::*;
mod edn_main; pub use self::edn_main::*;
#[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> { #[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> {
use EdnItem::*; use EdnItem::*;

View file

@ -1,6 +1,4 @@
use crate::*; use crate::*;
use std::fmt::{Debug, Display};
use std::ops::{Add, Sub, Mul, Div};
/// Platform backend. /// Platform backend.
pub trait Engine: Send + Sync + Sized { pub trait Engine: Send + Sync + Sized {
@ -13,9 +11,9 @@ pub trait Engine: Send + Sync + Sized {
/// Unit of length /// Unit of length
type Unit: Coordinate; type Unit: Coordinate;
/// Rectangle without offset /// Rectangle without offset
type Size: Size<Self::Unit> + From<[Self::Unit;2]> + Debug + Copy; type Size: Size<Self::Unit>;
/// Rectangle with offset /// Rectangle with offset
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug + Copy; type Area: Area<Self::Unit>;
/// Prepare before run /// Prepare before run
fn setup (&mut self) -> Usually<()> { Ok(()) } fn setup (&mut self) -> Usually<()> { Ok(()) }
/// True if done /// True if done
@ -23,139 +21,3 @@ pub trait Engine: Send + Sync + Sized {
/// Clean up after run /// Clean up after run
fn teardown (&mut self) -> Usually<()> { Ok(()) } fn teardown (&mut self) -> Usually<()> { Ok(()) }
} }
/// A linear coordinate.
pub trait Coordinate: 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>
{
#[inline] fn minus (self, other: Self) -> Self {
if self >= other {
self - other
} else {
0.into()
}
}
#[inline] fn zero () -> Self {
0.into()
}
}
pub trait Size<N: Coordinate> {
fn x (&self) -> N;
fn y (&self) -> N;
#[inline] fn w (&self) -> N { self.x() }
#[inline] fn h (&self) -> N { self.y() }
#[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] }
#[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
#[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
#[inline] fn zero () -> [N;2] {
[N::zero(), N::zero()]
}
}
impl<N: Coordinate> Size<N> for (N, N) {
#[inline] fn x (&self) -> N { self.0 }
#[inline] fn y (&self) -> N { self.1 }
}
impl<N: Coordinate> Size<N> for [N;2] {
#[inline] fn x (&self) -> N { self[0] }
#[inline] fn y (&self) -> N { self[1] }
}
pub trait Area<N: Coordinate> {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
#[inline] fn xy (&self) -> [N;2] {
[self.x(), self.y()]
}
#[inline] fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
#[inline] fn xywh (&self) -> [N;4] {
[self.x(), self.y(), self.w(), self.h()]
}
#[inline] fn clip_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), self.h().min(h)]
}
#[inline] fn clip_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), self.w().min(w), self.h()]
}
#[inline] fn clip (&self, wh: impl Size<N>) -> [N;4] {
[self.x(), self.y(), wh.w(), wh.h()]
}
#[inline] fn set_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), w, self.h()]
}
#[inline] fn set_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), h]
}
#[inline] fn x2 (&self) -> N {
self.x() + self.w()
}
#[inline] fn y2 (&self) -> N {
self.y() + self.h()
}
#[inline] fn lrtb (&self) -> [N;4] {
[self.x(), self.x2(), self.y(), self.y2()]
}
#[inline] fn center (&self) -> [N;2] {
[self.x() + self.w()/2.into(), self.y() + self.h()/2.into()]
}
#[inline] fn center_x (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x + w / 2.into()).minus(n / 2.into()), y + h / 2.into(), n, 1.into()]
}
#[inline] fn center_y (&self, m: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[x + w / 2.into(), (y + h / 2.into()).minus(m / 2.into()), 1.into(), m]
}
#[inline] fn center_xy (&self, [n, m]: [N;2]) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x + w / 2.into()).minus(n / 2.into()), (y + h / 2.into()).minus(m / 2.into()), n, m]
}
#[inline] fn centered (&self) -> [N;2] {
[self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())]
}
fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()]
}
}
impl<N: Coordinate> Area<N> for (N, N, N, N) {
#[inline] fn x (&self) -> N { self.0 }
#[inline] fn y (&self) -> N { self.1 }
#[inline] fn w (&self) -> N { self.2 }
#[inline] fn h (&self) -> N { self.3 }
}
impl<N: Coordinate> Area<N> for [N;4] {
#[inline] fn x (&self) -> N { self[0] }
#[inline] fn y (&self) -> N { self[1] }
#[inline] fn w (&self) -> N { self[2] }
#[inline] fn h (&self) -> N { self[3] }
}

View file

@ -1,12 +1,6 @@
use crate::*; use crate::*;
use std::sync::{Arc, Mutex, RwLock};
/// Handle input mod handle; pub use self::handle::*;
pub trait Handle<E: Engine>: Send + Sync {
fn handle (&mut self, _input: &E::Input) -> Perhaps<E::Handled> {
Ok(None)
}
}
/// Current input state /// Current input state
pub trait Input<E: Engine> { pub trait Input<E: Engine> {
@ -19,53 +13,3 @@ pub trait Input<E: Engine> {
/// Mark component as done /// Mark component as done
fn done (&self); fn done (&self);
} }
#[macro_export] macro_rules! handle {
(<$E:ty>|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl Handle<$E> for $Struct {
fn handle (&mut $self, $input: &<$E as Engine>::Input) -> Perhaps<<$E as Engine>::Handled> {
$handler
}
}
}
}
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
(*self).handle(context)
}
}
impl<E: Engine, H: Handle<E>> Handle<E> for Option<H> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
if let Some(ref mut handle) = self {
handle.handle(context)
} else {
Ok(None)
}
}
}
impl<H, E: Engine> Handle<E> for Mutex<H> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.get_mut().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.lock().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for RwLock<H> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}

View file

@ -0,0 +1,59 @@
use crate::*;
use std::sync::{Mutex, Arc, RwLock};
/// Handle input
pub trait Handle<E: Engine>: Send + Sync {
fn handle (&mut self, _input: &E::Input) -> Perhaps<E::Handled> {
Ok(None)
}
}
#[macro_export] macro_rules! handle {
(<$E:ty>|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl Handle<$E> for $Struct {
fn handle (&mut $self, $input: &<$E as Engine>::Input) -> Perhaps<<$E as Engine>::Handled> {
$handler
}
}
}
}
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
(*self).handle(context)
}
}
impl<E: Engine, H: Handle<E>> Handle<E> for Option<H> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
if let Some(ref mut handle) = self {
handle.handle(context)
} else {
Ok(None)
}
}
}
impl<H, E: Engine> Handle<E> for Mutex<H> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.get_mut().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.lock().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for RwLock<H> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}

View file

@ -1,5 +1,13 @@
use crate::*; use crate::*;
use std::marker::PhantomData;
mod coordinate; pub use self::coordinate::*;
mod size; pub use self::size::*;
mod area; pub use self::area::*;
mod render; pub use self::render::*;
mod content; pub use self::content::*;
mod thunk; pub use self::thunk::*;
/// Rendering target /// Rendering target
pub trait Output<E: Engine> { pub trait Output<E: Engine> {
/// Current output area /// Current output area
@ -14,35 +22,6 @@ pub trait Output<E: Engine> {
#[inline] fn h (&self) -> E::Unit { self.area().h() } #[inline] fn h (&self) -> E::Unit { self.area().h() }
#[inline] fn wh (&self) -> E::Size { self.area().wh().into() } #[inline] fn wh (&self) -> E::Size { self.area().wh().into() }
} }
pub trait Render<E: Engine>: Send + Sync {
fn layout (&self, area: E::Area) -> E::Area;
fn render (&self, output: &mut E::Output);
}
pub trait Content<E: Engine>: Send + Sync + Sized {
fn content (&self) -> impl Render<E> { () }
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
fn render (&self, output: &mut E::Output) { self.content().render(output) }
}
impl<E: Engine, C: Content<E>> Render<E> for C {
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
fn render (&self, output: &mut E::Output) { Content::render(self, output) }
}
impl<'a, E: Engine> Content<E> for Box<dyn Render<E> + 'a> {
fn content (&self) -> impl Render<E> { self }
}
impl<'a, E: Engine> Content<E> for Box<dyn Render<E> + Send + Sync + 'a> {
fn content (&self) -> impl Render<E> { self }
}
impl<E: Engine> Content<E> for &(dyn Render<E> + '_) {
fn content (&self) -> impl Render<E> { self }
}
pub struct Thunk<E: Engine, T: Render<E>, F: Fn()->T + Send + Sync>(F, PhantomData<E>);
impl<E: Engine, T: Render<E>, F: Fn()->T + Send + Sync> Thunk<E, T, F> {
pub fn new (thunk: F) -> Self { Self(thunk, Default::default()) }
}
impl<E: Engine, T: Render<E>, F: Fn()->T + Send + Sync> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.0)() }
}
impl<E: Engine, T: Content<E>> Content<E> for &T { impl<E: Engine, T: Content<E>> Content<E> for &T {
fn content (&self) -> impl Render<E> { fn content (&self) -> impl Render<E> {
(*self).content() (*self).content()

84
engine/src/output/area.rs Normal file
View file

@ -0,0 +1,84 @@
use crate::*;
use std::fmt::Debug;
pub trait Area<N: Coordinate>: From<[N;4]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
#[inline] fn xy (&self) -> [N;2] {
[self.x(), self.y()]
}
#[inline] fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
#[inline] fn xywh (&self) -> [N;4] {
[self.x(), self.y(), self.w(), self.h()]
}
#[inline] fn clip_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), self.h().min(h)]
}
#[inline] fn clip_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), self.w().min(w), self.h()]
}
#[inline] fn clip (&self, wh: impl Size<N>) -> [N;4] {
[self.x(), self.y(), wh.w(), wh.h()]
}
#[inline] fn set_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), w, self.h()]
}
#[inline] fn set_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), h]
}
#[inline] fn x2 (&self) -> N {
self.x() + self.w()
}
#[inline] fn y2 (&self) -> N {
self.y() + self.h()
}
#[inline] fn lrtb (&self) -> [N;4] {
[self.x(), self.x2(), self.y(), self.y2()]
}
#[inline] fn center (&self) -> [N;2] {
[self.x() + self.w()/2.into(), self.y() + self.h()/2.into()]
}
#[inline] fn center_x (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x + w / 2.into()).minus(n / 2.into()), y + h / 2.into(), n, 1.into()]
}
#[inline] fn center_y (&self, m: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[x + w / 2.into(), (y + h / 2.into()).minus(m / 2.into()), 1.into(), m]
}
#[inline] fn center_xy (&self, [n, m]: [N;2]) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x + w / 2.into()).minus(n / 2.into()), (y + h / 2.into()).minus(m / 2.into()), n, m]
}
#[inline] fn centered (&self) -> [N;2] {
[self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())]
}
fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()]
}
}
impl<N: Coordinate> Area<N> for (N, N, N, N) {
#[inline] fn x (&self) -> N { self.0 }
#[inline] fn y (&self) -> N { self.1 }
#[inline] fn w (&self) -> N { self.2 }
#[inline] fn h (&self) -> N { self.3 }
}
impl<N: Coordinate> Area<N> for [N;4] {
#[inline] fn x (&self) -> N { self[0] }
#[inline] fn y (&self) -> N { self[1] }
#[inline] fn w (&self) -> N { self[2] }
#[inline] fn h (&self) -> N { self[3] }
}

View file

@ -0,0 +1,17 @@
use crate::*;
/// Build a [Render]able out of other [Render]ables,
/// then apply optional custom render/layout on top.
pub trait Content<E: Engine>: Send + Sync + Sized {
fn content (&self) -> impl Render<E> { () }
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
fn render (&self, output: &mut E::Output) { self.content().render(output) }
}
impl<'a, E: Engine> Content<E> for Box<dyn Render<E> + 'a> {
fn content (&self) -> impl Render<E> { self }
}
impl<'a, E: Engine> Content<E> for Box<dyn Render<E> + Send + Sync + 'a> {
fn content (&self) -> impl Render<E> { self }
}
impl<E: Engine> Content<E> for &(dyn Render<E> + '_) {
fn content (&self) -> impl Render<E> { self }
}

View file

@ -0,0 +1,26 @@
use std::fmt::{Debug, Display};
use std::ops::{Add, Sub, Mul, Div};
/// A linear coordinate.
pub trait Coordinate: 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>
{
#[inline] fn minus (self, other: Self) -> Self {
if self >= other {
self - other
} else {
0.into()
}
}
#[inline] fn zero () -> Self {
0.into()
}
}

View file

@ -0,0 +1,12 @@
use crate::*;
/// Custom layout and rendering.
pub trait Render<E: Engine>: Send + Sync {
fn layout (&self, area: E::Area) -> E::Area;
fn render (&self, output: &mut E::Output);
}
impl<E: Engine, C: Content<E>> Render<E> for C {
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
fn render (&self, output: &mut E::Output) { Content::render(self, output) }
}

32
engine/src/output/size.rs Normal file
View file

@ -0,0 +1,32 @@
use crate::*;
use std::fmt::Debug;
pub trait Size<N: Coordinate>: From<[N;2]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
#[inline] fn w (&self) -> N { self.x() }
#[inline] fn h (&self) -> N { self.y() }
#[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] }
#[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
#[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
#[inline] fn zero () -> [N;2] {
[N::zero(), N::zero()]
}
}
impl<N: Coordinate> Size<N> for (N, N) {
#[inline] fn x (&self) -> N { self.0 }
#[inline] fn y (&self) -> N { self.1 }
}
impl<N: Coordinate> Size<N> for [N;2] {
#[inline] fn x (&self) -> N { self[0] }
#[inline] fn y (&self) -> N { self[1] }
}

View file

@ -0,0 +1,10 @@
use crate::*;
use std::marker::PhantomData;
/// Lazily-evaluated [Render]able.
pub struct Thunk<E: Engine, T: Render<E>, F: Fn()->T + Send + Sync>(F, PhantomData<E>);
impl<E: Engine, T: Render<E>, F: Fn()->T + Send + Sync> Thunk<E, T, F> {
pub fn new (thunk: F) -> Self { Self(thunk, Default::default()) }
}
impl<E: Engine, T: Render<E>, F: Fn()->T + Send + Sync> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.0)() }
}

View file

@ -1,11 +1,10 @@
mod tui_output; pub use self::tui_output::*; mod tui_output; pub use self::tui_output::*;
mod tui_input; pub use self::tui_input::*; mod tui_input; pub use self::tui_input::*;
mod tui_run; pub use self::tui_run::*;
use crate::*; use crate::*;
use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
use std::io::{stdout, Stdout}; use std::io::{stdout, Stdout};
use std::time::Duration;
use std::thread::{spawn, JoinHandle};
pub use ::better_panic; pub use ::better_panic;
pub(crate) use better_panic::{Settings, Verbosity}; pub(crate) use better_panic::{Settings, Verbosity};
@ -91,74 +90,3 @@ impl Tui {
buffer buffer
} }
} }
pub trait TuiRun<R: Render<Tui> + Handle<Tui> + Sized + 'static> {
/// Run an app in the main loop.
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
/// Spawn the input thread.
fn run_input (&self, state: &Arc<RwLock<R>>, poll: Duration) -> JoinHandle<()>;
/// Spawn the output thread.
fn run_output (&self, state: &Arc<RwLock<R>>, sleep: Duration) -> JoinHandle<()>;
}
impl<T: Render<Tui> + Handle<Tui> + Sized + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
let _input_thread = self.run_input(state, Duration::from_millis(100));
self.write().unwrap().setup()?;
let render_thread = self.run_output(state, Duration::from_millis(10));
render_thread.join().expect("main thread failed");
self.write().unwrap().teardown()?;
Ok(())
}
fn run_input (&self, state: &Arc<RwLock<T>>, poll: Duration) -> JoinHandle<()> {
let exited = self.read().unwrap().exited.clone();
let state = state.clone();
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
if ::crossterm::event::poll(poll).is_ok() {
let event = ::crossterm::event::read().unwrap();
match event {
kpat!(Ctrl-KeyCode::Char('c')) => {
exited.store(true, Relaxed);
},
_ => {
let exited = exited.clone();
if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) {
panic!("{e}")
}
}
}
}
})
}
fn run_output (&self, state: &Arc<RwLock<T>>, sleep: Duration) -> JoinHandle<()> {
let exited = self.read().unwrap().exited.clone();
let engine = self.clone();
let state = state.clone();
let Size { width, height } = engine.read().unwrap().backend.size().expect("get size failed");
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
let Size { width, height } = engine.read().unwrap().backend.size()
.expect("get size failed");
if let Ok(state) = state.try_read() {
let size = Rect { x: 0, y: 0, width, height };
if buffer.area != size {
engine.write().unwrap().backend.clear_region(ClearType::All)
.expect("clear failed");
buffer.resize(size);
buffer.reset();
}
let mut output = TuiOut { buffer, area: [0, 0, width, height] };
state.render(&mut output);
buffer = engine.write().unwrap().flip(output.buffer, size);
}
std::thread::sleep(sleep);
})
}
}

75
engine/src/tui/tui_run.rs Normal file
View file

@ -0,0 +1,75 @@
use crate::{*, tui::*};
use ratatui::prelude::Size;
use std::time::Duration;
use std::thread::{spawn, JoinHandle};
pub trait TuiRun<R: Render<Tui> + Handle<Tui> + Sized + 'static> {
/// Run an app in the main loop.
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
/// Spawn the input thread.
fn run_input (&self, state: &Arc<RwLock<R>>, poll: Duration) -> JoinHandle<()>;
/// Spawn the output thread.
fn run_output (&self, state: &Arc<RwLock<R>>, sleep: Duration) -> JoinHandle<()>;
}
impl<T: Render<Tui> + Handle<Tui> + Sized + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
let _input_thread = self.run_input(state, Duration::from_millis(100));
self.write().unwrap().setup()?;
let render_thread = self.run_output(state, Duration::from_millis(10));
render_thread.join().expect("main thread failed");
self.write().unwrap().teardown()?;
Ok(())
}
fn run_input (&self, state: &Arc<RwLock<T>>, poll: Duration) -> JoinHandle<()> {
let exited = self.read().unwrap().exited.clone();
let state = state.clone();
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
if ::crossterm::event::poll(poll).is_ok() {
let event = ::crossterm::event::read().unwrap();
match event {
kpat!(Ctrl-KeyCode::Char('c')) => {
exited.store(true, Relaxed);
},
_ => {
let exited = exited.clone();
if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) {
panic!("{e}")
}
}
}
}
})
}
fn run_output (&self, state: &Arc<RwLock<T>>, sleep: Duration) -> JoinHandle<()> {
let exited = self.read().unwrap().exited.clone();
let engine = self.clone();
let state = state.clone();
let Size { width, height } = engine.read().unwrap().backend.size().expect("get size failed");
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
let Size { width, height } = engine.read().unwrap().backend.size()
.expect("get size failed");
if let Ok(state) = state.try_read() {
let size = Rect { x: 0, y: 0, width, height };
if buffer.area != size {
engine.write().unwrap().backend.clear_region(ClearType::All)
.expect("clear failed");
buffer.resize(size);
buffer.reset();
}
let mut output = TuiOut { buffer, area: [0, 0, width, height] };
state.render(&mut output);
buffer = engine.write().unwrap().flip(output.buffer, size);
}
std::thread::sleep(sleep);
})
}
}