wip: and sweeps right through the codebase

This commit is contained in:
🪞👃🪞 2024-12-31 02:03:16 +01:00
parent d37bd3e0c5
commit c9b09b7dea
16 changed files with 370 additions and 625 deletions

View file

@ -1,27 +0,0 @@
use crate::*;
/// A UI component that can render itself as a [Render], and [Handle] input.
pub trait Component<E: Engine>: Render<E> + Handle<E> {}
/// Everything that implements [Render] and [Handle] is a [Component].
impl<E: Engine, C: Render<E> + Handle<E>> Component<E> for C {}
/// A component that can exit.
pub trait Exit: Send {
//fn exited (&self) -> bool;
//fn exit (&mut self);
//fn boxed (self) -> Box<dyn Exit> where Self: Sized + 'static {
//Box::new(self)
//}
}
/// Marker trait for [Component]s that can [Exit].
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
///// Perform type erasure for collecting heterogeneous components.
//fn boxed (self) -> Box<dyn ExitableComponent<E>> where Self: Sized + 'static {
//Box::new(self)
//}
}
/// All [Components]s that implement [Exit] implement [ExitableComponent].
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}

View file

@ -63,6 +63,9 @@ pub trait Size<N: Coordinate> {
Ok(self)
}
}
#[inline] fn zero () -> [N;2] {
[N::zero(), N::zero()]
}
}
impl<N: Coordinate> Size<N> for (N, N) {
@ -129,6 +132,10 @@ pub trait Area<N: Coordinate>: Copy {
#[inline] fn shrink_y (&self, y: N) -> [N;4] {
[self.x(), self.y(), self.w(), self.h().minus(y)]
}
fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()]
}
}
impl<N: Coordinate> Area<N> for (N, N, N, N) {

View file

@ -46,11 +46,11 @@ pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
fn area_mut (&mut self) -> &mut [u16;4] {
&mut self.0
}
fn render_in (&mut self, _: [u16;4], _: &impl Render<TestEngine>) -> Usually<()> {
Ok(())
fn place (&mut self, _: [u16;4], _: &impl Layout<TestEngine>) {
()
}
}
impl Render<TestEngine> for String {
impl Layout<TestEngine> for String {
fn render (&self, to: &mut TestOutput) {
to.area_mut().set_w(self.len() as u16);
}
@ -63,7 +63,7 @@ pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl Layout<Tui> for TestComponent {
fn layout (&self) -> Option<impl Render<Tui>> {
fn layout (&self) -> Option<impl Layout<Tui>> {
Some(self.0.as_str())
}
}

View file

@ -8,35 +8,50 @@ pub trait Output<E: Engine> {
/// 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: &impl Render<E>) -> Usually<()>;
fn place (&mut self, area: E::Area, content: &impl Content<E>);
#[inline] fn x (&self) -> E::Unit { self.area().x() }
#[inline] fn y (&self) -> E::Unit { self.area().y() }
#[inline] fn w (&self) -> E::Unit { self.area().w() }
#[inline] fn h (&self) -> E::Unit { self.area().h() }
#[inline] fn wh (&self) -> E::Size { self.area().wh().into() }
}
pub trait Layout<E: Engine>: Send + Sync {
fn layout (&self) -> Option<impl Render<E>> {
pub trait Content<E: Engine>: Send + Sync {
fn content (&self) -> Option<impl Content<E>> {
None::<()>
}
fn area (&self, area: E::Area) -> E::Area {
if let Some(content) = self.content() {
content.area(area)
} else {
[0.into(), 0.into(), 0.into(), 0.into()].into()
}
}
fn render (&self, output: &mut E::Output) {
if let Some(content) = self.content() {
output.place(self.area(output.area()), &content)
}
}
}
impl<E: Engine> Layout<E> for () {}
impl<E: Engine> Content<E> for () {}
impl<E: Engine, L: Layout<E>> Layout<E> for &L {}
impl<E: Engine, T: Content<E>> Content<E> for &T {}
pub trait Render<E: Engine>: Send + Sync {
fn render (&self, _: &mut E::Output);
}
//impl<E: Engine, R: Render<E>> Render<E> for &R {}
impl<E: Engine, L: Layout<E>> Render<E> for L {
fn render (&self, to: &mut E::Output) {
if let Some(content) = self.layout() {
content.render(to)
}
}
}
//pub trait Render<E: Engine>: Send + Sync {
//fn render (&self, _: &mut E::Output);
//}
//impl<E: Engine, L: Layout<E>> Render<E> for L {
//fn render (&self, to: &mut E::Output) {
//if let Some(content) = self.layout() {
//content.render(to)
//}
//}
//}
//impl<E: Engine, L: Layout<E>> Layout<E> for &L {}

View file

@ -89,7 +89,7 @@ impl Tui {
}
}
pub trait TuiRun<R: Layout<Tui> + Handle<Tui> + Sized + 'static> {
pub trait TuiRun<R: Content<Tui> + Handle<Tui> + Sized + 'static> {
/// Run an app in the main loop.
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
/// Spawn the input thread.
@ -98,7 +98,7 @@ pub trait TuiRun<R: Layout<Tui> + Handle<Tui> + Sized + 'static> {
fn run_output (&self, state: &Arc<RwLock<R>>, sleep: Duration) -> JoinHandle<()>;
}
impl<T: Layout<Tui> + Handle<Tui> + Sized + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
impl<T: Content<Tui> + Handle<Tui> + Sized + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
let _input_thread = self.run_input(state, Duration::from_millis(100));
self.write().unwrap().setup()?;
@ -254,12 +254,11 @@ pub struct TuiOutput {
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: &impl Render<Tui>) -> Usually<()> {
#[inline] fn place (&mut self, area: [u16;4], content: &impl Content<Tui>) {
let last = self.area();
*self.area_mut() = area;
widget.render(self);
content.render(self);
*self.area_mut() = last;
Ok(())
}
}
@ -330,13 +329,13 @@ pub fn half_block (lower: bool, upper: bool) -> Option<char> {
//impl<T: Content<Tui>> Render<Tui> for T {}
impl Render<Tui> for &str {
impl Content<Tui> for &str {
fn render (&self, to: &mut TuiOutput) {
to.blit(self, to.area.x(), to.area.y(), None)
}
}
impl Render<Tui> for String {
impl Content<Tui> for String {
fn render (&self, to: &mut TuiOutput) {
to.blit(self, to.area.x(), to.area.y(), None)
}

View file

@ -1,139 +1,76 @@
use crate::*;
////////////////////////////////////////////////////////////////////////////////////////////////////
//! Groupings of elements.
mod bsp; pub use self::bsp::*;
mod layers; pub use self::layers::*;
mod split; pub use self::split::*;
mod stack; pub use self::stack::*;
////////////////////////////////////////////////////////////////////////////////////////////////////
use crate::*;
/// A function or closure that emits renderables.
pub trait CollectCallback<E: Engine>: Send
+ Sync
+ Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
{}
pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {}
/// Any function or closure that emits renderables for the given engine matches [CollectCallback].
impl<E, F> CollectCallback<E> for F
where
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
{}
impl<E, F> Collector<E> for F
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)) {}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Map<E, T, I, R, F>(PhantomData<E>, I, F)
where E: Engine, I: Iterator<Item = T>, R: Content<E>, F: Fn(T)->R;
pub enum Collect<'a, E: Engine, const N: usize> {
Callback(CallbackCollection<'a, E>),
//Iterator(IteratorCollection<'a, E>),
Array(ArrayCollection<'a, E, N>),
Slice(SliceCollection<'a, E>),
}
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F)
where E: Engine, I: Iterator<Item = T>, R: Content<E>, F: Fn(&dyn Content<E>, T)->R;
impl<'a, E: Engine, const N: usize> Collect<'a, E, N> {
pub fn iter (&'a self) -> CollectIterator<'a, E, N> {
CollectIterator(0, &self)
}
}
impl<'a, E: Engine, const N: usize> From<CallbackCollection<'a, E>> for Collect<'a, E, N> {
fn from (callback: CallbackCollection<'a, E>) -> Self {
Self::Callback(callback)
}
}
impl<'a, E: Engine, const N: usize> From<SliceCollection<'a, E>> for Collect<'a, E, N> {
fn from (slice: SliceCollection<'a, E>) -> Self {
Self::Slice(slice)
}
}
impl<'a, E: Engine, const N: usize> From<ArrayCollection<'a, E, N>> for Collect<'a, E, N>{
fn from (array: ArrayCollection<'a, E, N>) -> Self {
Self::Array(array)
}
}
type CallbackCollection<'a, E> =
&'a dyn Fn(&'a mut dyn FnMut(&dyn Render<E>)->Usually<()>);
//type IteratorCollection<'a, E> =
//&'a mut dyn Iterator<Item = dyn Render<E>>;
type SliceCollection<'a, E> =
&'a [&'a dyn Render<E>];
type ArrayCollection<'a, E, const N: usize> =
[&'a dyn Render<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> {
type Item = &'a dyn Render<E>;
fn next (&mut self) -> Option<Self::Item> {
match self.1 {
Collect::Callback(_callback) => {
todo!()
},
//Collection::Iterator(iterator) => {
//iterator.next()
//},
Collect::Array(array) => {
if let Some(_item) = array.get(self.0) {
self.0 += 1;
//Some(item)
None
} else {
None
}
}
Collect::Slice(slice) => {
if let Some(_item) = slice.get(self.0) {
self.0 += 1;
//Some(item)
None
} else {
None
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Map<E: Engine, T, I: Iterator<Item=T>, R: Render<E>, F: Fn(T)->R>(
PhantomData<E>,
I,
F
);
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Reduce<E: Engine, T, I: Iterator<Item=T>, R: Render<E>, F: Fn(&dyn Render<E>, T)->R>(
PhantomData<(E, R)>,
I,
F
);
impl<E: Engine, T, I: Iterator<Item=T>+Send+Sync, R: Render<E>, F: Fn(&dyn Render<E>, T)->R+Send+Sync> Render<E> for Reduce<E, T, I, R, F> {
fn min_size (&self, _to: E::Size) -> Perhaps<E::Size> {
todo!()
}
fn render (&self, _to: &mut E::Output) -> Usually<()> {
impl<E: Engine, T, I: Iterator<Item=T>+Send+Sync, R: Content<E>, F: Fn(&dyn Content<E>, T)->R+Send+Sync> Content<E> for Reduce<E, T, I, R, F> {
fn render (&self, _to: &mut E::Output) {
todo!()
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Conditional rendering, in unary and binary forms.
pub struct Cond;
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#[cfg(test)] #[test] fn test_bsp () {
// TODO
impl Cond {
/// Content `item` when `cond` is true.
pub fn when <E: Engine, A: Content<E>> (cond: bool, item: A) -> When<E, A> {
When(cond, item, Default::default())
}
/// Content `item` if `cond` is true, otherwise render `other`.
pub fn either <E: Engine, A: Content<E>, B: Content<E>> (cond: bool, a: A, b: B) -> Either<E, A, B> {
Either(cond, a, b, Default::default())
}
}
/// Contents `self.1` when `self.0` is true.
pub struct When<E: Engine, A>(bool, A, PhantomData<E>);
impl<E: Engine, A: Content<E>> Content<E> for When<E, A> {
fn area (&self, to: E::Area) -> E::Area {
let Self(cond, item, ..) = self;
let mut area = E::Area::zero();
if *cond {
let item_area = item.area(to);
area[0] = item_area.x();
area[1] = item_area.y();
area[2] = item_area.w();
area[3] = item_area.h();
}
area.into()
}
fn render (&self, to: &mut E::Output) {
let Self(cond, item, ..) = self;
if *cond { item.render(to) }
}
}
/// Contents `self.1` when `self.0` is true, otherwise renders `self.2`
pub struct Either<E: Engine, A, B>(bool, A, B, PhantomData<E>);
impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Either<E, A, B> {
fn area (&self, to: E::Area) -> E::Area {
let Self(cond, a, b, ..) = self;
if *cond { a.area(to) } else { b.area(to) }
}
fn render (&self, to: &mut E::Output) {
let Self(cond, a, b, ..) = self;
if *cond { a.render(to) } else { b.render(to) }
}
}

View file

@ -1,14 +1,32 @@
use crate::*;
pub enum Bsp<E: Engine, X: Render<E>, Y: Render<E>> {
/// Renders multiple things on top of each other,
macro_rules! lay {
($($expr:expr),*) => {{
let layers = ();
$(let layers = Bsp::b(layers, $expr);)*;
layers
}}
}
/// Renders multiple things on top of each other,
macro_rules! col {
($($expr:expr),*) => {{
let layers = ();
$(let layers = Bsp::s(layers, $expr);)*;
layers
}}
}
pub enum Bsp<E: Engine, X: Content<E>, Y: Content<E>> {
/// X is north of Y
N(Option<X>, Option<Y>),
N(f64, Option<X>, Option<Y>),
/// X is south of Y
S(Option<X>, Option<Y>),
S(f64, Option<X>, Option<Y>),
/// X is east of Y
E(Option<X>, Option<Y>),
E(f64, Option<X>, Option<Y>),
/// X is west of Y
W(Option<X>, Option<Y>),
W(f64, Option<X>, Option<Y>),
/// X is above Y
A(Option<X>, Option<Y>),
/// X is below Y
@ -17,83 +35,56 @@ pub enum Bsp<E: Engine, X: Render<E>, Y: Render<E>> {
Null(PhantomData<E>),
}
impl<E: Engine, X: Render<E>, Y: Render<E>> Bsp<E, X, Y> {
pub fn new (x: X) -> Self { Self::A(Some(x), None) }
pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) }
pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) }
pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) }
pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) }
impl<E: Engine, X: Content<E>, Y: Content<E>> Bsp<E, X, Y> {
pub fn n (p: f64, x: X, y: Y) -> Self { Self::N(p, Some(x), Some(y)) }
pub fn s (p: f64, x: X, y: Y) -> Self { Self::S(p, Some(x), Some(y)) }
pub fn e (p: f64, x: X, y: Y) -> Self { Self::E(p, Some(x), Some(y)) }
pub fn w (p: f64, x: X, y: Y) -> Self { Self::W(p, Some(x), Some(y)) }
pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) }
pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) }
}
impl<E: Engine, X: Render<E>, Y: Render<E>> Default for Bsp<E, X, Y> {
impl<E: Engine, X: Content<E>, Y: Content<E>> Default for Bsp<E, X, Y> {
fn default () -> Self {
Self::Null(Default::default())
}
}
impl<E: Engine, X: Render<E>, Y: Render<E>> Render<E> for Bsp<E, X, Y> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(Some(match self {
Self::Null(_) => [0.into(), 0.into()].into(),
Self::S(a, b) => {
let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
[a.w().max(b.w()), a.h() + b.h()].into()
},
Self::E(a, b) => {
let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
[a.w() + b.w(), a.h().max(b.h())].into()
},
Self::W(a, b) => {
let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
[a.w() + b.w(), a.h().max(b.h())].into()
},
Self::N(a, b) => {
let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into());
[a.w().max(b.w()), a.h() + b.h()].into()
},
_ => todo!()
}))
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
let n = [0.into(), 0.into()].into();
let s = to.area().wh().into();
Ok(match self {
impl<E: Engine, X: Content<E>, Y: Content<E>> Content<E> for Bsp<E, X, Y> {
fn render (&self, to: &mut E::Output) {
let n = E::Size::zero();
let s = to.wh();
match self {
Self::Null(_) => {},
Self::S(a, b) => {
let s_a = a.min_size(s)?.unwrap_or(n);
let _ = b.min_size(s)?.unwrap_or(n);
let h = s_a.h().into();
to.render_in(to.area().clip_h(h).into(), a)?;
to.render_in(to.area().shrink_y(h).push_y(h).into(), b)?;
},
Self::E(a, b) => {
let s_a = a.min_size(s)?.unwrap_or(n);
let _ = b.min_size(s)?.unwrap_or(n);
let w = s_a.w().into();
to.render_in(to.area().clip_w(w).into(), a)?;
to.render_in(to.area().push_x(w).shrink_x(w).into(), b)?;
},
Self::W(a, b) => {
let s_a = a.min_size(s)?.unwrap_or(n);
let _ = b.min_size(s)?.unwrap_or(n);
let w = (to.area().w() - s_a.w()).into();
to.render_in(to.area().push_x(w).into(), a)?;
to.render_in(to.area().shrink_x(w).into(), b)?;
},
Self::N(a, b) => {
let s_a = a.min_size(s)?.unwrap_or(n);
let _ = b.min_size(s)?.unwrap_or(n);
let h = to.area().h() - s_a.h();
to.render_in(to.area().push_y(h).into(), a)?;
to.render_in(to.area().shrink_y(h).into(), b)?;
},
//Self::S(p, a, b) => {
//let s_a = a.min_size(s)?.unwrap_or(n);
//let _ = b.min_size(s)?.unwrap_or(n);
//let h = s_a.h().into();
//to.render_in(to.area().clip_h(h).into(), a);
//to.render_in(to.area().shrink_y(h).push_y(h).into(), b);
//},
//Self::E(p, a, b) => {
//let s_a = a.min_size(s)?.unwrap_or(n);
//let _ = b.min_size(s)?.unwrap_or(n);
//let w = s_a.w().into();
//to.render_in(to.area().clip_w(w).into(), a);
//to.render_in(to.area().push_x(w).shrink_x(w).into(), b);
//},
//Self::W(p, a, b) => {
//let s_a = a.min_size(s)?.unwrap_or(n);
//let _ = b.min_size(s)?.unwrap_or(n);
//let w = (to.area().w() - s_a.w()).into();
//to.render_in(to.area().push_x(w).into(), a);
//to.render_in(to.area().shrink_x(w).into(), b);
//},
//Self::N(p, a, b) => {
//let s_a = a.min_size(s)?.unwrap_or(n);
//let _ = b.min_size(s)?.unwrap_or(n);
//let h = to.area().h() - s_a.h();
//to.render_in(to.area().push_y(h).into(), a);
//to.render_in(to.area().shrink_y(h).into(), b);
//},
_ => todo!()
})
}
}
}

View file

@ -1,56 +0,0 @@
use crate::*;
/// Renders multiple things on top of each other,
/// in the order they are provided by the callback.
/// Total size is largest width x largest height.
pub struct Layers<E: Engine, F: CollectCallback<E>>(pub F, PhantomData<E>);
/// Shorthand for defining an instance of [Layers].
#[macro_export] macro_rules! lay {
([$($expr:expr),* $(,)?]) => {
Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) })
};
(![$($expr:expr),* $(,)?]) => {
Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) })
};
($expr:expr) => {
Layers::new($expr)
};
}
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(())
}
}
}

