refactor engine and layout into input and output

This commit is contained in:
🪞👃🪞 2025-01-07 21:30:07 +01:00
parent f052891473
commit 4d0f98acd2
40 changed files with 104 additions and 109 deletions

7
engine/Cargo.lock generated
View file

@ -1,7 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "tek_engine"
version = "0.2.0"

View file

@ -1,6 +0,0 @@
[package]
name = "tek_engine"
edition = "2021"
version = "0.2.0"
[dependencies]

View file

@ -1,65 +0,0 @@
# `tek_engine`
this crate provides the `Engine` trait,
which defines an application's lifecycle.
currently, there is one kind of engine implemented, `Tui`.
it uses `ratatui` to present an interactive user interface
in text mode.
at launch, the `Tui` engine spawns two threads,
a **render thread** and an **input thread**. (the
application may spawn further threads, such as a
**jack thread**.)
all threads communicate using shared ownership,
`Arc<RwLock>` and `Arc<Atomic>`. the engine and
application instances are expected to be wrapped
in `Arc<RwLock>`; internally, those synchronization
mechanisms may be used liberally.
## rendering
the **render thread** continually invokes the
`Content::render` method of the application
to redraw the display. it does this efficiently
by using ratatui's double buffering.
thus, for a type to be a valid application for engine `E`,
it must implement the trait `Content<E>`, which allows
it to display content to the engine's output.
the most important thing about the `Content` trait is that
it composes:
* you can implement `Content::content` to build
`Content`s out of other `Content`s
* and/or `Content::area` for custom positioning and sizing,
* and/or `Content::render` for custom rendering
within the given `Content`'s area.
the manner of output is determined by the
`Engine::Output` type, a mutable pointer to which
is passed to the render method, e.g. in the case of
the `Tui` engine: `fn render(&self, output: &mut TuiOut)`
you can use `TuiOut::blit` and `TuiOut::place`
to draw at specified coordinates of the display, and/or
directly modify the underlying `ratatui::Buffer` at
`output.buffer`
rendering is intended to work with read-only access
to the application state. if you really need to update
values during rendering, use interior mutability.
## input handling
the **input thread** polls for keyboard events
and passes them onto the application's `Handle::handle` method.
thus, for a type to be a valid application for engine `E`,
it must implement the trait `Handle<E>`, which allows it
to respond to user input.
this thread has write access to the application state,
and is responsible for mutating it in response to
user activity.

View file

@ -1,92 +0,0 @@
use crate::*;
use std::fmt::Debug;
pub trait Area<N: Coordinate>: From<[N;4]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
#[inline] fn xy (&self) -> [N;2] {
[self.x(), self.y()]
}
#[inline] fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
#[inline] fn xywh (&self) -> [N;4] {
[self.x(), self.y(), self.w(), self.h()]
}
#[inline] fn clip_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), self.h().min(h)]
}
#[inline] fn clip_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), self.w().min(w), self.h()]
}
#[inline] fn clip (&self, wh: impl Size<N>) -> [N;4] {
[self.x(), self.y(), wh.w(), wh.h()]
}
#[inline] fn set_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), w, self.h()]
}
#[inline] fn set_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), h]
}
#[inline] fn x2 (&self) -> N {
self.x() + self.w()
}
#[inline] fn y2 (&self) -> N {
self.y() + self.h()
}
#[inline] fn lrtb (&self) -> [N;4] {
[self.x(), self.x2(), self.y(), self.y2()]
}
#[inline] fn center (&self) -> [N;2] {
[self.x() + self.w()/2.into(), self.y() + self.h()/2.into()]
}
#[inline] fn center_x (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x + w / 2.into()).minus(n / 2.into()), y + h / 2.into(), n, 1.into()]
}
#[inline] fn center_y (&self, m: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[x + w / 2.into(), (y + h / 2.into()).minus(m / 2.into()), 1.into(), m]
}
#[inline] fn center_xy (&self, [n, m]: [N;2]) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x + w / 2.into()).minus(n / 2.into()), (y + h / 2.into()).minus(m / 2.into()), n, m]
}
#[inline] fn centered (&self) -> [N;2] {
[self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())]
}
#[inline] fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()]
}
#[inline] fn from_position (pos: impl Size<N>) -> [N;4] {
let [x, y] = pos.wh();
[x, y, 0.into(), 0.into()]
}
#[inline] fn from_size (size: impl Size<N>) -> [N;4] {
let [w, h] = size.wh();
[0.into(), 0.into(), w, h]
}
}
impl<N: Coordinate> Area<N> for (N, N, N, N) {
#[inline] fn x (&self) -> N { self.0 }
#[inline] fn y (&self) -> N { self.1 }
#[inline] fn w (&self) -> N { self.2 }
#[inline] fn h (&self) -> N { self.3 }
}
impl<N: Coordinate> Area<N> for [N;4] {
#[inline] fn x (&self) -> N { self[0] }
#[inline] fn y (&self) -> N { self[1] }
#[inline] fn w (&self) -> N { self[2] }
#[inline] fn h (&self) -> N { self[3] }
}

View file

@ -1,45 +0,0 @@
use crate::*;
pub trait Command<S>: Send + Sync + Sized {
fn execute (self, state: &mut S) -> Perhaps<Self>;
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T> {
Ok(self.execute(state)?.map(wrap))
}
}
#[macro_export] macro_rules! input_to_command {
(<$($l:lifetime),+> $Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => {
impl<$($l),+> InputToCommand<$Input, $State> for $Command {
fn input_to_command ($state: &$State, $input: &$Input) -> Option<Self> {
Some($handler)
}
}
};
($Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => {
impl InputToCommand<$Input, $State> for $Command {
fn input_to_command ($state: &$State, $input: &$Input) -> Option<Self> {
Some($handler)
}
}
}
}
pub trait InputToCommand<I, S>: Command<S> + Sized {
fn input_to_command (state: &S, input: &I) -> Option<Self>;
fn execute_with_state (state: &mut S, input: &I) -> Perhaps<bool> {
Ok(if let Some(command) = Self::input_to_command(state, input) {
let _undo = command.execute(state)?;
Some(true)
} else {
None
})
}
}
#[macro_export] macro_rules! command {
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
impl$(<$($l),+>)? Command<$State> for $Command {
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
Ok($handler)
}
}
};
}

View file

@ -1,46 +0,0 @@
use crate::*;
/// Build a [Render]able out of other [Render]ables,
/// then apply optional custom render/layout on top.
pub trait Content<E: Output>: Send + Sync + Sized {
fn content (&self) -> impl Render<E> { () }
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
fn render (&self, output: &mut E) { self.content().render(output) }
}
impl<E: Output, C: Content<E>> Content<E> for &C {
fn content (&self) -> impl Render<E> { (*self).content() }
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
fn render (&self, output: &mut E) { (*self).render(output) }
}
/// The platonic ideal unit of [Content]: total emptiness at dead center.
impl<E: Output> Content<E> for () {
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
fn render (&self, _: &mut E) {}
}
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
fn content (&self) -> impl Render<E> {
self.as_ref()
}
fn layout (&self, area: E::Area) -> E::Area {
self.as_ref()
.map(|content|content.layout(area))
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
}
fn render (&self, output: &mut E) {
self.as_ref()
.map(|content|content.render(output));
}
}
//impl<E: Output, T: Content<E>, E: Content<E>> Content<E> for Option<T> {
//fn content (&self) -> impl Render<E> {
//self.as_ref()
//}
//fn layout (&self, area: E::Area) -> E::Area {
//self.as_ref()
//.map(|content|content.layout(area))
//.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
//}
//fn render (&self, output: &mut E) {
//self.as_ref()
//.map(|content|content.render(output));
//}
//}

