diff --git a/Cargo.lock b/Cargo.lock index 3091aae7..d7cf48d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2721,6 +2721,13 @@ dependencies = [ "toml", ] +[[package]] +name = "tek_layout" +version = "0.1.0" +dependencies = [ + "tek_core", +] + [[package]] name = "tek_tui" version = "0.1.0" @@ -2732,6 +2739,7 @@ dependencies = [ "symphonia", "tek_api", "tek_core", + "tek_layout", "vst", "wavers", "winit", diff --git a/Cargo.toml b/Cargo.toml index bca535cc..e3660088 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ members = [ "crates/tek_core", "crates/tek_api", "crates/tek_tui", - "crates/tek_cli" + "crates/tek_cli", + "crates/tek_layout" ] diff --git a/crates/tek_core/src/layout.rs b/crates/tek_core/src/collect.rs similarity index 88% rename from crates/tek_core/src/layout.rs rename to crates/tek_core/src/collect.rs index e42e4cf5..09595e74 100644 --- a/crates/tek_core/src/layout.rs +++ b/crates/tek_core/src/collect.rs @@ -32,21 +32,21 @@ impl<'a, E: Engine, const N: usize> From> for Collect< } type CallbackCollection<'a, E> = - &'a dyn Fn(&'a mut dyn FnMut(&dyn Render)->Usually<()>); + &'a dyn Fn(&'a mut dyn FnMut(&dyn Render)->Usually<()>); //type IteratorCollection<'a, E> = - //&'a mut dyn Iterator>; + //&'a mut dyn Iterator>; type SliceCollection<'a, E> = - &'a [&'a dyn Render]; + &'a [&'a dyn Render]; type ArrayCollection<'a, E, const N: usize> = - [&'a dyn Render; N]; + [&'a dyn Render; 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; + type Item = &'a dyn Render; fn next (&mut self) -> Option { match self.1 { Collect::Callback(callback) => { diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs index 03c6983f..10effc1b 100644 --- a/crates/tek_core/src/engine.rs +++ b/crates/tek_core/src/engine.rs @@ -1,6 +1,10 @@ use crate::*; + /// Entry point for main loop -pub trait App { fn run (self, context: T) -> Usually; } +pub trait App { + fn run (self, context: T) -> Usually; +} + /// Platform backend. pub trait Engine: Send + Sync + Sized { /// Input event type @@ -22,213 +26,13 @@ pub trait Engine: Send + Sync + Sized { /// Clean up after run fn teardown (&mut self) -> Usually<()> { Ok(()) } } -/// Current input state -pub trait Input { - /// Type of input event - type Event; - /// Currently handled event - fn event (&self) -> &Self::Event; - /// Whether component should exit - fn is_done (&self) -> bool; - /// Mark component as done - fn done (&self); -} -/// Rendering target -pub trait Output { - /// Current output area - fn area (&self) -> E::Area; - /// 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: &dyn Render) -> Usually<()>; -} -/// Cast to dynamic pointer -pub fn widget > (w: &T) -> &dyn Render { - w as &dyn Render -} -/// A renderable component -pub trait Render: Send + Sync { - /// Engine for which this component is implemented - type Engine: Engine; - /// Minimum size to use - fn min_size (&self, to: ::Size) - -> Perhaps<::Size> - { - Ok(Some(to)) - } - /// Draw to output render target - fn render (&self,to: &mut ::Output) -> Usually<()>; -} -impl Render for &dyn Render { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - (*self).min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - (*self).render(to) - } -} -impl Render for &mut dyn Render { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - (**self).min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - (**self).render(to) - } -} -impl<'a, E: Engine> Render for Box + 'a> { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - (**self).min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - (**self).render(to) - } -} -impl> Render for Arc { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - self.as_ref().min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.as_ref().render(to) - } -} -impl> Render for Mutex { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - self.lock().unwrap().min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.lock().unwrap().render(to) - } -} -impl> Render for RwLock { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - self.read().unwrap().min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.read().unwrap().render(to) - } -} -impl> Render for Option { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten()) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(())) - } -} -/// Render either of two widgets depending on predicate -pub struct Either, B: Render>( - pub bool, - pub A, - pub B, -); -impl, B: Render> Render for Either { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - if self.0 { self.1.min_size(to) } else { self.2.min_size(to) } - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - if self.0 { self.1.render(to) } else { self.2.render(to) } - } -} -/// A custom [Render] defined by passing layout and render closures in place. -pub struct Widget< - E: Engine, - L: Send + Sync + Fn(E::Size)->Perhaps, - R: Send + Sync + Fn(&mut E::Output)->Usually<()> ->(L, R, PhantomData); -impl< - E: Engine, - L: Send + Sync + Fn(E::Size)->Perhaps, - R: Send + Sync + Fn(&mut E::Output)->Usually<()> -> Widget { - pub fn new (layout: L, render: R) -> Self { - Self(layout, render, Default::default()) - } -} -impl< - E: Engine, - L: Send + Sync + Fn(E::Size)->Perhaps, - R: Send + Sync + Fn(&mut E::Output)->Usually<()> -> Render for Widget { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - self.0(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.1(to) - } -} -/// A [Render] that contains other [Render]s -pub trait Content: Send + Sync { - type Engine: Engine; - fn content (&self) -> impl Render::Engine>; -} -/// Every struct that has [Content] is a renderable [Render]. -impl> Render for W { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - self.content().min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - match self.min_size(to.area().wh().into())? { - Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()), - None => Ok(()) - } - } -} -/// Handle input -pub trait Handle: Send + Sync { - fn handle (&mut self, context: &E::Input) -> Perhaps; -} -impl> Handle for &mut H { - fn handle (&mut self, context: &E::Input) -> Perhaps { - (*self).handle(context) - } -} -impl> Handle for Option { - fn handle (&mut self, context: &E::Input) -> Perhaps { - if let Some(ref mut handle) = self { - handle.handle(context) - } else { - Ok(None) - } - } -} -impl Handle for Mutex where H: Handle { - fn handle (&mut self, context: &E::Input) -> Perhaps { - self.lock().unwrap().handle(context) - } -} -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E::Input) -> Perhaps { - self.lock().unwrap().handle(context) - } -} -impl Handle for RwLock where H: Handle { - fn handle (&mut self, context: &E::Input) -> Perhaps { - self.write().unwrap().handle(context) - } -} -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E::Input) -> Perhaps { - self.write().unwrap().handle(context) - } -} + /// A UI component that can render itself as a [Render], and [Handle] input. -pub trait Component: Render + Handle {} +pub trait Component: Render + Handle {} + /// Everything that implements [Render] and [Handle] is a [Component]. -impl + Handle> Component for C {} -/// A UI component that has [Content] and can [Handle] input. -pub trait ContentComponent: Render + Handle {} -/// Everything that implements [Content] and [Handle] is a [Component]. -impl + Handle> ContentComponent for C {} +impl + Handle> Component for C {} + /// A component that can exit. pub trait Exit: Send { fn exited (&self) -> bool; @@ -237,6 +41,7 @@ pub trait Exit: Send { Box::new(self) } } + /// Marker trait for [Component]s that can [Exit]. pub trait ExitableComponent: Exit + Component where E: Engine { /// Perform type erasure for collecting heterogeneous components. @@ -244,5 +49,6 @@ pub trait ExitableComponent: Exit + Component where E: Engine { Box::new(self) } } + /// All [Components]s that implement [Exit] implement [ExitableComponent]. impl + Exit> ExitableComponent for C {} diff --git a/crates/tek_core/src/input.rs b/crates/tek_core/src/input.rs new file mode 100644 index 00000000..e9388793 --- /dev/null +++ b/crates/tek_core/src/input.rs @@ -0,0 +1,58 @@ +use crate::*; + +/// Current input state +pub trait Input { + /// Type of input event + type Event; + /// Currently handled event + fn event (&self) -> &Self::Event; + /// Whether component should exit + fn is_done (&self) -> bool; + /// Mark component as done + fn done (&self); +} + +/// Handle input +pub trait Handle: Send + Sync { + fn handle (&mut self, context: &E::Input) -> Perhaps; +} + +impl> Handle for &mut H { + fn handle (&mut self, context: &E::Input) -> Perhaps { + (*self).handle(context) + } +} + +impl> Handle for Option { + fn handle (&mut self, context: &E::Input) -> Perhaps { + if let Some(ref mut handle) = self { + handle.handle(context) + } else { + Ok(None) + } + } +} + +impl Handle for Mutex where H: Handle { + fn handle (&mut self, context: &E::Input) -> Perhaps { + self.lock().unwrap().handle(context) + } +} + +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &E::Input) -> Perhaps { + self.lock().unwrap().handle(context) + } +} + +impl Handle for RwLock where H: Handle { + fn handle (&mut self, context: &E::Input) -> Perhaps { + self.write().unwrap().handle(context) + } +} + +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &E::Input) -> Perhaps { + self.write().unwrap().handle(context) + } +} diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 5ce97392..e18c3c6a 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -32,17 +32,19 @@ use std::fmt::{Debug, Display}; } submod! { + //tui audio color + collect command edn engine focus + input + output pitch space time - //tui - layout } testmod! { diff --git a/crates/tek_core/src/output.rs b/crates/tek_core/src/output.rs new file mode 100644 index 00000000..a2fd5d6f --- /dev/null +++ b/crates/tek_core/src/output.rs @@ -0,0 +1,137 @@ +use crate::*; + +/// Rendering target +pub trait Output { + /// Current output area + fn area (&self) -> E::Area; + /// 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: &dyn Render) -> Usually<()>; +} + +/// Cast to dynamic pointer +pub fn widget > (w: &T) -> &dyn Render { + w as &dyn Render +} + +/// A renderable component +pub trait Render: Send + Sync { + /// Minimum size to use + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(Some(to)) + } + /// Draw to output render target + fn render (&self, to: &mut E::Output) -> Usually<()>; +} + +impl Render for &dyn Render { + fn min_size (&self, to: E::Size) -> Perhaps { + (*self).min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + (*self).render(to) + } +} + +impl Render for &mut dyn Render { + fn min_size (&self, to: E::Size) -> Perhaps { + (**self).min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + (**self).render(to) + } +} + +impl<'a, E: Engine> Render for Box + 'a> { + fn min_size (&self, to: E::Size) -> Perhaps { + (**self).min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + (**self).render(to) + } +} + +impl> Render for Arc { + fn min_size (&self, to: E::Size) -> Perhaps { + self.as_ref().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.as_ref().render(to) + } +} + +impl> Render for Mutex { + fn min_size (&self, to: E::Size) -> Perhaps { + self.lock().unwrap().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.lock().unwrap().render(to) + } +} + +impl> Render for RwLock { + fn min_size (&self, to: E::Size) -> Perhaps { + self.read().unwrap().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.read().unwrap().render(to) + } +} + +impl> Render for Option { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten()) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(())) + } +} + +/// A [Render] that contains other [Render]s +pub trait Content: Send + Sync { + fn content (&self) -> impl Render; +} + +/// Every struct that has [Content] is a renderable [Render]. +impl> Render for &W { + fn min_size (&self, to: E::Size) -> Perhaps { + self.content().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + match self.min_size(to.area().wh().into())? { + Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()), + None => Ok(()) + } + } +} + +/// A custom [Render] defined by passing layout and render closures in place. +pub struct Widget< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +>(L, R, PhantomData); + +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> Widget { + pub fn new (layout: L, render: R) -> Self { + Self(layout, render, Default::default()) + } +} + +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> Render for Widget { + fn min_size (&self, to: E::Size) -> Perhaps { + self.0(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.1(to) + } +} diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index 04f89fcf..a83f78c7 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -129,133 +129,6 @@ impl Area for [N;4] { #[inline] fn h (&self) -> N { self[3] } } -pub trait Layout: Render + Sized { - fn align_center (self) -> Align { Align::Center(self) } - fn align_n (self) -> Align { Align::N(self) } - fn align_s (self) -> Align { Align::S(self) } - fn align_e (self) -> Align { Align::E(self) } - fn align_w (self) -> Align { Align::W(self) } - fn align_nw (self) -> Align { Align::NW(self) } - fn align_sw (self) -> Align { Align::SW(self) } - fn align_ne (self) -> Align { Align::NE(self) } - fn align_se (self) -> Align { Align::SE(self) } - fn align_x (self) -> Align { Align::X(self) } - fn align_y (self) -> Align { Align::Y(self) } - fn fixed_x (self, x: E::Unit) -> Fixed { Fixed::X(x, self) } - fn fixed_y (self, y: E::Unit) -> Fixed { Fixed::Y(y, self) } - fn fixed_xy (self, x: E::Unit, y: E::Unit) -> Fixed { Fixed::XY(x, y, self) } - fn min_x (self, x: E::Unit) -> Min { Min::X(x, self) } - fn min_y (self, y: E::Unit) -> Min { Min::Y(y, self) } - fn min_xy (self, x: E::Unit, y: E::Unit) -> Min { Min::XY(x, y, self) } - fn max_x (self, x: E::Unit) -> Max { Max::X(x, self) } - fn max_y (self, y: E::Unit) -> Max { Max::Y(y, self) } - fn max_xy (self, x: E::Unit, y: E::Unit) -> Max { Max::XY(x, y, self) } - fn push_x (self, x: E::Unit) -> Push { Push::X(x, self) } - fn push_y (self, y: E::Unit) -> Push { Push::Y(y, self) } - fn push_xy (self, x: E::Unit, y: E::Unit) -> Push { Push::XY(x, y, self) } - fn pull_x (self, x: E::Unit) -> Pull { Pull::X(x, self) } - fn pull_y (self, y: E::Unit) -> Pull { Pull::Y(y, self) } - fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull { Pull::XY(x, y, self) } - fn grow_x (self, x: E::Unit) -> Grow { Grow::X(x, self) } - fn grow_y (self, y: E::Unit) -> Grow { Grow::Y(y, self) } - fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow { Grow::XY(x, y, self) } - fn shrink_x (self, x: E::Unit) -> Shrink { Shrink::X(x, self) } - fn shrink_y (self, y: E::Unit) -> Shrink { Shrink::Y(y, self) } - fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink { Shrink::XY(x, y, self) } - fn inset_x (self, x: E::Unit) -> Inset { Inset::X(x, self) } - fn inset_y (self, y: E::Unit) -> Inset { Inset::Y(y, self) } - fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset { Inset::XY(x, y, self) } - fn outset_x (self, x: E::Unit) -> Outset { Outset::X(x, self) } - fn outset_y (self, y: E::Unit) -> Outset { Outset::Y(y, self) } - fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset { Outset::XY(x, y, self) } - fn fill_x (self) -> Fill { Fill::X(self) } - fn fill_y (self) -> Fill { Fill::Y(self) } - fn fill_xy (self) -> Fill { Fill::XY(self) } - fn debug (self) -> DebugOverlay { DebugOverlay(self) } - fn split > ( - self, direction: Direction, amount: E::Unit, other: W - ) -> Split { Split::new(direction, amount, self, other) } - fn split_flip > ( - self, direction: Direction, amount: E::Unit, other: W - ) -> Split { Split::new(direction, amount, other, self) } -} - -impl> Layout for W {} - -pub struct DebugOverlay>(pub W); - -pub enum Fill> { X(W), Y(W), XY(W) } - -impl> Fill { - fn inner (&self) -> &W { - match self { - Self::X(inner) => &inner, - Self::Y(inner) => &inner, - Self::XY(inner) => &inner, - } - } -} - -impl> Render for Fill { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - let area = self.inner().min_size(to.into())?; - if let Some(area) = area { - Ok(Some(match self { - Self::X(_) => [to.w().into(), area.h()], - Self::Y(_) => [area.w(), to.h().into()], - Self::XY(_) => [to.w().into(), to.h().into()], - }.into())) - } else { - Ok(None) - } - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.inner().render(to) - } -} - -pub struct Layers< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, PhantomData); - -impl< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -> Layers { - #[inline] - pub fn new (build: F) -> Self { - Self(build, Default::default()) - } -} - -impl Render for Layers -where - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -{ - type Engine = E; - fn min_size (&self, area: E::Size) -> Perhaps { - 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(()) - } - } -} - #[derive(Copy, Clone, PartialEq)] pub enum Direction { Up, Down, Left, Right, } impl Direction { @@ -282,676 +155,3 @@ impl Direction { } } } - -/// Override X and Y coordinates, aligning to corner, side, or center of area -pub enum Align { - /// Draw at center of container - Center(L), - /// Draw at center of X axis - X(L), - /// Draw at center of Y axis - Y(L), - /// Draw at upper left corner of contaier - NW(L), - /// Draw at center of upper edge of container - N(L), - /// Draw at right left corner of contaier - NE(L), - /// Draw at center of left edge of container - W(L), - /// Draw at center of right edge of container - E(L), - /// Draw at lower left corner of container - SW(L), - /// Draw at center of lower edge of container - S(L), - /// Draw at lower right edge of container - SE(L) -} - -impl Align { - pub fn inner (&self) -> &T { - match self { - Self::Center(inner) => inner, - Self::X(inner) => inner, - Self::Y(inner) => inner, - Self::NW(inner) => inner, - Self::N(inner) => inner, - Self::NE(inner) => inner, - Self::W(inner) => inner, - Self::E(inner) => inner, - Self::SW(inner) => inner, - Self::S(inner) => inner, - Self::SE(inner) => inner, - } - } -} - -fn align + From<[N;4]>> (align: &Align, outer: R, inner: R) -> Option { - if outer.w() < inner.w() || outer.h() < inner.h() { - None - } else { - let [ox, oy, ow, oh] = outer.xywh(); - let [ix, iy, iw, ih] = inner.xywh(); - Some(match align { - Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(), - Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::NW(_) => [ox, oy, iw, ih,].into(), - Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(), - Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(), - Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(), - Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(), - Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(), - }) - } -} - -impl> Render for Align { - type Engine = E; - fn min_size (&self, outer_area: E::Size) -> Perhaps { - self.inner().min_size(outer_area) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - let outer_area = to.area(); - Ok(if let Some(inner_size) = self.min_size(outer_area.wh().into())? { - let inner_area = outer_area.clip(inner_size); - if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) { - to.render_in(aligned, self.inner())? - } else { - () - } - } else { - () - }) - } -} - -/// Enforce fixed size of drawing area -pub enum Fixed { - /// Enforce fixed width - X(U, T), - /// Enforce fixed height - Y(U, T), - /// Enforce fixed width and height - XY(U, U, T), -} -impl Fixed { - pub fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } -} -impl> Render for Fixed { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(match self { - Self::X(w, _) => - if to.w() >= *w { Some([*w, to.h()].into()) } else { None }, - Self::Y(h, _) => - if to.h() >= *h { Some([to.w(), *h].into()) } else { None }, - Self::XY(w, h, _) - => if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None }, - }) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - // 🡘 🡙 ←🡙→ - if let Some(size) = self.min_size(to.area().wh().into())? { - to.render_in(to.area().clip(size).into(), self.inner()) - } else { - Ok(()) - } - } -} - -/// Enforce minimum size of drawing area -pub enum Min { - /// Enforce minimum width - X(U, T), - /// Enforce minimum height - Y(U, T), - /// Enforce minimum width and height - XY(U, U, T), -} -impl Min { - pub fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } -} -impl> Render for Min { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [to.w().max(w), to.h()], - Self::Y(h, _) => [to.w(), to.h().max(h)], - Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)], - }.into())) - } - // TODO: 🡘 🡙 ←🡙→ - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} - -/// Enforce maximum size of drawing area -pub enum Max { - /// Enforce maximum width - X(U, T), - /// Enforce maximum height - Y(U, T), - /// Enforce maximum width and height - XY(U, U, T), -} - -impl Max { - fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } -} - -impl> Render for Max { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [to.w().min(w), to.h()], - Self::Y(h, _) => [to.w(), to.h().min(h)], - Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)], - }.into())) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} - -/// Expand drawing area -pub enum Grow { - /// Increase width - X(N, T), - /// Increase height - Y(N, T), - /// Increase width and height - XY(N, N, T) -} - -impl Grow { - fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } -} - -impl> Render for Grow { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [to.w() + w, to.h()], - Self::Y(h, _) => [to.w(), to.h() + h], - Self::XY(w, h, _) => [to.w() + w, to.h() + h], - }.into())) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} - -/// Shrink drawing area -pub enum Shrink { - /// Decrease width - X(N, T), - /// Decrease height - Y(N, T), - /// Decrease width and height - XY(N, N, T), -} - -impl Shrink { - fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } -} - -impl> Render for Shrink { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [ - if to.w() > w { to.w() - w } else { 0.into() }, - to.h() - ], - Self::Y(h, _) => [ - to.w(), - if to.h() > h { to.h() - h } else { 0.into() } - ], - Self::XY(w, h, _) => [ - if to.w() > w { to.w() - w } else { 0.into() }, - if to.h() > h { to.h() - h } else { 0.into() } - ] - }.into())) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} - -/// Shrink from each side -pub enum Inset { - /// Decrease width - X(N, T), - /// Decrease height - Y(N, T), - /// Decrease width and height - XY(N, N, T), -} - -impl Inset { - pub fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } -} - -/// Grow on each side -pub enum Outset { - /// Increase width - X(N, T), - /// Increase height - Y(N, T), - /// Increase width and height - XY(N, N, T), -} - -impl Outset { - pub fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } -} - -impl> Render for Inset { - type Engine = E; - fn render (&self, to: &mut E::Output) -> Usually<()> { - match *self { - Self::X(x, ref inner) => - (inner as &dyn Render).shrink_x(x).push_x(x), - Self::Y(y, ref inner) => - (inner as &dyn Render).shrink_y(y).push_y(y), - Self::XY(x, y, ref inner) => - (inner as &dyn Render).shrink_xy(x, y).push_xy(x, y) - }.render(to) - } -} - -impl> Render for Outset { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - match *self { - Self::X(x, ref inner) => - (inner as &dyn Render).grow_x(x + x), - Self::Y(y, ref inner) => - (inner as &dyn Render).grow_y(y + y), - Self::XY(x, y, ref inner) => - (inner as &dyn Render).grow_xy(x + x, y + y), - }.min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - match *self { - Self::X(x, ref inner) => - (inner as &dyn Render).push_x(x), - Self::Y(y, ref inner) => - (inner as &dyn Render).push_y(y), - Self::XY(x, y, ref inner) => - (inner as &dyn Render).push_xy(x, y), - }.render(to) - } -} - -/// Move origin point of drawing area -pub enum Push { - /// Move origin to the right - X(N, T), - /// Move origin downwards - Y(N, T), - /// Move origin to the right and downwards - XY(N, N, T), -} - -impl Push { - pub fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } - pub fn x (&self) -> N { - match self { Self::X(x, _) => *x, Self::Y(_, _) => N::default(), Self::XY(x, _, _) => *x } - } - pub fn y (&self) -> N { - match self { Self::X(_, _) => N::default(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y } - } -} - -impl> Render for Push { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - self.inner().min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - let area = to.area(); - Ok(self.min_size(area.wh().into())? - .map(|size|to.render_in(match *self { - Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()], - Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()], - Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()], - }.into(), self.inner())).transpose()?.unwrap_or(())) - } -} - -/// Move origin point of drawing area -pub enum Pull { - /// Move origin to the right - X(N, T), - /// Move origin downwards - Y(N, T), - /// Move origin to the right and downwards - XY(N, N, T), -} - -impl Pull { - pub fn inner (&self) -> &T { - match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } - } - pub fn x (&self) -> N { - match self { Self::X(x, _) => *x, Self::Y(_, _) => N::default(), Self::XY(x, _, _) => *x } - } - pub fn y (&self) -> N { - match self { Self::X(_, _) => N::default(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y } - } -} - -impl> Render for Pull { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - self.inner().min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - let area = to.area(); - Ok(self.min_size(area.wh().into())? - .map(|size|to.render_in(match *self { - Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()], - Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()], - Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()], - }.into(), self.inner())).transpose()?.unwrap_or(())) - } -} - -pub struct Stack< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, pub Direction, PhantomData); - -impl< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -> Stack { - #[inline] pub fn new (direction: Direction, build: F) -> Self { - Self(build, direction, Default::default()) - } - #[inline] pub fn right (build: F) -> Self { - Self::new(Direction::Right, build) - } - #[inline] pub fn down (build: F) -> Self { - Self::new(Direction::Down, build) - } - #[inline] pub fn up (build: F) -> Self { - Self::new(Direction::Up, build) - } -} - -impl Render for Stack -where - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -{ - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - match self.1 { - - Direction::Down => { - let mut w: E::Unit = 0.into(); - let mut h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - let max = to.h().minus(h); - if max > E::Unit::ZERO() { - let item = component.push_y(h).max_y(max); - 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())) - }, - - Direction::Right => { - let mut w: E::Unit = 0.into(); - let mut h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - let max = to.w().minus(w); - if max > E::Unit::ZERO() { - let item = component.push_x(w).max_x(max); - 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())) - }, - - Direction::Up => { - let mut w: E::Unit = 0.into(); - let mut h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - let max = to.h().minus(h); - if max > E::Unit::ZERO() { - let item = component.max_y(to.h() - h); - 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())) - }, - - Direction::Left => { - let mut w: E::Unit = 0.into(); - let mut h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - 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 { - Direction::Down => { - (self.0)(&mut |item| { - if h < area.h() { - let item = item.push_y(h).max_y(area.h() - h); - 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(()) - })?; - }, - Direction::Right => { - (self.0)(&mut |item| { - if w < area.w() { - let item = item.push_x(w).max_x(area.w() - w); - 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(()) - })?; - }, - Direction::Up => { - (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 { - item.push_y(area.h() - height).shrink_y(height).render(to)?; - h = h + height; - if width > w { w = width } - }; - } - Ok(()) - })?; - }, - _ => todo!() - }; - Ok(()) - } -} - -#[macro_export] macro_rules! lay { - ($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) } -} -#[macro_export] macro_rules! col { - ($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) }; - ($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::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) }; - ($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(()) }) }; - ($pat:pat in $collection:expr => $item:expr) => { - Stack::right(move |add|{ - for $pat in $collection { add(&$item)?; } - Ok(()) - }) - } -} - -/// A binary split with fixed proportion -pub struct Split, B: Render>( - pub Direction, pub E::Unit, A, B, PhantomData -); - -impl, B: Render> Split { - pub fn new (direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { - Self(direction, proportion, a, b, Default::default()) - } - pub fn up (proportion: E::Unit, a: A, b: B) -> Self { - Self(Direction::Up, proportion, a, b, Default::default()) - } - pub fn down (proportion: E::Unit, a: A, b: B) -> Self { - Self(Direction::Down, proportion, a, b, Default::default()) - } - pub fn left (proportion: E::Unit, a: A, b: B) -> Self { - Self(Direction::Left, proportion, a, b, Default::default()) - } - pub fn right (proportion: E::Unit, a: A, b: B) -> Self { - Self(Direction::Right, proportion, a, b, Default::default()) - } -} - -impl, B: Render> Render for Split { - type Engine = E; - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(Some(to)) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - let (a, b) = to.area().split_fixed(self.0, self.1); - to.render_in(a.into(), &self.2)?; - to.render_in(b.into(), &self.3)?; - Ok(()) - } -} - -/// A widget that tracks its render width and height -pub struct Measure(PhantomData, AtomicUsize, AtomicUsize); - -impl Clone for Measure { - fn clone (&self) -> Self { - Self( - Default::default(), - AtomicUsize::from(self.1.load(Ordering::Relaxed)), - AtomicUsize::from(self.2.load(Ordering::Relaxed)), - ) - } -} - -impl std::fmt::Debug for Measure { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("Measure") - .field("width", &self.0) - .field("height", &self.1) - .finish() - } -} - -impl Measure { - pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) } - pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) } - pub fn wh (&self) -> [usize;2] { [self.w(), self.h()] } - pub fn set_w (&self, w: impl Into) { self.1.store(w.into(), Ordering::Relaxed) } - pub fn set_h (&self, h: impl Into) { self.2.store(h.into(), Ordering::Relaxed) } - pub fn set_wh (&self, w: impl Into, h: impl Into) { self.set_w(w); self.set_h(h); } - pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) } - pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) } -} - -impl Render for Measure { - type Engine = E; - fn min_size (&self, _: E::Size) -> Perhaps { - Ok(Some([0u16.into(), 0u16.into()].into())) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.set_w(to.area().w()); - self.set_h(to.area().h()); - Ok(()) - } -} - -/// A scrollable area. -pub struct Scroll< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, pub Direction, pub u64, PhantomData); diff --git a/crates/tek_layout/Cargo.toml b/crates/tek_layout/Cargo.toml new file mode 100644 index 00000000..29fc6a14 --- /dev/null +++ b/crates/tek_layout/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "tek_layout" +edition = "2021" +version = "0.1.0" + +[dependencies] +tek_core = { path = "../tek_core" } diff --git a/crates/tek_layout/src/align.rs b/crates/tek_layout/src/align.rs new file mode 100644 index 00000000..1a89e3db --- /dev/null +++ b/crates/tek_layout/src/align.rs @@ -0,0 +1,102 @@ +use crate::*; + +impl> LayoutAlign for W {} + +pub trait LayoutAlign: Render + Sized { + fn align_x (self) -> Align { Align::X(self) } + fn align_y (self) -> Align { Align::Y(self) } + fn align_center (self) -> Align { Align::Center(self) } + fn align_n (self) -> Align { Align::N(self) } + fn align_s (self) -> Align { Align::S(self) } + fn align_e (self) -> Align { Align::E(self) } + fn align_w (self) -> Align { Align::W(self) } + fn align_nw (self) -> Align { Align::NW(self) } + fn align_sw (self) -> Align { Align::SW(self) } + fn align_ne (self) -> Align { Align::NE(self) } + fn align_se (self) -> Align { Align::SE(self) } +} + +/// Override X and Y coordinates, aligning to corner, side, or center of area +pub enum Align { + /// Draw at center of container + Center(L), + /// Draw at center of X axis + X(L), + /// Draw at center of Y axis + Y(L), + /// Draw at upper left corner of contaier + NW(L), + /// Draw at center of upper edge of container + N(L), + /// Draw at right left corner of contaier + NE(L), + /// Draw at center of left edge of container + W(L), + /// Draw at center of right edge of container + E(L), + /// Draw at lower left corner of container + SW(L), + /// Draw at center of lower edge of container + S(L), + /// Draw at lower right edge of container + SE(L) +} + +impl Align { + pub fn inner (&self) -> &T { + match self { + Self::Center(inner) => inner, + Self::X(inner) => inner, + Self::Y(inner) => inner, + Self::NW(inner) => inner, + Self::N(inner) => inner, + Self::NE(inner) => inner, + Self::W(inner) => inner, + Self::E(inner) => inner, + Self::SW(inner) => inner, + Self::S(inner) => inner, + Self::SE(inner) => inner, + } + } +} + +fn align + From<[N;4]>> (align: &Align, outer: R, inner: R) -> Option { + if outer.w() < inner.w() || outer.h() < inner.h() { + None + } else { + let [ox, oy, ow, oh] = outer.xywh(); + let [ix, iy, iw, ih] = inner.xywh(); + Some(match align { + Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(), + Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::NW(_) => [ox, oy, iw, ih,].into(), + Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(), + Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(), + Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(), + Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(), + Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(), + }) + } +} + +impl> Render for Align { + fn min_size (&self, outer_area: E::Size) -> Perhaps { + self.inner().min_size(outer_area) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let outer_area = to.area(); + Ok(if let Some(inner_size) = self.min_size(outer_area.wh().into())? { + let inner_area = outer_area.clip(inner_size); + if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) { + to.render_in(aligned, self.inner())? + } else { + () + } + } else { + () + }) + } +} diff --git a/crates/tek_layout/src/bsp.rs b/crates/tek_layout/src/bsp.rs new file mode 100644 index 00000000..d5f904d4 --- /dev/null +++ b/crates/tek_layout/src/bsp.rs @@ -0,0 +1,54 @@ +use crate::*; + +impl> LayoutBsp for R {} + +pub trait LayoutBsp: Render + Sized { + fn when (self, cond: bool) -> If { + If(Default::default(), cond, self) + } + fn or > (self, cond: bool, other: B) -> Either { + Either(Default::default(), cond, self, other) + } + fn over > (self, other: B) -> Over { + Over(Default::default(), self, other) + } + fn under > (self, other: B) -> Under { + Under(Default::default(), self, other) + } + fn north_of > (self, other: B) -> North { + North(Default::default(), self, other) + } + fn south_of > (self, other: B) -> South { + South(Default::default(), self, other) + } + fn east_of > (self, other: B) -> East { + East(Default::default(), self, other) + } + fn west_of > (self, other: B) -> West { + West(Default::default(), self, other) + } +} + +/// Render widget if predicate is true +pub struct If>(PhantomData, bool, A); + +impl> Content for If { + fn content (&self) -> impl Render { + if self.1 { Some(widget(&self.2)) } else { None } + } +} + +/// Render widget A if predicate is true, otherwise widget B +pub struct Either, B: Render>(PhantomData, bool, A, B); + +pub struct Over, B: Render>(PhantomData, A, B); + +pub struct Under, B: Render>(PhantomData, A, B); + +pub struct North, B: Render>(PhantomData, A, B); + +pub struct South, B: Render>(PhantomData, A, B); + +pub struct East, B: Render>(PhantomData, A, B); + +pub struct West, B: Render>(PhantomData, A, B); diff --git a/crates/tek_layout/src/debug.rs b/crates/tek_layout/src/debug.rs new file mode 100644 index 00000000..6de7cbce --- /dev/null +++ b/crates/tek_layout/src/debug.rs @@ -0,0 +1,11 @@ +use crate::*; + +impl> LayoutDebug for W {} + +pub trait LayoutDebug: Render + Sized { + fn debug (self) -> DebugOverlay { + DebugOverlay(Default::default(), self) + } +} + +pub struct DebugOverlay>(PhantomData, pub W); diff --git a/crates/tek_layout/src/fill.rs b/crates/tek_layout/src/fill.rs new file mode 100644 index 00000000..b9269f7b --- /dev/null +++ b/crates/tek_layout/src/fill.rs @@ -0,0 +1,52 @@ +use crate::*; + +impl> LayoutFill for W {} + +pub trait LayoutFill: Render + Sized { + fn fill_x (self) -> Fill { + Fill::X(self) + } + fn fill_y (self) -> Fill { + Fill::Y(self) + } + fn fill_xy (self) -> Fill { + Fill::XY(self) + } +} + +pub enum Fill> { + X(W), + Y(W), + XY(W), + _Unused(PhantomData) +} + +impl> Fill { + fn inner (&self) -> &W { + match self { + Self::X(inner) => &inner, + Self::Y(inner) => &inner, + Self::XY(inner) => &inner, + _ => unreachable!(), + } + } +} + +impl> Render for Fill { + fn min_size (&self, to: E::Size) -> Perhaps { + let area = self.inner().min_size(to.into())?; + if let Some(area) = area { + Ok(Some(match self { + Self::X(_) => [to.w().into(), area.h()], + Self::Y(_) => [area.w(), to.h().into()], + Self::XY(_) => [to.w().into(), to.h().into()], + _ => unreachable!(), + }.into())) + } else { + Ok(None) + } + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.inner().render(to) + } +} diff --git a/crates/tek_layout/src/fixed.rs b/crates/tek_layout/src/fixed.rs new file mode 100644 index 00000000..67b01e24 --- /dev/null +++ b/crates/tek_layout/src/fixed.rs @@ -0,0 +1,58 @@ +use crate::*; + +impl> LayoutFixed for W {} + +pub trait LayoutFixed: Render + Sized { + fn fixed_x (self, x: E::Unit) -> Fixed { + Fixed::X(x, self) + } + fn fixed_y (self, y: E::Unit) -> Fixed { + Fixed::Y(y, self) + } + fn fixed_xy (self, x: E::Unit, y: E::Unit) -> Fixed { + Fixed::XY(x, y, self) + } +} + +/// Enforce fixed size of drawing area +pub enum Fixed { + _Unused(PhantomData), + /// Enforce fixed width + X(E::Unit, T), + /// Enforce fixed height + Y(E::Unit, T), + /// Enforce fixed width and height + XY(E::Unit, E::Unit, T), +} + +impl Fixed { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } +} +impl> Render for Fixed { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(match self { + Self::X(w, _) => + if to.w() >= *w { Some([*w, to.h()].into()) } else { None }, + Self::Y(h, _) => + if to.h() >= *h { Some([to.w(), *h].into()) } else { None }, + Self::XY(w, h, _) + => if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None }, + _ => unreachable!(), + }) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + // 🡘 🡙 ←🡙→ + if let Some(size) = self.min_size(to.area().wh().into())? { + to.render_in(to.area().clip(size).into(), self.inner()) + } else { + Ok(()) + } + } +} diff --git a/crates/tek_layout/src/grow.rs b/crates/tek_layout/src/grow.rs new file mode 100644 index 00000000..572ef9e4 --- /dev/null +++ b/crates/tek_layout/src/grow.rs @@ -0,0 +1,46 @@ +use crate::*; + +impl> LayoutGrow for W {} + +pub trait LayoutGrow: Render + Sized { + fn grow_x (self, x: E::Unit) -> Grow { + Grow::X(x, self) + } + fn grow_y (self, y: E::Unit) -> Grow { + Grow::Y(y, self) + } + fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow { + Grow::XY(x, y, self) + } +} + +/// Expand drawing area +pub enum Grow { + /// Increase width + X(N, T), + /// Increase height + Y(N, T), + /// Increase width and height + XY(N, N, T) +} + +impl Grow { + fn inner (&self) -> &T { + match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } + } +} + +impl> Render for Grow { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w() + w, to.h()], + Self::Y(h, _) => [to.w(), to.h() + h], + Self::XY(w, h, _) => [to.w() + w, to.h() + h], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} diff --git a/crates/tek_layout/src/inset.rs b/crates/tek_layout/src/inset.rs new file mode 100644 index 00000000..10442993 --- /dev/null +++ b/crates/tek_layout/src/inset.rs @@ -0,0 +1,48 @@ +use crate::*; + +impl> LayoutInset for W {} + +pub trait LayoutInset: Render + Sized { + fn inset_x (self, x: E::Unit) -> Inset { + Inset::X(x, self) + } + fn inset_y (self, y: E::Unit) -> Inset { + Inset::Y(y, self) + } + fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset { + Inset::XY(x, y, self) + } +} + +/// Shrink from each side +pub enum Inset { + _Unused(PhantomData), + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +impl> Inset { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } +} + +impl> Render for Inset { + fn render (&self, to: &mut E::Output) -> Usually<()> { + match *self { + Self::X(x, ref inner) => (inner as &dyn Render).shrink_x(x).push_x(x), + Self::Y(y, ref inner) => (inner as &dyn Render).shrink_y(y).push_y(y), + Self::XY(x, y, ref inner) => (inner as &dyn Render).shrink_xy(x, y).push_xy(x, y), + _ => unreachable!(), + }.render(to) + } +} diff --git a/crates/tek_layout/src/layers.rs b/crates/tek_layout/src/layers.rs new file mode 100644 index 00000000..0269a727 --- /dev/null +++ b/crates/tek_layout/src/layers.rs @@ -0,0 +1,45 @@ +use crate::*; + +#[macro_export] macro_rules! lay { + ($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) } +} + +pub struct Layers< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, PhantomData); + +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +> Layers { + #[inline] + pub fn new (build: F) -> Self { + Self(build, Default::default()) + } +} + +impl Render for Layers +where + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +{ + fn min_size (&self, area: E::Size) -> Perhaps { + 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(()) + } + } +} diff --git a/crates/tek_layout/src/lib.rs b/crates/tek_layout/src/lib.rs new file mode 100644 index 00000000..81497eb3 --- /dev/null +++ b/crates/tek_layout/src/lib.rs @@ -0,0 +1,18 @@ +pub(crate) use tek_core::*; + +submod! { + align + bsp + debug + fill + fixed + grow + inset + max + min + outset + pull + push + shrink + split +} diff --git a/crates/tek_layout/src/max.rs b/crates/tek_layout/src/max.rs new file mode 100644 index 00000000..e0d36928 --- /dev/null +++ b/crates/tek_layout/src/max.rs @@ -0,0 +1,38 @@ +use crate::*; + +pub trait LayoutMax: Render + Sized { + fn max_x (self, x: E::Unit) -> Max { Max::X(x, self) } + fn max_y (self, y: E::Unit) -> Max { Max::Y(y, self) } + fn max_xy (self, x: E::Unit, y: E::Unit) -> Max { Max::XY(x, y, self) } +} + +/// Enforce maximum size of drawing area +pub enum Max { + /// Enforce maximum width + X(U, T), + /// Enforce maximum height + Y(U, T), + /// Enforce maximum width and height + XY(U, U, T), +} + +impl Max { + fn inner (&self) -> &T { + match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } + } +} + +impl> Render for Max { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w().min(w), to.h()], + Self::Y(h, _) => [to.w(), to.h().min(h)], + Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} diff --git a/crates/tek_layout/src/measure.rs b/crates/tek_layout/src/measure.rs new file mode 100644 index 00000000..aaffbc18 --- /dev/null +++ b/crates/tek_layout/src/measure.rs @@ -0,0 +1,46 @@ +use crate::*; + +/// A widget that tracks its render width and height +pub struct Measure(PhantomData, AtomicUsize, AtomicUsize); + +impl Clone for Measure { + fn clone (&self) -> Self { + Self( + Default::default(), + AtomicUsize::from(self.1.load(Ordering::Relaxed)), + AtomicUsize::from(self.2.load(Ordering::Relaxed)), + ) + } +} + +impl std::fmt::Debug for Measure { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Measure") + .field("width", &self.0) + .field("height", &self.1) + .finish() + } +} + +impl Measure { + pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) } + pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) } + pub fn wh (&self) -> [usize;2] { [self.w(), self.h()] } + pub fn set_w (&self, w: impl Into) { self.1.store(w.into(), Ordering::Relaxed) } + pub fn set_h (&self, h: impl Into) { self.2.store(h.into(), Ordering::Relaxed) } + pub fn set_wh (&self, w: impl Into, h: impl Into) { self.set_w(w); self.set_h(h); } + pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) } + pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) } +} + +impl Render for Measure { + fn min_size (&self, _: E::Size) -> Perhaps { + Ok(Some([0u16.into(), 0u16.into()].into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.set_w(to.area().w()); + self.set_h(to.area().h()); + Ok(()) + } +} + diff --git a/crates/tek_layout/src/min.rs b/crates/tek_layout/src/min.rs new file mode 100644 index 00000000..299ecbb1 --- /dev/null +++ b/crates/tek_layout/src/min.rs @@ -0,0 +1,37 @@ +use crate::*; + +pub trait LayoutMin: Render + Sized { + fn min_x (self, x: E::Unit) -> Min { Min::X(x, self) } + fn min_y (self, y: E::Unit) -> Min { Min::Y(y, self) } + fn min_xy (self, x: E::Unit, y: E::Unit) -> Min { Min::XY(x, y, self) } +} + +/// Enforce minimum size of drawing area +pub enum Min { + /// Enforce minimum width + X(U, T), + /// Enforce minimum height + Y(U, T), + /// Enforce minimum width and height + XY(U, U, T), +} +impl Min { + pub fn inner (&self) -> &T { + match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, } + } +} +impl> Render for Min { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w().max(w), to.h()], + Self::Y(h, _) => [to.w(), to.h().max(h)], + Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)], + }.into())) + } + // TODO: 🡘 🡙 ←🡙→ + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} diff --git a/crates/tek_layout/src/outset.rs b/crates/tek_layout/src/outset.rs new file mode 100644 index 00000000..13851b68 --- /dev/null +++ b/crates/tek_layout/src/outset.rs @@ -0,0 +1,57 @@ +use crate::*; + +impl> LayoutOutset for W {} + +pub trait LayoutOutset: Render + Sized { + fn outset_x (self, x: E::Unit) -> Outset { + Outset::X(x, self) + } + fn outset_y (self, y: E::Unit) -> Outset { + Outset::Y(y, self) + } + fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset { + Outset::XY(x, y, self) + } +} + +/// Grow on each side +pub enum Outset> { + _Unused(PhantomData), + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T), +} + + +impl> Outset { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } +} + +impl> Render for Outset { + fn min_size (&self, to: E::Size) -> Perhaps { + match *self { + Self::X(x, ref inner) => (inner as &dyn Render).grow_x(x + x), + Self::Y(y, ref inner) => (inner as &dyn Render).grow_y(y + y), + Self::XY(x, y, ref inner) => (inner as &dyn Render).grow_xy(x + x, y + y), + _ => unreachable!(), + }.min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + match *self { + Self::X(x, ref inner) => (inner as &dyn Render).push_x(x), + Self::Y(y, ref inner) => (inner as &dyn Render).push_y(y), + Self::XY(x, y, ref inner) => (inner as &dyn Render).push_xy(x, y), + _ => unreachable!(), + }.render(to) + } +} diff --git a/crates/tek_layout/src/pull.rs b/crates/tek_layout/src/pull.rs new file mode 100644 index 00000000..918c870c --- /dev/null +++ b/crates/tek_layout/src/pull.rs @@ -0,0 +1,69 @@ +use crate::*; + +impl> LayoutPull for W {} + +pub trait LayoutPull: Render + Sized { + fn pull_x (self, x: E::Unit) -> Pull { + Pull::X(x, self) + } + fn pull_y (self, y: E::Unit) -> Pull { + Pull::Y(y, self) + } + fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull { + Pull::XY(x, y, self) + } +} + +/// Move origin point of drawing area +pub enum Pull> { + _Unused(PhantomData), + /// Move origin to the right + X(E::Unit, T), + /// Move origin downwards + Y(E::Unit, T), + /// Move origin to the right and downwards + XY(E::Unit, E::Unit, T), +} + +impl> Pull { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } + pub fn x (&self) -> E::Unit { + match self { + Self::X(x, _) => *x, + Self::Y(_, _) => E::Unit::default(), + Self::XY(x, _, _) => *x, + _ => unreachable!(), + } + } + pub fn y (&self) -> E::Unit { + match self { + Self::X(_, _) => E::Unit::default(), + Self::Y(y, _) => *y, + Self::XY(_, y, _) => *y, + _ => unreachable!(), + } + } +} + +impl> Render for Pull { + fn min_size (&self, to: E::Size) -> Perhaps { + self.inner().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let area = to.area(); + Ok(self.min_size(area.wh().into())? + .map(|size|to.render_in(match *self { + Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()], + Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()], + Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()], + _ => unreachable!(), + }.into(), self.inner())).transpose()?.unwrap_or(())) + } +} diff --git a/crates/tek_layout/src/push.rs b/crates/tek_layout/src/push.rs new file mode 100644 index 00000000..ff41c553 --- /dev/null +++ b/crates/tek_layout/src/push.rs @@ -0,0 +1,68 @@ +use crate::*; + +impl> LayoutPush for W {} + +pub trait LayoutPush: Render + Sized { + fn push_x (self, x: E::Unit) -> Push { + Push::X(x, self) + } + fn push_y (self, y: E::Unit) -> Push { + Push::Y(y, self) + } + fn push_xy (self, x: E::Unit, y: E::Unit) -> Push { + Push::XY(x, y, self) + } +} + +/// Move origin point of drawing area +pub enum Push> { + /// Move origin to the right + X(E::Unit, T), + /// Move origin downwards + Y(E::Unit, T), + /// Move origin to the right and downwards + XY(E::Unit, E::Unit, T), +} + +impl> Push { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } + pub fn x (&self) -> E::Unit { + match self { + Self::X(x, _) => *x, + Self::Y(_, _) => E::Unit::default(), + Self::XY(x, _, _) => *x, + _ => unreachable!(), + } + } + pub fn y (&self) -> E::Unit { + match self { + Self::X(_, _) => E::Unit::default(), + Self::Y(y, _) => *y, + Self::XY(_, y, _) => *y, + _ => unreachable!(), + } + } +} + +impl> Render for Push { + fn min_size (&self, to: E::Size) -> Perhaps { + self.inner().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let area = to.area(); + Ok(self.min_size(area.wh().into())? + .map(|size|to.render_in(match *self { + Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()], + Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()], + Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()], + _ => unreachable!(), + }.into(), self.inner())).transpose()?.unwrap_or(())) + } +} diff --git a/crates/tek_layout/src/scroll.rs b/crates/tek_layout/src/scroll.rs new file mode 100644 index 00000000..326f6ab6 --- /dev/null +++ b/crates/tek_layout/src/scroll.rs @@ -0,0 +1,8 @@ +use crate::*; + +/// A scrollable area. +pub struct Scroll< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, pub Direction, pub u64, PhantomData); + diff --git a/crates/tek_layout/src/shrink.rs b/crates/tek_layout/src/shrink.rs new file mode 100644 index 00000000..706973dc --- /dev/null +++ b/crates/tek_layout/src/shrink.rs @@ -0,0 +1,62 @@ +use crate::*; + +impl> LayoutShrink for W {} + +pub trait LayoutShrink: Render + Sized { + fn shrink_x (self, x: E::Unit) -> Shrink { + Shrink::X(x, self) + } + fn shrink_y (self, y: E::Unit) -> Shrink { + Shrink::Y(y, self) + } + fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink { + Shrink::XY(x, y, self) + } +} + +/// Shrink drawing area +pub enum Shrink { + _Unused(PhantomData), + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +impl> Shrink { + fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } +} + +impl> Render for Shrink { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [ + if to.w() > w { to.w() - w } else { 0.into() }, + to.h() + ], + Self::Y(h, _) => [ + to.w(), + if to.h() > h { to.h() - h } else { 0.into() } + ], + Self::XY(w, h, _) => [ + if to.w() > w { to.w() - w } else { 0.into() }, + if to.h() > h { to.h() - h } else { 0.into() } + ], + _ => unreachable!(), + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} diff --git a/crates/tek_layout/src/split.rs b/crates/tek_layout/src/split.rs new file mode 100644 index 00000000..476ecc10 --- /dev/null +++ b/crates/tek_layout/src/split.rs @@ -0,0 +1,45 @@ +use crate::*; + +pub trait LayoutSplit: Render + Sized { + fn split > ( + self, direction: Direction, amount: E::Unit, other: W + ) -> Split { Split::new(direction, amount, self, other) } + fn split_flip > ( + self, direction: Direction, amount: E::Unit, other: W + ) -> Split { Split::new(direction, amount, other, self) } +} + +/// A binary split with fixed proportion +pub struct Split, B: Render>( + pub Direction, pub E::Unit, A, B, PhantomData +); + +impl, B: Render> Split { + pub fn new (direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { + Self(direction, proportion, a, b, Default::default()) + } + pub fn up (proportion: E::Unit, a: A, b: B) -> Self { + Self(Direction::Up, proportion, a, b, Default::default()) + } + pub fn down (proportion: E::Unit, a: A, b: B) -> Self { + Self(Direction::Down, proportion, a, b, Default::default()) + } + pub fn left (proportion: E::Unit, a: A, b: B) -> Self { + Self(Direction::Left, proportion, a, b, Default::default()) + } + pub fn right (proportion: E::Unit, a: A, b: B) -> Self { + Self(Direction::Right, proportion, a, b, Default::default()) + } +} + +impl, B: Render> Render for Split { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(Some(to)) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let (a, b) = to.area().split_fixed(self.0, self.1); + to.render_in(a.into(), &self.2)?; + to.render_in(b.into(), &self.3)?; + Ok(()) + } +} diff --git a/crates/tek_layout/src/stack.rs b/crates/tek_layout/src/stack.rs new file mode 100644 index 00000000..82c6e46e --- /dev/null +++ b/crates/tek_layout/src/stack.rs @@ -0,0 +1,179 @@ +use crate::*; + +#[macro_export] macro_rules! col { + ($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) }; + ($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::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) }; + ($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(()) }) }; + ($pat:pat in $collection:expr => $item:expr) => { + Stack::right(move |add|{ + for $pat in $collection { add(&$item)?; } + Ok(()) + }) + } +} + +pub struct Stack< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, pub Direction, PhantomData); + +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +> Stack { + #[inline] pub fn new (direction: Direction, build: F) -> Self { + Self(build, direction, Default::default()) + } + #[inline] pub fn right (build: F) -> Self { + Self::new(Direction::Right, build) + } + #[inline] pub fn down (build: F) -> Self { + Self::new(Direction::Down, build) + } + #[inline] pub fn up (build: F) -> Self { + Self::new(Direction::Up, build) + } +} + +impl Render for Stack +where + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +{ + fn min_size (&self, to: E::Size) -> Perhaps { + match self.1 { + + Direction::Down => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::ZERO() { + let item = component.push_y(h).max_y(max); + 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())) + }, + + Direction::Right => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.w().minus(w); + if max > E::Unit::ZERO() { + let item = component.push_x(w).max_x(max); + 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())) + }, + + Direction::Up => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::ZERO() { + let item = component.max_y(to.h() - h); + 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())) + }, + + Direction::Left => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + 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 { + Direction::Down => { + (self.0)(&mut |item| { + if h < area.h() { + let item = item.push_y(h).max_y(area.h() - h); + 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(()) + })?; + }, + Direction::Right => { + (self.0)(&mut |item| { + if w < area.w() { + let item = item.push_x(w).max_x(area.w() - w); + 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(()) + })?; + }, + Direction::Up => { + (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 { + item.push_y(area.h() - height).shrink_y(height).render(to)?; + h = h + height; + if width > w { w = width } + }; + } + Ok(()) + })?; + }, + _ => todo!() + }; + Ok(()) + } +} diff --git a/crates/tek_tui/Cargo.toml b/crates/tek_tui/Cargo.toml index ecf36a1e..c50b78fe 100644 --- a/crates/tek_tui/Cargo.toml +++ b/crates/tek_tui/Cargo.toml @@ -4,8 +4,9 @@ edition = "2021" version = "0.1.0" [dependencies] -tek_core = { path = "../tek_core" } -tek_api = { path = "../tek_api" } +tek_core = { path = "../tek_core" } +tek_layout = { path = "../tek_layout" } +tek_api = { path = "../tek_api" } #tek_snd = { path = "../tek_snd" } livi = "0.7.4" diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index ba9cafae..45849006 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -11,7 +11,10 @@ pub(crate) use std::fs::read_dir; pub(crate) use better_panic::{Settings, Verbosity}; submod! { - tui + tui_engine + tui_engine_input + tui_engine_output + tui_engine_style tui_app_arranger tui_app_sequencer diff --git a/crates/tek_tui/src/tui.rs b/crates/tek_tui/src/tui.rs deleted file mode 100644 index 503b32a6..00000000 --- a/crates/tek_tui/src/tui.rs +++ /dev/null @@ -1,693 +0,0 @@ -use crate::*; -pub(crate) use std::io::{stdout}; -pub(crate) use std::thread::{spawn, JoinHandle}; -pub(crate) use std::time::Duration; -pub(crate) use ratatui::buffer::Cell; -pub(crate) use crossterm::{ExecutableCommand}; -pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; -pub use ratatui::prelude::{Rect, Style, Color, Buffer}; -pub use ratatui::style::{Stylize, Modifier}; -use ratatui::backend::{Backend, CrosstermBackend, ClearType}; -use std::io::Stdout; -use crossterm::terminal::{ - EnterAlternateScreen, LeaveAlternateScreen, - enable_raw_mode, disable_raw_mode -}; - -pub struct Tui { - pub exited: Arc, - pub buffer: Buffer, - pub backend: CrosstermBackend, - pub area: [u16;4], // FIXME auto resize -} -impl Engine for Tui { - type Unit = u16; - type Size = [Self::Unit;2]; - type Area = [Self::Unit;4]; - type Input = TuiInput; - type Handled = bool; - type Output = TuiOutput; - fn exited (&self) -> bool { - self.exited.fetch_and(true, Ordering::Relaxed) - } - fn setup (&mut self) -> Usually<()> { - let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); - std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ - stdout().execute(LeaveAlternateScreen).unwrap(); - CrosstermBackend::new(stdout()).show_cursor().unwrap(); - disable_raw_mode().unwrap(); - better_panic_handler(info); - })); - stdout().execute(EnterAlternateScreen)?; - self.backend.hide_cursor()?; - enable_raw_mode().map_err(Into::into) - } - fn teardown (&mut self) -> Usually<()> { - stdout().execute(LeaveAlternateScreen)?; - self.backend.show_cursor()?; - disable_raw_mode().map_err(Into::into) - } -} -impl Tui { - /// Run the main loop. - pub fn run + Sized + 'static> ( - state: Arc> - ) -> Usually>> { - let backend = CrosstermBackend::new(stdout()); - let area = backend.size()?; - let engine = Self { - exited: Arc::new(AtomicBool::new(false)), - buffer: Buffer::empty(area), - area: area.xywh(), - backend, - }; - let engine = Arc::new(RwLock::new(engine)); - let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100)); - engine.write().unwrap().setup()?; - let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10)); - render_thread.join().expect("main thread failed"); - engine.write().unwrap().teardown()?; - Ok(state) - } - fn spawn_input_thread + Sized + 'static> ( - engine: &Arc>, state: &Arc>, poll: Duration - ) -> JoinHandle<()> { - let exited = engine.read().unwrap().exited.clone(); - let state = state.clone(); - spawn(move || loop { - if exited.fetch_and(true, Ordering::Relaxed) { - break - } - if ::crossterm::event::poll(poll).is_ok() { - let event = TuiEvent::Input(::crossterm::event::read().unwrap()); - match event { - key!(Ctrl-KeyCode::Char('c')) => { - exited.store(true, Ordering::Relaxed); - }, - _ => { - let exited = exited.clone(); - if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) { - panic!("{e}") - } - } - } - } - }) - } - fn spawn_render_thread + Sized + 'static> ( - engine: &Arc>, state: &Arc>, sleep: Duration - ) -> JoinHandle<()> { - let exited = engine.read().unwrap().exited.clone(); - let engine = engine.clone(); - let state = state.clone(); - let size = engine.read().unwrap().backend.size().expect("get size failed"); - let mut buffer = Buffer::empty(size); - spawn(move || loop { - if exited.fetch_and(true, Ordering::Relaxed) { - break - } - let size = engine.read().unwrap().backend.size() - .expect("get size failed"); - if let Ok(state) = state.try_read() { - if buffer.area != size { - engine.write().unwrap().backend.clear_region(ClearType::All) - .expect("clear failed"); - buffer.resize(size); - buffer.reset(); - } - let mut output = TuiOutput { buffer, area: size.xywh() }; - state.render(&mut output).expect("render failed"); - buffer = engine.write().unwrap().flip(output.buffer, size); - } - std::thread::sleep(sleep); - }) - } - fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { - if self.buffer.area != size { - self.backend.clear_region(ClearType::All).unwrap(); - self.buffer.resize(size); - self.buffer.reset(); - } - let updates = self.buffer.diff(&buffer); - self.backend.draw(updates.into_iter()).expect("failed to render"); - self.backend.flush().expect("failed to flush output buffer"); - std::mem::swap(&mut self.buffer, &mut buffer); - buffer.reset(); - buffer - } -} -pub struct TuiInput { event: TuiEvent, exited: Arc, } -impl Input for TuiInput { - type Event = TuiEvent; - fn event (&self) -> &TuiEvent { &self.event } - fn is_done (&self) -> bool { self.exited.fetch_and(true, Ordering::Relaxed) } - fn done (&self) { self.exited.store(true, Ordering::Relaxed); } -} -impl TuiInput { - // TODO remove - pub fn handle_keymap (&self, state: &mut T, keymap: &KeyMap) -> Usually { - match self.event() { - TuiEvent::Input(crossterm::event::Event::Key(event)) => { - for (code, modifiers, _, _, command) in keymap.iter() { - if *code == event.code && modifiers.bits() == event.modifiers.bits() { - return command(state) - } - } - }, - _ => {} - }; - Ok(false) - } -} -pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; -pub type KeyBinding = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler); -pub type KeyMap = [KeyBinding]; -pub struct TuiOutput { pub buffer: Buffer, pub area: [u16;4] } -impl Output 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: &dyn Render - ) -> Usually<()> { - let last = self.area(); - *self.area_mut() = area; - widget.render(self)?; - *self.area_mut() = last; - Ok(()) - } -} -impl TuiOutput { - pub fn buffer_update (&mut self, - area: [u16;4], - callback: &impl Fn(&mut Cell, u16, u16) - ) { - buffer_update(&mut self.buffer, area, callback); - } - pub fn fill_bold (&mut self, area: [u16;4], on: bool) { - if on { - self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD)) - } else { - self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD)) - } - } - pub fn fill_bg (&mut self, area: [u16;4], color: Color) { - self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);}) - } - pub fn fill_fg (&mut self, area: [u16;4], color: Color) { - self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);}) - } - pub fn fill_ul (&mut self, area: [u16;4], color: Color) { - self.buffer_update(area, &|cell,_,_|{ - cell.modifier = ratatui::prelude::Modifier::UNDERLINED; - cell.underline_color = color; - }) - } - pub fn fill_char (&mut self, area: [u16;4], c: char) { - self.buffer_update(area, &|cell,_,_|{cell.set_char(c);}) - } - pub fn make_dim (&mut self) { - for cell in self.buffer.content.iter_mut() { - cell.bg = ratatui::style::Color::Rgb(30,30,30); - cell.fg = ratatui::style::Color::Rgb(100,100,100); - cell.modifier = ratatui::style::Modifier::DIM; - } - } - pub fn blit ( - &mut self, text: &impl AsRef, x: u16, y: u16, style: Option