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>
This commit is contained in:
1 parent
58ce74dd7d
commit
55df32a33c
21 files changed
+465
-490
No files matched your search
+75
-45
@@ -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];
|
||||
|
||||
/// What one evaluation of a widget depends on: the window lengths its reads
|
||||
/// hold for, the pixel lengths of its own box, and the symbolic lengths of
|
||||
/// that box and of its rel base where either one is what it was expressed in.
|
||||
/// What one evaluation of a widget depends on along one axis: the window
|
||||
/// lengths its reads hold for, the pixel lengths of its own box, and the
|
||||
/// 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
|
||||
/// 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
|
||||
/// whatever the rel base turns out to be.
|
||||
#[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 window: [Holds; 2],
|
||||
pub rel_base: [Option<Len>; 2],
|
||||
pub region: [Holds; 2],
|
||||
pub region_len: [Option<Len>; 2],
|
||||
pub x: AxisHolds,
|
||||
pub y: AxisHolds,
|
||||
}
|
||||
|
||||
impl LayoutHolds {
|
||||
pub const ANY: Self = Self {
|
||||
window: [Holds::ANY; 2],
|
||||
rel_base: [None; 2],
|
||||
region: [Holds::ANY; 2],
|
||||
region_len: [None; 2],
|
||||
x: AxisHolds::ANY,
|
||||
y: AxisHolds::ANY,
|
||||
};
|
||||
|
||||
pub fn and(self, other: Self) -> Self {
|
||||
let mut result = Self::ANY;
|
||||
for n in 0..2 {
|
||||
result.window[n] = self.window[n].and(other.window[n]);
|
||||
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]);
|
||||
Self {
|
||||
x: self.x.and(other.x),
|
||||
y: self.y.and(other.y),
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn covers(self, other: Self) -> bool {
|
||||
(0..2).all(|n| {
|
||||
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))
|
||||
})
|
||||
self.x.covers(other.x) && self.y.covers(other.y)
|
||||
}
|
||||
|
||||
pub fn contains(self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
|
||||
AXES.into_iter().all(|axis| {
|
||||
let n = axis as usize;
|
||||
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)
|
||||
})
|
||||
AXES.into_iter()
|
||||
.all(|axis| self[axis].contains(window[axis], rel_base[axis], region[axis].len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(LayoutHolds => AxisHolds);
|
||||
Reference in new issue
Block a user