View file

@ -1,28 +0,0 @@
use std::fmt::{Debug, Display};
use std::ops::{Add, Sub, Mul, Div};
impl Coordinate for u16 {}
/// A linear coordinate.
pub trait Coordinate: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
+ Mul<Self, Output=Self>
+ Div<Self, Output=Self>
+ Ord + PartialEq + Eq
+ Debug + Display + Default
+ From<u16> + Into<u16>
+ Into<usize>
+ Into<f64>
{
#[inline] fn minus (self, other: Self) -> Self {
if self >= other {
self - other
} else {
0.into()
}
}
#[inline] fn zero () -> Self {
0.into()
}
}

View file

@ -1,50 +0,0 @@
use crate::*;
/// Platform backend.
pub trait Engine: Send + Sync + Sized {
/// Input event type
type Input: Input;
/// Render target
type Output: Output;
/// Prepare before run
fn setup (&mut self) -> Usually<()> { Ok(()) }
/// True if done
fn exited (&self) -> bool;
/// Clean up after run
fn teardown (&mut self) -> Usually<()> { Ok(()) }
}
/// Event source
pub trait Input: Send + Sync + Sized {
/// Type of input event
type Event;
/// Result of handling input
type Handled;
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
/// Render target
pub trait Output: Send + Sync + Sized {
/// Unit of length
type Unit: Coordinate;
/// Rectangle without offset
type Size: Size<Self::Unit>;
/// Rectangle with offset
type Area: Area<Self::Unit>;
/// Current output area
fn area (&self) -> Self::Area;
/// Mutable pointer to area
fn area_mut (&mut self) -> &mut Self::Area;
/// Render widget in area
fn place (&mut self, area: Self::Area, content: &impl Render<Self>);
#[inline] fn x (&self) -> Self::Unit { self.area().x() }
#[inline] fn y (&self) -> Self::Unit { self.area().y() }
#[inline] fn w (&self) -> Self::Unit { self.area().w() }
#[inline] fn h (&self) -> Self::Unit { self.area().h() }
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
}

View file

@ -1,43 +0,0 @@
use crate::*;
pub struct EventMap<'a, S, I: PartialEq, C> {
pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option<C>)],
pub fallback: Option<&'a dyn Fn(&S, &I) -> Option<C>>
}
impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> {
pub fn handle (&self, state: &S, input: &I) -> Option<C> {
for (binding, handler) in self.bindings.iter() {
if input == binding {
return handler(state)
}
}
if let Some(fallback) = self.fallback {
fallback(state, input)
} else {
None
}
}
}
#[macro_export] macro_rules! keymap {
(
$(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
{ $($key:expr => $handler:expr),* $(,)? } $(,)?
) => {
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
fallback: None,
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
};
input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?);
};
(
$(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
{ $($key:expr => $handler:expr),* $(,)? }, $default:expr
) => {
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
fallback: Some(&|$state, $input|Some($default)),
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
};
input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?);
};
}

View file

@ -1,61 +0,0 @@
use crate::*;
use std::sync::{Mutex, Arc, RwLock};
/// Implement the [Handle] trait.
#[macro_export] macro_rules! handle {
(|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl<E: Engine> Handle<E> for $Struct {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler
}
}
};
($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl Handle<$E> for $Struct {
fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> {
$handler
}
}
}
}
/// Handle input
pub trait Handle<E: Input>: Send + Sync {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
}
impl<E: Input, H: Handle<E>> Handle<E> for &mut H {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
(*self).handle(context)
}
}
impl<E: Input, H: Handle<E>> Handle<E> for Option<H> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
if let Some(ref mut handle) = self {
handle.handle(context)
} else {
Ok(None)
}
}
}
impl<H, E: Input> Handle<E> for Mutex<H> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.get_mut().unwrap().handle(context)
}
}
impl<H, E: Input> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.lock().unwrap().handle(context)
}
}
impl<H, E: Input> Handle<E> for RwLock<H> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}
impl<H, E: Input> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}