View file

@ -2,9 +2,9 @@ use crate::*;
/// A binary split with fixed proportion
pub struct Split<E, A, B>(pub bool, pub Direction, pub E::Unit, A, B, PhantomData<E>)
where E: Engine, A: Render<E>, B: Render<E>;
where E: Engine, A: Content<E>, B: Content<E>;
impl<E: Engine, A: Render<E>, B: Render<E>> Split<E, A, B> {
impl<E: Engine, A: Content<E>, B: Content<E>> Split<E, A, B> {
#[inline] pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self {
Self(flip, direction, proportion, a, b, Default::default())
}
@ -22,148 +22,15 @@ impl<E: Engine, A: Render<E>, B: Render<E>> Split<E, A, B> {
}
}
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<()> {
impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Split<E, A, B> {
fn render (&self, to: &mut E::Output) {
let (a, b) = self.1.split_fixed(to.area(), self.2);
Ok(if self.0 {
to.render_in(a.into(), &self.4)?;
to.render_in(b.into(), &self.3)?;
if self.0 {
to.place(a.into(), &self.4);
to.place(b.into(), &self.3);
} else {
to.render_in(a.into(), &self.3)?;
to.render_in(b.into(), &self.4)?;
})
to.place(a.into(), &self.3);
to.place(b.into(), &self.4);
}
}
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 {
South => {
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 = Max::y(max, Push::y(h, component));
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()))
},
East => {
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 = Max::x(max, Push::x(h, component));
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()))
},
North => {
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 = Max::y(to.h() - h, component);
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()))
},
West => {
let w: E::Unit = 0.into();
let 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 {
South => {
(self.0)(&mut |item| {
if h < area.h() {
let item = Max::y(area.h() - h, Push::y(h, item));
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(())
})?;
},
East => {
(self.0)(&mut |item| {
if w < area.w() {
let item = Max::x(area.w() - w, Push::x(w, item));
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(())
})?;
},
North => {
(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 {
Shrink::y(height, Push::y(area.h() - height, item))
.render(to)?;
h = h + height;
if width > w { w = width }
};
}
Ok(())
})?;
},
_ => todo!()
};
Ok(())
}
}

