wip: generic layout!

This commit is contained in:
🪞👃🪞 2024-09-06 21:52:23 +03:00
parent 0bbf74e915
commit 93ba611e33
11 changed files with 267 additions and 82 deletions

View file

@ -1,38 +0,0 @@
use crate::*;
use std::ops::Add;
pub struct Offset<N>(pub N, pub N);
impl<N: Copy> From<N> for Offset<N> {
fn from (n: N) -> Self {
Self(n, n)
}
}
pub struct Inset<N>(pub N, pub N, pub N, pub N);
impl<N: Copy> From<N> for Inset<N> {
fn from (n: N) -> Self {
Self(n, n, n, n)
}
}
impl<N: Copy> From<(N, N)> for Inset<N> {
fn from ((n1, n2): (N, N)) -> Self {
Self(n1, n2, n1, n2)
}
}
pub struct Outset<N>(pub N, pub N, pub N, pub N);
impl<N: Copy> From<N> for Outset<N> {
fn from (n: N) -> Self {
Self(n, n, n, n)
}
}
impl<N: Copy> From<(N, N)> for Outset<N> {
fn from ((n1, n2): (N, N)) -> Self {
Self(n1, n2, n1, n2)
}
}

View file

@ -1,22 +1,36 @@
use crate::*;
use std::ops::{Add, Sub};
use std::cmp::{Ord, Eq, PartialEq};
/// Entry point for main loop
pub trait App<T: Engine> {
fn run (self, context: T) -> Usually<T>;
}
pub trait Number: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
+ Ord + PartialEq + Eq {}
impl<T> Number for T where
T: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
+ Ord + PartialEq + Eq
{}
/// Platform backend.
pub trait Engine: Sized {
fn setup (&mut self) -> Usually<()> { Ok(()) }
fn exited (&self) -> bool;
fn teardown (&mut self) -> Usually<()> { Ok(()) }
type HandleInput;
fn handle (&self, _: &mut impl Handle<Self>) -> Usually<()> where Self: Sized;
type Handled;
/// Unit of distance.
type Unit: Number;
type HandleInput;
type Handled;
type RenderInput;
fn render (&mut self, _: &impl Render<Self>) -> Usually<()> where Self: Sized;
type Rendered;
}
@ -25,9 +39,14 @@ pub trait HandleContext {}
pub trait RenderContext {}
submod! {
collect
component
exit
focus
handle
keymap
layered
layout
render
split
}

View file

@ -5,10 +5,3 @@ 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 {}
submod! {
collect
layered
offset
split
}

View file

@ -0,0 +1,100 @@
use crate::*;
/// Compute drawing area before rendering
pub trait Layout<E: Engine> {
fn layout (&self, area: impl Rectangle<E::Unit>) -> Perhaps<impl Rectangle<E::Unit>>;
}
/// Enforce minimum size of drawing area
pub enum Min<U: Number, L> { W(U, L), H(U, L), WH(U, U, L), }
/// Enforce maximum size of drawing area
pub enum Max<U: Number, L> { W(U, L), H(U, L), WH(U, U, L), }
/// Expand drawing area
pub enum Outset<U: Number, L> { W(U, L), H(U, L), WH(U, U, L), }
/// Shrink drawing area
pub enum Inset<U: Number, L> { W(U, L), H(U, L), WH(U, U, L), }
/// Move origin point of drawing area
pub enum Offset<U: Number, L> { X(U, L), Y(U, L), XY(U, U, L), }
impl<E: Engine, L: Layout<E>> Layout<E> for Min<E:: Unit, L> {
fn layout (&self, area: impl Rectangle<E::Unit>) -> Perhaps<impl Rectangle<E::Unit>> {
match self {
Self::W(w, item) => if area.w() < *w { Ok(None) } else {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w().max(*w), area.h()])
},
Self::H(h, item) => if area.w() < *h { Ok(None) } else {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w(), area.h().max(*h)])
},
Self::WH(w, h, item) => if area.w() < *w || area.h() < *h { Ok(None) } else {
item.layout([area.x(), area.y(), area.w().max(*w), area.h().max(*h)])
}
}
}
}
impl<E: Engine, L: Layout<E>> Layout<E> for Max<E:: Unit, L> {
fn layout (&self, area: impl Rectangle<E::Unit>) -> Perhaps<impl Rectangle<E::Unit>> {
match self {
Self::W(w, item) => {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w().min(*w), area.h()])
},
Self::H(h, item) => {
// TODO: axis clamp (subtract from x if width goes out of area
item.layout([area.x(), area.y(), area.w(), area.h().min(*h)])
},
Self::WH(w, h, item) => {
item.layout([area.x(), area.y(), area.w().min(*w), area.h().min(*h)])
}
}
}
}
impl<E: Engine, L: Layout<E>> Layout<E> for Outset<E::Unit, L> {
fn layout (&self, area: impl Rectangle<E::Unit>) -> Perhaps<impl Rectangle<E::Unit>> {
match self {
Self::W(w, item) => if area.x() < *w { Ok(None) } else {
item.layout([area.x() - *w, area.y(), area.w() + *w, area.h()])
},
Self::H(h, item) => if area.y() < *h { Ok(None) } else {
item.layout([area.x(), area.y() - *h, area.w(), area.h() + *h])
},
Self::WH(w, h, item) => if area.x() < *w || area.y() < *h { Ok(None) } else {
item.layout([area.x()-*w, area.y() - *h, area.w() + *w, area.h() + *h])
}
}
}
}
impl<E: Engine, L: Layout<E>> Layout<E> for Inset<E::Unit, L> {
fn layout (&self, area: impl Rectangle<E::Unit>) -> Perhaps<impl Rectangle<E::Unit>> {
match self {
Self::W(w, item) => if area.w() < *w { Ok(None) } else {
item.layout([area.x() + *w, area.y(), area.w() - *w, area.h()])
},
Self::H(h, item) => if area.h() < *h { Ok(None) } else {
item.layout([area.x(), area.y() + *h, area.w(), area.h() - *h])
},
Self::WH(w, h, item) => if area.w() < *w || area.h() < *h { Ok(None) } else {
item.layout([area.x() - *w, area.y() - *h, area.w() + *w, area.h() + *h])
}
}
}
}
impl<E: Engine, L: Layout<E>> Layout<E> for Offset<E::Unit, L> {
fn layout (&self, area: impl Rectangle<E::Unit>) -> Perhaps<impl Rectangle<E::Unit>> {
match self {
Self::X(x, item) => if area.w() < *x { Ok(None) } else {
item.layout([area.x() + *x, area.y(), area.w() - *x, area.h()])
},
Self::Y(y, item) => if area.h() < *y { Ok(None) } else {
item.layout([area.x(), area.y() + *y, area.w(), area.h() - *y])
},
Self::XY(x, y, item) => if area.w() < *x || area.h() < *y { Ok(None) } else {
item.layout([area.x() + *x, area.y() + *y, area.w() - *x, area.h() - *y])
}
}
}
}

View file

