engine: unify

This commit is contained in:
🪞👃🪞 2024-09-12 15:20:53 +03:00
parent 2a024cd3a3
commit c45c3bf7f9
5 changed files with 584 additions and 595 deletions

View file

@ -1,12 +1,5 @@
use crate::*; use crate::*;
submod! {
focus
handle
keymap
layout
}
/// Entry point for main loop /// Entry point for main loop
pub trait App<T: Engine> { pub trait App<T: Engine> {
fn run (self, context: T) -> Usually<T>; fn run (self, context: T) -> Usually<T>;
@ -155,3 +148,587 @@ pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
} }
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {} impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}
/// Handle input
pub trait Handle<E: Engine>: Send + Sync {
fn handle (&mut self, context: &E::HandleInput) -> Perhaps<E::Handled>;
}
impl<H, E: Engine> Handle<E> for &mut H where H: Handle<E> {
fn handle (&mut self, context: &E::HandleInput) -> Perhaps<E::Handled> {
(*self).handle(context)
}
}
impl<H, E: Engine> Handle<E> for Option<H> where H: Handle<E> {
fn handle (&mut self, context: &E::HandleInput) -> 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::HandleInput) -> Perhaps<E::Handled> {
self.lock().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::HandleInput) -> 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::HandleInput) -> 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::HandleInput) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
pub type KeyBinding<T> = (
KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>
);
pub type KeyMap<T> = [KeyBinding<T>];
pub fn handle_keymap <T> (
state: &mut T, event: &TuiEvent, keymap: &KeyMap<T>,
) -> Usually<bool> {
match event {
TuiEvent::Input(crossterm::event::Event::Key(event)) => {
for (code, modifiers, _, _, command) in keymap.iter() {
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
return command(state)
}
}
},
_ => {}
};
Ok(false)
}
/// Define a keymap
#[macro_export] macro_rules! keymap {
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => {
&[
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),*
] as &'static [KeyBinding<$T>]
}
}
#[macro_export] macro_rules! map_key {
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr) => {
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as &dyn Fn()->Usually<bool>)
}
}
#[macro_export] macro_rules! key {
($code:pat) => {
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $code,
modifiers: crossterm::event::KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}))
};
(Ctrl-$code:pat) => {
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $code,
modifiers: crossterm::event::KeyModifiers::CONTROL,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}))
};
(Alt-$code:pat) => {
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $code,
modifiers: crossterm::event::KeyModifiers::ALT,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}))
}
}
#[macro_export] macro_rules! match_key {
($event:expr, {
$($key:pat=>$block:expr),* $(,)?
}) => {
match $event {
$(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $key,
modifiers: crossterm::event::KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}) => {
$block
})*
_ => Ok(None)
}
}
}
pub enum Collected<'a, E: Engine> {
Box(Box<dyn Widget<Engine = E> + 'a>),
Ref(&'a (dyn Widget<Engine = E> + 'a)),
}
impl<'a, E: Engine> Widget for Collected<'a, E> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::Box(inner) => (*inner).layout(area),
Self::Ref(inner) => (*inner).layout(area),
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
match self {
Self::Box(inner) => (*inner).render(to),
Self::Ref(inner) => (*inner).render(to),
}
}
}
pub struct Collection<'a, E: Engine>(
pub Vec<Collected<'a, E>>
);
impl<'a, E: Engine> Collection<'a, E> {
pub fn new () -> Self {
Self(vec![])
}
}
pub trait Collect<'a, E: Engine> {
fn add_box (self, item: Box<dyn Widget<Engine = E> + 'a>) -> Self;
fn add_ref (self, item: &'a dyn Widget<Engine = E>) -> Self;
fn add <R: Widget<Engine = E> + Sized + 'a> (self, item: R) -> Self
where Self: Sized
{
self.add_box(Box::new(item))
}
}
impl<'a, E: Engine> Collect<'a, E> for Collection<'a, E> {
fn add_box (mut self, item: Box<dyn Widget<Engine = E> + 'a>) -> Self {
self.0.push(Collected::Box(item));
self
}
fn add_ref (mut self, item: &'a dyn Widget<Engine = E>) -> Self {
self.0.push(Collected::Ref(item));
self
}
}
pub struct Split<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
>(pub F, pub Direction, PhantomData<E>);
impl<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
> Split<E, F> {
#[inline]
pub fn new (direction: Direction, build: F) -> Self {
Self(build, direction, Default::default())
}
#[inline]
pub fn right (build: F) -> Self {
Self::new(Direction::Right, build)
}
#[inline]
pub fn down (build: F) -> Self {
Self::new(Direction::Down, build)
}
}
pub struct Layers<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
>(pub F, PhantomData<E>);
impl<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
> Layers<E, F> {
#[inline]
pub fn new (build: F) -> Self {
Self(build, Default::default())
}
}
#[derive(Copy, Clone)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
pub fn is_down (&self) -> bool {
match self { Self::Down => true, _ => false }
}
pub fn is_right (&self) -> bool {
match self { Self::Right => true, _ => false }
}
}
/// Override X and Y coordinates, aligning to corner, side, or center of area
pub enum Align<L> {
Center(L),
NW(L),
N(L),
NE(L),
W(L),
E(L),
SW(L),
S(L),
SE(L)
}
impl<T> Align<T> {
pub fn inner (&self) -> &T {
match self {
Self::Center(inner) => inner,
Self::NW(inner) => inner,
Self::N(inner) => inner,
Self::NE(inner) => inner,
Self::W(inner) => inner,
Self::E(inner) => inner,
Self::SW(inner) => inner,
Self::S(inner) => inner,
Self::SE(inner) => inner,
}
}
}
/// Enforce minimum size of drawing area
pub enum Min<U: Number, T> {
/// Enforce minimum width
W(U, T),
/// Enforce minimum height
H(U, T),
/// Enforce minimum width and height
WH(U, U, T),
}
impl<N: Number, T> Min<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Min<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => if area.w() < *w { Ok(None) } else {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w().max(*w), area.h()].into())
},
Self::H(h, item) => if area.w() < *h { Ok(None) } else {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w(), area.h().max(*h)].into())
},
Self::WH(w, h, item) => if area.w() < *w || area.h() < *h { Ok(None) } else {
item.layout([area.x(), area.y(), area.w().max(*w), area.h().max(*h)].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
// 🡘 🡙 ←🡙→
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Enforce maximum size of drawing area
pub enum Max<U: Number, T> {
/// Enforce maximum width
W(U, T),
/// Enforce maximum height
H(U, T),
/// Enforce maximum width and height
WH(U, U, T),
}
impl<N: Number, T> Max<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Max<E:: Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w().min(*w), area.h()].into())
},
Self::H(h, item) => {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w(), area.h().min(*h)].into())
},
Self::WH(w, h, item) => {
item.layout([area.x(), area.y(), area.w().min(*w), area.h().min(*h)].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Expand drawing area
pub enum Outset<N: Number, T> {
/// Increase width
W(N, T),
/// Increase height
H(N, T),
/// Increase width and height
WH(N, N, T)
}
impl<N: Number, T> Outset<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Outset<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => if area.x() < *w { Ok(None) } else {
item.layout([area.x() - *w, area.y(), area.w() + *w, area.h()].into())
},
Self::H(h, item) => if area.y() < *h { Ok(None) } else {
item.layout([area.x(), area.y() - *h, area.w(), area.h() + *h].into())
},
Self::WH(w, h, item) => if area.x() < *w || area.y() < *h { Ok(None) } else {
item.layout([area.x()- *w, area.y() - *h, area.w() + *w, area.h() + *h].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Shrink drawing area
pub enum Inset<N: Number, T> {
/// Decrease width
W(N, T),
/// Decrease height
H(N, T),
/// Decrease width and height
WH(N, N, T),
}
impl<N: Number, T: Widget> Inset<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Inset<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => if area.w() < *w { Ok(None) } else {
item.layout([area.x() + *w, area.y(), area.w() - *w, area.h()].into())
},
Self::H(h, item) => if area.h() < *h { Ok(None) } else {
item.layout([area.x(), area.y() + *h, area.w(), area.h() - *h].into())
},
Self::WH(w, h, item) => if area.w() < *w || area.h() < *h { Ok(None) } else {
item.layout([area.x() - *w, area.y() - *h, area.w() + *w, area.h() + *h].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Move origin point of drawing area
pub enum Offset<N: Number, T: Widget> {
X(N, T),
Y(N, T),
XY(N, N, T),
}
impl<N: Number, T: Widget> Offset<N, T> {
fn inner (&self) -> &T {
match self {
Self::X(_, inner) => inner,
Self::Y(_, inner) => inner,
Self::XY(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Offset<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::X(x, item) => if area.w() < *x { Ok(None) } else {
item.layout([area.x() + *x, area.y(), area.w() - *x, area.h()].into())
},
Self::Y(y, item) => if area.h() < *y { Ok(None) } else {
item.layout([area.x(), area.y() + *y, area.w(), area.h() - *y].into())
},
Self::XY(x, y, item) => if area.w() < *x || area.h() < *y { Ok(None) } else {
item.layout([area.x() + *x, area.y() + *y, area.w() - *x, area.h() - *y].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// A component that may contain [Focusable] components.
pub trait Focus <const N: usize, E: Engine>: Widget<Engine = E> + Handle<E> {
fn focus (&self) -> usize;
fn focus_mut (&mut self) -> &mut usize;
fn focusable (&self) -> [&dyn Focusable<E>;N];
fn focusable_mut (&mut self) -> [&mut dyn Focusable<E>;N];
fn focused (&self) -> &dyn Focusable<E> {
let focus = self.focus();
self.focusable()[focus]
}
fn focused_mut (&mut self) -> &mut dyn Focusable<E> {
let focus = self.focus();
self.focusable_mut()[focus]
}
fn focus_prev (&mut self) {
let focus = self.focus();
self.focus_set(if focus > 0 { focus - 1 } else { N - 1 });
}
fn focus_next (&mut self) {
let focus = self.focus();
self.focus_set(if focus < N - 1 { focus + 1 } else { 0 });
}
fn focus_set (&mut self, focus: usize) {
*self.focus_mut() = focus;
let focusable = self.focusable_mut();
for index in 0..N {
focusable[index].set_focused(index == focus);
}
}
}
/// A component that may be focused.
pub trait Focusable<E: Engine>: Widget<Engine = E> + Handle<E> {
fn is_focused (&self) -> bool;
fn set_focused (&mut self, focused: bool);
}
impl<F: Focusable<E>, E: Engine> Focusable<E> for Option<F>
where Option<F>: Widget<Engine = E>
{
fn is_focused (&self) -> bool {
match self {
Some(focusable) => focusable.is_focused(),
None => false
}
}
fn set_focused (&mut self, focused: bool) {
if let Some(focusable) = self {
focusable.set_focused(focused)
}
}
}
/// Implement the [Focus] trait for a component.
#[macro_export] macro_rules! focus {
($struct:ident ($focus:ident) : $count:expr => [
$($focusable:ident),*
]) => {
impl Focus<$count, E> for $struct {
fn focus (&self) -> usize {
self.$focus
}
fn focus_mut (&mut self) -> &mut usize {
&mut self.$focus
}
fn focusable (&self) -> [&dyn Focusable<E>;$count] {
[
$(&self.$focusable as &dyn Focusable<E>,)*
]
}
fn focusable_mut (&mut self) -> [&mut dyn Focusable<E>;$count] {
[
$(&mut self.$focusable as &mut dyn Focusable<E>,)*
]
}
}
}
}
/// Implement the [Focusable] trait for a component.
#[macro_export] macro_rules! focusable {
($struct:ident) => {
focusable!($struct (focused));
};
($struct:ident ($focused:ident)) => {
impl Focusable for $struct {
fn is_focused (&self) -> bool {
self.$focused
}
fn set_focused (&mut self, focused: bool) {
self.$focused = focused
}
}
}
}

View file

@ -1,98 +0,0 @@
use crate::*;
/// A component that may contain [Focusable] components.
pub trait Focus <const N: usize, E: Engine>: Widget<Engine = E> + Handle<E> {
fn focus (&self) -> usize;
fn focus_mut (&mut self) -> &mut usize;
fn focusable (&self) -> [&dyn Focusable<E>;N];
fn focusable_mut (&mut self) -> [&mut dyn Focusable<E>;N];
fn focused (&self) -> &dyn Focusable<E> {
let focus = self.focus();
self.focusable()[focus]
}
fn focused_mut (&mut self) -> &mut dyn Focusable<E> {
let focus = self.focus();
self.focusable_mut()[focus]
}
fn focus_prev (&mut self) {
let focus = self.focus();
self.focus_set(if focus > 0 { focus - 1 } else { N - 1 });
}
fn focus_next (&mut self) {
let focus = self.focus();
self.focus_set(if focus < N - 1 { focus + 1 } else { 0 });
}
fn focus_set (&mut self, focus: usize) {
*self.focus_mut() = focus;
let focusable = self.focusable_mut();
for index in 0..N {
focusable[index].set_focused(index == focus);
}
}
}
/// A component that may be focused.
pub trait Focusable<E: Engine>: Widget<Engine = E> + Handle<E> {
fn is_focused (&self) -> bool;
fn set_focused (&mut self, focused: bool);
}
impl<F: Focusable<E>, E: Engine> Focusable<E> for Option<F>
where Option<F>: Widget<Engine = E>
{
fn is_focused (&self) -> bool {
match self {
Some(focusable) => focusable.is_focused(),
None => false
}
}
fn set_focused (&mut self, focused: bool) {
if let Some(focusable) = self {
focusable.set_focused(focused)
}
}
}
/// Implement the [Focus] trait for a component.
#[macro_export] macro_rules! focus {
($struct:ident ($focus:ident) : $count:expr => [
$($focusable:ident),*
]) => {
impl Focus<$count, E> for $struct {
fn focus (&self) -> usize {
self.$focus
}
fn focus_mut (&mut self) -> &mut usize {
&mut self.$focus
}
fn focusable (&self) -> [&dyn Focusable<E>;$count] {
[
$(&self.$focusable as &dyn Focusable<E>,)*
]
}
fn focusable_mut (&mut self) -> [&mut dyn Focusable<E>;$count] {
[
$(&mut self.$focusable as &mut dyn Focusable<E>,)*
]
}
}
}
}
/// Implement the [Focusable] trait for a component.
#[macro_export] macro_rules! focusable {
($struct:ident) => {
focusable!($struct (focused));
};
($struct:ident ($focused:ident)) => {
impl Focusable for $struct {
fn is_focused (&self) -> bool {
self.$focused
}
fn set_focused (&mut self, focused: bool) {
self.$focused = focused
}
}
}
}

View file

@ -1,46 +0,0 @@
use crate::*;
/// Handle input
pub trait Handle<E: Engine>: Send + Sync {
fn handle (&mut self, context: &E::HandleInput) -> Perhaps<E::Handled>;
}
impl<H, E: Engine> Handle<E> for &mut H where H: Handle<E> {
fn handle (&mut self, context: &E::HandleInput) -> Perhaps<E::Handled> {
(*self).handle(context)
}
}
impl<H, E: Engine> Handle<E> for Option<H> where H: Handle<E> {
fn handle (&mut self, context: &E::HandleInput) -> 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::HandleInput) -> Perhaps<E::Handled> {
self.lock().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::HandleInput) -> 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::HandleInput) -> 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::HandleInput) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}

View file

@ -1,85 +0,0 @@
use crate::*;
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
pub type KeyBinding<T> = (
KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>
);
pub type KeyMap<T> = [KeyBinding<T>];
pub fn handle_keymap <T> (
state: &mut T, event: &TuiEvent, keymap: &KeyMap<T>,
) -> Usually<bool> {
match event {
TuiEvent::Input(crossterm::event::Event::Key(event)) => {
for (code, modifiers, _, _, command) in keymap.iter() {
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
return command(state)
}
}
},
_ => {}
};
Ok(false)
}
/// Define a keymap
#[macro_export] macro_rules! keymap {
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => {
&[
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),*
] as &'static [KeyBinding<$T>]
}
}
#[macro_export] macro_rules! map_key {
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr) => {
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as &dyn Fn()->Usually<bool>)
}
}
#[macro_export] macro_rules! key {
($code:pat) => {
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $code,
modifiers: crossterm::event::KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}))
};
(Ctrl-$code:pat) => {
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $code,
modifiers: crossterm::event::KeyModifiers::CONTROL,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}))
};
(Alt-$code:pat) => {
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $code,
modifiers: crossterm::event::KeyModifiers::ALT,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}))
}
}
#[macro_export] macro_rules! match_key {
($event:expr, {
$($key:pat=>$block:expr),* $(,)?
}) => {
match $event {
$(crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: $key,
modifiers: crossterm::event::KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE
}) => {
$block
})*
_ => Ok(None)
}
}
}

