diff --git a/src/arranger/arranger_v/v_clips.rs b/src/arranger/arranger_v/v_clips.rs index 6d5691ed..19784fca 100644 --- a/src/arranger/arranger_v/v_clips.rs +++ b/src/arranger/arranger_v/v_clips.rs @@ -31,7 +31,7 @@ impl<'a> ArrangerVClips<'a> { Tui::bg(scene.color.base.rgb, if playing { "▶ " } else { " " }), Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, - Tui::grow_x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), + Grow::x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), row!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { Self::format_clip(scene, index, track, (x2 - x1) as u16, height) })]) @@ -53,7 +53,7 @@ impl<'a> ArrangerVClips<'a> { } }; add(&Tui::bg(bg, - Tui::push_x(1, Fixed::w(w, &name.as_str()[0..max_w]))) + Push::x(1, Fixed::w(w, &name.as_str()[0..max_w]))) )?; } Ok(()) diff --git a/src/arranger/arranger_v/v_head.rs b/src/arranger/arranger_v/v_head.rs index 4ff23a15..f64c84d5 100644 --- a/src/arranger/arranger_v/v_head.rs +++ b/src/arranger/arranger_v/v_head.rs @@ -15,7 +15,7 @@ from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16, }); -render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( +render!(|self: ArrangerVHead<'a>|Push::x(self.scenes_w, row!( (_, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => { let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); let color = track.color(); @@ -25,7 +25,7 @@ render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( Tui::fg(color.lightest.rgb, field) ]) } - Tui::bg(color.base.rgb, Tui::min_xy(w as u16, h, Fixed::wh(w as u16, 5, col!([ + Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::wh(w as u16, 5, col!([ row(color, &Self::format_name(track, w)), row(color, &Self::format_input(track)?), row(color, &Self::format_output(track)?), diff --git a/src/groovebox.rs b/src/groovebox.rs index 875564ec..00457ed4 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -120,14 +120,14 @@ render!(|self:Groovebox|{ Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), - Tui::shrink_y(2, col!([ + Shrink::y(2, col!([ Fixed::h(2, row!([ Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), Fixed::h(2, TransportView::from((self, self.player.play_phrase().as_ref().map(|(_,p)| p.as_ref().map(|p|p.read().unwrap().color) ).flatten().clone(), true))), ])), - Tui::push_x(sampler_w, Fixed::h(1, row!([ + Push::x(sampler_w, Fixed::h(1, row!([ PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), ]))), @@ -153,7 +153,7 @@ render!(|self:Groovebox|{ ]), ]), Split::w(false, pool_w, - Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))), + Pull::y(1, Fill::h(Align::e(PoolView(&self.pool)))), Split::e(false, sampler_w, Fill::wh(col!([ Meters(self.sampler.input_meter.as_ref()), GrooveboxSamples(self), diff --git a/src/piano_h.rs b/src/piano_h.rs index be9cb76e..d7e46bfb 100644 --- a/src/piano_h.rs +++ b/src/piano_h.rs @@ -77,16 +77,16 @@ render!(|self: PianoHorizontal|{ let cursor = move||PianoHorizontalCursor(self); let border = Fill::wh(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); - let with_border = |x|lay!([border, Tui::inset_xy(0, 0, &x)]); + let with_border = |x|lay!([border, Inset::xy(0, 0, &x)]); with_border(lay!([ - Tui::push_x(0, row!(![ + Push::x(0, row!(![ //" ", field("Edit:", name.to_string()), " ", field("Length:", length.to_string()), " ", field("Loop:", looped.to_string()) ])), - Tui::inset_xy(0, 1, Fill::wh(Bsp::s( + Inset::xy(0, 1, Fill::wh(Bsp::s( Fixed::h(1, Bsp::e( Fixed::w(self.keys_width, ""), Fill::w(timeline()), diff --git a/src/pool.rs b/src/pool.rs index 0ab0e060..a7ad1a42 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -210,7 +210,7 @@ render!(|self: PoolView<'a>|{ Tui::bg(bg, lay!(move|add|{ add(&Fill::wh(Outer(Style::default().fg(color.base.rgb).bg(bg))))?; //add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; - add(&Tui::inset_xy(0, 1, Fill::wh(col!(move|add|match mode { + add(&Inset::xy(0, 1, Fill::wh(col!(move|add|match mode { Some(PoolMode::Import(_, ref file_picker)) => add(file_picker), Some(PoolMode::Export(_, ref file_picker)) => add(file_picker), _ => Ok(for (i, phrase) in phrases.iter().enumerate() { @@ -226,7 +226,7 @@ render!(|self: PoolView<'a>|{ add(&Tui::bg(color.base.rgb, Fill::w(col!([ Fill::w(lay!(|add|{ add(&Fill::w(Align::w(format!(" {i}"))))?; - add(&Fill::w(Align::e(Tui::pull_x(1, length.clone())))) + add(&Fill::w(Align::e(Pull::x(1, length.clone())))) })), Tui::bold(true, { let mut row2 = format!(" {name}"); @@ -245,8 +245,8 @@ render!(|self: PoolView<'a>|{ }))?; }) }))))?; - add(&Fill::w(Align::nw(Tui::push_x(1, Tui::fg(title_color, upper_left.to_string())))))?; - add(&Fill::w(Align::ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))?; + add(&Fill::w(Align::nw(Push::x(1, Tui::fg(title_color, upper_left.to_string())))))?; + add(&Fill::w(Align::ne(Pull::x(1, Tui::fg(title_color, upper_right.to_string())))))?; add(&self.0.size) })) }); diff --git a/src/sampler/sampler_tui.rs b/src/sampler/sampler_tui.rs index 386910c6..4aad6ce4 100644 --- a/src/sampler/sampler_tui.rs +++ b/src/sampler/sampler_tui.rs @@ -54,7 +54,7 @@ render!(|self: SamplerTui|{ let with_size = |x|lay!([self.size, x]); Tui::bg(bg, Fill::wh(with_border(Bsp::s( Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")), - with_size(Tui::shrink_y(1, Bsp::e( + with_size(Shrink::y(1, Bsp::e( Fixed::w(keys_width, keys()), Fill::wh(render(|to: &mut TuiOutput|Ok({ let x = to.area.x(); diff --git a/src/sequencer.rs b/src/sequencer.rs index 47ac79f6..02577226 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -44,7 +44,7 @@ render!(|self: SequencerTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.phrases.visible { phrase_w } else { 0 }; - let pool = Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.phrases)))); + let pool = Pull::y(1, Fill::h(Align::e(PoolView(&self.phrases)))); let with_pool = move|x|Split::w(false, pool_w, pool, x); let status = SequencerStatus::from(self); let with_status = |x|Split::n(false, if self.status { 2 } else { 0 }, status, x); @@ -66,7 +66,7 @@ render!(|self: SequencerTui|{ PhraseSelector::next_phrase(&self.player), ])); - Tui::min_y(15, with_size(with_status(col!([ + Min::y(15, with_size(with_status(col!([ toolbar, play_queue, editor, diff --git a/src/space.rs b/src/space.rs index 83c6b7d8..2543ee07 100644 --- a/src/space.rs +++ b/src/space.rs @@ -2,58 +2,58 @@ use crate::*; use std::ops::{Add, Sub, Mul, Div}; use std::fmt::{Display, Debug}; -// TODO: return impl Point and impl Size instead of [N;x] -// to disambiguate between usage of 2-"tuple"s +mod cond; pub use self::cond::*; +mod coord; pub use self::coord::*; +mod layers; pub use self::layers::*; +mod measure; pub use self::measure::*; +mod position; pub use self::position::*; +mod scroll; pub use self::scroll::*; +mod size; pub use self::size::*; +mod split; pub use self::split::*; -////////////////////////////////////////////////////// +/// Has static methods for conditional rendering, +/// in unary and binary forms. +pub struct Cond; -pub(crate) mod align; -pub(crate) mod cond; pub(crate) use cond::*; -pub(crate) mod layers; pub(crate) use layers::*; -pub(crate) mod measure; pub(crate) use measure::*; -pub(crate) mod position; pub(crate) use position::*; -pub(crate) mod scroll; -pub(crate) mod size; pub(crate) use size::*; -pub(crate) mod split; pub(crate) use split::*; - -pub use self::{ - align::*, - cond::*, - measure::*, - position::*, - size::*, - split::*, -}; - -#[derive(Copy, Clone, PartialEq)] -pub enum Direction { North, South, West, East, } - -impl Direction { - pub fn is_north (&self) -> bool { matches!(self, Self::North) } - pub fn is_south (&self) -> bool { matches!(self, Self::South) } - pub fn is_east (&self) -> bool { matches!(self, Self::West) } - pub fn is_west (&self) -> bool { matches!(self, Self::East) } - /// Return next direction clockwise - pub fn cw (&self) -> Self { - match self { - Self::North => Self::East, - Self::South => Self::West, - Self::West => Self::North, - Self::East => Self::South, - } +impl Cond { + /// Render `item` when `cond` is true. + pub fn when > (cond: bool, item: A) -> When { + When(cond, item, Default::default()) } - /// Return next direction counterclockwise - pub fn ccw (&self) -> Self { - match self { - Self::North => Self::West, - Self::South => Self::East, - Self::West => Self::South, - Self::East => Self::North, - } + /// Render `item` if `cond` is true, otherwise render `other`. + pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { + Either(cond, item, other, Default::default()) } } -/// Standard numeric type. +/// Renders `self.1` when `self.0` is true. +pub struct When>(bool, A, PhantomData); + +/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` +pub struct Either, B: Render>(bool, A, B, PhantomData); + +/// Renders multiple things on top of each other, +/// in the order they are provided by the callback. +/// Total size is largest width x largest height. +pub struct Layers< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, PhantomData); + +/// Shorthand for defining an instance of [Layers]. +#[macro_export] macro_rules! lay { + ([$($expr:expr),* $(,)?]) => { + Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Layers::new($expr) + }; +} + +/// A linear coordinate. pub trait Coordinate: Send + Sync + Copy + Add + Sub @@ -77,18 +77,6 @@ pub trait Coordinate: Send + Sync + Copy } } -impl Coordinate for T where T: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{} - pub trait Size { fn x (&self) -> N; fn y (&self) -> N; @@ -105,43 +93,52 @@ pub trait Size { } } } -impl Size for (N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } -} - -impl Size for [N;2] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } -} pub trait Area: Copy { 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() } - #[inline] fn wh (&self) -> [N;2] { [self.w(), self.h()] } - #[inline] fn xywh (&self) -> [N;4] { [self.x(), self.y(), self.w(), self.h()] } - #[inline] fn lrtb (&self) -> [N;4] { [self.x(), self.x2(), self.y(), self.y2()] } - #[inline] fn push_x (&self, x: N) -> [N;4] { [self.x() + x, self.y(), self.w(), self.h()] } - #[inline] fn push_y (&self, y: N) -> [N;4] { [self.x(), self.y() + y, self.w(), self.h()] } + fn x2 (&self) -> N { + self.x() + self.w() + } + fn y2 (&self) -> N { + self.y() + self.h() + } + #[inline] fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } + #[inline] fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + #[inline] fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] + } + #[inline] fn push_x (&self, x: N) -> [N;4] { + [self.x() + x, self.y(), self.w(), self.h()] + } + #[inline] fn push_y (&self, y: N) -> [N;4] { + [self.x(), self.y() + y, self.w(), self.h()] + } #[inline] fn shrink_x (&self, x: N) -> [N;4] { [self.x(), self.y(), self.w().minus(x), self.h()] } #[inline] fn shrink_y (&self, y: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h().minus(y)] } - #[inline] fn set_w (&self, w: N) -> [N;4] { [self.x(), self.y(), w, self.h()] } - #[inline] fn set_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), h] } - #[inline] fn clip_h (&self, h: N) -> [N;4] { + #[inline] fn set_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), w, self.h()] + } + #[inline] fn set_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), h] + } + #[inline] fn clip_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h().min(h)] } - #[inline] fn clip_w (&self, w: N) -> [N;4] { + #[inline] fn clip_w (&self, w: N) -> [N;4] { [self.x(), self.y(), self.w().min(w), self.h()] } - #[inline] fn clip (&self, wh: impl Size) -> [N;4] { + #[inline] fn clip (&self, wh: impl Size) -> [N;4] { [self.x(), self.y(), wh.w(), wh.h()] } #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { @@ -173,20 +170,186 @@ pub trait Area: Copy { } } -impl Area for (N, N, N, N) { - #[inline] fn x (&self) -> N { self.0 } - #[inline] fn y (&self) -> N { self.1 } - #[inline] fn w (&self) -> N { self.2 } - #[inline] fn h (&self) -> N { self.3 } +macro_rules! by_axis { + (!$Enum:ident) => { + impl> $Enum { + pub fn x (item: T) -> Self { + Self::X(item) + } + pub fn y (item: T) -> Self { + Self::Y(item) + } + pub fn xy (item: T) -> Self { + Self::XY(item) + } + } + }; + (+$Enum:ident) => { + impl> $Enum { + pub fn x (x: E::Unit, item: T) -> Self { + Self::X(x, item) + } + pub fn y (y: E::Unit, item: T) -> Self { + Self::Y(y, item) + } + pub fn xy (x: E::Unit, y: E::Unit, item: T) -> Self { + Self::XY(x, y, item) + } + } + }; } -impl Area for [N;4] { - #[inline] fn x (&self) -> N { self[0] } - #[inline] fn y (&self) -> N { self[1] } - #[inline] fn w (&self) -> N { self[2] } - #[inline] fn h (&self) -> N { self[3] } +/// Shrink drawing area +pub enum Shrink> { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), } +by_axis!(+Shrink); + +/// Expand drawing area +pub enum Grow> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T) +} + +by_axis!(+Grow); + +pub enum Fill> { + _Unused(PhantomData), + /// Maximize width + X(W), + /// Maximize height + Y(W), + /// Maximize width and height + XY(W), +} + +by_axis!(!Fill); + +/// Enforce fixed size of drawing area +pub enum Fixed> { + /// Set width + X(E::Unit, T), + /// Set height + Y(E::Unit, T), + /// Set width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Fixed); + +/// Enforce minimum size of drawing area +pub enum Min> { + /// Enforce minimum width + X(E::Unit, T), + /// Enforce minimum height + Y(E::Unit, T), + /// Enforce minimum width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Min); + +/// Enforce maximum size of drawing area +pub enum Max> { + /// Enforce maximum width + X(E::Unit, T), + /// Enforce maximum height + Y(E::Unit, T), + /// Enforce maximum width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Max); + +/// Increment 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), +} + +by_axis!(+Push); + +/// Decrement origin point of drawing area +pub enum Pull> { + /// 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), +} + +by_axis!(+Pull); + +/// Shrink from each side +pub enum Inset { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Inset); + +/// Grow on each side +pub enum Outset> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Outset); + +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq)] +pub enum Direction { North, South, West, East, } + +/// A binary split with fixed proportion +pub struct Split, B: Render>( + pub bool, pub Direction, pub E::Unit, A, B, PhantomData +); + +pub enum Bsp, Y: Render> { + /// X is north of Y + N(Option, Option), + /// X is south of Y + S(Option, Option), + /// X is east of Y + E(Option, Option), + /// X is west of Y + W(Option, Option), + /// X is above Y + A(Option, Option), + /// X is below Y + B(Option, Option), + /// Should be avoided. + Null(PhantomData), +} + +pub struct Stack< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, pub Direction, PhantomData); + #[macro_export] macro_rules! col { ([$($expr:expr),* $(,)?]) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) @@ -232,14 +395,65 @@ impl Area for [N;4] { }; } -#[macro_export] macro_rules! lay { - ([$($expr:expr),* $(,)?]) => { - Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - (![$($expr:expr),* $(,)?]) => { - Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - ($expr:expr) => { - Layers::new($expr) - }; +pub trait HasSize { + fn size (&self) -> &Measure; +} + +#[macro_export] macro_rules! has_size { + (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { + fn size (&$self) -> &Measure<$E> { $cb } + } + } +} + +pub trait LayoutDebug { + fn debug > (other: W) -> DebugOverlay { + DebugOverlay(Default::default(), other) + } +} + +pub struct DebugOverlay>(PhantomData, pub W); + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +pub struct ShowMeasure<'a>(&'a Measure); + +/// 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); + +/// Override X and Y coordinates, aligning to corner, side, or center of area +pub enum Align> { + _Unused(PhantomData), + /// Draw at center of container + Center(T), + /// Draw at center of X axis + X(T), + /// Draw at center of Y axis + Y(T), + /// Draw at upper left corner of contaier + NW(T), + /// Draw at center of upper edge of container + N(T), + /// Draw at right left corner of contaier + NE(T), + /// Draw at center of left edge of container + W(T), + /// Draw at center of right edge of container + E(T), + /// Draw at lower left corner of container + SW(T), + /// Draw at center of lower edge of container + S(T), + /// Draw at lower right edge of container + SE(T) } diff --git a/src/space/align.rs b/src/space/align.rs deleted file mode 100644 index d1246da1..00000000 --- a/src/space/align.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::*; - -/// Override X and Y coordinates, aligning to corner, side, or center of area -pub enum Align> { - _Unused(PhantomData), - /// Draw at center of container - Center(T), - /// Draw at center of X axis - X(T), - /// Draw at center of Y axis - Y(T), - /// Draw at upper left corner of contaier - NW(T), - /// Draw at center of upper edge of container - N(T), - /// Draw at right left corner of contaier - NE(T), - /// Draw at center of left edge of container - W(T), - /// Draw at center of right edge of container - E(T), - /// Draw at lower left corner of container - SW(T), - /// Draw at center of lower edge of container - S(T), - /// Draw at lower right edge of container - SE(T) -} - -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, - _ => unreachable!(), - } - } - pub fn c (w: T) -> Self { Self::Center(w) } - pub fn x (w: T) -> Self { Self::X(w) } - pub fn y (w: T) -> Self { Self::Y(w) } - pub fn n (w: T) -> Self { Self::N(w) } - pub fn s (w: T) -> Self { Self::S(w) } - pub fn e (w: T) -> Self { Self::E(w) } - pub fn w (w: T) -> Self { Self::W(w) } - pub fn nw (w: T) -> Self { Self::NW(w) } - pub fn sw (w: T) -> Self { Self::SW(w) } - pub fn ne (w: T) -> Self { Self::NE(w) } - pub fn se (w: T) -> Self { Self::SE(w) } -} - -fn align, N: Coordinate, R: Area + 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(), - _ => unreachable!() - }) - } -} - -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())? - } - }) - } -} diff --git a/src/space/collect.rs b/src/space/collect.rs index 141992f1..99167436 100644 --- a/src/space/collect.rs +++ b/src/space/collect.rs @@ -1,4 +1,5 @@ use crate::*; +use super::*; pub enum Collect<'a, E: Engine, const N: usize> { Callback(CallbackCollection<'a, E>), diff --git a/src/space/cond.rs b/src/space/cond.rs index 2f8a6006..33dc6e52 100644 --- a/src/space/cond.rs +++ b/src/space/cond.rs @@ -1,24 +1,5 @@ use crate::*; - -/// Conditional rendering, in unary and binary forms. -pub struct Cond; - -impl Cond { - /// Render `item` when `cond` is true. - pub fn when > (cond: bool, item: A) -> When { - When(cond, item, Default::default()) - } - /// Render `item` if `cond` is true, otherwise render `other`. - pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { - Either(cond, item, other, Default::default()) - } -} - -/// Renders `self.1` when `self.0` is true. -pub struct When>(bool, A, PhantomData); - -/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` -pub struct Either, B: Render>(bool, A, B, PhantomData); +use super::*; impl> Render for When { fn min_size (&self, to: E::Size) -> Perhaps { diff --git a/src/space/coord.rs b/src/space/coord.rs new file mode 100644 index 00000000..59a29742 --- /dev/null +++ b/src/space/coord.rs @@ -0,0 +1,14 @@ +use crate::*; +use super::*; + +impl Coordinate for T where T: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{} diff --git a/src/space/layers.rs b/src/space/layers.rs index 9fe07ecd..b0736f24 100644 --- a/src/space/layers.rs +++ b/src/space/layers.rs @@ -1,9 +1,5 @@ use crate::*; - -pub struct Layers< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, PhantomData); +use super::*; impl< E: Engine, diff --git a/src/space/measure.rs b/src/space/measure.rs index c92264cb..569c6845 100644 --- a/src/space/measure.rs +++ b/src/space/measure.rs @@ -1,35 +1,8 @@ use crate::*; - -pub trait HasSize { - fn size (&self) -> &Measure; -} - -#[macro_export] macro_rules! has_size { - (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { - fn size (&$self) -> &Measure<$E> { $cb } - } - } -} +use super::*; impl LayoutDebug for E {} -pub trait LayoutDebug { - fn debug > (other: W) -> DebugOverlay { - DebugOverlay(Default::default(), other) - } -} - -pub struct DebugOverlay>(PhantomData, pub W); - -/// A widget that tracks its render width and height -#[derive(Default)] -pub struct Measure { - _engine: PhantomData, - pub x: Arc, - pub y: Arc, -} - impl Clone for Measure { fn clone (&self) -> Self { Self { @@ -83,7 +56,6 @@ impl Measure { } } -pub struct ShowMeasure<'a>(&'a Measure); render!(|self: ShowMeasure<'a>|render(|to|Ok({ let w = self.0.w(); let h = self.0.h(); diff --git a/src/space/position.rs b/src/space/position.rs index 459329ee..8b0804b8 100644 --- a/src/space/position.rs +++ b/src/space/position.rs @@ -1,59 +1,18 @@ use crate::*; - -impl LayoutPushPull for E {} - -pub trait LayoutPushPull { - fn push_x > (x: E::Unit, w: W) -> Push { - Push::X(x, w) - } - fn push_y > (y: E::Unit, w: W) -> Push { - Push::Y(y, w) - } - fn push_xy > (x: E::Unit, y: E::Unit, w: W) -> Push { - Push::XY(x, y, w) - } - fn pull_x > (x: E::Unit, w: W) -> Pull { - Pull::X(x, w) - } - fn pull_y > (y: E::Unit, w: W) -> Pull { - Pull::Y(y, w) - } - fn pull_xy > (x: E::Unit, y: E::Unit, w: W) -> Pull { - Pull::XY(x, y, w) - } -} - -/// Increment 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), -} +use super::*; impl> Push { pub fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } + use Push::*; + match self { X(_, i) => i, Y(_, i) => i, XY(_, _, i) => i, } } - pub fn x (&self) -> E::Unit { - match self { - Self::X(x, _) => *x, - Self::Y(_, _) => E::Unit::default(), - Self::XY(x, _, _) => *x, - } + pub fn dx (&self) -> E::Unit { + use Push::*; + match self { X(x, _) => *x, Y(_, _) => E::Unit::default(), XY(x, _, _) => *x, } } - pub fn y (&self) -> E::Unit { - match self { - Self::X(_, _) => E::Unit::default(), - Self::Y(y, _) => *y, - Self::XY(_, y, _) => *y, - } + pub fn dy (&self) -> E::Unit { + use Push::*; + match self { X(_, _) => E::Unit::default(), Y(y, _) => *y, XY(_, y, _) => *y, } } } @@ -72,17 +31,6 @@ impl> Render for Push { } } -/// Decrement 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 { @@ -92,7 +40,7 @@ impl> Pull { _ => unreachable!(), } } - pub fn x (&self) -> E::Unit { + pub fn dx (&self) -> E::Unit { match self { Self::X(x, _) => *x, Self::Y(_, _) => E::Unit::default(), @@ -100,7 +48,7 @@ impl> Pull { _ => unreachable!(), } } - pub fn y (&self) -> E::Unit { + pub fn dy (&self) -> E::Unit { match self { Self::X(_, _) => E::Unit::default(), Self::Y(y, _) => *y, @@ -126,40 +74,6 @@ impl> Render for Pull { } } - -impl + LayoutShrinkGrow> LayoutInsetOutset for E {} - -pub trait LayoutInsetOutset: LayoutPushPull + LayoutShrinkGrow { - fn inset_x > (x: E::Unit, w: W) -> Inset { - Inset::X(x, w) - } - fn inset_y > (y: E::Unit, w: W) -> Inset { - Inset::Y(y, w) - } - fn inset_xy > (x: E::Unit, y: E::Unit, w: W) -> Inset { - Inset::XY(x, y, w) - } - fn outset_x > (x: E::Unit, w: W) -> Outset { - Outset::X(x, w) - } - fn outset_y > (y: E::Unit, w: W) -> Outset { - Outset::Y(y, w) - } - fn outset_xy > (x: E::Unit, y: E::Unit, w: W) -> Outset { - Outset::XY(x, y, w) - } -} - -/// Shrink from each side -pub enum Inset { - /// 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 { @@ -173,23 +87,13 @@ impl> Inset { impl> Render for Inset { fn render (&self, to: &mut E::Output) -> Usually<()> { match self { - Self::X(x, inner) => E::push_x(*x, E::shrink_x(*x, inner)), - Self::Y(y, inner) => E::push_y(*y, E::shrink_y(*y, inner)), - Self::XY(x, y, inner) => E::push_xy(*x, *y, E::shrink_xy(*x, *y, inner)), + Self::X(x, inner) => Push::x(*x, Shrink::x(*x, inner)), + Self::Y(y, inner) => Push::y(*y, Shrink::y(*y, inner)), + Self::XY(x, y, inner) => Push::xy(*x, *y, Shrink::xy(*x, *y, inner)), }.render(to) } } -/// Grow on each side -pub enum Outset> { - /// 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 { @@ -204,16 +108,84 @@ impl> Outset { impl> Render for Outset { fn min_size (&self, to: E::Size) -> Perhaps { match *self { - Self::X(x, ref inner) => E::grow_x(x + x, inner), - Self::Y(y, ref inner) => E::grow_y(y + y, inner), - Self::XY(x, y, ref inner) => E::grow_xy(x + x, y + y, inner), + Self::X(x, ref inner) => Grow::x(x + x, inner), + Self::Y(y, ref inner) => Grow::y(y + y, inner), + Self::XY(x, y, ref inner) => Grow::xy(x + x, y + y, inner), }.min_size(to) } fn render (&self, to: &mut E::Output) -> Usually<()> { match *self { - Self::X(x, ref inner) => E::push_x(x, inner), - Self::Y(y, ref inner) => E::push_y(y, inner), - Self::XY(x, y, ref inner) => E::push_xy(x, y, inner), + Self::X(x, ref inner) => Push::x(x, inner), + Self::Y(y, ref inner) => Push::y(y, inner), + Self::XY(x, y, ref inner) => Push::xy(x, y, inner), }.render(to) } } + +impl> Align { + pub fn c (w: T) -> Self { Self::Center(w) } + pub fn x (w: T) -> Self { Self::X(w) } + pub fn y (w: T) -> Self { Self::Y(w) } + pub fn n (w: T) -> Self { Self::N(w) } + pub fn s (w: T) -> Self { Self::S(w) } + pub fn e (w: T) -> Self { Self::E(w) } + pub fn w (w: T) -> Self { Self::W(w) } + pub fn nw (w: T) -> Self { Self::NW(w) } + pub fn sw (w: T) -> Self { Self::SW(w) } + pub fn ne (w: T) -> Self { Self::NE(w) } + pub fn se (w: T) -> Self { Self::SE(w) } + 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, + _ => unreachable!(), + } + } +} + +fn align, N: Coordinate, R: Area + 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(), + _ => unreachable!() + }) + } +} + +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())? + } + }) + } +} diff --git a/src/space/scroll.rs b/src/space/scroll.rs index 326f6ab6..c7b7e813 100644 --- a/src/space/scroll.rs +++ b/src/space/scroll.rs @@ -1,8 +1 @@ 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/src/space/size.rs b/src/space/size.rs index 4200bdf4..bcfcbdf8 100644 --- a/src/space/size.rs +++ b/src/space/size.rs @@ -1,46 +1,28 @@ use crate::*; +use super::*; -impl LayoutShrinkGrow for E {} - -pub trait LayoutShrinkGrow { - fn shrink_x > (x: E::Unit, w: W) -> Shrink { - Shrink::X(x, w) - } - fn shrink_y > (y: E::Unit, w: W) -> Shrink { - Shrink::Y(y, w) - } - fn shrink_xy > (x: E::Unit, y: E::Unit, w: W) -> Shrink { - Shrink::XY(x, y, w) - } - fn grow_x > (x: E::Unit, w: W) -> Grow { - Grow::X(x, w) - } - fn grow_y > (y: E::Unit, w: W) -> Grow { - Grow::Y(y, w) - } - fn grow_xy > (x: E::Unit, y: E::Unit, w: W) -> Grow { - Grow::XY(x, y, w) - } +impl Size for (N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } } -/// Shrink drawing area -pub enum Shrink> { - /// Decrease width - X(E::Unit, T), - /// Decrease height - Y(E::Unit, T), - /// Decrease width and height - XY(E::Unit, E::Unit, T), +impl Size for [N;2] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } } -/// Expand drawing area -pub enum Grow> { - /// Increase width - X(E::Unit, T), - /// Increase height - Y(E::Unit, T), - /// Increase width and height - XY(E::Unit, E::Unit, T) +impl Area for (N, N, N, N) { + #[inline] fn x (&self) -> N { self.0 } + #[inline] fn y (&self) -> N { self.1 } + #[inline] fn w (&self) -> N { self.2 } + #[inline] fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + #[inline] fn x (&self) -> N { self[0] } + #[inline] fn y (&self) -> N { self[1] } + #[inline] fn w (&self) -> N { self[2] } + #[inline] fn h (&self) -> N { self[3] } } impl> Shrink { @@ -102,13 +84,6 @@ impl> Render for Grow { } } -pub enum Fill> { - _Unused(PhantomData), - X(W), - Y(W), - XY(W), -} - impl> Fill { fn inner (&self) -> &W { match self { @@ -148,17 +123,6 @@ impl> Render for Fill { } } -/// 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 { @@ -201,49 +165,6 @@ impl> Render for Fixed { } } -impl LayoutMinMax for E {} - -pub trait LayoutMinMax { - fn min_x > (x: E::Unit, w: W) -> Min { - Min::X(x, w) - } - fn min_y > (y: E::Unit, w: W) -> Min { - Min::Y(y, w) - } - fn min_xy > (x: E::Unit, y: E::Unit, w: W) -> Min { - Min::XY(x, y, w) - } - fn max_x > (x: E::Unit, w: W) -> Max { - Max::X(x, w) - } - fn max_y > (y: E::Unit, w: W) -> Max { - Max::Y(y, w) - } - fn max_xy > (x: E::Unit, y: E::Unit, w: W) -> Max { - Max::XY(x, y, w) - } -} - -/// Enforce minimum size of drawing area -pub enum Min> { - /// Enforce minimum width - X(E::Unit, T), - /// Enforce minimum height - Y(E::Unit, T), - /// Enforce minimum width and height - XY(E::Unit, E::Unit, T), -} - -/// Enforce maximum size of drawing area -pub enum Max> { - /// Enforce maximum width - X(E::Unit, T), - /// Enforce maximum height - Y(E::Unit, T), - /// Enforce maximum width and height - XY(E::Unit, E::Unit, T), -} - impl> Min { pub fn inner (&self) -> &T { match self { diff --git a/src/space/split.rs b/src/space/split.rs index 264b49f2..e5f6217c 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -1,10 +1,31 @@ use crate::*; +use super::*; use Direction::*; -/// A binary split with fixed proportion -pub struct Split, B: Render>( - pub bool, pub Direction, pub E::Unit, A, B, PhantomData -); +impl Direction { + pub fn is_north (&self) -> bool { matches!(self, Self::North) } + pub fn is_south (&self) -> bool { matches!(self, Self::South) } + pub fn is_east (&self) -> bool { matches!(self, Self::West) } + pub fn is_west (&self) -> bool { matches!(self, Self::East) } + /// Return next direction clockwise + pub fn cw (&self) -> Self { + match self { + Self::North => Self::East, + Self::South => Self::West, + Self::West => Self::North, + Self::East => Self::South, + } + } + /// Return next direction counterclockwise + pub fn ccw (&self) -> Self { + match self { + Self::North => Self::West, + Self::South => Self::East, + Self::West => Self::South, + Self::East => Self::North, + } + } +} impl, B: Render> Split { pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { @@ -40,23 +61,6 @@ impl, B: Render> Render for Split { } } -pub enum Bsp, Y: Render> { - /// X is north of Y - N(Option, Option), - /// X is south of Y - S(Option, Option), - /// X is east of Y - E(Option, Option), - /// X is west of Y - W(Option, Option), - /// X is above Y - A(Option, Option), - /// X is below Y - B(Option, Option), - /// Should be avoided. - Null(PhantomData), -} - impl, Y: Render> Bsp { pub fn new (x: X) -> Self { Self::A(Some(x), None) } pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } @@ -138,11 +142,6 @@ impl, Y: Render> Render for Bsp { } } -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<()> @@ -174,7 +173,7 @@ where (self.0)(&mut |component: &dyn Render| { let max = to.h().minus(h); if max > E::Unit::zero() { - let item = E::max_y(max, E::push_y(h, component)); + let item = Max::y(max, Push::y(h, component)); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { h = h + height.into(); @@ -192,7 +191,7 @@ where (self.0)(&mut |component: &dyn Render| { let max = to.w().minus(w); if max > E::Unit::zero() { - let item = E::max_x(max, E::push_x(h, component)); + let item = Max::x(max, Push::x(h, component)); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { w = w + width.into(); @@ -210,7 +209,7 @@ where (self.0)(&mut |component: &dyn Render| { let max = to.h().minus(h); if max > E::Unit::zero() { - let item = E::max_y(to.h() - h, component); + let item = Max::y(to.h() - h, component); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { h = h + height.into(); @@ -244,7 +243,7 @@ where South => { (self.0)(&mut |item| { if h < area.h() { - let item = E::max_y(area.h() - h, E::push_y(h, item)); + let item = Max::y(area.h() - h, Push::y(h, item)); let show = item.min_size(area.wh().into())?.map(|s|s.wh()); if let Some([width, height]) = show { item.render(to)?; @@ -258,7 +257,7 @@ where East => { (self.0)(&mut |item| { if w < area.w() { - let item = E::max_x(area.w() - w, E::push_x(w, item)); + let item = Max::x(area.w() - w, Push::x(w, item)); let show = item.min_size(area.wh().into())?.map(|s|s.wh()); if let Some([width, height]) = show { item.render(to)?; @@ -274,7 +273,7 @@ where 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 { - E::shrink_y(height, E::push_y(area.h() - height, item)) + Shrink::y(height, Push::y(area.h() - height, item)) .render(to)?; h = h + height; if width > w { w = width } diff --git a/src/tui/tui_border.rs b/src/tui/tui_border.rs index 8408ed44..ba19074f 100644 --- a/src/tui/tui_border.rs +++ b/src/tui/tui_border.rs @@ -3,7 +3,7 @@ use crate::*; pub struct Bordered>(pub S, pub W); render!(|self: Bordered>|{ - Fill::wh(lay!([Border(self.0), Tui::inset_xy(1, 1, widget(&self.1))])) + Fill::wh(lay!([Border(self.0), Inset::xy(1, 1, widget(&self.1))])) }); pub struct Border(pub S);