Compare commits

...
2 Commits
Author SHA1 Message Date
iris-aiandClaude Opus 5 2807a925af Make a declared length one that cannot carry a share
`declared_lens` filtered `leftover` out of both its sources and every
consumer then re-dropped it, so the rule lived in two filters and a comment.
A declaration is a `Len`: `LayoutLen::declared` states the rule once and both
sources go through it, and `Declared` replaces the bare two-element array on
`ActiveData` and in four signatures.

The two sources stay one value deliberately. A rule decides the child's box;
a hint only promises what it will report -- but `size_hint` is by contract an
exact answer with no painter context, and `hints_agree` fails a widget that
draws something else, so narrowing the box to a hint cannot change what is
drawn. Every consumer asks about the length, never which said it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 21:06:31 -04:00
iris-aiandClaude Opus 5 55df32a33c Say layout's operations by name, and index a pair by its axis
Four rounds over the same idea: an expression that needed a comment to say
what it computed wanted to be a named operation.

The placement description is built by chaining off the value that says it.
`UiSpan::within_desc`/`shifted_desc` and `Len::as_desc` replace the
`PlaceDescAxis::` constructors, `PlaceDescAxis::axis` lifts one axis into a
pair with the whole box across it, and `PlaceDesc::per_axis` covers the case
where the two axes differ. `beside` is dropped: `from_axis` already said it.

Seven module-level functions become methods on the value each took first --
`Widgets::declared_lens`, `LayoutLen::fills`, `PlaceDesc::placement` and
`::rel_base_and_region`, `Size::within_box`, `UiRegion::at_origin` and
`::as_translation`.

`UiSpan::place` is the aligned-placement rule, which was written out three
times; `LayoutLen::without_leftover` is the sibling `apply_leftover` never
had, at six sites; `is_px` and `is_only_leftover` name field comparisons the
surrounding comments had to translate; `Holds::covers` was interval
containment spelled out by hand. A span's `shared` loses the two arguments
that did not vary across its loop.

`LayoutHolds` was four two-element arrays where every other pair here is a
struct of two per-axis values, so nothing it did could be written once.
It becomes `AxisHolds` on `x` and `y`, and `and`, `covers` and `contains`
lose their loops.

Every pair gets `Index<Axis>`/`IndexMut<Axis>` through one macro, and the
eighteen `axis`/`axis_mut` methods go. `const_index` keeps the accessors
usable in const context.

Cold layout is unchanged: `layout_dump` over 400 depth-5 trees is identical
to 58ce74d byte for byte, across all 34,492 boxes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:56:59 -04:00
22 changed files with 501 additions and 503 deletions

No files matched your search