@ -28,7 +28,6 @@ use better_panic::{Settings, Verbosity};
submod! {
audio
component
edn
engine
space

View file

@ -1,3 +1,77 @@
use crate::*;
pub trait Point<N: Number> {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N {
self.x()
}
fn h (&self) -> N {
self.y()
}
}
impl<N: Number> Point<N> for (N, N) {
fn x (&self) -> N {
self.0
}
fn y (&self) -> N {
self.1
}
}
impl<N: Number> Point<N> for [N;2] {
fn x (&self) -> N {
self[0]
}
fn y (&self) -> N {
self[1]
}
}
pub trait Rectangle<N: Number> {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
fn x2 (&self) -> N {
self.x() + self.w()
}
fn y2 (&self) -> N {
self.y() + self.h()
}
}
impl<N: Number> Rectangle<N> for (N, N, N, N) {
fn x (&self) -> N {
self.0
}
fn y (&self) -> N {
self.1
}
fn w (&self) -> N {
self.2
}
fn h (&self) -> N {
self.3
}
}
impl<N: Number> Rectangle<N> for [N;4] {
fn x (&self) -> N {
self[0]
}
fn y (&self) -> N {
self[1]
}
fn w (&self) -> N {
self[2]
}
fn h (&self) -> N {
self[3]
}
}
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
impl $A<$T> {
pub fn start_inc (&mut self) -> $T {
@ -19,16 +93,25 @@ macro_rules! impl_axis_common { ($A:ident $T:ty) => {
}
} }
pub struct FixedAxis<T> { pub start: T, pub point: Option<T> }
pub struct FixedAxis<T> {
pub start: T,
pub point: Option<T>
}
impl_axis_common!(FixedAxis u16);
impl_axis_common!(FixedAxis usize);
pub struct ScaledAxis<T> { pub start: T, pub scale: T, pub point: Option<T> }
pub struct ScaledAxis<T> {
pub start: T,
pub scale: T,
pub point: Option<T>
}
impl_axis_common!(ScaledAxis u16);
impl_axis_common!(ScaledAxis usize);
impl<T: Copy> ScaledAxis<T> {
pub fn scale_mut (&mut self, cb: &impl Fn(T)->T) {
self.scale = cb(self.scale)
}
}

View file

@ -25,10 +25,11 @@ pub struct Tui {
area: Rect,
}
impl Engine for Tui {
type Unit = u16;
type HandleInput = Self;
type Handled = bool;
type Handled = bool;
type RenderInput = Self;
type Rendered = Rect;
type Rendered = Rect;
fn exited (&self) -> bool {
self.exited.fetch_and(true, Ordering::Relaxed)
}
@ -46,12 +47,6 @@ impl Engine for Tui {
stdout().execute(LeaveAlternateScreen)?;
disable_raw_mode().map_err(Into::into)
}
fn handle (&self, _: &mut impl Handle<Self>) -> Usually<()> {
Ok(())
}
fn render (&mut self, _: &impl Render<Self>) -> Usually<()> {
Ok(())
}
}
impl Tui {
/// Run the main loop.

View file

@ -1,35 +1,69 @@
use crate::*;
impl<R: Render<Tui>> Render<Tui> for (Offset<u16>, R) {
impl Rectangle<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 }
}
impl<R> Render<Tui> for Min<<Tui as Engine>::Unit, R> where R: Render<Tui> + Layout<Tui> {
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
self.1.render(engine.alter_area(|x, y, width, height|(
x + self.0.0,
y + self.0.1,
width.saturating_sub(self.0.0),
height.saturating_sub(self.0.1),
)))
self.layout(engine.area())?
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|engine|engine.render(match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}))
}
}
impl<R: Render<Tui>> Render<Tui> for (Inset<u16>, R) {
impl<R> Render<Tui> for Max<<Tui as Engine>::Unit, R> where R: Render<Tui> + Layout<Tui> {
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
self.1.render(engine.alter_area(|x, y, width, height|(
x + self.0.0,
y + self.0.1,
width.saturating_sub(self.0.0 + self.0.2),
height.saturating_sub(self.0.1 + self.0.3),
)))
self.layout(engine.area())?
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|engine|engine.render(match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}))
}
}
impl<R: Render<Tui>> Render<Tui> for (Outset<u16>, R) {
impl<R> Render<Tui> for Inset<<Tui as Engine>::Unit, R> where R: Render<Tui> + Layout<Tui> {
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
self.1.render(engine.alter_area(|x, y, width, height|(
x.saturating_sub(self.0.0),
y.saturating_sub(self.0.1),
width + self.0.0 + self.0.2,
height + self.0.1 + self.0.3,
)))
self.layout(engine.area())?
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|engine|engine.render(match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}))
}
}
impl<R> Render<Tui> for Outset<<Tui as Engine>::Unit, R> where R: Render<Tui> + Layout<Tui> {
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
self.layout(engine.area())?
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|engine|engine.render(match self {
Self::W(_, inner) => inner,
Self::H(_, inner) => inner,
Self::WH(_, _, inner) => inner,
}))
}
}
impl<R> Render<Tui> for Offset<<Tui as Engine>::Unit, R> where R: Render<Tui> + Layout<Tui> {
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
self.layout(engine.area())?
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|engine|engine.render(match self {
Self::X(_, inner) => inner,
Self::Y(_, inner) => inner,
Self::XY(_, _, inner) => inner,
}))
}
}