mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-09 05:06:43 +01:00
wip: add TuiStyle, unifying Modal
This commit is contained in:
parent
7c555848e4
commit
29f11b5977
6 changed files with 467 additions and 434 deletions
|
|
@ -1,62 +1,59 @@
|
|||
use crate::*;
|
||||
|
||||
/// Entry point for main loop
|
||||
pub trait App<T: Engine> {
|
||||
fn run (self, context: T) -> Usually<T>;
|
||||
}
|
||||
|
||||
pub trait App<T: Engine> { fn run (self, context: T) -> Usually<T>; }
|
||||
/// Platform backend.
|
||||
pub trait Engine: Send + Sync + Sized {
|
||||
/// Input event type
|
||||
type Input: Input<Self>;
|
||||
/// Result of handling input
|
||||
type Handled;
|
||||
/// Render target
|
||||
type Output: Output<Self>;
|
||||
|
||||
/// Unit of length
|
||||
type Unit: Number;
|
||||
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug + Copy;
|
||||
/// Rectangle without offset
|
||||
type Size: Size<Self::Unit> + From<[Self::Unit;2]> + Debug + Copy;
|
||||
|
||||
/// Rectangle with offset
|
||||
type Area: Area<Self::Unit> + 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<E: Engine> {
|
||||
/// 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<E: Engine> {
|
||||
fn area (&self)
|
||||
-> E::Area;
|
||||
fn area_mut (&mut self)
|
||||
-> &mut E::Area;
|
||||
fn render_in (&mut self, area: E::Area, widget: &dyn Widget<Engine = E>)
|
||||
-> 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<Engine = E>) -> Usually<()>;
|
||||
}
|
||||
|
||||
/// A renderable component
|
||||
pub trait Widget: Send + Sync {
|
||||
/// Engine for which this component is implemented
|
||||
type Engine: Engine;
|
||||
fn layout (&self,
|
||||
to: <Self::Engine as Engine>::Size
|
||||
) -> Perhaps<<Self::Engine as Engine>::Size> {
|
||||
/// Minimum size to use
|
||||
fn layout (&self, to: <Self::Engine as Engine>::Size)
|
||||
-> Perhaps<<Self::Engine as Engine>::Size>
|
||||
{
|
||||
Ok(Some(to))
|
||||
}
|
||||
fn render (&self,
|
||||
to: &mut <Self::Engine as Engine>::Output
|
||||
) -> Usually<()>;
|
||||
}
|
||||
impl<'a, E: Engine> Widget for Box<dyn Widget<Engine = E> + 'a> {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
(**self).layout(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
(**self).render(to)
|
||||
}
|
||||
/// Draw to output render target
|
||||
fn render (&self,to: &mut <Self::Engine as Engine>::Output) -> Usually<()>;
|
||||
}
|
||||
impl<E: Engine> Widget for &dyn Widget<Engine = E> {
|
||||
type Engine = E;
|
||||
|
|
@ -76,6 +73,15 @@ impl<E: Engine> Widget for &mut dyn Widget<Engine = E> {
|
|||
(**self).render(to)
|
||||
}
|
||||
}
|
||||
impl<'a, E: Engine> Widget for Box<dyn Widget<Engine = E> + 'a> {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
(**self).layout(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
(**self).render(to)
|
||||
}
|
||||
}
|
||||
impl<E: Engine, W: Widget<Engine = E>> Widget for Arc<W> {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
|
|
@ -112,16 +118,12 @@ impl<E: Engine, W: Widget<Engine = E>> Widget for Option<W> {
|
|||
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 = <Self as Content>::Engine>;
|
||||
}
|
||||
//impl<E> Content<E> for () where E: Engine {
|
||||
//fn content (&self) -> impl Widget<E> {
|
||||
//()
|
||||
//}
|
||||
//}
|
||||
/// Every component is renderable
|
||||
impl<E: Engine, W: Content<Engine = E>> Widget for W {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
|
|
@ -134,41 +136,16 @@ impl<E: Engine, W: Content<Engine = E>> Widget for W {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A UI component.
|
||||
pub trait Component<E: Engine>: Widget<Engine = E> + Handle<E> {}
|
||||
|
||||
/// Everything that implements [Render] and [Handle] is a [Component].
|
||||
impl<E: Engine, C: Widget<Engine = E> + Handle<E>> Component<E> for C {}
|
||||
|
||||
pub trait Exit: Send {
|
||||
fn exited (&self) -> bool;
|
||||
fn exit (&mut self);
|
||||
fn boxed (self) -> Box<dyn Exit> where Self: Sized + 'static {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker trait for [Component]s that can [Exit]
|
||||
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
|
||||
/// Perform type erasure for collecting heterogeneous components.
|
||||
fn boxed (self) -> Box<dyn ExitableComponent<E>> where Self: Sized + 'static {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
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::Input) -> Perhaps<E::Handled>;
|
||||
}
|
||||
impl<H, E: Engine> Handle<E> for &mut H where H: Handle<E> {
|
||||
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
(*self).handle(context)
|
||||
}
|
||||
}
|
||||
impl<H, E: Engine> Handle<E> for Option<H> where H: Handle<E> {
|
||||
impl<E: Engine, H: Handle<E>> Handle<E> for Option<H> {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
if let Some(ref mut handle) = self {
|
||||
handle.handle(context)
|
||||
|
|
@ -197,90 +174,25 @@ impl<H, E: Engine> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
|
|||
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>]
|
||||
/// A UI component that can render itself and handle input
|
||||
pub trait Component<E: Engine>: Widget<Engine = E> + Handle<E> {}
|
||||
/// Everything that implements [Render] and [Handle] is a [Component].
|
||||
impl<E: Engine, C: Widget<Engine = E> + Handle<E>> Component<E> for C {}
|
||||
pub trait Exit: Send {
|
||||
fn exited (&self) -> bool;
|
||||
fn exit (&mut self);
|
||||
fn boxed (self) -> Box<dyn Exit> 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<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)
|
||||
}
|
||||
/// Marker trait for [Component]s that can [Exit]
|
||||
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
|
||||
/// Perform type erasure for collecting heterogeneous components.
|
||||
fn boxed (self) -> Box<dyn ExitableComponent<E>> where Self: Sized + 'static {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}
|
||||
|
||||
/// A component that may contain [Focusable] components.
|
||||
pub trait Focus <const N: usize, E: Engine>: Widget<Engine = E> + Handle<E> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue