Two findings from a sweep over the WGSL prelude and the position widgets, scoped against upstream/main atca2b4b2. `module_source` already builds each shader's preamble from iris_core's own constants, so the move-chain work's second copy of `MOVE_NONE` and `CHAIN_LIMIT` -- under "keep in step with iris_core::CHAIN_LIMIT" -- asked a reader by hand for what the mechanism beside it exists to do. Both are injected now, with `MASK_NONE` beside them replacing a bare literal, and the shader declares none of them. `Scroll`'s `content_len` is never less than its box, so `slack` and the `anchor` computed from it were always zero whatever the alignment: the framework centres short content by placing the answer in the whole box, and the comment credited arithmetic that could not have done it. The same belief guarded the fits-in-the-box contract with `align == NEG`, so at the default alignment -- the middle -- every box change redrew the scroll, measured as 1 widget against 0 at TOP_LEFT. `align` now has no reader at all. `UiSpan::translated` and `UiRegion::translated` are reachable only from each other and from nothing else. Format, clippy with and without layout-diagnostics, and the 131-test suite are clean. The cold dump over 400 depth-5 trees is byte-identical to1096c31, and all three seed scans pass: 400 at depth 5 in 69.07s, 1,000 at depth 6 in 169.29s, 2,000 at depth 4 in 300.75s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
418 lines
11 KiB
Rust
418 lines
11 KiB
Rust
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<Vec2>) -> 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<Vec2>) -> 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<UiVec2>) {
|
|
let offset = offset.into();
|
|
*self += offset;
|
|
}
|
|
|
|
pub const fn offset(mut self, offset: impl const Into<UiVec2>) -> 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<Vec2> for UiVec2 {
|
|
fn from(px: Vec2) -> Self {
|
|
Self::px(px)
|
|
}
|
|
}
|
|
|
|
const impl<T: const UiNum, U: const UiNum> 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<UiVec2>) {
|
|
let offset = offset.into();
|
|
self.x.shift(offset.x);
|
|
self.y.shift(offset.y);
|
|
}
|
|
|
|
pub fn offset(mut self, offset: impl Into<UiVec2>) -> 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);
|