mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-01-31 16:36:40 +01:00
wip: extract tek_layout
This commit is contained in:
parent
ddb3c28c01
commit
2265d951d1
36 changed files with 2010 additions and 1780 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -2721,6 +2721,13 @@ dependencies = [
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tek_layout"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"tek_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_tui"
|
name = "tek_tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -2732,6 +2739,7 @@ dependencies = [
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"tek_api",
|
"tek_api",
|
||||||
"tek_core",
|
"tek_core",
|
||||||
|
"tek_layout",
|
||||||
"vst",
|
"vst",
|
||||||
"wavers",
|
"wavers",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@ members = [
|
||||||
"crates/tek_core",
|
"crates/tek_core",
|
||||||
"crates/tek_api",
|
"crates/tek_api",
|
||||||
"crates/tek_tui",
|
"crates/tek_tui",
|
||||||
"crates/tek_cli"
|
"crates/tek_cli",
|
||||||
|
"crates/tek_layout"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -32,21 +32,21 @@ impl<'a, E: Engine, const N: usize> From<ArrayCollection<'a, E, N>> for Collect<
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackCollection<'a, E> =
|
type CallbackCollection<'a, E> =
|
||||||
&'a dyn Fn(&'a mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>);
|
&'a dyn Fn(&'a mut dyn FnMut(&dyn Render<E>)->Usually<()>);
|
||||||
|
|
||||||
//type IteratorCollection<'a, E> =
|
//type IteratorCollection<'a, E> =
|
||||||
//&'a mut dyn Iterator<Item = dyn Render<Engine = E>>;
|
//&'a mut dyn Iterator<Item = dyn Render<E>>;
|
||||||
|
|
||||||
type SliceCollection<'a, E> =
|
type SliceCollection<'a, E> =
|
||||||
&'a [&'a dyn Render<Engine = E>];
|
&'a [&'a dyn Render<E>];
|
||||||
|
|
||||||
type ArrayCollection<'a, E, const N: usize> =
|
type ArrayCollection<'a, E, const N: usize> =
|
||||||
[&'a dyn Render<Engine = E>; N];
|
[&'a dyn Render<E>; N];
|
||||||
|
|
||||||
pub struct CollectIterator<'a, E: Engine, const N: usize>(usize, &'a Collect<'a, E, N>);
|
pub struct CollectIterator<'a, E: Engine, const N: usize>(usize, &'a Collect<'a, E, N>);
|
||||||
|
|
||||||
impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> {
|
impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> {
|
||||||
type Item = &'a dyn Render<Engine = E>;
|
type Item = &'a dyn Render<E>;
|
||||||
fn next (&mut self) -> Option<Self::Item> {
|
fn next (&mut self) -> Option<Self::Item> {
|
||||||
match self.1 {
|
match self.1 {
|
||||||
Collect::Callback(callback) => {
|
Collect::Callback(callback) => {
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Entry point for main loop
|
/// 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.
|
/// Platform backend.
|
||||||
pub trait Engine: Send + Sync + Sized {
|
pub trait Engine: Send + Sync + Sized {
|
||||||
/// Input event type
|
/// Input event type
|
||||||
|
|
@ -22,213 +26,13 @@ pub trait Engine: Send + Sync + Sized {
|
||||||
/// Clean up after run
|
/// Clean up after run
|
||||||
fn teardown (&mut self) -> Usually<()> { Ok(()) }
|
fn teardown (&mut self) -> Usually<()> { Ok(()) }
|
||||||
}
|
}
|
||||||
/// Current input state
|
|
||||||
pub trait Input<E: Engine> {
|
|
||||||
/// Type of input event
|
|
||||||
type Event;
|
|
||||||
/// 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> {
|
|
||||||
/// 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 Render<Engine = E>) -> Usually<()>;
|
|
||||||
}
|
|
||||||
/// Cast to dynamic pointer
|
|
||||||
pub fn widget <E: Engine, T: Render<Engine = E>> (w: &T) -> &dyn Render<Engine = E> {
|
|
||||||
w as &dyn Render<Engine = E>
|
|
||||||
}
|
|
||||||
/// A renderable component
|
|
||||||
pub trait Render: Send + Sync {
|
|
||||||
/// Engine for which this component is implemented
|
|
||||||
type Engine: Engine;
|
|
||||||
/// Minimum size to use
|
|
||||||
fn min_size (&self, to: <Self::Engine as Engine>::Size)
|
|
||||||
-> Perhaps<<Self::Engine as Engine>::Size>
|
|
||||||
{
|
|
||||||
Ok(Some(to))
|
|
||||||
}
|
|
||||||
/// Draw to output render target
|
|
||||||
fn render (&self,to: &mut <Self::Engine as Engine>::Output) -> Usually<()>;
|
|
||||||
}
|
|
||||||
impl<E: Engine> Render for &dyn Render<Engine = E> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
(*self).min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
(*self).render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<E: Engine> Render for &mut dyn Render<Engine = E> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
(**self).min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
(**self).render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, E: Engine> Render for Box<dyn Render<Engine = E> + 'a> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
(**self).min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
(**self).render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<E: Engine, W: Render<Engine = E>> Render for Arc<W> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.as_ref().min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
self.as_ref().render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<E: Engine, W: Render<Engine = E>> Render for Mutex<W> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.lock().unwrap().min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
self.lock().unwrap().render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<E: Engine, W: Render<Engine = E>> Render for RwLock<W> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.read().unwrap().min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
self.read().unwrap().render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<E: Engine, W: Render<Engine = E>> Render for Option<W> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten())
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Render either of two widgets depending on predicate
|
|
||||||
pub struct Either<E: Engine, A: Render<Engine = E>, B: Render<Engine = E>>(
|
|
||||||
pub bool,
|
|
||||||
pub A,
|
|
||||||
pub B,
|
|
||||||
);
|
|
||||||
impl<E: Engine, A: Render<Engine = E>, B: Render<Engine = E>> Render for Either<E, A, B> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
if self.0 { self.1.min_size(to) } else { self.2.min_size(to) }
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
if self.0 { self.1.render(to) } else { self.2.render(to) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A custom [Render] defined by passing layout and render closures in place.
|
|
||||||
pub struct Widget<
|
|
||||||
E: Engine,
|
|
||||||
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
|
||||||
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
|
||||||
>(L, R, PhantomData<E>);
|
|
||||||
impl<
|
|
||||||
E: Engine,
|
|
||||||
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
|
||||||
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
|
||||||
> Widget<E, L, R> {
|
|
||||||
pub fn new (layout: L, render: R) -> Self {
|
|
||||||
Self(layout, render, Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<
|
|
||||||
E: Engine,
|
|
||||||
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
|
||||||
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
|
||||||
> Render for Widget<E, L, R> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.0(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
self.1(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A [Render] that contains other [Render]s
|
|
||||||
pub trait Content: Send + Sync {
|
|
||||||
type Engine: Engine;
|
|
||||||
fn content (&self) -> impl Render<Engine = <Self as Content>::Engine>;
|
|
||||||
}
|
|
||||||
/// Every struct that has [Content] is a renderable [Render].
|
|
||||||
impl<E: Engine, W: Content<Engine = E>> Render for W {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.content().min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
match self.min_size(to.area().wh().into())? {
|
|
||||||
Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()),
|
|
||||||
None => Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Handle input
|
|
||||||
pub trait Handle<E: Engine>: Send + Sync {
|
|
||||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
|
|
||||||
}
|
|
||||||
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<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)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<H, E: Engine> Handle<E> for Mutex<H> where H: Handle<E> {
|
|
||||||
fn handle (&mut self, context: &E::Input) -> 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::Input) -> 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::Input) -> 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::Input) -> Perhaps<E::Handled> {
|
|
||||||
self.write().unwrap().handle(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A UI component that can render itself as a [Render], and [Handle] input.
|
/// A UI component that can render itself as a [Render], and [Handle] input.
|
||||||
pub trait Component<E: Engine>: Render<Engine = E> + Handle<E> {}
|
pub trait Component<E: Engine>: Render<E> + Handle<E> {}
|
||||||
|
|
||||||
/// Everything that implements [Render] and [Handle] is a [Component].
|
/// Everything that implements [Render] and [Handle] is a [Component].
|
||||||
impl<E: Engine, C: Render<Engine = E> + Handle<E>> Component<E> for C {}
|
impl<E: Engine, C: Render<E> + Handle<E>> Component<E> for C {}
|
||||||
/// A UI component that has [Content] and can [Handle] input.
|
|
||||||
pub trait ContentComponent<E: Engine>: Render<Engine = E> + Handle<E> {}
|
|
||||||
/// Everything that implements [Content] and [Handle] is a [Component].
|
|
||||||
impl<E: Engine, C: Content<Engine = E> + Handle<E>> ContentComponent<E> for C {}
|
|
||||||
/// A component that can exit.
|
/// A component that can exit.
|
||||||
pub trait Exit: Send {
|
pub trait Exit: Send {
|
||||||
fn exited (&self) -> bool;
|
fn exited (&self) -> bool;
|
||||||
|
|
@ -237,6 +41,7 @@ pub trait Exit: Send {
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marker trait for [Component]s that can [Exit].
|
/// Marker trait for [Component]s that can [Exit].
|
||||||
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
|
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
|
||||||
/// Perform type erasure for collecting heterogeneous components.
|
/// Perform type erasure for collecting heterogeneous components.
|
||||||
|
|
@ -244,5 +49,6 @@ pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All [Components]s that implement [Exit] implement [ExitableComponent].
|
/// All [Components]s that implement [Exit] implement [ExitableComponent].
|
||||||
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}
|
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}
|
||||||
|
|
|
||||||
58
crates/tek_core/src/input.rs
Normal file
58
crates/tek_core/src/input.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Current input state
|
||||||
|
pub trait Input<E: Engine> {
|
||||||
|
/// Type of input event
|
||||||
|
type Event;
|
||||||
|
/// Currently handled event
|
||||||
|
fn event (&self) -> &Self::Event;
|
||||||
|
/// Whether component should exit
|
||||||
|
fn is_done (&self) -> bool;
|
||||||
|
/// Mark component as done
|
||||||
|
fn done (&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle input
|
||||||
|
pub trait Handle<E: Engine>: Send + Sync {
|
||||||
|
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<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)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H, E: Engine> Handle<E> for Mutex<H> where H: Handle<E> {
|
||||||
|
fn handle (&mut self, context: &E::Input) -> 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::Input) -> 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::Input) -> 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::Input) -> Perhaps<E::Handled> {
|
||||||
|
self.write().unwrap().handle(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,17 +32,19 @@ use std::fmt::{Debug, Display};
|
||||||
}
|
}
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
|
//tui
|
||||||
audio
|
audio
|
||||||
color
|
color
|
||||||
|
collect
|
||||||
command
|
command
|
||||||
edn
|
edn
|
||||||
engine
|
engine
|
||||||
focus
|
focus
|
||||||
|
input
|
||||||
|
output
|
||||||
pitch
|
pitch
|
||||||
space
|
space
|
||||||
time
|
time
|
||||||
//tui
|
|
||||||
layout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testmod! {
|
testmod! {
|
||||||
|
|
|
||||||
137
crates/tek_core/src/output.rs
Normal file
137
crates/tek_core/src/output.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Rendering target
|
||||||
|
pub trait Output<E: Engine> {
|
||||||
|
/// 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 Render<E>) -> Usually<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast to dynamic pointer
|
||||||
|
pub fn widget <E: Engine, T: Render<E>> (w: &T) -> &dyn Render<E> {
|
||||||
|
w as &dyn Render<E>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A renderable component
|
||||||
|
pub trait Render<E: Engine>: Send + Sync {
|
||||||
|
/// Minimum size to use
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(Some(to))
|
||||||
|
}
|
||||||
|
/// Draw to output render target
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Render<E> for &dyn Render<E> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
(*self).min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
(*self).render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Render<E> for &mut dyn Render<E> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
(**self).min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
(**self).render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, E: Engine> Render<E> for Box<dyn Render<E> + 'a> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
(**self).min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
(**self).render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> Render<E> for Arc<W> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.as_ref().min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.as_ref().render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> Render<E> for Mutex<W> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.lock().unwrap().min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.lock().unwrap().render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> Render<E> for RwLock<W> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.read().unwrap().min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.read().unwrap().render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> Render<E> for Option<W> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten())
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Render] that contains other [Render]s
|
||||||
|
pub trait Content<E: Engine>: Send + Sync {
|
||||||
|
fn content (&self) -> impl Render<E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every struct that has [Content] is a renderable [Render].
|
||||||
|
impl<E: Engine, W: Content<E>> Render<E> for &W {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.content().min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
match self.min_size(to.area().wh().into())? {
|
||||||
|
Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()),
|
||||||
|
None => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A custom [Render] defined by passing layout and render closures in place.
|
||||||
|
pub struct Widget<
|
||||||
|
E: Engine,
|
||||||
|
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
||||||
|
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
||||||
|
>(L, R, PhantomData<E>);
|
||||||
|
|
||||||
|
impl<
|
||||||
|
E: Engine,
|
||||||
|
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
||||||
|
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
||||||
|
> Widget<E, L, R> {
|
||||||
|
pub fn new (layout: L, render: R) -> Self {
|
||||||
|
Self(layout, render, Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
E: Engine,
|
||||||
|
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
||||||
|
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
||||||
|
> Render<E> for Widget<E, L, R> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.0(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.1(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -129,133 +129,6 @@ impl<N: Coordinate> Area<N> for [N;4] {
|
||||||
#[inline] fn h (&self) -> N { self[3] }
|
#[inline] fn h (&self) -> N { self[3] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Layout<E: Engine>: Render<Engine = E> + Sized {
|
|
||||||
fn align_center (self) -> Align<Self> { Align::Center(self) }
|
|
||||||
fn align_n (self) -> Align<Self> { Align::N(self) }
|
|
||||||
fn align_s (self) -> Align<Self> { Align::S(self) }
|
|
||||||
fn align_e (self) -> Align<Self> { Align::E(self) }
|
|
||||||
fn align_w (self) -> Align<Self> { Align::W(self) }
|
|
||||||
fn align_nw (self) -> Align<Self> { Align::NW(self) }
|
|
||||||
fn align_sw (self) -> Align<Self> { Align::SW(self) }
|
|
||||||
fn align_ne (self) -> Align<Self> { Align::NE(self) }
|
|
||||||
fn align_se (self) -> Align<Self> { Align::SE(self) }
|
|
||||||
fn align_x (self) -> Align<Self> { Align::X(self) }
|
|
||||||
fn align_y (self) -> Align<Self> { Align::Y(self) }
|
|
||||||
fn fixed_x (self, x: E::Unit) -> Fixed<E::Unit, Self> { Fixed::X(x, self) }
|
|
||||||
fn fixed_y (self, y: E::Unit) -> Fixed<E::Unit, Self> { Fixed::Y(y, self) }
|
|
||||||
fn fixed_xy (self, x: E::Unit, y: E::Unit) -> Fixed<E::Unit, Self> { Fixed::XY(x, y, self) }
|
|
||||||
fn min_x (self, x: E::Unit) -> Min<E::Unit, Self> { Min::X(x, self) }
|
|
||||||
fn min_y (self, y: E::Unit) -> Min<E::Unit, Self> { Min::Y(y, self) }
|
|
||||||
fn min_xy (self, x: E::Unit, y: E::Unit) -> Min<E::Unit, Self> { Min::XY(x, y, self) }
|
|
||||||
fn max_x (self, x: E::Unit) -> Max<E::Unit, Self> { Max::X(x, self) }
|
|
||||||
fn max_y (self, y: E::Unit) -> Max<E::Unit, Self> { Max::Y(y, self) }
|
|
||||||
fn max_xy (self, x: E::Unit, y: E::Unit) -> Max<E::Unit, Self> { Max::XY(x, y, self) }
|
|
||||||
fn push_x (self, x: E::Unit) -> Push<E::Unit, Self> { Push::X(x, self) }
|
|
||||||
fn push_y (self, y: E::Unit) -> Push<E::Unit, Self> { Push::Y(y, self) }
|
|
||||||
fn push_xy (self, x: E::Unit, y: E::Unit) -> Push<E::Unit, Self> { Push::XY(x, y, self) }
|
|
||||||
fn pull_x (self, x: E::Unit) -> Pull<E::Unit, Self> { Pull::X(x, self) }
|
|
||||||
fn pull_y (self, y: E::Unit) -> Pull<E::Unit, Self> { Pull::Y(y, self) }
|
|
||||||
fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull<E::Unit, Self> { Pull::XY(x, y, self) }
|
|
||||||
fn grow_x (self, x: E::Unit) -> Grow<E::Unit, Self> { Grow::X(x, self) }
|
|
||||||
fn grow_y (self, y: E::Unit) -> Grow<E::Unit, Self> { Grow::Y(y, self) }
|
|
||||||
fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow<E::Unit, Self> { Grow::XY(x, y, self) }
|
|
||||||
fn shrink_x (self, x: E::Unit) -> Shrink<E::Unit, Self> { Shrink::X(x, self) }
|
|
||||||
fn shrink_y (self, y: E::Unit) -> Shrink<E::Unit, Self> { Shrink::Y(y, self) }
|
|
||||||
fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink<E::Unit, Self> { Shrink::XY(x, y, self) }
|
|
||||||
fn inset_x (self, x: E::Unit) -> Inset<E::Unit, Self> { Inset::X(x, self) }
|
|
||||||
fn inset_y (self, y: E::Unit) -> Inset<E::Unit, Self> { Inset::Y(y, self) }
|
|
||||||
fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset<E::Unit, Self> { Inset::XY(x, y, self) }
|
|
||||||
fn outset_x (self, x: E::Unit) -> Outset<E::Unit, Self> { Outset::X(x, self) }
|
|
||||||
fn outset_y (self, y: E::Unit) -> Outset<E::Unit, Self> { Outset::Y(y, self) }
|
|
||||||
fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset<E::Unit, Self> { Outset::XY(x, y, self) }
|
|
||||||
fn fill_x (self) -> Fill<E, Self> { Fill::X(self) }
|
|
||||||
fn fill_y (self) -> Fill<E, Self> { Fill::Y(self) }
|
|
||||||
fn fill_xy (self) -> Fill<E, Self> { Fill::XY(self) }
|
|
||||||
fn debug (self) -> DebugOverlay<E, Self> { DebugOverlay(self) }
|
|
||||||
fn split <W: Render<Engine = E>> (
|
|
||||||
self, direction: Direction, amount: E::Unit, other: W
|
|
||||||
) -> Split<E, Self, W> { Split::new(direction, amount, self, other) }
|
|
||||||
fn split_flip <W: Render<Engine = E>> (
|
|
||||||
self, direction: Direction, amount: E::Unit, other: W
|
|
||||||
) -> Split<E, W, Self> { Split::new(direction, amount, other, self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, W: Render<Engine = E>> Layout<E> for W {}
|
|
||||||
|
|
||||||
pub struct DebugOverlay<E: Engine, W: Render<Engine = E>>(pub W);
|
|
||||||
|
|
||||||
pub enum Fill<E: Engine, W: Render<Engine = E>> { X(W), Y(W), XY(W) }
|
|
||||||
|
|
||||||
impl<E: Engine, W: Render<Engine = E>> Fill<E, W> {
|
|
||||||
fn inner (&self) -> &W {
|
|
||||||
match self {
|
|
||||||
Self::X(inner) => &inner,
|
|
||||||
Self::Y(inner) => &inner,
|
|
||||||
Self::XY(inner) => &inner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, W: Render<Engine = E>> Render for Fill<E, W> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
let area = self.inner().min_size(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
self.inner().render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Layers<
|
|
||||||
E: Engine,
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>)->Usually<()>
|
|
||||||
>(pub F, PhantomData<E>);
|
|
||||||
|
|
||||||
impl<
|
|
||||||
E: Engine,
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>)->Usually<()>
|
|
||||||
> Layers<E, F> {
|
|
||||||
#[inline]
|
|
||||||
pub fn new (build: F) -> Self {
|
|
||||||
Self(build, Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, F> Render for Layers<E, F>
|
|
||||||
where
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>)->Usually<()>
|
|
||||||
{
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, area: E::Size) -> Perhaps<E::Size> {
|
|
||||||
let mut w: E::Unit = 0.into();
|
|
||||||
let mut h: E::Unit = 0.into();
|
|
||||||
(self.0)(&mut |layer| {
|
|
||||||
if let Some(layer_area) = layer.min_size(area)? {
|
|
||||||
w = w.max(layer_area.w());
|
|
||||||
h = h.max(layer_area.h());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
Ok(Some([w, h].into()))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
if let Some(size) = self.min_size(to.area().wh().into())? {
|
|
||||||
(self.0)(&mut |layer|to.render_in(to.area().clip(size).into(), &layer))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum Direction { Up, Down, Left, Right, }
|
pub enum Direction { Up, Down, Left, Right, }
|
||||||
impl Direction {
|
impl Direction {
|
||||||
|
|
@ -282,676 +155,3 @@ impl Direction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 center of X axis
|
|
||||||
X(L),
|
|
||||||
/// Draw at center of Y axis
|
|
||||||
Y(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::X(inner) => inner,
|
|
||||||
Self::Y(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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn align<T, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<T>, outer: R, inner: R) -> Option<R> {
|
|
||||||
if outer.w() < inner.w() || outer.h() < inner.h() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let [ox, oy, ow, oh] = outer.xywh();
|
|
||||||
let [ix, iy, iw, ih] = inner.xywh();
|
|
||||||
Some(match align {
|
|
||||||
Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
|
||||||
Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(),
|
|
||||||
Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
|
||||||
Align::NW(_) => [ox, oy, iw, ih,].into(),
|
|
||||||
Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(),
|
|
||||||
Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(),
|
|
||||||
Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
|
||||||
Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
|
||||||
Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(),
|
|
||||||
Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(),
|
|
||||||
Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, T: Render<Engine = E>> Render for Align<T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, outer_area: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.inner().min_size(outer_area)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
let outer_area = to.area();
|
|
||||||
Ok(if let Some(inner_size) = self.min_size(outer_area.wh().into())? {
|
|
||||||
let inner_area = outer_area.clip(inner_size);
|
|
||||||
if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) {
|
|
||||||
to.render_in(aligned, self.inner())?
|
|
||||||
} else {
|
|
||||||
()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enforce fixed size of drawing area
|
|
||||||
pub enum Fixed<U: Coordinate, T> {
|
|
||||||
/// Enforce fixed width
|
|
||||||
X(U, T),
|
|
||||||
/// Enforce fixed height
|
|
||||||
Y(U, T),
|
|
||||||
/// Enforce fixed width and height
|
|
||||||
XY(U, U, T),
|
|
||||||
}
|
|
||||||
impl<N: Coordinate, T> Fixed<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: Render<Engine = E>> Render for Fixed<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(match self {
|
|
||||||
Self::X(w, _) =>
|
|
||||||
if to.w() >= *w { Some([*w, to.h()].into()) } else { None },
|
|
||||||
Self::Y(h, _) =>
|
|
||||||
if to.h() >= *h { Some([to.w(), *h].into()) } else { None },
|
|
||||||
Self::XY(w, h, _)
|
|
||||||
=> if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
// 🡘 🡙 ←🡙→
|
|
||||||
if let Some(size) = self.min_size(to.area().wh().into())? {
|
|
||||||
to.render_in(to.area().clip(size).into(), self.inner())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enforce minimum size of drawing area
|
|
||||||
pub enum Min<U: Coordinate, T> {
|
|
||||||
/// Enforce minimum width
|
|
||||||
X(U, T),
|
|
||||||
/// Enforce minimum height
|
|
||||||
Y(U, T),
|
|
||||||
/// Enforce minimum width and height
|
|
||||||
XY(U, U, T),
|
|
||||||
}
|
|
||||||
impl<N: Coordinate, 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: Render<Engine = E>> Render for Min<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
|
||||||
Self::X(w, _) => [to.w().max(w), to.h()],
|
|
||||||
Self::Y(h, _) => [to.w(), to.h().max(h)],
|
|
||||||
Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)],
|
|
||||||
}.into()))
|
|
||||||
}
|
|
||||||
// TODO: 🡘 🡙 ←🡙→
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
Ok(self.min_size(to.area().wh().into())?
|
|
||||||
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
||||||
.transpose()?.unwrap_or(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enforce maximum size of drawing area
|
|
||||||
pub enum Max<U: Coordinate, T> {
|
|
||||||
/// Enforce maximum width
|
|
||||||
X(U, T),
|
|
||||||
/// Enforce maximum height
|
|
||||||
Y(U, T),
|
|
||||||
/// Enforce maximum width and height
|
|
||||||
XY(U, U, T),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Coordinate, 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: Render<Engine = E>> Render for Max<E:: Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
|
||||||
Self::X(w, _) => [to.w().min(w), to.h()],
|
|
||||||
Self::Y(h, _) => [to.w(), to.h().min(h)],
|
|
||||||
Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)],
|
|
||||||
}.into()))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
Ok(self.min_size(to.area().wh().into())?
|
|
||||||
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
||||||
.transpose()?.unwrap_or(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expand drawing area
|
|
||||||
pub enum Grow<N: Coordinate, T> {
|
|
||||||
/// Increase width
|
|
||||||
X(N, T),
|
|
||||||
/// Increase height
|
|
||||||
Y(N, T),
|
|
||||||
/// Increase width and height
|
|
||||||
XY(N, N, T)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Coordinate, 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: Render<Engine = E>> Render for Grow<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
|
||||||
Self::X(w, _) => [to.w() + w, to.h()],
|
|
||||||
Self::Y(h, _) => [to.w(), to.h() + h],
|
|
||||||
Self::XY(w, h, _) => [to.w() + w, to.h() + h],
|
|
||||||
}.into()))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
Ok(self.min_size(to.area().wh().into())?
|
|
||||||
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
||||||
.transpose()?.unwrap_or(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shrink drawing area
|
|
||||||
pub enum Shrink<N: Coordinate, T> {
|
|
||||||
/// Decrease width
|
|
||||||
X(N, T),
|
|
||||||
/// Decrease height
|
|
||||||
Y(N, T),
|
|
||||||
/// Decrease width and height
|
|
||||||
XY(N, N, T),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Coordinate, T: Render> 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: Render<Engine = E>> Render for Shrink<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
|
||||||
Self::X(w, _) => [
|
|
||||||
if to.w() > w { to.w() - w } else { 0.into() },
|
|
||||||
to.h()
|
|
||||||
],
|
|
||||||
Self::Y(h, _) => [
|
|
||||||
to.w(),
|
|
||||||
if to.h() > h { to.h() - h } else { 0.into() }
|
|
||||||
],
|
|
||||||
Self::XY(w, h, _) => [
|
|
||||||
if to.w() > w { to.w() - w } else { 0.into() },
|
|
||||||
if to.h() > h { to.h() - h } else { 0.into() }
|
|
||||||
]
|
|
||||||
}.into()))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
Ok(self.min_size(to.area().wh().into())?
|
|
||||||
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
||||||
.transpose()?.unwrap_or(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shrink from each side
|
|
||||||
pub enum Inset<N: Coordinate, T> {
|
|
||||||
/// Decrease width
|
|
||||||
X(N, T),
|
|
||||||
/// Decrease height
|
|
||||||
Y(N, T),
|
|
||||||
/// Decrease width and height
|
|
||||||
XY(N, N, T),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Coordinate, T: Render> Inset<N, T> {
|
|
||||||
pub fn inner (&self) -> &T {
|
|
||||||
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Grow on each side
|
|
||||||
pub enum Outset<N: Coordinate, T> {
|
|
||||||
/// Increase width
|
|
||||||
X(N, T),
|
|
||||||
/// Increase height
|
|
||||||
Y(N, T),
|
|
||||||
/// Increase width and height
|
|
||||||
XY(N, N, T),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Coordinate, T: Render> Outset<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: Render<Engine = E>> Render for Inset<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
match *self {
|
|
||||||
Self::X(x, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).shrink_x(x).push_x(x),
|
|
||||||
Self::Y(y, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).shrink_y(y).push_y(y),
|
|
||||||
Self::XY(x, y, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).shrink_xy(x, y).push_xy(x, y)
|
|
||||||
}.render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, T: Render<Engine = E>> Render for Outset<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
match *self {
|
|
||||||
Self::X(x, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).grow_x(x + x),
|
|
||||||
Self::Y(y, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).grow_y(y + y),
|
|
||||||
Self::XY(x, y, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).grow_xy(x + x, y + y),
|
|
||||||
}.min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
match *self {
|
|
||||||
Self::X(x, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).push_x(x),
|
|
||||||
Self::Y(y, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).push_y(y),
|
|
||||||
Self::XY(x, y, ref inner) =>
|
|
||||||
(inner as &dyn Render<Engine = E>).push_xy(x, y),
|
|
||||||
}.render(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move origin point of drawing area
|
|
||||||
pub enum Push<N: Coordinate, T: Render> {
|
|
||||||
/// 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: Coordinate, T: Render> Push<N, T> {
|
|
||||||
pub fn inner (&self) -> &T {
|
|
||||||
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
||||||
}
|
|
||||||
pub fn x (&self) -> N {
|
|
||||||
match self { Self::X(x, _) => *x, Self::Y(_, _) => N::default(), Self::XY(x, _, _) => *x }
|
|
||||||
}
|
|
||||||
pub fn y (&self) -> N {
|
|
||||||
match self { Self::X(_, _) => N::default(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, T: Render<Engine = E>> Render for Push<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.inner().min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
let area = to.area();
|
|
||||||
Ok(self.min_size(area.wh().into())?
|
|
||||||
.map(|size|to.render_in(match *self {
|
|
||||||
Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()],
|
|
||||||
Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()],
|
|
||||||
Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()],
|
|
||||||
}.into(), self.inner())).transpose()?.unwrap_or(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move origin point of drawing area
|
|
||||||
pub enum Pull<N: Coordinate, T: Render> {
|
|
||||||
/// 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: Coordinate, T: Render> Pull<N, T> {
|
|
||||||
pub fn inner (&self) -> &T {
|
|
||||||
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
||||||
}
|
|
||||||
pub fn x (&self) -> N {
|
|
||||||
match self { Self::X(x, _) => *x, Self::Y(_, _) => N::default(), Self::XY(x, _, _) => *x }
|
|
||||||
}
|
|
||||||
pub fn y (&self) -> N {
|
|
||||||
match self { Self::X(_, _) => N::default(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, T: Render<Engine = E>> Render for Pull<E::Unit, T> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
self.inner().min_size(to)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
let area = to.area();
|
|
||||||
Ok(self.min_size(area.wh().into())?
|
|
||||||
.map(|size|to.render_in(match *self {
|
|
||||||
Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()],
|
|
||||||
Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()],
|
|
||||||
Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()],
|
|
||||||
}.into(), self.inner())).transpose()?.unwrap_or(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Stack<
|
|
||||||
E: Engine,
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>)->Usually<()>
|
|
||||||
>(pub F, pub Direction, PhantomData<E>);
|
|
||||||
|
|
||||||
impl<
|
|
||||||
E: Engine,
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>)->Usually<()>
|
|
||||||
> Stack<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)
|
|
||||||
}
|
|
||||||
#[inline] pub fn up (build: F) -> Self {
|
|
||||||
Self::new(Direction::Up, build)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, F> Render for Stack<E, F>
|
|
||||||
where
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>)->Usually<()>
|
|
||||||
{
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
match self.1 {
|
|
||||||
|
|
||||||
Direction::Down => {
|
|
||||||
let mut w: E::Unit = 0.into();
|
|
||||||
let mut h: E::Unit = 0.into();
|
|
||||||
(self.0)(&mut |component: &dyn Render<Engine = E>| {
|
|
||||||
let max = to.h().minus(h);
|
|
||||||
if max > E::Unit::ZERO() {
|
|
||||||
let item = component.push_y(h).max_y(max);
|
|
||||||
let size = item.min_size(to)?.map(|size|size.wh());
|
|
||||||
if let Some([width, height]) = size {
|
|
||||||
h = h + height.into();
|
|
||||||
w = w.max(width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
Ok(Some([w, h].into()))
|
|
||||||
},
|
|
||||||
|
|
||||||
Direction::Right => {
|
|
||||||
let mut w: E::Unit = 0.into();
|
|
||||||
let mut h: E::Unit = 0.into();
|
|
||||||
(self.0)(&mut |component: &dyn Render<Engine = E>| {
|
|
||||||
let max = to.w().minus(w);
|
|
||||||
if max > E::Unit::ZERO() {
|
|
||||||
let item = component.push_x(w).max_x(max);
|
|
||||||
let size = item.min_size(to)?.map(|size|size.wh());
|
|
||||||
if let Some([width, height]) = size {
|
|
||||||
w = w + width.into();
|
|
||||||
h = h.max(height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
Ok(Some([w, h].into()))
|
|
||||||
},
|
|
||||||
|
|
||||||
Direction::Up => {
|
|
||||||
let mut w: E::Unit = 0.into();
|
|
||||||
let mut h: E::Unit = 0.into();
|
|
||||||
(self.0)(&mut |component: &dyn Render<Engine = E>| {
|
|
||||||
let max = to.h().minus(h);
|
|
||||||
if max > E::Unit::ZERO() {
|
|
||||||
let item = component.max_y(to.h() - h);
|
|
||||||
let size = item.min_size(to)?.map(|size|size.wh());
|
|
||||||
if let Some([width, height]) = size {
|
|
||||||
h = h + height.into();
|
|
||||||
w = w.max(width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
Ok(Some([w, h].into()))
|
|
||||||
},
|
|
||||||
|
|
||||||
Direction::Left => {
|
|
||||||
let mut w: E::Unit = 0.into();
|
|
||||||
let mut h: E::Unit = 0.into();
|
|
||||||
(self.0)(&mut |component: &dyn Render<Engine = E>| {
|
|
||||||
if w < to.w() {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
Ok(Some([w, h].into()))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
let area = to.area();
|
|
||||||
let mut w = 0.into();
|
|
||||||
let mut h = 0.into();
|
|
||||||
match self.1 {
|
|
||||||
Direction::Down => {
|
|
||||||
(self.0)(&mut |item| {
|
|
||||||
if h < area.h() {
|
|
||||||
let item = item.push_y(h).max_y(area.h() - h);
|
|
||||||
let show = item.min_size(area.wh().into())?.map(|s|s.wh());
|
|
||||||
if let Some([width, height]) = show {
|
|
||||||
item.render(to)?;
|
|
||||||
h = h + height;
|
|
||||||
if width > w { w = width }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
Direction::Right => {
|
|
||||||
(self.0)(&mut |item| {
|
|
||||||
if w < area.w() {
|
|
||||||
let item = item.push_x(w).max_x(area.w() - w);
|
|
||||||
let show = item.min_size(area.wh().into())?.map(|s|s.wh());
|
|
||||||
if let Some([width, height]) = show {
|
|
||||||
item.render(to)?;
|
|
||||||
w = width + w;
|
|
||||||
if height > h { h = height }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
Direction::Up => {
|
|
||||||
(self.0)(&mut |item| {
|
|
||||||
if h < area.h() {
|
|
||||||
let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh());
|
|
||||||
if let Some([width, height]) = show {
|
|
||||||
item.push_y(area.h() - height).shrink_y(height).render(to)?;
|
|
||||||
h = h + height;
|
|
||||||
if width > w { w = width }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
_ => todo!()
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! lay {
|
|
||||||
($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) }
|
|
||||||
}
|
|
||||||
#[macro_export] macro_rules! col {
|
|
||||||
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
|
||||||
($pat:pat in $collection:expr => $item:expr) => {
|
|
||||||
Stack::down(move |add|{
|
|
||||||
for $pat in $collection { add(&$item)?; }
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[macro_export] macro_rules! col_up {
|
|
||||||
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
|
||||||
($pat:pat in $collection:expr => $item:expr) => {
|
|
||||||
Stack::up(move |add|{
|
|
||||||
for $pat in $collection { add(&$item)?; }
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[macro_export] macro_rules! row {
|
|
||||||
($($expr:expr),* $(,)?) => { Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
|
||||||
($pat:pat in $collection:expr => $item:expr) => {
|
|
||||||
Stack::right(move |add|{
|
|
||||||
for $pat in $collection { add(&$item)?; }
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A binary split with fixed proportion
|
|
||||||
pub struct Split<E: Engine, A: Render<Engine = E>, B: Render<Engine = E>>(
|
|
||||||
pub Direction, pub E::Unit, A, B, PhantomData<E>
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<E: Engine, A: Render<Engine = E>, B: Render<Engine = E>> Split<E, A, B> {
|
|
||||||
pub fn new (direction: Direction, proportion: E::Unit, a: A, b: B) -> Self {
|
|
||||||
Self(direction, proportion, a, b, Default::default())
|
|
||||||
}
|
|
||||||
pub fn up (proportion: E::Unit, a: A, b: B) -> Self {
|
|
||||||
Self(Direction::Up, proportion, a, b, Default::default())
|
|
||||||
}
|
|
||||||
pub fn down (proportion: E::Unit, a: A, b: B) -> Self {
|
|
||||||
Self(Direction::Down, proportion, a, b, Default::default())
|
|
||||||
}
|
|
||||||
pub fn left (proportion: E::Unit, a: A, b: B) -> Self {
|
|
||||||
Self(Direction::Left, proportion, a, b, Default::default())
|
|
||||||
}
|
|
||||||
pub fn right (proportion: E::Unit, a: A, b: B) -> Self {
|
|
||||||
Self(Direction::Right, proportion, a, b, Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, A: Render<Engine = E>, B: Render<Engine = E>> Render for Split<E, A, B> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(Some(to))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
let (a, b) = to.area().split_fixed(self.0, self.1);
|
|
||||||
to.render_in(a.into(), &self.2)?;
|
|
||||||
to.render_in(b.into(), &self.3)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A widget that tracks its render width and height
|
|
||||||
pub struct Measure<E: Engine>(PhantomData<E>, AtomicUsize, AtomicUsize);
|
|
||||||
|
|
||||||
impl<E: Engine> Clone for Measure<E> {
|
|
||||||
fn clone (&self) -> Self {
|
|
||||||
Self(
|
|
||||||
Default::default(),
|
|
||||||
AtomicUsize::from(self.1.load(Ordering::Relaxed)),
|
|
||||||
AtomicUsize::from(self.2.load(Ordering::Relaxed)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine> std::fmt::Debug for Measure<E> {
|
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
f.debug_struct("Measure")
|
|
||||||
.field("width", &self.0)
|
|
||||||
.field("height", &self.1)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine> Measure<E> {
|
|
||||||
pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) }
|
|
||||||
pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) }
|
|
||||||
pub fn wh (&self) -> [usize;2] { [self.w(), self.h()] }
|
|
||||||
pub fn set_w (&self, w: impl Into<usize>) { self.1.store(w.into(), Ordering::Relaxed) }
|
|
||||||
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) }
|
|
||||||
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
|
|
||||||
pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) }
|
|
||||||
pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine> Render for Measure<E> {
|
|
||||||
type Engine = E;
|
|
||||||
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
|
|
||||||
Ok(Some([0u16.into(), 0u16.into()].into()))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
||||||
self.set_w(to.area().w());
|
|
||||||
self.set_h(to.area().h());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scrollable area.
|
|
||||||
pub struct Scroll<
|
|
||||||
E: Engine,
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<Engine = E>)->Usually<()>)->Usually<()>
|
|
||||||
>(pub F, pub Direction, pub u64, PhantomData<E>);
|
|
||||||
|
|
|
||||||
7
crates/tek_layout/Cargo.toml
Normal file
7
crates/tek_layout/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "tek_layout"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tek_core = { path = "../tek_core" }
|
||||||
102
crates/tek_layout/src/align.rs
Normal file
102
crates/tek_layout/src/align.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutAlign<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutAlign<E: Engine>: Render<E> + Sized {
|
||||||
|
fn align_x (self) -> Align<Self> { Align::X(self) }
|
||||||
|
fn align_y (self) -> Align<Self> { Align::Y(self) }
|
||||||
|
fn align_center (self) -> Align<Self> { Align::Center(self) }
|
||||||
|
fn align_n (self) -> Align<Self> { Align::N(self) }
|
||||||
|
fn align_s (self) -> Align<Self> { Align::S(self) }
|
||||||
|
fn align_e (self) -> Align<Self> { Align::E(self) }
|
||||||
|
fn align_w (self) -> Align<Self> { Align::W(self) }
|
||||||
|
fn align_nw (self) -> Align<Self> { Align::NW(self) }
|
||||||
|
fn align_sw (self) -> Align<Self> { Align::SW(self) }
|
||||||
|
fn align_ne (self) -> Align<Self> { Align::NE(self) }
|
||||||
|
fn align_se (self) -> Align<Self> { Align::SE(self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 center of X axis
|
||||||
|
X(L),
|
||||||
|
/// Draw at center of Y axis
|
||||||
|
Y(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::X(inner) => inner,
|
||||||
|
Self::Y(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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align<T, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<T>, outer: R, inner: R) -> Option<R> {
|
||||||
|
if outer.w() < inner.w() || outer.h() < inner.h() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let [ox, oy, ow, oh] = outer.xywh();
|
||||||
|
let [ix, iy, iw, ih] = inner.xywh();
|
||||||
|
Some(match align {
|
||||||
|
Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
||||||
|
Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(),
|
||||||
|
Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
||||||
|
Align::NW(_) => [ox, oy, iw, ih,].into(),
|
||||||
|
Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(),
|
||||||
|
Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(),
|
||||||
|
Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
||||||
|
Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(),
|
||||||
|
Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(),
|
||||||
|
Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(),
|
||||||
|
Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Render<E> for Align<T> {
|
||||||
|
fn min_size (&self, outer_area: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.inner().min_size(outer_area)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
let outer_area = to.area();
|
||||||
|
Ok(if let Some(inner_size) = self.min_size(outer_area.wh().into())? {
|
||||||
|
let inner_area = outer_area.clip(inner_size);
|
||||||
|
if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) {
|
||||||
|
to.render_in(aligned, self.inner())?
|
||||||
|
} else {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
54
crates/tek_layout/src/bsp.rs
Normal file
54
crates/tek_layout/src/bsp.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, R: Render<E>> LayoutBsp<E> for R {}
|
||||||
|
|
||||||
|
pub trait LayoutBsp<E: Engine>: Render<E> + Sized {
|
||||||
|
fn when (self, cond: bool) -> If<E, Self> {
|
||||||
|
If(Default::default(), cond, self)
|
||||||
|
}
|
||||||
|
fn or <B: Render<E>> (self, cond: bool, other: B) -> Either<E, Self, B> {
|
||||||
|
Either(Default::default(), cond, self, other)
|
||||||
|
}
|
||||||
|
fn over <B: Render<E>> (self, other: B) -> Over<E, Self, B> {
|
||||||
|
Over(Default::default(), self, other)
|
||||||
|
}
|
||||||
|
fn under <B: Render<E>> (self, other: B) -> Under<E, Self, B> {
|
||||||
|
Under(Default::default(), self, other)
|
||||||
|
}
|
||||||
|
fn north_of <B: Render<E>> (self, other: B) -> North<E, Self, B> {
|
||||||
|
North(Default::default(), self, other)
|
||||||
|
}
|
||||||
|
fn south_of <B: Render<E>> (self, other: B) -> South<E, Self, B> {
|
||||||
|
South(Default::default(), self, other)
|
||||||
|
}
|
||||||
|
fn east_of <B: Render<E>> (self, other: B) -> East<E, Self, B> {
|
||||||
|
East(Default::default(), self, other)
|
||||||
|
}
|
||||||
|
fn west_of <B: Render<E>> (self, other: B) -> West<E, Self, B> {
|
||||||
|
West(Default::default(), self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render widget if predicate is true
|
||||||
|
pub struct If<E: Engine, A: Render<E>>(PhantomData<E>, bool, A);
|
||||||
|
|
||||||
|
impl<E: Engine, A: Render<E>> Content<E> for If<E, A> {
|
||||||
|
fn content (&self) -> impl Render<E> {
|
||||||
|
if self.1 { Some(widget(&self.2)) } else { None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render widget A if predicate is true, otherwise widget B
|
||||||
|
pub struct Either<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, bool, A, B);
|
||||||
|
|
||||||
|
pub struct Over<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
|
||||||
|
|
||||||
|
pub struct Under<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
|
||||||
|
|
||||||
|
pub struct North<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
|
||||||
|
|
||||||
|
pub struct South<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
|
||||||
|
|
||||||
|
pub struct East<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
|
||||||
|
|
||||||
|
pub struct West<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
|
||||||
11
crates/tek_layout/src/debug.rs
Normal file
11
crates/tek_layout/src/debug.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutDebug<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutDebug<E: Engine>: Render<E> + Sized {
|
||||||
|
fn debug (self) -> DebugOverlay<E, Self> {
|
||||||
|
DebugOverlay(Default::default(), self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DebugOverlay<E: Engine, W: Render<E>>(PhantomData<E>, pub W);
|
||||||
52
crates/tek_layout/src/fill.rs
Normal file
52
crates/tek_layout/src/fill.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutFill<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutFill<E: Engine>: Render<E> + Sized {
|
||||||
|
fn fill_x (self) -> Fill<E, Self> {
|
||||||
|
Fill::X(self)
|
||||||
|
}
|
||||||
|
fn fill_y (self) -> Fill<E, Self> {
|
||||||
|
Fill::Y(self)
|
||||||
|
}
|
||||||
|
fn fill_xy (self) -> Fill<E, Self> {
|
||||||
|
Fill::XY(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Fill<E: Engine, W: Render<E>> {
|
||||||
|
X(W),
|
||||||
|
Y(W),
|
||||||
|
XY(W),
|
||||||
|
_Unused(PhantomData<E>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> Fill<E, W> {
|
||||||
|
fn inner (&self) -> &W {
|
||||||
|
match self {
|
||||||
|
Self::X(inner) => &inner,
|
||||||
|
Self::Y(inner) => &inner,
|
||||||
|
Self::XY(inner) => &inner,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> Render<E> for Fill<E, W> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
let area = self.inner().min_size(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()],
|
||||||
|
_ => unreachable!(),
|
||||||
|
}.into()))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.inner().render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
58
crates/tek_layout/src/fixed.rs
Normal file
58
crates/tek_layout/src/fixed.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutFixed<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutFixed<E: Engine>: Render<E> + Sized {
|
||||||
|
fn fixed_x (self, x: E::Unit) -> Fixed<E, Self> {
|
||||||
|
Fixed::X(x, self)
|
||||||
|
}
|
||||||
|
fn fixed_y (self, y: E::Unit) -> Fixed<E, Self> {
|
||||||
|
Fixed::Y(y, self)
|
||||||
|
}
|
||||||
|
fn fixed_xy (self, x: E::Unit, y: E::Unit) -> Fixed<E, Self> {
|
||||||
|
Fixed::XY(x, y, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enforce fixed size of drawing area
|
||||||
|
pub enum Fixed<E: Engine, T> {
|
||||||
|
_Unused(PhantomData<E>),
|
||||||
|
/// Enforce fixed width
|
||||||
|
X(E::Unit, T),
|
||||||
|
/// Enforce fixed height
|
||||||
|
Y(E::Unit, T),
|
||||||
|
/// Enforce fixed width and height
|
||||||
|
XY(E::Unit, E::Unit, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T> Fixed<E, T> {
|
||||||
|
pub fn inner (&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Self::X(_, i) => i,
|
||||||
|
Self::Y(_, i) => i,
|
||||||
|
Self::XY(_, _, i) => i,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<E: Engine, T: Render<E>> Render<E> for Fixed<E, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(match self {
|
||||||
|
Self::X(w, _) =>
|
||||||
|
if to.w() >= *w { Some([*w, to.h()].into()) } else { None },
|
||||||
|
Self::Y(h, _) =>
|
||||||
|
if to.h() >= *h { Some([to.w(), *h].into()) } else { None },
|
||||||
|
Self::XY(w, h, _)
|
||||||
|
=> if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None },
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
// 🡘 🡙 ←🡙→
|
||||||
|
if let Some(size) = self.min_size(to.area().wh().into())? {
|
||||||
|
to.render_in(to.area().clip(size).into(), self.inner())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
crates/tek_layout/src/grow.rs
Normal file
46
crates/tek_layout/src/grow.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutGrow<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutGrow<E: Engine>: Render<E> + Sized {
|
||||||
|
fn grow_x (self, x: E::Unit) -> Grow<E::Unit, Self> {
|
||||||
|
Grow::X(x, self)
|
||||||
|
}
|
||||||
|
fn grow_y (self, y: E::Unit) -> Grow<E::Unit, Self> {
|
||||||
|
Grow::Y(y, self)
|
||||||
|
}
|
||||||
|
fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow<E::Unit, Self> {
|
||||||
|
Grow::XY(x, y, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand drawing area
|
||||||
|
pub enum Grow<N: Coordinate, T> {
|
||||||
|
/// Increase width
|
||||||
|
X(N, T),
|
||||||
|
/// Increase height
|
||||||
|
Y(N, T),
|
||||||
|
/// Increase width and height
|
||||||
|
XY(N, N, T)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Coordinate, 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: Render<E>> Render<E> for Grow<E::Unit, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
||||||
|
Self::X(w, _) => [to.w() + w, to.h()],
|
||||||
|
Self::Y(h, _) => [to.w(), to.h() + h],
|
||||||
|
Self::XY(w, h, _) => [to.w() + w, to.h() + h],
|
||||||
|
}.into()))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
Ok(self.min_size(to.area().wh().into())?
|
||||||
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
||||||
|
.transpose()?.unwrap_or(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
48
crates/tek_layout/src/inset.rs
Normal file
48
crates/tek_layout/src/inset.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutInset<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutInset<E: Engine>: Render<E> + Sized {
|
||||||
|
fn inset_x (self, x: E::Unit) -> Inset<E, Self> {
|
||||||
|
Inset::X(x, self)
|
||||||
|
}
|
||||||
|
fn inset_y (self, y: E::Unit) -> Inset<E, Self> {
|
||||||
|
Inset::Y(y, self)
|
||||||
|
}
|
||||||
|
fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset<E, Self> {
|
||||||
|
Inset::XY(x, y, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shrink from each side
|
||||||
|
pub enum Inset<E: Engine, T> {
|
||||||
|
_Unused(PhantomData<E>),
|
||||||
|
/// Decrease width
|
||||||
|
X(E::Unit, T),
|
||||||
|
/// Decrease height
|
||||||
|
Y(E::Unit, T),
|
||||||
|
/// Decrease width and height
|
||||||
|
XY(E::Unit, E::Unit, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Inset<E, T> {
|
||||||
|
pub fn inner (&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Self::X(_, i) => i,
|
||||||
|
Self::Y(_, i) => i,
|
||||||
|
Self::XY(_, _, i) => i,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Render<E> for Inset<E, T> {
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
match *self {
|
||||||
|
Self::X(x, ref inner) => (inner as &dyn Render<E>).shrink_x(x).push_x(x),
|
||||||
|
Self::Y(y, ref inner) => (inner as &dyn Render<E>).shrink_y(y).push_y(y),
|
||||||
|
Self::XY(x, y, ref inner) => (inner as &dyn Render<E>).shrink_xy(x, y).push_xy(x, y),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}.render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
crates/tek_layout/src/layers.rs
Normal file
45
crates/tek_layout/src/layers.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! lay {
|
||||||
|
($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Layers<
|
||||||
|
E: Engine,
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||||
|
>(pub F, PhantomData<E>);
|
||||||
|
|
||||||
|
impl<
|
||||||
|
E: Engine,
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||||
|
> Layers<E, F> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new (build: F) -> Self {
|
||||||
|
Self(build, Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, F> Render<E> for Layers<E, F>
|
||||||
|
where
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||||
|
{
|
||||||
|
fn min_size (&self, area: E::Size) -> Perhaps<E::Size> {
|
||||||
|
let mut w: E::Unit = 0.into();
|
||||||
|
let mut h: E::Unit = 0.into();
|
||||||
|
(self.0)(&mut |layer| {
|
||||||
|
if let Some(layer_area) = layer.min_size(area)? {
|
||||||
|
w = w.max(layer_area.w());
|
||||||
|
h = h.max(layer_area.h());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(Some([w, h].into()))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
if let Some(size) = self.min_size(to.area().wh().into())? {
|
||||||
|
(self.0)(&mut |layer|to.render_in(to.area().clip(size).into(), &layer))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
crates/tek_layout/src/lib.rs
Normal file
18
crates/tek_layout/src/lib.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
pub(crate) use tek_core::*;
|
||||||
|
|
||||||
|
submod! {
|
||||||
|
align
|
||||||
|
bsp
|
||||||
|
debug
|
||||||
|
fill
|
||||||
|
fixed
|
||||||
|
grow
|
||||||
|
inset
|
||||||
|
max
|
||||||
|
min
|
||||||
|
outset
|
||||||
|
pull
|
||||||
|
push
|
||||||
|
shrink
|
||||||
|
split
|
||||||
|
}
|
||||||
38
crates/tek_layout/src/max.rs
Normal file
38
crates/tek_layout/src/max.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait LayoutMax<E: Engine>: Render<E> + Sized {
|
||||||
|
fn max_x (self, x: E::Unit) -> Max<E::Unit, Self> { Max::X(x, self) }
|
||||||
|
fn max_y (self, y: E::Unit) -> Max<E::Unit, Self> { Max::Y(y, self) }
|
||||||
|
fn max_xy (self, x: E::Unit, y: E::Unit) -> Max<E::Unit, Self> { Max::XY(x, y, self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enforce maximum size of drawing area
|
||||||
|
pub enum Max<U: Coordinate, T> {
|
||||||
|
/// Enforce maximum width
|
||||||
|
X(U, T),
|
||||||
|
/// Enforce maximum height
|
||||||
|
Y(U, T),
|
||||||
|
/// Enforce maximum width and height
|
||||||
|
XY(U, U, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Coordinate, 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: Render<E>> Render<E> for Max<E:: Unit, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
||||||
|
Self::X(w, _) => [to.w().min(w), to.h()],
|
||||||
|
Self::Y(h, _) => [to.w(), to.h().min(h)],
|
||||||
|
Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)],
|
||||||
|
}.into()))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
Ok(self.min_size(to.area().wh().into())?
|
||||||
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
||||||
|
.transpose()?.unwrap_or(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
46
crates/tek_layout/src/measure.rs
Normal file
46
crates/tek_layout/src/measure.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// A widget that tracks its render width and height
|
||||||
|
pub struct Measure<E: Engine>(PhantomData<E>, AtomicUsize, AtomicUsize);
|
||||||
|
|
||||||
|
impl<E: Engine> Clone for Measure<E> {
|
||||||
|
fn clone (&self) -> Self {
|
||||||
|
Self(
|
||||||
|
Default::default(),
|
||||||
|
AtomicUsize::from(self.1.load(Ordering::Relaxed)),
|
||||||
|
AtomicUsize::from(self.2.load(Ordering::Relaxed)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> std::fmt::Debug for Measure<E> {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("Measure")
|
||||||
|
.field("width", &self.0)
|
||||||
|
.field("height", &self.1)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Measure<E> {
|
||||||
|
pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) }
|
||||||
|
pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) }
|
||||||
|
pub fn wh (&self) -> [usize;2] { [self.w(), self.h()] }
|
||||||
|
pub fn set_w (&self, w: impl Into<usize>) { self.1.store(w.into(), Ordering::Relaxed) }
|
||||||
|
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) }
|
||||||
|
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
|
||||||
|
pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) }
|
||||||
|
pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Render<E> for Measure<E> {
|
||||||
|
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(Some([0u16.into(), 0u16.into()].into()))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.set_w(to.area().w());
|
||||||
|
self.set_h(to.area().h());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
37
crates/tek_layout/src/min.rs
Normal file
37
crates/tek_layout/src/min.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait LayoutMin<E: Engine>: Render<E> + Sized {
|
||||||
|
fn min_x (self, x: E::Unit) -> Min<E::Unit, Self> { Min::X(x, self) }
|
||||||
|
fn min_y (self, y: E::Unit) -> Min<E::Unit, Self> { Min::Y(y, self) }
|
||||||
|
fn min_xy (self, x: E::Unit, y: E::Unit) -> Min<E::Unit, Self> { Min::XY(x, y, self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enforce minimum size of drawing area
|
||||||
|
pub enum Min<U: Coordinate, T> {
|
||||||
|
/// Enforce minimum width
|
||||||
|
X(U, T),
|
||||||
|
/// Enforce minimum height
|
||||||
|
Y(U, T),
|
||||||
|
/// Enforce minimum width and height
|
||||||
|
XY(U, U, T),
|
||||||
|
}
|
||||||
|
impl<N: Coordinate, 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: Render<E>> Render<E> for Min<E::Unit, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
||||||
|
Self::X(w, _) => [to.w().max(w), to.h()],
|
||||||
|
Self::Y(h, _) => [to.w(), to.h().max(h)],
|
||||||
|
Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)],
|
||||||
|
}.into()))
|
||||||
|
}
|
||||||
|
// TODO: 🡘 🡙 ←🡙→
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
Ok(self.min_size(to.area().wh().into())?
|
||||||
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
||||||
|
.transpose()?.unwrap_or(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
57
crates/tek_layout/src/outset.rs
Normal file
57
crates/tek_layout/src/outset.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutOutset<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutOutset<E: Engine>: Render<E> + Sized {
|
||||||
|
fn outset_x (self, x: E::Unit) -> Outset<E, Self> {
|
||||||
|
Outset::X(x, self)
|
||||||
|
}
|
||||||
|
fn outset_y (self, y: E::Unit) -> Outset<E, Self> {
|
||||||
|
Outset::Y(y, self)
|
||||||
|
}
|
||||||
|
fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset<E, Self> {
|
||||||
|
Outset::XY(x, y, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Grow on each side
|
||||||
|
pub enum Outset<E: Engine, T: Render<E>> {
|
||||||
|
_Unused(PhantomData<E>),
|
||||||
|
/// Increase width
|
||||||
|
X(E::Unit, T),
|
||||||
|
/// Increase height
|
||||||
|
Y(E::Unit, T),
|
||||||
|
/// Increase width and height
|
||||||
|
XY(E::Unit, E::Unit, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Outset<E, T> {
|
||||||
|
pub fn inner (&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Self::X(_, i) => i,
|
||||||
|
Self::Y(_, i) => i,
|
||||||
|
Self::XY(_, _, i) => i,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Render<E> for Outset<E, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
match *self {
|
||||||
|
Self::X(x, ref inner) => (inner as &dyn Render<E>).grow_x(x + x),
|
||||||
|
Self::Y(y, ref inner) => (inner as &dyn Render<E>).grow_y(y + y),
|
||||||
|
Self::XY(x, y, ref inner) => (inner as &dyn Render<E>).grow_xy(x + x, y + y),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}.min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
match *self {
|
||||||
|
Self::X(x, ref inner) => (inner as &dyn Render<E>).push_x(x),
|
||||||
|
Self::Y(y, ref inner) => (inner as &dyn Render<E>).push_y(y),
|
||||||
|
Self::XY(x, y, ref inner) => (inner as &dyn Render<E>).push_xy(x, y),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}.render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
69
crates/tek_layout/src/pull.rs
Normal file
69
crates/tek_layout/src/pull.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutPull<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutPull<E: Engine>: Render<E> + Sized {
|
||||||
|
fn pull_x (self, x: E::Unit) -> Pull<E, Self> {
|
||||||
|
Pull::X(x, self)
|
||||||
|
}
|
||||||
|
fn pull_y (self, y: E::Unit) -> Pull<E, Self> {
|
||||||
|
Pull::Y(y, self)
|
||||||
|
}
|
||||||
|
fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull<E, Self> {
|
||||||
|
Pull::XY(x, y, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move origin point of drawing area
|
||||||
|
pub enum Pull<E: Engine, T: Render<E>> {
|
||||||
|
_Unused(PhantomData<E>),
|
||||||
|
/// Move origin to the right
|
||||||
|
X(E::Unit, T),
|
||||||
|
/// Move origin downwards
|
||||||
|
Y(E::Unit, T),
|
||||||
|
/// Move origin to the right and downwards
|
||||||
|
XY(E::Unit, E::Unit, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Pull<E, T> {
|
||||||
|
pub fn inner (&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Self::X(_, i) => i,
|
||||||
|
Self::Y(_, i) => i,
|
||||||
|
Self::XY(_, _, i) => i,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn x (&self) -> E::Unit {
|
||||||
|
match self {
|
||||||
|
Self::X(x, _) => *x,
|
||||||
|
Self::Y(_, _) => E::Unit::default(),
|
||||||
|
Self::XY(x, _, _) => *x,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn y (&self) -> E::Unit {
|
||||||
|
match self {
|
||||||
|
Self::X(_, _) => E::Unit::default(),
|
||||||
|
Self::Y(y, _) => *y,
|
||||||
|
Self::XY(_, y, _) => *y,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Render<E> for Pull<E, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.inner().min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
let area = to.area();
|
||||||
|
Ok(self.min_size(area.wh().into())?
|
||||||
|
.map(|size|to.render_in(match *self {
|
||||||
|
Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()],
|
||||||
|
Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()],
|
||||||
|
Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()],
|
||||||
|
_ => unreachable!(),
|
||||||
|
}.into(), self.inner())).transpose()?.unwrap_or(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
68
crates/tek_layout/src/push.rs
Normal file
68
crates/tek_layout/src/push.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutPush<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutPush<E: Engine>: Render<E> + Sized {
|
||||||
|
fn push_x (self, x: E::Unit) -> Push<E, Self> {
|
||||||
|
Push::X(x, self)
|
||||||
|
}
|
||||||
|
fn push_y (self, y: E::Unit) -> Push<E, Self> {
|
||||||
|
Push::Y(y, self)
|
||||||
|
}
|
||||||
|
fn push_xy (self, x: E::Unit, y: E::Unit) -> Push<E, Self> {
|
||||||
|
Push::XY(x, y, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move origin point of drawing area
|
||||||
|
pub enum Push<E: Engine, T: Render<E>> {
|
||||||
|
/// Move origin to the right
|
||||||
|
X(E::Unit, T),
|
||||||
|
/// Move origin downwards
|
||||||
|
Y(E::Unit, T),
|
||||||
|
/// Move origin to the right and downwards
|
||||||
|
XY(E::Unit, E::Unit, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Push<E, T> {
|
||||||
|
pub fn inner (&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Self::X(_, i) => i,
|
||||||
|
Self::Y(_, i) => i,
|
||||||
|
Self::XY(_, _, i) => i,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn x (&self) -> E::Unit {
|
||||||
|
match self {
|
||||||
|
Self::X(x, _) => *x,
|
||||||
|
Self::Y(_, _) => E::Unit::default(),
|
||||||
|
Self::XY(x, _, _) => *x,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn y (&self) -> E::Unit {
|
||||||
|
match self {
|
||||||
|
Self::X(_, _) => E::Unit::default(),
|
||||||
|
Self::Y(y, _) => *y,
|
||||||
|
Self::XY(_, y, _) => *y,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Render<E> for Push<E, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
self.inner().min_size(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
let area = to.area();
|
||||||
|
Ok(self.min_size(area.wh().into())?
|
||||||
|
.map(|size|to.render_in(match *self {
|
||||||
|
Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()],
|
||||||
|
Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()],
|
||||||
|
Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()],
|
||||||
|
_ => unreachable!(),
|
||||||
|
}.into(), self.inner())).transpose()?.unwrap_or(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
8
crates/tek_layout/src/scroll.rs
Normal file
8
crates/tek_layout/src/scroll.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// A scrollable area.
|
||||||
|
pub struct Scroll<
|
||||||
|
E: Engine,
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||||
|
>(pub F, pub Direction, pub u64, PhantomData<E>);
|
||||||
|
|
||||||
62
crates/tek_layout/src/shrink.rs
Normal file
62
crates/tek_layout/src/shrink.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<E: Engine, W: Render<E>> LayoutShrink<E> for W {}
|
||||||
|
|
||||||
|
pub trait LayoutShrink<E: Engine>: Render<E> + Sized {
|
||||||
|
fn shrink_x (self, x: E::Unit) -> Shrink<E, Self> {
|
||||||
|
Shrink::X(x, self)
|
||||||
|
}
|
||||||
|
fn shrink_y (self, y: E::Unit) -> Shrink<E, Self> {
|
||||||
|
Shrink::Y(y, self)
|
||||||
|
}
|
||||||
|
fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink<E, Self> {
|
||||||
|
Shrink::XY(x, y, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shrink drawing area
|
||||||
|
pub enum Shrink<E: Engine, T> {
|
||||||
|
_Unused(PhantomData<E>),
|
||||||
|
/// Decrease width
|
||||||
|
X(E::Unit, T),
|
||||||
|
/// Decrease height
|
||||||
|
Y(E::Unit, T),
|
||||||
|
/// Decrease width and height
|
||||||
|
XY(E::Unit, E::Unit, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Shrink<E, T> {
|
||||||
|
fn inner (&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Self::X(_, i) => i,
|
||||||
|
Self::Y(_, i) => i,
|
||||||
|
Self::XY(_, _, i) => i,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, T: Render<E>> Render<E> for Shrink<E, T> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
||||||
|
Self::X(w, _) => [
|
||||||
|
if to.w() > w { to.w() - w } else { 0.into() },
|
||||||
|
to.h()
|
||||||
|
],
|
||||||
|
Self::Y(h, _) => [
|
||||||
|
to.w(),
|
||||||
|
if to.h() > h { to.h() - h } else { 0.into() }
|
||||||
|
],
|
||||||
|
Self::XY(w, h, _) => [
|
||||||
|
if to.w() > w { to.w() - w } else { 0.into() },
|
||||||
|
if to.h() > h { to.h() - h } else { 0.into() }
|
||||||
|
],
|
||||||
|
_ => unreachable!(),
|
||||||
|
}.into()))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
Ok(self.min_size(to.area().wh().into())?
|
||||||
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
||||||
|
.transpose()?.unwrap_or(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
45
crates/tek_layout/src/split.rs
Normal file
45
crates/tek_layout/src/split.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait LayoutSplit<E: Engine>: Render<E> + Sized {
|
||||||
|
fn split <W: Render<E>> (
|
||||||
|
self, direction: Direction, amount: E::Unit, other: W
|
||||||
|
) -> Split<E, Self, W> { Split::new(direction, amount, self, other) }
|
||||||
|
fn split_flip <W: Render<E>> (
|
||||||
|
self, direction: Direction, amount: E::Unit, other: W
|
||||||
|
) -> Split<E, W, Self> { Split::new(direction, amount, other, self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A binary split with fixed proportion
|
||||||
|
pub struct Split<E: Engine, A: Render<E>, B: Render<E>>(
|
||||||
|
pub Direction, pub E::Unit, A, B, PhantomData<E>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<E: Engine, A: Render<E>, B: Render<E>> Split<E, A, B> {
|
||||||
|
pub fn new (direction: Direction, proportion: E::Unit, a: A, b: B) -> Self {
|
||||||
|
Self(direction, proportion, a, b, Default::default())
|
||||||
|
}
|
||||||
|
pub fn up (proportion: E::Unit, a: A, b: B) -> Self {
|
||||||
|
Self(Direction::Up, proportion, a, b, Default::default())
|
||||||
|
}
|
||||||
|
pub fn down (proportion: E::Unit, a: A, b: B) -> Self {
|
||||||
|
Self(Direction::Down, proportion, a, b, Default::default())
|
||||||
|
}
|
||||||
|
pub fn left (proportion: E::Unit, a: A, b: B) -> Self {
|
||||||
|
Self(Direction::Left, proportion, a, b, Default::default())
|
||||||
|
}
|
||||||
|
pub fn right (proportion: E::Unit, a: A, b: B) -> Self {
|
||||||
|
Self(Direction::Right, proportion, a, b, Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Split<E, A, B> {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(Some(to))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
let (a, b) = to.area().split_fixed(self.0, self.1);
|
||||||
|
to.render_in(a.into(), &self.2)?;
|
||||||
|
to.render_in(b.into(), &self.3)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
179
crates/tek_layout/src/stack.rs
Normal file
179
crates/tek_layout/src/stack.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! col {
|
||||||
|
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
||||||
|
($pat:pat in $collection:expr => $item:expr) => {
|
||||||
|
Stack::down(move |add|{
|
||||||
|
for $pat in $collection { add(&$item)?; }
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! col_up {
|
||||||
|
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
||||||
|
($pat:pat in $collection:expr => $item:expr) => {
|
||||||
|
Stack::up(move |add|{
|
||||||
|
for $pat in $collection { add(&$item)?; }
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[macro_export] macro_rules! row {
|
||||||
|
($($expr:expr),* $(,)?) => { Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
||||||
|
($pat:pat in $collection:expr => $item:expr) => {
|
||||||
|
Stack::right(move |add|{
|
||||||
|
for $pat in $collection { add(&$item)?; }
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stack<
|
||||||
|
E: Engine,
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||||
|
>(pub F, pub Direction, PhantomData<E>);
|
||||||
|
|
||||||
|
impl<
|
||||||
|
E: Engine,
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||||
|
> Stack<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)
|
||||||
|
}
|
||||||
|
#[inline] pub fn up (build: F) -> Self {
|
||||||
|
Self::new(Direction::Up, build)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, F> Render<E> for Stack<E, F>
|
||||||
|
where
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||||
|
{
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
match self.1 {
|
||||||
|
|
||||||
|
Direction::Down => {
|
||||||
|
let mut w: E::Unit = 0.into();
|
||||||
|
let mut h: E::Unit = 0.into();
|
||||||
|
(self.0)(&mut |component: &dyn Render<E>| {
|
||||||
|
let max = to.h().minus(h);
|
||||||
|
if max > E::Unit::ZERO() {
|
||||||
|
let item = component.push_y(h).max_y(max);
|
||||||
|
let size = item.min_size(to)?.map(|size|size.wh());
|
||||||
|
if let Some([width, height]) = size {
|
||||||
|
h = h + height.into();
|
||||||
|
w = w.max(width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(Some([w, h].into()))
|
||||||
|
},
|
||||||
|
|
||||||
|
Direction::Right => {
|
||||||
|
let mut w: E::Unit = 0.into();
|
||||||
|
let mut h: E::Unit = 0.into();
|
||||||
|
(self.0)(&mut |component: &dyn Render<E>| {
|
||||||
|
let max = to.w().minus(w);
|
||||||
|
if max > E::Unit::ZERO() {
|
||||||
|
let item = component.push_x(w).max_x(max);
|
||||||
|
let size = item.min_size(to)?.map(|size|size.wh());
|
||||||
|
if let Some([width, height]) = size {
|
||||||
|
w = w + width.into();
|
||||||
|
h = h.max(height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(Some([w, h].into()))
|
||||||
|
},
|
||||||
|
|
||||||
|
Direction::Up => {
|
||||||
|
let mut w: E::Unit = 0.into();
|
||||||
|
let mut h: E::Unit = 0.into();
|
||||||
|
(self.0)(&mut |component: &dyn Render<E>| {
|
||||||
|
let max = to.h().minus(h);
|
||||||
|
if max > E::Unit::ZERO() {
|
||||||
|
let item = component.max_y(to.h() - h);
|
||||||
|
let size = item.min_size(to)?.map(|size|size.wh());
|
||||||
|
if let Some([width, height]) = size {
|
||||||
|
h = h + height.into();
|
||||||
|
w = w.max(width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(Some([w, h].into()))
|
||||||
|
},
|
||||||
|
|
||||||
|
Direction::Left => {
|
||||||
|
let mut w: E::Unit = 0.into();
|
||||||
|
let mut h: E::Unit = 0.into();
|
||||||
|
(self.0)(&mut |component: &dyn Render<E>| {
|
||||||
|
if w < to.w() {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(Some([w, h].into()))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
let area = to.area();
|
||||||
|
let mut w = 0.into();
|
||||||
|
let mut h = 0.into();
|
||||||
|
match self.1 {
|
||||||
|
Direction::Down => {
|
||||||
|
(self.0)(&mut |item| {
|
||||||
|
if h < area.h() {
|
||||||
|
let item = item.push_y(h).max_y(area.h() - h);
|
||||||
|
let show = item.min_size(area.wh().into())?.map(|s|s.wh());
|
||||||
|
if let Some([width, height]) = show {
|
||||||
|
item.render(to)?;
|
||||||
|
h = h + height;
|
||||||
|
if width > w { w = width }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
Direction::Right => {
|
||||||
|
(self.0)(&mut |item| {
|
||||||
|
if w < area.w() {
|
||||||
|
let item = item.push_x(w).max_x(area.w() - w);
|
||||||
|
let show = item.min_size(area.wh().into())?.map(|s|s.wh());
|
||||||
|
if let Some([width, height]) = show {
|
||||||
|
item.render(to)?;
|
||||||
|
w = width + w;
|
||||||
|
if height > h { h = height }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
Direction::Up => {
|
||||||
|
(self.0)(&mut |item| {
|
||||||
|
if h < area.h() {
|
||||||
|
let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh());
|
||||||
|
if let Some([width, height]) = show {
|
||||||
|
item.push_y(area.h() - height).shrink_y(height).render(to)?;
|
||||||
|
h = h + height;
|
||||||
|
if width > w { w = width }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tek_core = { path = "../tek_core" }
|
tek_core = { path = "../tek_core" }
|
||||||
|
tek_layout = { path = "../tek_layout" }
|
||||||
tek_api = { path = "../tek_api" }
|
tek_api = { path = "../tek_api" }
|
||||||
#tek_snd = { path = "../tek_snd" }
|
#tek_snd = { path = "../tek_snd" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ pub(crate) use std::fs::read_dir;
|
||||||
pub(crate) use better_panic::{Settings, Verbosity};
|
pub(crate) use better_panic::{Settings, Verbosity};
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
tui
|
tui_engine
|
||||||
|
tui_engine_input
|
||||||
|
tui_engine_output
|
||||||
|
tui_engine_style
|
||||||
|
|
||||||
tui_app_arranger
|
tui_app_arranger
|
||||||
tui_app_sequencer
|
tui_app_sequencer
|
||||||
|
|
|
||||||
|
|
@ -1,693 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
pub(crate) use std::io::{stdout};
|
|
||||||
pub(crate) use std::thread::{spawn, JoinHandle};
|
|
||||||
pub(crate) use std::time::Duration;
|
|
||||||
pub(crate) use ratatui::buffer::Cell;
|
|
||||||
pub(crate) use crossterm::{ExecutableCommand};
|
|
||||||
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState};
|
|
||||||
pub use ratatui::prelude::{Rect, Style, Color, Buffer};
|
|
||||||
pub use ratatui::style::{Stylize, Modifier};
|
|
||||||
use ratatui::backend::{Backend, CrosstermBackend, ClearType};
|
|
||||||
use std::io::Stdout;
|
|
||||||
use crossterm::terminal::{
|
|
||||||
EnterAlternateScreen, LeaveAlternateScreen,
|
|
||||||
enable_raw_mode, disable_raw_mode
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Tui {
|
|
||||||
pub exited: Arc<AtomicBool>,
|
|
||||||
pub buffer: Buffer,
|
|
||||||
pub backend: CrosstermBackend<Stdout>,
|
|
||||||
pub area: [u16;4], // FIXME auto resize
|
|
||||||
}
|
|
||||||
impl Engine for Tui {
|
|
||||||
type Unit = u16;
|
|
||||||
type Size = [Self::Unit;2];
|
|
||||||
type Area = [Self::Unit;4];
|
|
||||||
type Input = TuiInput;
|
|
||||||
type Handled = bool;
|
|
||||||
type Output = TuiOutput;
|
|
||||||
fn exited (&self) -> bool {
|
|
||||||
self.exited.fetch_and(true, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn setup (&mut self) -> Usually<()> {
|
|
||||||
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
|
||||||
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
|
||||||
stdout().execute(LeaveAlternateScreen).unwrap();
|
|
||||||
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
better_panic_handler(info);
|
|
||||||
}));
|
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
|
||||||
self.backend.hide_cursor()?;
|
|
||||||
enable_raw_mode().map_err(Into::into)
|
|
||||||
}
|
|
||||||
fn teardown (&mut self) -> Usually<()> {
|
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
|
||||||
self.backend.show_cursor()?;
|
|
||||||
disable_raw_mode().map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Tui {
|
|
||||||
/// Run the main loop.
|
|
||||||
pub fn run <R: Component<Tui> + Sized + 'static> (
|
|
||||||
state: Arc<RwLock<R>>
|
|
||||||
) -> Usually<Arc<RwLock<R>>> {
|
|
||||||
let backend = CrosstermBackend::new(stdout());
|
|
||||||
let area = backend.size()?;
|
|
||||||
let engine = Self {
|
|
||||||
exited: Arc::new(AtomicBool::new(false)),
|
|
||||||
buffer: Buffer::empty(area),
|
|
||||||
area: area.xywh(),
|
|
||||||
backend,
|
|
||||||
};
|
|
||||||
let engine = Arc::new(RwLock::new(engine));
|
|
||||||
let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100));
|
|
||||||
engine.write().unwrap().setup()?;
|
|
||||||
let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10));
|
|
||||||
render_thread.join().expect("main thread failed");
|
|
||||||
engine.write().unwrap().teardown()?;
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
||||||
fn spawn_input_thread <R: Component<Tui> + Sized + 'static> (
|
|
||||||
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, poll: Duration
|
|
||||||
) -> JoinHandle<()> {
|
|
||||||
let exited = engine.read().unwrap().exited.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
spawn(move || loop {
|
|
||||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if ::crossterm::event::poll(poll).is_ok() {
|
|
||||||
let event = TuiEvent::Input(::crossterm::event::read().unwrap());
|
|
||||||
match event {
|
|
||||||
key!(Ctrl-KeyCode::Char('c')) => {
|
|
||||||
exited.store(true, Ordering::Relaxed);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
let exited = exited.clone();
|
|
||||||
if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) {
|
|
||||||
panic!("{e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn spawn_render_thread <R: Component<Tui> + Sized + 'static> (
|
|
||||||
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, sleep: Duration
|
|
||||||
) -> JoinHandle<()> {
|
|
||||||
let exited = engine.read().unwrap().exited.clone();
|
|
||||||
let engine = engine.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
let size = engine.read().unwrap().backend.size().expect("get size failed");
|
|
||||||
let mut buffer = Buffer::empty(size);
|
|
||||||
spawn(move || loop {
|
|
||||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let size = engine.read().unwrap().backend.size()
|
|
||||||
.expect("get size failed");
|
|
||||||
if let Ok(state) = state.try_read() {
|
|
||||||
if buffer.area != size {
|
|
||||||
engine.write().unwrap().backend.clear_region(ClearType::All)
|
|
||||||
.expect("clear failed");
|
|
||||||
buffer.resize(size);
|
|
||||||
buffer.reset();
|
|
||||||
}
|
|
||||||
let mut output = TuiOutput { buffer, area: size.xywh() };
|
|
||||||
state.render(&mut output).expect("render failed");
|
|
||||||
buffer = engine.write().unwrap().flip(output.buffer, size);
|
|
||||||
}
|
|
||||||
std::thread::sleep(sleep);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer {
|
|
||||||
if self.buffer.area != size {
|
|
||||||
self.backend.clear_region(ClearType::All).unwrap();
|
|
||||||
self.buffer.resize(size);
|
|
||||||
self.buffer.reset();
|
|
||||||
}
|
|
||||||
let updates = self.buffer.diff(&buffer);
|
|
||||||
self.backend.draw(updates.into_iter()).expect("failed to render");
|
|
||||||
self.backend.flush().expect("failed to flush output buffer");
|
|
||||||
std::mem::swap(&mut self.buffer, &mut buffer);
|
|
||||||
buffer.reset();
|
|
||||||
buffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct TuiInput { event: TuiEvent, exited: Arc<AtomicBool>, }
|
|
||||||
impl Input<Tui> for TuiInput {
|
|
||||||
type Event = TuiEvent;
|
|
||||||
fn event (&self) -> &TuiEvent { &self.event }
|
|
||||||
fn is_done (&self) -> bool { self.exited.fetch_and(true, Ordering::Relaxed) }
|
|
||||||
fn done (&self) { self.exited.store(true, Ordering::Relaxed); }
|
|
||||||
}
|
|
||||||
impl TuiInput {
|
|
||||||
// TODO remove
|
|
||||||
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
|
|
||||||
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<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 struct TuiOutput { pub buffer: Buffer, pub area: [u16;4] }
|
|
||||||
impl Output<Tui> for TuiOutput {
|
|
||||||
#[inline] fn area (&self) -> [u16;4] { self.area }
|
|
||||||
#[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area }
|
|
||||||
#[inline] fn render_in (&mut self,
|
|
||||||
area: [u16;4],
|
|
||||||
widget: &dyn Render<Engine = Tui>
|
|
||||||
) -> Usually<()> {
|
|
||||||
let last = self.area();
|
|
||||||
*self.area_mut() = area;
|
|
||||||
widget.render(self)?;
|
|
||||||
*self.area_mut() = last;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TuiOutput {
|
|
||||||
pub fn buffer_update (&mut self,
|
|
||||||
area: [u16;4],
|
|
||||||
callback: &impl Fn(&mut Cell, u16, u16)
|
|
||||||
) {
|
|
||||||
buffer_update(&mut self.buffer, area, callback);
|
|
||||||
}
|
|
||||||
pub fn fill_bold (&mut self, area: [u16;4], on: bool) {
|
|
||||||
if on {
|
|
||||||
self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD))
|
|
||||||
} else {
|
|
||||||
self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn fill_bg (&mut self, area: [u16;4], color: Color) {
|
|
||||||
self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);})
|
|
||||||
}
|
|
||||||
pub fn fill_fg (&mut self, area: [u16;4], color: Color) {
|
|
||||||
self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);})
|
|
||||||
}
|
|
||||||
pub fn fill_ul (&mut self, area: [u16;4], color: Color) {
|
|
||||||
self.buffer_update(area, &|cell,_,_|{
|
|
||||||
cell.modifier = ratatui::prelude::Modifier::UNDERLINED;
|
|
||||||
cell.underline_color = color;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn fill_char (&mut self, area: [u16;4], c: char) {
|
|
||||||
self.buffer_update(area, &|cell,_,_|{cell.set_char(c);})
|
|
||||||
}
|
|
||||||
pub fn make_dim (&mut self) {
|
|
||||||
for cell in self.buffer.content.iter_mut() {
|
|
||||||
cell.bg = ratatui::style::Color::Rgb(30,30,30);
|
|
||||||
cell.fg = ratatui::style::Color::Rgb(100,100,100);
|
|
||||||
cell.modifier = ratatui::style::Modifier::DIM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn blit (
|
|
||||||
&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>
|
|
||||||
) {
|
|
||||||
let text = text.as_ref();
|
|
||||||
let buf = &mut self.buffer;
|
|
||||||
if x < buf.area.width && y < buf.area.height {
|
|
||||||
buf.set_string(x, y, text, style.unwrap_or(Style::default()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn with_rect (&mut self, area: [u16;4]) -> &mut Self {
|
|
||||||
self.area = area;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum TuiEvent {
|
|
||||||
/// Terminal input
|
|
||||||
Input(::crossterm::event::Event),
|
|
||||||
/// Update values but not the whole form.
|
|
||||||
Update,
|
|
||||||
/// Update the whole form.
|
|
||||||
Redraw,
|
|
||||||
/// Device gains focus
|
|
||||||
Focus,
|
|
||||||
/// Device loses focus
|
|
||||||
Blur,
|
|
||||||
// /// JACK notification
|
|
||||||
// Jack(JackEvent)
|
|
||||||
}
|
|
||||||
//impl Area<u16> for Rect {
|
|
||||||
//fn x (&self) -> u16 { self.x }
|
|
||||||
//fn y (&self) -> u16 { self.y }
|
|
||||||
//fn w (&self) -> u16 { self.width }
|
|
||||||
//fn h (&self) -> u16 { self.height }
|
|
||||||
//}
|
|
||||||
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
|
||||||
match (lower, upper) {
|
|
||||||
(true, true) => Some('█'),
|
|
||||||
(true, false) => Some('▄'),
|
|
||||||
(false, true) => Some('▀'),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BigBuffer {
|
|
||||||
pub width: usize,
|
|
||||||
pub height: usize,
|
|
||||||
pub content: Vec<Cell>
|
|
||||||
}
|
|
||||||
impl BigBuffer {
|
|
||||||
pub fn new (width: usize, height: usize) -> Self {
|
|
||||||
Self { width, height, content: vec![Cell::default(); width*height] }
|
|
||||||
}
|
|
||||||
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
|
||||||
let i = self.index_of(x, y);
|
|
||||||
self.content.get(i)
|
|
||||||
}
|
|
||||||
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
|
||||||
let i = self.index_of(x, y);
|
|
||||||
self.content.get_mut(i)
|
|
||||||
}
|
|
||||||
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
|
||||||
y * self.width + x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
|
|
||||||
for row in 0..area.h() {
|
|
||||||
let y = area.y() + row;
|
|
||||||
for col in 0..area.w() {
|
|
||||||
let x = area.x() + col;
|
|
||||||
if x < buf.area.width && y < buf.area.height {
|
|
||||||
callback(buf.get_mut(x, y), col, row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//impl Render for &str {
|
|
||||||
//type Engine = Tui;
|
|
||||||
//fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
//// TODO: line breaks
|
|
||||||
//Ok(Some([self.chars().count() as u16, 1]))
|
|
||||||
//}
|
|
||||||
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
//let [x, y, ..] = to.area();
|
|
||||||
////let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
|
||||||
//Ok(to.blit(&self, x, y, None))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//impl Render for String {
|
|
||||||
//type Engine = Tui;
|
|
||||||
//fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
//// TODO: line breaks
|
|
||||||
//Ok(Some([self.chars().count() as u16, 1]))
|
|
||||||
//}
|
|
||||||
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
//let [x, y, ..] = to.area();
|
|
||||||
////let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
|
||||||
//Ok(to.blit(&self, x, y, None))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//impl<T: Render<Engine = Tui>> Render for DebugOverlay<Tui, T> {
|
|
||||||
//type Engine = Tui;
|
|
||||||
//fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
//self.0.min_size(to)
|
|
||||||
//}
|
|
||||||
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
//let [x, y, w, h] = to.area();
|
|
||||||
//self.0.render(to)?;
|
|
||||||
//Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
pub struct Styled<T: Render<Engine = Tui>>(pub Option<Style>, pub T);
|
|
||||||
impl Render for Styled<&str> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
Ok(Some([self.1.chars().count() as u16, 1]))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
// FIXME
|
|
||||||
let [x, y, ..] = to.area();
|
|
||||||
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
|
||||||
Ok(to.blit(&self.1, x, y, None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub trait TuiStyle: Render<Engine = Tui> + Sized {
|
|
||||||
fn fg (self, color: Color) -> impl Render<Engine = Tui> {
|
|
||||||
Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
|
|
||||||
}
|
|
||||||
fn bg (self, color: Color) -> impl Render<Engine = Tui> {
|
|
||||||
Layers::new(move |add|{ add(&Background(color))?; add(&self) })
|
|
||||||
}
|
|
||||||
fn bold (self, on: bool) -> impl Render<Engine = Tui> {
|
|
||||||
Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
|
|
||||||
}
|
|
||||||
fn border (self, style: impl BorderStyle) -> impl Render<Engine = Tui> {
|
|
||||||
Bordered(style, self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<W: Render<Engine = Tui>> TuiStyle for W {}
|
|
||||||
pub struct Bold(pub bool);
|
|
||||||
impl Render for Bold {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_bold(to.area(), self.0)) }
|
|
||||||
}
|
|
||||||
pub struct Foreground(pub Color);
|
|
||||||
impl Render for Foreground {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_fg(to.area(), self.0)) }
|
|
||||||
}
|
|
||||||
pub struct Background(pub Color);
|
|
||||||
impl Render for Background {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_bg(to.area(), self.0)) }
|
|
||||||
}
|
|
||||||
pub struct Border<S: BorderStyle>(pub S);
|
|
||||||
impl<S: BorderStyle> Render for Border<S> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
Ok(Some([0, 0]))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
let area = to.area();
|
|
||||||
if area.w() > 0 && area.y() > 0 {
|
|
||||||
to.blit(&self.0.nw(), area.x(), area.y(), self.0.style());
|
|
||||||
to.blit(&self.0.ne(), area.x() + area.w() - 1, area.y(), self.0.style());
|
|
||||||
to.blit(&self.0.sw(), area.x(), area.y() + area.h() - 1, self.0.style());
|
|
||||||
to.blit(&self.0.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.0.style());
|
|
||||||
for x in area.x()+1..area.x()+area.w()-1 {
|
|
||||||
to.blit(&self.0.n(), x, area.y(), self.0.style());
|
|
||||||
to.blit(&self.0.s(), x, area.y() + area.h() - 1, self.0.style());
|
|
||||||
}
|
|
||||||
for y in area.y()+1..area.y()+area.h()-1 {
|
|
||||||
to.blit(&self.0.w(), area.x(), y, self.0.style());
|
|
||||||
to.blit(&self.0.e(), area.x() + area.w() - 1, y, self.0.style());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct Bordered<S: BorderStyle, W: Render<Engine = Tui>>(pub S, pub W);
|
|
||||||
impl<S: BorderStyle, W: Render<Engine = Tui>> Content for Bordered<S, W> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn content (&self) -> impl Render<Engine = Tui> {
|
|
||||||
let content: &dyn Render<Engine = Tui> = &self.1;
|
|
||||||
lay! { content.inset_xy(1, 1), Border(self.0) }.fill_xy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub trait BorderStyle: Send + Sync + Copy {
|
|
||||||
const NW: &'static str = "";
|
|
||||||
const N: &'static str = "";
|
|
||||||
const NE: &'static str = "";
|
|
||||||
const E: &'static str = "";
|
|
||||||
const SE: &'static str = "";
|
|
||||||
const S: &'static str = "";
|
|
||||||
const SW: &'static str = "";
|
|
||||||
const W: &'static str = "";
|
|
||||||
fn n (&self) -> &str { Self::N }
|
|
||||||
fn s (&self) -> &str { Self::S }
|
|
||||||
fn e (&self) -> &str { Self::E }
|
|
||||||
fn w (&self) -> &str { Self::W }
|
|
||||||
fn nw (&self) -> &str { Self::NW }
|
|
||||||
fn ne (&self) -> &str { Self::NE }
|
|
||||||
fn sw (&self) -> &str { Self::SW }
|
|
||||||
fn se (&self) -> &str { Self::SE }
|
|
||||||
#[inline] fn draw <'a> (
|
|
||||||
&self, to: &mut TuiOutput
|
|
||||||
) -> Usually<()> {
|
|
||||||
self.draw_horizontal(to, None)?;
|
|
||||||
self.draw_vertical(to, None)?;
|
|
||||||
self.draw_corners(to, None)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[inline] fn draw_horizontal (
|
|
||||||
&self, to: &mut TuiOutput, style: Option<Style>
|
|
||||||
) -> Usually<[u16;4]> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_horizontal());
|
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
|
||||||
for x in x..x2.saturating_sub(1) {
|
|
||||||
self.draw_north(to, x, y, style);
|
|
||||||
self.draw_south(to, x, y2.saturating_sub(1), style);
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
#[inline] fn draw_north (
|
|
||||||
&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>
|
|
||||||
) -> () {
|
|
||||||
to.blit(&Self::N, x, y, style)
|
|
||||||
}
|
|
||||||
#[inline] fn draw_south (
|
|
||||||
&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>
|
|
||||||
) -> () {
|
|
||||||
to.blit(&Self::S, x, y, style)
|
|
||||||
}
|
|
||||||
#[inline] fn draw_vertical (
|
|
||||||
&self, to: &mut TuiOutput, style: Option<Style>
|
|
||||||
) -> Usually<[u16;4]> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_vertical());
|
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
|
||||||
for y in y..y2.saturating_sub(1) {
|
|
||||||
to.blit(&Self::W, x, y, style);
|
|
||||||
to.blit(&Self::E, x2.saturating_sub(1), y, style);
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
#[inline] fn draw_corners (
|
|
||||||
&self, to: &mut TuiOutput, style: Option<Style>
|
|
||||||
) -> Usually<[u16;4]> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_corners());
|
|
||||||
let [x, y, width, height] = area.xywh();
|
|
||||||
if width > 0 && height > 0 {
|
|
||||||
to.blit(&Self::NW, x, y, style);
|
|
||||||
to.blit(&Self::NE, x + width - 1, y, style);
|
|
||||||
to.blit(&Self::SW, x, y + height - 1, style);
|
|
||||||
to.blit(&Self::SE, x + width - 1, y + height - 1, style);
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
#[inline] fn style (&self) -> Option<Style> { None }
|
|
||||||
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
|
||||||
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
|
|
||||||
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
|
|
||||||
}
|
|
||||||
macro_rules! border {
|
|
||||||
($($T:ident {
|
|
||||||
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
|
||||||
$($x:tt)*
|
|
||||||
}),+) => {$(
|
|
||||||
impl BorderStyle for $T {
|
|
||||||
const NW: &'static str = $nw;
|
|
||||||
const N: &'static str = $n;
|
|
||||||
const NE: &'static str = $ne;
|
|
||||||
const W: &'static str = $w;
|
|
||||||
const E: &'static str = $e;
|
|
||||||
const SW: &'static str = $sw;
|
|
||||||
const S: &'static str = $s;
|
|
||||||
const SE: &'static str = $se;
|
|
||||||
$($x)*
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct $T(pub Style);
|
|
||||||
impl Render for $T {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> { self.draw(to) }
|
|
||||||
}
|
|
||||||
)+}
|
|
||||||
}
|
|
||||||
border! {
|
|
||||||
Square {
|
|
||||||
"┌" "─" "┐"
|
|
||||||
"│" "│"
|
|
||||||
"└" "─" "┘" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
SquareBold {
|
|
||||||
"┏" "━" "┓"
|
|
||||||
"┃" "┃"
|
|
||||||
"┗" "━" "┛" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
Tab {
|
|
||||||
"╭" "─" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"│" " " "│" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
Lozenge {
|
|
||||||
"╭" "─" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "─" "╯" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
Brace {
|
|
||||||
"╭" "" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "" "╯" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
LozengeDotted {
|
|
||||||
"╭" "┅" "╮"
|
|
||||||
"┇" "┇"
|
|
||||||
"╰" "┅" "╯" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
Quarter {
|
|
||||||
"▎" "▔" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "▁" "🮇" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
QuarterV {
|
|
||||||
"▎" "" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "" "🮇" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
Chamfer {
|
|
||||||
"🭂" "▔" "🭍"
|
|
||||||
"▎" "🮇"
|
|
||||||
"🭓" "▁" "🭞" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
Corners {
|
|
||||||
"🬆" "" "🬊" // 🬴 🬸
|
|
||||||
"" ""
|
|
||||||
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
},
|
|
||||||
CornersTall {
|
|
||||||
"🭽" "" "🭾"
|
|
||||||
"" ""
|
|
||||||
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const CORNERS: CornersTall = CornersTall(Style {
|
|
||||||
fg: Some(Color::Rgb(96, 255, 32)),
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::empty(),
|
|
||||||
sub_modifier: Modifier::DIM
|
|
||||||
});
|
|
||||||
/// Define a key
|
|
||||||
pub const fn key (code: KeyCode) -> KeyEvent {
|
|
||||||
let modifiers = KeyModifiers::NONE;
|
|
||||||
let kind = KeyEventKind::Press;
|
|
||||||
let state = KeyEventState::NONE;
|
|
||||||
KeyEvent { code, modifiers, kind, state }
|
|
||||||
}
|
|
||||||
/// Add Ctrl modifier to key
|
|
||||||
pub const fn ctrl (key: KeyEvent) -> KeyEvent {
|
|
||||||
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key }
|
|
||||||
}
|
|
||||||
/// Add Alt modifier to key
|
|
||||||
pub const fn alt (key: KeyEvent) -> KeyEvent {
|
|
||||||
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key }
|
|
||||||
}
|
|
||||||
/// Add Shift modifier to key
|
|
||||||
pub const fn shift (key: KeyEvent) -> KeyEvent {
|
|
||||||
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key }
|
|
||||||
}
|
|
||||||
/// 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<bool>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// 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
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
(Shift-$code:pat) => {
|
|
||||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
|
||||||
code: $code,
|
|
||||||
modifiers: crossterm::event::KeyModifiers::SHIFT,
|
|
||||||
kind: crossterm::event::KeyEventKind::Press,
|
|
||||||
state: crossterm::event::KeyEventState::NONE
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[macro_export] macro_rules! key_lit {
|
|
||||||
($code:expr) => {
|
|
||||||
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:expr) => {
|
|
||||||
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:expr) => {
|
|
||||||
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
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
(Shift-$code:expr) => {
|
|
||||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
|
||||||
code: $code,
|
|
||||||
modifiers: crossterm::event::KeyModifiers::SHIFT,
|
|
||||||
kind: crossterm::event::KeyEventKind::Press,
|
|
||||||
state: crossterm::event::KeyEventState::NONE
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
138
crates/tek_tui/src/tui_engine.rs
Normal file
138
crates/tek_tui/src/tui_engine.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
use crate::*;
|
||||||
|
pub(crate) use std::io::{stdout};
|
||||||
|
pub(crate) use std::thread::{spawn, JoinHandle};
|
||||||
|
pub(crate) use std::time::Duration;
|
||||||
|
pub(crate) use ratatui::buffer::Cell;
|
||||||
|
pub(crate) use crossterm::{ExecutableCommand};
|
||||||
|
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState};
|
||||||
|
pub use ratatui::prelude::{Rect, Style, Color, Buffer};
|
||||||
|
pub use ratatui::style::{Stylize, Modifier};
|
||||||
|
use ratatui::backend::{Backend, CrosstermBackend, ClearType};
|
||||||
|
use std::io::Stdout;
|
||||||
|
use crossterm::terminal::{
|
||||||
|
EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
|
enable_raw_mode, disable_raw_mode
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Tui {
|
||||||
|
pub exited: Arc<AtomicBool>,
|
||||||
|
pub buffer: Buffer,
|
||||||
|
pub backend: CrosstermBackend<Stdout>,
|
||||||
|
pub area: [u16;4], // FIXME auto resize
|
||||||
|
}
|
||||||
|
impl Engine for Tui {
|
||||||
|
type Unit = u16;
|
||||||
|
type Size = [Self::Unit;2];
|
||||||
|
type Area = [Self::Unit;4];
|
||||||
|
type Input = TuiInput;
|
||||||
|
type Handled = bool;
|
||||||
|
type Output = TuiOutput;
|
||||||
|
fn exited (&self) -> bool {
|
||||||
|
self.exited.fetch_and(true, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn setup (&mut self) -> Usually<()> {
|
||||||
|
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
||||||
|
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
||||||
|
stdout().execute(LeaveAlternateScreen).unwrap();
|
||||||
|
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
better_panic_handler(info);
|
||||||
|
}));
|
||||||
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
self.backend.hide_cursor()?;
|
||||||
|
enable_raw_mode().map_err(Into::into)
|
||||||
|
}
|
||||||
|
fn teardown (&mut self) -> Usually<()> {
|
||||||
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
self.backend.show_cursor()?;
|
||||||
|
disable_raw_mode().map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Tui {
|
||||||
|
/// Run the main loop.
|
||||||
|
pub fn run <R: Component<Tui> + Sized + 'static> (
|
||||||
|
state: Arc<RwLock<R>>
|
||||||
|
) -> Usually<Arc<RwLock<R>>> {
|
||||||
|
let backend = CrosstermBackend::new(stdout());
|
||||||
|
let area = backend.size()?;
|
||||||
|
let engine = Self {
|
||||||
|
exited: Arc::new(AtomicBool::new(false)),
|
||||||
|
buffer: Buffer::empty(area),
|
||||||
|
area: area.xywh(),
|
||||||
|
backend,
|
||||||
|
};
|
||||||
|
let engine = Arc::new(RwLock::new(engine));
|
||||||
|
let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100));
|
||||||
|
engine.write().unwrap().setup()?;
|
||||||
|
let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10));
|
||||||
|
render_thread.join().expect("main thread failed");
|
||||||
|
engine.write().unwrap().teardown()?;
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
fn spawn_input_thread <R: Component<Tui> + Sized + 'static> (
|
||||||
|
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, poll: Duration
|
||||||
|
) -> JoinHandle<()> {
|
||||||
|
let exited = engine.read().unwrap().exited.clone();
|
||||||
|
let state = state.clone();
|
||||||
|
spawn(move || loop {
|
||||||
|
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ::crossterm::event::poll(poll).is_ok() {
|
||||||
|
let event = TuiEvent::Input(::crossterm::event::read().unwrap());
|
||||||
|
match event {
|
||||||
|
key!(Ctrl-KeyCode::Char('c')) => {
|
||||||
|
exited.store(true, Ordering::Relaxed);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let exited = exited.clone();
|
||||||
|
if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) {
|
||||||
|
panic!("{e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn spawn_render_thread <R: Component<Tui> + Sized + 'static> (
|
||||||
|
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, sleep: Duration
|
||||||
|
) -> JoinHandle<()> {
|
||||||
|
let exited = engine.read().unwrap().exited.clone();
|
||||||
|
let engine = engine.clone();
|
||||||
|
let state = state.clone();
|
||||||
|
let size = engine.read().unwrap().backend.size().expect("get size failed");
|
||||||
|
let mut buffer = Buffer::empty(size);
|
||||||
|
spawn(move || loop {
|
||||||
|
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let size = engine.read().unwrap().backend.size()
|
||||||
|
.expect("get size failed");
|
||||||
|
if let Ok(state) = state.try_read() {
|
||||||
|
if buffer.area != size {
|
||||||
|
engine.write().unwrap().backend.clear_region(ClearType::All)
|
||||||
|
.expect("clear failed");
|
||||||
|
buffer.resize(size);
|
||||||
|
buffer.reset();
|
||||||
|
}
|
||||||
|
let mut output = TuiOutput { buffer, area: size.xywh() };
|
||||||
|
state.render(&mut output).expect("render failed");
|
||||||
|
buffer = engine.write().unwrap().flip(output.buffer, size);
|
||||||
|
}
|
||||||
|
std::thread::sleep(sleep);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer {
|
||||||
|
if self.buffer.area != size {
|
||||||
|
self.backend.clear_region(ClearType::All).unwrap();
|
||||||
|
self.buffer.resize(size);
|
||||||
|
self.buffer.reset();
|
||||||
|
}
|
||||||
|
let updates = self.buffer.diff(&buffer);
|
||||||
|
self.backend.draw(updates.into_iter()).expect("failed to render");
|
||||||
|
self.backend.flush().expect("failed to flush output buffer");
|
||||||
|
std::mem::swap(&mut self.buffer, &mut buffer);
|
||||||
|
buffer.reset();
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
181
crates/tek_tui/src/tui_engine_input.rs
Normal file
181
crates/tek_tui/src/tui_engine_input.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub struct TuiInput {
|
||||||
|
exited: Arc<AtomicBool>,
|
||||||
|
event: TuiEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TuiEvent {
|
||||||
|
/// Terminal input
|
||||||
|
Input(::crossterm::event::Event),
|
||||||
|
/// Update values but not the whole form.
|
||||||
|
Update,
|
||||||
|
/// Update the whole form.
|
||||||
|
Redraw,
|
||||||
|
/// Device gains focus
|
||||||
|
Focus,
|
||||||
|
/// Device loses focus
|
||||||
|
Blur,
|
||||||
|
// /// JACK notification
|
||||||
|
// Jack(JackEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input<Tui> for TuiInput {
|
||||||
|
type Event = TuiEvent;
|
||||||
|
fn event (&self) -> &TuiEvent { &self.event }
|
||||||
|
fn is_done (&self) -> bool { self.exited.fetch_and(true, Ordering::Relaxed) }
|
||||||
|
fn done (&self) { self.exited.store(true, Ordering::Relaxed); }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TuiInput {
|
||||||
|
// TODO remove
|
||||||
|
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
|
||||||
|
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<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>];
|
||||||
|
|
||||||
|
/// Define a key
|
||||||
|
pub const fn key (code: KeyCode) -> KeyEvent {
|
||||||
|
let modifiers = KeyModifiers::NONE;
|
||||||
|
let kind = KeyEventKind::Press;
|
||||||
|
let state = KeyEventState::NONE;
|
||||||
|
KeyEvent { code, modifiers, kind, state }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add Ctrl modifier to key
|
||||||
|
pub const fn ctrl (key: KeyEvent) -> KeyEvent {
|
||||||
|
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add Alt modifier to key
|
||||||
|
pub const fn alt (key: KeyEvent) -> KeyEvent {
|
||||||
|
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add Shift modifier to key
|
||||||
|
pub const fn shift (key: KeyEvent) -> KeyEvent {
|
||||||
|
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<bool>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
(Shift-$code:pat) => {
|
||||||
|
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||||
|
code: $code,
|
||||||
|
modifiers: crossterm::event::KeyModifiers::SHIFT,
|
||||||
|
kind: crossterm::event::KeyEventKind::Press,
|
||||||
|
state: crossterm::event::KeyEventState::NONE
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! key_lit {
|
||||||
|
($code:expr) => {
|
||||||
|
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:expr) => {
|
||||||
|
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:expr) => {
|
||||||
|
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
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
(Shift-$code:expr) => {
|
||||||
|
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||||
|
code: $code,
|
||||||
|
modifiers: crossterm::event::KeyModifiers::SHIFT,
|
||||||
|
kind: crossterm::event::KeyEventKind::Press,
|
||||||
|
state: crossterm::event::KeyEventState::NONE
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
161
crates/tek_tui/src/tui_engine_output.rs
Normal file
161
crates/tek_tui/src/tui_engine_output.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
use crate::*;
|
||||||
|
use ratatui::buffer::Cell;
|
||||||
|
|
||||||
|
pub struct TuiOutput {
|
||||||
|
pub buffer: Buffer,
|
||||||
|
pub area: [u16;4]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Output<Tui> for TuiOutput {
|
||||||
|
#[inline] fn area (&self) -> [u16;4] { self.area }
|
||||||
|
#[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area }
|
||||||
|
#[inline] fn render_in (&mut self,
|
||||||
|
area: [u16;4],
|
||||||
|
widget: &dyn Render<Engine = Tui>
|
||||||
|
) -> Usually<()> {
|
||||||
|
let last = self.area();
|
||||||
|
*self.area_mut() = area;
|
||||||
|
widget.render(self)?;
|
||||||
|
*self.area_mut() = last;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TuiOutput {
|
||||||
|
pub fn buffer_update (&mut self,
|
||||||
|
area: [u16;4],
|
||||||
|
callback: &impl Fn(&mut Cell, u16, u16)
|
||||||
|
) {
|
||||||
|
buffer_update(&mut self.buffer, area, callback);
|
||||||
|
}
|
||||||
|
pub fn fill_bold (&mut self, area: [u16;4], on: bool) {
|
||||||
|
if on {
|
||||||
|
self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD))
|
||||||
|
} else {
|
||||||
|
self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fill_bg (&mut self, area: [u16;4], color: Color) {
|
||||||
|
self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);})
|
||||||
|
}
|
||||||
|
pub fn fill_fg (&mut self, area: [u16;4], color: Color) {
|
||||||
|
self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);})
|
||||||
|
}
|
||||||
|
pub fn fill_ul (&mut self, area: [u16;4], color: Color) {
|
||||||
|
self.buffer_update(area, &|cell,_,_|{
|
||||||
|
cell.modifier = ratatui::prelude::Modifier::UNDERLINED;
|
||||||
|
cell.underline_color = color;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn fill_char (&mut self, area: [u16;4], c: char) {
|
||||||
|
self.buffer_update(area, &|cell,_,_|{cell.set_char(c);})
|
||||||
|
}
|
||||||
|
pub fn make_dim (&mut self) {
|
||||||
|
for cell in self.buffer.content.iter_mut() {
|
||||||
|
cell.bg = ratatui::style::Color::Rgb(30,30,30);
|
||||||
|
cell.fg = ratatui::style::Color::Rgb(100,100,100);
|
||||||
|
cell.modifier = ratatui::style::Modifier::DIM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn blit (
|
||||||
|
&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>
|
||||||
|
) {
|
||||||
|
let text = text.as_ref();
|
||||||
|
let buf = &mut self.buffer;
|
||||||
|
if x < buf.area.width && y < buf.area.height {
|
||||||
|
buf.set_string(x, y, text, style.unwrap_or(Style::default()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn with_rect (&mut self, area: [u16;4]) -> &mut Self {
|
||||||
|
self.area = area;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//impl Area<u16> for Rect {
|
||||||
|
//fn x (&self) -> u16 { self.x }
|
||||||
|
//fn y (&self) -> u16 { self.y }
|
||||||
|
//fn w (&self) -> u16 { self.width }
|
||||||
|
//fn h (&self) -> u16 { self.height }
|
||||||
|
//}
|
||||||
|
|
||||||
|
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
||||||
|
match (lower, upper) {
|
||||||
|
(true, true) => Some('█'),
|
||||||
|
(true, false) => Some('▄'),
|
||||||
|
(false, true) => Some('▀'),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BigBuffer {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub content: Vec<Cell>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BigBuffer {
|
||||||
|
pub fn new (width: usize, height: usize) -> Self {
|
||||||
|
Self { width, height, content: vec![Cell::default(); width*height] }
|
||||||
|
}
|
||||||
|
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
||||||
|
let i = self.index_of(x, y);
|
||||||
|
self.content.get(i)
|
||||||
|
}
|
||||||
|
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
||||||
|
let i = self.index_of(x, y);
|
||||||
|
self.content.get_mut(i)
|
||||||
|
}
|
||||||
|
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
||||||
|
y * self.width + x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
|
||||||
|
for row in 0..area.h() {
|
||||||
|
let y = area.y() + row;
|
||||||
|
for col in 0..area.w() {
|
||||||
|
let x = area.x() + col;
|
||||||
|
if x < buf.area.width && y < buf.area.height {
|
||||||
|
callback(buf.get_mut(x, y), col, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//impl Render for &str {
|
||||||
|
//type Engine = Tui;
|
||||||
|
//fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
//// TODO: line breaks
|
||||||
|
//Ok(Some([self.chars().count() as u16, 1]))
|
||||||
|
//}
|
||||||
|
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
//let [x, y, ..] = to.area();
|
||||||
|
////let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
||||||
|
//Ok(to.blit(&self, x, y, None))
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//impl Render for String {
|
||||||
|
//type Engine = Tui;
|
||||||
|
//fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
//// TODO: line breaks
|
||||||
|
//Ok(Some([self.chars().count() as u16, 1]))
|
||||||
|
//}
|
||||||
|
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
//let [x, y, ..] = to.area();
|
||||||
|
////let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
||||||
|
//Ok(to.blit(&self, x, y, None))
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//impl<T: Render<Engine = Tui>> Render for DebugOverlay<Tui, T> {
|
||||||
|
//type Engine = Tui;
|
||||||
|
//fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
//self.0.min_size(to)
|
||||||
|
//}
|
||||||
|
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
//let [x, y, w, h] = to.area();
|
||||||
|
//self.0.render(to)?;
|
||||||
|
//Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
|
||||||
|
//}
|
||||||
|
//}
|
||||||
247
crates/tek_tui/src/tui_engine_style.rs
Normal file
247
crates/tek_tui/src/tui_engine_style.rs
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
use crate::*;
|
||||||
|
pub struct Styled<T: Render<Engine = Tui>>(pub Option<Style>, pub T);
|
||||||
|
impl Render for Styled<&str> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
Ok(Some([self.1.chars().count() as u16, 1]))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
// FIXME
|
||||||
|
let [x, y, ..] = to.area();
|
||||||
|
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
||||||
|
Ok(to.blit(&self.1, x, y, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub trait TuiStyle: Render<Engine = Tui> + Sized {
|
||||||
|
fn fg (self, color: Color) -> impl Render<Engine = Tui> {
|
||||||
|
Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
|
||||||
|
}
|
||||||
|
fn bg (self, color: Color) -> impl Render<Engine = Tui> {
|
||||||
|
Layers::new(move |add|{ add(&Background(color))?; add(&self) })
|
||||||
|
}
|
||||||
|
fn bold (self, on: bool) -> impl Render<Engine = Tui> {
|
||||||
|
Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
|
||||||
|
}
|
||||||
|
fn border (self, style: impl BorderStyle) -> impl Render<Engine = Tui> {
|
||||||
|
Bordered(style, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<W: Render<Engine = Tui>> TuiStyle for W {}
|
||||||
|
pub struct Bold(pub bool);
|
||||||
|
impl Render for Bold {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_bold(to.area(), self.0)) }
|
||||||
|
}
|
||||||
|
pub struct Foreground(pub Color);
|
||||||
|
impl Render for Foreground {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_fg(to.area(), self.0)) }
|
||||||
|
}
|
||||||
|
pub struct Background(pub Color);
|
||||||
|
impl Render for Background {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> { Ok(to.fill_bg(to.area(), self.0)) }
|
||||||
|
}
|
||||||
|
pub struct Border<S: BorderStyle>(pub S);
|
||||||
|
impl<S: BorderStyle> Render for Border<S> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
Ok(Some([0, 0]))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
let area = to.area();
|
||||||
|
if area.w() > 0 && area.y() > 0 {
|
||||||
|
to.blit(&self.0.nw(), area.x(), area.y(), self.0.style());
|
||||||
|
to.blit(&self.0.ne(), area.x() + area.w() - 1, area.y(), self.0.style());
|
||||||
|
to.blit(&self.0.sw(), area.x(), area.y() + area.h() - 1, self.0.style());
|
||||||
|
to.blit(&self.0.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.0.style());
|
||||||
|
for x in area.x()+1..area.x()+area.w()-1 {
|
||||||
|
to.blit(&self.0.n(), x, area.y(), self.0.style());
|
||||||
|
to.blit(&self.0.s(), x, area.y() + area.h() - 1, self.0.style());
|
||||||
|
}
|
||||||
|
for y in area.y()+1..area.y()+area.h()-1 {
|
||||||
|
to.blit(&self.0.w(), area.x(), y, self.0.style());
|
||||||
|
to.blit(&self.0.e(), area.x() + area.w() - 1, y, self.0.style());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Bordered<S: BorderStyle, W: Render<Engine = Tui>>(pub S, pub W);
|
||||||
|
impl<S: BorderStyle, W: Render<Engine = Tui>> Content for Bordered<S, W> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn content (&self) -> impl Render<Engine = Tui> {
|
||||||
|
let content: &dyn Render<Engine = Tui> = &self.1;
|
||||||
|
lay! { content.inset_xy(1, 1), Border(self.0) }.fill_xy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub trait BorderStyle: Send + Sync + Copy {
|
||||||
|
const NW: &'static str = "";
|
||||||
|
const N: &'static str = "";
|
||||||
|
const NE: &'static str = "";
|
||||||
|
const E: &'static str = "";
|
||||||
|
const SE: &'static str = "";
|
||||||
|
const S: &'static str = "";
|
||||||
|
const SW: &'static str = "";
|
||||||
|
const W: &'static str = "";
|
||||||
|
fn n (&self) -> &str { Self::N }
|
||||||
|
fn s (&self) -> &str { Self::S }
|
||||||
|
fn e (&self) -> &str { Self::E }
|
||||||
|
fn w (&self) -> &str { Self::W }
|
||||||
|
fn nw (&self) -> &str { Self::NW }
|
||||||
|
fn ne (&self) -> &str { Self::NE }
|
||||||
|
fn sw (&self) -> &str { Self::SW }
|
||||||
|
fn se (&self) -> &str { Self::SE }
|
||||||
|
#[inline] fn draw <'a> (
|
||||||
|
&self, to: &mut TuiOutput
|
||||||
|
) -> Usually<()> {
|
||||||
|
self.draw_horizontal(to, None)?;
|
||||||
|
self.draw_vertical(to, None)?;
|
||||||
|
self.draw_corners(to, None)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[inline] fn draw_horizontal (
|
||||||
|
&self, to: &mut TuiOutput, style: Option<Style>
|
||||||
|
) -> Usually<[u16;4]> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_horizontal());
|
||||||
|
let [x, x2, y, y2] = area.lrtb();
|
||||||
|
for x in x..x2.saturating_sub(1) {
|
||||||
|
self.draw_north(to, x, y, style);
|
||||||
|
self.draw_south(to, x, y2.saturating_sub(1), style);
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
#[inline] fn draw_north (
|
||||||
|
&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>
|
||||||
|
) -> () {
|
||||||
|
to.blit(&Self::N, x, y, style)
|
||||||
|
}
|
||||||
|
#[inline] fn draw_south (
|
||||||
|
&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>
|
||||||
|
) -> () {
|
||||||
|
to.blit(&Self::S, x, y, style)
|
||||||
|
}
|
||||||
|
#[inline] fn draw_vertical (
|
||||||
|
&self, to: &mut TuiOutput, style: Option<Style>
|
||||||
|
) -> Usually<[u16;4]> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_vertical());
|
||||||
|
let [x, x2, y, y2] = area.lrtb();
|
||||||
|
for y in y..y2.saturating_sub(1) {
|
||||||
|
to.blit(&Self::W, x, y, style);
|
||||||
|
to.blit(&Self::E, x2.saturating_sub(1), y, style);
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
#[inline] fn draw_corners (
|
||||||
|
&self, to: &mut TuiOutput, style: Option<Style>
|
||||||
|
) -> Usually<[u16;4]> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_corners());
|
||||||
|
let [x, y, width, height] = area.xywh();
|
||||||
|
if width > 0 && height > 0 {
|
||||||
|
to.blit(&Self::NW, x, y, style);
|
||||||
|
to.blit(&Self::NE, x + width - 1, y, style);
|
||||||
|
to.blit(&Self::SW, x, y + height - 1, style);
|
||||||
|
to.blit(&Self::SE, x + width - 1, y + height - 1, style);
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
#[inline] fn style (&self) -> Option<Style> { None }
|
||||||
|
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
||||||
|
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
|
||||||
|
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
|
||||||
|
}
|
||||||
|
macro_rules! border {
|
||||||
|
($($T:ident {
|
||||||
|
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
||||||
|
$($x:tt)*
|
||||||
|
}),+) => {$(
|
||||||
|
impl BorderStyle for $T {
|
||||||
|
const NW: &'static str = $nw;
|
||||||
|
const N: &'static str = $n;
|
||||||
|
const NE: &'static str = $ne;
|
||||||
|
const W: &'static str = $w;
|
||||||
|
const E: &'static str = $e;
|
||||||
|
const SW: &'static str = $sw;
|
||||||
|
const S: &'static str = $s;
|
||||||
|
const SE: &'static str = $se;
|
||||||
|
$($x)*
|
||||||
|
}
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct $T(pub Style);
|
||||||
|
impl Render for $T {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> { self.draw(to) }
|
||||||
|
}
|
||||||
|
)+}
|
||||||
|
}
|
||||||
|
border! {
|
||||||
|
Square {
|
||||||
|
"┌" "─" "┐"
|
||||||
|
"│" "│"
|
||||||
|
"└" "─" "┘" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
SquareBold {
|
||||||
|
"┏" "━" "┓"
|
||||||
|
"┃" "┃"
|
||||||
|
"┗" "━" "┛" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
Tab {
|
||||||
|
"╭" "─" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"│" " " "│" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
Lozenge {
|
||||||
|
"╭" "─" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"╰" "─" "╯" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
Brace {
|
||||||
|
"╭" "" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"╰" "" "╯" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
LozengeDotted {
|
||||||
|
"╭" "┅" "╮"
|
||||||
|
"┇" "┇"
|
||||||
|
"╰" "┅" "╯" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
Quarter {
|
||||||
|
"▎" "▔" "🮇"
|
||||||
|
"▎" "🮇"
|
||||||
|
"▎" "▁" "🮇" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
QuarterV {
|
||||||
|
"▎" "" "🮇"
|
||||||
|
"▎" "🮇"
|
||||||
|
"▎" "" "🮇" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
Chamfer {
|
||||||
|
"🭂" "▔" "🭍"
|
||||||
|
"▎" "🮇"
|
||||||
|
"🭓" "▁" "🭞" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
Corners {
|
||||||
|
"🬆" "" "🬊" // 🬴 🬸
|
||||||
|
"" ""
|
||||||
|
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
CornersTall {
|
||||||
|
"🭽" "" "🭾"
|
||||||
|
"" ""
|
||||||
|
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const CORNERS: CornersTall = CornersTall(Style {
|
||||||
|
fg: Some(Color::Rgb(96, 255, 32)),
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::empty(),
|
||||||
|
sub_modifier: Modifier::DIM
|
||||||
|
});
|
||||||
|
|
@ -65,73 +65,3 @@ impl Content for SequencerStatusBar {
|
||||||
return Stack::None
|
return Stack::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//struct Either<A: Render<Engine = Tui>, B: Render<Engine = Tui>>(bool, A, B);
|
|
||||||
|
|
||||||
//impl<A: Render<Engine = Tui>, B: Render<Engine = Tui>> Content for Either<A, B> {
|
|
||||||
//type Engine = Tui;
|
|
||||||
//fn content (&self) -> impl Render<Engine = Tui> {
|
|
||||||
//if self.0 { self.2.content() } else { self.1.content() }
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
struct Both<A: Render<Engine = Tui>, B: Render<Engine = Tui>>(A, B);
|
|
||||||
|
|
||||||
impl<A: Render<Engine = Tui>, B: Render<Engine = Tui>> Render for Both<A, B> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
self.0.render(to)?;
|
|
||||||
self.1.render(to)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Layers<'a, const N: usize>(Collect<'a, Tui, N>);
|
|
||||||
|
|
||||||
impl<'a, const N: usize> Render for Layers<'a, N> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
for item in self.0.iter() {
|
|
||||||
item.render(to)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl<'a> Content for Layers<'a> {
|
|
||||||
//type Engine = Tui;
|
|
||||||
//fn content (&self) -> impl Render<Engine = Tui> {
|
|
||||||
//self
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
enum Stack<A: Render<Engine = Tui>, B: Render<Engine = Tui>> {
|
|
||||||
None,
|
|
||||||
Up(A, B),
|
|
||||||
Down(A, B),
|
|
||||||
Left(A, B),
|
|
||||||
Right(A, B),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Render<Engine = Tui>, B: Render<Engine = Tui>> Content for Stack<A, B> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn content (&self) -> impl Render<Engine = Tui> {
|
|
||||||
todo!();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Split<A: Render<Engine = Tui>, B: Render<Engine = Tui>> {
|
|
||||||
Up(usize, A, B),
|
|
||||||
Down(usize, A, B),
|
|
||||||
Left(usize, A, B),
|
|
||||||
Right(usize, A, B),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Render<Engine = Tui>, B: Render<Engine = Tui>> Content for Split< A, B> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn content (&self) -> impl Render<Engine = Tui> {
|
|
||||||
todo!();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue