mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-09-18 13:26:42 +02:00
458 lines
15 KiB
Rust
458 lines
15 KiB
Rust
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<T>(
|
|
pub(crate) Option<Azimuth>,
|
|
pub(crate) T
|
|
);
|
|
|
|
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, _to: S|{
|
|
todo!()
|
|
});
|
|
|
|
/// Something that has `[0, 0]` at a particular point.
|
|
pub trait HasOrigin {
|
|
fn origin (&self) -> Azimuth;
|
|
}
|
|
|
|
impl<T: AsRef<Azimuth>> 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<S: Screen, T: Draw<S>> CanAlign<S> for T {}
|
|
|
|
pub trait CanAlign<S: Screen>: Draw<S> + Sized {
|
|
fn align (self, azimuth: impl Into<Option<Azimuth>>) -> Align<Self> {
|
|
Align(azimuth.into(), self)
|
|
}
|
|
fn align_c (self) -> Align<Self> {
|
|
Align(Some(Azimuth::C), self)
|
|
}
|
|
fn align_x (self) -> Align<Self> {
|
|
Align(Some(Azimuth::X), self)
|
|
}
|
|
fn align_y (self) -> Align<Self> {
|
|
Align(Some(Azimuth::Y), self)
|
|
}
|
|
fn align_n (self) -> Align<Self> {
|
|
Align(Some(Azimuth::N), self)
|
|
}
|
|
fn align_s (self) -> Align<Self> {
|
|
Align(Some(Azimuth::S), self)
|
|
}
|
|
fn align_e (self) -> Align<Self> {
|
|
Align(Some(Azimuth::E), self)
|
|
}
|
|
fn align_w (self) -> Align<Self> {
|
|
Align(Some(Azimuth::W), self)
|
|
}
|
|
fn align_ne (self) -> Align<Self> {
|
|
Align(Some(Azimuth::NE), self)
|
|
}
|
|
fn align_se (self) -> Align<Self> {
|
|
Align(Some(Azimuth::SE), self)
|
|
}
|
|
fn align_nw (self) -> Align<Self> {
|
|
Align(Some(Azimuth::NW), self)
|
|
}
|
|
fn align_sw (self) -> Align<Self> {
|
|
Align(Some(Azimuth::SW), self)
|
|
}
|
|
}
|
|
|
|
pub struct Align<T>(
|
|
pub(crate) Option<Azimuth>,
|
|
pub(crate) T,
|
|
);
|
|
|
|
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
|
|
fn draw (&self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
|
let Self(azimuth, item) = self;
|
|
let area0 = to.area();
|
|
let size = to.size(area0, item)?;
|
|
let area1 = align::<S>(area0, size, *azimuth);
|
|
//println!("\n\r{azimuth:?} {area0:?} {size:?}=>{area1:?}");
|
|
Ok(if let Some(area) = area1 {
|
|
to.draw(area, &item)?
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
}
|
|
|
|
fn align <S: Screen> (
|
|
area0: XYWH<S::Unit>, area: Option<XYWH<S::Unit>>, azimuth: Option<Azimuth>
|
|
) -> Option<XYWH<S::Unit>> {
|
|
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<S: Screen, A: Draw<S>, B: Draw<S>>(Split, A, B, PhantomData<S>);
|
|
|
|
pub fn split <S: Screen, A: Draw<S>, B: Draw<S>> (
|
|
split: Split, a: A, b: B
|
|
) -> Pair<S, A, B> {
|
|
Pair(split, a, b, PhantomData)
|
|
}
|
|
|
|
impl<S: Screen, A: Draw<S>, B: Draw<S>> Draw<S> for Pair<S, A, B> {
|
|
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
|
|
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 <S: Screen> (
|
|
split: &Split,
|
|
to: &mut S,
|
|
a: impl Draw<S>,
|
|
area_a: impl Into<Option<XYWH<S::Unit>>>,
|
|
origin_a: impl Into<Option<Azimuth>>,
|
|
b: impl Draw<S>,
|
|
area_b: impl Into<Option<XYWH<S::Unit>>>,
|
|
origin_b: impl Into<Option<Azimuth>>,
|
|
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
|
|
let draw_a = |to: &mut S|Ok::<_, Box<dyn Error>>(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<dyn Error>>(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 <S: Screen> (
|
|
split: &Split,
|
|
to: &mut S,
|
|
a: impl Draw<S>,
|
|
b: impl Draw<S>,
|
|
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
|
|
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 <S: Coord> (
|
|
split: &Split,
|
|
drawn_a: Option<XYWH<S>>,
|
|
drawn_b: Option<XYWH<S>>,
|
|
) -> Option<XYWH<S>> {
|
|
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 <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
|
|
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 <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: &A, b: &B) -> impl Draw<S> {
|
|
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 <S: Screen, T: Draw<S>> (&self, _: impl Iterator<Item = T>) {
|
|
//todo!()
|
|
//}
|
|
|
|
}
|
|
|
|
pub const fn east <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
Pair(Split::East, a, b, PhantomData)
|
|
}
|
|
|
|
pub const fn north <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
Pair(Split::North, a, b, PhantomData)
|
|
}
|
|
|
|
pub const fn west <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
Pair(Split::West, a, b, PhantomData)
|
|
}
|
|
|
|
pub const fn south <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
Pair(Split::South, a, b, PhantomData)
|
|
}
|
|
|
|
pub const fn above <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
Pair(Split::Above, a, b, PhantomData)
|
|
}
|
|
|
|
pub const fn below <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
|
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 <A: Draw<Tui>, B: Draw<Tui>> (stack: &Pair<Tui, A, B>) -> Perhaps<XYWH<u16>> {
|
|
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(())
|
|
}
|