From c45c3bf7f9b971c645902afbda1c15fd8b30e8ee Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 12 Sep 2024 15:20:53 +0300 Subject: [PATCH] engine: unify --- crates/tek_core/src/engine.rs | 591 ++++++++++++++++++++++++++- crates/tek_core/src/engine/focus.rs | 98 ----- crates/tek_core/src/engine/handle.rs | 46 --- crates/tek_core/src/engine/keymap.rs | 85 ---- crates/tek_core/src/engine/layout.rs | 359 ---------------- 5 files changed, 584 insertions(+), 595 deletions(-) delete mode 100644 crates/tek_core/src/engine/focus.rs delete mode 100644 crates/tek_core/src/engine/handle.rs delete mode 100644 crates/tek_core/src/engine/keymap.rs delete mode 100644 crates/tek_core/src/engine/layout.rs diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs index 324ef99a..8128d619 100644 --- a/crates/tek_core/src/engine.rs +++ b/crates/tek_core/src/engine.rs @@ -1,12 +1,5 @@ use crate::*; -submod! { - focus - handle - keymap - layout -} - /// Entry point for main loop pub trait App { fn run (self, context: T) -> Usually; @@ -155,3 +148,587 @@ pub trait ExitableComponent: Exit + Component where E: Engine { } impl + Exit> ExitableComponent for C {} + +/// Handle input +pub trait Handle: Send + Sync { + fn handle (&mut self, context: &E::HandleInput) -> Perhaps; +} + +impl Handle for &mut H where H: Handle { + fn handle (&mut self, context: &E::HandleInput) -> Perhaps { + (*self).handle(context) + } +} + +impl Handle for Option where H: Handle { + fn handle (&mut self, context: &E::HandleInput) -> Perhaps { + if let Some(ref mut handle) = self { + handle.handle(context) + } else { + Ok(None) + } + } +} + +impl Handle for Mutex where H: Handle { + fn handle (&mut self, context: &E::HandleInput) -> Perhaps { + self.lock().unwrap().handle(context) + } +} + +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &E::HandleInput) -> Perhaps { + self.lock().unwrap().handle(context) + } +} + +impl Handle for RwLock where H: Handle { + fn handle (&mut self, context: &E::HandleInput) -> Perhaps { + self.write().unwrap().handle(context) + } +} + +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &E::HandleInput) -> Perhaps { + self.write().unwrap().handle(context) + } +} + +pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; + +pub type KeyBinding = ( + KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler +); + +pub type KeyMap = [KeyBinding]; + +pub fn handle_keymap ( + state: &mut T, event: &TuiEvent, keymap: &KeyMap, +) -> Usually { + 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) + } +} + +#[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 + 'a>), + Ref(&'a (dyn Widget + 'a)), +} + +impl<'a, E: Engine> Widget for Collected<'a, E> { + type Engine = E; + fn layout (&self, area: E::Area) -> Perhaps { + match self { + Self::Box(inner) => (*inner).layout(area), + Self::Ref(inner) => (*inner).layout(area), + } + } + fn render (&self, to: &mut E) -> Perhaps { + match self { + Self::Box(inner) => (*inner).render(to), + Self::Ref(inner) => (*inner).render(to), + } + } +} + +pub struct Collection<'a, E: Engine>( + pub Vec> +); + +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 + 'a>) -> Self; + fn add_ref (self, item: &'a dyn Widget) -> Self; + fn add + 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 + 'a>) -> Self { + self.0.push(Collected::Box(item)); + self + } + fn add_ref (mut self, item: &'a dyn Widget) -> Self { + self.0.push(Collected::Ref(item)); + self + } +} + +pub struct Split< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> +>(pub F, pub Direction, PhantomData); + +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> +> Split { + #[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)->Usually<()>)->Usually<()> +>(pub F, PhantomData); + +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> +> Layers { + #[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 { + Center(L), + NW(L), + N(L), + NE(L), + W(L), + E(L), + SW(L), + S(L), + SE(L) +} + +impl Align { + 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 { + /// Enforce minimum width + W(U, T), + /// Enforce minimum height + H(U, T), + /// Enforce minimum width and height + WH(U, U, T), +} + +impl Min { + fn inner (&self) -> &T { + match self { + Self::W(_, inner) => inner, + Self::H(_, inner) => inner, + Self::WH(_, _, inner) => inner, + } + } +} + +impl> Widget for Min { + type Engine = E; + fn layout (&self, area: E::Area) -> Perhaps { + 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 { + // 🡘 🡙 ←🡙→ + 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 { + /// Enforce maximum width + W(U, T), + /// Enforce maximum height + H(U, T), + /// Enforce maximum width and height + WH(U, U, T), +} + +impl Max { + fn inner (&self) -> &T { + match self { + Self::W(_, inner) => inner, + Self::H(_, inner) => inner, + Self::WH(_, _, inner) => inner, + } + } +} + +impl> Widget for Max { + type Engine = E; + fn layout (&self, area: E::Area) -> Perhaps { + 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 { + 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 { + /// Increase width + W(N, T), + /// Increase height + H(N, T), + /// Increase width and height + WH(N, N, T) +} + +impl Outset { + fn inner (&self) -> &T { + match self { + Self::W(_, inner) => inner, + Self::H(_, inner) => inner, + Self::WH(_, _, inner) => inner, + } + } +} + +impl> Widget for Outset { + type Engine = E; + fn layout (&self, area: E::Area) -> Perhaps { + 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 { + 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 { + /// Decrease width + W(N, T), + /// Decrease height + H(N, T), + /// Decrease width and height + WH(N, N, T), +} + +impl Inset { + fn inner (&self) -> &T { + match self { + Self::W(_, inner) => inner, + Self::H(_, inner) => inner, + Self::WH(_, _, inner) => inner, + } + } +} + +impl> Widget for Inset { + type Engine = E; + fn layout (&self, area: E::Area) -> Perhaps { + 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 { + 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 { + X(N, T), + Y(N, T), + XY(N, N, T), +} + +impl Offset { + fn inner (&self) -> &T { + match self { + Self::X(_, inner) => inner, + Self::Y(_, inner) => inner, + Self::XY(_, _, inner) => inner, + } + } +} + +impl> Widget for Offset { + type Engine = E; + fn layout (&self, area: E::Area) -> Perhaps { + 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 { + 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 : Widget + Handle { + fn focus (&self) -> usize; + fn focus_mut (&mut self) -> &mut usize; + fn focusable (&self) -> [&dyn Focusable;N]; + fn focusable_mut (&mut self) -> [&mut dyn Focusable;N]; + + fn focused (&self) -> &dyn Focusable { + let focus = self.focus(); + self.focusable()[focus] + } + fn focused_mut (&mut self) -> &mut dyn Focusable { + 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: Widget + Handle { + fn is_focused (&self) -> bool; + fn set_focused (&mut self, focused: bool); +} + +impl, E: Engine> Focusable for Option + where Option: Widget +{ + 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;$count] { + [ + $(&self.$focusable as &dyn Focusable,)* + ] + } + fn focusable_mut (&mut self) -> [&mut dyn Focusable;$count] { + [ + $(&mut self.$focusable as &mut dyn Focusable,)* + ] + } + } + } +} + +/// 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 + } + } + } +} diff --git a/crates/tek_core/src/engine/focus.rs b/crates/tek_core/src/engine/focus.rs deleted file mode 100644 index 3e680126..00000000 --- a/crates/tek_core/src/engine/focus.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::*; - -/// A component that may contain [Focusable] components. -pub trait Focus : Widget + Handle { - fn focus (&self) -> usize; - fn focus_mut (&mut self) -> &mut usize; - fn focusable (&self) -> [&dyn Focusable;N]; - fn focusable_mut (&mut self) -> [&mut dyn Focusable;N]; - - fn focused (&self) -> &dyn Focusable { - let focus = self.focus(); - self.focusable()[focus] - } - fn focused_mut (&mut self) -> &mut dyn Focusable { - 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: Widget + Handle { - fn is_focused (&self) -> bool; - fn set_focused (&mut self, focused: bool); -} - -impl, E: Engine> Focusable for Option - where Option: Widget -{ - 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;$count] { - [ - $(&self.$focusable as &dyn Focusable,)* - ] - } - fn focusable_mut (&mut self) -> [&mut dyn Focusable;$count] { - [ - $(&mut self.$focusable as &mut dyn Focusable,)* - ] - } - } - } -} - -/// 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 - } - } - } -} diff --git a/crates/tek_core/src/engine/handle.rs b/crates/tek_core/src/engine/handle.rs deleted file mode 100644 index 0478b259..00000000 --- a/crates/tek_core/src/engine/handle.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::*; - -/// Handle input -pub trait Handle: Send + Sync { - fn handle (&mut self, context: &E::HandleInput) -> Perhaps; -} - -impl Handle for &mut H where H: Handle { - fn handle (&mut self, context: &E::HandleInput) -> Perhaps { - (*self).handle(context) - } -} - -impl Handle for Option where H: Handle { - fn handle (&mut self, context: &E::HandleInput) -> Perhaps { - if let Some(ref mut handle) = self { - handle.handle(context) - } else { - Ok(None) - } - } -} - -impl Handle for Mutex where H: Handle { - fn handle (&mut self, context: &E::HandleInput) -> Perhaps { - self.lock().unwrap().handle(context) - } -} - -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E::HandleInput) -> Perhaps { - self.lock().unwrap().handle(context) - } -} - -impl Handle for RwLock where H: Handle { - fn handle (&mut self, context: &E::HandleInput) -> Perhaps { - self.write().unwrap().handle(context) - } -} - -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E::HandleInput) -> Perhaps { - self.write().unwrap().handle(context) - } -} diff --git a/crates/tek_core/src/engine/keymap.rs b/crates/tek_core/src/engine/keymap.rs deleted file mode 100644 index 816bb145..00000000 --- a/crates/tek_core/src/engine/keymap.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::*; - -pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; - -pub type KeyBinding = ( - KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler -); - -pub type KeyMap = [KeyBinding]; - -pub fn handle_keymap ( - state: &mut T, event: &TuiEvent, keymap: &KeyMap, -) -> Usually { - 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) - } -} - -#[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) - } - } -} diff --git a/crates/tek_core/src/engine/layout.rs b/crates/tek_core/src/engine/layout.rs deleted file mode 100644 index 1a906efe..00000000 --- a/crates/tek_core/src/engine/layout.rs +++ /dev/null @@ -1,359 +0,0 @@ -use crate::*; - -pub enum Collected<'a, E: Engine> { - Box(Box + 'a>), - Ref(&'a (dyn Widget + 'a)), -} - -impl<'a, E: Engine> Widget for Collected<'a, E> { - type Engine = E; - fn layout (&self, area: E::Area) -> Perhaps { - match self { - Self::Box(inner) => (*inner).layout(area), - Self::Ref(inner) => (*inner).layout(area), - } - } - fn render (&self, to: &mut E) -> Perhaps { - match self { - Self::Box(inner) => (*inner).render(to), - Self::Ref(inner) => (*inner).render(to), - } - } -} - -pub struct Collection<'a, E: Engine>( - pub Vec> -); - -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 + 'a>) -> Self; - fn add_ref (self, item: &'a dyn Widget) -> Self; - fn add + 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 + 'a>) -> Self { - self.0.push(Collected::Box(item)); - self - } - fn add_ref (mut self, item: &'a dyn Widget) -> Self { - self.0.push(Collected::Ref(item)); - self - } -} - -pub struct Split< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> ->(pub F, pub Direction, PhantomData); - -impl< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> -> Split { - #[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)->Usually<()>)->Usually<()> ->(pub F, PhantomData); - -impl< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> -> Layers { - #[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 { - Center(L), - NW(L), - N(L), - NE(L), - W(L), - E(L), - SW(L), - S(L), - SE(L) -} - -impl Align { - 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 { - /// Enforce minimum width - W(U, T), - /// Enforce minimum height - H(U, T), - /// Enforce minimum width and height - WH(U, U, T), -} - -impl Min { - fn inner (&self) -> &T { - match self { - Self::W(_, inner) => inner, - Self::H(_, inner) => inner, - Self::WH(_, _, inner) => inner, - } - } -} - -impl> Widget for Min { - type Engine = E; - fn layout (&self, area: E::Area) -> Perhaps { - 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 { - // 🡘 🡙 ←🡙→ - 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 { - /// Enforce maximum width - W(U, T), - /// Enforce maximum height - H(U, T), - /// Enforce maximum width and height - WH(U, U, T), -} - -impl Max { - fn inner (&self) -> &T { - match self { - Self::W(_, inner) => inner, - Self::H(_, inner) => inner, - Self::WH(_, _, inner) => inner, - } - } -} - -impl> Widget for Max { - type Engine = E; - fn layout (&self, area: E::Area) -> Perhaps { - 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 { - 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 { - /// Increase width - W(N, T), - /// Increase height - H(N, T), - /// Increase width and height - WH(N, N, T) -} - -impl Outset { - fn inner (&self) -> &T { - match self { - Self::W(_, inner) => inner, - Self::H(_, inner) => inner, - Self::WH(_, _, inner) => inner, - } - } -} - -impl> Widget for Outset { - type Engine = E; - fn layout (&self, area: E::Area) -> Perhaps { - 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 { - 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 { - /// Decrease width - W(N, T), - /// Decrease height - H(N, T), - /// Decrease width and height - WH(N, N, T), -} - -impl Inset { - fn inner (&self) -> &T { - match self { - Self::W(_, inner) => inner, - Self::H(_, inner) => inner, - Self::WH(_, _, inner) => inner, - } - } -} - -impl> Widget for Inset { - type Engine = E; - fn layout (&self, area: E::Area) -> Perhaps { - 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 { - 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 { - X(N, T), - Y(N, T), - XY(N, N, T), -} - -impl Offset { - fn inner (&self) -> &T { - match self { - Self::X(_, inner) => inner, - Self::Y(_, inner) => inner, - Self::XY(_, _, inner) => inner, - } - } -} - -impl> Widget for Offset { - type Engine = E; - fn layout (&self, area: E::Area) -> Perhaps { - 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 { - 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()) - } -}