use crate::*; use Azimuth::*; /// Where is [0, 0] located? /// /// ``` /// use tengri::*; /// use Azimuth::*; /// let _ = "".align(NW); /// ``` #[cfg_attr(test, derive(Arbitrary))] #[derive(Debug, Copy, Clone, Default)] pub enum Azimuth { #[default] C, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Origin( pub(crate) Option, pub(crate) T ); impl_draw!(,>|self: Origin, _to: S|{ todo!() }); /// Something that has `[0, 0]` at a particular point. pub trait HasOrigin { fn origin (&self) -> Azimuth; } impl> HasOrigin for T { fn origin (&self) -> Azimuth { *self.as_ref() } } fn_kw_layout!(kw_align |state, output, expr| { let head = expr.head(); let mut frags = head.src()?.unwrap_or_default().split("/"); Ok(matches!(frags.next(), Some("align")).then(||{ draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align( eval_enum!("align", output, state, expr.head().src()?.unwrap_or_default().split("/").skip(1).next(), Azimuth { "c" => C, "x" => X, "y" => Y, "n" => N, "s" => S, "e" => E, "w" => W, "nw" => NW, "sw" => SW, "ne" => NE, "se" => SE, }) ).draw(output) }).transpose()?.flatten()) }); impl> CanAlign for T {} pub trait CanAlign: Draw + Sized { fn align (self, azimuth: impl Into>) -> Align { Align(azimuth.into(), self) } fn align_c (self) -> Align { Align(Some(Azimuth::C), self) } fn align_x (self) -> Align { Align(Some(Azimuth::X), self) } fn align_y (self) -> Align { Align(Some(Azimuth::Y), self) } fn align_n (self) -> Align { Align(Some(Azimuth::N), self) } fn align_s (self) -> Align { Align(Some(Azimuth::S), self) } fn align_e (self) -> Align { Align(Some(Azimuth::E), self) } fn align_w (self) -> Align { Align(Some(Azimuth::W), self) } fn align_ne (self) -> Align { Align(Some(Azimuth::NE), self) } fn align_se (self) -> Align { Align(Some(Azimuth::SE), self) } fn align_nw (self) -> Align { Align(Some(Azimuth::NW), self) } fn align_sw (self) -> Align { Align(Some(Azimuth::SW), self) } } pub struct Align( pub(crate) Option, pub(crate) T, ); impl> Draw for Align { fn draw (&self, to: &mut S) -> Perhaps> { let Self(azimuth, item) = self; let area0 = to.area(); let size = to.size(area0, item)?; let area1 = align::(area0, size, *azimuth); //println!("\n\r{azimuth:?} {area0:?} {size:?}=>{area1:?}"); Ok(if let Some(area) = area1 { to.draw(area, &item)? } else { None }) } } fn align ( area0: XYWH, area: Option>, azimuth: Option ) -> Option> { area.map(|XYWH(x, y, w, h)|{ let XYWH(x0, y0, w0, h0) = area0; match azimuth { Some(NW) => XYWH(x0, y0, w, h), Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h), Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h), Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h), Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h), Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h), Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h), Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h), Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h), Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h), Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h), None => XYWH(x, y, w, h) } }) } fn_kw_layout!(kw_split |state, output, expr| { let head = expr.head(); let mut frags = head.src()?.unwrap_or_default().split("/"); Ok(matches!(frags.next(), Some("bsp")).then(||{ eval_enum!("bsp", output, state, frags.next(), Split { "n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below }).stack( draw(move|output: &mut O|{ state.interpret(output, &expr.tail().head()?) }), draw(move|output: &mut O|{ state.interpret(output, &expr.tail().tail().head()?) }), ).draw(output) }).transpose()?.flatten()) }); /// Split along an axis. Direction determines order. #[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split { North, South, East, West, Above, #[default] Below } pub struct Pair, B: Draw>(Split, A, B, PhantomData); pub fn split , B: Draw> ( split: Split, a: A, b: B ) -> Pair { Pair(split, a, b, PhantomData) } impl, B: Draw> Draw for Pair { fn draw (&self, to: &mut S) -> Drawn { let Self(split, a, b, ..) = self; let (area_a, area_b) = stack_areas(split, to, a, b)?; let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?; Ok(stack_drawn(split, drawn_a, drawn_b)) } } fn draw_stacks ( split: &Split, to: &mut S, a: impl Draw, area_a: impl Into>>, origin_a: impl Into>, b: impl Draw, area_b: impl Into>>, origin_b: impl Into>, ) -> Usually<(Option>, Option>)> { let draw_a = |to: &mut S|Ok::<_, Box>(match origin_a.into() { Some(origin_a) => to.draw(area_a.into(), a.align(origin_a))?, None => to.draw(area_a.into(), a)? }); let draw_b = |to: &mut S|Ok::<_, Box>(match origin_b.into() { Some(origin_b) => to.draw(area_b.into(), b.align(origin_b))?, None => to.draw(area_b.into(), b)? }); Ok(if matches!(split, Split::Below) { let drawn_b = draw_b(to)?; let drawn_a = draw_a(to)?; (drawn_a, drawn_b) } else { (draw_a(to)?, draw_b(to)?) }) } pub fn stack_areas ( split: &Split, to: &mut S, a: impl Draw, b: impl Draw, ) -> Usually<(Option>, Option>)> { let area_a = to.size(None, a)?; Ok(match split { Split::South => ( area_a, area_a .map(|used|to.size(XYWH(to.x(), to.y() + used.h(), to.w(), to.h().minus(used.h())), b)) .transpose()? .flatten() ), Split::East => ( area_a, area_a.map(|used|to.size(XYWH(to.x() + used.w(), to.y(), to.w().minus(used.w()), to.h()), b)) .transpose()? .flatten() ), Split::North => ( area_a.map(|used|XYWH(used.x(), (to.y() + to.h()).minus(used.h()), used.w(), used.h())), if let Some(used) = area_a { to.size(XYWH(to.x(), to.y(), to.w(), to.h().minus(used.h())), b)? } else { to.size(None, b)? .map(|area_b|XYWH(area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h())) } ), Split::West => ( area_a.map(|used|XYWH((to.x() + to.w()).minus(used.w()), used.y(), used.w(), used.h())), if let Some(used) = area_a { to.size(XYWH(to.x(), to.y(), to.w().minus(used.w()), to.h()), b)? } else { to.size(None, b)? .map(|area_b|XYWH(area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h())) } ), Split::Above | Split::Below => ( area_a, to.size(None, b)?, ), }) } fn stack_drawn ( split: &Split, drawn_a: Option>, drawn_b: Option>, ) -> Option> { if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) { match split { Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)), Split::East => Some(XYWH(xa, ya.min(yb), wa + wb, ha.max(hb))), Split::North => Some(XYWH(xa.min(xb), yb, wa.max(wb), ha + hb)), Split::West => Some(XYWH(xb, ya.min(yb), wa + wb, ha.max(hb))), Split::Above | Split::Below => Some(XYWH(xa.min(xb), ya.min(yb), wa.max(wb), ha.max(hb))), } } else if let Some(a) = drawn_a { Some(a) } else if let Some(b) = drawn_b { Some(b) } else { None } } impl Split { /// ``` /// use tengri::*; /// let _ = Split::Above.stack(&"", &""); /// let _ = Split::Below.stack(&"", &""); /// let _ = Split::North.stack(&"", &""); /// let _ = Split::South.stack(&"", &""); /// let _ = Split::East.stack(&"", &""); /// let _ = Split::West.stack(&"", &""); /// ``` pub const fn stack , B: Draw> (&self, a: A, b: B) -> impl Draw { Pair(*self, a, b, PhantomData) } /// ``` /// use tengri::*; /// let _ = Split::Above.half(&"", &""); /// let _ = Split::Below.half(&"", &""); /// let _ = Split::North.half(&"", &""); /// let _ = Split::South.half(&"", &""); /// let _ = Split::East.half(&"", &""); /// let _ = Split::West.half(&"", &""); /// ``` pub const fn half , B: Draw> (&self, a: &A, b: &B) -> impl Draw { draw(move|to: &mut S|{ let (area_a, area_b) = to.xywh().split_half(self); let (origin_a, origin_b) = self.origins(); let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, origin_a, b, area_b, origin_b)?; Ok(stack_drawn(self, drawn_a, drawn_b)) }) } /// Newly split areas begin at the center of the split /// to maintain centeredness in the user's field of view. /// /// Use [align] to override that and always start /// at the top, bottom, etc. /// /// ``` /// /* /// /// Split east: Split south: /// | | | | A | /// | <-A|B-> | |---------| /// | | | | B | /// /// */ /// ``` const fn origins (&self) -> (Azimuth, Azimuth) { use Azimuth::*; match self { Self::South => (S, N), Self::East => (E, W), Self::North => (N, S), Self::West => (W, E), Self::Above => (C, C), Self::Below => (C, C), } } ///// ``` ///// use tengri::*; ///// let _ = Split::Below.iter([ ///// "Leftbar" ///// .min_w(10).max_w(15).align(Azimuth::NW), ///// "Rightbar" ///// .min_w(10).max_w(12).align(Azimuth::NE), ///// "Center" ///// .min_w(20).max_w(40).align(Azimuth::C), ///// ].iter()); ///// ``` //pub fn iter > (&self, _: impl Iterator) { //todo!() //} } pub const fn east , B: Draw> (a: A, b: B) -> impl Draw { Pair(Split::East, a, b, PhantomData) } pub const fn north , B: Draw> (a: A, b: B) -> impl Draw { Pair(Split::North, a, b, PhantomData) } pub const fn west , B: Draw> (a: A, b: B) -> impl Draw { Pair(Split::West, a, b, PhantomData) } pub const fn south , B: Draw> (a: A, b: B) -> impl Draw { Pair(Split::South, a, b, PhantomData) } pub const fn above , B: Draw> (a: A, b: B) -> impl Draw { Pair(Split::Above, a, b, PhantomData) } pub const fn below , B: Draw> (a: A, b: B) -> impl Draw { Pair(Split::Below, a, b, PhantomData) } #[macro_export] macro_rules! north { ($head:expr $(,)?) => { $head }; ($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) }; } #[macro_export] macro_rules! south { ($head:expr $(,)?) => { $head }; ($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) }; } #[macro_export] macro_rules! east { ($head:expr $(,)?) => { $head }; ($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) }; } #[macro_export] macro_rules! west { ($head:expr $(,)?) => { $head }; ($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) }; } #[macro_export] macro_rules! above { ($head:expr $(,)?) => { $head }; ($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) }; } #[macro_export] macro_rules! below { ($head:expr $(,)?) => { $head }; ($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) }; } #[cfg(test)] #[test] fn test_stack_areas () -> Usually<()> { let area = XYWH(0u16, 0, 80, 25); assert_eq!(stack_areas(&Split::East, &mut Tui::Layout(area), &"foo", &"bar")?, ( Some(XYWH(0u16, 0, 3, 1)), Some(XYWH(3u16, 0, 3, 1)), )); assert_eq!(stack_areas(&Split::South, &mut Tui::Layout(area), &"foo", &"bar")?, ( Some(XYWH(0u16, 0, 3, 1)), Some(XYWH(0u16, 1, 3, 1)), )); Ok(()) } #[cfg(test)] #[test] fn test_split_stack () -> Usually<()> { use Split::*; fn size_of , B: Draw> (stack: &Pair) -> Perhaps> { Tui::Layout(XYWH(0, 0, 80, 25)).size(None, stack) } assert_eq!(size_of(&split(East, "foo", "bar"))?, Some(XYWH(0, 0, 6, 1))); assert_eq!(size_of(&split(South, "foo", "bar"))?, Some(XYWH(0, 0, 3, 2))); assert_eq!(size_of(&split(South, split(East, "foo", "bar"), "baz"))?, Some(XYWH(0, 0, 6, 2))); assert_eq!(size_of(&split(East, split(South, "foo", "bar"), "baz"))?, Some(XYWH(0, 0, 6, 2))); return Ok(()); } #[cfg(test)] #[test] fn test_align () -> Usually<()> { let mut screen = Tui::Layout(XYWH(0, 0, 80, 25)); assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_nw().draw(&mut screen)?, Some(XYWH(0, 0, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_n().draw(&mut screen)?, Some(XYWH(37, 0, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_ne().draw(&mut screen)?, Some(XYWH(74, 0, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_w().draw(&mut screen)?, Some(XYWH(0, 11, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_c().draw(&mut screen)?, Some(XYWH(37, 11, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_e().draw(&mut screen)?, Some(XYWH(74, 11, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_sw().draw(&mut screen)?, Some(XYWH(0, 23, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_s().draw(&mut screen)?, Some(XYWH(37, 23, 6, 2))); assert_eq!("FOOBAR\nKILROY".align_se().draw(&mut screen)?, Some(XYWH(74, 23, 6, 2))); Ok(()) }