View file

@ -1,80 +0,0 @@
#![feature(associated_type_defaults)]
//mod component; pub use self::component::*;
mod engine; pub use self::engine::*;
mod handle; pub use self::handle::*;
mod command; pub use self::command::*;
mod event_map; pub use self::event_map::*;
mod coordinate; pub use self::coordinate::*;
mod size; pub use self::size::*;
mod area; pub use self::area::*;
mod render; pub use self::render::*;
mod content; pub use self::content::*;
mod thunk; pub use self::thunk::*;
pub use std::error::Error;
/// Standard result type.
pub type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type.
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
/// Prototypal case of implementor macro.
/// Saves 4loc per data pats.
#[macro_export] macro_rules! from {
($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => {
impl $(<$($lt),+>)? From<$Source> for $Target {
fn from ($state:$Source) -> Self { $cb }
}
};
}
#[cfg(test)] #[test] fn test_dimensions () {
assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]);
}
#[cfg(test)] #[test] fn test_stub_engine () -> Usually<()> {
struct TestEngine(bool);
struct TestInput(bool);
struct TestOutput([u16;4]);
enum TestEvent { Test1 }
impl Engine for TestEngine {
type Input = TestInput;
type Handled = ();
type Output = TestOutput;
type Unit = u16;
type Size = [u16;2];
type Area = [u16;4];
fn exited (&self) -> bool {
self.0
}
}
impl Input<TestEngine> for TestInput {
type Event = TestEvent;
fn event (&self) -> &Self::Event {
&TestEvent::Test1
}
fn is_done (&self) -> bool {
self.0
}
fn done (&self) {}
}
impl Output<TestEngine> for TestOutput {
fn area (&self) -> [u16;4] {
self.0
}
fn area_mut (&mut self) -> &mut [u16;4] {
&mut self.0
}
fn place (&mut self, _: [u16;4], _: &impl Render<TestEngine>) {
()
}
}
impl Content<TestEngine> for String {
fn render (&self, to: &mut TestOutput) {
to.area_mut().set_w(self.len() as u16);
}
}
Ok(())
}

View file

@ -1,66 +0,0 @@
use crate::*;
use std::ops::Deref;
/// Custom layout and rendering.
pub trait Render<E: Output>: Send + Sync {
fn layout (&self, area: E::Area) -> E::Area;
fn render (&self, output: &mut E);
fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Sized + 'a {
Box::new(self) as RenderBox<'a, E>
}
}
pub type RenderDyn<'a, Output> = dyn Render<Output> + 'a;
impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
fn content (&self) -> impl Render<E> { self.deref() }
fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) }
fn render (&self, output: &mut E) { Render::render(self.deref(), output) }
}
pub type RenderBox<'a, E: Output> = Box<RenderDyn<'a, E>>;
impl<'a, E: Output> Content<E> for RenderBox<'a, E> {
fn content (&self) -> impl Render<E> { self.deref() }
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
}
impl<E: Output, C: Content<E>> Render<E> for C {
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
fn render (&self, output: &mut E) { Content::render(self, output) }
}
#[macro_export] macro_rules! render {
(($self:ident:$Struct:ty) => $content:expr) => {
impl <E: Output> Content<E> for $Struct {
fn content (&$self) -> impl Render<E> { Some($content) }
}
};
(|$self:ident:$Struct:ident $(<
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
>)?, $to:ident | $render:expr) => {
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
for $Struct $(<$($L),* $($T),*>>)? {
fn render (&$self, $to: &mut E) { $render }
}
};
($Output:ty:
($self:ident:$Struct:ident $(<$(
$($L:lifetime)? $($T:ident)? $(:$Trait:path)?
),+>)?) => $content:expr
) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn content (&$self) -> impl Render<$Output> { $content }
}
};
($Output:ty:
|$self:ident : $Struct:ident $(<$(
$($L:lifetime)? $($T:ident)? $(:$Trait:path)?
),+>)?, $to:ident| $render:expr
) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn render (&$self, $to: &mut $Output) { $render }
}
};
}

