mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
912 lines
32 KiB
Rust
912 lines
32 KiB
Rust
use crate::*;
|
|
|
|
/// Standard numeric type.
|
|
pub trait Coordinate: Send + Sync + Copy
|
|
+ Add<Self, Output=Self>
|
|
+ Sub<Self, Output=Self>
|
|
+ Mul<Self, Output=Self>
|
|
+ Div<Self, Output=Self>
|
|
+ Ord + PartialEq + Eq
|
|
+ Debug + Display + Default
|
|
+ From<u16> + Into<u16>
|
|
+ Into<usize>
|
|
+ Into<f64>
|
|
{
|
|
fn minus (self, other: Self) -> Self {
|
|
if self >= other {
|
|
self - other
|
|
} else {
|
|
0.into()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Coordinate for T where
|
|
T: Send + Sync + Copy
|
|
+ Add<Self, Output=Self>
|
|
+ Sub<Self, Output=Self>
|
|
+ Mul<Self, Output=Self>
|
|
+ Div<Self, Output=Self>
|
|
+ Ord + PartialEq + Eq
|
|
+ Debug + Display + Default
|
|
+ From<u16> + Into<u16>
|
|
+ Into<usize>
|
|
+ Into<f64>
|
|
{}
|
|
|
|
pub struct FixedAxis<T> {
|
|
pub start: T,
|
|
pub point: Option<T>,
|
|
pub clamp: Option<T>,
|
|
}
|
|
pub struct ScaledAxis<T> {
|
|
pub start: T,
|
|
pub scale: T,
|
|
pub point: Option<T>,
|
|
pub clamp: Option<T>,
|
|
}
|
|
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
|
impl $A<$T> {
|
|
#[inline] pub fn start_inc (&mut self, n: $T) -> $T {
|
|
self.start = (self.start + n).min(self.clamp.unwrap_or(<$T>::MAX));
|
|
self.start
|
|
}
|
|
#[inline] pub fn start_dec (&mut self, n: $T) -> $T {
|
|
self.start = self.start.saturating_sub(n);
|
|
self.start
|
|
}
|
|
#[inline] pub fn point_inc (&mut self, n: $T) -> Option<$T> {
|
|
self.point = self.point.map(|p|(p + n).min(self.clamp.unwrap_or(<$T>::MAX)));
|
|
self.point
|
|
}
|
|
#[inline] pub fn point_dec (&mut self, n: $T) -> Option<$T> {
|
|
self.point = self.point.map(|p|p.saturating_sub(n));
|
|
self.point
|
|
}
|
|
}
|
|
} }
|
|
impl_axis_common!(FixedAxis u16);
|
|
impl_axis_common!(FixedAxis usize);
|
|
impl_axis_common!(ScaledAxis u16);
|
|
impl_axis_common!(ScaledAxis usize);
|
|
|
|
// TODO: return impl Point and impl Size instead of [N;x]
|
|
// to disambiguate between usage of 2-"tuple"s
|
|
|
|
pub trait Size<N: Coordinate> {
|
|
fn x (&self) -> N;
|
|
fn y (&self) -> N;
|
|
#[inline] fn w (&self) -> N { self.x() }
|
|
#[inline] fn h (&self) -> N { self.y() }
|
|
#[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] }
|
|
#[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w.into()), self.h()] }
|
|
#[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h.into())] }
|
|
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
|
if self.w() < w || self.h() < h {
|
|
Err(format!("min {w}x{h}").into())
|
|
} else {
|
|
Ok(self)
|
|
}
|
|
}
|
|
}
|
|
impl<N: Coordinate> Size<N> for (N, N) {
|
|
fn x (&self) -> N { self.0 }
|
|
fn y (&self) -> N { self.1 }
|
|
}
|
|
impl<N: Coordinate> Size<N> for [N;2] {
|
|
fn x (&self) -> N { self[0] }
|
|
fn y (&self) -> N { self[1] }
|
|
}
|
|
|
|
pub trait Area<N: Coordinate>: 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()] }
|
|
#[inline] fn shrink_x (&self, x: N) -> [N;4] { [self.x(), self.y(), self.w() - x, self.h()] }
|
|
#[inline] fn shrink_y (&self, y: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h() - 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] {
|
|
[self.x(), self.y(), self.w(), self.h().min(h.into())]
|
|
}
|
|
#[inline] fn clip_w (&self, w: N) -> [N;4] {
|
|
[self.x(), self.y(), self.w().min(w.into()), self.h()]
|
|
}
|
|
#[inline] fn clip (&self, wh: impl Size<N>) -> [N;4] {
|
|
[self.x(), self.y(), wh.w(), wh.h()]
|
|
}
|
|
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
|
if self.w() < w || self.h() < h {
|
|
Err(format!("min {w}x{h}").into())
|
|
} else {
|
|
Ok(self)
|
|
}
|
|
}
|
|
#[inline] fn split_fixed (&self, direction: Direction, a: N) -> ([N;4],[N;4]) {
|
|
match direction {
|
|
Direction::Up => (
|
|
[self.x(), self.y() + self.h() - a, self.w(), a],
|
|
[self.x(), self.y(), self.w(), self.h() - a],
|
|
),
|
|
Direction::Down => (
|
|
[self.x(), self.y(), self.w(), a],
|
|
[self.x(), self.y() + a, self.w(), self.h() - a],
|
|
),
|
|
Direction::Right => (
|
|
[self.x(), self.y(), a, self.h()],
|
|
[self.x() + a, self.y(), self.w() - a, self.h()],
|
|
),
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<N: Coordinate> Area<N> 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<N: Coordinate> Area<N> 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] }
|
|
}
|
|
|
|
pub trait Layout<E: Engine>: Widget<Engine = E> + Sized {
|
|
fn align_center (self) -> Align<Self> { Align::Center(self) }
|
|
fn align_n (self) -> Align<Self> { Align::N(self) }
|
|
fn align_s (self) -> Align<Self> { Align::S(self) }
|
|
fn align_e (self) -> Align<Self> { Align::E(self) }
|
|
fn align_w (self) -> Align<Self> { Align::W(self) }
|
|
fn align_nw (self) -> Align<Self> { Align::NW(self) }
|
|
fn align_sw (self) -> Align<Self> { Align::SW(self) }
|
|
fn align_ne (self) -> Align<Self> { Align::NE(self) }
|
|
fn align_se (self) -> Align<Self> { Align::SE(self) }
|
|
fn align_x (self) -> Align<Self> { Align::X(self) }
|
|
fn align_y (self) -> Align<Self> { Align::Y(self) }
|
|
fn fixed_x (self, x: E::Unit) -> Fixed<E::Unit, Self> { Fixed::X(x, self) }
|
|
fn fixed_y (self, y: E::Unit) -> Fixed<E::Unit, Self> { Fixed::Y(y, self) }
|
|
fn fixed_xy (self, x: E::Unit, y: E::Unit) -> Fixed<E::Unit, Self> { Fixed::XY(x, y, self) }
|
|
fn min_x (self, x: E::Unit) -> Min<E::Unit, Self> { Min::X(x, self) }
|
|
fn min_y (self, y: E::Unit) -> Min<E::Unit, Self> { Min::Y(y, self) }
|
|
fn min_xy (self, x: E::Unit, y: E::Unit) -> Min<E::Unit, Self> { Min::XY(x, y, self) }
|
|
fn max_x (self, x: E::Unit) -> Max<E::Unit, Self> { Max::X(x, self) }
|
|
fn max_y (self, y: E::Unit) -> Max<E::Unit, Self> { Max::Y(y, self) }
|
|
fn max_xy (self, x: E::Unit, y: E::Unit) -> Max<E::Unit, Self> { Max::XY(x, y, self) }
|
|
fn push_x (self, x: E::Unit) -> Push<E::Unit, Self> { Push::X(x, self) }
|
|
fn push_y (self, y: E::Unit) -> Push<E::Unit, Self> { Push::Y(y, self) }
|
|
fn push_xy (self, x: E::Unit, y: E::Unit) -> Push<E::Unit, Self> { Push::XY(x, y, self) }
|
|
fn pull_x (self, x: E::Unit) -> Pull<E::Unit, Self> { Pull::X(x, self) }
|
|
fn pull_y (self, y: E::Unit) -> Pull<E::Unit, Self> { Pull::Y(y, self) }
|
|
fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull<E::Unit, Self> { Pull::XY(x, y, self) }
|
|
fn grow_x (self, x: E::Unit) -> Grow<E::Unit, Self> { Grow::X(x, self) }
|
|
fn grow_y (self, y: E::Unit) -> Grow<E::Unit, Self> { Grow::Y(y, self) }
|
|
fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow<E::Unit, Self> { Grow::XY(x, y, self) }
|
|
fn shrink_x (self, x: E::Unit) -> Shrink<E::Unit, Self> { Shrink::X(x, self) }
|
|
fn shrink_y (self, y: E::Unit) -> Shrink<E::Unit, Self> { Shrink::Y(y, self) }
|
|
fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink<E::Unit, Self> { Shrink::XY(x, y, self) }
|
|
fn inset_x (self, x: E::Unit) -> Inset<E::Unit, Self> { Inset::X(x, self) }
|
|
fn inset_y (self, y: E::Unit) -> Inset<E::Unit, Self> { Inset::Y(y, self) }
|
|
fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset<E::Unit, Self> { Inset::XY(x, y, self) }
|
|
fn outset_x (self, x: E::Unit) -> Outset<E::Unit, Self> { Outset::X(x, self) }
|
|
fn outset_y (self, y: E::Unit) -> Outset<E::Unit, Self> { Outset::Y(y, self) }
|
|
fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset<E::Unit, Self> { Outset::XY(x, y, self) }
|
|
fn fill_x (self) -> Fill<E, Self> { Fill::X(self) }
|
|
fn fill_y (self) -> Fill<E, Self> { Fill::Y(self) }
|
|
fn fill_xy (self) -> Fill<E, Self> { Fill::XY(self) }
|
|
fn debug (self) -> DebugOverlay<E, Self> { DebugOverlay(self) }
|
|
fn split <W: Widget<Engine = E>> (
|
|
self, direction: Direction, amount: E::Unit, other: W
|
|
) -> Split<E, Self, W> { Split::new(direction, amount, self, other) }
|
|
fn split_flip <W: Widget<Engine = E>> (
|
|
self, direction: Direction, amount: E::Unit, other: W
|
|
) -> Split<E, W, Self> { Split::new(direction, amount, other, self) }
|
|
}
|
|
|
|
impl<E: Engine, W: Widget<Engine = E>> Layout<E> for W {}
|
|
|
|
pub struct DebugOverlay<E: Engine, W: Widget<Engine = E>>(pub W);
|
|
|
|
pub enum Fill<E: Engine, W: Widget<Engine = E>> { X(W), Y(W), XY(W) }
|
|
|
|
impl<E: Engine, W: Widget<Engine = E>> Fill<E, W> {
|
|
fn inner (&self) -> &W {
|
|
match self {
|
|
Self::X(inner) => &inner,
|
|
Self::Y(inner) => &inner,
|
|
Self::XY(inner) => &inner,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, W: Widget<Engine = E>> Widget for Fill<E, W> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
let area = self.inner().layout(to.into())?;
|
|
if let Some(area) = area {
|
|
Ok(Some(match self {
|
|
Self::X(_) => [to.w().into(), area.h()],
|
|
Self::Y(_) => [area.w(), to.h().into()],
|
|
Self::XY(_) => [to.w().into(), to.h().into()],
|
|
}.into()))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
self.inner().render(to)
|
|
}
|
|
}
|
|
|
|
pub struct Layers<
|
|
E: Engine,
|
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
|
>(pub F, PhantomData<E>);
|
|
|
|
impl<
|
|
E: Engine,
|
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
|
> Layers<E, F> {
|
|
#[inline]
|
|
pub fn new (build: F) -> Self {
|
|
Self(build, Default::default())
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, F> Widget for Layers<E, F>
|
|
where
|
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
|
{
|
|
type Engine = E;
|
|
fn layout (&self, area: E::Size) -> Perhaps<E::Size> {
|
|
let mut w: E::Unit = 0.into();
|
|
let mut h: E::Unit = 0.into();
|
|
(self.0)(&mut |layer| {
|
|
if let Some(layer_area) = layer.layout(area)? {
|
|
w = w.max(layer_area.w());
|
|
h = h.max(layer_area.h());
|
|
}
|
|
Ok(())
|
|
})?;
|
|
Ok(Some([w, h].into()))
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
if let Some(size) = self.layout(to.area().wh().into())? {
|
|
(self.0)(&mut |layer|to.render_in(to.area().clip(size).into(), &layer))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
//`Layers<_, impl (Fn(&mut dyn FnMut(&dyn Widget<Engine = _>) -> Result<(), Box<...>>) -> ... ) + Send + Sync>`
|
|
//`Layers<Tui, impl (Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) + Send + Sync + '_>`
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub enum Direction {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
impl Direction {
|
|
pub fn is_down (&self) -> bool {
|
|
match self { Self::Down => true, _ => false }
|
|
}
|
|
pub fn is_right (&self) -> bool {
|
|
match self { Self::Right => true, _ => false }
|
|
}
|
|
pub fn cw (&self) -> Self {
|
|
match self {
|
|
Self::Up => Self::Right,
|
|
Self::Down => Self::Left,
|
|
Self::Left => Self::Up,
|
|
Self::Right => Self::Down,
|
|
}
|
|
}
|
|
pub fn ccw (&self) -> Self {
|
|
match self {
|
|
Self::Up => Self::Left,
|
|
Self::Down => Self::Right,
|
|
Self::Left => Self::Down,
|
|
Self::Right => Self::Up,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Override X and Y coordinates, aligning to corner, side, or center of area
|
|
pub enum Align<L> {
|
|
/// Draw at center of container
|
|
Center(L),
|
|
/// Draw at center of X axis
|
|
X(L),
|
|
/// Draw at center of Y axis
|
|
Y(L),
|
|
/// Draw at upper left corner of contaier
|
|
NW(L),
|
|
/// Draw at center of upper edge of container
|
|
N(L),
|
|
/// Draw at right left corner of contaier
|
|
NE(L),
|
|
/// Draw at center of left edge of container
|
|
W(L),
|
|
/// Draw at center of right edge of container
|
|
E(L),
|
|
/// Draw at lower left corner of container
|
|
SW(L),
|
|
/// Draw at center of lower edge of container
|
|
S(L),
|
|
/// Draw at lower right edge of container
|
|
SE(L)
|
|
}
|
|
|
|
impl<T> Align<T> {
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn align<T, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<T>, outer: R, inner: R) -> Option<R> {
|
|
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(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Align<T> {
|
|
type Engine = E;
|
|
fn layout (&self, outer_area: E::Size) -> Perhaps<E::Size> {
|
|
self.inner().layout(outer_area)
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
let outer_area = to.area();
|
|
Ok(if let Some(inner_size) = self.layout(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())?
|
|
} else {
|
|
()
|
|
}
|
|
} else {
|
|
()
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Enforce fixed size of drawing area
|
|
pub enum Fixed<U: Coordinate, T> {
|
|
/// Enforce fixed width
|
|
X(U, T),
|
|
/// Enforce fixed height
|
|
Y(U, T),
|
|
/// Enforce fixed width and height
|
|
XY(U, U, T),
|
|
}
|
|
impl<N: Coordinate, T> Fixed<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Fixed<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
Ok(match self {
|
|
Self::X(w, _) =>
|
|
if to.w() >= *w { Some([*w, to.h()].into()) } else { None },
|
|
Self::Y(h, _) =>
|
|
if to.h() >= *h { Some([to.w(), *h].into()) } else { None },
|
|
Self::XY(w, h, _)
|
|
=> if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None },
|
|
})
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
// 🡘 🡙 ←🡙→
|
|
if let Some(size) = self.layout(to.area().wh().into())? {
|
|
to.render_in(to.area().clip(size).into(), self.inner())
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Enforce minimum size of drawing area
|
|
pub enum Min<U: Coordinate, T> {
|
|
/// Enforce minimum width
|
|
X(U, T),
|
|
/// Enforce minimum height
|
|
Y(U, T),
|
|
/// Enforce minimum width and height
|
|
XY(U, U, T),
|
|
}
|
|
impl<N: Coordinate, T> Min<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Min<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [to.w().max(w), to.h()],
|
|
Self::Y(h, _) => [to.w(), to.h().max(h)],
|
|
Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)],
|
|
}.into()))
|
|
}
|
|
// TODO: 🡘 🡙 ←🡙→
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
Ok(self.layout(to.area().wh().into())?
|
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
.transpose()?.unwrap_or(()))
|
|
}
|
|
}
|
|
|
|
/// Enforce maximum size of drawing area
|
|
pub enum Max<U: Coordinate, T> {
|
|
/// Enforce maximum width
|
|
X(U, T),
|
|
/// Enforce maximum height
|
|
Y(U, T),
|
|
/// Enforce maximum width and height
|
|
XY(U, U, T),
|
|
}
|
|
|
|
impl<N: Coordinate, T> Max<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Max<E:: Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [to.w().min(w), to.h()],
|
|
Self::Y(h, _) => [to.w(), to.h().min(h)],
|
|
Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)],
|
|
}.into()))
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
Ok(self.layout(to.area().wh().into())?
|
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
.transpose()?.unwrap_or(()))
|
|
}
|
|
}
|
|
|
|
/// Expand drawing area
|
|
pub enum Grow<N: Coordinate, T> {
|
|
/// Increase width
|
|
X(N, T),
|
|
/// Increase height
|
|
Y(N, T),
|
|
/// Increase width and height
|
|
XY(N, N, T)
|
|
}
|
|
|
|
impl<N: Coordinate, T> Grow<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Grow<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [to.w() + w, to.h()],
|
|
Self::Y(h, _) => [to.w(), to.h() + h],
|
|
Self::XY(w, h, _) => [to.w() + w, to.h() + h],
|
|
}.into()))
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
Ok(self.layout(to.area().wh().into())?
|
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
.transpose()?.unwrap_or(()))
|
|
}
|
|
}
|
|
|
|
/// Shrink drawing area
|
|
pub enum Shrink<N: Coordinate, T> {
|
|
/// Decrease width
|
|
X(N, T),
|
|
/// Decrease height
|
|
Y(N, T),
|
|
/// Decrease width and height
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Coordinate, T: Widget> Shrink<N, T> {
|
|
fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Shrink<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
Ok(self.inner().layout(to)?.map(|to|match *self {
|
|
Self::X(w, _) => [
|
|
if to.w() > w { to.w() - w } else { 0.into() },
|
|
to.h()
|
|
],
|
|
Self::Y(h, _) => [
|
|
to.w(),
|
|
if to.h() > h { to.h() - h } else { 0.into() }
|
|
],
|
|
Self::XY(w, h, _) => [
|
|
if to.w() > w { to.w() - w } else { 0.into() },
|
|
if to.h() > h { to.h() - h } else { 0.into() }
|
|
]
|
|
}.into()))
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
Ok(self.layout(to.area().wh().into())?
|
|
.map(|size|to.render_in(to.area().clip(size).into(), self.inner()))
|
|
.transpose()?.unwrap_or(()))
|
|
}
|
|
}
|
|
|
|
/// Shrink from each side
|
|
pub enum Inset<N: Coordinate, T> {
|
|
/// Decrease width
|
|
X(N, T),
|
|
/// Decrease height
|
|
Y(N, T),
|
|
/// Decrease width and height
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Coordinate, T: Widget> Inset<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
/// Grow on each side
|
|
pub enum Outset<N: Coordinate, T> {
|
|
/// Increase width
|
|
X(N, T),
|
|
/// Increase height
|
|
Y(N, T),
|
|
/// Increase width and height
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Coordinate, T: Widget> Outset<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Inset<E::Unit, T> {
|
|
type Engine = E;
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
match *self {
|
|
Self::X(x, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).shrink_x(x).push_x(x),
|
|
Self::Y(y, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).shrink_y(y).push_y(y),
|
|
Self::XY(x, y, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).shrink_xy(x, y).push_xy(x, y)
|
|
}.render(to)
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Outset<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
match *self {
|
|
Self::X(x, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).grow_x(x + x),
|
|
Self::Y(y, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).grow_y(y + y),
|
|
Self::XY(x, y, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).grow_xy(x + x, y + y),
|
|
}.layout(to)
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
match *self {
|
|
Self::X(x, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).push_x(x),
|
|
Self::Y(y, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).push_y(y),
|
|
Self::XY(x, y, ref inner) =>
|
|
(inner as &dyn Widget<Engine = E>).push_xy(x, y),
|
|
}.render(to)
|
|
}
|
|
}
|
|
|
|
/// Move origin point of drawing area
|
|
pub enum Push<N: Coordinate, T: Widget> {
|
|
/// Move origin to the right
|
|
X(N, T),
|
|
/// Move origin downwards
|
|
Y(N, T),
|
|
/// Move origin to the right and downwards
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Coordinate, T: Widget> Push<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
pub fn x (&self) -> N {
|
|
match self { Self::X(x, _) => *x, Self::Y(_, _) => N::default(), Self::XY(x, _, _) => *x }
|
|
}
|
|
pub fn y (&self) -> N {
|
|
match self { Self::X(_, _) => N::default(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Push<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
self.inner().layout(to)
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
let area = to.area();
|
|
Ok(self.layout(area.wh().into())?
|
|
.map(|size|to.render_in(match *self {
|
|
Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()],
|
|
Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()],
|
|
Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()],
|
|
}.into(), self.inner())).transpose()?.unwrap_or(()))
|
|
}
|
|
}
|
|
|
|
/// Move origin point of drawing area
|
|
pub enum Pull<N: Coordinate, T: Widget> {
|
|
/// Move origin to the right
|
|
X(N, T),
|
|
/// Move origin downwards
|
|
Y(N, T),
|
|
/// Move origin to the right and downwards
|
|
XY(N, N, T),
|
|
}
|
|
|
|
impl<N: Coordinate, T: Widget> Pull<N, T> {
|
|
pub fn inner (&self) -> &T {
|
|
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
|
}
|
|
pub fn x (&self) -> N {
|
|
match self { Self::X(x, _) => *x, Self::Y(_, _) => N::default(), Self::XY(x, _, _) => *x }
|
|
}
|
|
pub fn y (&self) -> N {
|
|
match self { Self::X(_, _) => N::default(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y }
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, T: Widget<Engine = E>> Widget for Pull<E::Unit, T> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
self.inner().layout(to)
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
let area = to.area();
|
|
Ok(self.layout(area.wh().into())?
|
|
.map(|size|to.render_in(match *self {
|
|
Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()],
|
|
Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()],
|
|
Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()],
|
|
}.into(), self.inner())).transpose()?.unwrap_or(()))
|
|
}
|
|
}
|
|
|
|
pub struct Stack<
|
|
E: Engine,
|
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
|
>(pub F, pub Direction, PhantomData<E>);
|
|
|
|
impl<
|
|
E: Engine,
|
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
|
> Stack<E, F> {
|
|
#[inline] pub fn new (direction: Direction, build: F) -> Self {
|
|
Self(build, direction, Default::default())
|
|
}
|
|
#[inline] pub fn right (build: F) -> Self {
|
|
Self::new(Direction::Right, build)
|
|
}
|
|
#[inline] pub fn down (build: F) -> Self {
|
|
Self::new(Direction::Down, build)
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, F> Widget for Stack<E, F>
|
|
where
|
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
|
{
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
let mut w = 0.into();
|
|
let mut h = 0.into();
|
|
match self.1 {
|
|
Direction::Down => {
|
|
(self.0)(&mut |component| {
|
|
if h >= to.h() { return Ok(()) }
|
|
let size = component.push_y(h).max_y(to.h() - h).layout(to)?;
|
|
if let Some([width, height]) = size.map(|size|size.wh()) {
|
|
h = h + height.into();
|
|
if width > w { w = width; }
|
|
}
|
|
Ok(())
|
|
})?;
|
|
},
|
|
Direction::Right => {
|
|
(self.0)(&mut |component| {
|
|
if w >= to.w() { return Ok(()) }
|
|
let size = component.push_x(w).max_x(to.w() - w).layout(to)?;
|
|
if let Some([width, height]) = size.map(|size|size.wh()) {
|
|
w = w + width.into();
|
|
if height > h { h = height }
|
|
}
|
|
Ok(())
|
|
})?;
|
|
},
|
|
_ => todo!()
|
|
};
|
|
Ok(Some([w, h].into()))
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
let area = to.area();
|
|
let mut w = 0.into();
|
|
let mut h = 0.into();
|
|
match self.1 {
|
|
Direction::Down => {
|
|
(self.0)(&mut |component| {
|
|
if h >= area.h() { return Ok(()) }
|
|
let item = component.push_y(h).max_y(area.h() - h);
|
|
let size = item.layout(area.wh().into())?;
|
|
if let Some([width, height]) = size.map(|size|size.wh()) {
|
|
item.render(to)?;
|
|
h = h + height;
|
|
if width > w { w = width }
|
|
};
|
|
Ok(())
|
|
})?;
|
|
},
|
|
Direction::Right => {
|
|
(self.0)(&mut |component| {
|
|
if w >= area.w() { return Ok(()) }
|
|
let item = component.push_x(w).max_x(area.w() - w);
|
|
let size = item.layout(area.wh().into())?;
|
|
if let Some([width, height]) = size.map(|size|size.wh()) {
|
|
item.render(to)?;
|
|
w = width + w;
|
|
if height > h { h = height }
|
|
};
|
|
Ok(())
|
|
})?;
|
|
},
|
|
_ => todo!()
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[macro_export] macro_rules! lay {
|
|
($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) }
|
|
}
|
|
#[macro_export] macro_rules! col {
|
|
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
|
($pat:pat in $collection:expr => $item:expr) => {
|
|
Stack::down(move |add|{
|
|
for $pat in $collection { add(&$item)?; }
|
|
Ok(())
|
|
})
|
|
}
|
|
}
|
|
#[macro_export] macro_rules! row {
|
|
($($expr:expr),* $(,)?) => { Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
|
($pat:pat in $collection:expr => $item:expr) => {
|
|
Stack::right(move |add|{
|
|
for $pat in $collection { add(&$item)?; }
|
|
Ok(())
|
|
})
|
|
}
|
|
}
|
|
|
|
/// A binary split with fixed proportion
|
|
pub struct Split<E: Engine, A: Widget<Engine = E>, B: Widget<Engine = E>>(
|
|
pub Direction, pub E::Unit, A, B, PhantomData<E>
|
|
);
|
|
|
|
impl<E: Engine, A: Widget<Engine = E>, B: Widget<Engine = E>> Split<E, A, B> {
|
|
pub fn new (direction: Direction, proportion: E::Unit, a: A, b: B) -> Self {
|
|
Self(direction, proportion, a, b, Default::default())
|
|
}
|
|
pub fn up (proportion: E::Unit, a: A, b: B) -> Self {
|
|
Self(Direction::Up, proportion, a, b, Default::default())
|
|
}
|
|
pub fn down (proportion: E::Unit, a: A, b: B) -> Self {
|
|
Self(Direction::Down, proportion, a, b, Default::default())
|
|
}
|
|
pub fn left (proportion: E::Unit, a: A, b: B) -> Self {
|
|
Self(Direction::Left, proportion, a, b, Default::default())
|
|
}
|
|
pub fn right (proportion: E::Unit, a: A, b: B) -> Self {
|
|
Self(Direction::Right, proportion, a, b, Default::default())
|
|
}
|
|
}
|
|
|
|
impl<E: Engine, A: Widget<Engine = E>, B: Widget<Engine = E>> Widget for Split<E, A, B> {
|
|
type Engine = E;
|
|
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
|
Ok(Some(to))
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
let (a, b) = to.area().split_fixed(self.0, self.1);
|
|
to.render_in(a.into(), &self.2)?;
|
|
to.render_in(b.into(), &self.3)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A widget that tracks its render width and height
|
|
pub struct Measure<E: Engine>(PhantomData<E>, AtomicUsize, AtomicUsize);
|
|
|
|
impl<E: Engine> Measure<E> {
|
|
pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) }
|
|
pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) }
|
|
pub fn wh (&self) -> [usize;2] { [self.w(), self.h()] }
|
|
pub fn set_w (&self, w: impl Into<usize>) { self.1.store(w.into(), Ordering::Relaxed) }
|
|
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) }
|
|
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
|
|
pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) }
|
|
}
|
|
|
|
impl<E: Engine> Widget for Measure<E> {
|
|
type Engine = E;
|
|
fn layout (&self, _: E::Size) -> Perhaps<E::Size> {
|
|
Ok(Some([0u16.into(), 0u16.into()].into()))
|
|
}
|
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
|
self.set_w(to.area().w());
|
|
self.set_h(to.area().h());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A scrollable area.
|
|
pub struct Scroll<
|
|
E: Engine,
|
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
|
>(pub F, pub Direction, pub u64, PhantomData<E>);
|