Files
iris/core/src/orientation/pos.rs
T
iris-aiandClaude Opus 5 b7b8d09e40 Write a shared constant once, and stop a scroll placing its own content
Two findings from a sweep over the WGSL prelude and the position widgets,
scoped against upstream/main at ca2b4b2.

`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 to
1096c31, 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>
2026-09-20 02:09:41 -04:00

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(&region.x),
y: self.y.within(&region.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);