use std::{fmt, ops, str::FromStr};
#[cfg(feature = "reflect")]
use bevy::prelude::Reflect;
pub type Axis = Flow;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct Size<T> {
pub width: T,
pub height: T,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct Oriented<T> {
pub main: T,
pub cross: T,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub enum Flow {
#[default]
Horizontal,
Vertical,
}
impl Flow {
pub const fn relative<T: Copy>(self, Size { width, height }: Size<T>) -> Oriented<T> {
let Size { width: main, height: cross } = self.absolute(Oriented::new(width, height));
Oriented { main, cross }
}
pub const fn absolute<T: Copy>(self, Oriented { main, cross }: Oriented<T>) -> Size<T> {
match self {
Self::Horizontal => Size::new(main, cross),
Self::Vertical => Size::new(cross, main),
}
}
}
impl Size<f32> {
pub const ZERO: Self = Self { width: 0., height: 0. };
}
impl ops::Add for Size<f32> {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
width: self.width + other.width,
height: self.height + other.height,
}
}
}
impl<T> Size<T> {
pub const fn new(width: T, height: T) -> Self {
Self { width, height }
}
pub fn all(value: T) -> Self
where
T: Clone,
{
Self { width: value.clone(), height: value }
}
pub fn map<U>(self, mut f: impl FnMut(T) -> U) -> Size<U> {
Size { width: f(self.width), height: f(self.height) }
}
pub fn map_into<U: From<T>>(self) -> Size<U> {
self.map(Into::into)
}
pub const fn as_ref(&self) -> Size<&T> {
let Self { width, height } = self;
Size { width, height }
}
}
impl<T: Copy> Oriented<T> {
pub const fn new(main: T, cross: T) -> Self {
Self { main, cross }
}
pub const fn as_ref(&self) -> Oriented<&T> {
let Self { main, cross } = self;
Oriented { main, cross }
}
}
impl fmt::Display for Flow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Horizontal => f.write_str("width"),
Self::Vertical => f.write_str("height"),
}
}
}
impl FromStr for Flow {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"v" => Ok(Self::Vertical),
">" => Ok(Self::Horizontal),
_ => Err(()),
}
}
}
impl From<bevy::math::Vec2> for Size<f32> {
fn from(value: bevy::math::Vec2) -> Self {
Self::new(value.x, value.y)
}
}
impl From<Size<f32>> for bevy::math::Vec2 {
fn from(value: Size<f32>) -> Self {
Self::new(value.width, value.height)
}
}
impl<T: fmt::Display> fmt::Display for Size<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}×{}", self.width, self.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn relative() {
let Oriented { main: main_v, cross: cross_v } =
Flow::Vertical.relative(Size::new("width", "height"));
let Oriented { main: main_h, cross: cross_h } =
Flow::Horizontal.relative(Size::new("width", "height"));
assert_eq!(main_v, cross_h);
assert_eq!(main_h, cross_v);
}
#[test]
fn absolute() {
let Size { width: width_v, height: height_v } =
Flow::Vertical.absolute(Oriented::new("main", "cross"));
let Size { width: width_h, height: height_h } =
Flow::Horizontal.absolute(Oriented::new("main", "cross"));
assert_eq!(width_v, height_h);
assert_eq!(width_h, height_v);
}
}