Files
ai-app/docs/bench/parked-ortho-and-asserts.patch
T

1143 lines
38 KiB
Diff

diff --git a/core/src/orientation/align.rs b/core/src/orientation/align.rs
index 876208a..ff79dbf 100644
--- a/core/src/orientation/align.rs
+++ b/core/src/orientation/align.rs
@@ -144,10 +144,10 @@ impl UiScalar {
pub const fn align(&self, align: AxisAlign) -> UiSpan {
let rel = align.rel();
let mut start = UiScalar::rel(rel);
- start.abs -= self.abs * rel;
+ start.px -= self.px * rel;
start.rel -= self.rel * rel;
let mut end = UiScalar::rel(rel);
- end.abs += self.abs * (1.0 - rel);
+ end.px += self.px * (1.0 - rel);
end.rel += self.rel * (1.0 - rel);
UiSpan { start, end }
}
diff --git a/core/src/orientation/len.rs b/core/src/orientation/len.rs
index 8725211..5afeb81 100644
--- a/core/src/orientation/len.rs
+++ b/core/src/orientation/len.rs
@@ -9,14 +9,14 @@ pub struct Size {
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Len {
- pub abs: f32,
+ pub px: f32,
pub rel: f32,
pub rest: f32,
}
impl<N: UiNum> From<N> for Len {
fn from(value: N) -> Self {
- Len::abs(value.to_f32())
+ Len::px(value.to_f32())
}
}
@@ -46,10 +46,10 @@ impl Size {
y: Len::REST,
};
- pub fn abs(v: Vec2) -> Self {
+ pub fn px(v: Vec2) -> Self {
Self {
- x: Len::abs(v.x),
- y: Len::abs(v.y),
+ x: Len::px(v.x),
+ y: Len::px(v.y),
}
}
@@ -97,13 +97,13 @@ impl Size {
impl Len {
pub const ZERO: Self = Self {
- abs: 0.0,
+ px: 0.0,
rel: 0.0,
rest: 0.0,
};
pub const REST: Self = Self {
- abs: 0.0,
+ px: 0.0,
rel: 0.0,
rest: 1.0,
};
@@ -111,27 +111,27 @@ impl Len {
pub fn apply_rest(&self) -> UiScalar {
UiScalar {
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
- abs: self.abs,
+ px: self.px,
}
}
- pub fn abs(abs: impl UiNum) -> Self {
+ pub fn px(px: impl UiNum) -> Self {
Self {
- abs: abs.to_f32(),
+ px: px.to_f32(),
rel: 0.0,
rest: 0.0,
}
}
pub fn rel(rel: impl UiNum) -> Self {
Self {
- abs: 0.0,
+ px: 0.0,
rel: rel.to_f32(),
rest: 0.0,
}
}
pub fn rest(ratio: impl UiNum) -> Self {
Self {
- abs: 0.0,
+ px: 0.0,
rel: 0.0,
rest: ratio.to_f32(),
}
@@ -141,31 +141,31 @@ impl Len {
pub mod len_fns {
use super::*;
- pub fn abs(abs: impl UiNum) -> Len {
+ pub fn px(px: impl UiNum) -> Len {
Len {
- abs: abs.to_f32(),
+ px: px.to_f32(),
rel: 0.0,
rest: 0.0,
}
}
pub fn rel(rel: impl UiNum) -> Len {
Len {
- abs: 0.0,
+ px: 0.0,
rel: rel.to_f32(),
rest: 0.0,
}
}
pub fn rest(ratio: impl UiNum) -> Len {
Len {
- abs: 0.0,
+ px: 0.0,
rel: 0.0,
rest: ratio.to_f32(),
}
}
}
-impl_op!(Len Add add; abs rel rest);
-impl_op!(Len Sub sub; abs rel rest);
+impl_op!(Len Add add; px rel rest);
+impl_op!(Len Sub sub; px rel rest);
impl_op!(Size Add add; x y);
impl_op!(Size Sub sub; x y);
@@ -184,8 +184,8 @@ impl std::fmt::Display for Size {
impl std::fmt::Display for Len {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if self.abs != 0.0 {
- write!(f, "{} abs;", self.abs)?;
+ if self.px != 0.0 {
+ write!(f, "{} px;", self.px)?;
}
if self.rel != 0.0 {
write!(f, "{} rel;", self.rel)?;
diff --git a/core/src/orientation/pos.rs b/core/src/orientation/pos.rs
index d2fc8f4..6a31276 100644
--- a/core/src/orientation/pos.rs
+++ b/core/src/orientation/pos.rs
@@ -23,11 +23,11 @@ impl UiVec2 {
Self { x, y }
}
- pub const fn abs(abs: impl const Into<Vec2>) -> Self {
- let abs = abs.into();
+ pub const fn px(px: impl const Into<Vec2>) -> Self {
+ let px = px.into();
Self {
- x: UiScalar::abs(abs.x),
- y: UiScalar::abs(abs.y),
+ x: UiScalar::px(px.x),
+ y: UiScalar::px(px.y),
}
}
@@ -70,10 +70,10 @@ impl UiVec2 {
}
}
- pub fn to_abs(&self, rel: Vec2) -> Vec2 {
+ pub fn to_px(&self, rel: Vec2) -> Vec2 {
Vec2 {
- x: self.x.to_abs(rel.x),
- y: self.y.to_abs(rel.y),
+ x: self.x.to_px(rel.x),
+ y: self.y.to_px(rel.y),
}
}
@@ -92,8 +92,8 @@ impl UiVec2 {
}
}
- pub fn get_abs(&self) -> Vec2 {
- (self.x.abs, self.y.abs).into()
+ pub fn get_px(&self) -> Vec2 {
+ (self.x.px, self.y.px).into()
}
pub fn get_rel(&self) -> Vec2 {
@@ -102,15 +102,15 @@ impl UiVec2 {
pub fn abs_mut(&mut self) -> Vec2View<'_> {
Vec2View {
- x: &mut self.x.abs,
- y: &mut self.y.abs,
+ x: &mut self.x.px,
+ y: &mut self.y.px,
}
}
}
impl Display for UiVec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "rel{};abs{}", self.get_rel(), self.get_abs())
+ write!(f, "rel{};px{}", self.get_rel(), self.get_px())
}
}
@@ -118,8 +118,8 @@ impl_op!(UiVec2 Add add; x y);
impl_op!(UiVec2 Sub sub; x y);
const impl From<Vec2> for UiVec2 {
- fn from(abs: Vec2) -> Self {
- Self::abs(abs)
+ fn from(px: Vec2) -> Self {
+ Self::px(px)
}
}
@@ -127,8 +127,8 @@ const impl<T: const UiNum, U: const UiNum> From<(T, U)> for UiVec2
where
(T, U): const Destruct,
{
- fn from(abs: (T, U)) -> Self {
- Self::abs(abs)
+ fn from(px: (T, U)) -> Self {
+ Self::px(px)
}
}
@@ -136,34 +136,34 @@ where
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, Default, bytemuck::Zeroable)]
pub struct UiScalar {
pub rel: f32,
- pub abs: f32,
+ pub px: f32,
}
impl Eq for UiScalar {}
impl Hash for UiScalar {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u32(self.rel.to_bits());
- state.write_u32(self.abs.to_bits());
+ state.write_u32(self.px.to_bits());
}
}
-impl_op!(UiScalar Add add; rel abs);
-impl_op!(UiScalar Sub sub; rel abs);
+impl_op!(UiScalar Add add; rel px);
+impl_op!(UiScalar Sub sub; rel px);
impl UiScalar {
- pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 };
- pub const FULL: Self = Self { rel: 1.0, abs: 0.0 };
+ pub const ZERO: Self = Self { rel: 0.0, px: 0.0 };
+ pub const FULL: Self = Self { rel: 1.0, px: 0.0 };
- pub const fn new(rel: f32, abs: f32) -> Self {
- Self { rel, abs }
+ pub const fn new(rel: f32, px: f32) -> Self {
+ Self { rel, px }
}
pub const fn rel(rel: f32) -> Self {
- Self { rel, abs: 0.0 }
+ Self { rel, px: 0.0 }
}
- pub const fn abs(abs: f32) -> Self {
- Self { rel: 0.0, abs }
+ pub const fn px(px: f32) -> Self {
+ Self { rel: 0.0, px }
}
pub const fn rel_min() -> Self {
@@ -177,28 +177,28 @@ impl UiScalar {
pub const fn max(&self, other: Self) -> Self {
Self {
rel: self.rel.max(other.rel),
- abs: self.abs.max(other.abs),
+ px: self.px.max(other.px),
}
}
pub const fn min(&self, other: Self) -> Self {
Self {
rel: self.rel.min(other.rel),
- abs: self.abs.min(other.abs),
+ px: self.px.min(other.px),
}
}
pub const fn offset(mut self, amt: f32) -> Self {
- self.abs += amt;
+ self.px += amt;
self
}
pub const fn within(&self, span: &UiSpan) -> Self {
let anchor = self.rel.lerp(span.start.rel, span.end.rel);
- let offset = self.abs + self.rel.lerp(span.start.abs, span.end.abs);
+ let offset = self.px + self.rel.lerp(span.start.px, span.end.px);
Self {
rel: anchor,
- abs: offset,
+ px: offset,
}
}
@@ -215,15 +215,15 @@ impl UiScalar {
pub const fn flip(&mut self) {
self.rel = 1.0 - self.rel;
- self.abs = -self.abs;
+ self.px = -self.px;
}
pub const fn to(&self, end: Self) -> UiSpan {
UiSpan { start: *self, end }
}
- pub const fn to_abs(&self, rel: f32) -> f32 {
- self.rel * rel + self.abs
+ pub const fn to_px(&self, rel: f32) -> f32 {
+ self.rel * rel + self.px
}
}
@@ -255,7 +255,7 @@ impl UiSpan {
self.start.flip();
self.end.flip();
std::mem::swap(&mut self.start.rel, &mut self.end.rel);
- std::mem::swap(&mut self.start.abs, &mut self.end.abs);
+ std::mem::swap(&mut self.start.px, &mut self.end.px);
}
pub const fn shift(&mut self, offset: UiScalar) {
@@ -338,8 +338,8 @@ impl UiRegion {
pub fn to_px(&self, size: Vec2) -> PixelRegion {
PixelRegion {
- top_left: self.top_left().get_rel() * size + self.top_left().get_abs(),
- bot_right: self.bot_right().get_rel() * size + self.bot_right().get_abs(),
+ top_left: self.top_left().get_rel() * size + self.top_left().get_px(),
+ bot_right: self.bot_right().get_rel() * size + self.bot_right().get_px(),
}
}
diff --git a/core/src/render/shader/prelude.wgsl b/core/src/render/shader/prelude.wgsl
index 42bd824..2b98a50 100644
--- a/core/src/render/shader/prelude.wgsl
+++ b/core/src/render/shader/prelude.wgsl
@@ -40,7 +40,7 @@ const CHAIN_LIMIT: u32 = 64u;
fn scalar_within(s: UiScalar, p: UiSpan) -> UiScalar {
return UiScalar(
mix(p.start.rel, p.end.rel, s.rel),
- s.abs + mix(p.start.abs, p.end.abs, s.rel),
+ s.px + mix(p.start.px, p.end.px, s.rel),
);
}
@@ -69,7 +69,7 @@ struct UiSpan {
struct UiScalar {
rel: f32,
- abs: f32,
+ px: f32,
}
struct InstanceInput {
@@ -104,12 +104,12 @@ fn vs_main(
);
let r = resolve_move(in.move_idx, local);
let top_left_rel = vec2(r.x.start.rel, r.y.start.rel);
- let top_left_abs = vec2(r.x.start.abs, r.y.start.abs);
+ let top_left_px = vec2(r.x.start.px, r.y.start.px);
let bot_right_rel = vec2(r.x.end.rel, r.y.end.rel);
- let bot_right_abs = vec2(r.x.end.abs, r.y.end.abs);
+ let bot_right_px = vec2(r.x.end.px, r.y.end.px);
- let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
- let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
+ let top_left = floor(top_left_rel * window.dim) + floor(top_left_px);
+ let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_px);
let size = bot_right - top_left;
let uv = vec2<f32>(
@@ -136,12 +136,12 @@ fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
// clips content that moves inside it.
let m = resolve_move(mask.move_idx, Region(mask.x, mask.y));
let tl = vec2(m.x.start.rel, m.y.start.rel);
- let tl_abs = vec2(m.x.start.abs, m.y.start.abs);
+ let tl_px = vec2(m.x.start.px, m.y.start.px);
let br = vec2(m.x.end.rel, m.y.end.rel);
- let br_abs = vec2(m.x.end.abs, m.y.end.abs);
+ let br_px = vec2(m.x.end.px, m.y.end.px);
- let top_left = floor(tl * window.dim) + floor(tl_abs);
- let bot_right = floor(br * window.dim) + floor(br_abs);
+ let top_left = floor(tl * window.dim) + floor(tl_px);
+ let bot_right = floor(br * window.dim) + floor(br_px);
let pos = in.clip_position.xy;
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
return color * 0.0;
diff --git a/core/src/render/shader/rect.wgsl b/core/src/render/shader/rect.wgsl
index 6d8694d..5a01564 100644
--- a/core/src/render/shader/rect.wgsl
+++ b/core/src/render/shader/rect.wgsl
@@ -34,6 +34,6 @@ fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner:
// vec from center to pixel
let p = pixel_pos - rect_center;
// vec from inner rect corner to pixel
- let q = abs(p) - (rect_corner - radius);
+ let q = px(p) - (rect_corner - radius);
return length(max(q, vec2(0.0))) - radius;
}
diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs
index 5a403ff..db3ad0e 100644
--- a/core/src/ui/painter.rs
+++ b/core/src/ui/painter.rs
@@ -260,9 +260,9 @@ impl<'a> Painter<'a> {
let mut region = origin;
region.x.end = region.x.start;
region.y.end = region.y.start;
- let mut region = region.offset(UiVec2::abs(glyph.offset));
- region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
- region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
+ let mut region = region.offset(UiVec2::px(glyph.offset));
+ region.x.end = region.x.start + UiScalar::px(glyph.entry.width as f32);
+ region.y.end = region.y.start + UiScalar::px(glyph.entry.height as f32);
self.write(
kind,
GlyphPrimitive {
@@ -306,7 +306,7 @@ impl<'a> Painter<'a> {
self.reads_output = [true; 2];
self.size_box_inputs = [true; 2];
let region = self.state.moves.resolve(self.move_idx, self.region);
- region.size().to_abs(self.state.output_size)
+ region.size().to_px(self.state.output_size)
}
/// One axis of this widget's box in pixels. Prefer this to
@@ -318,7 +318,7 @@ impl<'a> Painter<'a> {
region
.size()
.axis(axis)
- .to_abs(self.state.output_size.axis(axis))
+ .to_px(self.state.output_size.axis(axis))
}
pub fn text_data(&mut self) -> &mut TextData {
diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs
index 1dc40b7..12c1e61 100644
--- a/core/src/ui/render_state.rs
+++ b/core/src/ui/render_state.rs
@@ -325,7 +325,7 @@ impl UiRenderState {
self.moves
.resolve(slot, region)
.size()
- .to_abs(self.output_size)
+ .to_px(self.output_size)
}
/// A clean widget's retained size, when the offered pixel axes which
@@ -435,6 +435,11 @@ impl UiRenderState {
diag::bump(Counter::ReuseExact);
diag::reuse(id, ReuseOutcome::Exact);
}
+ debug_assert!(
+ !self.dirty_size_under(id, rsc.widgets()),
+ "reused a retained size with a dirty widget under it; \
+ the settle order in `redraw_updates` is what prevents this"
+ );
self.keep_depth(id, depth);
return Some(size);
}
@@ -475,6 +480,11 @@ impl UiRenderState {
return None;
}
}
+ debug_assert!(
+ !self.dirty_size_under(id, rsc.widgets()),
+ "reused a retained size with a dirty widget under it; \
+ the settle order in `redraw_updates` is what prevents this"
+ );
self.moves.set(slot, region);
self.keep_depth(id, depth);
let active = self.active.get_mut(&id).unwrap();
diff --git a/src/random.rs b/src/random.rs
index 921b1e8..4204d1d 100644
--- a/src/random.rs
+++ b/src/random.rs
@@ -142,7 +142,7 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
fn len(&mut self) -> Option<Len> {
match self.rng.below(4) {
- 0 => Some(Len::abs(20.0 + self.rng.below(180) as f32)),
+ 0 => Some(Len::px(20.0 + self.rng.below(180) as f32)),
1 => Some(Len::REST),
_ => None,
}
@@ -258,10 +258,17 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
let attach = edit.attach.min(spares.len());
children.extend(spares.drain(..attach));
let dir = [Dir::RIGHT, Dir::DOWN, Dir::LEFT, Dir::UP][self.rng.below(4)];
+ // Both ways of sizing across the axis, since which one a span uses
+ // decides whether it reads its own pixel length.
+ let ortho = match self.rng.chance() {
+ true => OrthoSize::Children,
+ false => OrthoSize::Fill,
+ };
let id = Span {
children,
dir,
gap: self.rng.below(3) as f32 * 4.0,
+ ortho,
}
.add(self.rsc);
self.tree.ids.push(id.id());
diff --git a/src/widget/image.rs b/src/widget/image.rs
index cc7d1bc..7491167 100644
--- a/src/widget/image.rs
+++ b/src/widget/image.rs
@@ -8,11 +8,11 @@ pub struct Image {
impl Widget for Image {
fn draw(&mut self, painter: &mut Painter) -> Size {
painter.primitive(&self.handle);
- Size::abs(self.handle.size())
+ Size::px(self.handle.size())
}
fn size_hint(&self, axis: Axis) -> Option<Len> {
- Some(Len::abs(self.handle.size().axis(axis)))
+ Some(Len::px(self.handle.size().axis(axis)))
}
fn on_resize(&self, _: Axis) -> OnResize {
diff --git a/src/widget/position/max_size.rs b/src/widget/position/max_size.rs
index 714e534..a73171c 100644
--- a/src/widget/position/max_size.rs
+++ b/src/widget/position/max_size.rs
@@ -19,7 +19,7 @@ impl Widget for MaxSize {
fn capped(len: Len, max: Option<Len>, output: f32) -> Len {
match max {
- Some(max) if len.apply_rest().to_abs(output) > max.apply_rest().to_abs(output) => max,
+ Some(max) if len.apply_rest().to_px(output) > max.apply_rest().to_px(output) => max,
_ => len,
}
}
diff --git a/src/widget/position/pad.rs b/src/widget/position/pad.rs
index 6894421..e7cf5d9 100644
--- a/src/widget/position/pad.rs
+++ b/src/widget/position/pad.rs
@@ -12,11 +12,11 @@ impl Widget for Pad {
.size();
Size {
x: Len {
- abs: inner.x.abs + self.padding.left + self.padding.right,
+ px: inner.x.px + self.padding.left + self.padding.right,
..inner.x
},
y: Len {
- abs: inner.y.abs + self.padding.top + self.padding.bottom,
+ px: inner.y.px + self.padding.top + self.padding.bottom,
..inner.y
},
}
@@ -55,10 +55,10 @@ impl Padding {
}
pub fn region(&self) -> UiRegion {
let mut region = UiRegion::FULL;
- region.x.start.abs += self.left;
- region.y.start.abs += self.top;
- region.x.end.abs -= self.right;
- region.y.end.abs -= self.bottom;
+ region.x.start.px += self.left;
+ region.y.start.px += self.top;
+ region.x.end.px -= self.right;
+ region.y.end.px -= self.bottom;
region
}
pub fn x(amt: impl UiNum) -> Self {
diff --git a/src/widget/position/scroll.rs b/src/widget/position/scroll.rs
index cfac561..22c6e71 100644
--- a/src/widget/position/scroll.rs
+++ b/src/widget/position/scroll.rs
@@ -12,7 +12,7 @@ pub struct Scroll {
impl Widget for Scroll {
fn draw(&mut self, painter: &mut Painter) -> Size {
let output_len = painter.output_len(self.axis);
- let container_len = UiScalar::abs(painter.px_len(self.axis));
+ let container_len = UiScalar::px(painter.px_len(self.axis));
// Draw in the whole container only when its scrolling-axis length is
// not already known, then place it at the scrolled offset.
let known_len = painter.known_len(&self.inner, self.axis, UiRegion::FULL);
@@ -22,8 +22,8 @@ impl Widget for Scroll {
.unwrap_or_else(|| child.unwrap().axis(self.axis))
.apply_rest()
.within_len(container_len)
- .to_abs(output_len);
- self.container_len = container_len.to_abs(output_len);
+ .to_px(output_len);
+ self.container_len = container_len.to_px(output_len);
self.content_len = content_len;
if self.snap_end {
diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs
index 7fe29cc..82d5b5b 100644
--- a/src/widget/position/span.rs
+++ b/src/widget/position/span.rs
@@ -1,10 +1,28 @@
use crate::prelude::*;
use std::marker::PhantomData;
+/// What a span reports across its own axis.
+///
+/// Sizing to the children needs their lengths compared, and a length is an
+/// `px`, a `rel` and a `rest` together, so which is largest is only decided
+/// once they are resolved against a box. [`OrthoSize::Children`] therefore
+/// reads the span's own pixel length on that axis, and gives up scaling on a
+/// resize for it.
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub enum OrthoSize {
+ /// Takes whatever it is offered.
+ #[default]
+ Fill,
+ /// Reports its largest child, whole: a child at `rel` 0.5 makes the span
+ /// `rel` 0.5, rather than collapsing to an absolute length.
+ Children,
+}
+
pub struct Span {
pub children: Vec<StrongWidget>,
pub dir: Dir,
pub gap: f32,
+ pub ortho: OrthoSize,
}
impl Widget for Span {
@@ -24,26 +42,26 @@ impl Widget for Span {
Some(len) => len,
None => painter.place(child, region).len(axis),
};
- cursor.abs += len.abs + self.gap;
+ cursor.px += len.px + self.gap;
cursor.rel += len.rel;
lens.push(len);
}
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
- let total = lens.iter().fold(Len::abs(gap), |sum, len| sum + *len);
+ let total = lens.iter().fold(Len::px(gap), |sum, len| sum + *len);
let mut start = UiScalar::rel_min();
- let mut ortho = Len::ZERO;
+ let mut ortho: Option<Len> = None;
for (child, len) in self.children.iter().zip(&lens) {
let mut span = UiSpan::FULL;
span.start = start;
if len.rest > 0.0 {
- let offset = UiScalar::new(total.rel, total.abs);
+ let offset = UiScalar::new(total.rel, total.px);
let rel_end = UiScalar::rel(len.rest / total.rest);
let end = (UiScalar::rel_max() + start) - offset;
start = rel_end.within(&start.to(end));
}
- start.abs += len.abs;
+ start.px += len.px;
start.rel += len.rel;
span.end = start;
let mut region = UiRegion::from_axis(axis, span, UiSpan::FULL);
@@ -51,35 +69,65 @@ impl Widget for Span {
region.flip(axis);
}
let used = painter.place(child, region).size().axis(!axis);
- // TODO: rel shouldn't do this, but no easy way before actually calculating pixels
- if used.rel > 0.0 || used.rest > 0.0 {
- ortho = Len::REST;
- } else if ortho.rest == 0.0 {
- ortho.abs = ortho.abs.max(used.abs);
- }
- start.abs += self.gap;
+ // TODO: a child shorter than the span across its axis is still
+ // drawn in the whole of it, because a span cannot align one
+ // without placing it a second time -- which is a second draw at a
+ // different length, the thing this sizing exists to avoid. Wants
+ // alignment built into placement.
+ ortho = match ortho {
+ Some(ortho) => Some(longer(painter, !axis, ortho, used)),
+ None => Some(used),
+ };
+ start.px += self.gap;
}
let along = match total.rest == 0.0 && total.rel == 0.0 {
true => total,
false => Len::default(),
};
+ let ortho = match self.ortho {
+ OrthoSize::Fill => Len::REST,
+ OrthoSize::Children => ortho.unwrap_or(Len::ZERO),
+ };
Size::from_axis(axis, along, ortho)
}
/// Every child is placed in fractions and offsets of the span's own box,
/// so a longer box holds the same layout and the children follow it.
- fn on_resize(&self, _: Axis) -> OnResize {
- OnResize::Scale
+ /// Across its axis [`OrthoSize::Children`] is the exception: which child
+ /// is longest is resolved in pixels, so a box of a new length can have a
+ /// different answer.
+ fn on_resize(&self, axis: Axis) -> OnResize {
+ match self.ortho == OrthoSize::Children && axis != self.dir.axis {
+ true => OnResize::Redraw,
+ false => OnResize::Scale,
+ }
}
}
+/// The longer of two lengths across an axis, comparing in pixels only when
+/// one of them has a share the other does not, since that is the only case
+/// the components cannot be compared directly.
+fn longer(painter: &mut Painter, axis: Axis, a: Len, b: Len) -> Len {
+ let share = |len: &Len| len.rel != 0.0 || len.rest != 0.0;
+ if !share(&a) && !share(&b) {
+ return if b.px > a.px { b } else { a };
+ }
+ let px = painter.px_len(axis);
+ let resolve = |len: Len| {
+ let scalar = len.apply_rest();
+ scalar.rel * px + scalar.px
+ };
+ if resolve(b) > resolve(a) { b } else { a }
+}
+
impl Span {
pub fn empty(dir: Dir) -> Self {
Self {
children: Vec::new(),
dir,
gap: 0.0,
+ ortho: OrthoSize::Fill,
}
}
@@ -88,6 +136,11 @@ impl Span {
self
}
+ pub fn ortho(mut self, ortho: OrthoSize) -> Self {
+ self.ortho = ortho;
+ self
+ }
+
pub fn push(&mut self, w: StrongWidget) {
self.children.push(w);
}
@@ -101,6 +154,7 @@ pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Ta
pub children: Wa,
pub dir: Dir,
pub gap: f32,
+ pub ortho: OrthoSize,
_pd: PhantomData<(State, Tag)>,
}
@@ -115,6 +169,7 @@ impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait
children: self.children.add(rsc).arr.into_iter().collect(),
dir: self.dir,
gap: self.gap,
+ ortho: self.ortho,
}
}
}
@@ -127,6 +182,7 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
children,
dir,
gap: 0.0,
+ ortho: OrthoSize::Fill,
_pd: PhantomData,
}
}
@@ -135,6 +191,11 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
self.gap = gap.to_f32();
self
}
+
+ pub fn ortho(mut self, ortho: OrthoSize) -> Self {
+ self.ortho = ortho;
+ self
+ }
}
impl std::ops::Deref for Span {
diff --git a/src/widget/text/edit.rs b/src/widget/text/edit.rs
index 5eafe59..a5d82fe 100644
--- a/src/widget/text/edit.rs
+++ b/src/widget/text/edit.rs
@@ -280,7 +280,7 @@ impl<'a> TextEditCtx<'a> {
}
pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool, recent: bool) {
- let pos = pos - self.text.region().top_left().to_abs(size);
+ let pos = pos - self.text.region().top_left().to_px(size);
let prev_sel = self.text.selection;
let prev_hit = self.text.double_hit;
diff --git a/src/widget/text/mod.rs b/src/widget/text/mod.rs
index 9c1b142..6f2bc4c 100644
--- a/src/widget/text/mod.rs
+++ b/src/widget/text/mod.rs
@@ -72,7 +72,7 @@ impl TextView {
let tex = self.render(painter);
let region = tex.size.align(align);
- let size = Size::abs(tex.size);
+ let size = Size::px(tex.size);
let within = region.within(&painter.region());
painter.glyphs(tex, within);
(region, size)
diff --git a/tests/chain_cost.rs b/tests/chain_cost.rs
index fc05aa3..2b36ece 100644
--- a/tests/chain_cost.rs
+++ b/tests/chain_cost.rs
@@ -80,7 +80,7 @@ fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
slot = render.moves.push(slot, UiRegion::FULL);
}
- let px = |v: f32| UiScalar { rel: 0.0, abs: v };
+ let px = |v: f32| UiScalar { rel: 0.0, px: v };
for i in 0..INSTANCES {
let x = (i % (SIZE as usize / 2)) as f32 * 2.0;
let y = (i / (SIZE as usize / 2)) as f32;
diff --git a/tests/generated.rs b/tests/generated.rs
index e572c7d..b3187c9 100644
--- a/tests/generated.rs
+++ b/tests/generated.rs
@@ -16,7 +16,12 @@ use iris::harness::Harness;
use iris::prelude::*;
use iris::random::{Edits, Lens, Rng, SpanEdit, Tree, grow};
-const DEPTH: usize = 4;
+fn depth() -> usize {
+ std::env::var("IRIS_GENERATED_DEPTH")
+ .ok()
+ .and_then(|d| d.parse().ok())
+ .unwrap_or(4)
+}
const SEEDS: [u64; 7] = [1, 2, 3, 5, 8, 13, 98];
const REGION_EPSILON_PX: f32 = 0.05;
@@ -38,7 +43,7 @@ fn same_region(got: Option<PixelRegion>, want: Option<PixelRegion>) -> bool {
}
fn plant(h: &mut Harness, seed: u64, edits: &Edits) -> Tree {
- let (root, tree) = grow(&mut h.rsc, seed, DEPTH, edits);
+ let (root, tree) = grow(&mut h.rsc, seed, depth(), edits);
h.state.root = Some(root);
h.frame();
tree
@@ -46,8 +51,8 @@ fn plant(h: &mut Harness, seed: u64, edits: &Edits) -> Tree {
fn resize_one(h: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Lens {
let lens = [
- Some(Len::abs(20.0 + rng.below(180) as f32)),
- Some(Len::abs(20.0 + rng.below(180) as f32)),
+ Some(Len::px(20.0 + rng.below(180) as f32)),
+ Some(Len::px(20.0 + rng.below(180) as f32)),
];
let sized = &mut h.rsc[tree.sized[idx]];
sized.x = lens[0];
@@ -298,6 +303,39 @@ fn repainted_together(seed: u64) {
assert_same(seed, what, (&warm, &grown), (&cold, &same));
}
+/// A size change and a spread of repaints in the same frame. Alone, each
+/// settles one dependency path at a time; together, a reader escalated to the
+/// top of its chain by a changed box redraws a whole subtree while marks
+/// elsewhere are still outstanding, which is the case the settle order does
+/// not order.
+fn changed_every_size_while_repainting(seed: u64) {
+ let mut warm = Harness::new((900, 1200));
+ let grown = plant(&mut warm, seed, &Edits::default());
+ if grown.sized.is_empty() {
+ return;
+ }
+
+ let mut rng = Rng::new(seed ^ 0xa11);
+ let sizes = edit_every(&mut warm, &grown, &mut rng);
+ for &id in grown.ids.iter().step_by(5) {
+ warm.rsc.widgets_mut().get_dyn_mut(id);
+ }
+ warm.frame();
+
+ let mut cold = Harness::new((900, 1200));
+ let same = plant(
+ &mut cold,
+ seed,
+ &Edits {
+ sizes,
+ ..Default::default()
+ },
+ );
+
+ let what = "every size at once, while repainting";
+ assert_same(seed, what, (&warm, &grown), (&cold, &same));
+}
+
fn resized(seed: u64) {
let mut warm = Harness::new((1920, 1200));
let grown = plant(&mut warm, seed, &Edits::default());
@@ -349,6 +387,13 @@ fn many_widgets_redrawing_at_once_leaves_every_box_where_it_was() {
SEEDS.into_iter().for_each(repainted_together);
}
+#[test]
+fn a_size_change_while_many_widgets_repaint_lands_the_same_way() {
+ SEEDS
+ .into_iter()
+ .for_each(changed_every_size_while_repainting);
+}
+
#[test]
fn a_resize_lands_where_starting_at_that_size_would() {
SEEDS.into_iter().for_each(resized);
@@ -394,11 +439,17 @@ fn a_long_run_of_seeds_agrees() {
.ok()
.and_then(|seed| seed.parse().ok())
.map(|seed| seed..=seed)
- .unwrap_or(1..=100);
+ .unwrap_or_else(|| {
+ 1..=std::env::var("IRIS_GENERATED_SEEDS")
+ .ok()
+ .and_then(|n| n.parse().ok())
+ .unwrap_or(100)
+ });
for seed in seeds {
changed_size(seed);
changed_every_size(seed);
repainted_together(seed);
+ changed_every_size_while_repainting(seed);
resized(seed);
resized_then_changed(seed);
for shuffle in SHUFFLES {
diff --git a/tests/layout.rs b/tests/layout.rs
index 8ae1a10..569e9d3 100644
--- a/tests/layout.rs
+++ b/tests/layout.rs
@@ -54,7 +54,7 @@ fn a_child_drawn_twice_moves_once() {
h.set_root((left, centered).span(Dir::RIGHT));
assert_corners!(h, inner, (100, 0), (300, 200));
- h.rsc[left].x = Some(Len::abs(150));
+ h.rsc[left].x = Some(Len::px(150));
h.frame();
assert_corners!(h, inner, (150, 0), (350, 200));
@@ -104,7 +104,7 @@ fn a_fixed_box_is_drawn_again_rather_than_stretched() {
h.set_root(stack.align(Align::TOP));
assert_corners!(h, panel, (0, 0), (400, 100));
- h.rsc[leaf].y = Some(Len::abs(250));
+ h.rsc[leaf].y = Some(Len::px(250));
h.frame();
assert_corners!(h, panel, (0, 0), (400, 250));
@@ -119,7 +119,7 @@ fn a_moved_subtree_takes_its_children_with_it() {
h.set_root((first, row).span(Dir::DOWN));
assert_corners!(h, inner, (10, 50), (390, 70));
- h.rsc[first].y = Some(Len::abs(80));
+ h.rsc[first].y = Some(Len::px(80));
h.frame();
// The row is the same shape somewhere else, so one slot moved it and
@@ -140,7 +140,7 @@ fn a_fixed_length_child_keeps_it_when_the_box_around_it_grows() {
assert_corners!(h, fixed, (100, 0), (150, 200));
assert_corners!(h, rest, (150, 0), (400, 200));
- h.rsc[bar].x = Some(Len::abs(200));
+ h.rsc[bar].x = Some(Len::px(200));
h.frame();
// The panel's box is 100 shorter, so the fixed child is the same 50 wide
@@ -163,7 +163,7 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
h.set_root((bar, column).span(Dir::RIGHT));
assert_corners!(h, inner, (110, 10), (390, 30));
- h.rsc[bar].x = Some(Len::abs(200));
+ h.rsc[bar].x = Some(Len::px(200));
h.frame();
assert_corners!(h, inner, (210, 10), (390, 30));
diff --git a/tests/layout_diagnostics.rs b/tests/layout_diagnostics.rs
index 9240324..77942a8 100644
--- a/tests/layout_diagnostics.rs
+++ b/tests/layout_diagnostics.rs
@@ -216,7 +216,7 @@ fn layout_cost() {
trace_selected(&tree);
let sized = tree.sized[0];
run("size", frames, &mut harness, move |harness, frame| {
- harness.rsc[sized].x = Some(Len::abs(100.0 + (frame % 2) as f32 * 40.0));
+ harness.rsc[sized].x = Some(Len::px(100.0 + (frame % 2) as f32 * 40.0));
});
}
diff --git a/tests/replace_cost.rs b/tests/replace_cost.rs
index e0dd964..c92ce02 100644
--- a/tests/replace_cost.rs
+++ b/tests/replace_cost.rs
@@ -37,7 +37,7 @@ fn replacing_rows_every_frame() {
}
h.set_root(span);
for i in 0..FRAMES {
- h.rsc[first].y = Some(Len::abs(40.0 + (i % 2) as f32));
+ h.rsc[first].y = Some(Len::px(40.0 + (i % 2) as f32));
h.frame();
}
}
diff --git a/tests/retained.rs b/tests/retained.rs
index ac4437f..816a62b 100644
--- a/tests/retained.rs
+++ b/tests/retained.rs
@@ -156,7 +156,7 @@ impl Widget for FromHint {
fn draw(&mut self, painter: &mut Painter) -> Size {
let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
let mut region = UiRegion::FULL;
- region.y.end = region.y.start.offset(len.abs);
+ region.y.end = region.y.start.offset(len.px);
painter.widget_within(&self.inner, region);
Size::REST
}
@@ -173,7 +173,7 @@ fn a_parent_that_only_read_a_hint_relays_out_when_the_hint_changes() {
h.set_root(parent);
assert_corners!(h, inner, (0, 0), (400, 80));
- h.rsc[inner].y = Some(Len::abs(120));
+ h.rsc[inner].y = Some(Len::px(120));
h.frame();
assert_corners!(h, inner, (0, 0), (400, 120));
@@ -187,7 +187,7 @@ struct ReadsOutput {
impl Widget for ReadsOutput {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
- Size::abs(painter.output_size() / 4.0)
+ Size::px(painter.output_size() / 4.0)
}
}
@@ -198,7 +198,7 @@ struct ReadsWidth {
impl Widget for ReadsWidth {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
- Size::abs((painter.output_len(Axis::X) / 4.0, 20.0).into())
+ Size::px((painter.output_len(Axis::X) / 4.0, 20.0).into())
}
}
@@ -287,12 +287,12 @@ fn subpixel_box_changes_accumulate_from_the_last_draw() {
let settled = draws.get();
for width in [100.02, 100.04, 100.05] {
- h.rsc[first].size.x = Len::abs(width);
+ h.rsc[first].size.x = Len::px(width);
h.frame();
assert_eq!(draws.get(), settled);
}
- h.rsc[first].size.x = Len::abs(100.06);
+ h.rsc[first].size.x = Len::px(100.06);
h.frame();
assert_eq!(draws.get(), settled + 1);
}
@@ -342,13 +342,13 @@ fn a_change_two_levels_under_its_reader_still_reaches_it() {
// Every wrapper up to the outer pad read the size below it, so the outer
// pad is what draws again -- and the span it hands the box to is the same
// size as before, which is what lets a draw reuse its way past the leaf.
- let (leaf, _) = counted(&mut h, Size::abs((100, 100).into()), OnResize::Redraw);
+ let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), OnResize::Redraw);
let padded = leaf.pad(10).add(&mut h.rsc);
let below = rect(Color::RED).add(&mut h.rsc);
h.set_root((padded, below).span(Dir::DOWN).pad(12));
assert_corners!(h, below, (12, 132), (388, 388));
- h.rsc[leaf].size = Size::abs((100, 200).into());
+ h.rsc[leaf].size = Size::px((100, 200).into());
h.frame();
assert_corners!(h, below, (12, 232), (388, 388));
@@ -387,7 +387,7 @@ fn stretching_a_subtree_carries_the_children_in_it() {
let settled = draws.get();
assert_corners!(h, inner, (0, 40), (400, 400));
- h.rsc[first].y = Some(Len::abs(80));
+ h.rsc[first].y = Some(Len::px(80));
h.frame();
assert_eq!(
@@ -411,7 +411,7 @@ fn a_widened_row_redraws_what_reads_its_length_and_nothing_else() {
h.set_root((bar, row).span(Dir::RIGHT));
let (settled_wrap, settled_back) = (wrap_draws.get(), back_draws.get());
- h.rsc[bar].x = Some(Len::abs(200));
+ h.rsc[bar].x = Some(Len::px(200));
h.frame();
// The span reads every child's size, so redrawing one takes the span
@@ -437,7 +437,7 @@ fn a_declared_length_child_is_not_redrawn_when_the_box_around_it_grows() {
h.set_root((bar, row).span(Dir::RIGHT));
let settled = draws.get();
- h.rsc[bar].x = Some(Len::abs(200));
+ h.rsc[bar].x = Some(Len::px(200));
h.frame();
assert_eq!(draws.get(), settled, "its own length did not change");
diff --git a/tests/revision_cost.rs b/tests/revision_cost.rs
index 2007362..a6d5a16 100644
--- a/tests/revision_cost.rs
+++ b/tests/revision_cost.rs
@@ -94,11 +94,7 @@ fn build(h: &mut Harness, rows: usize) -> Vec<WidgetId> {
let mut col = Span::empty(Dir::DOWN);
for _ in 0..rows {
let mut row = Span::empty(Dir::RIGHT);
- row.push(
- rect(Color::RED)
- .width(Len::abs(40.0))
- .add_strong(&mut h.rsc),
- );
+ row.push(rect(Color::RED).width(Len::px(40.0)).add_strong(&mut h.rsc));
let mut body = Span::empty(Dir::DOWN);
let para = wtext(words(&mut rng, 12, 52))
.size(16)