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
@@ -9,6 +9,7 @@
|
||||
#![feature(unsize)]
|
||||
#![feature(coerce_unsized)]
|
||||
#![feature(option_into_flat_iter)]
|
||||
#![feature(const_index)]
|
||||
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
pub mod layout_diagnostics;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Px, Rel};
|
||||
|
||||
use super::*;
|
||||
@@ -89,20 +90,6 @@ impl RegionAlign {
|
||||
x: 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 {
|
||||
@@ -231,3 +218,5 @@ impl RegionAlign {
|
||||
UiVec2::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(RegionAlign => AxisAlign);
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Fixed, FixedVec2};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
@@ -53,20 +54,6 @@ pub enum Sign {
|
||||
}
|
||||
|
||||
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 {
|
||||
match axis {
|
||||
Axis::X => Self::new(aligned, ortho),
|
||||
@@ -76,20 +63,6 @@ impl<const SHIFT: u32> FixedVec2<SHIFT> {
|
||||
}
|
||||
|
||||
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 {
|
||||
Self {
|
||||
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);
|
||||
+25
-15
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Px, PxVec2, Rel, UiNum, Weight, util::impl_op};
|
||||
|
||||
#[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 {
|
||||
@@ -158,11 +145,32 @@ impl LayoutLen {
|
||||
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
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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.
|
||||
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 {
|
||||
px: part.px,
|
||||
rel: part.rel,
|
||||
@@ -236,3 +244,5 @@ impl std::fmt::Display for LayoutLen {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(Size => LayoutLen);
|
||||
+13
-28
@@ -1,3 +1,4 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use std::{fmt::Display, marker::Destruct};
|
||||
|
||||
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
|
||||
/// being one and becomes a place.
|
||||
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 {
|
||||
self.end - self.start
|
||||
}
|
||||
@@ -348,20 +344,6 @@ impl UiRegion {
|
||||
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) {
|
||||
match axis {
|
||||
Axis::X => self.x.flip(),
|
||||
@@ -462,3 +444,6 @@ impl Display for PixelRegion {
|
||||
write!(f, "{} -> {}", self.top_left, self.bot_right)
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(UiVec2 => Len);
|
||||
impl_axis_index!(UiRegion => UiSpan);
|
||||
@@ -33,6 +33,12 @@ impl Holds {
|
||||
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 {
|
||||
Self {
|
||||
lo: self.lo.max(other.lo),
|
||||
|
||||
+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);
|
||||
+118
-119
@@ -3,7 +3,7 @@ use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel,
|
||||
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::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||
TexturePrimitive,
|
||||
@@ -157,10 +157,10 @@ impl<'a> Painter<'a> {
|
||||
/// only moves its child does not pin its drawing to a rel base.
|
||||
fn state_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
|
||||
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 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
|
||||
@@ -186,7 +186,7 @@ impl<'a> Painter<'a> {
|
||||
let declared = self.declared_lens(id);
|
||||
let align = self.rsc.widgets().alignment(id.id());
|
||||
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")]
|
||||
if region_node {
|
||||
diag::bump(Counter::RegionNodeDraws);
|
||||
@@ -258,7 +258,7 @@ impl<'a> Painter<'a> {
|
||||
let place = self.state_rel_base(place.into());
|
||||
let states_rel_base = AXES
|
||||
.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()) {
|
||||
return self.widget_at(id, place);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ impl<'a> Painter<'a> {
|
||||
/// that comes of them is kept on the child, and `redraw` compares it
|
||||
/// there.
|
||||
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] {
|
||||
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,
|
||||
@@ -304,12 +304,12 @@ impl<'a> Painter<'a> {
|
||||
let widgets = self.rsc.widgets();
|
||||
// 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.
|
||||
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
|
||||
let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
|
||||
widgets
|
||||
.get_dyn(id.id())
|
||||
.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));
|
||||
#[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
|
||||
// left to see it by.
|
||||
if hint.rel != Rel::ZERO {
|
||||
self.own.rel_base[axis as usize] = Some(rel_base);
|
||||
self.own[axis].rel_base = Some(rel_base);
|
||||
}
|
||||
}
|
||||
resolved
|
||||
@@ -392,8 +392,8 @@ impl<'a> Painter<'a> {
|
||||
/// again. One axis at a time, because a container that divides one axis
|
||||
/// holds for any length of the other.
|
||||
pub fn region_len(&mut self, axis: Axis) -> Len {
|
||||
let len = self.region.axis(axis).len();
|
||||
self.own.region_len[axis as usize] = Some(len);
|
||||
let len = self.region[axis].len();
|
||||
self.own[axis].region_len = Some(len);
|
||||
len
|
||||
}
|
||||
|
||||
@@ -403,8 +403,8 @@ impl<'a> Painter<'a> {
|
||||
/// Reading it pins the drawing to that rel base, the way
|
||||
/// [`Self::region_len`] pins it to the box.
|
||||
pub fn rel_base(&mut self, axis: Axis) -> Len {
|
||||
let len = self.rel_base.axis(axis);
|
||||
self.own.rel_base[axis as usize] = Some(len);
|
||||
let len = self.rel_base[axis];
|
||||
self.own[axis].rel_base = Some(len);
|
||||
len
|
||||
}
|
||||
|
||||
@@ -425,10 +425,7 @@ impl<'a> Painter<'a> {
|
||||
/// worth anything, since reading one is also what makes its own size
|
||||
/// depend on it.
|
||||
pub fn has_exact_size(&self, axis: Axis) -> bool {
|
||||
self.rsc
|
||||
.widgets()
|
||||
.size_rules(self.id)
|
||||
.axis(axis)
|
||||
self.rsc.widgets().size_rules(self.id)[axis]
|
||||
.exact()
|
||||
.is_some()
|
||||
}
|
||||
@@ -442,9 +439,9 @@ impl<'a> Painter<'a> {
|
||||
/// One axis of this widget's own box in pixels. Prefer this to
|
||||
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
||||
pub fn px_len(&mut self, axis: Axis) -> Px {
|
||||
let len = self.region.axis(axis).len();
|
||||
let px = len.to_px(self.window.axis(axis));
|
||||
let own = &mut self.own.region[axis as usize];
|
||||
let len = self.region[axis].len();
|
||||
let px = len.to_px(self.window[axis]);
|
||||
let own = &mut self.own[axis].region;
|
||||
if *own == Holds::ANY {
|
||||
*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
|
||||
/// in pixels holds for that one alone until it says otherwise.
|
||||
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();
|
||||
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",
|
||||
self.label(),
|
||||
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
|
||||
@@ -472,9 +469,9 @@ impl<'a> Painter<'a> {
|
||||
/// length is a fraction of it; one that is only pixels is that many
|
||||
/// pixels in any window and pins nothing.
|
||||
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 {
|
||||
let own = &mut self.own.window[axis as usize];
|
||||
let own = &mut self.own[axis].window;
|
||||
if *own == Holds::ANY {
|
||||
*own = Holds::at(window);
|
||||
}
|
||||
@@ -489,12 +486,12 @@ impl<'a> Painter<'a> {
|
||||
pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
let holds = holds.into();
|
||||
debug_assert!(
|
||||
holds.contains(self.window.axis(axis)),
|
||||
holds.contains(self.window[axis]),
|
||||
"'{}' ({:?}) says its drawing holds for windows that leave out this one",
|
||||
self.label(),
|
||||
self.id
|
||||
);
|
||||
self.own.window[axis as usize] = holds;
|
||||
self.own[axis].window = holds;
|
||||
}
|
||||
|
||||
pub fn text_data(&mut self) -> &mut TextData {
|
||||
@@ -553,7 +550,7 @@ impl<W: ?Sized> DrawResult<'_, '_, W> {
|
||||
}
|
||||
|
||||
pub fn len(self, axis: Axis) -> LayoutLen {
|
||||
self.size().axis(axis)
|
||||
self.size()[axis]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,16 +603,18 @@ impl Painter<'_> {
|
||||
) -> LayoutHolds {
|
||||
let mut result = LayoutHolds::ANY;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let declared = declared[axis as usize];
|
||||
let holds = holds[axis];
|
||||
let result = &mut result[axis];
|
||||
// Every read became pixels against the window, so a range on
|
||||
// it is already in this widget's terms.
|
||||
result.window[n] = holds.window[n];
|
||||
let at = *place.axis(axis);
|
||||
result.window = holds.window;
|
||||
let at = place[axis];
|
||||
let reaches = at.stated_rel_base().is_none()
|
||||
&& !at.is_sized()
|
||||
&& declared[n].is_none_or(|len| len.rel != Rel::ZERO);
|
||||
result.rel_base[n] = holds.rel_base[n].and(reaches.then(|| self.rel_base.axis(axis)));
|
||||
match (at.within_span(), declared[n].is_some()) {
|
||||
&& declared.is_none_or(|len| len.rel != Rel::ZERO);
|
||||
result.rel_base = holds.rel_base.and(reaches.then(|| self.rel_base[axis]));
|
||||
match (at.within_span(), declared.is_some()) {
|
||||
// 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
|
||||
// part into a range on this widget's box. A length it pinned
|
||||
@@ -625,10 +624,10 @@ impl Painter<'_> {
|
||||
// widget's own length.
|
||||
(Some(span), false) => {
|
||||
let part_len = span.len();
|
||||
result.region[n] = holds.region[n].through(part_len);
|
||||
result.region_len[n] = holds.region_len[n].map(|pinned| match part_len.rel {
|
||||
result.region = holds.region.through(part_len);
|
||||
result.region_len = holds.region_len.map(|pinned| match part_len.rel {
|
||||
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
|
||||
@@ -636,8 +635,7 @@ impl Painter<'_> {
|
||||
// widget's box reaches it, so what it holds for is a range
|
||||
// on the window and none of it on that box.
|
||||
_ => {
|
||||
result.window[n] =
|
||||
result.window[n].and(holds.region[n].through(region.axis(axis).len()));
|
||||
result.window = result.window.and(holds.region.through(region[axis].len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -645,35 +643,40 @@ impl Painter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a widget declares a length of its box to be. `leftover` is not one: a
|
||||
/// share of what is left over is only a length to the widget dividing one,
|
||||
/// so it passes up in the size instead.
|
||||
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLen>; 2] {
|
||||
let rules = widgets.size_rules(id);
|
||||
let widget = widgets.get_dyn(id);
|
||||
AXES.map(|axis| {
|
||||
rules.axis(axis).declared().or_else(|| {
|
||||
// A hint still narrows the box where no rule does, which is how a
|
||||
// widget with a natural pixel size -- an image, a gap -- gets that
|
||||
// size rather than the whole offer. That is the offer's business
|
||||
// rather than a declaration's, and this falls away once a widget
|
||||
// occupies its reported size inside the box it was offered.
|
||||
widget
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
.filter(|len| len.leftover == Weight::ZERO)
|
||||
impl Widgets {
|
||||
/// What a widget declares a length of its box to be. `leftover` is not
|
||||
/// one: a share of what is left over is only a length to the widget
|
||||
/// dividing one, so it passes up in the size instead.
|
||||
pub(crate) fn declared_lens(&self, id: WidgetId) -> [Option<LayoutLen>; 2] {
|
||||
let rules = self.size_rules(id);
|
||||
let widget = self.get_dyn(id);
|
||||
AXES.map(|axis| {
|
||||
rules[axis].declared().or_else(|| {
|
||||
// A hint still narrows the box where no rule does, which is
|
||||
// how a widget with a natural pixel size -- an image, a gap
|
||||
// -- gets that size rather than the whole offer. That is the
|
||||
// offer's business rather than a declaration's, and this
|
||||
// falls away once a widget occupies its reported size inside
|
||||
// the box it was offered.
|
||||
widget
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
.filter(|len| len.leftover == Weight::ZERO)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether what a widget reported along an axis is the whole of the box it
|
||||
/// is in rather than a part to be placed inside it. A share fills, because a
|
||||
/// share is a length only to whoever divides one, and whoever did is the one
|
||||
/// that handed down this box. A declared axis does too: the rule already gave
|
||||
/// the region its length, and the rule's length is what the widget reports
|
||||
/// there. And an axis the parent decided from the answer is
|
||||
/// the answer already.
|
||||
pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: bool) -> bool {
|
||||
reported.leftover != Weight::ZERO || declared.is_some() || decided
|
||||
impl LayoutLen {
|
||||
/// Whether what a widget reported along an axis is the whole of the box
|
||||
/// it is in rather than a part to be placed inside it. A share fills,
|
||||
/// because a share is a length only to whoever divides one, and whoever
|
||||
/// did is the one that handed down this box. A declared axis does too:
|
||||
/// the rule already gave the region its length, and the rule's length is
|
||||
/// what the widget reports there. And an axis the parent decided from
|
||||
/// the answer is the answer already.
|
||||
pub(crate) fn fills(self, declared: Option<LayoutLen>, 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
|
||||
@@ -685,63 +688,59 @@ 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
|
||||
/// placed in sits and however long it is -- the fraction is resolved once,
|
||||
/// here, against the rel base it was reported of.
|
||||
pub(crate) fn placement(
|
||||
region: UiRegion,
|
||||
size: Size,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
place: PlaceDesc,
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
let mut placed = region;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let reported = size.axis(axis);
|
||||
if fills(reported, declared[n], place.axis(axis).does_fill()) {
|
||||
continue;
|
||||
impl PlaceDesc {
|
||||
pub(crate) fn placement(
|
||||
self,
|
||||
region: UiRegion,
|
||||
size: Size,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
let mut placed = region;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let reported = size[axis];
|
||||
if reported.fills(declared[n], self[axis].does_fill()) {
|
||||
continue;
|
||||
}
|
||||
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
|
||||
}
|
||||
let len = Len::from_parts(reported.rel, reported.px);
|
||||
let span = placed.axis_mut(axis);
|
||||
span.start += (span.len() - len).scale(align.axis(axis).rel());
|
||||
span.end = span.start + len;
|
||||
placed
|
||||
}
|
||||
placed
|
||||
}
|
||||
|
||||
/// The rel base length and the box a child is asked in, in the coordinates the
|
||||
/// widget asking draws in.
|
||||
///
|
||||
/// `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
|
||||
/// 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
|
||||
/// 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
|
||||
/// what `place` names.
|
||||
pub(crate) fn rel_base_and_region(
|
||||
own: UiRegion,
|
||||
parent_rel_base: UiVec2,
|
||||
place: PlaceDesc,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
align: RegionAlign,
|
||||
) -> (UiVec2, UiRegion) {
|
||||
let given = place.of(own, align);
|
||||
let mut rel_base = parent_rel_base;
|
||||
let mut region = given;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let base = place
|
||||
.axis(axis)
|
||||
.stated_rel_base()
|
||||
.unwrap_or_else(|| parent_rel_base.axis(axis));
|
||||
let len = declared[n]
|
||||
.map(|len| Len::from_parts(len.rel, len.px).within_len(base))
|
||||
.unwrap_or(base);
|
||||
*rel_base.axis_mut(axis) = len;
|
||||
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);
|
||||
/// The rel base length and the box a child is asked in, in the coordinates the
|
||||
/// widget asking draws in.
|
||||
///
|
||||
/// `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
|
||||
/// 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
|
||||
/// 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
|
||||
/// what `place` names.
|
||||
pub(crate) fn rel_base_and_region(
|
||||
self,
|
||||
own: UiRegion,
|
||||
parent_rel_base: UiVec2,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
align: RegionAlign,
|
||||
) -> (UiVec2, UiRegion) {
|
||||
let given = self.of(own, align);
|
||||
let mut rel_base = parent_rel_base;
|
||||
let mut region = given;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let base = self[axis]
|
||||
.stated_rel_base()
|
||||
.unwrap_or_else(|| parent_rel_base[axis]);
|
||||
let len = declared[n]
|
||||
.map(|len| len.without_leftover().within_len(base))
|
||||
.unwrap_or(base);
|
||||
rel_base[axis] = len;
|
||||
if declared[n].is_some() {
|
||||
region[axis] = given[axis].place(len, align[axis]);
|
||||
}
|
||||
}
|
||||
(rel_base, region)
|
||||
}
|
||||
(rel_base, region)
|
||||
}
|
||||
+74
-71
@@ -1,3 +1,4 @@
|
||||
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
|
||||
@@ -35,53 +36,7 @@ enum RelBase {
|
||||
|
||||
impl PlaceDescAxis {
|
||||
/// The whole of the caller's box.
|
||||
pub const WHOLE: Self = Self::within(UiSpan::FULL);
|
||||
|
||||
/// `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),
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -91,6 +46,14 @@ impl PlaceDescAxis {
|
||||
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 {
|
||||
@@ -112,10 +75,7 @@ impl PlaceDescAxis {
|
||||
span.shift(own.start);
|
||||
span
|
||||
}
|
||||
PlaceSpan::Sized(len) => {
|
||||
let start = own.start + (own.len() - len).scale(align.rel());
|
||||
UiSpan::new(start, start + len)
|
||||
}
|
||||
PlaceSpan::Sized(len) => own.place(len, align),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,29 +145,21 @@ impl PlaceDesc {
|
||||
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 fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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`].
|
||||
pub const fn fills(self) -> Self {
|
||||
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`].
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
fn from(region: UiRegion) -> Self {
|
||||
Self::new(
|
||||
PlaceDescAxis::within(region.x),
|
||||
PlaceDescAxis::within(region.y),
|
||||
)
|
||||
Self::new(region.x.within_desc(), region.y.within_desc())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,3 +248,5 @@ pub struct RetainedPrimitive {
|
||||
pub handle: PrimitiveHandle,
|
||||
pub region: UiRegion,
|
||||
}
|
||||
|
||||
impl_axis_index!(PlaceDesc => PlaceDescAxis);
|
||||
+59
-71
@@ -1,6 +1,5 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::ui::painter::{declared_lens, placement, rel_base_and_region};
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Moves,
|
||||
Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan,
|
||||
@@ -194,11 +193,10 @@ impl UiRenderState {
|
||||
/// rules. Nothing above it narrowed anything or chose where it goes, so
|
||||
/// its declaration is the whole of what decides either.
|
||||
fn root_layout(id: WidgetId, widgets: &Widgets) -> (UiVec2, UiRegion) {
|
||||
rel_base_and_region(
|
||||
PlaceDesc::WHOLE.rel_base_and_region(
|
||||
UiRegion::FULL,
|
||||
UiVec2::FULL_SIZE,
|
||||
PlaceDesc::WHOLE,
|
||||
declared_lens(widgets, id),
|
||||
widgets.declared_lens(id),
|
||||
widgets.alignment(id),
|
||||
)
|
||||
}
|
||||
@@ -221,7 +219,7 @@ impl UiRenderState {
|
||||
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||
}
|
||||
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
|
||||
// one bottom-up walk, so anything deeper has settled or deferred to
|
||||
// its own parent, and a deferred one leaves that parent marked.
|
||||
@@ -235,7 +233,7 @@ impl UiRenderState {
|
||||
.then(|| self.retained_answer(id, region, info))
|
||||
.flatten()
|
||||
.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)
|
||||
.map(|()| answer)
|
||||
});
|
||||
@@ -247,7 +245,7 @@ impl UiRenderState {
|
||||
// Where the drawing goes: the part its parent gave it, with the
|
||||
// answer placed inside that part on any axis the parent left
|
||||
// open.
|
||||
let placed = placement(region, answer.0, declared, info.placed, align);
|
||||
let placed = info.placed.placement(region, answer.0, declared, align);
|
||||
if placed != region {
|
||||
self.relocate(id, placed, info, rsc);
|
||||
}
|
||||
@@ -292,8 +290,8 @@ impl UiRenderState {
|
||||
// A node entry is only a translation. Its local box keeps the
|
||||
// same window-unit length as the box in its parent's node.
|
||||
true => (
|
||||
self.move_slot(id, info.parent_move, translation(region)),
|
||||
local_region(region),
|
||||
self.move_slot(id, info.parent_move, region.as_translation()),
|
||||
region.at_origin(),
|
||||
None,
|
||||
),
|
||||
// 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
|
||||
// again here would take the fraction of a fraction.
|
||||
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,
|
||||
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
|
||||
rel: info.rel_base.axis(axis).rel,
|
||||
px: info.rel_base.axis(axis).px,
|
||||
rel: info.rel_base[axis].rel,
|
||||
px: info.rel_base[axis].px,
|
||||
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 {
|
||||
x: ruled(Axis::X, size.x),
|
||||
@@ -399,7 +397,7 @@ impl UiRenderState {
|
||||
mask == info.mask
|
||||
|| AXES
|
||||
.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}",
|
||||
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
|
||||
// that many pixels of this window -- the same pin a widget that read
|
||||
// its rel base took for its drawing.
|
||||
let rel_base = AXES.map(|axis| {
|
||||
let fraction = rules
|
||||
.axis(axis)
|
||||
.exact()
|
||||
.is_some_and(|len| len.rel != Rel::ZERO);
|
||||
match fraction {
|
||||
true => Some(info.rel_base.axis(axis)),
|
||||
false => own.rel_base[axis as usize],
|
||||
let mut own_holds = own;
|
||||
for axis in AXES {
|
||||
let fraction = rules[axis].exact().is_some_and(|len| len.rel != Rel::ZERO);
|
||||
if fraction {
|
||||
own_holds[axis].rel_base = Some(info.rel_base[axis]);
|
||||
}
|
||||
});
|
||||
let own_holds = LayoutHolds { rel_base, ..own };
|
||||
}
|
||||
let answer_holds = own_holds.and(answer_under);
|
||||
let holds = under
|
||||
.into_iter()
|
||||
@@ -485,7 +479,7 @@ impl UiRenderState {
|
||||
mask_region,
|
||||
children,
|
||||
size_deps,
|
||||
declared: declared_lens(rsc.widgets(), id),
|
||||
declared: rsc.widgets().declared_lens(id),
|
||||
own_align: rsc.widgets().alignment(id),
|
||||
move_idx,
|
||||
parent_move: info.parent_move,
|
||||
@@ -613,21 +607,21 @@ impl UiRenderState {
|
||||
// Which of the three said no, so a rel base that redraws more
|
||||
// than it should says where to look. They overlap: a drawing
|
||||
// can be outside two of them at once.
|
||||
let holds = active.holds;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
if holds.region_len[n].is_some_and(|pinned| pinned != region.axis(axis).len()) {
|
||||
let holds = active.holds[axis];
|
||||
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);
|
||||
}
|
||||
if !holds.window[n].contains(self.output_size.axis(axis))
|
||||
|| holds.rel_base[n]
|
||||
.is_some_and(|pinned| pinned != info.rel_base.axis(axis))
|
||||
if !holds.window.contains(window)
|
||||
|| holds
|
||||
.rel_base
|
||||
.is_some_and(|pinned| pinned != info.rel_base[axis])
|
||||
{
|
||||
diag::bump(Counter::OutsideRelBase);
|
||||
}
|
||||
if !holds.region[n]
|
||||
.contains(region.axis(axis).len().to_px(self.output_size.axis(axis)))
|
||||
{
|
||||
if !holds.region.contains(len.to_px(window)) {
|
||||
diag::bump(Counter::OutsideRegion);
|
||||
}
|
||||
}
|
||||
@@ -652,13 +646,13 @@ impl UiRenderState {
|
||||
);
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
let local = match has_region_node {
|
||||
true => local_region(placed),
|
||||
true => placed.at_origin(),
|
||||
false => placed,
|
||||
};
|
||||
let moved = active.placement != local;
|
||||
let slot = active.move_idx;
|
||||
if has_region_node {
|
||||
self.moves.set(slot, translation(placed));
|
||||
self.moves.set(slot, placed.as_translation());
|
||||
}
|
||||
if moved {
|
||||
self.reposition(id, local, info, rsc);
|
||||
@@ -707,11 +701,10 @@ impl UiRenderState {
|
||||
) {
|
||||
let active = &self.active[&child];
|
||||
let (rel_base, region) = Self::ask_again(active, at, place);
|
||||
let placed = placement(
|
||||
let placed = place.placement(
|
||||
region,
|
||||
active.measured().unwrap_or(active.size),
|
||||
active.declared,
|
||||
place,
|
||||
active.own_align,
|
||||
);
|
||||
let info = DrawInfo {
|
||||
@@ -736,13 +729,7 @@ impl UiRenderState {
|
||||
/// 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.
|
||||
fn ask_again(active: &ActiveData, at: &Placing, place: PlaceDesc) -> (UiVec2, UiRegion) {
|
||||
rel_base_and_region(
|
||||
at.region,
|
||||
at.rel_base,
|
||||
place,
|
||||
active.declared,
|
||||
active.own_align,
|
||||
)
|
||||
place.rel_base_and_region(at.region, at.rel_base, active.declared, active.own_align)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
return true;
|
||||
};
|
||||
AXES.into_iter().all(|axis| {
|
||||
widget
|
||||
.size_hint(axis)
|
||||
.is_none_or(|hint| hint == size.axis(axis))
|
||||
})
|
||||
AXES.into_iter()
|
||||
.all(|axis| widget.size_hint(axis).is_none_or(|hint| hint == size[axis]))
|
||||
}
|
||||
|
||||
/// Takes a widget's record out and frees what it drew.
|
||||
@@ -1074,7 +1058,7 @@ impl UiRenderState {
|
||||
// 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
|
||||
// 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;
|
||||
if let Some(parent) = active.parent
|
||||
&& (declared_changed
|
||||
@@ -1189,29 +1173,33 @@ impl UiRenderState {
|
||||
/// 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
|
||||
/// this box and cannot exceed it.
|
||||
fn within_box(size: Size, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||
let len = size.axis(axis);
|
||||
let window = window.axis(axis);
|
||||
len.leftover != Weight::ZERO
|
||||
|| Len::from_parts(len.rel, len.px).to_px(window) <= region.axis(axis).len().to_px(window)
|
||||
impl Size {
|
||||
fn within_box(self, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||
let len = self[axis];
|
||||
let window = window[axis];
|
||||
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
|
||||
/// that node's origin.
|
||||
fn local_region(region: UiRegion) -> UiRegion {
|
||||
let size = region.size();
|
||||
UiRegion::new(
|
||||
UiSpan::new(Len::ZERO, size.x),
|
||||
UiSpan::new(Len::ZERO, size.y),
|
||||
)
|
||||
}
|
||||
impl UiRegion {
|
||||
/// A box in a fresh region node keeps its window-unit length and starts
|
||||
/// at that node's origin.
|
||||
fn at_origin(self) -> UiRegion {
|
||||
let size = self.size();
|
||||
UiRegion::new(
|
||||
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
|
||||
/// the box start composes as that translation in both the CPU and shader.
|
||||
fn translation(region: UiRegion) -> UiRegion {
|
||||
UiRegion {
|
||||
x: UiSpan::new(region.x.start, region.x.start + Len::FULL),
|
||||
y: UiSpan::new(region.y.start, region.y.start + Len::FULL),
|
||||
/// 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.
|
||||
fn as_translation(self) -> UiRegion {
|
||||
UiRegion {
|
||||
x: UiSpan::new(self.x.start, self.x.start + Len::FULL),
|
||||
y: UiSpan::new(self.y.start, self.y.start + Len::FULL),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,3 +93,31 @@ macro_rules! 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;
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{Axis, LayoutLen, Weight};
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{LayoutLen, Weight};
|
||||
|
||||
/// 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.
|
||||
@@ -70,18 +71,4 @@ pub struct SizeRules {
|
||||
pub y: SizeRule,
|
||||
}
|
||||
|
||||
impl SizeRules {
|
||||
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 {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_axis_index!(SizeRules => SizeRule);
|
||||
@@ -130,10 +130,10 @@ impl Widgets {
|
||||
pub fn set_size_rule(&mut self, id: impl IdLike, axis: Axis, rule: SizeRule) {
|
||||
let id = id.id();
|
||||
let data = self.data_mut(id).unwrap();
|
||||
if *data.size.axis_mut(axis) == rule {
|
||||
if data.size[axis] == rule {
|
||||
return;
|
||||
}
|
||||
*data.size.axis_mut(axis) = rule;
|
||||
data.size[axis] = rule;
|
||||
self.needs_redraw.insert(id);
|
||||
}
|
||||
|
||||
@@ -147,10 +147,10 @@ impl Widgets {
|
||||
pub fn set_alignment(&mut self, id: impl IdLike, axis: Axis, align: AxisAlign) {
|
||||
let id = id.id();
|
||||
let data = self.data_mut(id).unwrap();
|
||||
if *data.align.axis_mut(axis) == align {
|
||||
if data.align[axis] == align {
|
||||
return;
|
||||
}
|
||||
*data.align.axis_mut(axis) = align;
|
||||
data.align[axis] = align;
|
||||
self.needs_redraw.insert(id);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -118,9 +118,9 @@ pub struct Branch {
|
||||
impl Widget for Branch {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
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
|
||||
.widget_at(&self.probe, PlaceDesc::new(PlaceDescAxis::WHOLE, top))
|
||||
.widget_at(&self.probe, top.axis(Axis::Y))
|
||||
.len(Axis::X);
|
||||
let len = measured.apply_leftover();
|
||||
let px = painter.to_px(len, Axis::X);
|
||||
@@ -134,8 +134,8 @@ impl Widget for Branch {
|
||||
};
|
||||
painter.window_holds(Axis::X, holds.through(len));
|
||||
|
||||
let below = PlaceDescAxis::shifted(UiSpan::new(cut, painter.region_len(Axis::Y)));
|
||||
let place = PlaceDesc::new(PlaceDescAxis::WHOLE, below);
|
||||
let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
|
||||
let place = below.axis(Axis::Y);
|
||||
match px > threshold {
|
||||
true => painter.widget_at(&self.wide, place),
|
||||
false => painter.widget_at(&self.narrow, place),
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ impl Widget for Image {
|
||||
}
|
||||
|
||||
fn size_hint(&self, axis: Axis) -> Option<LayoutLen> {
|
||||
Some(LayoutLen::px(self.handle.size().axis(axis)))
|
||||
Some(LayoutLen::px(self.handle.size()[axis]))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ impl Widget for Scroll {
|
||||
let answer_len = painter
|
||||
.widget_at(&self.inner, PlaceDesc::WHOLE.fills())
|
||||
.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.content_len = fixed.max(container_len);
|
||||
|
||||
@@ -24,14 +24,14 @@ impl Widget for Scroll {
|
||||
self.amt = self.content_len - self.container_len;
|
||||
}
|
||||
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
|
||||
// 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 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
|
||||
// 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 {
|
||||
painter.holds(self.axis, fixed..=Px::MAX);
|
||||
} 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 {
|
||||
true => {
|
||||
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,
|
||||
};
|
||||
@@ -62,10 +62,7 @@ impl Widget for Scroll {
|
||||
// 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
|
||||
// box, scrolled: its drawing moved there, not made again there.
|
||||
painter.place_at(
|
||||
&self.inner,
|
||||
PlaceDesc::from_axis(self.axis, content.fills(), PlaceDescAxis::WHOLE.fills()),
|
||||
);
|
||||
painter.place_at(&self.inner, content.axis(self.axis).fills());
|
||||
// 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
|
||||
// more. The content's length is what it scrolls through, not what it
|
||||
|
||||
+17
-29
@@ -18,10 +18,6 @@ impl Widget for Span {
|
||||
Sign::Pos => UiSpan::new(from, to),
|
||||
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 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
|
||||
@@ -34,10 +30,11 @@ impl Widget for Span {
|
||||
let len = match painter.size_hint(child, axis) {
|
||||
Some(len) => len,
|
||||
None => {
|
||||
let room = PlaceDescAxis::shifted(along(cursor, far));
|
||||
painter
|
||||
.widget_at(child, PlaceDesc::from_axis(axis, room, across))
|
||||
.len(axis)
|
||||
// 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 room = along(cursor, far).shifted_desc().axis(axis);
|
||||
painter.widget_at(child, room).len(axis)
|
||||
}
|
||||
};
|
||||
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
|
||||
// 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)`
|
||||
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
||||
// 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 start = Len::rel_min();
|
||||
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) {
|
||||
// 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
|
||||
// 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);
|
||||
fixed.px += self.gap;
|
||||
start = shared(fixed, taken, total.leftover, room);
|
||||
start = shared(fixed, taken);
|
||||
continue;
|
||||
}
|
||||
let from = start;
|
||||
if len.leftover > Weight::ZERO && shares {
|
||||
taken += len.leftover;
|
||||
}
|
||||
fixed.px += len.px;
|
||||
fixed.rel += len.rel;
|
||||
start = shared(fixed, taken, total.leftover, room);
|
||||
fixed += len.without_leftover();
|
||||
start = shared(fixed, taken);
|
||||
// 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
|
||||
// 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
|
||||
// room is put there as it is, and one not made yet is made here.
|
||||
let slot = along(from, start);
|
||||
let slot_place = PlaceDescAxis::shifted(slot).fills();
|
||||
let mut place = PlaceDesc::from_axis(axis, slot_place, across);
|
||||
let mut place = slot.shifted_desc().fills().axis(axis);
|
||||
if len.leftover > Weight::ZERO && shares {
|
||||
place = place.rel_base(axis, slot.len());
|
||||
}
|
||||
@@ -131,14 +129,14 @@ impl Widget for Span {
|
||||
// span's own eventual width admits multiple fixed points.
|
||||
// A scalable child therefore makes Children scalable too;
|
||||
// only fixed children are compared with one another.
|
||||
if used.rel != Rel::ZERO || used.leftover != Weight::ZERO {
|
||||
if !used.is_px() {
|
||||
ortho = LayoutLen::LEFTOVER;
|
||||
} else if ortho.leftover == Weight::ZERO {
|
||||
ortho.px = ortho.px.max(used.px);
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -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 {
|
||||
pub fn empty(dir: Dir) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -31,14 +31,13 @@ impl Widget for Stack {
|
||||
// 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
|
||||
// bigger than itself is its own business.
|
||||
let on = |axis| {
|
||||
let len = size.axis(axis);
|
||||
let place = PlaceDesc::per_axis(|axis| {
|
||||
let len = size[axis];
|
||||
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,
|
||||
}
|
||||
};
|
||||
let place = PlaceDesc::new(on(Axis::X), on(Axis::Y));
|
||||
});
|
||||
for (i, child) in self.children.iter().enumerate() {
|
||||
if sizing == Some(i) {
|
||||
continue;
|
||||
|
||||
@@ -22,14 +22,14 @@ struct BranchesOnMeasurement {
|
||||
impl Widget for BranchesOnMeasurement {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
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
|
||||
.widget_at(&self.probe, PlaceDesc::new(PlaceDescAxis::WHOLE, top))
|
||||
.widget_at(&self.probe, top.axis(Axis::Y))
|
||||
.len(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 place = PlaceDesc::new(PlaceDescAxis::WHOLE, below);
|
||||
let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
|
||||
let place = below.axis(Axis::Y);
|
||||
match px > Px::from_f32(self.threshold) {
|
||||
true => painter.widget_at(&self.wide, place),
|
||||
false => painter.widget_at(&self.narrow, place),
|
||||
|
||||
@@ -501,10 +501,10 @@ fn a_row_of_equal_shares_fills_it_exactly() {
|
||||
fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
|
||||
let active = &h.render.active[&id];
|
||||
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 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))
|
||||
}
|
||||
|
||||
@@ -853,8 +853,8 @@ fn a_collapsed_share_keeps_the_gaps_before_the_next_slot() {
|
||||
Sign::Pos => (400 - tail_len, 400),
|
||||
Sign::Neg => (0, tail_len),
|
||||
};
|
||||
assert_eq!(region.top_left.axis(dir.axis), Px::from_int(from));
|
||||
assert_eq!(region.bot_right.axis(dir.axis), Px::from_int(to));
|
||||
assert_eq!(region.top_left[dir.axis], Px::from_int(from));
|
||||
assert_eq!(region.bot_right[dir.axis], Px::from_int(to));
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-24
@@ -214,10 +214,7 @@ impl Widget for FromHint {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
|
||||
let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px));
|
||||
painter.widget_at(
|
||||
&self.inner,
|
||||
PlaceDesc::new(PlaceDescAxis::WHOLE, PlaceDescAxis::shifted(top)),
|
||||
);
|
||||
painter.widget_at(&self.inner, top.shifted_desc().axis(Axis::Y));
|
||||
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 {
|
||||
painter.widget_at(
|
||||
&self.child,
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x),
|
||||
PlaceDescAxis::shifted(self.region.y),
|
||||
),
|
||||
PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
}
|
||||
@@ -965,8 +959,8 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
|
||||
painter.widget_at(
|
||||
&self.child,
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x).fills(),
|
||||
PlaceDescAxis::shifted(self.region.y).fills(),
|
||||
self.region.x.shifted_desc().fills(),
|
||||
self.region.y.shifted_desc().fills(),
|
||||
)
|
||||
.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(
|
||||
&self.child,
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x).fills(),
|
||||
PlaceDescAxis::shifted(self.region.y).fills(),
|
||||
self.region.x.shifted_desc().fills(),
|
||||
self.region.y.shifted_desc().fills(),
|
||||
),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
@@ -1222,12 +1216,10 @@ fn moving_a_childs_region_preserves_the_slot_chosen_from_its_measurement() {
|
||||
painter.widget_at(
|
||||
&self.child,
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(UiSpan::new(
|
||||
Len::px(self.start),
|
||||
Len::px(self.start + 200.0),
|
||||
))
|
||||
.fills(),
|
||||
PlaceDescAxis::shifted(UiSpan::FULL).fills(),
|
||||
UiSpan::new(Len::px(self.start), Len::px(self.start + 200.0))
|
||||
.shifted_desc()
|
||||
.fills(),
|
||||
UiSpan::FULL.shifted_desc().fills(),
|
||||
),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
@@ -1263,10 +1255,7 @@ fn changing_regions_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
painter
|
||||
.widget_at(
|
||||
&self.child,
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x),
|
||||
PlaceDescAxis::shifted(self.region.y),
|
||||
),
|
||||
PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()),
|
||||
)
|
||||
.size()
|
||||
}
|
||||
@@ -1283,8 +1272,8 @@ fn changing_regions_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
.widget_at(
|
||||
&self.child,
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x).fills(),
|
||||
PlaceDescAxis::shifted(self.region.y).fills(),
|
||||
self.region.x.shifted_desc().fills(),
|
||||
self.region.y.shifted_desc().fills(),
|
||||
),
|
||||
)
|
||||
.size(),
|
||||
|
||||
Reference in new issue
Block a user