mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 03:36:41 +01:00
break down engine modules
This commit is contained in:
parent
f6c603bf73
commit
905486edbd
16 changed files with 376 additions and 352 deletions
|
|
@ -55,52 +55,8 @@ impl<T: AsRef<str> + PartialEq + Default + Clone + std::fmt::Debug> EdnItem<T> {
|
|||
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> {
|
||||
fn from (other: EdnItem<impl AsRef<str>>) -> EdnItem<String> {
|
||||
pub fn from (other: EdnItem<impl AsRef<str>>) -> EdnItem<String> {
|
||||
use EdnItem::*;
|
||||
match other {
|
||||
Nil => Nil,
|
||||
|
|
|
|||
45
edn/src/edn_iter.rs
Normal file
45
edn/src/edn_iter.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
use crate::*;
|
||||
|
||||
#[macro_export] macro_rules! run_tek_edn {
|
||||
($source:expr) => {
|
||||
struct EdnRunner;
|
||||
|
|
|
|||
|
|
@ -5,16 +5,13 @@ pub(crate) use std::{
|
|||
fmt::{Debug, Formatter, Error as FormatError}
|
||||
};
|
||||
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_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_view; pub use self::edn_view::*;
|
||||
mod edn_main; pub use self::edn_main::*;
|
||||
|
||||
#[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> {
|
||||
use EdnItem::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use crate::*;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{Add, Sub, Mul, Div};
|
||||
|
||||
/// Platform backend.
|
||||
pub trait Engine: Send + Sync + Sized {
|
||||
|
|
@ -13,9 +11,9 @@ pub trait Engine: Send + Sync + Sized {
|
|||
/// Unit of length
|
||||
type Unit: Coordinate;
|
||||
/// Rectangle without offset
|
||||
type Size: Size<Self::Unit> + From<[Self::Unit;2]> + Debug + Copy;
|
||||
type Size: Size<Self::Unit>;
|
||||
/// Rectangle with offset
|
||||
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug + Copy;
|
||||
type Area: Area<Self::Unit>;
|
||||
/// Prepare before run
|
||||
fn setup (&mut self) -> Usually<()> { Ok(()) }
|
||||
/// True if done
|
||||
|
|
@ -23,139 +21,3 @@ pub trait Engine: Send + Sync + Sized {
|
|||
/// Clean up after run
|
||||
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] }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
use crate::*;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
/// Handle input
|
||||
pub trait Handle<E: Engine>: Send + Sync {
|
||||
fn handle (&mut self, _input: &E::Input) -> Perhaps<E::Handled> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
mod handle; pub use self::handle::*;
|
||||
|
||||
/// Current input state
|
||||
pub trait Input<E: Engine> {
|
||||
|
|
@ -19,53 +13,3 @@ pub trait Input<E: Engine> {
|
|||
/// Mark component as done
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
engine/src/input/handle.rs
Normal file
59
engine/src/input/handle.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,13 @@
|
|||
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
|
||||
pub trait Output<E: Engine> {
|
||||
/// Current output area
|
||||
|
|
@ -14,35 +22,6 @@ pub trait Output<E: Engine> {
|
|||
#[inline] fn h (&self) -> E::Unit { self.area().h() }
|
||||
#[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 {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
(*self).content()
|
||||
|
|
|
|||
84
engine/src/output/area.rs
Normal file
84
engine/src/output/area.rs
Normal 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] }
|
||||
}
|
||||
17
engine/src/output/content.rs
Normal file
17
engine/src/output/content.rs
Normal 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 }
|
||||
}
|
||||
26
engine/src/output/coordinate.rs
Normal file
26
engine/src/output/coordinate.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
12
engine/src/output/render.rs
Normal file
12
engine/src/output/render.rs
Normal 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
32
engine/src/output/size.rs
Normal 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] }
|
||||
}
|
||||
10
engine/src/output/thunk.rs
Normal file
10
engine/src/output/thunk.rs
Normal 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)() }
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
mod tui_output; pub use self::tui_output::*;
|
||||
mod tui_input; pub use self::tui_input::*;
|
||||
mod tui_run; pub use self::tui_run::*;
|
||||
|
||||
use crate::*;
|
||||
use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
|
||||
use std::io::{stdout, Stdout};
|
||||
use std::time::Duration;
|
||||
use std::thread::{spawn, JoinHandle};
|
||||
|
||||
pub use ::better_panic;
|
||||
pub(crate) use better_panic::{Settings, Verbosity};
|
||||
|
|
@ -91,74 +90,3 @@ impl Tui {
|
|||
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
75
engine/src/tui/tui_run.rs
Normal 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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue