The plumbing for O(1) subtree movement (LAYOUT.md §2), with every slot still at zero, so this changes no pixels and the next commit can change behaviour against a known-good picture. Every active widget owns a slot in `UiData::moves`: a translation in physical pixels and the slot it is relative to. A primitive instance and a mask each name one, and `prelude.wgsl` walks the chain and adds the accumulated delta. A mask resolves its own chain rather than the drawn primitive's, so a stationary viewport can clip content that moves inside it. `CHAIN_LIMIT` is stated on both sides; it bounds a malformed cycle rather than any real tree. A slot outlives any one `ActiveData`, because a redraw replaces that while the widget's children go on pointing at the slot, so it lives in `UiRenderState::moves` keyed by widget and is retired when the widget stops being drawn. `MoveIdx` is its own type rather than another `Id<u32>`: it sits beside `MaskIdx` in an instance and the two must not be swappable. `Vec2` is now `repr(align(8))`, which is WGSL's alignment for a `vec2<f32>`, so a GPU struct holding one is laid out the way its shader reads it without saying so itself -- `GlyphPrimitive` no longer states its own alignment, and `MoveOffset` never has to. Both keep a manual `unsafe impl Pod`, since the trailing padding that alignment introduces is what `derive(Pod)` refuses. `WindowUniform` holds the `Vec2` its shader has always called `dim` rather than two loose floats, which was the last place the two sides described the same bytes differently. Checked: fmt, clippy and 40 tests. `tabs` (with the image replay), `view` and `minimal` render byte-identical to `upstream/main`, and `text` is unchanged.
95 lines
2.2 KiB
Rust
95 lines
2.2 KiB
Rust
use crate::util::impl_op;
|
|
use std::{hash::Hash, ops::*};
|
|
|
|
/// `align(8)` because that is WGSL's alignment for a `vec2<f32>`, so any GPU
|
|
/// struct holding one is laid out the way its shader reads it without having
|
|
/// to say so itself. Those structs still need a manual `unsafe impl Pod`,
|
|
/// since the trailing padding this introduces is what `derive(Pod)` refuses.
|
|
#[repr(C, align(8))]
|
|
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
|
pub struct Vec2 {
|
|
pub x: f32,
|
|
pub y: f32,
|
|
}
|
|
|
|
impl Eq for Vec2 {}
|
|
|
|
impl Hash for Vec2 {
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
state.write_u32(self.x.to_bits());
|
|
state.write_u32(self.y.to_bits());
|
|
}
|
|
}
|
|
|
|
impl Vec2 {
|
|
pub const ZERO: Self = Self::new(0.0, 0.0);
|
|
pub const ONE: Self = Self::new(1.0, 1.0);
|
|
|
|
pub const fn new(x: f32, y: f32) -> Self {
|
|
Self { x, y }
|
|
}
|
|
|
|
pub const fn round(self) -> Self {
|
|
Self {
|
|
x: self.x.round(),
|
|
y: self.y.round(),
|
|
}
|
|
}
|
|
|
|
pub const fn floor(self) -> Self {
|
|
Self {
|
|
x: self.x.floor(),
|
|
y: self.y.floor(),
|
|
}
|
|
}
|
|
|
|
pub const fn ceil(self) -> Self {
|
|
Self {
|
|
x: self.x.ceil(),
|
|
y: self.y.ceil(),
|
|
}
|
|
}
|
|
|
|
pub const fn tuple(&self) -> (f32, f32) {
|
|
(self.x, self.y)
|
|
}
|
|
|
|
pub const fn with_x(mut self, x: f32) -> Self {
|
|
self.x = x;
|
|
self
|
|
}
|
|
|
|
pub const fn with_y(mut self, y: f32) -> Self {
|
|
self.y = y;
|
|
self
|
|
}
|
|
}
|
|
|
|
// this version looks kinda cool... is it more readable? more annoying to copy and change though
|
|
impl_op!(impl Add for Vec2: add x y);
|
|
impl_op!(Vec2 Sub sub; x y);
|
|
impl_op!(Vec2 Mul mul; x y);
|
|
impl_op!(Vec2 Div div; x y);
|
|
|
|
impl Neg for Vec2 {
|
|
type Output = Self;
|
|
|
|
fn neg(mut self) -> Self::Output {
|
|
self.x = -self.x;
|
|
self.y = -self.y;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Vec2 {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "({}, {})", self.x, self.y)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Vec2 {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "({}, {})", self.x, self.y)
|
|
}
|
|
}
|