mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
708 lines
21 KiB
Rust
708 lines
21 KiB
Rust
use crate::*;
|
|
|
|
/// Entry point for main loop
|
|
pub trait App<T: Engine> {
|
|
fn run (self, context: T) -> Usually<T>;
|
|
}
|
|
|
|
/// Platform backend.
|
|
pub trait Engine: Send + Sync + Sized {
|
|
fn setup (&mut self) -> Usually<()> { Ok(()) }
|
|
fn exited (&self) -> bool;
|
|
fn teardown (&mut self) -> Usually<()> { Ok(()) }
|
|
|
|
/// Unit of distance.
|
|
type Unit: Number;
|
|
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug;
|
|
|
|
type HandleInput;
|
|
type Handled;
|
|
|
|
// FIXME
|
|
fn area (&self) -> Self::Area;
|
|
// FIXME
|
|
fn with_area (&mut self, x: Self::Unit, y: Self::Unit, w: Self::Unit, h: Self::Unit)
|
|
-> &mut Self;
|
|
// FIXME
|
|
fn render_in (
|
|
&mut self, area: Self::Area, widget: &impl Widget<Engine = Self>
|
|
) -> Perhaps<Self::Area>;
|
|
}
|
|
|
|
pub trait Widget: Send + Sync {
|
|
type Engine: Engine;
|
|
fn layout (&self, to: <<Self as Widget>::Engine as Engine>::Area) ->
|
|
Perhaps<<<Self as Widget>::Engine as Engine>::Area>
|
|
{
|
|
Ok(Some(to))
|
|
}
|
|
fn render (&self, to: &mut Self::Engine) ->
|
|
Perhaps<<<Self as Widget>::Engine as Engine>::Area>;
|
|
}
|
|
impl<'a, E: Engine> Widget for Box<dyn Widget<Engine = E> + 'a> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
(**self).layout(to)
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
(**self).render(to)
|
|
}
|
|
}
|
|
impl<E: Engine> Widget for &dyn Widget<Engine = E> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
(*self).layout(to)
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
(*self).render(to)
|
|
}
|
|
}
|
|
impl<E: Engine> Widget for &mut dyn Widget<Engine = E> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
(**self).layout(to)
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
(**self).render(to)
|
|
}
|
|
}
|
|
impl<E: Engine, W: Widget<Engine = E>> Widget for Arc<W> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
self.as_ref().layout(to)
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
self.as_ref().render(to)
|
|
}
|
|
}
|
|
impl<E: Engine, W: Widget<Engine = E>> Widget for Mutex<W> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
self.lock().unwrap().layout(to)
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
self.lock().unwrap().render(to)
|
|
}
|
|
}
|
|
impl<E: Engine, W: Widget<Engine = E>> Widget for RwLock<W> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
self.read().unwrap().layout(to)
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
self.read().unwrap().render(to)
|
|
}
|
|
}
|
|
impl<E: Engine, W: Widget<Engine = E>> Widget for Option<W> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
Ok(self.as_ref().map(|widget|widget.layout(to)).transpose()?.flatten())
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
Ok(self.as_ref().map(|widget|widget.render(to)).transpose()?.flatten())
|
|
}
|
|
}
|
|
|
|
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> {
|
|
//()
|
|
//}
|
|
//}
|
|
impl<E: Engine, W: Content<Engine = E>> Widget for W {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
self.content().layout(to)
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
match self.layout(to.area())? {
|
|
Some(area) => to.render_in(area, &self.content()),
|
|
None => Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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::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 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())
|
|
}
|
|
}
|
|
|
|
//pub fn collect <'a, E: Engine, const N: usize> (
|
|
//items: &'a [&'a dyn Widget<Engine = E>;N]
|
|
//) -> impl Send + Sync + Fn(&'a mut dyn FnMut(&'a dyn Widget<Engine = E>)->Usually<()>)->Usually<()> + '_ {
|
|
//|add: &'a mut dyn FnMut(&'a dyn Widget<Engine = E>)->Usually<()>| {
|
|
//for item in items.iter() {
|
|
//add(item)?;
|
|
//}
|
|
//Ok(())
|
|
//}
|
|
//}
|
|
|
|
//`Layers<_, impl (Fn(&mut dyn FnMut(&dyn Widget<Engine = _>) -> Result<(), Box<...>>) -> ... ) + Send + Sync>`
|
|
//`Layers<Tui, impl (Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) + Send + Sync + '_>`
|
|
|
|
#[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> {
|
|
/// Draw at center of container
|
|
Center(L),
|
|
/// Draw at upper left corner of contaier
|
|
NW(L),
|
|
/// Draw at center of upper edge of container
|
|
N(L),
|
|
/// Draw at right left corner of contaier
|
|
NE(L),
|
|
/// Draw at center of left edge of container
|
|
W(L),
|
|
/// Draw at center of right edge of container
|
|
E(L),
|
|
/// Draw at lower left corner of container
|
|
SW(L),
|
|
/// Draw at center of lower edge of container
|
|
S(L),
|
|
/// Draw at lower right edge of container
|
|
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 fixed size of drawing area
|
|
pub enum Fixed<U: Number, T> {
|
|
/// Enforce fixed width
|
|
X(U, T),
|
|
/// Enforce fixed height
|
|
Y(U, T),
|
|
/// Enforce fixed width and height
|
|
XY(U, U, T),
|
|
}
|
|
|
|
impl<N: Number, T> Fixed<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
/// Enforce minimum size of drawing area
|
|
pub enum Min<U: Number, T> {
|
|
/// Enforce minimum width
|
|
X(U, T),
|
|
/// Enforce minimum height
|
|
Y(U, T),
|
|
/// Enforce minimum width and height
|
|
XY(U, U, T),
|
|
}
|
|
impl<N: Number, T> Min<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Min<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [to.x(), to.y(), to.w().max(w), to.h()],
|
|
Self::Y(h, _) => [to.x(), to.y(), to.w(), to.h().max(h)],
|
|
Self::XY(w, h, _) => [to.x(), to.y(), to.w().max(w), to.h().max(h)],
|
|
}.into()))
|
|
}
|
|
// TODO: 🡘 🡙 ←🡙→
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
Ok(self.layout(to.area())?.map(|a|to.render_in(a, self.inner())).transpose()?.flatten())
|
|
}
|
|
}
|
|
|
|
/// Enforce maximum size of drawing area
|
|
pub enum Max<U: Number, T> {
|
|
/// Enforce maximum width
|
|
X(U, T),
|
|
/// Enforce maximum height
|
|
Y(U, T),
|
|
/// Enforce maximum width and height
|
|
XY(U, U, T),
|
|
}
|
|
|
|
impl<N: Number, T> Max<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Max<E:: Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [to.x(), to.y(), to.w().min(w), to.h()],
|
|
Self::Y(h, _) => [to.x(), to.y(), to.w(), to.h().min(h)],
|
|
Self::XY(w, h, _) => [to.x(), to.y(), to.w().min(w), to.h().min(h)],
|
|
}.into()))
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
Ok(self.layout(to.area())?.map(|a|to.render_in(a, self.inner())).transpose()?.flatten())
|
|
}
|
|
}
|
|
|
|
/// Expand drawing area
|
|
pub enum Grow<N: Number, T> {
|
|
/// Increase width
|
|
X(N, T),
|
|
/// Increase height
|
|
Y(N, T),
|
|
/// Increase width and height
|
|
XY(N, N, T)
|
|
}
|
|
|
|
impl<N: Number, T> Grow<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Grow<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [to.x(), to.y(), to.w() + w, to.h()],
|
|
Self::Y(h, _) => [to.x(), to.y(), to.w(), to.h() + h],
|
|
Self::XY(w, h, _) => [to.x(), to.y(), to.w() + w, to.h() + h],
|
|
}.into()))
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
Ok(self.layout(to.area())?.map(|a|to.render_in(a, self.inner())).transpose()?.flatten())
|
|
}
|
|
}
|
|
|
|
/// Shrink drawing area
|
|
pub enum Shrink<N: Number, T> {
|
|
/// Decrease width
|
|
X(N, T),
|
|
/// Decrease height
|
|
Y(N, T),
|
|
/// Decrease width and height
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Number, T: Widget> Shrink<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Shrink<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [to.x(), to.y(), to.w() - w, to.h()],
|
|
Self::Y(h, _) => [to.x(), to.y(), to.w(), to.h() - h],
|
|
Self::XY(w, h, _) => [to.x(), to.y(), to.w() - w, to.h() - h]
|
|
}.into()))
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
Ok(self.layout(to.area())?.map(|a|to.render_in(a, self.inner())).transpose()?.flatten())
|
|
}
|
|
}
|
|
|
|
/// Shrink from each side
|
|
pub enum Inset<N: Number, T> {
|
|
/// Decrease width
|
|
X(N, T),
|
|
/// Decrease height
|
|
Y(N, T),
|
|
/// Decrease width and height
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Number, T: Widget> Inset<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Content for Inset<E::Unit, T> {
|
|
type Engine = E;
|
|
fn content (&self) -> impl Widget<Engine = E> {
|
|
Align::Center(match *self {
|
|
Self::X(x, inner) => Shrink::X(x + x, inner),
|
|
Self::Y(y, inner) => Shrink::X(y + y, inner),
|
|
Self::XY(x, y, inner) => Shrink::XY(x, y, inner),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Grow on each side
|
|
pub enum Outset<N: Number, T> {
|
|
/// Increase width
|
|
X(N, T),
|
|
/// Increase height
|
|
Y(N, T),
|
|
/// Increase width and height
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Number, T: Widget> Outset<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Content for Outset<E::Unit, T> {
|
|
type Engine = E;
|
|
fn content (&self) -> impl Widget<Engine = E> {
|
|
Align::Center(match self {
|
|
Self::X(x, inner) => Grow::X(*x + *x, inner),
|
|
Self::Y(y, inner) => Grow::X(*y + *y, inner),
|
|
Self::XY(x, y, inner) => Grow::XY(*x, *y, inner),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Move origin point of drawing area
|
|
pub enum Offset<N: Number, T: Widget> {
|
|
/// Move origin to the right
|
|
X(N, T),
|
|
/// Move origin downwards
|
|
Y(N, T),
|
|
/// Move origin to the right and downwards
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Number, T: Widget> Offset<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
fn x (&self) -> N {
|
|
match self { Self::X(x, _) => *x, Self::Y(_, _) => N::default(), Self::XY(x, _, _) => *x }
|
|
}
|
|
fn y (&self) -> N {
|
|
match self { Self::X(_, _) => N::default(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Offset<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(x, _) => [to.x() + x, to.y(), to.w(), to.h()],
|
|
Self::Y(y, _) => [to.x(), to.y() + y, to.w(), to.h()],
|
|
Self::XY(x, y, _) => [to.x() + x, to.y() + y, to.w(), to.h()]
|
|
}.into()))
|
|
}
|
|
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
|
|
Ok(self.layout(to.area())?.map(|a|to.render_in(a, self.inner())).transpose()?.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
|
|
}
|
|
}
|
|
}
|
|
}
|