mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-08-07 22:17:07 +02:00
moar flat
This commit is contained in:
parent
fc01fd6ad3
commit
a27dacff93
29 changed files with 649 additions and 582 deletions
315
src/layout/split.rs
Normal file
315
src/layout/split.rs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
use super::*;
|
||||
|
||||
/// 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 layout (&self, to: XYWH<S::Unit>) -> Drawn<S::Unit> {
|
||||
let Self(split, a, b, ..) = self;
|
||||
let (area_a, area_b) = stack_areas(split, to, a, b)?;
|
||||
Ok(stack_drawn(split, area_a, area_b))
|
||||
}
|
||||
fn draw (self, to: &mut S) -> Drawn<S::Unit> {
|
||||
let Self(ref split, a, b, ..) = self;
|
||||
let (area_a, area_b) = stack_areas(split, to.area(), &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))
|
||||
}
|
||||
}
|
||||
|
||||
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!()
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
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>>(if let Some(origin_a) = origin_a.into() {
|
||||
to.clip(area_a.into(), |to|a.align(origin_a).draw(to))?
|
||||
} else {
|
||||
to.clip(area_a.into(), |to|a.draw(to))?
|
||||
});
|
||||
let draw_b = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_b) = origin_b.into() {
|
||||
to.clip(area_b.into(), |to|b.align(origin_b).draw(to))?
|
||||
} else {
|
||||
to.clip(area_b.into(), |to|b.draw(to))?
|
||||
});
|
||||
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,
|
||||
area: XYWH<S::Unit>,
|
||||
a: &impl Draw<S>,
|
||||
b: &impl Draw<S>,
|
||||
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
|
||||
let area_a = a.layout(area)?;
|
||||
Ok(match split {
|
||||
Split::South => (
|
||||
area_a,
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(area.x(), area.y() + used.h(), area.w(), area.h().minus(used.h())))?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
),
|
||||
Split::East => (
|
||||
area_a,
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(area.x() + used.w(), area.y(), area.w().minus(used.w()), area.h()))?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
),
|
||||
Split::North => (
|
||||
if let Some(used) = area_a {
|
||||
Some(XYWH(used.x(), (area.y() + area.h()).minus(used.h()), used.w(), used.h()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(area.x(), area.y(), area.w(), area.h().minus(used.h())))?
|
||||
} else {
|
||||
b.layout(area)?.map(|area_b|XYWH(
|
||||
area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h()
|
||||
))
|
||||
}
|
||||
),
|
||||
Split::West => (
|
||||
if let Some(used) = area_a {
|
||||
Some(XYWH((area.x() + area.w()).minus(used.w()), used.y(), used.w(), used.h()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(area.x(), area.y(), area.w().minus(used.w()), area.h()))?
|
||||
} else {
|
||||
b.layout(area)?.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,
|
||||
b.layout(area)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[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,)*)) };
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(test)] mod test {
|
||||
use crate::*;
|
||||
|
||||
#[test] fn test_stack_areas () -> Usually<()> {
|
||||
let area = XYWH(0u16, 0, 80, 25);
|
||||
|
||||
assert_eq!(stack_areas(&Split::East, area, &"foo", &"bar")?, (
|
||||
Some(XYWH(0u16, 0, 3, 1)),
|
||||
Some(XYWH(3u16, 0, 3, 1)),
|
||||
));
|
||||
|
||||
assert_eq!(stack_areas(&Split::South, area, &"foo", &"bar")?, (
|
||||
Some(XYWH(0u16, 0, 3, 1)),
|
||||
Some(XYWH(0u16, 1, 3, 1)),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test] fn test_split_stack () -> Usually<()> {
|
||||
use tengri::{*, Split::*};
|
||||
|
||||
let stack = split(East, "foo", "bar");
|
||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 1)));
|
||||
|
||||
let stack = split(South, "foo", "bar");
|
||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 3, 2)));
|
||||
|
||||
let stack = split(South, split(East, "foo", "bar"), "baz");
|
||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2)));
|
||||
|
||||
let stack = split(East, split(South, "foo", "bar"), "baz");
|
||||
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue