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>
253 lines
8.8 KiB
Rust
253 lines
8.8 KiB
Rust
use crate::util::impl_axis_index;
|
|
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan};
|
|
|
|
/// How a child's region along one axis comes from the region of the widget
|
|
/// asking, and what its fractions are of.
|
|
///
|
|
/// The three ways of saying a region are the three the geometry already has:
|
|
/// a span composed into the caller's box, a span shifted to where that box
|
|
/// starts, and a length placed in it by alignment. Which one is meant cannot
|
|
/// be read off the numbers, since two of them take the same span and apply
|
|
/// it differently, so it is said here.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct PlaceDescAxis {
|
|
span: PlaceSpan,
|
|
fills: bool,
|
|
rel_base: RelBase,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
enum PlaceSpan {
|
|
Within(UiSpan),
|
|
Shifted(UiSpan),
|
|
Sized(Len),
|
|
}
|
|
|
|
/// What a child's fractions are of, where the caller has not named a length.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
enum RelBase {
|
|
/// The caller's own, unchanged.
|
|
Inherit,
|
|
/// The caller's own, narrowed the way the region is.
|
|
WithRegion,
|
|
/// This length of the window.
|
|
Len(Len),
|
|
}
|
|
|
|
impl PlaceDescAxis {
|
|
/// The whole of the caller's box.
|
|
pub const WHOLE: Self = UiSpan::FULL.within_desc();
|
|
|
|
/// 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
|
|
/// child asked for -- a row placing a child at the length it reported.
|
|
pub const fn fills(mut self) -> Self {
|
|
self.fills = true;
|
|
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
|
|
/// resolved share, or a box a sibling's answer decided.
|
|
pub const fn rel_base(mut self, len: Len) -> Self {
|
|
self.rel_base = RelBase::Len(len);
|
|
self
|
|
}
|
|
|
|
/// Whether the region is the placement outright, rather than a box the
|
|
/// answer is placed inside.
|
|
pub(crate) const fn does_fill(self) -> bool {
|
|
self.fills
|
|
}
|
|
|
|
/// Where it lands in the coordinates `own` is in.
|
|
pub(crate) fn of(self, own: UiSpan, align: AxisAlign) -> UiSpan {
|
|
match self.span {
|
|
PlaceSpan::Within(span) => span.within(&own),
|
|
PlaceSpan::Shifted(mut span) => {
|
|
span.shift(own.start);
|
|
span
|
|
}
|
|
PlaceSpan::Sized(len) => own.place(len, align),
|
|
}
|
|
}
|
|
|
|
/// The child's rel base, where this says one outright. `None` forwards
|
|
/// the caller's own, and [`RelBase::WithRegion`] is resolved by whoever
|
|
/// can read that rel base, so it does not reach here.
|
|
pub(crate) const fn stated_rel_base(self) -> Option<Len> {
|
|
match self.rel_base {
|
|
RelBase::Len(len) => Some(len),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// The length this narrows the caller's rel base by, where it does.
|
|
/// `None` leaves that rel base alone, and reading it is then a
|
|
/// dependency the caller does not take.
|
|
pub(crate) const fn narrows_rel_base(self) -> Option<UiSpan> {
|
|
match (self.rel_base, self.span) {
|
|
(RelBase::WithRegion, PlaceSpan::Within(span)) => Some(span),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// The span it composes into the caller's box, where that is what it
|
|
/// does: the one case whose validity maps back through the part.
|
|
pub(crate) const fn within_span(self) -> Option<UiSpan> {
|
|
match self.span {
|
|
PlaceSpan::Within(span) => Some(span),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Whether the caller decided this length rather than a place along its
|
|
/// own box, which is what stops its length reaching the child at all.
|
|
pub(crate) const fn is_sized(self) -> bool {
|
|
matches!(self.span, PlaceSpan::Sized(_))
|
|
}
|
|
|
|
/// The same, with its rel base stated outright.
|
|
pub(crate) const fn with_rel_base(mut self, len: Option<Len>) -> Self {
|
|
self.rel_base = match len {
|
|
Some(len) => RelBase::Len(len),
|
|
None => RelBase::Inherit,
|
|
};
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Where a child is asked, on both axes. A [`UiRegion`] converts into the
|
|
/// common case: that box of the caller's own, the answer placed inside it.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct PlaceDesc {
|
|
pub x: PlaceDescAxis,
|
|
pub y: PlaceDescAxis,
|
|
}
|
|
|
|
impl PlaceDesc {
|
|
/// The whole of the caller's box, on both axes.
|
|
pub const WHOLE: Self = Self::splat(PlaceDescAxis::WHOLE);
|
|
|
|
pub const fn new(x: PlaceDescAxis, y: PlaceDescAxis) -> Self {
|
|
Self { x, y }
|
|
}
|
|
|
|
/// The same on both axes.
|
|
pub const fn splat(place: PlaceDescAxis) -> Self {
|
|
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
|
|
/// container that divides one axis says what it is doing.
|
|
pub const fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
|
|
match axis {
|
|
Axis::X => Self::new(aligned, ortho),
|
|
Axis::Y => Self::new(ortho, aligned),
|
|
}
|
|
}
|
|
|
|
/// Both regions are the child's placement. See [`PlaceDescAxis::fills`].
|
|
pub const fn fills(self) -> Self {
|
|
Self::new(self.x.fills(), self.y.fills())
|
|
}
|
|
|
|
/// The child's rel base on one axis. See [`PlaceDescAxis::rel_base`].
|
|
pub const fn rel_base(mut self, axis: Axis, len: Len) -> Self {
|
|
self[axis] = self[axis].rel_base(len);
|
|
self
|
|
}
|
|
|
|
/// The box each axis names, in the coordinates `own` is in.
|
|
pub(crate) fn of(self, own: UiRegion, align: RegionAlign) -> UiRegion {
|
|
UiRegion::new(self.x.of(own.x, align.x), self.y.of(own.y, align.y))
|
|
}
|
|
}
|
|
|
|
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 {
|
|
fn from(region: UiRegion) -> Self {
|
|
Self::new(region.x.within_desc(), region.y.within_desc())
|
|
}
|
|
}
|
|
|
|
impl From<PlaceDescAxis> for PlaceDesc {
|
|
fn from(place: PlaceDescAxis) -> Self {
|
|
Self::splat(place)
|
|
}
|
|
}
|
|
|
|
/// A primitive as it was written: its box in the widget's own box's
|
|
/// coordinates, which is what a move of that box re-composes from.
|
|
#[derive(Debug)]
|
|
pub struct RetainedPrimitive {
|
|
pub handle: PrimitiveHandle,
|
|
pub region: UiRegion,
|
|
}
|
|
|
|
impl_axis_index!(PlaceDesc => PlaceDescAxis);
|