View file

@ -1,8 +1,8 @@
use crate::*;
pub struct Stack<E: Engine, F: CollectCallback<E>>(pub F, pub Direction, PhantomData<E>);
pub struct Stack<E: Engine, F: Collector<E>>(pub F, pub Direction, PhantomData<E>);
impl<E: Engine, F: CollectCallback<E>> Stack<E, F> {
impl<E: Engine, F: Collector<E>> Stack<E, F> {
#[inline] pub fn new (direction: Direction, build: F) -> Self {
Self(build, direction, Default::default())
}
@ -17,49 +17,101 @@ impl<E: Engine, F: CollectCallback<E>> Stack<E, F> {
}
}
#[macro_export] macro_rules! col {
([$($expr:expr),* $(,)?]) => {
Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) })
};
(![$($expr:expr),* $(,)?]) => {
Stack::down(|add|{ $(add(&$expr)?;)* Ok(()) })
};
($expr:expr) => {
Stack::down($expr)
};
($pat:pat in $collection:expr => $item:expr) => {
Stack::down(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
};
}
//#[macro_export] macro_rules! col {
//([$($expr:expr),* $(,)?]) => {
//Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//(![$($expr:expr),* $(,)?]) => {
//Stack::down(|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//($expr:expr) => {
//Stack::down($expr)
//};
//($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::up(move|add|{ $(add(&$expr)?;)* Ok(()) })
};
(![$($expr:expr),* $(,)?]) => {
Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) })
};
($expr:expr) => {
Stack::up(expr)
};
($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(()) })
};
(![$($expr:expr),* $(,)?]) => {
Stack::right(|add|{ $(add(&$expr)?;)* Ok(()) })
};
($expr:expr) => {
Stack::right($expr)
};
($pat:pat in $collection:expr => $item:expr) => {
Stack::right(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
};
}
//#[macro_export] macro_rules! col_up {
//([$($expr:expr),* $(,)?]) => {
//Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//(![$($expr:expr),* $(,)?]) => {
//Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//($expr:expr) => {
//Stack::up(expr)
//};
//($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(()) })
//};
//(![$($expr:expr),* $(,)?]) => {
//Stack::right(|add|{ $(add(&$expr)?;)* Ok(()) })
//};
//($expr:expr) => {
//Stack::right($expr)
//};
//($pat:pat in $collection:expr => $item:expr) => {
//Stack::right(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
//};
//}
//impl<E: Engine, F: Collector<E>> Content<E> for Stack<E, F> {
//fn render (&self, to: &mut E::Output) {
//let area = to.area();
//let mut w = 0.into();
//let mut h = 0.into();
//match self.1 {
//South => {
//(self.0)(&mut |item| {
//if h < area.h() {
//let item = Max::y(area.h() - h, Push::y(h, item));
//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(())
//})?;
//},
//East => {
//(self.0)(&mut |item| {
//if w < area.w() {
//let item = Max::x(area.w() - w, Push::x(w, item));
//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(())
//})?;
//},
//North => {
//(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 {
//Shrink::y(height, Push::y(area.h() - height, item))
//.render(to)?;
//h = h + height;
//if width > w { w = width }
//};
//}
//Ok(())
//})?;
//},
//_ => todo!()
//};
//Ok(())
//}
//}

