refactor(output): group ops/ and space/

This commit is contained in:
🪞👃🪞 2025-04-13 21:19:25 +03:00
parent 18a01b8355
commit ec7621eff9
17 changed files with 29 additions and 13 deletions

135
output/src/space/area.rs Normal file
View file

@ -0,0 +1,135 @@
use crate::*;
use std::fmt::Debug;
pub trait Area<N: Coordinate>: From<[N;4]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()]
}
fn from_position (pos: impl Size<N>) -> [N;4] {
let [x, y] = pos.wh();
[x, y, 0.into(), 0.into()]
}
fn from_size (size: impl Size<N>) -> [N;4] {
let [w, h] = size.wh();
[0.into(), 0.into(), w, h]
}
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)
}
}
fn xy (&self) -> [N;2] {
[self.x(), self.y()]
}
fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
fn xywh (&self) -> [N;4] {
[self.x(), self.y(), self.w(), self.h()]
}
fn clip_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), self.h().min(h)]
}
fn clip_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), self.w().min(w), self.h()]
}
fn clip (&self, wh: impl Size<N>) -> [N;4] {
[self.x(), self.y(), wh.w(), wh.h()]
}
fn set_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), w, self.h()]
}
fn set_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), h]
}
fn x2 (&self) -> N {
self.x().plus(self.w())
}
fn y2 (&self) -> N {
self.y().plus(self.h())
}
fn lrtb (&self) -> [N;4] {
[self.x(), self.x2(), self.y(), self.y2()]
}
fn center (&self) -> [N;2] {
[self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())]
}
fn center_x (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()]
}
fn center_y (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n]
}
fn center_xy (&self, [n, m]: [N;2]) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m]
}
fn centered (&self) -> [N;2] {
[self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())]
}
fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.x()..(self.x()+self.w())
}
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.y()..(self.y()+self.h())
}
}
impl<N: Coordinate> Area<N> for (N, N, N, N) {
fn x (&self) -> N { self.0 }
fn y (&self) -> N { self.1 }
fn w (&self) -> N { self.2 }
fn h (&self) -> N { self.3 }
}
impl<N: Coordinate> Area<N> for [N;4] {
fn x (&self) -> N { self[0] }
fn y (&self) -> N { self[1] }
fn w (&self) -> N { self[2] }
fn h (&self) -> N { self[3] }
}
#[cfg(test)] mod test_area {
use super::*;
use proptest::prelude::*;
proptest! {
#[test] fn test_area_prop (
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
w in u16::MIN..u16::MAX,
h in u16::MIN..u16::MAX,
a in u16::MIN..u16::MAX,
b in u16::MIN..u16::MAX,
) {
let _: [u16;4] = <[u16;4] as Area<u16>>::zero();
let _: [u16;4] = <[u16;4] as Area<u16>>::from_position([a, b]);
let _: [u16;4] = <[u16;4] as Area<u16>>::from_size([a, b]);
let area: [u16;4] = [x, y, w, h];
let _ = area.expect_min(a, b);
let _ = area.xy();
let _ = area.wh();
let _ = area.xywh();
let _ = area.clip_h(a);
let _ = area.clip_w(b);
let _ = area.clip([a, b]);
let _ = area.set_w(a);
let _ = area.set_h(b);
let _ = area.x2();
let _ = area.y2();
let _ = area.lrtb();
let _ = area.center();
let _ = area.center_x(a);
let _ = area.center_y(b);
let _ = area.center_xy([a, b]);
let _ = area.centered();
}
}
}

View file

@ -0,0 +1,31 @@
use std::fmt::{Debug, Display};
use std::ops::{Add, Sub, Mul, Div};
/// A linear coordinate.
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 zero () -> Self { 0.into() }
fn plus (self, other: Self) -> Self;
fn minus (self, other: Self) -> Self {
if self >= other {
self - other
} else {
0.into()
}
}
}
impl Coordinate for u16 {
fn plus (self, other: Self) -> Self {
self.saturating_add(other)
}
}

View file

