use crate::util::impl_axis_index; use std::{fmt::Display, marker::Destruct}; use super::*; use crate::{Px, PxVec2, Rel, UiNum, util::impl_op}; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable, Default)] pub struct UiVec2 { pub x: Len, pub y: Len, } impl UiVec2 { pub const ZERO: Self = Self { x: Len::ZERO, y: Len::ZERO, }; pub const fn new(x: Len, y: Len) -> Self { Self { x, y } } pub const fn px(px: impl const Into) -> Self { let px = px.into(); Self { x: Len::px(px.x), y: Len::px(px.y), } } /// From lengths already on the grid, with no fraction of a box. pub const fn from_px(px: PxVec2) -> Self { Self { x: Len::from_parts(Rel::ZERO, px.x), y: Len::from_parts(Rel::ZERO, px.y), } } pub const fn rel(rel: impl const Into) -> Self { let rel = rel.into(); Self { x: Len::rel(rel.x), y: Len::rel(rel.y), } } 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 { UiVec2 { x: self.x.within(®ion.x), y: self.y.within(®ion.y), } } /// Resolved against a box of `size`, which is where a fraction stops /// being one and becomes a place. pub fn to_px(&self, size: PxVec2) -> PxVec2 { PxVec2::new(self.x.to_px(size.x), self.y.to_px(size.y)) } pub const FULL_SIZE: Self = Self::rel(Vec2::ONE); pub const fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self { match axis { Axis::X => Self { x: aligned, y: ortho, }, Axis::Y => Self { x: ortho, y: aligned, }, } } pub fn get_px(&self) -> Vec2 { (self.x.px.to_f32(), self.y.px.to_f32()).into() } pub fn get_rel(&self) -> Vec2 { (self.x.rel.to_f32(), self.y.rel.to_f32()).into() } } impl Display for UiVec2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "rel{};px{}", self.get_rel(), self.get_px()) } } impl_op!(same UiVec2 Add add; x y); impl_op!(same UiVec2 Sub sub; x y); const impl From for UiVec2 { fn from(px: Vec2) -> Self { Self::px(px) } } const impl From<(T, U)> for UiVec2 where (T, U): const Destruct, { fn from(px: (T, U)) -> Self { Self::px(px) } } /// A length along one axis: a fraction of the box it is measured in plus an /// offset, `rel * box + px`. A position is the same number -- the length from /// the start of the box to the point -- which is why a [`UiSpan`] is two of /// these. Both parts are fixed point, so composing one through a chain of /// boxes rounds only where it multiplies, and lands on the same number as any /// other route to the same place. /// /// It carries no claim on what a container has left over. That is /// [`crate::LayoutLen`], which is this plus a weight, and which means nothing /// to anyone but whoever divides the room. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, Default, bytemuck::Zeroable)] pub struct Len { pub rel: Rel, pub px: Px, } impl_op!(same Len Add add; rel px); impl_op!(same Len Sub sub; rel px); impl Len { pub const ZERO: Self = Self { rel: Rel::ZERO, px: Px::ZERO, }; pub const FULL: Self = Self { rel: Rel::ONE, px: Px::ZERO, }; pub const fn new(rel: f32, px: f32) -> Self { Self::from_parts(Rel::from_f32(rel), Px::from_f32(px)) } /// From parts already on the grid, rather than numbers to be put on it. pub const fn from_parts(rel: Rel, px: Px) -> Self { Self { rel, px } } pub const fn rel(rel: f32) -> Self { Self::from_parts(Rel::from_f32(rel), Px::ZERO) } pub const fn px(px: f32) -> Self { Self::from_parts(Rel::ZERO, Px::from_f32(px)) } pub const fn max(&self, other: Self) -> Self { Self { rel: self.rel.max(other.rel), px: self.px.max(other.px), } } pub const fn min(&self, other: Self) -> Self { Self { rel: self.rel.min(other.rel), px: self.px.min(other.px), } } /// Both parts by the same fraction, which is what a part of a length /// means when the length is part pixels and part a fraction of a box. pub const fn scale(&self, by: Rel) -> Self { Self { rel: self.rel.mul(by), px: self.px.mul(by), } } pub const fn offset(mut self, amt: Px) -> Self { self.px = self.px.add(amt); self } pub const fn within(&self, span: &UiSpan) -> Self { Self { rel: self.rel.lerp(span.start.rel, span.end.rel), px: self.px.add(self.rel.lerp(span.start.px, span.end.px)), } } pub const fn within_len(&self, len: Len) -> Self { self.within(&UiSpan { start: Len::ZERO, end: len, }) } pub const fn flip(&mut self) { self.rel = Rel::ONE.sub(self.rel); self.px = self.px.neg(); } pub const fn to(&self, end: Self) -> UiSpan { UiSpan { start: *self, end } } /// Resolved against a box of `len`, which is the only place a fraction /// becomes a number of pixels. pub const fn to_px(&self, len: Px) -> Px { self.px.add(len.mul(self.rel)) } } #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] pub struct UiSpan { pub start: Len, pub end: Len, } impl UiSpan { pub const FULL: Self = Self { start: Len::ZERO, end: Len::FULL, }; pub const fn rel(rel: f32) -> Self { Self { start: Len::rel(rel), end: Len::rel(rel), } } pub const fn new(start: Len, end: Len) -> Self { Self { start, end } } pub const fn flip(&mut self) { self.start.flip(); self.end.flip(); std::mem::swap(&mut self.start.rel, &mut self.end.rel); std::mem::swap(&mut self.start.px, &mut self.end.px); } pub const fn shift(&mut self, offset: Len) { self.start += offset; self.end += offset; } /// Composing a box through the one it sits in, and the hottest line in /// layout. It used to skip the multiplies where a span was the whole of /// its parent or the parent the whole of its own; both come out of the /// multiply unchanged anyway, and the body those comparisons cost was /// what kept the inliner from taking this at all. pub const fn within(&self, parent: &Self) -> Self { Self { start: self.start.within(parent), end: self.end.within(parent), } } /// A box `len` long inside this one, on the side `align` says. Both must /// be lengths of the same rel base: it subtracts one from the other /// rather than composing it in, which is what keeps a fraction the same /// fraction however long this box turns out to be. pub const fn place(self, len: Len, align: AxisAlign) -> Self { let start = self.start + (self.len() - len).scale(align.rel()); Self::new(start, start + len) } pub const fn len(&self) -> Len { self.end - self.start } } #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] pub struct UiRegion { pub x: UiSpan, pub y: UiSpan, } impl UiRegion { pub const FULL: Self = Self { x: UiSpan::FULL, y: UiSpan::FULL, }; pub const fn new(x: UiSpan, y: UiSpan) -> Self { Self { x, y } } pub const fn rel(rel: Vec2) -> Self { Self { x: UiSpan::rel(rel.x), y: UiSpan::rel(rel.y), } } pub const fn within(&self, parent: &Self) -> Self { Self { x: self.x.within(&parent.x), y: self.y.within(&parent.y), } } pub const fn flip(&mut self, axis: Axis) { match axis { Axis::X => self.x.flip(), Axis::Y => self.y.flip(), } } pub fn shift(&mut self, offset: impl Into) { let offset = offset.into(); self.x.shift(offset.x); self.y.shift(offset.y); } pub fn offset(mut self, offset: impl Into) -> Self { self.shift(offset); self } pub fn to_px(&self, size: PxVec2) -> PixelRegion { PixelRegion { top_left: self.top_left().to_px(size), bot_right: self.bot_right().to_px(size), } } pub const fn center(&self) -> UiVec2 { Align::CENTER.pos().within(self) } pub const fn size(&self) -> UiVec2 { UiVec2 { x: self.x.len(), y: self.y.len(), } } pub const fn top_left(&self) -> UiVec2 { UiVec2 { x: self.x.start, y: self.y.start, } } pub const fn bot_right(&self) -> UiVec2 { UiVec2 { x: self.x.end, y: self.y.end, } } pub const fn from_axis(axis: Axis, aligned: UiSpan, ortho: UiSpan) -> Self { Self { x: match axis { Axis::X => aligned, Axis::Y => ortho, }, y: match axis { Axis::X => ortho, Axis::Y => aligned, }, } } } impl Display for UiRegion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{} -> {} (size: {})", self.top_left(), self.bot_right(), self.size() ) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PixelRegion { pub top_left: PxVec2, pub bot_right: PxVec2, } impl PixelRegion { pub fn contains(&self, pos: PxVec2) -> 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 fn size(&self) -> PxVec2 { self.bot_right - self.top_left } } impl Display for PixelRegion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} -> {}", self.top_left, self.bot_right) } } impl_axis_index!(UiVec2 => Len); impl_axis_index!(UiRegion => UiSpan);