View file

@ -1,40 +0,0 @@
use crate::*;
use std::fmt::Debug;
pub trait Size<N: Coordinate>: From<[N;2]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
#[inline] fn w (&self) -> N { self.x() }
#[inline] fn h (&self) -> N { self.y() }
#[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] }
#[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
#[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
#[inline] fn zero () -> [N;2] {
[N::zero(), N::zero()]
}
#[inline] fn to_area_pos (&self) -> [N;4] {
let [x, y] = self.wh();
[x, y, 0.into(), 0.into()]
}
#[inline] fn to_area_size (&self) -> [N;4] {
let [w, h] = self.wh();
[0.into(), 0.into(), w, h]
}
}
impl<N: Coordinate> Size<N> for (N, N) {
#[inline] fn x (&self) -> N { self.0 }
#[inline] fn y (&self) -> N { self.1 }
}
impl<N: Coordinate> Size<N> for [N;2] {
#[inline] fn x (&self) -> N { self[0] }
#[inline] fn y (&self) -> N { self[1] }
}

View file

@ -1,50 +0,0 @@
use crate::*;
use std::marker::PhantomData;
/// Lazily-evaluated [Render]able.
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T + Send + Sync>(PhantomData<E>, F);
impl<E: Output, T: Render<E>, F: Fn()->T + Send + Sync> Thunk<E, T, F> {
pub fn new (thunk: F) -> Self {
Self(Default::default(), thunk)
}
}
impl<E: Output, T: Render<E>, F: Fn()->T + Send + Sync> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.1)() }
}
pub struct BoxThunk<'a, E: Output>(PhantomData<E>, Box<dyn Fn()->Box<dyn Render<E> + 'a> + Send + Sync + 'a>);
impl<'a, E: Output> BoxThunk<'a, E> {
pub fn new (thunk: Box<dyn Fn()->Box<dyn Render<E> + 'a> + Send + Sync + 'a>) -> Self {
Self(Default::default(), thunk)
}
}
impl<'a, E: Output> Content<E> for BoxThunk<'a, E> {
fn content (&self) -> impl Render<E> { (self.1)() }
}
impl<'a, E: Output, F: Fn()->T + Send + Sync + 'a, T: Render<E> + Send + Sync + 'a> From<F> for BoxThunk<'a, E> {
fn from (f: F) -> Self {
Self(Default::default(), Box::new(move||f().boxed()))
}
}
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + Send + Sync + 'a> From<F> for BoxThunk<'a, E> {
//fn from (f: F) -> Self {
//Self(Default::default(), Box::new(f))
//}
//}
pub struct RenderThunk<E: Output, F: Fn(&mut E) + Send + Sync>(PhantomData<E>, F);
impl<E: Output, F: Fn(&mut E) + Send + Sync> RenderThunk<E, F> {
pub fn new (render: F) -> Self { Self(Default::default(), render) }
}
impl<E: Output, F: Fn(&mut E) + Send + Sync> Content<E> for RenderThunk<E, F> {
fn render (&self, to: &mut E) { (self.1)(to) }
}
pub struct LayoutThunk<E: Output, F1: Fn(E::Area)->E::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync>(PhantomData<E>, F1, F2);
impl<E: Output, F1: Fn(E::Area)->E::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync> LayoutThunk<E, F1, F2> {
pub fn new (layout: F1, render: F2) -> Self { Self(Default::default(), layout, render) }
}
impl<E: Output, F1: Fn(E::Area)->E::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync> Content<E> for LayoutThunk<E, F1, F2> {
fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) }
fn render (&self, to: &mut E) { (self.2)(to) }
}