Resolve deferred size comparisons before allocating span slots
This commit is contained in:
1 parent
de1eb7e406
commit
8780b40bb7
23 files changed
+1471
-130
No files matched your search
@@ -6,6 +6,10 @@ pub struct LayerOffset {
|
||||
}
|
||||
|
||||
impl Widget for LayerOffset {
|
||||
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
||||
requests.widget(&self.inner, axis)
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
for _ in 0..self.offset {
|
||||
painter.next_layer();
|
||||
|
||||
@@ -30,6 +30,14 @@ impl MaxSize {
|
||||
}
|
||||
|
||||
impl Widget for MaxSize {
|
||||
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
||||
let inner = requests.widget(&self.inner, axis)?;
|
||||
Some(match self.max(axis) {
|
||||
Some(max) => requests.min(inner, max.into()),
|
||||
None => inner,
|
||||
})
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let align = painter.alignment();
|
||||
let mut region = UiRegion::FULL;
|
||||
|
||||
@@ -6,6 +6,10 @@ pub struct Offset {
|
||||
}
|
||||
|
||||
impl Widget for Offset {
|
||||
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
||||
requests.widget(&self.inner, axis)
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter
|
||||
.widget_at(&self.inner, UiRegion::FULL.offset(self.amt))
|
||||
|
||||
@@ -6,6 +6,14 @@ pub struct Pad {
|
||||
}
|
||||
|
||||
impl Widget for Pad {
|
||||
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
||||
let padding = match axis {
|
||||
Axis::X => self.padding.left + self.padding.right,
|
||||
Axis::Y => self.padding.top + self.padding.bottom,
|
||||
};
|
||||
requests.inset(&self.inner, axis, padding)
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
// The inner's own alignment, not the near edge. This reports the
|
||||
// inner's size plus the padding, so where the box is that answer the
|
||||
|
||||
+109
-49
@@ -8,57 +8,79 @@ pub struct Span {
|
||||
}
|
||||
|
||||
impl Widget for Span {
|
||||
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
||||
if axis != self.dir.axis {
|
||||
// A share can be hidden when the other axis has no room. Its
|
||||
// cross-axis length then contributes nothing to the drawn answer.
|
||||
return None;
|
||||
}
|
||||
let mut total = RequestedLen::from(Len::from_parts(
|
||||
Rel::ZERO,
|
||||
self.gap
|
||||
.mul_int(self.children.len().saturating_sub(1) as i32),
|
||||
));
|
||||
for child in &self.children {
|
||||
let child = requests.widget(child, axis)?;
|
||||
total = requests.sum(total, child);
|
||||
}
|
||||
Some(total)
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.with_requests(|painter, lens, values| self.layout(painter, lens, values))
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
fn layout(
|
||||
&self,
|
||||
painter: &mut Painter,
|
||||
lens: &mut Vec<RequestedLen>,
|
||||
values: &mut Vec<Px>,
|
||||
) -> Size {
|
||||
let axis = self.dir.axis;
|
||||
// The row this span lays its children out along, as a length of the
|
||||
// rel base they are laid out against. Where it starts is nothing's
|
||||
// business -- a slot is a length from there -- so what this reads is
|
||||
// the length alone.
|
||||
let row = painter.region_len(axis);
|
||||
// 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
|
||||
// given whatever else is in it and wherever this child sits among
|
||||
// them; what a drawn child is asked in is the room left from the
|
||||
// cursor, because a text has to wrap at the width actually there.
|
||||
let mut cursor = Len::ZERO;
|
||||
let mut lens = Vec::with_capacity(self.children.len());
|
||||
for child in &self.children {
|
||||
let len = match painter.size_hint(child, axis) {
|
||||
Some(len) => len,
|
||||
None => {
|
||||
// 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 = self.slot(row, cursor, row).shifted_desc().on_axis(axis);
|
||||
painter.widget_at(child, room).len(axis)
|
||||
}
|
||||
};
|
||||
cursor += len.without_leftover();
|
||||
cursor.px += self.gap;
|
||||
lens.push(len);
|
||||
}
|
||||
|
||||
self.collect(painter, row, lens, true);
|
||||
let gaps = self
|
||||
.gap
|
||||
.mul_int(self.children.len().saturating_sub(1) as i32);
|
||||
let total = lens.iter().fold(
|
||||
LayoutLen {
|
||||
px: gaps,
|
||||
let fixed = lens
|
||||
.iter()
|
||||
.try_fold(Len::from_parts(Rel::ZERO, gaps), |sum, len| {
|
||||
Some(sum + len.linear()?.without_leftover())
|
||||
});
|
||||
if let Some(fixed) = fixed
|
||||
&& lens.iter().any(|len| len.has_leftover())
|
||||
&& !painter.longer_than(row, fixed, axis)
|
||||
{
|
||||
// With no share to assign, intrinsic drawings keep the remaining
|
||||
// offer, including overflow. Their answer is only moved into a slot.
|
||||
self.collect(painter, row, lens, false);
|
||||
}
|
||||
let nonlinear = lens.iter().any(|len| len.linear().is_none());
|
||||
if nonlinear {
|
||||
painter.allocate(lens, row - Len::from_parts(Rel::ZERO, gaps), axis, values);
|
||||
}
|
||||
let allocated = nonlinear.then_some(&values);
|
||||
let total = match &allocated {
|
||||
Some(allocated) => LayoutLen {
|
||||
px: allocated.iter().fold(gaps, |sum, len| sum + *len),
|
||||
..LayoutLen::ZERO
|
||||
},
|
||||
|sum, len| sum + *len,
|
||||
);
|
||||
|
||||
// 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.
|
||||
None => lens.iter().fold(
|
||||
LayoutLen {
|
||||
px: gaps,
|
||||
..LayoutLen::ZERO
|
||||
},
|
||||
|sum, len| sum + len.linear().unwrap(),
|
||||
),
|
||||
};
|
||||
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 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.longer_than(row, all_fixed, axis);
|
||||
|
||||
@@ -83,17 +105,30 @@ impl Widget for Span {
|
||||
false => fixed,
|
||||
true => fixed + room.scale(Rel::ratio(taken, total.leftover)),
|
||||
};
|
||||
for (child, &len) in self.children.iter().zip(&lens) {
|
||||
for (index, (child, request)) in self.children.iter().zip(lens.iter()).enumerate() {
|
||||
let len = match &allocated {
|
||||
Some(allocated) => LayoutLen {
|
||||
px: allocated[index],
|
||||
..LayoutLen::ZERO
|
||||
},
|
||||
None => request.linear().unwrap(),
|
||||
};
|
||||
let shares = match &allocated {
|
||||
Some(_) => request.has_leftover(),
|
||||
None => len.leftover > Weight::ZERO && has_room,
|
||||
};
|
||||
// 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.is_only_leftover() && !has_room {
|
||||
if (len.is_only_leftover() && !has_room)
|
||||
|| (allocated.is_some() && shares && len.px == Px::ZERO)
|
||||
{
|
||||
painter.undraw(child);
|
||||
fixed.px += self.gap;
|
||||
continue;
|
||||
}
|
||||
let from = reached(fixed, taken);
|
||||
if len.leftover > Weight::ZERO && has_room {
|
||||
if shares {
|
||||
taken += len.leftover;
|
||||
}
|
||||
fixed += len.without_leftover();
|
||||
@@ -106,8 +141,8 @@ 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 = self.slot(row, from, to);
|
||||
let mut place = slot.shifted_desc().fills().on_axis(axis);
|
||||
if len.leftover > Weight::ZERO && has_room {
|
||||
let mut place = slot.shifted_desc().allocated().on_axis(axis);
|
||||
if shares {
|
||||
place = place.rel_base(axis, slot.len());
|
||||
}
|
||||
let used = painter.place_at(child, place).len(!axis);
|
||||
@@ -125,13 +160,8 @@ impl Widget for Span {
|
||||
fixed.px += self.gap;
|
||||
}
|
||||
|
||||
// Carried whole rather than collapsed to one share: a span that sizes
|
||||
// from its children does not resolve `leftover`, it passes the weight up,
|
||||
// so nesting spans divides the same space rather than re-dividing a
|
||||
// share of it. Four `leftover(1)` children under two spans under one span
|
||||
// get a quarter each, which collapsing to `leftover(1)` per level does
|
||||
// not give. Resolution happens at the nearest ancestor with a length,
|
||||
// and the root always has one.
|
||||
// Discovery carries nested requests to the allocating ancestor. The
|
||||
// draw still returns an ordinary Size for callers measuring content.
|
||||
let ortho = match shrinks {
|
||||
true => ortho,
|
||||
false => LayoutLen::rel(1.0),
|
||||
@@ -141,6 +171,36 @@ impl Widget for Span {
|
||||
}
|
||||
|
||||
impl Span {
|
||||
fn collect(
|
||||
&self,
|
||||
painter: &mut Painter,
|
||||
row: Len,
|
||||
lens: &mut Vec<RequestedLen>,
|
||||
discover: bool,
|
||||
) {
|
||||
let axis = self.dir.axis;
|
||||
let mut cursor = Len::ZERO;
|
||||
lens.clear();
|
||||
for child in &self.children {
|
||||
let request = if discover {
|
||||
painter.size_request(child, axis)
|
||||
} else {
|
||||
painter.size_hint(child, axis).map(Into::into)
|
||||
};
|
||||
let len = match request {
|
||||
Some(len) => len,
|
||||
None => {
|
||||
let room = self.slot(row, cursor, row).shifted_desc().on_axis(axis);
|
||||
let len = painter.widget_at(child, room).len(axis);
|
||||
painter.measured_request(child, axis, len)
|
||||
}
|
||||
};
|
||||
cursor += painter.minimum_request(&len, axis);
|
||||
cursor.px += self.gap;
|
||||
lens.push(len);
|
||||
}
|
||||
}
|
||||
|
||||
/// The stretch of the row between two distances from where this span
|
||||
/// starts laying children out, as a span of its own box. A negative
|
||||
/// direction lays out from the far end, so the same two distances mirror
|
||||
|
||||
@@ -8,6 +8,16 @@ pub struct Stack {
|
||||
}
|
||||
|
||||
impl Widget for Stack {
|
||||
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
||||
match self.size {
|
||||
StackSize::Default => Some(LayoutLen::LEFTOVER.into()),
|
||||
StackSize::Child(i) => match self.children.get(i) {
|
||||
Some(child) => requests.widget(child, axis),
|
||||
None => Some(LayoutLen::LEFTOVER.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let sizing = match self.size {
|
||||
StackSize::Default => None,
|
||||
|
||||
@@ -59,14 +59,14 @@ widget_trait! {
|
||||
}
|
||||
}
|
||||
|
||||
fn width(self, len: impl Into<LayoutLen>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||
fn width(self, len: impl Into<SizeRequest>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||
let len = len.into();
|
||||
move |state| {
|
||||
let id = self.add(state);
|
||||
state
|
||||
.ui_mut()
|
||||
.widgets
|
||||
.set_size_rule(id, Axis::X, SizeRule::Exact(len));
|
||||
.set_size_rule(id, Axis::X, SizeRule::from(len));
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -115,14 +115,14 @@ widget_trait! {
|
||||
}
|
||||
}
|
||||
|
||||
fn height(self, len: impl Into<LayoutLen>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||
fn height(self, len: impl Into<SizeRequest>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||
let len = len.into();
|
||||
move |state| {
|
||||
let id = self.add(state);
|
||||
state
|
||||
.ui_mut()
|
||||
.widgets
|
||||
.set_size_rule(id, Axis::Y, SizeRule::Exact(len));
|
||||
.set_size_rule(id, Axis::Y, SizeRule::from(len));
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user