diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 703f8ba..36fc266 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -5,6 +5,7 @@ extern crate const_panic; use const_panic::{concat_panic, PanicFmt}; pub(crate) use ::tengri_core::*; +use std::ops::Deref; pub(crate) use std::error::Error; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; @@ -61,8 +62,11 @@ pub trait DslExp: PartialEq + Clone + Default + Debug + Dsl {} impl DslExp for T {} /// The string representation for a [Dsl] implementation. /// [Cst] uses `&'s str`. [Ast] uses `Arc`. -pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} -impl + std::ops::Deref> DslStr for T {} +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + Deref { + fn as_str (&self) -> &str { self.as_ref() } + fn as_arc (&self) -> Arc { self.as_ref().into() } +} +impl + Deref> DslStr for Str {} impl> Val { pub const fn err (&self) -> Option {match self{Val::Error(e)=>Some(*e), _=>None}} pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} diff --git a/output/src/lib.rs b/output/src/lib.rs index 45abdda..334c418 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -3,6 +3,9 @@ #![feature(impl_trait_in_assoc_type)] pub(crate) use std::marker::PhantomData; +pub(crate) use std::fmt::{Debug, Display}; +pub(crate) use std::ops::{Add, Sub, Mul, Div}; +pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; pub(crate) use tengri_core::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; diff --git a/output/src/ops.rs b/output/src/ops.rs index 976ab51..6b6f401 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -48,32 +48,21 @@ use crate::*; use Direction::*; +use std::marker::PhantomData; +use std::sync::{Arc, RwLock}; -mod map; pub use self::map::*; -mod memo; pub use self::memo::*; -mod stack; pub use self::stack::*; -mod thunk; pub use self::thunk::*; - -/// Renders multiple things on top of each other, -#[macro_export] macro_rules! lay { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} -} - +/// 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 }}; -} - +#[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 }} -} - +#[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 }}; -} - +#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}); /// Show an item only when a condition is true. pub struct When(pub bool, pub A); impl When { @@ -686,3 +675,353 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ //_ => None //}) } + +/// Lazily-evaluated [Render]able. +pub struct Thunk, F: Fn()->T>( + PhantomData, + F +); +impl, F: Fn()->T> Thunk { + pub const fn new (thunk: F) -> Self { + Self(PhantomData, thunk) + } +} +impl, F: Fn()->T> Content for Thunk { + fn content (&self) -> impl Render { (self.1)() } +} + +pub struct ThunkBox( + PhantomData, + BoxBox>>, +); +impl ThunkBox { + pub const fn new (thunk: BoxBox>>) -> Self { + Self(PhantomData, thunk) + } +} +impl Content for ThunkBox { + fn content (&self) -> impl Render { (&self.1)() } +} +impl FromBox>>> for ThunkBox { + fn from (f: BoxBox>>) -> Self { + Self(PhantomData, f) + } +} +//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { + //fn from (f: F) -> Self { + //Self(Default::default(), Box::new(f)) + //} +//} + +pub struct ThunkRender(PhantomData, F); +impl ThunkRender { + pub fn new (render: F) -> Self { Self(PhantomData, render) } +} +impl Content for ThunkRender { + fn render (&self, to: &mut E) { (self.1)(to) } +} + +pub struct ThunkLayout< + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +>( + PhantomData, + F1, + F2 +); +implE::Area, F2: Fn(&mut E)> ThunkLayout { + pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } +} +impl Content for ThunkLayout +where + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +{ + fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } + fn render (&self, to: &mut E) { (self.2)(to) } +} + +#[derive(Debug, Default)] pub struct Memo { + pub value: T, + pub view: Arc> +} + +impl Memo { + pub fn new (value: T, view: U) -> Self { + Self { value, view: Arc::new(view.into()) } + } + pub fn update ( + &mut self, + newval: T, + render: impl Fn(&mut U, &T, &T)->R + ) -> Option { + if newval != self.value { + let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); + self.value = newval; + return Some(result); + } + None + } +} + +/// Clear a pre-allocated buffer, then write into it. +#[macro_export] macro_rules! rewrite { + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } +} + +pub struct Stack { + __: PhantomData, + direction: Direction, + callback: F +} +impl Stack { + pub fn new (direction: Direction, callback: F) -> Self { + Self { direction, callback, __: Default::default(), } + } + pub fn north (callback: F) -> Self { + Self::new(North, callback) + } + pub fn south (callback: F) -> Self { + Self::new(South, callback) + } + pub fn east (callback: F) -> Self { + Self::new(East, callback) + } + pub fn west (callback: F) -> Self { + Self::new(West, callback) + } +} +impl)->())->()> Content for Stack { + fn layout (&self, to: E::Area) -> E::Area { + let mut x = to.x(); + let mut y = to.y(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); + (self.callback)(&mut move |component: &dyn Render|{ + let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); + match self.direction { + South => { + y = y.plus(h); + h_used = h_used.plus(h); + h_remaining = h_remaining.minus(h); + w_used = w_used.max(w); + }, + East => { + x = x.plus(w); + w_used = w_used.plus(w); + w_remaining = w_remaining.minus(w); + h_used = h_used.max(h); + }, + North | West => { + todo!() + }, + _ => unreachable!(), + } + }); + match self.direction { + North | West => { + todo!() + }, + South | East => { + [to.x(), to.y(), w_used.into(), h_used.into()].into() + }, + _ => unreachable!(), + } + } + fn render (&self, to: &mut E) { + let mut x = to.x(); + let mut y = to.y(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); + (self.callback)(&mut move |component: &dyn Render|{ + let layout = component.layout([x, y, w_remaining, h_remaining].into()); + match self.direction { + South => { + y = y.plus(layout.h()); + h_remaining = h_remaining.minus(layout.h()); + h_used = h_used.plus(layout.h()); + to.place(layout, component); + }, + East => { + x = x.plus(layout.w()); + w_remaining = w_remaining.minus(layout.w()); + w_used = w_used.plus(layout.h()); + to.place(layout, component); + }, + North | West => { + todo!() + }, + _ => unreachable!() + } + }); + } +} + +/*Stack::down(|add|{ + let mut i = 0; + for (_, name) in self.dirs.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + for (_, name) in self.files.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + add(&format!("{}/{i}", self.index))?; + Ok(()) +}));*/ + +/// Renders items from an iterator. +pub struct Map +where + I: Iterator + Send + Sync, + F: Fn() -> I + Send + Sync, +{ + __: PhantomData<(E, B)>, + /// Function that returns iterator over stacked components + get_iter: F, + /// Function that returns each stacked component + get_item: G, +} + +impl<'a, E, A, B, I, F, G> Map where + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a, +{ + pub const fn new (get_iter: F, get_item: G) -> Self { + Self { + __: PhantomData, + get_iter, + get_item + } + } +} + +impl<'a, E, A, B, I, F> Map>>>, I, F, fn(A, usize)->B> +where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a +{ + pub const fn east ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>, + I, F, + impl Fn(A, usize)->Push>> + Send + Sync + > { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for _ in 0..index { + push = push + size; + } + Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) + } + } + } + + pub const fn south ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>, + I, F, + impl Fn(A, usize)->Push>> + Send + Sync + > where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a + { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for _ in 0..index { + push = push + size; + } + Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) + } + } + } +} + +impl<'a, E, A, B, I, F, G> Content for Map where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a, + G: Fn(A, usize)->B + Send + Sync +{ + fn layout (&self, area: E::Area) -> E::Area { + let Self { get_iter, get_item, .. } = self; + let mut index = 0; + let [mut min_x, mut min_y] = area.center(); + let [mut max_x, mut max_y] = area.center(); + for item in get_iter() { + let [x,y,w,h] = get_item(item, index).layout(area).xywh(); + min_x = min_x.min(x.into()); + min_y = min_y.min(y.into()); + max_x = max_x.max((x + w).into()); + max_y = max_y.max((y + h).into()); + index += 1; + } + let w = max_x - min_x; + let h = max_y - min_y; + //[min_x.into(), min_y.into(), w.into(), h.into()].into() + area.center_xy([w.into(), h.into()].into()).into() + } + fn render (&self, to: &mut E) { + let Self { get_iter, get_item, .. } = self; + let mut index = 0; + let area = Content::layout(self, to.area()); + for item in get_iter() { + let item = get_item(item, index); + //to.place(area.into(), &item); + to.place(item.layout(area), &item); + index += 1; + } + } +} + +#[inline] pub fn map_south( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) +} + +#[inline] pub fn map_south_west( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) +} + +#[inline] pub fn map_east( + item_offset: O::Unit, + item_width: O::Unit, + item: impl Content +) -> impl Content { + Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) +} diff --git a/output/src/ops/_collect.rs b/output/src/ops/_collect.rs deleted file mode 100644 index 5559afe..0000000 --- a/output/src/ops/_collect.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Groupings of elements. -use crate::*; - -/// A function or closure that emits renderables. -pub trait Collector: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)) {} - -/// Any function or closure that emits renderables for the given engine matches [CollectCallback]. -impl Collector for F -where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)) {} - -pub trait Render { - fn area (&self, to: E::Area) -> E::Area; - fn render (&self, to: &mut E::Output); -} - -impl> Render for C { - fn area (&self, to: E::Area) -> E::Area { - Content::area(self, to) - } - fn render (&self, to: &mut E::Output) { - Content::render(self, to) - } -} diff --git a/output/src/ops/_reduce.rs b/output/src/ops/_reduce.rs deleted file mode 100644 index dfcc00e..0000000 --- a/output/src/ops/_reduce.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::*; - -pub struct Reduce(pub PhantomData, pub F, pub G) where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync; - -impl Reduce where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync -{ - pub const fn new (f: F, g: G) -> Self { Self(Default::default(), f, g) } -} - -impl Content for Reduce where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync -{ - fn content (&self) -> impl Render { - } -} - -/* - - //pub fn reduce (iterator: I, callback: F) -> Reduce where - //E: Output, - //I: Iterator + Send + Sync, - //R: Render, - //F: Fn(R, T, usize) -> R + Send + Sync - //{ - //Reduce(Default::default(), iterator, callback) - //} -pub struct Reduce(PhantomData<(E, R)>, I, F) where - E: Output, - I: Iterator + Send + Sync, - R: Render, - F: Fn(R, T, usize) -> R + Send + Sync; -impl Content for Reduce where - E: Output, - I: Iterator + Send + Sync, - R: Render, - F: Fn(R, T, usize) -> R + Send + Sync -{ - fn render (&self, to: &mut E) { - todo!() - } -} -*/ - -//macro_rules! define_ops { - //($Trait:ident<$E:ident:$Output:path> { $( - //$(#[$attr:meta $($attr_args:tt)*])* - //( - //$fn:ident - //$(<$($G:ident$(:$Gen:path)?, )+>)? - //$Op:ident - //($($arg:ident:$Arg:ty),*) - //) - //)* }) => { - //impl<$E: $Output> $Trait for E {} - //pub trait $Trait<$E: $Output> { - //$( - //$(#[$attr $($attr_args)*])* - //fn $fn $(<$($G),+>)? - //($($arg:$Arg),*)-> $Op<$($(, $G)+)?> - //$(where $($G: $($Gen + Send + Sync)?),+)? - //{ $Op($($arg),*) } - //)* - //} - //} -//} - -//define_ops! { - //Layout { - //(when ,> - //When(cond: bool, item: A)) - ///// When `cond` is `true`, render `a`, otherwise render `b`. - //(either , B: Render,> - //Either(cond: bool, a: A, b: B)) - ///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing. - //(opt B, B: Render,> - //Opt(option: Option, cb: F)) - ///// Maps items of iterator through callback. - //(map , I: Iterator, F: Fn() -> I, G: Fn(A, usize)->B,> - //Map(get_iterator: F, callback: G)) - //} -//} - diff --git a/output/src/ops/map.rs b/output/src/ops/map.rs deleted file mode 100644 index d124569..0000000 --- a/output/src/ops/map.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::*; - -/// Renders items from an iterator. -pub struct Map -where - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, -{ - __: PhantomData<(E, B)>, - /// Function that returns iterator over stacked components - get_iter: F, - /// Function that returns each stacked component - get_item: G, -} - -impl<'a, E, A, B, I, F, G> Map where - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a, -{ - pub const fn new (get_iter: F, get_item: G) -> Self { - Self { - __: PhantomData, - get_iter, - get_item - } - } -} - -impl<'a, E, A, B, I, F> Map>>>, I, F, fn(A, usize)->B> -where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a -{ - pub const fn east ( - size: E::Unit, - get_iter: F, - get_item: impl Fn(A, usize)->B + Send + Sync - ) -> Map< - E, A, - Push>>, - I, F, - impl Fn(A, usize)->Push>> + Send + Sync - > { - Map { - __: PhantomData, - get_iter, - get_item: move |item: A, index: usize|{ - // FIXME: multiply - let mut push: E::Unit = E::Unit::from(0u16); - for _ in 0..index { - push = push + size; - } - Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) - } - } - } - - pub const fn south ( - size: E::Unit, - get_iter: F, - get_item: impl Fn(A, usize)->B + Send + Sync - ) -> Map< - E, A, - Push>>, - I, F, - impl Fn(A, usize)->Push>> + Send + Sync - > where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a - { - Map { - __: PhantomData, - get_iter, - get_item: move |item: A, index: usize|{ - // FIXME: multiply - let mut push: E::Unit = E::Unit::from(0u16); - for _ in 0..index { - push = push + size; - } - Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) - } - } - } -} - -impl<'a, E, A, B, I, F, G> Content for Map where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a, - G: Fn(A, usize)->B + Send + Sync -{ - fn layout (&self, area: E::Area) -> E::Area { - let Self { get_iter, get_item, .. } = self; - let mut index = 0; - let [mut min_x, mut min_y] = area.center(); - let [mut max_x, mut max_y] = area.center(); - for item in get_iter() { - let [x,y,w,h] = get_item(item, index).layout(area).xywh(); - min_x = min_x.min(x.into()); - min_y = min_y.min(y.into()); - max_x = max_x.max((x + w).into()); - max_y = max_y.max((y + h).into()); - index += 1; - } - let w = max_x - min_x; - let h = max_y - min_y; - //[min_x.into(), min_y.into(), w.into(), h.into()].into() - area.center_xy([w.into(), h.into()].into()).into() - } - fn render (&self, to: &mut E) { - let Self { get_iter, get_item, .. } = self; - let mut index = 0; - let area = Content::layout(self, to.area()); - for item in get_iter() { - let item = get_item(item, index); - //to.place(area.into(), &item); - to.place(item.layout(area), &item); - index += 1; - } - } -} - -#[inline] pub fn map_south( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) -} - -#[inline] pub fn map_south_west( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) -} - -#[inline] pub fn map_east( - item_offset: O::Unit, - item_width: O::Unit, - item: impl Content -) -> impl Content { - Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) -} diff --git a/output/src/ops/memo.rs b/output/src/ops/memo.rs deleted file mode 100644 index 8168f2d..0000000 --- a/output/src/ops/memo.rs +++ /dev/null @@ -1,30 +0,0 @@ -//use crate::*; -use std::sync::{Arc, RwLock}; - -#[derive(Debug, Default)] pub struct Memo { - pub value: T, - pub view: Arc> -} - -impl Memo { - pub fn new (value: T, view: U) -> Self { - Self { value, view: Arc::new(view.into()) } - } - pub fn update ( - &mut self, - newval: T, - render: impl Fn(&mut U, &T, &T)->R - ) -> Option { - if newval != self.value { - let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); - self.value = newval; - return Some(result); - } - None - } -} - -/// Clear a pre-allocated buffer, then write into it. -#[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } -} diff --git a/output/src/ops/stack.rs b/output/src/ops/stack.rs deleted file mode 100644 index 59930ce..0000000 --- a/output/src/ops/stack.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::*; -use Direction::*; - -pub struct Stack { - __: PhantomData, - direction: Direction, - callback: F -} -impl Stack { - pub fn new (direction: Direction, callback: F) -> Self { - Self { direction, callback, __: Default::default(), } - } - pub fn north (callback: F) -> Self { - Self::new(North, callback) - } - pub fn south (callback: F) -> Self { - Self::new(South, callback) - } - pub fn east (callback: F) -> Self { - Self::new(East, callback) - } - pub fn west (callback: F) -> Self { - Self::new(West, callback) - } -} -impl)->())->()> Content for Stack { - fn layout (&self, to: E::Area) -> E::Area { - let mut x = to.x(); - let mut y = to.y(); - let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); - let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); - (self.callback)(&mut move |component: &dyn Render|{ - let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); - match self.direction { - South => { - y = y.plus(h); - h_used = h_used.plus(h); - h_remaining = h_remaining.minus(h); - w_used = w_used.max(w); - }, - East => { - x = x.plus(w); - w_used = w_used.plus(w); - w_remaining = w_remaining.minus(w); - h_used = h_used.max(h); - }, - North | West => { - todo!() - }, - _ => unreachable!(), - } - }); - match self.direction { - North | West => { - todo!() - }, - South | East => { - [to.x(), to.y(), w_used.into(), h_used.into()].into() - }, - _ => unreachable!(), - } - } - fn render (&self, to: &mut E) { - let mut x = to.x(); - let mut y = to.y(); - let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); - let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); - (self.callback)(&mut move |component: &dyn Render|{ - let layout = component.layout([x, y, w_remaining, h_remaining].into()); - match self.direction { - South => { - y = y.plus(layout.h()); - h_remaining = h_remaining.minus(layout.h()); - h_used = h_used.plus(layout.h()); - to.place(layout, component); - }, - East => { - x = x.plus(layout.w()); - w_remaining = w_remaining.minus(layout.w()); - w_used = w_used.plus(layout.h()); - to.place(layout, component); - }, - North | West => { - todo!() - }, - _ => unreachable!() - } - }); - } -} - -/*Stack::down(|add|{ - let mut i = 0; - for (_, name) in self.dirs.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - for (_, name) in self.files.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - add(&format!("{}/{i}", self.index))?; - Ok(()) -}));*/ - diff --git a/output/src/ops/thunk.rs b/output/src/ops/thunk.rs deleted file mode 100644 index d0e2877..0000000 --- a/output/src/ops/thunk.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::*; -use std::marker::PhantomData; - -/// Lazily-evaluated [Render]able. -pub struct Thunk, F: Fn()->T>( - PhantomData, - F -); -impl, F: Fn()->T> Thunk { - pub const fn new (thunk: F) -> Self { - Self(PhantomData, thunk) - } -} -impl, F: Fn()->T> Content for Thunk { - fn content (&self) -> impl Render { (self.1)() } -} - -pub struct ThunkBox( - PhantomData, - BoxBox>>, -); -impl ThunkBox { - pub const fn new (thunk: BoxBox>>) -> Self { - Self(PhantomData, thunk) - } -} -impl Content for ThunkBox { - fn content (&self) -> impl Render { (&self.1)() } -} -impl FromBox>>> for ThunkBox { - fn from (f: BoxBox>>) -> Self { - Self(PhantomData, f) - } -} -//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { - //fn from (f: F) -> Self { - //Self(Default::default(), Box::new(f)) - //} -//} - -pub struct ThunkRender(PhantomData, F); -impl ThunkRender { - pub fn new (render: F) -> Self { Self(PhantomData, render) } -} -impl Content for ThunkRender { - fn render (&self, to: &mut E) { (self.1)(to) } -} - -pub struct ThunkLayout< - E: Output, - F1: Fn(E::Area)->E::Area, - F2: Fn(&mut E) ->( - PhantomData, - F1, - F2 -); -implE::Area, F2: Fn(&mut E)> ThunkLayout { - pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } -} -impl Content for ThunkLayout -where - E: Output, - F1: Fn(E::Area)->E::Area, - F2: Fn(&mut E) -{ - fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } - fn render (&self, to: &mut E) { (self.2)(to) } -} diff --git a/output/src/space.rs b/output/src/space.rs index 056e12f..3e1298b 100644 --- a/output/src/space.rs +++ b/output/src/space.rs @@ -1,5 +1,274 @@ -mod area; pub use self::area::*; -mod coordinate; pub use self::coordinate::*; -mod direction; pub use self::direction::*; -mod measure; pub use self::measure::*; -mod size; pub use self::size::*; +use crate::*; +use Direction::*; + +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(test, derive(Arbitrary))] +pub enum Direction { + North, South, East, West, Above, Below +} + +impl Direction { + pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { + let [x, y, w, h] = area.xywh(); + match self { + North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]), + South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]), + East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]), + West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]), + Above | Below => (area.xywh(), area.xywh()) + } + } +} + +/// A linear coordinate. +pub trait Coordinate: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{ + fn zero () -> Self { 0.into() } + fn plus (self, other: Self) -> Self; + fn minus (self, other: Self) -> Self { + if self >= other { + self - other + } else { + 0.into() + } + } +} + +impl Coordinate for u16 { + fn plus (self, other: Self) -> Self { + self.saturating_add(other) + } +} + +pub trait Area: From<[N;4]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; + fn zero () -> [N;4] { + [N::zero(), N::zero(), N::zero(), N::zero()] + } + fn from_position (pos: impl Size) -> [N;4] { + let [x, y] = pos.wh(); + [x, y, 0.into(), 0.into()] + } + fn from_size (size: impl Size) -> [N;4] { + let [w, h] = size.wh(); + [0.into(), 0.into(), w, h] + } + 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) + } + } + fn xy (&self) -> [N;2] { + [self.x(), self.y()] + } + fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } + fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + fn clip_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().min(h)] + } + fn clip_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), self.w().min(w), self.h()] + } + fn clip (&self, wh: impl Size) -> [N;4] { + [self.x(), self.y(), wh.w(), wh.h()] + } + fn set_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), w, self.h()] + } + fn set_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), h] + } + fn x2 (&self) -> N { + self.x().plus(self.w()) + } + fn y2 (&self) -> N { + self.y().plus(self.h()) + } + fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] + } + fn center (&self) -> [N;2] { + [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] + } + fn center_x (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] + } + fn center_y (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n] + } + fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { + let [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] + } + fn centered (&self) -> [N;2] { + [self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())] + } + fn iter_x (&self) -> impl Iterator where N: std::iter::Step { + self.x()..(self.x()+self.w()) + } + fn iter_y (&self) -> impl Iterator where N: std::iter::Step { + self.y()..(self.y()+self.h()) + } +} + +impl Area for (N, N, N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } + fn w (&self) -> N { self.2 } + fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } + fn w (&self) -> N { self[2] } + fn h (&self) -> N { self[3] } +} + +pub trait Size: From<[N;2]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N { self.x() } + fn h (&self) -> N { self.y() } + fn wh (&self) -> [N;2] { [self.x(), self.y()] } + 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 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) + } + } + fn zero () -> [N;2] { + [N::zero(), N::zero()] + } + fn to_area_pos (&self) -> [N;4] { + let [x, y] = self.wh(); + [x, y, 0.into(), 0.into()] + } + fn to_area_size (&self) -> [N;4] { + let [w, h] = self.wh(); + [0.into(), 0.into(), w, h] + } +} + +impl Size for (N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } +} + +impl Size for [N;2] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } +} + +pub trait HasSize { + fn size (&self) -> &Measure; + fn width (&self) -> usize { + self.size().w() + } + fn height (&self) -> usize { + self.size().h() + } +} + +impl>> HasSize for T { + fn size (&self) -> &Measure { + self.get() + } +} + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small +impl Content for Measure { + fn render (&self, to: &mut E) { + self.x.store(to.area().w().into(), Relaxed); + self.y.store(to.area().h().into(), Relaxed); + } +} + +impl Clone for Measure { + fn clone (&self) -> Self { + Self { + _engine: Default::default(), + x: self.x.clone(), + y: self.y.clone(), + } + } +} + +impl std::fmt::Debug for Measure { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Measure") + .field("width", &self.x) + .field("height", &self.y) + .finish() + } +} + +impl Measure { + pub fn new () -> Self { + Self { + _engine: PhantomData::default(), + x: Arc::new(0.into()), + y: Arc::new(0.into()), + } + } + pub fn set_w (&self, w: impl Into) -> &Self { + self.x.store(w.into(), Relaxed); + self + } + pub fn set_h (&self, h: impl Into) -> &Self { + self.y.store(h.into(), Relaxed); + self + } + pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { + self.set_w(w); + self.set_h(h); + self + } + pub fn w (&self) -> usize { + self.x.load(Relaxed) + } + pub fn h (&self) -> usize { + self.y.load(Relaxed) + } + pub fn wh (&self) -> [usize;2] { + [self.w(), self.h()] + } + pub fn format (&self) -> Arc { + format!("{}x{}", self.w(), self.h()).into() + } + pub fn of > (&self, item: T) -> Bsp, T> { + Bsp::b(Fill::xy(self), item) + } +} diff --git a/output/src/space/area.rs b/output/src/space/area.rs deleted file mode 100644 index 4393790..0000000 --- a/output/src/space/area.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::*; -use std::fmt::Debug; - -pub trait Area: From<[N;4]> + Debug + Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N; - fn h (&self) -> N; - fn zero () -> [N;4] { - [N::zero(), N::zero(), N::zero(), N::zero()] - } - fn from_position (pos: impl Size) -> [N;4] { - let [x, y] = pos.wh(); - [x, y, 0.into(), 0.into()] - } - fn from_size (size: impl Size) -> [N;4] { - let [w, h] = size.wh(); - [0.into(), 0.into(), w, h] - } - 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) - } - } - fn xy (&self) -> [N;2] { - [self.x(), self.y()] - } - fn wh (&self) -> [N;2] { - [self.w(), self.h()] - } - fn xywh (&self) -> [N;4] { - [self.x(), self.y(), self.w(), self.h()] - } - fn clip_h (&self, h: N) -> [N;4] { - [self.x(), self.y(), self.w(), self.h().min(h)] - } - fn clip_w (&self, w: N) -> [N;4] { - [self.x(), self.y(), self.w().min(w), self.h()] - } - fn clip (&self, wh: impl Size) -> [N;4] { - [self.x(), self.y(), wh.w(), wh.h()] - } - fn set_w (&self, w: N) -> [N;4] { - [self.x(), self.y(), w, self.h()] - } - fn set_h (&self, h: N) -> [N;4] { - [self.x(), self.y(), self.w(), h] - } - fn x2 (&self) -> N { - self.x().plus(self.w()) - } - fn y2 (&self) -> N { - self.y().plus(self.h()) - } - fn lrtb (&self) -> [N;4] { - [self.x(), self.x2(), self.y(), self.y2()] - } - fn center (&self) -> [N;2] { - [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] - } - fn center_x (&self, n: N) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] - } - fn center_y (&self, n: N) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n] - } - fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { - let [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] - } - fn centered (&self) -> [N;2] { - [self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())] - } - fn iter_x (&self) -> impl Iterator where N: std::iter::Step { - self.x()..(self.x()+self.w()) - } - fn iter_y (&self) -> impl Iterator where N: std::iter::Step { - self.y()..(self.y()+self.h()) - } -} - -impl Area for (N, N, N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } - fn w (&self) -> N { self.2 } - fn h (&self) -> N { self.3 } -} - -impl Area for [N;4] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } - fn w (&self) -> N { self[2] } - fn h (&self) -> N { self[3] } -} diff --git a/output/src/space/coordinate.rs b/output/src/space/coordinate.rs deleted file mode 100644 index 262be9e..0000000 --- a/output/src/space/coordinate.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::ops::{Add, Sub, Mul, Div}; - -/// A linear coordinate. -pub trait Coordinate: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{ - fn zero () -> Self { 0.into() } - fn plus (self, other: Self) -> Self; - fn minus (self, other: Self) -> Self { - if self >= other { - self - other - } else { - 0.into() - } - } -} - -impl Coordinate for u16 { - fn plus (self, other: Self) -> Self { - self.saturating_add(other) - } -} diff --git a/output/src/space/direction.rs b/output/src/space/direction.rs deleted file mode 100644 index ed9df46..0000000 --- a/output/src/space/direction.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::*; -use crate::Direction::*; - -/// A cardinal direction. -#[derive(Copy, Clone, PartialEq, Debug)] -#[cfg_attr(test, derive(Arbitrary))] -pub enum Direction { - North, South, East, West, Above, Below -} - -impl Direction { - pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { - let [x, y, w, h] = area.xywh(); - match self { - North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]), - South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]), - East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]), - West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]), - Above | Below => (area.xywh(), area.xywh()) - } - } -} diff --git a/output/src/space/measure.rs b/output/src/space/measure.rs deleted file mode 100644 index 9ed5c2c..0000000 --- a/output/src/space/measure.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::*; -use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; - -pub trait HasSize { - fn size (&self) -> &Measure; - fn width (&self) -> usize { - self.size().w() - } - fn height (&self) -> usize { - self.size().h() - } -} - -impl>> HasSize for T { - fn size (&self) -> &Measure { - self.get() - } -} - -/// A widget that tracks its render width and height -#[derive(Default)] -pub struct Measure { - _engine: PhantomData, - pub x: Arc, - pub y: Arc, -} - -// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small -impl Content for Measure { - fn render (&self, to: &mut E) { - self.x.store(to.area().w().into(), Relaxed); - self.y.store(to.area().h().into(), Relaxed); - } -} - -impl Clone for Measure { - fn clone (&self) -> Self { - Self { - _engine: Default::default(), - x: self.x.clone(), - y: self.y.clone(), - } - } -} - -impl std::fmt::Debug for Measure { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("Measure") - .field("width", &self.x) - .field("height", &self.y) - .finish() - } -} - -impl Measure { - pub fn new () -> Self { - Self { - _engine: PhantomData::default(), - x: Arc::new(0.into()), - y: Arc::new(0.into()), - } - } - pub fn set_w (&self, w: impl Into) -> &Self { - self.x.store(w.into(), Relaxed); - self - } - pub fn set_h (&self, h: impl Into) -> &Self { - self.y.store(h.into(), Relaxed); - self - } - pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { - self.set_w(w); - self.set_h(h); - self - } - pub fn w (&self) -> usize { - self.x.load(Relaxed) - } - pub fn h (&self) -> usize { - self.y.load(Relaxed) - } - pub fn wh (&self) -> [usize;2] { - [self.w(), self.h()] - } - pub fn format (&self) -> Arc { - format!("{}x{}", self.w(), self.h()).into() - } - pub fn of > (&self, item: T) -> Bsp, T> { - Bsp::b(Fill::xy(self), item) - } -} diff --git a/output/src/space/size.rs b/output/src/space/size.rs deleted file mode 100644 index f9e2d19..0000000 --- a/output/src/space/size.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::*; -use std::fmt::Debug; - -pub trait Size: From<[N;2]> + Debug + Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N { self.x() } - fn h (&self) -> N { self.y() } - fn wh (&self) -> [N;2] { [self.x(), self.y()] } - 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 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) - } - } - fn zero () -> [N;2] { - [N::zero(), N::zero()] - } - fn to_area_pos (&self) -> [N;4] { - let [x, y] = self.wh(); - [x, y, 0.into(), 0.into()] - } - fn to_area_size (&self) -> [N;4] { - let [w, h] = self.wh(); - [0.into(), 0.into(), w, h] - } -} - -impl Size for (N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } -} - -impl Size for [N;2] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } -} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 5e11c0f..9edf714 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -50,7 +50,7 @@ impl ToTokens for ViewDef { impl ViewDef { fn generated (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { output }, ViewImpl { block, .. }) = self; let self_ty = &block.self_ty; let builtins = self.builtins(); let exposed = self.exposed(); @@ -74,7 +74,7 @@ impl ViewDef { /// Expressions are handled by built-in functions /// that operate over constants and symbols. fn builtins (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { output }, ViewImpl { .. }) = self; let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { ::tengri::dsl::Val::Exp(_, expr) => return Ok(Some( #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? @@ -85,7 +85,7 @@ impl ViewDef { } /// Symbols are handled by user-taked functions that take no parameters but `&self`. fn exposed (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { .. }, ViewImpl { exposed, .. }) = self; let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { #key => return Ok(Some(self.#value().boxed())), })); diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index b8d60f4..9cd527d 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -3,7 +3,6 @@ use std::time::Duration; mod tui_buffer; pub use self::tui_buffer::*; mod tui_input; pub use self::tui_input::*; -mod tui_keys; pub use self::tui_keys::*; mod tui_output; pub use self::tui_output::*; mod tui_perf; pub use self::tui_perf::*; @@ -72,8 +71,7 @@ pub trait TuiRun + Handle + 'static> { fn run (&self, state: &Arc>) -> Usually<()>; } -impl + Handle + Send + Sync + 'static> -TuiRun for Arc> { +impl + Handle + Send + Sync + 'static> TuiRun for Arc> { fn run (&self, state: &Arc>) -> Usually<()> { let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); self.write().unwrap().setup()?; diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 9ee6be6..b58c594 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -4,21 +4,36 @@ use std::thread::{spawn, JoinHandle}; use crossterm::event::{poll, read}; #[derive(Debug, Clone)] -pub struct TuiIn( +pub struct TuiIn { /// Exit flag - pub Arc, + pub exited: Arc, /// Input event - pub crossterm::event::Event, -); - -impl Input for TuiIn { - type Event = crossterm::event::Event; - type Handled = bool; - fn event (&self) -> &crossterm::event::Event { &self.1 } - fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } - fn done (&self) { self.0.store(true, Relaxed); } + pub event: TuiEvent, +} +impl Input for TuiIn { + type Event = TuiEvent; + type Handled = bool; + fn event (&self) -> &TuiEvent { &self.event } + fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } + fn done (&self) { self.exited.store(true, Relaxed); } +} +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] +pub struct TuiEvent(crossterm::event::Event); +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 + } +} +impl From for TuiEvent { + fn from (event: crossterm::event::Event) -> Self { + Self(event) + } +} +impl From> for TuiEvent { + fn from (x: Arc) -> Self { + TuiEvent(TuiKey::new(x.as_ref()).build().unwrap_or_else(||panic!("invalid key: {x}"))) + } } - impl TuiIn { /// Spawn the input thread. pub fn run_input + Send + Sync + 'static> ( @@ -35,7 +50,6 @@ impl TuiIn { if poll(timer).is_ok() { let event = read().unwrap(); match event { - crossterm::event::Event::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, @@ -46,7 +60,8 @@ impl TuiIn { }, _ => { let exited = exited.clone(); - if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) { + let event = event.into(); + if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) { panic!("{e}") } } @@ -59,10 +74,101 @@ impl TuiIn { //#[cfg(feature = "dsl")] //impl DslInput for TuiIn { //fn matches_dsl (&self, token: &str) -> bool { - //if let Some(event) = KeyMatcher::new(token).build() { + //if let Some(event) = TuiKey::new(token).build() { //&event == self.event() //} else { //false //} //} //} + +pub struct TuiKey { + valid: bool, + key: Option, + mods: KeyModifiers, +} + +impl TuiKey { + pub fn new (token: impl AsRef) -> Self { + let token = token.as_ref(); + if token.len() < 2 { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else if token.chars().next() != Some('@') { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else { + Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) + } + } + pub fn build (self) -> Option { + if self.valid && self.key.is_some() { + Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) + } else { + None + } + } + fn next (mut self, token: &str) -> Self { + let mut tokens = token.split('-').peekable(); + while let Some(token) = tokens.next() { + if tokens.peek().is_some() { + match token { + "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, + "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, + "shift" | "Shift" | "s" | "S" => { + self.mods |= KeyModifiers::SHIFT; + // + TODO normalize character case, BackTab, etc. + }, + _ => panic!("unknown modifier {token}"), + } + } else { + self.key = if token.len() == 1 { + Some(KeyCode::Char(token.chars().next().unwrap())) + } else { + Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + } + } + } + self + } + fn named_key (token: &str) -> Option { + use KeyCode::*; + Some(match token { + "up" => Up, + "down" => Down, + "left" => Left, + "right" => Right, + "esc" | "escape" => Esc, + "enter" | "return" => Enter, + "delete" | "del" => Delete, + "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, + }) + } +} diff --git a/tui/src/tui_engine/tui_keys.rs b/tui/src/tui_engine/tui_keys.rs deleted file mode 100644 index cda5899..0000000 --- a/tui/src/tui_engine/tui_keys.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::*; - -pub struct KeyMatcher { - valid: bool, - key: Option, - mods: KeyModifiers, -} - -impl KeyMatcher { - pub fn new (token: impl AsRef) -> Self { - let token = token.as_ref(); - if token.len() < 2 { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else if token.chars().next() != Some('@') { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else { - Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) - } - } - pub fn build (self) -> Option { - if self.valid && self.key.is_some() { - Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) - } else { - None - } - } - fn next (mut self, token: &str) -> Self { - let mut tokens = token.split('-').peekable(); - while let Some(token) = tokens.next() { - if tokens.peek().is_some() { - match token { - "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, - "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, - "shift" | "Shift" | "s" | "S" => { - self.mods |= KeyModifiers::SHIFT; - // + TODO normalize character case, BackTab, etc. - }, - _ => panic!("unknown modifier {token}"), - } - } else { - self.key = if token.len() == 1 { - Some(KeyCode::Char(token.chars().next().unwrap())) - } else { - Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) - } - } - } - self - } - fn named_key (token: &str) -> Option { - use KeyCode::*; - Some(match token { - "up" => Up, - "down" => Down, - "left" => Left, - "right" => Right, - "esc" | "escape" => Esc, - "enter" | "return" => Enter, - "delete" | "del" => Delete, - "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, - }) - } -}