moar flat

This commit is contained in:
facile pop culture reference 2026-08-01 17:52:37 +03:00
parent fc01fd6ad3
commit a27dacff93
29 changed files with 649 additions and 582 deletions

View file

@ -8,11 +8,13 @@ pub struct Align<T>(
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
let XYWH(x0, y0, w0, h0) = area;
Ok(align::<S>(area, self.1.layout(area)?, self.0))
}
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
Ok(self.layout(to.area())?.map(|area|to.clip(area, |to|self.1.draw(to))).transpose()?.flatten())
Ok(self.layout(to.area())?
.map(|area|to.clip(area, |to|self.1.draw(to)))
.transpose()?
.flatten())
}
}

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// ```
/// use tengri::*;

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Set size of of drawing area.
///
@ -8,22 +8,42 @@ use crate::{*, draw::*};
/// let _ = "".exact_h(1);
/// let _ = "".exact_wh(1, 1);
/// ```
pub enum Exact<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
__(PhantomData<T>),
pub enum Exact<S: Screen, I: Draw<S>, X: Into<Option<S::Unit>>> {
__(PhantomData<S>),
W(I, X),
H(I, X),
WH(I, X, X),
}
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Exact<T, I, X>, to: T|{
let area: XYWH<T::Unit> = to.area();
let (item, area) = match self {
Self::W(item, w1) =>
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), area.3)),
Self::H(item, h1) =>
(item, XYWH(area.0, area.1, area.2, h1.into().unwrap_or(area.3))),
Self::WH(item, w1, h1) =>
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), h1.into().unwrap_or(area.3))),
_ => return Ok(None)
impl <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Exact<S, I, X> {
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
Ok(Some(layout_exact(self, area)))
}
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
let area = layout_exact(&self, to.area());
let item = match self { Self::W(i, ..) => i, Self::H(i, ..) => i, Self::WH(i, ..) => i, _ => unreachable!() };
to.clip(area, |to|item.draw(to))
}
}
fn layout_exact <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> (
exact: &Exact<S, I, X>, area: XYWH<S::Unit>
) -> XYWH<S::Unit> {
let (w, h): (S::Unit, S::Unit) = match exact {
Exact::W(_, w) => {
let w: Option<S::Unit> = (*w).into();
(w.unwrap_or(area.2), area.3)
},
Exact::H(_, h) => {
let h: Option<S::Unit> = (*h).into();
(area.2, h.unwrap_or(area.3))
},
Exact::WH(_, w, h) => {
let w: Option<S::Unit> = (*w).into();
let h: Option<S::Unit> = (*h).into();
(w.unwrap_or(area.2), w.unwrap_or(area.3))
},
_ => unreachable!()
};
to.clip(area, |to|item.draw(to))
});
XYWH(area.0, area.1, w, h)
}

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Use whole drawing area along one or both axes.
///

86
src/layout/iter.rs Normal file
View file

@ -0,0 +1,86 @@
use super::*;
/// Iterate over a collection of the same kind of [Draw]able:
pub fn iter <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
/// Iterate over a collection of the same kind of [Draw]able:
pub fn iter_once <'a, S: Screen, D: 'a, U: Draw<S>> (
_iter: impl Iterator<Item = D>, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
/// Iterate over a collection of various [Draw]ables:
pub fn iter_dyn <
S: Screen, // Target screen
D, // Input doesn't need to be [Draw]able, output does
V: Fn()->I, // Function that returns the iterator
I: Iterator<Item = D>, // Type of the iterator
F: Fn(&D, usize)->dyn Draw<S>, // Function that returns [Draw]able from iterator item
> (_items: V, _cb: F) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_east_fixed <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_south_fixed <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
draw(move|_to: &mut S|{ todo!() })
}
pub fn row_south <S: Screen> (south: S::Unit, height: S::Unit, content: impl Draw<S>)
-> impl Draw<S>
{
content.exact_h(height).push_y(south)
}
pub fn row_north <S: Screen> (north: S::Unit, height: S::Unit, content: impl Draw<S>)
-> impl Draw<S>
{
content.exact_h(height).pull_y(north)
}
pub fn col_east <S: Screen> (east: S::Unit, width: S::Unit, content: impl Draw<S>)
-> impl Draw<S>
{
content.exact_w(width).push_x(east)
}
pub fn col_west <S: Screen> (west: S::Unit, width: S::Unit, content: impl Draw<S>)
-> impl Draw<S>
{
content.exact_w(width).pull_x(west)
}

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Set maximum size of of drawing area.
///
@ -50,6 +50,6 @@ impl<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> Draw<T> for Max<T,
)),
_ => return Ok(None)
};
to.clip(area, draw(item))
to.clip(area, |to|item.draw(to))
}
}

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Only draw content if area is above a certain size.
///

315
src/layout/split.rs Normal file
View 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(())
}
}