A widget's region is now held in the coordinates of the slot it draws in rather than the window's, and `Painter::place` is how a container asks for a slot: it draws a child it decides the box of and may decide again. Everything under that slot is a fraction of its box, so placing the child a second time is one entry to write whether it moved or changed length. A child drawn any other way has no slot and shares its nearest ancestor's. That is what keeps the chain short. `chain_cost` measured depth as the cost -- free to 8, +42.6% at 16 -- and a slot per widget put a transcript's glyphs past that for nothing, since almost every slot was zero. `Span`, `Aligned` and `Scroll` are the containers that re-place a child after drawing it, and `tests/layout.rs` pins that four widgets between a span and a leaf leave the leaf's chain one deep. `UiRegion::stretch`, `UiRegion::stretchable` and `UiScalar::stretch` are gone. Nothing is inverted any more: a box that changed length is written to its slot, and the descendants recompose against it in the shader. That also retires the case the guard existed for, where a fixed length has no fraction to recover -- `tests/layout.rs` now stretches a 40-tall row on its other axis, which `stretchable` refused outright. What still walks the CPU is deciding who must draw again, which no chain can answer: `mark_resized` descends from the widget whose box changed and marks anything whose own box changed length and whose drawing reads it. A part of a box with no relative extent on an axis is a fixed length, and composing into it leaves none either, so the walk stops where a length did not change -- an 80-wide child in a widened row is not redrawn though it says `Redraw`. `Span`, `Pad`, `Stack`, `Offset`, `Aligned`, `SetSize` and `LayerOffset` say `Scale`: each places in fractions and offsets of its own box and none reads the box's pixel length. `Scroll` and `MaxSize` do read pixels and stay `Redraw`. 45 tests pass, five of them new. Render verification comes after the CPU side, per the owner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
2.2 KiB
Rust
89 lines
2.2 KiB
Rust
use crate::{UiRegion, util::Id, util::Vec2};
|
|
use wgpu::*;
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
|
pub struct WindowUniform {
|
|
pub dim: Vec2,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
pub struct PrimitiveInstance {
|
|
pub region: UiRegion,
|
|
pub mask_idx: MaskIdx,
|
|
pub move_idx: MoveIdx,
|
|
}
|
|
|
|
impl PrimitiveInstance {
|
|
const ATTRIBS: [VertexAttribute; 6] = vertex_attr_array![
|
|
0 => Float32x2,
|
|
1 => Float32x2,
|
|
2 => Float32x2,
|
|
3 => Float32x2,
|
|
4 => Uint32,
|
|
5 => Uint32,
|
|
];
|
|
|
|
pub fn desc() -> VertexBufferLayout<'static> {
|
|
VertexBufferLayout {
|
|
array_stride: std::mem::size_of::<Self>() as BufferAddress,
|
|
step_mode: VertexStepMode::Instance,
|
|
attributes: &Self::ATTRIBS,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type MaskIdx = Id<u32>;
|
|
|
|
impl MaskIdx {
|
|
pub const NONE: Self = Self::preset(u32::MAX);
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
pub struct Mask {
|
|
pub region: UiRegion,
|
|
pub move_idx: MoveIdx,
|
|
}
|
|
|
|
/// Its own type rather than another `Id<u32>`, because it sits beside
|
|
/// `MaskIdx` in an instance and the two must not be swappable.
|
|
#[repr(transparent)]
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable)]
|
|
pub struct MoveIdx(u32);
|
|
|
|
impl MoveIdx {
|
|
pub const NONE: Self = Self(u32::MAX);
|
|
|
|
pub(crate) fn slot(idx: usize) -> Self {
|
|
Self(idx as u32)
|
|
}
|
|
|
|
pub(crate) fn idx(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
}
|
|
|
|
/// One link of the chain a primitive's position is resolved through: the box
|
|
/// its contents are placed within, given in the coordinates of the slot it
|
|
/// names. Moving or resizing a subtree writes its own slot and nothing else.
|
|
///
|
|
/// The identity is `UiRegion::FULL`, not zero: a zeroed entry is a box of no
|
|
/// extent, which collapses everything under it to a point.
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct MoveOffset {
|
|
pub region: UiRegion,
|
|
pub parent: MoveIdx,
|
|
}
|
|
|
|
unsafe impl bytemuck::Pod for MoveOffset {}
|
|
unsafe impl bytemuck::Zeroable for MoveOffset {}
|
|
|
|
impl MoveOffset {
|
|
pub fn new(parent: MoveIdx, region: UiRegion) -> Self {
|
|
Self { region, parent }
|
|
}
|
|
}
|