mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-09 21:26:43 +01:00
wip: generic layout!
This commit is contained in:
parent
0bbf74e915
commit
93ba611e33
11 changed files with 267 additions and 82 deletions
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,36 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use std::ops::{Add, Sub};
|
||||||
|
use std::cmp::{Ord, Eq, PartialEq};
|
||||||
|
|
||||||
/// Entry point for main loop
|
/// Entry point for main loop
|
||||||
pub trait App<T: Engine> {
|
pub trait App<T: Engine> {
|
||||||
fn run (self, context: T) -> Usually<T>;
|
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.
|
/// Platform backend.
|
||||||
pub trait Engine: Sized {
|
pub trait Engine: Sized {
|
||||||
fn setup (&mut self) -> Usually<()> { Ok(()) }
|
fn setup (&mut self) -> Usually<()> { Ok(()) }
|
||||||
fn exited (&self) -> bool;
|
fn exited (&self) -> bool;
|
||||||
fn teardown (&mut self) -> Usually<()> { Ok(()) }
|
fn teardown (&mut self) -> Usually<()> { Ok(()) }
|
||||||
|
|
||||||
type HandleInput;
|
/// Unit of distance.
|
||||||
fn handle (&self, _: &mut impl Handle<Self>) -> Usually<()> where Self: Sized;
|
type Unit: Number;
|
||||||
type Handled;
|
|
||||||
|
|
||||||
|
type HandleInput;
|
||||||
|
type Handled;
|
||||||
type RenderInput;
|
type RenderInput;
|
||||||
fn render (&mut self, _: &impl Render<Self>) -> Usually<()> where Self: Sized;
|
|
||||||
type Rendered;
|
type Rendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,9 +39,14 @@ pub trait HandleContext {}
|
||||||
pub trait RenderContext {}
|
pub trait RenderContext {}
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
|
collect
|
||||||
|
component
|
||||||
exit
|
exit
|
||||||
focus
|
focus
|
||||||
handle
|
handle
|
||||||
keymap
|
keymap
|
||||||
|
layered
|
||||||
|
layout
|
||||||
render
|
render
|
||||||
|
split
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,3 @@ 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<E> + Handle<E>> Component<E> for C {}
|
impl<E: Engine, C: Render<E> + Handle<E>> Component<E> for C {}
|
||||||
|
|
||||||
submod! {
|
|
||||||
collect
|
|
||||||
layered
|
|
||||||
offset
|
|
||||||
split
|
|
||||||
}
|
|
||||||
100
crates/tek_core/src/engine/layout.rs
Normal file
100
crates/tek_core/src/engine/layout.rs
Normal 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,6 @@ use better_panic::{Settings, Verbosity};
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
audio
|
audio
|
||||||
component
|
|
||||||
edn
|
edn
|
||||||
engine
|
engine
|
||||||
space
|
space
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
||||||
impl $A<$T> {
|
impl $A<$T> {
|
||||||
pub fn start_inc (&mut self) -> $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 u16);
|
||||||
impl_axis_common!(FixedAxis usize);
|
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 u16);
|
||||||
impl_axis_common!(ScaledAxis usize);
|
impl_axis_common!(ScaledAxis usize);
|
||||||
|
|
||||||
impl<T: Copy> ScaledAxis<T> {
|
impl<T: Copy> ScaledAxis<T> {
|
||||||
pub fn scale_mut (&mut self, cb: &impl Fn(T)->T) {
|
pub fn scale_mut (&mut self, cb: &impl Fn(T)->T) {
|
||||||
self.scale = cb(self.scale)
|
self.scale = cb(self.scale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub struct Tui {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
}
|
}
|
||||||
impl Engine for Tui {
|
impl Engine for Tui {
|
||||||
|
type Unit = u16;
|
||||||
type HandleInput = Self;
|
type HandleInput = Self;
|
||||||
type Handled = bool;
|
type Handled = bool;
|
||||||
type RenderInput = Self;
|
type RenderInput = Self;
|
||||||
|
|
@ -46,12 +47,6 @@ impl Engine for Tui {
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
disable_raw_mode().map_err(Into::into)
|
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 {
|
impl Tui {
|
||||||
/// Run the main loop.
|
/// Run the main loop.
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,69 @@
|
||||||
use crate::*;
|
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> {
|
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
|
||||||
self.1.render(engine.alter_area(|x, y, width, height|(
|
self.layout(engine.area())?
|
||||||
x + self.0.0,
|
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
|
||||||
y + self.0.1,
|
.map(|engine|engine.render(match self {
|
||||||
width.saturating_sub(self.0.0),
|
Self::W(_, inner) => inner,
|
||||||
height.saturating_sub(self.0.1),
|
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> {
|
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
|
||||||
self.1.render(engine.alter_area(|x, y, width, height|(
|
self.layout(engine.area())?
|
||||||
x + self.0.0,
|
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
|
||||||
y + self.0.1,
|
.map(|engine|engine.render(match self {
|
||||||
width.saturating_sub(self.0.0 + self.0.2),
|
Self::W(_, inner) => inner,
|
||||||
height.saturating_sub(self.0.1 + self.0.3),
|
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> {
|
fn render (&self, engine: &mut Tui) -> Perhaps<Rect> {
|
||||||
self.1.render(engine.alter_area(|x, y, width, height|(
|
self.layout(engine.area())?
|
||||||
x.saturating_sub(self.0.0),
|
.map(|area|engine.with_area(area.x(), area.y(), area.w(), area.h()))
|
||||||
y.saturating_sub(self.0.1),
|
.map(|engine|engine.render(match self {
|
||||||
width + self.0.0 + self.0.2,
|
Self::W(_, inner) => inner,
|
||||||
height + self.0.1 + self.0.3,
|
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,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue