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 From 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) -> Self { - let abs = abs.into(); + pub const fn px(px: impl const Into) -> 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 for UiVec2 { - fn from(abs: Vec2) -> Self { - Self::abs(abs) + fn from(px: Vec2) -> Self { + Self::px(px) } } @@ -127,8 +127,8 @@ const impl 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(&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( @@ -136,12 +136,12 @@ fn masked(in: VertexOutput, color: vec4) -> vec4 { // 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, rect_center: vec2, 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 Grow<'_, Rsc> { fn len(&mut self) -> Option { 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 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 { - 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, 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, 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 = 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, } @@ -115,6 +169,7 @@ impl, 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, Tag> children, dir, gap: 0.0, + ortho: OrthoSize::Fill, _pd: PhantomData, } } @@ -135,6 +191,11 @@ impl, 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, want: Option) -> 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 { 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)