use std::{fmt::Display, marker::Destruct}; use crate::{ layout::{Align, Axis, UiNum, Vec2}, util::{LerpUtil, impl_op}, }; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable, Default)] pub struct UiVec2 { pub rel: Vec2, pub abs: Vec2, } impl UiVec2 { pub const ZERO: Self = Self { rel: Vec2::ZERO, abs: Vec2::ZERO, }; /// expands this position into a sized region centered at self pub fn expand(&self, size: impl Into) -> UiRegion { let size = size.into(); UiRegion { top_left: self.offset(-size / 2.0), bot_right: self.offset(size / 2.0), } } pub const fn abs(abs: impl const Into) -> Self { Self { rel: Vec2::ZERO, abs: abs.into(), } } pub const fn rel(rel: impl const Into) -> Self { Self { rel: rel.into(), abs: Vec2::ZERO, } } pub const fn shift(&mut self, offset: impl const Into) { let offset = offset.into(); *self += offset; } pub const fn offset(mut self, offset: impl const Into) -> Self { self.shift(offset); self } pub const fn within(&self, region: &UiRegion) -> UiVec2 { let rel = self.rel.lerp(region.top_left.rel, region.bot_right.rel); let abs = self.abs + self.rel.lerp(region.top_left.abs, region.bot_right.abs); UiVec2 { rel, abs } } pub const fn outside(&self, region: &UiRegion) -> UiVec2 { let rel = self.rel.lerp_inv(region.top_left.rel, region.bot_right.rel); let abs = self.abs - rel.lerp(region.top_left.abs, region.bot_right.abs); UiVec2 { rel, abs } } pub fn axis_mut(&mut self, axis: Axis) -> UiScalarView<'_> { match axis { Axis::X => UiScalarView { rel: &mut self.rel.x, abs: &mut self.abs.x, }, Axis::Y => UiScalarView { rel: &mut self.rel.y, abs: &mut self.abs.y, }, } } pub fn axis(&self, axis: Axis) -> UiScalar { match axis { Axis::X => UiScalar { rel: self.rel.x, abs: self.abs.x, }, Axis::Y => UiScalar { rel: self.rel.y, abs: self.abs.y, }, } } /// reflection about an axis pub fn flip(&mut self, axis: Axis) { *self.rel.axis_mut(axis) = 1.0 - self.rel.axis(axis); *self.abs.axis_mut(axis) = -self.abs.axis(axis); } pub fn flipped(mut self, axis: Axis) -> Self { self.flip(axis); self } pub fn to_size(&self, size: Vec2) -> Vec2 { self.rel * size + self.abs } pub const MAX_SIZE: Self = Self::rel(Vec2::ONE); pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self { Self { rel: Vec2::from_axis(axis, aligned.rel, ortho.rel), abs: Vec2::from_axis(axis, aligned.abs, ortho.abs), } } } impl Display for UiVec2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "rel {} / abs {}", self.rel, self.abs) } } impl_op!(UiVec2 Add add; rel abs); impl_op!(UiVec2 Sub sub; rel abs); impl const From for UiVec2 { fn from(align: Align) -> Self { Self::rel(align.rel()) } } impl Align { pub fn pos(self) -> UiVec2 { UiVec2::from(self) } } impl const From for UiVec2 { fn from(abs: Vec2) -> Self { Self::abs(abs) } } impl const From<(T, U)> for UiVec2 where (T, U): const Destruct, { fn from(abs: (T, U)) -> Self { Self::abs(abs) } } #[derive(Clone, Copy, Debug, Default)] pub struct UiScalar { pub rel: f32, pub abs: f32, } impl_op!(UiScalar Add add; rel abs); impl_op!(UiScalar Sub sub; rel abs); impl UiScalar { pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 }; pub fn new(rel: f32, abs: f32) -> Self { Self { rel, abs } } pub fn rel_min() -> Self { Self::new(0.0, 0.0) } pub fn rel_max() -> Self { Self::new(1.0, 0.0) } pub fn max(&self, other: Self) -> Self { Self { rel: self.rel.max(other.rel), abs: self.abs.max(other.abs), } } pub fn min(&self, other: Self) -> Self { Self { rel: self.rel.min(other.rel), abs: self.abs.min(other.abs), } } pub fn from_anchor(anchor: f32) -> Self { Self::new(anchor, 0.0) } pub fn offset(mut self, amt: f32) -> Self { self.abs += amt; self } pub fn within(&self, start: UiScalar, end: UiScalar) -> Self { let anchor = self.rel.lerp(start.rel, end.rel); let offset = self.abs + self.rel.lerp(start.abs, end.abs); Self { rel: anchor, abs: offset, } } } #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] pub struct UiRegion { pub top_left: UiVec2, pub bot_right: UiVec2, } impl UiRegion { pub const fn full() -> Self { Self { top_left: Align::TopLeft.into(), bot_right: Align::BotRight.into(), } } pub fn rel(anchor: Vec2) -> Self { Self { top_left: UiVec2::rel(anchor), bot_right: UiVec2::rel(anchor), } } pub fn within(&self, parent: &Self) -> Self { Self { top_left: self.top_left.within(parent), bot_right: self.bot_right.within(parent), } } pub fn outside(&self, parent: &Self) -> Self { Self { top_left: self.top_left.outside(parent), bot_right: self.bot_right.outside(parent), } } pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> { UIRegionAxisView { top_left: self.top_left.axis_mut(axis), bot_right: self.bot_right.axis_mut(axis), } } pub fn flip(&mut self, axis: Axis) { self.top_left.flip(axis); self.bot_right.flip(axis); let tl = self.top_left.axis_mut(axis); let br = self.bot_right.axis_mut(axis); std::mem::swap(tl.rel, br.rel); std::mem::swap(tl.abs, br.abs); } pub fn shift(&mut self, offset: impl Into) { let offset = offset.into(); self.top_left.shift(offset); self.bot_right.shift(offset); } pub fn offset(mut self, offset: impl Into) -> Self { self.shift(offset); self } pub fn to_screen(&self, size: Vec2) -> ScreenRegion { ScreenRegion { top_left: self.top_left.rel * size + self.top_left.abs, bot_right: self.bot_right.rel * size + self.bot_right.abs, } } pub fn center(&self) -> UiVec2 { Align::Center.pos().within(self) } pub fn in_size(&self, size: Vec2) -> Vec2 { self.bot_right.to_size(size) - self.top_left.to_size(size) } pub fn size(&self) -> UiVec2 { self.bot_right - self.top_left } pub fn select_aligned(&self, size: Vec2, align: Align) -> Self { Self::from_size_align(size, align).within(self) } pub fn from_size_align(size: Vec2, align: Align) -> Self { let mut top_left = UiVec2::from(align); top_left.abs -= size * align.rel(); let mut bot_right = UiVec2::from(align); bot_right.abs += size * (Vec2::ONE - align.rel()); Self { top_left, bot_right, } } pub fn from_ui_size_align(size: UiVec2, align: Align) -> Self { let mut top_left = UiVec2::from(align); top_left.abs -= size.abs * align.rel(); top_left.rel -= size.rel * align.rel(); let mut bot_right = UiVec2::from(align); bot_right.abs += size.abs * (Vec2::ONE - align.rel()); bot_right.rel += size.rel * (Vec2::ONE - align.rel()); Self { top_left, bot_right, } } } impl Display for UiRegion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} -> {}", self.top_left, self.bot_right) } } #[derive(Debug)] pub struct ScreenRegion { pub top_left: Vec2, pub bot_right: Vec2, } impl ScreenRegion { pub fn contains(&self, pos: Vec2) -> bool { pos.x >= self.top_left.x && pos.x <= self.bot_right.x && pos.y >= self.top_left.y && pos.y <= self.bot_right.y } } pub struct UIRegionAxisView<'a> { pub top_left: UiScalarView<'a>, pub bot_right: UiScalarView<'a>, } pub struct UiScalarView<'a> { pub rel: &'a mut f32, pub abs: &'a mut f32, } impl UiScalarView<'_> { pub fn set(&mut self, scalar: UiScalar) { *self.rel = scalar.rel; *self.abs = scalar.abs; } pub fn get(self) -> UiScalar { UiScalar { rel: *self.rel, abs: *self.abs, } } }