View file

@ -3,10 +3,32 @@ use crate::*;
/// A cardinal direction.
#[derive(Copy, Clone, PartialEq)]
pub enum Direction { North, South, West, East, }
pub use self::Direction::*;
impl Direction {
#[inline]
pub fn is_north (&self) -> bool { matches!(self, Self::North) }
pub fn is_south (&self) -> bool { matches!(self, Self::South) }
pub fn is_east (&self) -> bool { matches!(self, Self::West) }
pub fn is_west (&self) -> bool { matches!(self, Self::East) }
/// Return next direction clockwise
pub fn cw (&self) -> Self {
match self {
Self::North => Self::East,
Self::South => Self::West,
Self::West => Self::North,
Self::East => Self::South,
}
}
/// Return next direction counterclockwise
pub fn ccw (&self) -> Self {
match self {
Self::North => Self::West,
Self::South => Self::East,
Self::West => Self::South,
Self::East => Self::North,
}
}
pub fn split_fixed <N: Coordinate> (self, area: impl Area<N>, a: N) -> ([N;4],[N;4]) {
match self {
North => (

View file

@ -1,7 +1,6 @@
mod collection; pub use self::collection::*;
mod direction; pub use self::direction::*;
mod logic; pub use self::logic::*;
mod space; pub use self::space::*;
mod measure; pub use self::measure::*;
mod transform; pub use self::transform::*;
pub use ::tek_engine;
@ -11,3 +10,7 @@ pub(crate) use std::marker::PhantomData;
#[cfg(test)] #[test] fn test_layout () -> Usually<()> {
Ok(())
}
#[cfg(test)] #[test] fn test_bsp () {
// TODO
}

View file

@ -1,29 +0,0 @@
use crate::*;
/// Conditional rendering, in unary and binary forms.
pub struct Cond;
impl Cond {
/// Show an item conditionally.
pub fn when <E: Engine> (cond: bool, item: Box<dyn Layout<E>>) -> When<E> {
When(cond, item)
}
/// Show either of two items.
pub fn either <E: Engine> (cond: bool, a: Box<dyn Layout<E>>, b: Box<dyn Layout<E>>) -> Either<E> {
Either(cond, a, b)
}
}
pub struct When<E>(bool, Box<dyn Layout<E>>);
impl<E: Engine> Layout<E> for When<E> {
fn layout (self, _: &mut E::Output) -> Option<Box<dyn Layout<E>>> {
if self.0 { Some(self.1) } else { None }
}
}
pub struct Either<E: Engine>(bool, Box<dyn Layout<E>>, Box<dyn Layout<E>>);
impl<E: Engine> Layout<E> for Either<E> {
fn layout (self, _: &mut E::Output) -> Option<Box<dyn Layout<E>>> {
Some(if self.0 { self.1 } else { self.2 })
}
}

View file

@ -3,31 +3,6 @@ use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
impl Direction {
pub fn is_north (&self) -> bool { matches!(self, Self::North) }
pub fn is_south (&self) -> bool { matches!(self, Self::South) }
pub fn is_east (&self) -> bool { matches!(self, Self::West) }
pub fn is_west (&self) -> bool { matches!(self, Self::East) }
/// Return next direction clockwise
pub fn cw (&self) -> Self {
match self {
Self::North => Self::East,
Self::South => Self::West,
Self::West => Self::North,
Self::East => Self::South,
}
}
/// Return next direction counterclockwise
pub fn ccw (&self) -> Self {
match self {
Self::North => Self::West,
Self::South => Self::East,
Self::West => Self::South,
Self::East => Self::North,
}
}
}
pub trait HasSize<E: Engine> {
fn size (&self) -> &Measure<E>;
}
@ -48,7 +23,7 @@ pub struct Measure<E: Engine> {
pub y: Arc<AtomicUsize>,
}
impl<E: Engine> Render<E> for Measure<E> {
impl<E: Engine> Content<E> for Measure<E> {
fn render (&self, to: &mut E::Output) {
self.x.store(to.area().w().into(), Relaxed);
self.y.store(to.area().h().into(), Relaxed);
@ -95,12 +70,12 @@ impl<E: Engine> Measure<E> {
pub struct Scroll<E, F>(pub F, pub Direction, pub u64, PhantomData<E>)
where
E: Engine,
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>;
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)->Usually<()>)->Usually<()>;
//pub trait LayoutDebug<E: Engine> {
//fn debug <W: Render<E>> (other: W) -> DebugOverlay<E, W> {
//pub trait ContentDebug<E: Engine> {
//fn debug <W: Content<E>> (other: W) -> DebugOverlay<E, W> {
//DebugOverlay(Default::default(), other)
//}
//}
//impl<E: Engine> LayoutDebug<E> for E {}
//impl<E: Engine> ContentDebug<E> for E {}

View file

@ -1 +0,0 @@
use crate::*;

View file

@ -8,46 +8,60 @@ use crate::*;
/// double generic.
macro_rules! content_enum {
($Enum:ident: $($Variant:ident),+ $(,)?) => {
pub enum $Enum<E: Engine, T: Render<E>> {
pub enum $Enum<E: Engine, T: Content<E>> {
_Unused(PhantomData<E>), $($Variant(T)),+
}
impl<E: Engine, T: Render<E>> $Enum<E, T> {
fn content (&self) -> Option<impl Render<E>> {
match self {
Self::_Unused(_) => None,
$(Self::$Variant(content) => Some(content)),+
}
}
}
}
}
/// Defines an enum that transforms its content
/// along either the X axis, the Y axis, or both.
macro_rules! transform_xy {
(|$self:ident : $Enum:ident, $to:ident|$render:expr) => {
($self:ident : $Enum:ident |$to:ident|$area:expr) => {
content_enum!($Enum: X, Y, XY);
impl<E: Engine, T: Render<E>> $Enum<E, T> {
impl<E: Engine, T: Content<E>> $Enum<E, T> {
pub fn x (item: T) -> Self { Self::X(item) }
pub fn y (item: T) -> Self { Self::Y(item) }
pub fn xy (item: T) -> Self { Self::XY(item) }
}
impl<E: Engine, T: Render<E>> Render<E> for $Enum<E, T> {
fn render (&$self, $to: &mut <E as Engine>::Output) {
$render
impl<E: Engine, T: Content<E>> Content<E> for $Enum<E, T> {
fn content (&self) -> Option<impl Content<E>> {
match self {
Self::_Unused(_) => None,
Self::X(item) => Some(item),
Self::Y(item) => Some(item),
Self::XY(item) => Some(item),
}
}
fn area (&$self, $to: <E as Engine>::Area) -> <E as Engine>::Area {
$area
}
}
}
}
transform_xy!(self: Fill |to|{
let [x0, y0, wmax, hmax] = to.xywh();
if let Some(content) = self.content() {
let [x, y, w, h] = content.area(to).xywh();
return match self {
Self::X(_) => [x0, y, wmax, h],
Self::Y(_) => [x, y0, w, hmax],
Self::XY(_) => [x0, y0, wmax, hmax],
_ => unreachable!()
}.into()
}
return [0.into(), 0.into(), 0.into(), 0.into(),].into()
});
/// Defines an enum that transforms its content parametrically
/// along either the X axis, the Y axis, or both
macro_rules! transform_xy_unit {
(|$self:ident : $Enum:ident, $to:ident|$render:expr) => {
pub enum $Enum<E: Engine, T: Render<E>> {
(|$self:ident : $Enum:ident, $to:ident|$area:expr) => {
pub enum $Enum<E: Engine, T: Content<E>> {
X(E::Unit, T), Y(E::Unit, T), XY(E::Unit, E::Unit, T),
}
impl<E: Engine, T: Render<E>> $Enum<E, T> {
impl<E: Engine, T: Content<E>> $Enum<E, T> {
pub fn x (x: E::Unit, item: T) -> Self { Self::X(x, item) }
pub fn y (y: E::Unit, item: T) -> Self { Self::Y(y, item) }
pub fn xy (x: E::Unit, y: E::Unit, item: T) -> Self { Self::XY(x, y, item) }
@ -66,86 +80,65 @@ macro_rules! transform_xy_unit {
}
}
}
impl<E: Engine, T: Render<E>> $Enum<E, T> {
fn content (&self) -> Option<impl Render<E>> {
impl<E: Engine, T: Content<E>> Content<E> for $Enum<E, T> {
fn content (&self) -> Option<impl Content<E>> {
Some(match self {
Self::X(_, content) => content,
Self::Y(_, content) => content,
Self::XY(_, _, content) => content,
})
}
}
impl<E: Engine, T: Render<E>> Render<E> for $Enum<E, T> {
fn render (&$self, $to: &mut E::Output) -> Usually<()> {
$render
fn area (&$self, $to: E::Area) -> E::Area {
$area.into()
}
}
}
}
transform_xy!(|self: Fill, to|todo!());
transform_xy_unit!(|self: Fixed, to|{
let [x, y, w, h] = to.area().xywh();
to.render_in(match self {
Self::X(fw, _) => [x, y, fw, h],
Self::Y(fh, _) => [x, y, w, fh],
Self::XY(fw, fh, _) => [x, y, fw, fh],
}, self.content())
transform_xy_unit!(|self: Fixed, to|match self {
Self::X(fw, _) => [to.x(), to.y(), *fw, to.h()],
Self::Y(fh, _) => [to.x(), to.y(), to.w(), *fh],
Self::XY(fw, fh, _) => [to.x(), to.y(), *fw, *fh], // tagn
});
transform_xy_unit!(|self: Shrink, to|to.render_in(
[to.x(), to.y(), to.w().minus(self.dx()), to.h().minus(self.dy())],
self.content()));
transform_xy_unit!(|self: Shrink, to|
[to.x(), to.y(), to.w().minus(self.dx()), to.h().minus(self.dy())]);
transform_xy_unit!(|self: Expand, to|to.render_in(
[to.x(), to.y(), to.w() + self.dx(), to.h() + self.dy()],
self.content()));
transform_xy_unit!(|self: Expand, to|
[to.x(), to.y(), to.w() + self.dx(), to.h() + self.dy()]);
transform_xy_unit!(|self: Min, to|to.render_in(match self {
Self::X(mw, _) => [to.x(), to.y(), to.w().max(mw), to.h()],
Self::Y(mh, _) => [to.x(), to.y(), to.w(), to.h().max(mh)],
Self::XY(mw, mh, _) => [to.x(), to.y(), to.w().max(mw), to.h().max(mh)]
}, self.content()));
transform_xy_unit!(|self: Min, to|match self {
Self::X(mw, _) => [to.x(), to.y(), to.w().max(*mw), to.h()],
Self::Y(mh, _) => [to.x(), to.y(), to.w(), to.h().max(*mh)],
Self::XY(mw, mh, _) => [to.x(), to.y(), to.w().max(*mw), to.h().max(*mh)]
});
transform_xy_unit!(|self: Max, to|to.render_in(match self {
Self::X(mw, _) => [to.x(), to.y(), to.w().min(mw), to.h()],
Self::Y(mh, _) => [to.x(), to.y(), to.w(), to.h().min(mh)],
Self::XY(mw, mh, _) => [to.x(), to.y(), to.w().min(mw), to.h().min(mh)],
}, self.content()));
transform_xy_unit!(|self: Max, to|match self {
Self::X(mw, _) => [to.x(), to.y(), to.w().min(*mw), to.h()],
Self::Y(mh, _) => [to.x(), to.y(), to.w(), to.h().min(*mh)],
Self::XY(mw, mh, _) => [to.x(), to.y(), to.w().min(*mw), to.h().min(*mh)],
});
transform_xy_unit!(|self: Push, to|to.render_in(
[to.x() + self.dx(), to.y() + self.dy(), to.w(), to.h()],
self.content()));
transform_xy_unit!(|self: Push, to|
[to.x() + self.dx(), to.y() + self.dy(), to.w(), to.h()]);
transform_xy_unit!(|self: Pull, to|to.render_in(
[to.x().minus(self.dx()), to.y().minus(self.dy()), to.w(), to.h()],
self.content()));
transform_xy_unit!(|self: Pull, to|
[to.x().minus(self.dx()), to.y().minus(self.dy()), to.w(), to.h()]);
transform_xy_unit!(|self: Margin, to|{
let dx = self.dx();
let dy = self.dy();
to.render_in([
to.x().minus(dx),
to.y().minus(dy),
to.w() + dy + dy,
to.h() + dy + dy,
])
[to.x().minus(dx), to.y().minus(dy), to.w() + dy + dy, to.h() + dy + dy]
});
transform_xy_unit!(|self: Padding, to|{
let dx = self.dx();
let dy = self.dy();
to.render_in([
to.x() + dx,
to.y() + dy,
to.w().minus(dy + dy),
to.h().minus(dy + dy),
])
[to.x() + dx, to.y() + dy, to.w().minus(dy + dy), to.h().minus(dy + dy), ]
});
content_enum!(Align: Center, X, Y, NW, N, NE, E, SE, S, SW, W);
impl<E: Engine, T: Render<E>> Align<E, T> {
impl<E: Engine, T: Content<E>> Align<E, T> {
pub fn c (w: T) -> Self { Self::Center(w) }
pub fn x (w: T) -> Self { Self::X(w) }
pub fn y (w: T) -> Self { Self::Y(w) }
@ -159,7 +152,7 @@ impl<E: Engine, T: Render<E>> Align<E, T> {
pub fn se (w: T) -> Self { Self::SE(w) }
}
fn align<E: Engine, T: Render<E>, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<E, T>, outer: R, content: R) -> Option<R> {
fn align<E: Engine, T: Content<E>, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<E, T>, outer: R, content: R) -> Option<R> {
if outer.w() < content.w() || outer.h() < content.h() {
None
} else {
@ -182,17 +175,14 @@ fn align<E: Engine, T: Render<E>, N: Coordinate, R: Area<N> + From<[N;4]>> (alig
}
}
impl<E: Engine, T: Render<E>> Render<E> for Align<E, T> {
fn min_size (&self, outer_area: E::Size) -> Perhaps<E::Size> {
self.content().min_size(outer_area)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
impl<E: Engine, T: Content<E>> Content<E> for Align<E, T> {
fn render (&self, to: &mut E::Output) {
let outer_area = to.area();
Ok(if let Some(content_size) = self.min_size(outer_area.wh().into())? {
let content_area = outer_area.clip(content_size);
if let Some(aligned) = align(&self, outer_area.into(), content_area.into()) {
to.render_in(aligned, &self.content())?
}
})
if let Some(content) = self.content() {
let inner_area = content.area(outer_area);
if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) {
to.place(aligned, &content)
}
}
}
}