diff --git a/crates/tek_core/src/component/offset.rs b/crates/tek_core/src/component/offset.rs deleted file mode 100644 index aeaa69a1..00000000 --- a/crates/tek_core/src/component/offset.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::*; -use std::ops::Add; - -pub struct Offset(pub N, pub N); - -impl From for Offset { - fn from (n: N) -> Self { - Self(n, n) - } -} - -pub struct Inset(pub N, pub N, pub N, pub N); - -impl From for Inset { - fn from (n: N) -> Self { - Self(n, n, n, n) - } -} - -impl From<(N, N)> for Inset { - fn from ((n1, n2): (N, N)) -> Self { - Self(n1, n2, n1, n2) - } -} - -pub struct Outset(pub N, pub N, pub N, pub N); - -impl From for Outset { - fn from (n: N) -> Self { - Self(n, n, n, n) - } -} - -impl From<(N, N)> for Outset { - fn from ((n1, n2): (N, N)) -> Self { - Self(n1, n2, n1, n2) - } -} diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs index 05fbbae7..da0e008a 100644 --- a/crates/tek_core/src/engine.rs +++ b/crates/tek_core/src/engine.rs @@ -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 { fn run (self, context: T) -> Usually; } +pub trait Number: Send + Sync + Copy + + Add + + Sub + + Ord + PartialEq + Eq {} + +impl Number for T where + T: Send + Sync + Copy + + Add + + Sub + + 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) -> Usually<()> where Self: Sized; - type Handled; + /// Unit of distance. + type Unit: Number; + type HandleInput; + type Handled; type RenderInput; - fn render (&mut self, _: &impl Render) -> 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 } diff --git a/crates/tek_core/src/component/collect.rs b/crates/tek_core/src/engine/collect.rs similarity index 100% rename from crates/tek_core/src/component/collect.rs rename to crates/tek_core/src/engine/collect.rs diff --git a/crates/tek_core/src/component.rs b/crates/tek_core/src/engine/component.rs similarity index 79% rename from crates/tek_core/src/component.rs rename to crates/tek_core/src/engine/component.rs index c9bf9e19..952a4d90 100644 --- a/crates/tek_core/src/component.rs +++ b/crates/tek_core/src/engine/component.rs @@ -5,10 +5,3 @@ pub trait Component: Render + Handle {} /// Everything that implements [Render] and [Handle] is a [Component]. impl + Handle> Component for C {} - -submod! { - collect - layered - offset - split -} diff --git a/crates/tek_core/src/component/layered.rs b/crates/tek_core/src/engine/layered.rs similarity index 100% rename from crates/tek_core/src/component/layered.rs rename to crates/tek_core/src/engine/layered.rs diff --git a/crates/tek_core/src/engine/layout.rs b/crates/tek_core/src/engine/layout.rs new file mode 100644 index 00000000..c8dad2e5 --- /dev/null +++ b/crates/tek_core/src/engine/layout.rs @@ -0,0 +1,100 @@ +use crate::*; + +/// Compute drawing area before rendering +pub trait Layout { + fn layout (&self, area: impl Rectangle) -> Perhaps>; +} +/// Enforce minimum size of drawing area +pub enum Min { W(U, L), H(U, L), WH(U, U, L), } +/// Enforce maximum size of drawing area +pub enum Max { W(U, L), H(U, L), WH(U, U, L), } +/// Expand drawing area +pub enum Outset { W(U, L), H(U, L), WH(U, U, L), } +/// Shrink drawing area +pub enum Inset { W(U, L), H(U, L), WH(U, U, L), } +/// Move origin point of drawing area +pub enum Offset { X(U, L), Y(U, L), XY(U, U, L), } + +impl> Layout for Min { + fn layout (&self, area: impl Rectangle) -> Perhaps> { + 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> Layout for Max { + fn layout (&self, area: impl Rectangle) -> Perhaps> { + 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> Layout for Outset { + fn layout (&self, area: impl Rectangle) -> Perhaps> { + 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> Layout for Inset { + fn layout (&self, area: impl Rectangle) -> Perhaps> { + 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> Layout for Offset { + fn layout (&self, area: impl Rectangle) -> Perhaps> { + 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]) + } + } + } +} diff --git a/crates/tek_core/src/component/split.rs b/crates/tek_core/src/engine/split.rs similarity index 100% rename from crates/tek_core/src/component/split.rs rename to crates/tek_core/src/engine/split.rs diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 7ae9fb84..ea624cd7 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -28,7 +28,6 @@ use better_panic::{Settings, Verbosity}; submod! { audio - component edn engine space diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index 920749b9..de2c78eb 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -1,3 +1,77 @@ +use crate::*; + +pub trait Point { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N { + self.x() + } + fn h (&self) -> N { + self.y() + } +} + +impl Point for (N, N) { + fn x (&self) -> N { + self.0 + } + fn y (&self) -> N { + self.1 + } +} + +impl Point for [N;2] { + fn x (&self) -> N { + self[0] + } + fn y (&self) -> N { + self[1] + } +} + +pub trait Rectangle { + 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 Rectangle 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 Rectangle 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 { pub start: T, pub point: Option } +pub struct FixedAxis { + pub start: T, + pub point: Option +} + impl_axis_common!(FixedAxis u16); impl_axis_common!(FixedAxis usize); -pub struct ScaledAxis { pub start: T, pub scale: T, pub point: Option } +pub struct ScaledAxis { + pub start: T, + pub scale: T, + pub point: Option +} + impl_axis_common!(ScaledAxis u16); impl_axis_common!(ScaledAxis usize); + impl ScaledAxis { pub fn scale_mut (&mut self, cb: &impl Fn(T)->T) { self.scale = cb(self.scale) } } - diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index 162d1d2b..1ffba0de 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -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) -> Usually<()> { - Ok(()) - } - fn render (&mut self, _: &impl Render) -> Usually<()> { - Ok(()) - } } impl Tui { /// Run the main loop. diff --git a/crates/tek_core/src/tui/tui_layout.rs b/crates/tek_core/src/tui/tui_layout.rs index b952385d..fffcebb3 100644 --- a/crates/tek_core/src/tui/tui_layout.rs +++ b/crates/tek_core/src/tui/tui_layout.rs @@ -1,35 +1,69 @@ use crate::*; -impl> Render for (Offset, R) { +impl Rectangle 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 Render for Min<::Unit, R> where R: Render + Layout { fn render (&self, engine: &mut Tui) -> Perhaps { - 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> Render for (Inset, R) { +impl Render for Max<::Unit, R> where R: Render + Layout { fn render (&self, engine: &mut Tui) -> Perhaps { - 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> Render for (Outset, R) { +impl Render for Inset<::Unit, R> where R: Render + Layout { fn render (&self, engine: &mut Tui) -> Perhaps { - 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 Render for Outset<::Unit, R> where R: Render + Layout { + fn render (&self, engine: &mut Tui) -> Perhaps { + 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 Render for Offset<::Unit, R> where R: Render + Layout { + fn render (&self, engine: &mut Tui) -> Perhaps { + 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, + })) } }