+1
View File
@@ -9,6 +9,7 @@
#![feature(unsize)] #![feature(unsize)]
#![feature(coerce_unsized)] #![feature(coerce_unsized)]
#![feature(option_into_flat_iter)] #![feature(option_into_flat_iter)]
#![feature(const_index)]
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
pub mod layout_diagnostics; pub mod layout_diagnostics;
+3 -14
View File
@@ -1,3 +1,4 @@
use crate::util::impl_axis_index;
use crate::{Px, Rel}; use crate::{Px, Rel};
use super::*; use super::*;
@@ -89,20 +90,6 @@ impl RegionAlign {
x: AxisAlign::NEG, x: AxisAlign::NEG,
y: AxisAlign::NEG, y: AxisAlign::NEG,
}; };
pub fn axis(&self, axis: Axis) -> AxisAlign {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn axis_mut(&mut self, axis: Axis) -> &mut AxisAlign {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
} }
impl RegionAlign { impl RegionAlign {
@@ -231,3 +218,5 @@ impl RegionAlign {
UiVec2::from(self) UiVec2::from(self)
} }
} }
impl_axis_index!(RegionAlign => AxisAlign);
+4 -28
View File
@@ -1,4 +1,5 @@
use super::*; use super::*;
use crate::util::impl_axis_index;
use crate::{Fixed, FixedVec2}; use crate::{Fixed, FixedVec2};
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -53,20 +54,6 @@ pub enum Sign {
} }
impl<const SHIFT: u32> FixedVec2<SHIFT> { impl<const SHIFT: u32> FixedVec2<SHIFT> {
pub const fn axis(&self, axis: Axis) -> Fixed<SHIFT> {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub const fn axis_mut(&mut self, axis: Axis) -> &mut Fixed<SHIFT> {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub const fn from_axis(axis: Axis, aligned: Fixed<SHIFT>, ortho: Fixed<SHIFT>) -> Self { pub const fn from_axis(axis: Axis, aligned: Fixed<SHIFT>, ortho: Fixed<SHIFT>) -> Self {
match axis { match axis {
Axis::X => Self::new(aligned, ortho), Axis::X => Self::new(aligned, ortho),
@@ -76,20 +63,6 @@ impl<const SHIFT: u32> FixedVec2<SHIFT> {
} }
impl Vec2 { impl Vec2 {
pub fn axis(&self, axis: Axis) -> f32 {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self { pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
Self { Self {
x: match axis { x: match axis {
@@ -148,3 +121,6 @@ impl<T> BothAxis<T> {
} }
} }
} }
impl_axis_index!({const SHIFT: u32} FixedVec2<SHIFT> => Fixed<SHIFT>);
impl_axis_index!(Vec2 => f32);
+32 -15
View File
@@ -1,4 +1,5 @@
use super::*; use super::*;
use crate::util::impl_axis_index;
use crate::{Px, PxVec2, Rel, UiNum, Weight, util::impl_op}; use crate::{Px, PxVec2, Rel, UiNum, Weight, util::impl_op};
#[derive(Debug, Default, Clone, Copy, PartialEq)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
@@ -118,20 +119,6 @@ impl Size {
}, },
} }
} }
pub fn axis(&self, axis: Axis) -> LayoutLen {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn axis_mut(&mut self, axis: Axis) -> &mut LayoutLen {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
} }
impl LayoutLen { impl LayoutLen {
@@ -158,11 +145,39 @@ impl LayoutLen {
Len::from_parts(self.rel.add(share), self.px) Len::from_parts(self.rel.add(share), self.px)
} }
/// Only pixels: the same number of them whatever box it lands in, and
/// whatever anyone else in the row asks for. A length that is any part
/// of a box or of what is left over is not one.
pub fn is_px(self) -> bool {
self.rel == Rel::ZERO && self.leftover == Weight::ZERO
}
/// Nothing but a claim on what is left over, so there is no length here
/// at all where nothing is.
pub fn is_only_leftover(self) -> bool {
self.leftover > Weight::ZERO && self.without_leftover() == Len::ZERO
}
/// This as a length of a box, where it is one. `leftover` is not: a
/// share of what is left over is a length only to whoever divides one,
/// so it passes up in the reported size instead and is resolved there.
pub fn declared(self) -> Option<Len> {
(self.leftover == Weight::ZERO).then(|| self.without_leftover())
}
/// What this takes whatever is left over: the reading of a length for
/// anyone not dividing a box between siblings, where a share is a claim
/// on someone else's room rather than a length of its own.
/// [`Self::apply_leftover`] is the opposite reading of the same value.
pub const fn without_leftover(self) -> Len {
Len::from_parts(self.rel, self.px)
}
/// This length, given as a part of a box `len` long, as a part of the /// This length, given as a part of a box `len` long, as a part of the
/// box `len` is itself a part of. The share is untouched: it is a claim /// box `len` is itself a part of. The share is untouched: it is a claim
/// on whoever divides the room, not a fraction of anything. /// on whoever divides the room, not a fraction of anything.
pub const fn within_len(self, len: Len) -> Self { pub const fn within_len(self, len: Len) -> Self {
let part = Len::from_parts(self.rel, self.px).within_len(len); let part = self.without_leftover().within_len(len);
Self { Self {
px: part.px, px: part.px,
rel: part.rel, rel: part.rel,
@@ -236,3 +251,5 @@ impl std::fmt::Display for LayoutLen {
Ok(()) Ok(())
} }
} }
impl_axis_index!(Size => LayoutLen);
+13 -28
View File
@@ -1,3 +1,4 @@
use crate::util::impl_axis_index;
use std::{fmt::Display, marker::Destruct}; use std::{fmt::Display, marker::Destruct};
use super::*; use super::*;
@@ -61,20 +62,6 @@ impl UiVec2 {
} }
} }
pub fn axis_mut(&mut self, axis: Axis) -> &mut Len {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub fn axis(&self, axis: Axis) -> Len {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
/// Resolved against a box of `size`, which is where a fraction stops /// Resolved against a box of `size`, which is where a fraction stops
/// being one and becomes a place. /// being one and becomes a place.
pub fn to_px(&self, size: PxVec2) -> PxVec2 { pub fn to_px(&self, size: PxVec2) -> PxVec2 {
@@ -294,6 +281,15 @@ impl UiSpan {
} }
} }
/// 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 { pub const fn len(&self) -> Len {
self.end - self.start self.end - self.start
} }
@@ -348,20 +344,6 @@ impl UiRegion {
y: self.y.within(&parent.y), y: self.y.within(&parent.y),
} }
} }
pub const fn axis(&self, axis: Axis) -> &UiSpan {
match axis {
Axis::X => &self.x,
Axis::Y => &self.y,
}
}
pub const fn axis_mut(&mut self, axis: Axis) -> &mut UiSpan {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub const fn flip(&mut self, axis: Axis) { pub const fn flip(&mut self, axis: Axis) {
match axis { match axis {
Axis::X => self.x.flip(), Axis::X => self.x.flip(),
@@ -462,3 +444,6 @@ impl Display for PixelRegion {
write!(f, "{} -> {}", self.top_left, self.bot_right) write!(f, "{} -> {}", self.top_left, self.bot_right)
} }
} }
impl_axis_index!(UiVec2 => Len);
impl_axis_index!(UiRegion => UiSpan);
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::{ use crate::{
LayerId, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, PlaceDesc, RegionAlign, RetainedPrimitive, Declared, LayerId, LayoutHolds, MaskIdx, MoveIdx, PlaceDesc, RegionAlign, RetainedPrimitive,
Size, TextureHandle, UiRegion, UiVec2, WidgetId, Size, TextureHandle, UiRegion, UiVec2, WidgetId,
}; };
@@ -59,7 +59,7 @@ pub struct ActiveData {
/// The declared lengths whoever drew this widget resolved into its rel base. /// The declared lengths whoever drew this widget resolved into its rel base.
/// A change to one moves a box this widget cannot fix by drawing again, /// A change to one moves a box this widget cannot fix by drawing again,
/// and comparing them is what says so. /// and comparing them is what says so.
pub declared: [Option<LayoutLen>; 2], pub declared: Declared,
/// Its alignment when it was last drawn, which a change to the property /// Its alignment when it was last drawn, which a change to the property
/// is found against. /// is found against.
pub own_align: RegionAlign, pub own_align: RegionAlign,
+6
View File
@@ -33,6 +33,12 @@ impl Holds {
len.raw() >= self.lo.raw() && len.raw() <= self.hi.raw() len.raw() >= self.lo.raw() && len.raw() <= self.hi.raw()
} }
/// Every length `other` holds for is one this holds for, so a drawing
/// made under this range is still good wherever `other` is.
pub const fn covers(self, other: Self) -> bool {
self.lo.raw() <= other.lo.raw() && self.hi.raw() >= other.hi.raw()
}
pub const fn and(self, other: Self) -> Self { pub const fn and(self, other: Self) -> Self {
Self { Self {
lo: self.lo.max(other.lo), lo: self.lo.max(other.lo),
+75 -45
View File
@@ -1,10 +1,12 @@
use crate::{Axis, Holds, Len, PxVec2, UiRegion, UiVec2}; use crate::util::impl_axis_index;
use crate::{Axis, Holds, Len, Px, PxVec2, UiRegion, UiVec2};
const AXES: [Axis; 2] = [Axis::X, Axis::Y]; const AXES: [Axis; 2] = [Axis::X, Axis::Y];
/// What one evaluation of a widget depends on: the window lengths its reads /// What one evaluation of a widget depends on along one axis: the window
/// hold for, the pixel lengths of its own box, and the symbolic lengths of /// lengths its reads hold for, the pixel lengths of its own box, and the
/// that box and of its rel base where either one is what it was expressed in. /// symbolic lengths of that box and of its rel base where either one is what
/// it was expressed in.
/// ///
/// The symbolic lengths are pins rather than ranges: a container places its /// The symbolic lengths are pins rather than ranges: a container places its
/// children as lengths of its rel base measured from where its own box starts, /// children as lengths of its rel base measured from where its own box starts,
@@ -19,61 +21,89 @@ const AXES: [Axis; 2] = [Axis::X, Axis::Y];
/// of the rel base that is only pixels is not one: it is that many pixels /// of the rel base that is only pixels is not one: it is that many pixels
/// whatever the rel base turns out to be. /// whatever the rel base turns out to be.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct AxisHolds {
pub window: Holds,
pub rel_base: Option<Len>,
pub region: Holds,
pub region_len: Option<Len>,
}
impl AxisHolds {
pub const ANY: Self = Self {
window: Holds::ANY,
rel_base: None,
region: Holds::ANY,
region_len: None,
};
pub fn and(self, other: Self) -> Self {
// Two pins of the same length disagreeing would mean one drawing was
// a fraction of two different lengths at once.
debug_assert!(
self.region_len.is_none()
|| other.region_len.is_none()
|| self.region_len == other.region_len
);
debug_assert!(
self.rel_base.is_none() || other.rel_base.is_none() || self.rel_base == other.rel_base
);
Self {
window: self.window.and(other.window),
rel_base: self.rel_base.or(other.rel_base),
region: self.region.and(other.region),
region_len: self.region_len.or(other.region_len),
}
}
pub fn covers(self, other: Self) -> bool {
self.window.covers(other.window)
&& self.region.covers(other.region)
&& self
.region_len
.is_none_or(|len| other.region_len == Some(len))
&& self.rel_base.is_none_or(|len| other.rel_base == Some(len))
}
/// Whether a widget in a box `len` long, with that rel base, in that
/// window, is one this drawing holds for.
pub fn contains(self, window: Px, rel_base: Len, len: Len) -> bool {
self.window.contains(window)
&& self.rel_base.is_none_or(|pinned| pinned == rel_base)
&& self.region.contains(len.to_px(window))
&& self.region_len.is_none_or(|pinned| pinned == len)
}
}
/// [`AxisHolds`] on both axes. Every question asked of it is asked of one
/// axis at a time, since a widget that read one length holds for any length
/// of the other.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LayoutHolds { pub struct LayoutHolds {
pub window: [Holds; 2], pub x: AxisHolds,
pub rel_base: [Option<Len>; 2], pub y: AxisHolds,
pub region: [Holds; 2],
pub region_len: [Option<Len>; 2],
} }
impl LayoutHolds { impl LayoutHolds {
pub const ANY: Self = Self { pub const ANY: Self = Self {
window: [Holds::ANY; 2], x: AxisHolds::ANY,
rel_base: [None; 2], y: AxisHolds::ANY,
region: [Holds::ANY; 2],
region_len: [None; 2],
}; };
pub fn and(self, other: Self) -> Self { pub fn and(self, other: Self) -> Self {
let mut result = Self::ANY; Self {
for n in 0..2 { x: self.x.and(other.x),
result.window[n] = self.window[n].and(other.window[n]); y: self.y.and(other.y),
result.region[n] = self.region[n].and(other.region[n]);
debug_assert!(
self.region_len[n].is_none()
|| other.region_len[n].is_none()
|| self.region_len[n] == other.region_len[n]
);
debug_assert!(
self.rel_base[n].is_none()
|| other.rel_base[n].is_none()
|| self.rel_base[n] == other.rel_base[n]
);
result.region_len[n] = self.region_len[n].or(other.region_len[n]);
result.rel_base[n] = self.rel_base[n].or(other.rel_base[n]);
} }
result
} }
pub fn covers(self, other: Self) -> bool { pub fn covers(self, other: Self) -> bool {
(0..2).all(|n| { self.x.covers(other.x) && self.y.covers(other.y)
self.window[n].lo <= other.window[n].lo
&& self.window[n].hi >= other.window[n].hi
&& self.region[n].lo <= other.region[n].lo
&& self.region[n].hi >= other.region[n].hi
&& self.region_len[n].is_none_or(|len| other.region_len[n] == Some(len))
&& self.rel_base[n].is_none_or(|len| other.rel_base[n] == Some(len))
})
} }
pub fn contains(self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool { pub fn contains(self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
AXES.into_iter().all(|axis| { AXES.into_iter()
let n = axis as usize; .all(|axis| self[axis].contains(window[axis], rel_base[axis], region[axis].len()))
let len = region.axis(axis).len();
self.window[n].contains(window.axis(axis))
&& self.rel_base[n].is_none_or(|pinned| pinned == rel_base.axis(axis))
&& self.region[n].contains(len.to_px(window.axis(axis)))
&& self.region_len[n].is_none_or(|pinned| pinned == len)
})
} }
} }
impl_axis_index!(LayoutHolds => AxisHolds);
+117 -122
View File
@@ -1,9 +1,9 @@
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
use crate::layout_diagnostics::{self as diag, Counter}; use crate::layout_diagnostics::{self as diag, Counter};
use crate::{ use crate::{
Axis, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel, Axis, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel,
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
render::{ render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind, GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
TexturePrimitive, TexturePrimitive,
@@ -157,10 +157,10 @@ impl<'a> Painter<'a> {
/// only moves its child does not pin its drawing to a rel base. /// only moves its child does not pin its drawing to a rel base.
fn state_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc { fn state_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
for axis in AXES { for axis in AXES {
if let Some(span) = place.axis(axis).narrows_rel_base() { if let Some(span) = place[axis].narrows_rel_base() {
let len = span.len(); let len = span.len();
let stated = (len != Len::FULL).then(|| len.within_len(self.rel_base(axis))); let stated = (len != Len::FULL).then(|| len.within_len(self.rel_base(axis)));
*place.axis_mut(axis) = place.axis(axis).with_rel_base(stated); place[axis] = place[axis].with_rel_base(stated);
} }
} }
place place
@@ -186,7 +186,7 @@ impl<'a> Painter<'a> {
let declared = self.declared_lens(id); let declared = self.declared_lens(id);
let align = self.rsc.widgets().alignment(id.id()); let align = self.rsc.widgets().alignment(id.id());
let (rel_base, region) = let (rel_base, region) =
rel_base_and_region(self.region, self.rel_base, place, declared, align); place.rel_base_and_region(self.region, self.rel_base, declared, align);
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
if region_node { if region_node {
diag::bump(Counter::RegionNodeDraws); diag::bump(Counter::RegionNodeDraws);
@@ -258,7 +258,7 @@ impl<'a> Painter<'a> {
let place = self.state_rel_base(place.into()); let place = self.state_rel_base(place.into());
let states_rel_base = AXES let states_rel_base = AXES
.iter() .iter()
.any(|&axis| place.axis(axis).stated_rel_base().is_some()); .any(|&axis| place[axis].stated_rel_base().is_some());
if states_rel_base || !self.children.contains(&id.id()) { if states_rel_base || !self.children.contains(&id.id()) {
return self.widget_at(id, place); return self.widget_at(id, place);
} }
@@ -292,8 +292,8 @@ impl<'a> Painter<'a> {
/// it resolves into its rel base. Reading them depends on nothing -- the box /// it resolves into its rel base. Reading them depends on nothing -- the box
/// that comes of them is kept on the child, and `redraw` compares it /// that comes of them is kept on the child, and `redraw` compares it
/// there. /// there.
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] { fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> Declared {
declared_lens(self.rsc.widgets(), id.id()) self.rsc.widgets().declared_lens(id.id())
} }
/// What a child says its length is without being drawn, if it can say, /// What a child says its length is without being drawn, if it can say,
@@ -304,12 +304,12 @@ impl<'a> Painter<'a> {
let widgets = self.rsc.widgets(); let widgets = self.rsc.widgets();
// A rule is the answer where there is one: it wins over whatever the // A rule is the answer where there is one: it wins over whatever the
// widget would draw, so it has to win over what the widget says too. // widget would draw, so it has to win over what the widget says too.
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| { let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
widgets widgets
.get_dyn(id.id()) .get_dyn(id.id())
.and_then(|widget| widget.size_hint(axis)) .and_then(|widget| widget.size_hint(axis))
}); });
let rel_base = self.rel_base.axis(axis); let rel_base = self.rel_base[axis];
let resolved = hint.map(|hint| hint.within_len(rel_base)); let resolved = hint.map(|hint| hint.within_len(rel_base));
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
{ {
@@ -326,7 +326,7 @@ impl<'a> Painter<'a> {
// the child's own: resolved against a rel base of pixels, none is // the child's own: resolved against a rel base of pixels, none is
// left to see it by. // left to see it by.
if hint.rel != Rel::ZERO { if hint.rel != Rel::ZERO {
self.own.rel_base[axis as usize] = Some(rel_base); self.own[axis].rel_base = Some(rel_base);
} }
} }
resolved resolved
@@ -392,8 +392,8 @@ impl<'a> Painter<'a> {
/// again. One axis at a time, because a container that divides one axis /// again. One axis at a time, because a container that divides one axis
/// holds for any length of the other. /// holds for any length of the other.
pub fn region_len(&mut self, axis: Axis) -> Len { pub fn region_len(&mut self, axis: Axis) -> Len {
let len = self.region.axis(axis).len(); let len = self.region[axis].len();
self.own.region_len[axis as usize] = Some(len); self.own[axis].region_len = Some(len);
len len
} }
@@ -403,8 +403,8 @@ impl<'a> Painter<'a> {
/// Reading it pins the drawing to that rel base, the way /// Reading it pins the drawing to that rel base, the way
/// [`Self::region_len`] pins it to the box. /// [`Self::region_len`] pins it to the box.
pub fn rel_base(&mut self, axis: Axis) -> Len { pub fn rel_base(&mut self, axis: Axis) -> Len {
let len = self.rel_base.axis(axis); let len = self.rel_base[axis];
self.own.rel_base[axis as usize] = Some(len); self.own[axis].rel_base = Some(len);
len len
} }
@@ -425,10 +425,7 @@ impl<'a> Painter<'a> {
/// worth anything, since reading one is also what makes its own size /// worth anything, since reading one is also what makes its own size
/// depend on it. /// depend on it.
pub fn has_exact_size(&self, axis: Axis) -> bool { pub fn has_exact_size(&self, axis: Axis) -> bool {
self.rsc self.rsc.widgets().size_rules(self.id)[axis]
.widgets()
.size_rules(self.id)
.axis(axis)
.exact() .exact()
.is_some() .is_some()
} }
@@ -442,9 +439,9 @@ impl<'a> Painter<'a> {
/// One axis of this widget's own box in pixels. Prefer this to /// One axis of this widget's own box in pixels. Prefer this to
/// [`Self::px_size`] when the other axis cannot affect the drawing. /// [`Self::px_size`] when the other axis cannot affect the drawing.
pub fn px_len(&mut self, axis: Axis) -> Px { pub fn px_len(&mut self, axis: Axis) -> Px {
let len = self.region.axis(axis).len(); let len = self.region[axis].len();
let px = len.to_px(self.window.axis(axis)); let px = len.to_px(self.window[axis]);
let own = &mut self.own.region[axis as usize]; let own = &mut self.own[axis].region;
if *own == Holds::ANY { if *own == Holds::ANY {
*own = Holds::at(px); *own = Holds::at(px);
} }
@@ -456,15 +453,15 @@ impl<'a> Painter<'a> {
/// of the box, and the same reported size. A widget that read its length /// of the box, and the same reported size. A widget that read its length
/// in pixels holds for that one alone until it says otherwise. /// in pixels holds for that one alone until it says otherwise.
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) { pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let len = self.region.axis(axis).len(); let len = self.region[axis].len();
let holds = holds.into(); let holds = holds.into();
debug_assert!( debug_assert!(
holds.contains(len.to_px(self.window.axis(axis))), holds.contains(len.to_px(self.window[axis])),
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box", "'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
self.label(), self.label(),
self.id self.id
); );
self.own.region[axis as usize] = holds; self.own[axis].region = holds;
} }
/// A window length in pixels, which is what every length in layout is /// A window length in pixels, which is what every length in layout is
@@ -472,9 +469,9 @@ impl<'a> Painter<'a> {
/// length is a fraction of it; one that is only pixels is that many /// length is a fraction of it; one that is only pixels is that many
/// pixels in any window and pins nothing. /// pixels in any window and pins nothing.
pub fn to_px(&mut self, len: Len, axis: Axis) -> Px { pub fn to_px(&mut self, len: Len, axis: Axis) -> Px {
let window = self.window.axis(axis); let window = self.window[axis];
if len.rel != Rel::ZERO { if len.rel != Rel::ZERO {
let own = &mut self.own.window[axis as usize]; let own = &mut self.own[axis].window;
if *own == Holds::ANY { if *own == Holds::ANY {
*own = Holds::at(window); *own = Holds::at(window);
} }
@@ -489,12 +486,12 @@ impl<'a> Painter<'a> {
pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) { pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let holds = holds.into(); let holds = holds.into();
debug_assert!( debug_assert!(
holds.contains(self.window.axis(axis)), holds.contains(self.window[axis]),
"'{}' ({:?}) says its drawing holds for windows that leave out this one", "'{}' ({:?}) says its drawing holds for windows that leave out this one",
self.label(), self.label(),
self.id self.id
); );
self.own.window[axis as usize] = holds; self.own[axis].window = holds;
} }
pub fn text_data(&mut self) -> &mut TextData { pub fn text_data(&mut self) -> &mut TextData {
@@ -553,7 +550,7 @@ impl<W: ?Sized> DrawResult<'_, '_, W> {
} }
pub fn len(self, axis: Axis) -> LayoutLen { pub fn len(self, axis: Axis) -> LayoutLen {
self.size().axis(axis) self.size()[axis]
} }
} }
@@ -602,20 +599,22 @@ impl Painter<'_> {
holds: LayoutHolds, holds: LayoutHolds,
region: UiRegion, region: UiRegion,
place: PlaceDesc, place: PlaceDesc,
declared: [Option<LayoutLen>; 2], declared: Declared,
) -> LayoutHolds { ) -> LayoutHolds {
let mut result = LayoutHolds::ANY; let mut result = LayoutHolds::ANY;
for axis in AXES { for axis in AXES {
let n = axis as usize; let declared = declared[axis];
let holds = holds[axis];
let result = &mut result[axis];
// Every read became pixels against the window, so a range on // Every read became pixels against the window, so a range on
// it is already in this widget's terms. // it is already in this widget's terms.
result.window[n] = holds.window[n]; result.window = holds.window;
let at = *place.axis(axis); let at = place[axis];
let reaches = at.stated_rel_base().is_none() let reaches = at.stated_rel_base().is_none()
&& !at.is_sized() && !at.is_sized()
&& declared[n].is_none_or(|len| len.rel != Rel::ZERO); && declared.is_none_or(|len| len.rel != Rel::ZERO);
result.rel_base[n] = holds.rel_base[n].and(reaches.then(|| self.rel_base.axis(axis))); result.rel_base = holds.rel_base.and(reaches.then(|| self.rel_base[axis]));
match (at.within_span(), declared[n].is_some()) { match (at.within_span(), declared.is_some()) {
// Its box is a part of this widget's own box, in that box's // Its box is a part of this widget's own box, in that box's
// own lengths, so what it holds for maps back through that // own lengths, so what it holds for maps back through that
// part into a range on this widget's box. A length it pinned // part into a range on this widget's box. A length it pinned
@@ -625,10 +624,10 @@ impl Painter<'_> {
// widget's own length. // widget's own length.
(Some(span), false) => { (Some(span), false) => {
let part_len = span.len(); let part_len = span.len();
result.region[n] = holds.region[n].through(part_len); result.region = holds.region.through(part_len);
result.region_len[n] = holds.region_len[n].map(|pinned| match part_len.rel { result.region_len = holds.region_len.map(|pinned| match part_len.rel {
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px), Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
_ => self.region.axis(axis).len(), _ => self.region[axis].len(),
}); });
} }
// Its box is a length this widget decided, from its own // Its box is a length this widget decided, from its own
@@ -636,8 +635,7 @@ impl Painter<'_> {
// widget's box reaches it, so what it holds for is a range // widget's box reaches it, so what it holds for is a range
// on the window and none of it on that box. // on the window and none of it on that box.
_ => { _ => {
result.window[n] = result.window = result.window.and(holds.region.through(region[axis].len()));
result.window[n].and(holds.region[n].through(region.axis(axis).len()));
} }
} }
} }
@@ -645,35 +643,38 @@ impl Painter<'_> {
} }
} }
/// What a widget declares a length of its box to be. `leftover` is not one: a impl Widgets {
/// share of what is left over is only a length to the widget dividing one, /// What a widget's box is where a rule or its own hint says so outright.
/// so it passes up in the size instead. pub(crate) fn declared_lens(&self, id: WidgetId) -> Declared {
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLen>; 2] { let rules = self.size_rules(id);
let rules = widgets.size_rules(id); let widget = self.get_dyn(id);
let widget = widgets.get_dyn(id); Declared::per_axis(|axis| {
AXES.map(|axis| { rules[axis].declared().or_else(|| {
rules.axis(axis).declared().or_else(|| { // A hint still narrows the box where no rule does, which is
// A hint still narrows the box where no rule does, which is how a // how a widget with a natural pixel size -- an image, a gap
// widget with a natural pixel size -- an image, a gap -- gets that // -- gets that size rather than the whole offer. That is the
// size rather than the whole offer. That is the offer's business // offer's business rather than a declaration's, and this
// rather than a declaration's, and this falls away once a widget // falls away once a widget occupies its reported size inside
// occupies its reported size inside the box it was offered. // the box it was offered.
widget widget
.and_then(|widget| widget.size_hint(axis)) .and_then(|widget| widget.size_hint(axis))
.filter(|len| len.leftover == Weight::ZERO) .and_then(LayoutLen::declared)
})
}) })
}) }
} }
/// Whether what a widget reported along an axis is the whole of the box it impl LayoutLen {
/// is in rather than a part to be placed inside it. A share fills, because a /// Whether what a widget reported along an axis is the whole of the box
/// share is a length only to whoever divides one, and whoever did is the one /// it is in rather than a part to be placed inside it. A share fills,
/// that handed down this box. A declared axis does too: the rule already gave /// because a share is a length only to whoever divides one, and whoever
/// the region its length, and the rule's length is what the widget reports /// did is the one that handed down this box. A declared axis does too:
/// there. And an axis the parent decided from the answer is /// the rule already gave the region its length, and the rule's length is
/// the answer already. /// what the widget reports there. And an axis the parent decided from
pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: bool) -> bool { /// the answer is the answer already.
reported.leftover != Weight::ZERO || declared.is_some() || decided pub(crate) fn fills(self, declared: Option<Len>, decided: bool) -> bool {
self.leftover != Weight::ZERO || declared.is_some() || decided
}
} }
/// Where a widget's drawing goes inside the part its parent gave it: what /// Where a widget's drawing goes inside the part its parent gave it: what
@@ -685,63 +686,57 @@ pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: b
/// That is what makes a fraction the same fraction wherever the part it is /// That is what makes a fraction the same fraction wherever the part it is
/// placed in sits and however long it is -- the fraction is resolved once, /// placed in sits and however long it is -- the fraction is resolved once,
/// here, against the rel base it was reported of. /// here, against the rel base it was reported of.
pub(crate) fn placement( impl PlaceDesc {
region: UiRegion, pub(crate) fn placement(
size: Size, self,
declared: [Option<LayoutLen>; 2], region: UiRegion,
place: PlaceDesc, size: Size,
align: RegionAlign, declared: Declared,
) -> UiRegion { align: RegionAlign,
let mut placed = region; ) -> UiRegion {
for axis in AXES { let mut placed = region;
let n = axis as usize; for axis in AXES {
let reported = size.axis(axis); let reported = size[axis];
if fills(reported, declared[n], place.axis(axis).does_fill()) { if reported.fills(declared[axis], self[axis].does_fill()) {
continue; continue;
}
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
} }
let len = Len::from_parts(reported.rel, reported.px); placed
let span = placed.axis_mut(axis);
span.start += (span.len() - len).scale(align.axis(axis).rel());
span.end = span.start + len;
} }
placed
}
/// The rel base length and the box a child is asked in, in the coordinates the /// The rel base length and the box a child is asked in, in the coordinates the
/// widget asking draws in. /// widget asking draws in.
/// ///
/// `own` is that widget's own box, and `place` what of it the child is /// `own` is that widget's own box, and `place` what of it the child is
/// given, including any rel base it states -- a row's slot, or padding's rel /// given, including any rel base it states -- a row's slot, or padding's rel
/// base less its pixels. That is a window length, like every other length /// base less its pixels. That is a window length, like every other length
/// here, since a slot of a row is not a fraction of anything the row can /// here, since a slot of a row is not a fraction of anything the row can
/// name. The child's declaration is a fraction of whichever reached it, and /// name. The child's declaration is a fraction of whichever reached it, and
/// is the only one that also places the box: a box the caller decided is /// is the only one that also places the box: a box the caller decided is
/// what `place` names. /// what `place` names.
pub(crate) fn rel_base_and_region( pub(crate) fn rel_base_and_region(
own: UiRegion, self,
parent_rel_base: UiVec2, own: UiRegion,
place: PlaceDesc, parent_rel_base: UiVec2,
declared: [Option<LayoutLen>; 2], declared: Declared,
align: RegionAlign, align: RegionAlign,
) -> (UiVec2, UiRegion) { ) -> (UiVec2, UiRegion) {
let given = place.of(own, align); let given = self.of(own, align);
let mut rel_base = parent_rel_base; let mut rel_base = parent_rel_base;
let mut region = given; let mut region = given;
for axis in AXES { for axis in AXES {
let n = axis as usize; let base = self[axis]
let base = place .stated_rel_base()
.axis(axis) .unwrap_or_else(|| parent_rel_base[axis]);
.stated_rel_base() let len = declared[axis]
.unwrap_or_else(|| parent_rel_base.axis(axis)); .map(|len| len.within_len(base))
let len = declared[n] .unwrap_or(base);
.map(|len| Len::from_parts(len.rel, len.px).within_len(base)) rel_base[axis] = len;
.unwrap_or(base); if declared[axis].is_some() {
*rel_base.axis_mut(axis) = len; region[axis] = given[axis].place(len, align[axis]);
if declared[n].is_some() { }
let slot = given.axis(axis);
let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
*region.axis_mut(axis) = UiSpan::new(start, start + len);
} }
(rel_base, region)
} }
(rel_base, region)
} }
+74 -71
View File
@@ -1,3 +1,4 @@
use crate::util::impl_axis_index;
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan}; use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan};
/// How a child's region along one axis comes from the region of the widget /// How a child's region along one axis comes from the region of the widget
@@ -35,53 +36,7 @@ enum RelBase {
impl PlaceDescAxis { impl PlaceDescAxis {
/// The whole of the caller's box. /// The whole of the caller's box.
pub const WHOLE: Self = Self::within(UiSpan::FULL); pub const WHOLE: Self = UiSpan::FULL.within_desc();
/// `span` composed into the caller's own box, so it moves and scales
/// with it: [`UiSpan::within`], which is what a container that insets
/// one speaks. Taking eleven pixels off the end needs no length, where
/// saying the same thing in window lengths would make the container read
/// its own box -- and a box chosen from its own answer then feeds back
/// into the answer.
///
/// The child's rel base is narrowed the same way, so padding takes its
/// pixels off both and `rel(1)` under it fills the caller rather than
/// overflowing it.
pub const fn within(span: UiSpan) -> Self {
Self {
span: PlaceSpan::Within(span),
fills: false,
rel_base: RelBase::WithRegion,
}
}
/// `span` shifted to where the caller's own box starts: window lengths
/// along a cursor, which is what a container dividing room among its
/// children speaks. A child's report is a window length, so the cursor
/// that sums those reports is one too, and a moved box re-places every
/// child by re-adding its start, exactly.
///
/// The child's rel base passes through: how far along the cursor a child
/// sits says nothing about what a fraction under it is of.
pub const fn shifted(span: UiSpan) -> Self {
Self {
span: PlaceSpan::Shifted(span),
fills: false,
rel_base: RelBase::Inherit,
}
}
/// A box this long, placed in the caller's own by the child's alignment:
/// the rule that places an answer, with the length given from above
/// rather than reported. What a stack's sizing child decides for the
/// rest. It is the child's rel base too.
pub const fn sized(len: Len) -> Self {
Self {
span: PlaceSpan::Sized(len),
fills: false,
rel_base: RelBase::Len(len),
}
}
/// This region is the child's placement: its answer is not placed inside /// This region is the child's placement: its answer is not placed inside
/// it again. A container uses it where it hands back exactly what the /// it again. A container uses it where it hands back exactly what the
@@ -91,6 +46,14 @@ impl PlaceDescAxis {
self self
} }
/// This along `axis`, and the whole of the caller's box across it: what
/// a container dividing one axis says, since nothing divides the other.
/// [`PlaceDesc::from_axis`] says the across one where it is not the
/// whole.
pub const fn axis(self, axis: Axis) -> PlaceDesc {
PlaceDesc::from_axis(axis, self, Self::WHOLE)
}
/// What the child's fractions are of, as a length of the window: a /// What the child's fractions are of, as a length of the window: a
/// resolved share, or a box a sibling's answer decided. /// resolved share, or a box a sibling's answer decided.
pub const fn rel_base(mut self, len: Len) -> Self { pub const fn rel_base(mut self, len: Len) -> Self {
@@ -112,10 +75,7 @@ impl PlaceDescAxis {
span.shift(own.start); span.shift(own.start);
span span
} }
PlaceSpan::Sized(len) => { PlaceSpan::Sized(len) => own.place(len, align),
let start = own.start + (own.len() - len).scale(align.rel());
UiSpan::new(start, start + len)
}
} }
} }
@@ -185,29 +145,21 @@ impl PlaceDesc {
Self { x: place, y: place } Self { x: place, y: place }
} }
/// A description per axis, where the two differ and neither is the
/// axis a container divides.
pub fn per_axis(f: impl Fn(Axis) -> PlaceDescAxis) -> Self {
Self::new(f(Axis::X), f(Axis::Y))
}
/// `aligned` on `axis` and `ortho` on the other, which is how a /// `aligned` on `axis` and `ortho` on the other, which is how a
/// container that divides one axis says what it is doing. /// container that divides one axis says what it is doing.
pub fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self { pub const fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
match axis { match axis {
Axis::X => Self::new(aligned, ortho), Axis::X => Self::new(aligned, ortho),
Axis::Y => Self::new(ortho, aligned), Axis::Y => Self::new(ortho, aligned),
} }
} }
pub const fn axis(&self, axis: Axis) -> &PlaceDescAxis {
match axis {
Axis::X => &self.x,
Axis::Y => &self.y,
}
}
pub const fn axis_mut(&mut self, axis: Axis) -> &mut PlaceDescAxis {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
/// Both regions are the child's placement. See [`PlaceDescAxis::fills`]. /// Both regions are the child's placement. See [`PlaceDescAxis::fills`].
pub const fn fills(self) -> Self { pub const fn fills(self) -> Self {
Self::new(self.x.fills(), self.y.fills()) Self::new(self.x.fills(), self.y.fills())
@@ -215,7 +167,7 @@ impl PlaceDesc {
/// The child's rel base on one axis. See [`PlaceDescAxis::rel_base`]. /// The child's rel base on one axis. See [`PlaceDescAxis::rel_base`].
pub const fn rel_base(mut self, axis: Axis, len: Len) -> Self { pub const fn rel_base(mut self, axis: Axis, len: Len) -> Self {
*self.axis_mut(axis) = self.axis(axis).rel_base(len); self[axis] = self[axis].rel_base(len);
self self
} }
@@ -225,12 +177,61 @@ impl PlaceDesc {
} }
} }
impl UiSpan {
/// This span composed into the caller's own box, so it moves and scales
/// with it: [`UiSpan::within`], which is what a container that insets
/// one speaks. Taking eleven pixels off the end needs no length, where
/// saying the same thing in window lengths would make the container read
/// its own box -- and a box chosen from its own answer then feeds back
/// into the answer.
///
/// The child's rel base is narrowed the same way, so padding takes its
/// pixels off both and `rel(1)` under it fills the caller rather than
/// overflowing it.
pub const fn within_desc(self) -> PlaceDescAxis {
PlaceDescAxis {
span: PlaceSpan::Within(self),
fills: false,
rel_base: RelBase::WithRegion,
}
}
/// This span shifted to where the caller's own box starts: window
/// lengths along a cursor, which is what a container dividing room among
/// its children speaks. A child's report is a window length, so the
/// cursor that sums those reports is one too, and a moved box re-places
/// every child by re-adding its start, exactly.
///
/// The child's rel base passes through: how far along the cursor a child
/// sits says nothing about what a fraction under it is of. The same span
/// says [`Self::within_desc`] as a part of that box instead, and which is
/// meant cannot be read off the numbers.
pub const fn shifted_desc(self) -> PlaceDescAxis {
PlaceDescAxis {
span: PlaceSpan::Shifted(self),
fills: false,
rel_base: RelBase::Inherit,
}
}
}
impl Len {
/// A box this long, placed in the caller's own by the child's alignment:
/// the rule that places an answer, with the length given from above
/// rather than reported. What a stack's sizing child decides for the
/// rest. It is the child's rel base too.
pub const fn as_desc(self) -> PlaceDescAxis {
PlaceDescAxis {
span: PlaceSpan::Sized(self),
fills: false,
rel_base: RelBase::Len(self),
}
}
}
impl From<UiRegion> for PlaceDesc { impl From<UiRegion> for PlaceDesc {
fn from(region: UiRegion) -> Self { fn from(region: UiRegion) -> Self {
Self::new( Self::new(region.x.within_desc(), region.y.within_desc())
PlaceDescAxis::within(region.x),
PlaceDescAxis::within(region.y),
)
} }
} }
@@ -247,3 +248,5 @@ pub struct RetainedPrimitive {
pub handle: PrimitiveHandle, pub handle: PrimitiveHandle,
pub region: UiRegion, pub region: UiRegion,
} }
impl_axis_index!(PlaceDesc => PlaceDescAxis);
+63 -75
View File
@@ -1,10 +1,9 @@
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
use crate::ui::painter::{declared_lens, placement, rel_base_and_region};
use crate::{ use crate::{
ActiveData, Axis, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Moves, ActiveData, Axis, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx,
Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc,
UiVec2, Weight, WidgetId, Widgets, UiSpan, UiVec2, Weight, WidgetId, Widgets,
util::{HashMap, Vec2}, util::{HashMap, Vec2},
}; };
@@ -194,11 +193,10 @@ impl UiRenderState {
/// rules. Nothing above it narrowed anything or chose where it goes, so /// rules. Nothing above it narrowed anything or chose where it goes, so
/// its declaration is the whole of what decides either. /// its declaration is the whole of what decides either.
fn root_layout(id: WidgetId, widgets: &Widgets) -> (UiVec2, UiRegion) { fn root_layout(id: WidgetId, widgets: &Widgets) -> (UiVec2, UiRegion) {
rel_base_and_region( PlaceDesc::WHOLE.rel_base_and_region(
UiRegion::FULL, UiRegion::FULL,
UiVec2::FULL_SIZE, UiVec2::FULL_SIZE,
PlaceDesc::WHOLE, widgets.declared_lens(id),
declared_lens(widgets, id),
widgets.alignment(id), widgets.alignment(id),
) )
} }
@@ -221,7 +219,7 @@ impl UiRenderState {
diag::draw_request(id, info.parent, region, info.px, info.region_node); diag::draw_request(id, info.parent, region, info.px, info.region_node);
} }
let align = rsc.widgets().alignment(id); let align = rsc.widgets().alignment(id);
let declared = declared_lens(rsc.widgets(), id); let declared = rsc.widgets().declared_lens(id);
// Nothing this widget measured can be dirty while it draws: layout is // Nothing this widget measured can be dirty while it draws: layout is
// one bottom-up walk, so anything deeper has settled or deferred to // one bottom-up walk, so anything deeper has settled or deferred to
// its own parent, and a deferred one leaves that parent marked. // its own parent, and a deferred one leaves that parent marked.
@@ -235,7 +233,7 @@ impl UiRenderState {
.then(|| self.retained_answer(id, region, info)) .then(|| self.retained_answer(id, region, info))
.flatten() .flatten()
.and_then(|answer| { .and_then(|answer| {
let placed = placement(region, answer.0, declared, info.placed, align); let placed = info.placed.placement(region, answer.0, declared, align);
self.try_reuse(id, region, placed, info, rsc) self.try_reuse(id, region, placed, info, rsc)
.map(|()| answer) .map(|()| answer)
}); });
@@ -247,7 +245,7 @@ impl UiRenderState {
// Where the drawing goes: the part its parent gave it, with the // Where the drawing goes: the part its parent gave it, with the
// answer placed inside that part on any axis the parent left // answer placed inside that part on any axis the parent left
// open. // open.
let placed = placement(region, answer.0, declared, info.placed, align); let placed = info.placed.placement(region, answer.0, declared, align);
if placed != region { if placed != region {
self.relocate(id, placed, info, rsc); self.relocate(id, placed, info, rsc);
} }
@@ -292,8 +290,8 @@ impl UiRenderState {
// A node entry is only a translation. Its local box keeps the // A node entry is only a translation. Its local box keeps the
// same window-unit length as the box in its parent's node. // same window-unit length as the box in its parent's node.
true => ( true => (
self.move_slot(id, info.parent_move, translation(region)), self.move_slot(id, info.parent_move, region.as_translation()),
local_region(region), region.at_origin(),
None, None,
), ),
// Keep the old entry alive until every descendant has migrated. // Keep the old entry alive until every descendant has migrated.
@@ -377,14 +375,14 @@ impl UiRenderState {
// resolved into the rel base when the child was asked, and resolving it // resolved into the rel base when the child was asked, and resolving it
// again here would take the fraction of a fraction. // again here would take the fraction of a fraction.
let rules = rsc.widgets().size_rules(id); let rules = rsc.widgets().size_rules(id);
let ruled = |axis: Axis, reported: LayoutLen| match rules.axis(axis).exact() { let ruled = |axis: Axis, reported: LayoutLen| match rules[axis].exact() {
None => reported, None => reported,
Some(len) if len.leftover == Weight::ZERO => LayoutLen { Some(len) if len.leftover == Weight::ZERO => LayoutLen {
rel: info.rel_base.axis(axis).rel, rel: info.rel_base[axis].rel,
px: info.rel_base.axis(axis).px, px: info.rel_base[axis].px,
leftover: Weight::ZERO, leftover: Weight::ZERO,
}, },
Some(len) => len.within_len(info.rel_base.axis(axis)), Some(len) => len.within_len(info.rel_base[axis]),
}; };
let size = Size { let size = Size {
x: ruled(Axis::X, size.x), x: ruled(Axis::X, size.x),
@@ -399,7 +397,7 @@ impl UiRenderState {
mask == info.mask mask == info.mask
|| AXES || AXES
.into_iter() .into_iter()
.all(|axis| within_box(size, region, self.output_size, axis)), .all(|axis| size.within_box(region, self.output_size, axis)),
"'{}' ({id:?}) clips to {px:?} and reports {size}", "'{}' ({id:?}) clips to {px:?} and reports {size}",
rsc.widgets().label(id), rsc.widgets().label(id),
); );
@@ -418,17 +416,13 @@ impl UiRenderState {
// rel base's own length, so the answer is that rel base's and not just // rel base's own length, so the answer is that rel base's and not just
// that many pixels of this window -- the same pin a widget that read // that many pixels of this window -- the same pin a widget that read
// its rel base took for its drawing. // its rel base took for its drawing.
let rel_base = AXES.map(|axis| { let mut own_holds = own;
let fraction = rules for axis in AXES {
.axis(axis) let fraction = rules[axis].exact().is_some_and(|len| len.rel != Rel::ZERO);
.exact() if fraction {
.is_some_and(|len| len.rel != Rel::ZERO); own_holds[axis].rel_base = Some(info.rel_base[axis]);
match fraction {
true => Some(info.rel_base.axis(axis)),
false => own.rel_base[axis as usize],
} }
}); }
let own_holds = LayoutHolds { rel_base, ..own };
let answer_holds = own_holds.and(answer_under); let answer_holds = own_holds.and(answer_under);
let holds = under let holds = under
.into_iter() .into_iter()
@@ -485,7 +479,7 @@ impl UiRenderState {
mask_region, mask_region,
children, children,
size_deps, size_deps,
declared: declared_lens(rsc.widgets(), id), declared: rsc.widgets().declared_lens(id),
own_align: rsc.widgets().alignment(id), own_align: rsc.widgets().alignment(id),
move_idx, move_idx,
parent_move: info.parent_move, parent_move: info.parent_move,
@@ -613,21 +607,21 @@ impl UiRenderState {
// Which of the three said no, so a rel base that redraws more // Which of the three said no, so a rel base that redraws more
// than it should says where to look. They overlap: a drawing // than it should says where to look. They overlap: a drawing
// can be outside two of them at once. // can be outside two of them at once.
let holds = active.holds;
for axis in AXES { for axis in AXES {
let n = axis as usize; let holds = active.holds[axis];
if holds.region_len[n].is_some_and(|pinned| pinned != region.axis(axis).len()) { let len = region[axis].len();
let window = self.output_size[axis];
if holds.region_len.is_some_and(|pinned| pinned != len) {
diag::bump(Counter::OutsidePinnedLen); diag::bump(Counter::OutsidePinnedLen);
} }
if !holds.window[n].contains(self.output_size.axis(axis)) if !holds.window.contains(window)
|| holds.rel_base[n] || holds
.is_some_and(|pinned| pinned != info.rel_base.axis(axis)) .rel_base
.is_some_and(|pinned| pinned != info.rel_base[axis])
{ {
diag::bump(Counter::OutsideRelBase); diag::bump(Counter::OutsideRelBase);
} }
if !holds.region[n] if !holds.region.contains(len.to_px(window)) {
.contains(region.axis(axis).len().to_px(self.output_size.axis(axis)))
{
diag::bump(Counter::OutsideRegion); diag::bump(Counter::OutsideRegion);
} }
} }
@@ -652,13 +646,13 @@ impl UiRenderState {
); );
let has_region_node = active.move_idx != active.parent_move; let has_region_node = active.move_idx != active.parent_move;
let local = match has_region_node { let local = match has_region_node {
true => local_region(placed), true => placed.at_origin(),
false => placed, false => placed,
}; };
let moved = active.placement != local; let moved = active.placement != local;
let slot = active.move_idx; let slot = active.move_idx;
if has_region_node { if has_region_node {
self.moves.set(slot, translation(placed)); self.moves.set(slot, placed.as_translation());
} }
if moved { if moved {
self.reposition(id, local, info, rsc); self.reposition(id, local, info, rsc);
@@ -707,11 +701,10 @@ impl UiRenderState {
) { ) {
let active = &self.active[&child]; let active = &self.active[&child];
let (rel_base, region) = Self::ask_again(active, at, place); let (rel_base, region) = Self::ask_again(active, at, place);
let placed = placement( let placed = place.placement(
region, region,
active.measured().unwrap_or(active.size), active.measured().unwrap_or(active.size),
active.declared, active.declared,
place,
active.own_align, active.own_align,
); );
let info = DrawInfo { let info = DrawInfo {
@@ -736,13 +729,7 @@ impl UiRenderState {
/// it declared are its own record's, so both are resolved against that /// it declared are its own record's, so both are resolved against that
/// parent's rel base again exactly as the first ask resolved them. /// parent's rel base again exactly as the first ask resolved them.
fn ask_again(active: &ActiveData, at: &Placing, place: PlaceDesc) -> (UiVec2, UiRegion) { fn ask_again(active: &ActiveData, at: &Placing, place: PlaceDesc) -> (UiVec2, UiRegion) {
rel_base_and_region( place.rel_base_and_region(at.region, at.rel_base, active.declared, active.own_align)
at.region,
at.rel_base,
place,
active.declared,
active.own_align,
)
} }
/// Re-places everything inside a widget whose own box moved. Every child /// Re-places everything inside a widget whose own box moved. Every child
@@ -797,11 +784,8 @@ impl UiRenderState {
let Some(widget) = rsc.widgets().get_dyn(id) else { let Some(widget) = rsc.widgets().get_dyn(id) else {
return true; return true;
}; };
AXES.into_iter().all(|axis| { AXES.into_iter()
widget .all(|axis| widget.size_hint(axis).is_none_or(|hint| hint == size[axis]))
.size_hint(axis)
.is_none_or(|hint| hint == size.axis(axis))
})
} }
/// Takes a widget's record out and frees what it drew. /// Takes a widget's record out and frees what it drew.
@@ -884,7 +868,7 @@ impl UiRenderState {
children: Vec::new(), children: Vec::new(),
size_deps: Vec::new(), size_deps: Vec::new(),
move_idx: info.parent_move, move_idx: info.parent_move,
declared: [None; 2], declared: Declared::NONE,
own_align: rsc.widgets().alignment(id), own_align: rsc.widgets().alignment(id),
parent_move: info.parent_move, parent_move: info.parent_move,
mask: info.mask, mask: info.mask,
@@ -1074,7 +1058,7 @@ impl UiRenderState {
// to draw -- with the mark left on, so the parent draws it rather // to draw -- with the mark left on, so the parent draws it rather
// than keeping it. So is a widget the parent asked twice: its // than keeping it. So is a widget the parent asked twice: its
// layout rests on an answer this widget cannot give again alone. // layout rests on an answer this widget cannot give again alone.
let declared_changed = declared_lens(rsc.widgets(), id) != active.declared; let declared_changed = rsc.widgets().declared_lens(id) != active.declared;
let alignment_changed = rsc.widgets().alignment(id) != active.own_align; let alignment_changed = rsc.widgets().alignment(id) != active.own_align;
if let Some(parent) = active.parent if let Some(parent) = active.parent
&& (declared_changed && (declared_changed
@@ -1189,29 +1173,33 @@ impl UiRenderState {
/// Both are lengths of the window, so the comparison is in its pixels. A /// Both are lengths of the window, so the comparison is in its pixels. A
/// share is a length only to whoever divides one, so it is not a claim about /// share is a length only to whoever divides one, so it is not a claim about
/// this box and cannot exceed it. /// this box and cannot exceed it.
fn within_box(size: Size, region: UiRegion, window: PxVec2, axis: Axis) -> bool { impl Size {
let len = size.axis(axis); fn within_box(self, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
let window = window.axis(axis); let len = self[axis];
len.leftover != Weight::ZERO let window = window[axis];
|| Len::from_parts(len.rel, len.px).to_px(window) <= region.axis(axis).len().to_px(window) len.leftover != Weight::ZERO
|| len.without_leftover().to_px(window) <= region[axis].len().to_px(window)
}
} }
/// A box in a fresh region node keeps its window-unit length and starts at impl UiRegion {
/// that node's origin. /// A box in a fresh region node keeps its window-unit length and starts
fn local_region(region: UiRegion) -> UiRegion { /// at that node's origin.
let size = region.size(); fn at_origin(self) -> UiRegion {
UiRegion::new( let size = self.size();
UiSpan::new(Len::ZERO, size.x), UiRegion::new(
UiSpan::new(Len::ZERO, size.y), UiSpan::new(Len::ZERO, size.x),
) UiSpan::new(Len::ZERO, size.y),
} )
}
/// A region node changes only the origin. A full relative span anchored at /// A region node changes only the origin. A full relative span anchored at
/// the box start composes as that translation in both the CPU and shader. /// the box start composes as that translation in both the CPU and shader.
fn translation(region: UiRegion) -> UiRegion { fn as_translation(self) -> UiRegion {
UiRegion { UiRegion {
x: UiSpan::new(region.x.start, region.x.start + Len::FULL), x: UiSpan::new(self.x.start, self.x.start + Len::FULL),
y: UiSpan::new(region.y.start, region.y.start + Len::FULL), y: UiSpan::new(self.y.start, self.y.start + Len::FULL),
}
} }
} }
+28
View File
@@ -93,3 +93,31 @@ macro_rules! impl_op {
} }
pub(crate) use impl_op; pub(crate) use impl_op;
/// `Index<Axis>` for a pair, which is how every pair here is read by axis.
/// The generics clause is given in braces where the type has one.
macro_rules! impl_axis_index {
($({$($gen:tt)*})? $T:ty => $Out:ty) => {
const impl $(<$($gen)*>)? std::ops::Index<crate::Axis> for $T {
type Output = $Out;
fn index(&self, axis: crate::Axis) -> &$Out {
match axis {
crate::Axis::X => &self.x,
crate::Axis::Y => &self.y,
}
}
}
const impl $(<$($gen)*>)? std::ops::IndexMut<crate::Axis> for $T {
fn index_mut(&mut self, axis: crate::Axis) -> &mut $Out {
match axis {
crate::Axis::X => &mut self.x,
crate::Axis::Y => &mut self.y,
}
}
}
};
}
pub(crate) use impl_axis_index;
+27 -20
View File
@@ -1,4 +1,5 @@
use crate::{Axis, LayoutLen, Weight}; use crate::util::impl_axis_index;
use crate::{Axis, LayoutLen, Len};
/// What a widget's length on one axis is, as a rule its parent applies where /// What a widget's length on one axis is, as a rule its parent applies where
/// it draws it rather than an answer the widget gives about itself. /// it draws it rather than an answer the widget gives about itself.
@@ -19,14 +20,9 @@ pub enum SizeRule {
impl SizeRule { impl SizeRule {
/// The length this rule gives without the widget being drawn, if it can /// The length this rule gives without the widget being drawn, if it can
/// give one. `leftover` is never among them: a share is a length only to /// give one.
/// whoever divides one, so it passes up in the reported size instead and pub fn declared(&self) -> Option<Len> {
/// is resolved there. self.exact().and_then(LayoutLen::declared)
pub fn declared(&self) -> Option<LayoutLen> {
match self {
Self::Exact(len) if len.leftover == Weight::ZERO => Some(*len),
_ => None,
}
} }
/// The length this rule gives outright, whatever the widget reports -- /// The length this rule gives outright, whatever the widget reports --
@@ -70,18 +66,29 @@ pub struct SizeRules {
pub y: SizeRule, pub y: SizeRule,
} }
impl SizeRules { impl_axis_index!(SizeRules => SizeRule);
pub fn axis(&self, axis: Axis) -> SizeRule {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn axis_mut(&mut self, axis: Axis) -> &mut SizeRule { /// What a widget's box is on each axis where something says so outright,
match axis { /// before it is drawn: a rule beside it, or a hint it gives about itself.
Axis::X => &mut self.x, /// Whoever draws the widget resolves these against its rel base.
Axis::Y => &mut self.y, ///
/// A [`Len`] rather than a [`LayoutLen`], because a share can never be one
/// -- see [`LayoutLen::declared`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Declared {
pub x: Option<Len>,
pub y: Option<Len>,
}
impl Declared {
pub const NONE: Self = Self { x: None, y: None };
pub fn per_axis(f: impl Fn(Axis) -> Option<Len>) -> Self {
Self {
x: f(Axis::X),
y: f(Axis::Y),
} }
} }
} }
impl_axis_index!(Declared => Option<Len>);
+4 -4
View File
@@ -130,10 +130,10 @@ impl Widgets {
pub fn set_size_rule(&mut self, id: impl IdLike, axis: Axis, rule: SizeRule) { pub fn set_size_rule(&mut self, id: impl IdLike, axis: Axis, rule: SizeRule) {
let id = id.id(); let id = id.id();
let data = self.data_mut(id).unwrap(); let data = self.data_mut(id).unwrap();
if *data.size.axis_mut(axis) == rule { if data.size[axis] == rule {
return; return;
} }
*data.size.axis_mut(axis) = rule; data.size[axis] = rule;
self.needs_redraw.insert(id); self.needs_redraw.insert(id);
} }
@@ -147,10 +147,10 @@ impl Widgets {
pub fn set_alignment(&mut self, id: impl IdLike, axis: Axis, align: AxisAlign) { pub fn set_alignment(&mut self, id: impl IdLike, axis: Axis, align: AxisAlign) {
let id = id.id(); let id = id.id();
let data = self.data_mut(id).unwrap(); let data = self.data_mut(id).unwrap();
if *data.align.axis_mut(axis) == align { if data.align[axis] == align {
return; return;
} }
*data.align.axis_mut(axis) = align; data.align[axis] = align;
self.needs_redraw.insert(id); self.needs_redraw.insert(id);
} }
+4 -4
View File
@@ -118,9 +118,9 @@ pub struct Branch {
impl Widget for Branch { impl Widget for Branch {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
let top = PlaceDescAxis::shifted(UiSpan::new(Len::ZERO, cut)); let top = UiSpan::new(Len::ZERO, cut).shifted_desc();
let measured = painter let measured = painter
.widget_at(&self.probe, PlaceDesc::new(PlaceDescAxis::WHOLE, top)) .widget_at(&self.probe, top.axis(Axis::Y))
.len(Axis::X); .len(Axis::X);
let len = measured.apply_leftover(); let len = measured.apply_leftover();
let px = painter.to_px(len, Axis::X); let px = painter.to_px(len, Axis::X);
@@ -134,8 +134,8 @@ impl Widget for Branch {
}; };
painter.window_holds(Axis::X, holds.through(len)); painter.window_holds(Axis::X, holds.through(len));
let below = PlaceDescAxis::shifted(UiSpan::new(cut, painter.region_len(Axis::Y))); let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
let place = PlaceDesc::new(PlaceDescAxis::WHOLE, below); let place = below.axis(Axis::Y);
match px > threshold { match px > threshold {
true => painter.widget_at(&self.wide, place), true => painter.widget_at(&self.wide, place),
false => painter.widget_at(&self.narrow, place), false => painter.widget_at(&self.narrow, place),
+1 -1
View File
@@ -12,7 +12,7 @@ impl Widget for Image {
} }
fn size_hint(&self, axis: Axis) -> Option<LayoutLen> { fn size_hint(&self, axis: Axis) -> Option<LayoutLen> {
Some(LayoutLen::px(self.handle.size().axis(axis))) Some(LayoutLen::px(self.handle.size()[axis]))
} }
} }
+5 -8
View File
@@ -16,7 +16,7 @@ impl Widget for Scroll {
let answer_len = painter let answer_len = painter
.widget_at(&self.inner, PlaceDesc::WHOLE.fills()) .widget_at(&self.inner, PlaceDesc::WHOLE.fills())
.len(self.axis); .len(self.axis);
let fixed = painter.to_px(Len::from_parts(answer_len.rel, answer_len.px), self.axis); let fixed = painter.to_px(answer_len.without_leftover(), self.axis);
self.container_len = container_len; self.container_len = container_len;
self.content_len = fixed.max(container_len); self.content_len = fixed.max(container_len);
@@ -24,14 +24,14 @@ impl Widget for Scroll {
self.amt = self.content_len - self.container_len; self.amt = self.content_len - self.container_len;
} }
self.update_amt(); self.update_amt();
let align = painter.alignment().axis(self.axis); let align = painter.alignment()[self.axis];
// Content of a fixed length that fits sits at the start of any box it // Content of a fixed length that fits sits at the start of any box it
// fits in -- but only anchored there. Anywhere else it is a part of // fits in -- but only anchored there. Anywhere else it is a part of
// the room left over, so it moves with every length the box takes and // the room left over, so it moves with every length the box takes and
// the drawing holds for that length alone. One scrolled part way sits // the drawing holds for that length alone. One scrolled part way sits
// where it is until the box shrinks past what is left of it. Kept to // where it is until the box shrinks past what is left of it. Kept to
// the end, it moves with every length. // the end, it moves with every length.
let fixed_len = answer_len.rel == Rel::ZERO && answer_len.leftover == Weight::ZERO; let fixed_len = answer_len.is_px();
if fixed_len && self.content_len <= self.container_len && align == AxisAlign::NEG { if fixed_len && self.content_len <= self.container_len && align == AxisAlign::NEG {
painter.holds(self.axis, fixed..=Px::MAX); painter.holds(self.axis, fixed..=Px::MAX);
} else if fixed_len && !self.snap_end { } else if fixed_len && !self.snap_end {
@@ -54,7 +54,7 @@ impl Widget for Scroll {
let content = match moved || self.content_len != self.container_len { let content = match moved || self.content_len != self.container_len {
true => { true => {
let start = Len::from_parts(Rel::ZERO, anchor - self.amt); let start = Len::from_parts(Rel::ZERO, anchor - self.amt);
PlaceDescAxis::shifted(UiSpan::new(start, start.offset(self.content_len))) UiSpan::new(start, start.offset(self.content_len)).shifted_desc()
} }
false => PlaceDescAxis::WHOLE, false => PlaceDescAxis::WHOLE,
}; };
@@ -62,10 +62,7 @@ impl Widget for Scroll {
// reports is a fraction of what is on screen rather than of the // reports is a fraction of what is on screen rather than of the
// content box its own answer decided. Where it goes is the content // content box its own answer decided. Where it goes is the content
// box, scrolled: its drawing moved there, not made again there. // box, scrolled: its drawing moved there, not made again there.
painter.place_at( painter.place_at(&self.inner, content.axis(self.axis).fills());
&self.inner,
PlaceDesc::from_axis(self.axis, content.fills(), PlaceDescAxis::WHOLE.fills()),
);
// What it occupies is its box, on both axes: it clips its content to // What it occupies is its box, on both axes: it clips its content to
// that box, so it can neither take less of one nor honestly ask for // that box, so it can neither take less of one nor honestly ask for
// more. The content's length is what it scrolls through, not what it // more. The content's length is what it scrolls through, not what it
+17 -29
View File
@@ -18,10 +18,6 @@ impl Widget for Span {
Sign::Pos => UiSpan::new(from, to), Sign::Pos => UiSpan::new(from, to),
Sign::Neg => UiSpan::new(far - to, far - from), Sign::Neg => UiSpan::new(far - to, far - from),
}; };
// Across itself the child sits where its own alignment says, in the
// whole of the row: a span is what contains its children there, and
// nothing divides that axis.
let across = PlaceDescAxis::WHOLE;
// A length for every child before their final slots are chosen: from // A length for every child before their final slots are chosen: from
// a hint where one says, and from drawing otherwise. The rel base passes // a hint where one says, and from drawing otherwise. The rel base passes
// through unchanged, so `rel(0.5)` is half the area this span was // through unchanged, so `rel(0.5)` is half the area this span was
@@ -34,10 +30,11 @@ impl Widget for Span {
let len = match painter.size_hint(child, axis) { let len = match painter.size_hint(child, axis) {
Some(len) => len, Some(len) => len,
None => { None => {
let room = PlaceDescAxis::shifted(along(cursor, far)); // Across itself the child sits where its own alignment
painter // says, in the whole of the row: a span is what contains
.widget_at(child, PlaceDesc::from_axis(axis, room, across)) // its children there, and nothing divides that axis.
.len(axis) let room = along(cursor, far).shifted_desc().axis(axis);
painter.widget_at(child, room).len(axis)
} }
}; };
cursor.px += len.px + self.gap; cursor.px += len.px + self.gap;
@@ -58,7 +55,7 @@ impl Widget for Span {
// What is left for the shares to divide: the row less everything // What is left for the shares to divide: the row less everything
// fixed, as a length of the rel base rather than a number of pixels. // fixed, as a length of the rel base rather than a number of pixels.
let room = far - Len::from_parts(total.rel, total.px); let room = far - total.without_leftover();
// Whether anything is left over is a question in pixels: `rel(0.5)` // Whether anything is left over is a question in pixels: `rel(0.5)`
// beside 300 px is full at 600 and overfull at 400. Asked of `room` // beside 300 px is full at 600 and overfull at 400. Asked of `room`
// itself, and answered back through the same expression, so the // itself, and answered back through the same expression, so the
@@ -94,24 +91,26 @@ impl Widget for Span {
let mut taken = Weight::ZERO; let mut taken = Weight::ZERO;
let mut start = Len::rel_min(); let mut start = Len::rel_min();
let mut ortho = LayoutLen::ZERO; let mut ortho = LayoutLen::ZERO;
let shared = |fixed: Len, taken: Weight| match taken == Weight::ZERO {
true => fixed,
false => fixed + room.scale(Rel::ratio(taken, total.leftover)),
};
for (child, &len) in self.children.iter().zip(&lens) { for (child, &len) in self.children.iter().zip(&lens) {
// A child asking for nothing but a part of what is left over, // A child asking for nothing but a part of what is left over,
// when nothing is, is not drawn at all. One that also asked for // when nothing is, is not drawn at all. One that also asked for
// pixels or a fraction keeps those and overflows. // pixels or a fraction keeps those and overflows.
if len.leftover > Weight::ZERO && len.px == Px::ZERO && len.rel == Rel::ZERO && !shares if len.is_only_leftover() && !shares {
{
painter.undraw(child); painter.undraw(child);
fixed.px += self.gap; fixed.px += self.gap;
start = shared(fixed, taken, total.leftover, room); start = shared(fixed, taken);
continue; continue;
} }
let from = start; let from = start;
if len.leftover > Weight::ZERO && shares { if len.leftover > Weight::ZERO && shares {
taken += len.leftover; taken += len.leftover;
} }
fixed.px += len.px; fixed += len.without_leftover();
fixed.rel += len.rel; start = shared(fixed, taken);
start = shared(fixed, taken, total.leftover, room);
// Along the row the span says where the child goes, and that slot // Along the row the span says where the child goes, and that slot
// is the child's box outright rather than something to place an // is the child's box outright rather than something to place an
// answer inside again. A share is decided here and nowhere // answer inside again. A share is decided here and nowhere
@@ -120,8 +119,7 @@ impl Widget for Span {
// fixed child's slot is its own answer, so a drawing made in the // fixed child's slot is its own answer, so a drawing made in the
// room is put there as it is, and one not made yet is made here. // room is put there as it is, and one not made yet is made here.
let slot = along(from, start); let slot = along(from, start);
let slot_place = PlaceDescAxis::shifted(slot).fills(); let mut place = slot.shifted_desc().fills().axis(axis);
let mut place = PlaceDesc::from_axis(axis, slot_place, across);
if len.leftover > Weight::ZERO && shares { if len.leftover > Weight::ZERO && shares {
place = place.rel_base(axis, slot.len()); place = place.rel_base(axis, slot.len());
} }
@@ -131,14 +129,14 @@ impl Widget for Span {
// span's own eventual width admits multiple fixed points. // span's own eventual width admits multiple fixed points.
// A scalable child therefore makes Children scalable too; // A scalable child therefore makes Children scalable too;
// only fixed children are compared with one another. // only fixed children are compared with one another.
if used.rel != Rel::ZERO || used.leftover != Weight::ZERO { if !used.is_px() {
ortho = LayoutLen::LEFTOVER; ortho = LayoutLen::LEFTOVER;
} else if ortho.leftover == Weight::ZERO { } else if ortho.leftover == Weight::ZERO {
ortho.px = ortho.px.max(used.px); ortho.px = ortho.px.max(used.px);
} }
} }
fixed.px += self.gap; fixed.px += self.gap;
start = shared(fixed, taken, total.leftover, room); start = shared(fixed, taken);
} }
// Carried whole rather than collapsed to one share: a span that sizes // Carried whole rather than collapsed to one share: a span that sizes
@@ -157,16 +155,6 @@ impl Widget for Span {
} }
} }
/// Where a row has reached: everything fixed before this point, which is a
/// sum and exact, plus the share of the room the weights so far are worth,
/// which is one rounding wherever it is asked for.
fn shared(fixed: Len, taken: Weight, weight: Weight, room: Len) -> Len {
if taken == Weight::ZERO {
return fixed;
}
fixed + room.scale(Rel::ratio(taken, weight))
}
impl Span { impl Span {
pub fn empty(dir: Dir) -> Self { pub fn empty(dir: Dir) -> Self {
Self { Self {
+4 -5
View File
@@ -31,14 +31,13 @@ impl Widget for Stack {
// fraction under them is a fraction of it. A share leaves the axis // fraction under them is a fraction of it. A share leaves the axis
// to whoever gave the stack its box. Where a child sits in a box // to whoever gave the stack its box. Where a child sits in a box
// bigger than itself is its own business. // bigger than itself is its own business.
let on = |axis| { let place = PlaceDesc::per_axis(|axis| {
let len = size.axis(axis); let len = size[axis];
match len.leftover == Weight::ZERO { match len.leftover == Weight::ZERO {
true => PlaceDescAxis::sized(Len::from_parts(len.rel, len.px)).fills(), true => len.without_leftover().as_desc().fills(),
false => PlaceDescAxis::WHOLE, false => PlaceDescAxis::WHOLE,
} }
}; });
let place = PlaceDesc::new(on(Axis::X), on(Axis::Y));
for (i, child) in self.children.iter().enumerate() { for (i, child) in self.children.iter().enumerate() {
if sizing == Some(i) { if sizing == Some(i) {
continue; continue;
+4 -4
View File
@@ -22,14 +22,14 @@ struct BranchesOnMeasurement {
impl Widget for BranchesOnMeasurement { impl Widget for BranchesOnMeasurement {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
let top = PlaceDescAxis::shifted(UiSpan::new(Len::ZERO, cut)); let top = UiSpan::new(Len::ZERO, cut).shifted_desc();
let measured = painter let measured = painter
.widget_at(&self.probe, PlaceDesc::new(PlaceDescAxis::WHOLE, top)) .widget_at(&self.probe, top.axis(Axis::Y))
.len(Axis::X); .len(Axis::X);
let px = painter.to_px(measured.apply_leftover(), Axis::X); let px = painter.to_px(measured.apply_leftover(), Axis::X);
let below = PlaceDescAxis::shifted(UiSpan::new(cut, painter.region_len(Axis::Y))); let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
let place = PlaceDesc::new(PlaceDescAxis::WHOLE, below); let place = below.axis(Axis::Y);
match px > Px::from_f32(self.threshold) { match px > Px::from_f32(self.threshold) {
true => painter.widget_at(&self.wide, place), true => painter.widget_at(&self.wide, place),
false => painter.widget_at(&self.narrow, place), false => painter.widget_at(&self.narrow, place),
+4 -4
View File
@@ -501,10 +501,10 @@ fn a_row_of_equal_shares_fills_it_exactly() {
fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) { fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
let active = &h.render.active[&id]; let active = &h.render.active[&id];
let region = h.render.moves.resolve(active.move_idx, active.placement); let region = h.render.moves.resolve(active.move_idx, active.placement);
let dim = h.size().axis(axis); let dim = h.size()[axis];
let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor(); let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor();
let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32()); let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32());
let span = region.axis(axis); let span = region[axis];
(edge(span.start), edge(span.end)) (edge(span.start), edge(span.end))
} }
@@ -853,8 +853,8 @@ fn a_collapsed_share_keeps_the_gaps_before_the_next_slot() {
Sign::Pos => (400 - tail_len, 400), Sign::Pos => (400 - tail_len, 400),
Sign::Neg => (0, tail_len), Sign::Neg => (0, tail_len),
}; };
assert_eq!(region.top_left.axis(dir.axis), Px::from_int(from)); assert_eq!(region.top_left[dir.axis], Px::from_int(from));
assert_eq!(region.bot_right.axis(dir.axis), Px::from_int(to)); assert_eq!(region.bot_right[dir.axis], Px::from_int(to));
} }
} }
} }
+13 -24
View File
@@ -214,10 +214,7 @@ impl Widget for FromHint {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let len = painter.size_hint(&self.inner, Axis::Y).unwrap(); let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px)); let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px));
painter.widget_at( painter.widget_at(&self.inner, top.shifted_desc().axis(Axis::Y));
&self.inner,
PlaceDesc::new(PlaceDescAxis::WHOLE, PlaceDescAxis::shifted(top)),
);
Size::LEFTOVER Size::LEFTOVER
} }
} }
@@ -876,10 +873,7 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
painter.widget_at( painter.widget_at(
&self.child, &self.child,
PlaceDesc::new( PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()),
PlaceDescAxis::shifted(self.region.x),
PlaceDescAxis::shifted(self.region.y),
),
); );
Size::LEFTOVER Size::LEFTOVER
} }
@@ -965,8 +959,8 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
painter.widget_at( painter.widget_at(
&self.child, &self.child,
PlaceDesc::new( PlaceDesc::new(
PlaceDescAxis::shifted(self.region.x).fills(), self.region.x.shifted_desc().fills(),
PlaceDescAxis::shifted(self.region.y).fills(), self.region.y.shifted_desc().fills(),
) )
.rel_base(Axis::X, self.frame.x.len()), .rel_base(Axis::X, self.frame.x.len()),
); );
@@ -1135,8 +1129,8 @@ fn padding_and_stack_boxes_follow_the_region_without_drawing_again() {
painter.widget_at( painter.widget_at(
&self.child, &self.child,
PlaceDesc::new( PlaceDesc::new(
PlaceDescAxis::shifted(self.region.x).fills(), self.region.x.shifted_desc().fills(),
PlaceDescAxis::shifted(self.region.y).fills(), self.region.y.shifted_desc().fills(),
), ),
); );
Size::LEFTOVER Size::LEFTOVER
@@ -1222,12 +1216,10 @@ fn moving_a_childs_region_preserves_the_slot_chosen_from_its_measurement() {
painter.widget_at( painter.widget_at(
&self.child, &self.child,
PlaceDesc::new( PlaceDesc::new(
PlaceDescAxis::shifted(UiSpan::new( UiSpan::new(Len::px(self.start), Len::px(self.start + 200.0))
Len::px(self.start), .shifted_desc()
Len::px(self.start + 200.0), .fills(),
)) UiSpan::FULL.shifted_desc().fills(),
.fills(),
PlaceDescAxis::shifted(UiSpan::FULL).fills(),
), ),
); );
Size::LEFTOVER Size::LEFTOVER
@@ -1263,10 +1255,7 @@ fn changing_regions_keep_fractional_reports_and_numeric_dependencies_valid() {
painter painter
.widget_at( .widget_at(
&self.child, &self.child,
PlaceDesc::new( PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()),
PlaceDescAxis::shifted(self.region.x),
PlaceDescAxis::shifted(self.region.y),
),
) )
.size() .size()
} }
@@ -1283,8 +1272,8 @@ fn changing_regions_keep_fractional_reports_and_numeric_dependencies_valid() {
.widget_at( .widget_at(
&self.child, &self.child,
PlaceDesc::new( PlaceDesc::new(
PlaceDescAxis::shifted(self.region.x).fills(), self.region.x.shifted_desc().fills(),
PlaceDescAxis::shifted(self.region.y).fills(), self.region.y.shifted_desc().fills(),
), ),
) )
.size(), .size(),