Constrain offered boxes with independent widget size bounds
This commit is contained in:
1 parent
4cb6f6882a
commit
2ac0843cb2
18 files changed
+554
-379
No files matched your search
+66
-65
@@ -3,8 +3,8 @@ use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Bound, Bounds, Declared, DrawScratch, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc,
|
||||
PlaceFit, Px, PxVec2, RegionAlign, Rel, RenderedText, RequestArena, RequestedLen,
|
||||
RetainedPrimitive, Size, SizeRequests, SizeRule, StrongWidget, TextAttrs, TextBuffer,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||
RetainedPrimitive, Size, SizeRequests, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
|
||||
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||
TexturePrimitive,
|
||||
@@ -88,9 +88,7 @@ impl<'a> Painter<'a> {
|
||||
child: &StrongWidget<W>,
|
||||
axis: Axis,
|
||||
) -> Option<RequestedLen> {
|
||||
if self.rsc.widgets().size_rules(child.id())[axis].bound() == Bound::ANY
|
||||
&& let Some(len) = self.size_hint(child, axis)
|
||||
{
|
||||
if let Some(len) = self.size_hint(child, axis) {
|
||||
self.request_deps.push(child.id());
|
||||
return Some(len.into());
|
||||
}
|
||||
@@ -106,10 +104,9 @@ impl<'a> Painter<'a> {
|
||||
// only a declaration or a share chooses the box it is drawn in.
|
||||
let request = requests.widget(child, axis).filter(|request| {
|
||||
request.has_leftover()
|
||||
|| matches!(
|
||||
self.rsc.widgets().size_rules(child.id())[axis],
|
||||
SizeRule::Request(_)
|
||||
)
|
||||
|| self.rsc.widgets().size_rules(child.id())[axis]
|
||||
.request
|
||||
.is_some()
|
||||
});
|
||||
if request.is_some() {
|
||||
self.rel_base(axis);
|
||||
@@ -130,7 +127,7 @@ impl<'a> Painter<'a> {
|
||||
len: LayoutLen,
|
||||
) -> RequestedLen {
|
||||
let start = self.request_deps.len();
|
||||
let bound = self.rsc.widgets().size_rules(child.id())[axis].bound();
|
||||
let bound = self.rsc.widgets().size_rules(child.id())[axis].bound;
|
||||
let mut requests = SizeRequests {
|
||||
arena: &mut self.state.requests,
|
||||
measured: Some(&self.state.active),
|
||||
@@ -442,6 +439,9 @@ impl<'a> Painter<'a> {
|
||||
/// against this widget's rel base, which is the rel base a child asked with
|
||||
/// nothing narrowed gets. Asking counts as reading its size.
|
||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
||||
if self.rsc.widgets().size_rules(id.id())[axis].bound != Bound::ANY {
|
||||
return None;
|
||||
}
|
||||
let hint = self.rsc.widgets().exact_len(id.id(), axis);
|
||||
let rel_base = self.rel_base[axis];
|
||||
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
||||
@@ -559,10 +559,9 @@ 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 {
|
||||
matches!(
|
||||
self.rsc.widgets().size_rules(self.id)[axis],
|
||||
SizeRule::Exact(_) | SizeRule::Request(_)
|
||||
)
|
||||
self.rsc.widgets().size_rules(self.id)[axis]
|
||||
.request
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// This widget's own box in pixels. Reading it makes the drawing one
|
||||
@@ -809,10 +808,11 @@ impl Widgets {
|
||||
// narrow the box in its place: what the request comes to is not known
|
||||
// until the parent allocates, and it is the parent's answer, not this
|
||||
// widget's.
|
||||
if matches!(self.size_rules(id)[axis], SizeRule::Request(_)) {
|
||||
let rule = &self.size_rules(id)[axis];
|
||||
if rule.deferred().is_some() {
|
||||
return None;
|
||||
}
|
||||
self.size_rules(id)[axis].exact().or_else(|| {
|
||||
rule.exact().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
|
||||
@@ -821,12 +821,6 @@ impl Widgets {
|
||||
self.get_dyn(id)?.size_hint(axis)
|
||||
})
|
||||
}
|
||||
|
||||
/// What a widget's box is where a rule or its own hint gives one outright,
|
||||
/// rather than a share for whoever draws it to divide.
|
||||
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
||||
Declared::from_axes(|axis| self.exact_len(id, axis)?.declared())
|
||||
}
|
||||
}
|
||||
|
||||
/// One ask of a widget: the box it draws in, what its fractions are of, and
|
||||
@@ -838,9 +832,8 @@ pub(super) struct Ask {
|
||||
/// past the box its parent offered.
|
||||
pub place: PlaceDesc,
|
||||
/// What the widget's box is on each axis where something says so
|
||||
/// outright: its rule or its hint, or a bound of its own that the box it
|
||||
/// was offered falls outside -- a bound that binds is a declaration, and
|
||||
/// the same one the widget answers with.
|
||||
/// outright: its rule, its hint, or a bound the offer fell outside.
|
||||
/// An intrinsic answer can still occupy less than a capped box.
|
||||
pub declared: Declared,
|
||||
/// Its bounds, resolved against the rel base its rules were resolved
|
||||
/// against, for the answer to be held to where the box was not.
|
||||
@@ -876,11 +869,18 @@ impl Placing {
|
||||
let rules = widgets.size_rules(id);
|
||||
let mut holds = LayoutHolds::ANY;
|
||||
let mut inputs = LayoutHolds::ANY;
|
||||
let mut declared = widgets.declared_lens(id);
|
||||
let mut declared = Declared::NONE;
|
||||
let mut bounds = Bounds::ANY;
|
||||
for axis in Axis::BOTH {
|
||||
let base = place.base(axis, self.rel_base);
|
||||
if let SizeRule::Request(request) = &rules[axis] {
|
||||
let stated = widgets.exact_len(id, axis);
|
||||
if let Some(len) = stated.and_then(|len| len.declared()) {
|
||||
if len.rel != Rel::ZERO {
|
||||
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
||||
}
|
||||
declared[axis] = Some(len.within_len(base));
|
||||
}
|
||||
if let Some(request) = rules[axis].deferred() {
|
||||
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
||||
inputs[axis].region_len = Some(self.region[axis].len());
|
||||
inputs[axis].window = Holds::at(window[axis]);
|
||||
@@ -889,6 +889,7 @@ impl Placing {
|
||||
offer.to_px(window[axis])
|
||||
} else {
|
||||
let request = requests.import(request, base);
|
||||
let request = requests.bounded(request, rules[axis].bound.within_len(base));
|
||||
holds[axis].window = Holds::at(window[axis]);
|
||||
requests
|
||||
.allocate(&[request], offer.to_px(window[axis]), window[axis])
|
||||
@@ -906,24 +907,38 @@ impl Placing {
|
||||
// a span follows with one child. Only the overflow is a box of
|
||||
// the widget's own: a share that fits is the box it was given,
|
||||
// which is what this place already says.
|
||||
let (share, kept) =
|
||||
self.share_past_the_offer(widgets, window[axis], id, place, align, axis);
|
||||
let (share, kept) = self.share_past_the_offer(stated, window[axis], place, align, axis);
|
||||
holds[axis].window = holds[axis].window.and(kept);
|
||||
if let Some(len) = share {
|
||||
place[axis] = len.as_desc().fills();
|
||||
}
|
||||
// A bound holds what the widget answers, not the box it is asked
|
||||
// in: the box it is given is whoever asked's to decide, and a
|
||||
// rule that read it would be decided again by every path that
|
||||
// hands the widget a box -- including the ones that never ask it
|
||||
// anything. Resolved here because only the ask knows the rel base
|
||||
// a fraction in it is of. `MaxSize` is the box version, and it is
|
||||
// a widget because a widget is drawn again when its box changes.
|
||||
let bound = rules[axis].bound();
|
||||
if bound.has_fraction() {
|
||||
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
||||
let bound = rules[axis].bound;
|
||||
if bound != Bound::ANY {
|
||||
if bound.has_fraction() {
|
||||
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
||||
}
|
||||
// A solved slot may narrow the widget's rel base, but the
|
||||
// allocator evaluated its bounds against this parent's base.
|
||||
let bound_base = if place[axis].fit == PlaceFit::Allocated {
|
||||
self.rel_base[axis]
|
||||
} else {
|
||||
base
|
||||
};
|
||||
bounds[axis] = bound.within_len(bound_base);
|
||||
let offer =
|
||||
declared[axis].unwrap_or_else(|| place.of(self.region, align)[axis].len());
|
||||
let (held, kept) = bounds[axis].outside(offer, window[axis]);
|
||||
holds[axis].window = holds[axis].window.and(kept);
|
||||
// The comparison reads the incoming box, before a bound
|
||||
// replaces it. Placement keeps that decision; only an ask
|
||||
// may compare a new offer.
|
||||
if declared[axis].is_none() {
|
||||
inputs[axis].region_len = Some(self.region[axis].len());
|
||||
}
|
||||
if let Some(len) = held {
|
||||
declared[axis] = Some(len);
|
||||
}
|
||||
}
|
||||
bounds[axis] = bound.within_len(base);
|
||||
}
|
||||
let (rel_base, region) =
|
||||
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
||||
@@ -950,9 +965,8 @@ impl Placing {
|
||||
/// crossing between them is a question in pixels.
|
||||
fn share_past_the_offer(
|
||||
&self,
|
||||
widgets: &Widgets,
|
||||
stated: Option<LayoutLen>,
|
||||
window: Px,
|
||||
id: WidgetId,
|
||||
place: PlaceDesc,
|
||||
align: RegionAlign,
|
||||
axis: Axis,
|
||||
@@ -966,7 +980,7 @@ impl Placing {
|
||||
}
|
||||
// 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 Some(stated) = widgets.exact_len(id, axis) else {
|
||||
let Some(stated) = stated else {
|
||||
return (None, Holds::ANY);
|
||||
};
|
||||
if stated.leftover == Weight::ZERO || stated.is_only_leftover() {
|
||||
@@ -985,12 +999,10 @@ 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(super) fn fills(&self, declared: Option<Len>, decided: bool) -> bool {
|
||||
self.leftover != Weight::ZERO || declared.is_some() || decided
|
||||
/// did is the one that handed down this box. An axis the parent decided
|
||||
/// from the answer is the answer already.
|
||||
pub(super) fn fills(&self, decided: bool) -> bool {
|
||||
self.leftover != Weight::ZERO || decided
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,17 +1016,11 @@ impl PlaceDesc {
|
||||
/// part. 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(super) fn placement(
|
||||
self,
|
||||
region: UiRegion,
|
||||
size: Size,
|
||||
declared: Declared,
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
pub(super) fn placement(self, region: UiRegion, size: Size, align: RegionAlign) -> UiRegion {
|
||||
let mut placed = region;
|
||||
for axis in Axis::BOTH {
|
||||
let reported = size[axis];
|
||||
if reported.fills(declared[axis], self[axis].fit.fills()) {
|
||||
if reported.fills(self[axis].fit.fills()) {
|
||||
continue;
|
||||
}
|
||||
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
|
||||
@@ -1027,11 +1033,8 @@ impl PlaceDesc {
|
||||
///
|
||||
/// `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.
|
||||
/// base less its pixels. The ask has already resolved declarations into
|
||||
/// window lengths, so placement only aligns them inside the given box.
|
||||
pub(super) fn rel_base_and_region(
|
||||
self,
|
||||
own: UiRegion,
|
||||
@@ -1044,9 +1047,7 @@ impl PlaceDesc {
|
||||
let mut region = given;
|
||||
for axis in Axis::BOTH {
|
||||
let base = self.base(axis, parent_rel_base);
|
||||
let len = declared[axis]
|
||||
.map(|len| len.within_len(base))
|
||||
.unwrap_or(base);
|
||||
let len = declared[axis].unwrap_or(base);
|
||||
rel_base[axis] = len;
|
||||
if declared[axis].is_some() {
|
||||
region[axis] = given[axis].place(len, align[axis]);
|
||||
|
||||
Reference in new issue
Block a user