View file

@ -1,359 +0,0 @@
use crate::*;
pub enum Collected<'a, E: Engine> {
Box(Box<dyn Widget<Engine = E> + 'a>),
Ref(&'a (dyn Widget<Engine = E> + 'a)),
}
impl<'a, E: Engine> Widget for Collected<'a, E> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::Box(inner) => (*inner).layout(area),
Self::Ref(inner) => (*inner).layout(area),
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
match self {
Self::Box(inner) => (*inner).render(to),
Self::Ref(inner) => (*inner).render(to),
}
}
}
pub struct Collection<'a, E: Engine>(
pub Vec<Collected<'a, E>>
);
impl<'a, E: Engine> Collection<'a, E> {
pub fn new () -> Self {
Self(vec![])
}
}
pub trait Collect<'a, E: Engine> {
fn add_box (self, item: Box<dyn Widget<Engine = E> + 'a>) -> Self;
fn add_ref (self, item: &'a dyn Widget<Engine = E>) -> Self;
fn add <R: Widget<Engine = E> + Sized + 'a> (self, item: R) -> Self
where Self: Sized
{
self.add_box(Box::new(item))
}
}
impl<'a, E: Engine> Collect<'a, E> for Collection<'a, E> {
fn add_box (mut self, item: Box<dyn Widget<Engine = E> + 'a>) -> Self {
self.0.push(Collected::Box(item));
self
}
fn add_ref (mut self, item: &'a dyn Widget<Engine = E>) -> Self {
self.0.push(Collected::Ref(item));
self
}
}
pub struct Split<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
>(pub F, pub Direction, PhantomData<E>);
impl<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
> Split<E, F> {
#[inline]
pub fn new (direction: Direction, build: F) -> Self {
Self(build, direction, Default::default())
}
#[inline]
pub fn right (build: F) -> Self {
Self::new(Direction::Right, build)
}
#[inline]
pub fn down (build: F) -> Self {
Self::new(Direction::Down, build)
}
}
pub struct Layers<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
>(pub F, PhantomData<E>);
impl<
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
> Layers<E, F> {
#[inline]
pub fn new (build: F) -> Self {
Self(build, Default::default())
}
}
#[derive(Copy, Clone)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
pub fn is_down (&self) -> bool {
match self { Self::Down => true, _ => false }
}
pub fn is_right (&self) -> bool {
match self { Self::Right => true, _ => false }
}
}
/// Override X and Y coordinates, aligning to corner, side, or center of area
pub enum Align<L> {
Center(L),
NW(L),
N(L),
NE(L),
W(L),
E(L),
SW(L),
S(L),
SE(L)
}
impl<T> Align<T> {
pub fn inner (&self) -> &T {
match self {
Self::Center(inner) => inner,
Self::NW(inner) => inner,
Self::N(inner) => inner,
Self::NE(inner) => inner,
Self::W(inner) => inner,
Self::E(inner) => inner,
Self::SW(inner) => inner,
Self::S(inner) => inner,
Self::SE(inner) => inner,
}
}
}
/// Enforce minimum size of drawing area
pub enum Min<U: Number, T> {
/// Enforce minimum width
W(U, T),
/// Enforce minimum height
H(U, T),
/// Enforce minimum width and height
WH(U, U, T),
}
impl<N: Number, T> Min<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Min<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => if area.w() < *w { Ok(None) } else {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w().max(*w), area.h()].into())
},
Self::H(h, item) => if area.w() < *h { Ok(None) } else {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w(), area.h().max(*h)].into())
},
Self::WH(w, h, item) => if area.w() < *w || area.h() < *h { Ok(None) } else {
item.layout([area.x(), area.y(), area.w().max(*w), area.h().max(*h)].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
// 🡘 🡙 ←🡙→
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Enforce maximum size of drawing area
pub enum Max<U: Number, T> {
/// Enforce maximum width
W(U, T),
/// Enforce maximum height
H(U, T),
/// Enforce maximum width and height
WH(U, U, T),
}
impl<N: Number, T> Max<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Max<E:: Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w().min(*w), area.h()].into())
},
Self::H(h, item) => {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w(), area.h().min(*h)].into())
},
Self::WH(w, h, item) => {
item.layout([area.x(), area.y(), area.w().min(*w), area.h().min(*h)].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Expand drawing area
pub enum Outset<N: Number, T> {
/// Increase width
W(N, T),
/// Increase height
H(N, T),
/// Increase width and height
WH(N, N, T)
}
impl<N: Number, T> Outset<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Outset<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => if area.x() < *w { Ok(None) } else {
item.layout([area.x() - *w, area.y(), area.w() + *w, area.h()].into())
},
Self::H(h, item) => if area.y() < *h { Ok(None) } else {
item.layout([area.x(), area.y() - *h, area.w(), area.h() + *h].into())
},
Self::WH(w, h, item) => if area.x() < *w || area.y() < *h { Ok(None) } else {
item.layout([area.x()- *w, area.y() - *h, area.w() + *w, area.h() + *h].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Shrink drawing area
pub enum Inset<N: Number, T> {
/// Decrease width
W(N, T),
/// Decrease height
H(N, T),
/// Decrease width and height
WH(N, N, T),
}
impl<N: Number, T: Widget> Inset<N, T> {
fn inner (&self) -> &T {
match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Inset<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::W(w, item) => if area.w() < *w { Ok(None) } else {
item.layout([area.x() + *w, area.y(), area.w() - *w, area.h()].into())
},
Self::H(h, item) => if area.h() < *h { Ok(None) } else {
item.layout([area.x(), area.y() + *h, area.w(), area.h() - *h].into())
},
Self::WH(w, h, item) => if area.w() < *w || area.h() < *h { Ok(None) } else {
item.layout([area.x() - *w, area.y() - *h, area.w() + *w, area.h() + *h].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}
/// Move origin point of drawing area
pub enum Offset<N: Number, T: Widget> {
X(N, T),
Y(N, T),
XY(N, N, T),
}
impl<N: Number, T: Widget> Offset<N, T> {
fn inner (&self) -> &T {
match self {
Self::X(_, inner) => inner,
Self::Y(_, inner) => inner,
Self::XY(_, _, inner) => inner,
}
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Offset<E::Unit, T> {
type Engine = E;
fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
match self {
Self::X(x, item) => if area.w() < *x { Ok(None) } else {
item.layout([area.x() + *x, area.y(), area.w() - *x, area.h()].into())
},
Self::Y(y, item) => if area.h() < *y { Ok(None) } else {
item.layout([area.x(), area.y() + *y, area.w(), area.h() - *y].into())
},
Self::XY(x, y, item) => if area.w() < *x || area.h() < *y { Ok(None) } else {
item.layout([area.x() + *x, area.y() + *y, area.w() - *x, area.h() - *y].into())
}
}
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|self.inner().render(to))
.transpose()
.map(|x|x.flatten())
}
}