@ -0,0 +1,38 @@
use crate::*;
#[cfg(test)] use proptest_derive::Arbitrary;
/// A cardinal direction.
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(test, derive(Arbitrary))]
pub enum Direction { North, South, East, West, Above, Below }
impl Direction {
pub fn split_fixed <N: Coordinate> (self, area: impl Area<N>, a: N) -> ([N;4],[N;4]) {
let [x, y, w, h] = area.xywh();
match self {
North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]),
South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]),
East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]),
West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]),
Above | Below => (area.xywh(), area.xywh())
}
}
}
#[cfg(test)] mod test {
use super::*;
use proptest::prelude::*;
proptest! {
#[test] fn proptest_direction (
d in prop_oneof![
Just(North), Just(South),
Just(East), Just(West),
Just(Above), Just(Below)
],
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
w in u16::MIN..u16::MAX,
h in u16::MIN..u16::MAX,
a in u16::MIN..u16::MAX,
) {
let _ = d.split_fixed([x, y, w, h], a);
}
}
}

View file

@ -0,0 +1,87 @@
use crate::*;
use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
pub trait HasSize<E: Output> {
fn size (&self) -> &Measure<E>;
}
#[macro_export] macro_rules! has_size {
(<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? {
fn size (&$self) -> &Measure<$E> { $cb }
}
}
}
/// A widget that tracks its render width and height
#[derive(Default)]
pub struct Measure<E: Output> {
_engine: PhantomData<E>,
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
}
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
impl<E: Output> Content<E> for Measure<E> {
fn render (&self, to: &mut E) {
self.x.store(to.area().w().into(), Relaxed);
self.y.store(to.area().h().into(), Relaxed);
}
}
impl<E: Output> Clone for Measure<E> {
fn clone (&self) -> Self {
Self {
_engine: Default::default(),
x: self.x.clone(),
y: self.y.clone(),
}
}
}
impl<E: Output> std::fmt::Debug for Measure<E> {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("Measure")
.field("width", &self.x)
.field("height", &self.y)
.finish()
}
}
impl<E: Output> Measure<E> {
pub fn new () -> Self {
Self {
_engine: PhantomData::default(),
x: Arc::new(0.into()),
y: Arc::new(0.into()),
}
}
pub fn set_w (&self, w: impl Into<usize>) -> &Self {
self.x.store(w.into(), Relaxed);
self
}
pub fn set_h (&self, h: impl Into<usize>) -> &Self {
self.y.store(h.into(), Relaxed);
self
}
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &Self {
self.set_w(w);
self.set_h(h);
self
}
pub fn w (&self) -> usize {
self.x.load(Relaxed)
}
pub fn h (&self) -> usize {
self.y.load(Relaxed)
}
pub fn wh (&self) -> [usize;2] {
[self.w(), self.h()]
}
pub fn format (&self) -> Arc<str> {
format!("{}x{}", self.w(), self.h()).into()
}
pub fn of <T: Content<E>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
Bsp::b(Fill::xy(self), item)
}
}

63
output/src/space/size.rs Normal file
View file

@ -0,0 +1,63 @@
use crate::*;
use std::fmt::Debug;
pub trait Size<N: Coordinate>: From<[N;2]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N { self.x() }
fn h (&self) -> N { self.y() }
fn wh (&self) -> [N;2] { [self.x(), self.y()] }
fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
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)
}
}
fn zero () -> [N;2] {
[N::zero(), N::zero()]
}
fn to_area_pos (&self) -> [N;4] {
let [x, y] = self.wh();
[x, y, 0.into(), 0.into()]
}
fn to_area_size (&self) -> [N;4] {
let [w, h] = self.wh();
[0.into(), 0.into(), w, h]
}
}
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] }
}
#[cfg(test)] mod test_size {
use super::*;
use proptest::prelude::*;
proptest! {
#[test] fn test_size (
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
a in u16::MIN..u16::MAX,
b in u16::MIN..u16::MAX,
) {
let size = [x, y];
let _ = size.w();
let _ = size.h();
let _ = size.wh();
let _ = size.clip_w(a);
let _ = size.clip_h(b);
let _ = size.expect_min(a, b);
let _ = size.to_area_pos();
let _ = size.to_area_size();
}
}
}