diff --git a/README.md b/README.md index a6829469..3948400e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ > > (Especially if you know how to host LV2 plugin UIs in `winit` 😁) > -> - the author ❤️ +> - the author # tek diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs index 5e581aa8..5cee6900 100644 --- a/crates/tek_core/src/engine.rs +++ b/crates/tek_core/src/engine.rs @@ -1,62 +1,59 @@ use crate::*; - /// Entry point for main loop -pub trait App { - fn run (self, context: T) -> Usually; -} - +pub trait App { fn run (self, context: T) -> Usually; } /// Platform backend. pub trait Engine: Send + Sync + Sized { + /// Input event type type Input: Input; + /// Result of handling input type Handled; + /// Render target type Output: Output; - + /// Unit of length type Unit: Number; - type Area: Area + From<[Self::Unit;4]> + Debug + Copy; + /// Rectangle without offset type Size: Size + From<[Self::Unit;2]> + Debug + Copy; - + /// Rectangle with offset + type Area: Area + From<[Self::Unit;4]> + Debug + Copy; + /// Prepare before run fn setup (&mut self) -> Usually<()> { Ok(()) } + /// True if done fn exited (&self) -> bool; + /// Clean up after run fn teardown (&mut self) -> Usually<()> { Ok(()) } } - +/// Current input state pub trait Input { + /// Type of input event type Event; - fn event (&self) - -> &Self::Event; - fn is_done (&self) - -> bool; + /// Currently handled event + fn event (&self) -> &Self::Event; + /// Whether component should exit + fn is_done (&self) -> bool; + /// Mark component as done fn done (&self); } - +/// Rendering target pub trait Output { - fn area (&self) - -> E::Area; - fn area_mut (&mut self) - -> &mut E::Area; - fn render_in (&mut self, area: E::Area, widget: &dyn Widget) - -> Usually<()>; + /// Current output area + fn area (&self) -> E::Area; + /// Mutable pointer to area + fn area_mut (&mut self) -> &mut E::Area; + /// Render widget in area + fn render_in (&mut self, area: E::Area, widget: &dyn Widget) -> Usually<()>; } - +/// A renderable component pub trait Widget: Send + Sync { + /// Engine for which this component is implemented type Engine: Engine; - fn layout (&self, - to: ::Size - ) -> Perhaps<::Size> { + /// Minimum size to use + fn layout (&self, to: ::Size) + -> Perhaps<::Size> + { Ok(Some(to)) } - fn render (&self, - to: &mut ::Output - ) -> Usually<()>; -} -impl<'a, E: Engine> Widget for Box + 'a> { - type Engine = E; - fn layout (&self, to: E::Size) -> Perhaps { - (**self).layout(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - (**self).render(to) - } + /// Draw to output render target + fn render (&self,to: &mut ::Output) -> Usually<()>; } impl Widget for &dyn Widget { type Engine = E; @@ -76,6 +73,15 @@ impl Widget for &mut dyn Widget { (**self).render(to) } } +impl<'a, E: Engine> Widget for Box + 'a> { + type Engine = E; + fn layout (&self, to: E::Size) -> Perhaps { + (**self).layout(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + (**self).render(to) + } +} impl> Widget for Arc { type Engine = E; fn layout (&self, to: E::Size) -> Perhaps { @@ -112,16 +118,12 @@ impl> Widget for Option { self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(())) } } - +/// A component that consists of other components pub trait Content: Send + Sync { type Engine: Engine; fn content (&self) -> impl Widget::Engine>; } -//impl Content for () where E: Engine { - //fn content (&self) -> impl Widget { - //() - //} -//} +/// Every component is renderable impl> Widget for W { type Engine = E; fn layout (&self, to: E::Size) -> Perhaps { @@ -134,41 +136,16 @@ impl> Widget for W { } } } - -/// A UI component. -pub trait Component: Widget + Handle {} - -/// Everything that implements [Render] and [Handle] is a [Component]. -impl + Handle> Component for C {} - -pub trait Exit: Send { - fn exited (&self) -> bool; - fn exit (&mut self); - fn boxed (self) -> Box where Self: Sized + 'static { - Box::new(self) - } -} - -/// Marker trait for [Component]s that can [Exit] -pub trait ExitableComponent: Exit + Component where E: Engine { - /// Perform type erasure for collecting heterogeneous components. - fn boxed (self) -> Box> where Self: Sized + 'static { - Box::new(self) - } -} - -impl + Exit> ExitableComponent for C {} - /// Handle input pub trait Handle: Send + Sync { fn handle (&mut self, context: &E::Input) -> Perhaps; } -impl Handle for &mut H where H: Handle { +impl> Handle for &mut H { fn handle (&mut self, context: &E::Input) -> Perhaps { (*self).handle(context) } } -impl Handle for Option where H: Handle { +impl> Handle for Option { fn handle (&mut self, context: &E::Input) -> Perhaps { if let Some(ref mut handle) = self { handle.handle(context) @@ -197,90 +174,25 @@ impl Handle for Arc> where H: Handle { 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>] +/// A UI component that can render itself and handle input +pub trait Component: Widget + Handle {} +/// Everything that implements [Render] and [Handle] is a [Component]. +impl + Handle> Component for C {} +pub trait Exit: Send { + fn exited (&self) -> bool; + fn exit (&mut self); + fn boxed (self) -> Box where Self: Sized + 'static { + Box::new(self) } } - -#[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) - } +/// Marker trait for [Component]s that can [Exit] +pub trait ExitableComponent: Exit + Component where E: Engine { + /// Perform type erasure for collecting heterogeneous components. + fn boxed (self) -> Box> where Self: Sized + 'static { + Box::new(self) } } +impl + Exit> ExitableComponent for C {} /// A component that may contain [Focusable] components. pub trait Focus : Widget + Handle { diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index bd2700df..51e8f4ab 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -108,26 +108,151 @@ impl Area for [N;4] { #[inline] fn h (&self) -> N { self[3] } } -pub struct Split< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> ->(pub F, pub Direction, PhantomData); +pub trait Layout: Widget + Sized { + fn align_center (self) -> Align { + Align::Center(self) + } + fn align_x (self) -> Align { + Align::X(self) + } + fn align_y (self) -> Align { + Align::Y(self) + } + fn fixed_x (self, x: E::Unit) -> Fixed { + Fixed::X(x, self) + } + fn fixed_y (self, y: E::Unit) -> Fixed { + Fixed::Y(y, self) + } + fn fixed_xy (self, x: E::Unit, y: E::Unit) -> Fixed { + Fixed::XY(x, y, self) + } + fn min_x (self, x: E::Unit) -> Min { + Min::X(x, self) + } + fn min_y (self, y: E::Unit) -> Min { + Min::Y(y, self) + } + fn min_xy (self, x: E::Unit, y: E::Unit) -> Min { + Min::XY(x, y, self) + } + fn max_x (self, x: E::Unit) -> Max { + Max::X(x, self) + } + fn max_y (self, y: E::Unit) -> Max { + Max::Y(y, self) + } + fn max_xy (self, x: E::Unit, y: E::Unit) -> Max { + Max::XY(x, y, self) + } + fn push_x (self, x: E::Unit) -> Push { + Push::X(x, self) + } + fn push_y (self, y: E::Unit) -> Push { + Push::Y(y, self) + } + fn push_xy (self, x: E::Unit, y: E::Unit) -> Push { + Push::XY(x, y, self) + } + fn pull_x (self, x: E::Unit) -> Pull { + Pull::X(x, self) + } + fn pull_y (self, y: E::Unit) -> Pull { + Pull::Y(y, self) + } + fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull { + Pull::XY(x, y, self) + } + fn grow_x (self, x: E::Unit) -> Grow { + Grow::X(x, self) + } + fn grow_y (self, y: E::Unit) -> Grow { + Grow::Y(y, self) + } + fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow { + Grow::XY(x, y, self) + } + fn shrink_x (self, x: E::Unit) -> Shrink { + Shrink::X(x, self) + } + fn shrink_y (self, y: E::Unit) -> Shrink { + Shrink::Y(y, self) + } + fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink { + Shrink::XY(x, y, self) + } + fn inset_x (self, x: E::Unit) -> Inset { + Inset::X(x, self) + } + fn inset_y (self, y: E::Unit) -> Inset { + Inset::Y(y, self) + } + fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset { + Inset::XY(x, y, self) + } + fn outset_x (self, x: E::Unit) -> Outset { + Outset::X(x, self) + } + fn outset_y (self, y: E::Unit) -> Outset { + Outset::Y(y, self) + } + fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset { + Outset::XY(x, y, self) + } + fn fill_x (self) -> Fill { + Fill::X(self) + } + fn fill_y (self) -> Fill { + Fill::Y(self) + } + fn fill_xy (self) -> Fill { + Fill::XY(self) + } + fn debug (self) -> DebugOverlay { + DebugOverlay(self) + } +} -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()) +impl> Layout for W {} + +pub struct DebugOverlay>(pub W); + +pub struct ModalHost, W: Widget>( + pub bool, pub M, pub W +); + +pub enum Fill> { + X(W), + Y(W), + XY(W) +} + +impl> Fill { + fn inner (&self) -> &W { + match self { + Self::X(inner) => &inner, + Self::Y(inner) => &inner, + Self::XY(inner) => &inner, + } } - #[inline] - pub fn right (build: F) -> Self { - Self::new(Direction::Right, build) +} + +impl> Widget for Fill { + type Engine = E; + fn layout (&self, to: E::Size) -> Perhaps { + let area = self.inner().layout(to.into())?; + if let Some(area) = area { + Ok(Some(match self { + Self::X(_) => [to.w().into(), area.h()], + Self::Y(_) => [area.w(), to.h().into()], + Self::XY(_) => [to.w().into(), to.h().into()], + }.into())) + } else { + Ok(None) + } } - #[inline] - pub fn down (build: F) -> Self { - Self::new(Direction::Down, build) + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.inner().render(to) } } @@ -172,17 +297,6 @@ where } } -//pub fn collect <'a, E: Engine, const N: usize> ( - //items: &'a [&'a dyn Widget;N] -//) -> impl Send + Sync + Fn(&'a mut dyn FnMut(&'a dyn Widget)->Usually<()>)->Usually<()> + '_ { - //|add: &'a mut dyn FnMut(&'a dyn Widget)->Usually<()>| { - //for item in items.iter() { - //add(item)?; - //} - //Ok(()) - //} -//} - //`Layers<_, impl (Fn(&mut dyn FnMut(&dyn Widget) -> Result<(), Box<...>>) -> ... ) + Send + Sync>` //`Layers) -> Result<(), Box<(dyn std::error::Error + 'static)>>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) + Send + Sync + '_>` @@ -446,7 +560,7 @@ impl> Widget for Shrink { ], Self::Y(h, _) => [ to.w(), - if to.h() > h { to.h() - h } else { 0.into() } +if to.h() > h { to.h() - h } else { 0.into() } ], Self::XY(w, h, _) => [ if to.w() > w { to.w() - w } else { 0.into() }, @@ -605,6 +719,26 @@ impl> Widget for Pull { } } +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) + } +} + impl Widget for Split where F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget)->Usually<()>)->Usually<()> @@ -693,3 +827,13 @@ where Ok(()) } } + +#[macro_export] macro_rules! lay { + ($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) } +} +#[macro_export] macro_rules! col { + ($($expr:expr),* $(,)?) => { Split::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) } +} +#[macro_export] macro_rules! row { + ($($expr:expr),* $(,)?) => { Split::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) } +} diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index ee566963..d2496b36 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -144,6 +144,83 @@ impl Input for TuiInput { self.exited.store(true, Ordering::Relaxed); } } +impl TuiInput { + pub fn handle_keymap (&self, state: &mut T, keymap: &KeyMap) -> Usually { + match self.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) + } +} +pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; +pub type KeyBinding = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler); +pub type KeyMap = [KeyBinding]; +/// 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>] + } +} +/// Define a key in a keymap +#[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) + } +} +/// Shorthand for key match statement +#[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) + } + } +} +/// Define key pattern in key match statement +#[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 + })) + } +} pub struct TuiOutput { pub buffer: Buffer, pub area: [u16;4], @@ -291,8 +368,19 @@ impl Widget for &str { } } -pub struct Styled>(pub Option