diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index d593d7f..c579879 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -187,10 +187,20 @@ impl<'a> Painter<'a> { id: &'s StrongWidget, place: impl Into, ) -> DrawResult<'s, 'a, W> { - let place = self.resolve_rel_base(place.into()); + let mut place = self.resolve_rel_base(place.into()); let region_node = self.rsc.widgets().is_region_node(id.id()); - let declared = self.declared_lens(id); let align = self.rsc.widgets().alignment(id.id()); + // A share fills what the pixels and fraction beside it leave of the + // box and overflows where they are longer, which is the rule a span + // follows with one child. Only the overflow is a box of the child's + // own: a share that fits is the box it was given, which is what this + // place already says. + for axis in Axis::BOTH { + if let Some(len) = self.share_past_the_offer(id.id(), place, align, axis) { + place[axis] = len.as_desc().fills(); + } + } + let declared = self.declared_lens(id); let (rel_base, region) = place.rel_base_and_region(self.region, self.rel_base, declared, align); #[cfg(feature = "layout-diagnostics")] @@ -299,6 +309,45 @@ impl<'a> Painter<'a> { self.rsc.widgets().declared_lens(id.id()) } + /// The box a child's own share asks for where that is longer than the box + /// `place` gives it, and nothing where the share fits. + /// + /// A share is a length only to whoever divides one, and nothing divides a + /// box handed to one child: what is left of it after the pixels and the + /// fraction beside the share is what the share takes, so the length comes + /// to the whole box until those are longer than it and to them once they + /// are. Only that second case is a box this widget did not give, and the + /// crossing between them is a question in pixels, so this widget's drawing + /// holds for the windows on one side of it. Narrowed rather than stated, + /// because this widget may have read its own box as well, and a range it + /// pinned for that still holds. + fn share_past_the_offer( + &mut self, + id: WidgetId, + place: PlaceDesc, + align: RegionAlign, + axis: Axis, + ) -> Option { + // A place that is the child's placement outright is a box its parent + // decided, and a parent that divides one has already given the share + // whatever it was owed. Only an offer -- a box with the answer still + // to be placed inside it -- is a box a share reads. + if place[axis].fills { + return None; + } + // A share with nothing beside it is the box whatever the box is, so + // there is no comparison to make and no range to keep for one. + let stated = self.rsc.widgets().exact_len(id, axis)?; + if stated.leftover == Weight::ZERO || stated.is_only_leftover() { + return None; + } + let fixed = stated + .without_leftover() + .within_len(place.base(axis, self.rel_base)); + let offer = place.of(self.region, align)[axis].len(); + self.longer_than(fixed, offer, axis).then_some(fixed) + } + /// What a child says its length is without being drawn, if it can say, /// as the length its draw would report: a fraction in it is resolved /// against this widget's rel base, which is the rel base a child asked with @@ -475,6 +524,40 @@ impl<'a> Painter<'a> { len.to_px(window) } + /// Whether `len` is longer than `than`, kept as the windows that comparison + /// comes out the same way on: a drawing that took one of two lengths holds + /// where the same one is the longer, and nowhere else. + /// + /// Which is longer is a question in pixels -- `rel(0.5)` is longer than 300 + /// px at a box of 600 and shorter at 400 -- and it is asked of the + /// difference and answered back through that same difference, so the + /// boundary is the drawing's own rather than a second way of finding it. + /// Narrowed rather than stated, because whatever else this widget read + /// about the window is a reason its drawing holds where it does too. + /// + /// This is the one operation a length that is the longer of two needs: the + /// room a container has left for the shares it divides, and a share that + /// overflows the box it was given because the pixels beside it are longer + /// than the box. + pub fn longer_than(&mut self, len: Len, than: Len, axis: Axis) -> bool { + let over = len - than; + let window = self.window[axis]; + let longer = over.to_px(window) > Px::ZERO; + let side = match longer { + true => Px::STEP..=Px::MAX, + false => Px::MIN..=Px::ZERO, + }; + let holds = Holds::from(side).through(over); + debug_assert!( + holds.contains(window), + "'{}' ({:?}) compared two lengths and kept a range without this window", + self.label(), + self.id + ); + self.own[axis].window = self.own[axis].window.and(holds); + longer + } + /// The windows this drawing holds for, stated rather than taken: a /// container that branched on a length in pixels says which side of the /// boundary it was on, which is wider than the one window reading that @@ -725,10 +808,7 @@ impl PlaceDesc { let mut rel_base = parent_rel_base; let mut region = given; for axis in Axis::BOTH { - let base = match self[axis].rel_base { - RelBase::Len(len) => len, - RelBase::Inherit | RelBase::WithRegion => parent_rel_base[axis], - }; + let base = self.base(axis, parent_rel_base); let len = declared[axis] .map(|len| len.within_len(base)) .unwrap_or(base); diff --git a/core/src/ui/place.rs b/core/src/ui/place.rs index b844e87..465e386 100644 --- a/core/src/ui/place.rs +++ b/core/src/ui/place.rs @@ -1,5 +1,5 @@ use crate::util::impl_axis_index; -use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan}; +use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan, UiVec2}; /// How a child's region along one axis comes from the region of the widget /// asking, and what its fractions are of. @@ -123,6 +123,16 @@ impl PlaceDesc { self } + /// What a child's fractions on one axis are of, as a length of the + /// window: a length this place names, or the rel base of the widget + /// giving it, which is `parent_rel_base`. + pub(super) fn base(&self, axis: Axis, parent_rel_base: UiVec2) -> Len { + match self[axis].rel_base { + RelBase::Len(len) => len, + RelBase::Inherit | RelBase::WithRegion => parent_rel_base[axis], + } + } + /// The box each axis names, in the coordinates `own` is in. pub fn of(self, own: UiRegion, align: RegionAlign) -> UiRegion { UiRegion::new(self.x.of(own.x, align.x), self.y.of(own.y, align.y)) diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index 561d8be..727caec 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -52,25 +52,15 @@ 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 = row - 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 - // boundary is the drawing's own and not a second way of finding it: - // the three cases a rounded division needed -- the fixed parts + let all_fixed = total.without_leftover(); + let room = row - all_fixed; + // The three cases a rounded division needed -- the fixed parts // growing slower than the box, faster, or exactly with it -- are the - // sign of `room.rel`, which `through` already reads. What the - // generated oracle checks is the consequence, since which children - // exist at all turns on this. + // sign of `room.rel`, which the range `longer_than` keeps already + // reads. What the generated oracle checks is the consequence, since + // which children exist at all turns on this. let any_leftover = total.leftover > Weight::ZERO; - let has_room = any_leftover && painter.to_px(room, axis) > Px::ZERO; - if any_leftover { - let holds = match has_room { - true => Holds::from(Px::STEP..=Px::MAX), - false => Holds::from(Px::MIN..=Px::ZERO), - }; - painter.window_holds(axis, holds.through(room)); - } + let has_room = any_leftover && painter.longer_than(row, all_fixed, axis); // Across itself a span is as long as its longest child -- unless a // rule beside it gives that length outright, and then reading them diff --git a/tests/cases/layout.rs b/tests/cases/layout.rs index e6ac7c4..1e1b8d5 100644 --- a/tests/cases/layout.rs +++ b/tests/cases/layout.rs @@ -260,6 +260,66 @@ fn a_share_rule_beats_the_widgets_own_pixel_size() { assert_eq!(asked.get(), 400.0, "the share is all of the box"); } +/// A share with pixels or a fraction beside it is the longer of the two: it +/// fills what they leave of the box and overflows the box where they are +/// longer than it. A parent that divides nothing gives the same length as a +/// span with one child, because in both there is nobody else to divide with. +#[test] +fn a_share_is_a_minimum_wherever_nothing_divides_it() { + let asked = |rule: LayoutLen, in_a_span: bool| { + let mut h = Harness::new((400, 200)); + let probe = rect(Color::RED).add(&mut h.rsc); + h.set_len(probe, Axis::X, rule); + match in_a_span { + true => h.set_root((probe,).span(Dir::RIGHT)), + false => h.set_root(probe.wrapper()), + } + h.region(&probe).unwrap().size().x + }; + for (rule, want) in [ + (LayoutLen::LEFTOVER, 400), + (LayoutLen::px(50) + LayoutLen::LEFTOVER, 400), + (LayoutLen::px(500) + LayoutLen::LEFTOVER, 500), + (LayoutLen::rel(0.5) + LayoutLen::LEFTOVER, 400), + (LayoutLen::px(500), 500), + ] { + let want = Px::from_int(want); + assert_eq!(asked(rule, false), want, "{rule:?} where nothing divides"); + assert_eq!(asked(rule, true), want, "{rule:?} in a span"); + } +} + +/// Which of the two is longer is a question in pixels, so the box is decided +/// again wherever the answer can change: a window that crosses the length the +/// pixels ask for, and the rule itself crossing it while the window holds +/// still. The first is a range the drawing holds for; the second cannot be +/// seen in what the widget declares, since a share declares nothing either +/// way, so it reaches the parent as a length only the parent can resolve. +#[test] +fn a_share_past_the_box_is_decided_again_on_either_side_of_the_crossing() { + let mut h = Harness::new((400, 200)); + let probe = rect(Color::RED).add(&mut h.rsc); + h.set_len(probe, Axis::X, LayoutLen::px(500) + LayoutLen::LEFTOVER); + h.set_root(probe.wrapper()); + assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(500)); + + h.resize((900, 200)); + h.frame(); + assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(900)); + + h.resize((400, 200)); + h.frame(); + assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(500)); + + h.set_len(probe, Axis::X, LayoutLen::px(50) + LayoutLen::LEFTOVER); + h.frame(); + assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(400)); + + h.set_len(probe, Axis::X, LayoutLen::px(500) + LayoutLen::LEFTOVER); + h.frame(); + assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(500)); +} + #[test] fn a_child_drawn_twice_moves_once() { let mut h = Harness::new((400, 200));