diff --git a/core/src/lib.rs b/core/src/lib.rs index 22dd198..e2218f3 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -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; diff --git a/core/src/orientation/align.rs b/core/src/orientation/align.rs index 1ce2dd6..6a10cc6 100644 --- a/core/src/orientation/align.rs +++ b/core/src/orientation/align.rs @@ -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); diff --git a/core/src/orientation/axis.rs b/core/src/orientation/axis.rs index 279484d..7e73f41 100644 --- a/core/src/orientation/axis.rs +++ b/core/src/orientation/axis.rs @@ -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 FixedVec2 { - pub const fn axis(&self, axis: Axis) -> Fixed { - match axis { - Axis::X => self.x, - Axis::Y => self.y, - } - } - - pub const fn axis_mut(&mut self, axis: Axis) -> &mut Fixed { - match axis { - Axis::X => &mut self.x, - Axis::Y => &mut self.y, - } - } - pub const fn from_axis(axis: Axis, aligned: Fixed, ortho: Fixed) -> Self { match axis { Axis::X => Self::new(aligned, ortho), @@ -76,20 +63,6 @@ impl FixedVec2 { } 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 BothAxis { } } } + +impl_axis_index!({const SHIFT: u32} FixedVec2 => Fixed); +impl_axis_index!(Vec2 => f32); diff --git a/core/src/orientation/len.rs b/core/src/orientation/len.rs index cd6059e..c272467 100644 --- a/core/src/orientation/len.rs +++ b/core/src/orientation/len.rs @@ -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); diff --git a/core/src/orientation/pos.rs b/core/src/orientation/pos.rs index ca6be56..fa9fd8a 100644 --- a/core/src/orientation/pos.rs +++ b/core/src/orientation/pos.rs @@ -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); diff --git a/core/src/ui/holds.rs b/core/src/ui/holds.rs index 067ef31..a9791f8 100644 --- a/core/src/ui/holds.rs +++ b/core/src/ui/holds.rs @@ -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), diff --git a/core/src/ui/layout_holds.rs b/core/src/ui/layout_holds.rs index 3a1a494..7ba7e3b 100644 --- a/core/src/ui/layout_holds.rs +++ b/core/src/ui/layout_holds.rs @@ -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, + pub region: Holds, + pub region_len: Option, +} + +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; 2], - pub region: [Holds; 2], - pub region_len: [Option; 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); diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index 489ff39..ba174e3 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -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(&self, id: &StrongWidget) -> [Option; 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) { - 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) { 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 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; 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; 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, 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, 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, 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; 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; 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; 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; 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) } diff --git a/core/src/ui/place.rs b/core/src/ui/place.rs index 7e61d7b..19ffae6 100644 --- a/core/src/ui/place.rs +++ b/core/src/ui/place.rs @@ -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 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); diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 9c2160f..031431d 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -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), + } } } diff --git a/core/src/util/math.rs b/core/src/util/math.rs index 95e7eef..cf251c1 100644 --- a/core/src/util/math.rs +++ b/core/src/util/math.rs @@ -93,3 +93,31 @@ macro_rules! impl_op { } pub(crate) use impl_op; + +/// `Index` 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 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 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; diff --git a/core/src/widget/size_rule.rs b/core/src/widget/size_rule.rs index d046ed4..d3a0f5e 100644 --- a/core/src/widget/size_rule.rs +++ b/core/src/widget/size_rule.rs @@ -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); diff --git a/core/src/widget/widgets.rs b/core/src/widget/widgets.rs index 6856d24..3dc044d 100644 --- a/core/src/widget/widgets.rs +++ b/core/src/widget/widgets.rs @@ -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); } diff --git a/src/random.rs b/src/random.rs index 5c49e30..6bf939c 100644 --- a/src/random.rs +++ b/src/random.rs @@ -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), diff --git a/src/widget/image.rs b/src/widget/image.rs index c6bb62e..50bd1ff 100644 --- a/src/widget/image.rs +++ b/src/widget/image.rs @@ -12,7 +12,7 @@ impl Widget for Image { } fn size_hint(&self, axis: Axis) -> Option { - Some(LayoutLen::px(self.handle.size().axis(axis))) + Some(LayoutLen::px(self.handle.size()[axis])) } } diff --git a/src/widget/position/scroll.rs b/src/widget/position/scroll.rs index ab369b0..7e66cc8 100644 --- a/src/widget/position/scroll.rs +++ b/src/widget/position/scroll.rs @@ -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 diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index 435ccf2..b99ef0b 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -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 { diff --git a/src/widget/position/stack.rs b/src/widget/position/stack.rs index 17d15ff..a5a6323 100644 --- a/src/widget/position/stack.rs +++ b/src/widget/position/stack.rs @@ -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; diff --git a/tests/cases/determinism.rs b/tests/cases/determinism.rs index 3c78c16..fd98eb3 100644 --- a/tests/cases/determinism.rs +++ b/tests/cases/determinism.rs @@ -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), diff --git a/tests/cases/layout.rs b/tests/cases/layout.rs index a62d857..2435775 100644 --- a/tests/cases/layout.rs +++ b/tests/cases/layout.rs @@ -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)); } } } diff --git a/tests/cases/retained.rs b/tests/cases/retained.rs index a9254fc..c95f213 100644 --- a/tests/cases/retained.rs +++ b/tests/cases/retained.rs @@ -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(),