WIP: a widget's region stays put and its placement moves in it
The protocol split: `region` is the box a parent gives a widget -- what a fraction it declares or reports is a fraction of, and the coordinates every region it writes composes within -- and it is the same box on the ask that measures and the ask that places. `placement` is what of that region the drawing takes, chosen by the parent per axis or by the widget's own answer and alignment. That is what stops a fraction being resolved twice: the placing ask no longer hands the widget its own answer as its box, so nothing under it re-resolves against a box that came from its own report. `reports_of` and `decided` are gone, folded into the two regions; `box_of` is gone; `declared_box` becomes `ask_box`, which gives a rule the region's length and takes the position from the placement. 84 of 92 suite tests pass. Five text and region-node cases still diverge warm against cold, and three count a second widget draw where a span's measuring ask and its placing ask give different placements.
This commit is contained in:
1 parent
e44dea34b4
commit
5fcace1bfa
11 files changed
+346
-206
No files matched your search
+2
-1
@@ -6,7 +6,8 @@ pub struct Masked {
|
||||
|
||||
impl Widget for Masked {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.set_mask(painter.region());
|
||||
let own = painter.placement();
|
||||
painter.set_mask(own);
|
||||
painter.widget(&self.inner);
|
||||
// What it occupies is its box, on both axes, for the reason `Scroll`
|
||||
// reports the same: it clips what is inside to that box, so it can
|
||||
|
||||
@@ -13,9 +13,8 @@ impl Widget for Pad {
|
||||
// it; where the box is bigger -- a share of a row, a rule over this
|
||||
// widget -- the slack is the inner's to sit in, and forcing the near
|
||||
// edge pinned it to a corner it had not asked for.
|
||||
let inner = painter
|
||||
.widget_within(&self.inner, self.padding.region())
|
||||
.size();
|
||||
let inside = self.padding.region_of(painter.placement());
|
||||
let inner = painter.widget_within(&self.inner, inside).size();
|
||||
Size {
|
||||
x: LayoutLen {
|
||||
px: inner.x.px + self.padding.left + self.padding.right,
|
||||
@@ -53,14 +52,18 @@ impl Padding {
|
||||
bottom: amt,
|
||||
}
|
||||
}
|
||||
pub fn region(&self) -> UiRegion {
|
||||
let mut region = UiRegion::FULL;
|
||||
/// `region` less this padding on each side.
|
||||
pub fn region_of(&self, mut region: UiRegion) -> UiRegion {
|
||||
region.x.start.px += self.left;
|
||||
region.y.start.px += self.top;
|
||||
region.x.end.px -= self.right;
|
||||
region.y.end.px -= self.bottom;
|
||||
region
|
||||
}
|
||||
|
||||
pub fn region(&self) -> UiRegion {
|
||||
self.region_of(UiRegion::FULL)
|
||||
}
|
||||
pub fn x(amt: impl UiNum) -> Self {
|
||||
let amt = Px::from_num(amt);
|
||||
Self {
|
||||
|
||||
@@ -15,7 +15,7 @@ impl Widget for Scroll {
|
||||
// Draw in the whole container only when its scrolling-axis length is
|
||||
// not already known, then draw it at the scrolled offset.
|
||||
let whole = UiRegion::FULL;
|
||||
let answer_len = match painter.known_len(&self.inner, self.axis, whole, whole.size()) {
|
||||
let answer_len = match painter.known_len(&self.inner, self.axis, whole) {
|
||||
Some(len) => len,
|
||||
None => painter.widget(&self.inner).size().axis(self.axis),
|
||||
};
|
||||
@@ -64,7 +64,11 @@ impl Widget for Scroll {
|
||||
region = region.offset(offset);
|
||||
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
||||
}
|
||||
painter.widget_at(&self.inner, region, region.size(), [true; 2]);
|
||||
// The viewport is the inner's region, so a fraction it declares or
|
||||
// reports is a fraction of what is on screen rather than of the
|
||||
// content box its own answer decided. Where it is put is the content
|
||||
// box, scrolled.
|
||||
painter.widget_at(&self.inner, whole, [Some(region.x), Some(region.y)]);
|
||||
// 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
|
||||
|
||||
+36
-34
@@ -10,25 +10,36 @@ pub struct Span {
|
||||
impl Widget for Span {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let axis = self.dir.axis;
|
||||
// The row: this span's own box, as a span of the region it was given.
|
||||
// Its children are laid out along it, and what they declare or report
|
||||
// is a fraction of the region -- the area this span was told it has,
|
||||
// which it passes on unchanged.
|
||||
let own = painter.placement();
|
||||
let row = *own.axis(axis);
|
||||
// Across itself the span's own box is the child's region: a span is
|
||||
// what contains its children there, and nothing divides that axis.
|
||||
// Along it the whole region is, so a fraction means the same thing
|
||||
// for every child however much of the row is left when it is asked.
|
||||
let region = UiRegion::from_axis(axis, UiSpan::FULL, *own.axis(!axis));
|
||||
let along = |from: Len, to: Len| match self.dir.sign {
|
||||
Sign::Pos => UiSpan::new(row.start + from, row.start + to),
|
||||
Sign::Neg => UiSpan::new(row.end - to, row.end - from),
|
||||
};
|
||||
let far = row.len();
|
||||
// A length for every child before their final boxes are chosen: from
|
||||
// a hint where one exists, and from drawing otherwise.
|
||||
let mut cursor = Len::rel_min();
|
||||
let mut lens = Vec::with_capacity(self.children.len());
|
||||
for child in &self.children {
|
||||
let mut span = UiSpan::new(cursor, Len::rel_max());
|
||||
if self.dir.sign == Sign::Neg {
|
||||
span.flip();
|
||||
}
|
||||
let region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
||||
// Offered the room left from the cursor, because a text has to
|
||||
// wrap at the width actually there, but reporting a fraction of
|
||||
// the whole row: `rel(0.5)` is half the span whatever else is in
|
||||
// it and wherever this child sits among them.
|
||||
let len = match painter.known_len(child, axis, region, UiVec2::FULL_SIZE) {
|
||||
// The whole region is the child's, 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 it is placed in is the room left
|
||||
// from the cursor, because a text has to wrap at the width
|
||||
// actually there.
|
||||
let room = axis.pair(Some(along(cursor, far)), None);
|
||||
let len = match painter.known_len(child, axis, region) {
|
||||
Some(len) => len,
|
||||
None => painter
|
||||
.widget_at(child, region, UiVec2::FULL_SIZE, [false; 2])
|
||||
.len(axis),
|
||||
None => painter.widget_at(child, region, room).len(axis),
|
||||
};
|
||||
cursor.px += len.px + self.gap;
|
||||
cursor.rel += len.rel;
|
||||
@@ -46,9 +57,9 @@ impl Widget for Span {
|
||||
|sum, len| sum + *len,
|
||||
);
|
||||
|
||||
// What is left for the shares to divide: the box less everything
|
||||
// fixed, as a length of the box rather than a number of pixels.
|
||||
let room = Len::rel_max() - Len::from_parts(total.rel, total.px);
|
||||
// What is left for the shares to divide: the row less everything
|
||||
// fixed, as a length of the region rather than a number of pixels.
|
||||
let room = far - Len::from_parts(total.rel, total.px);
|
||||
// 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
|
||||
@@ -60,12 +71,12 @@ impl Widget for Span {
|
||||
// exist at all turns on this.
|
||||
let mut shares = false;
|
||||
if total.leftover > Weight::ZERO {
|
||||
shares = room.to_px(painter.px_len(axis)) > Px::ZERO;
|
||||
shares = room.to_px(painter.region_px_len(axis)) > Px::ZERO;
|
||||
let holds = match shares {
|
||||
true => Holds::from(Px::STEP..=Px::MAX),
|
||||
false => Holds::from(Px::MIN..=Px::ZERO),
|
||||
};
|
||||
painter.holds(axis, holds.through(room));
|
||||
painter.region_holds(axis, holds.through(room));
|
||||
}
|
||||
|
||||
// Across itself a span is as long as its longest child -- unless a
|
||||
@@ -94,28 +105,19 @@ impl Widget for Span {
|
||||
fixed.px += self.gap;
|
||||
continue;
|
||||
}
|
||||
let mut span = UiSpan::FULL;
|
||||
span.start = start;
|
||||
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);
|
||||
span.end = start;
|
||||
let mut region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
||||
if self.dir.sign == Sign::Neg {
|
||||
region.flip(axis);
|
||||
}
|
||||
// Along the row this box is the child's own answer, so the answer
|
||||
// is not placed in it again; across it the child sits where its
|
||||
// alignment says.
|
||||
let placed = painter.widget_at(
|
||||
child,
|
||||
region,
|
||||
UiVec2::FULL_SIZE,
|
||||
[axis == Axis::X, axis == Axis::Y],
|
||||
);
|
||||
// Along the row the span says where the child goes; across it the
|
||||
// child sits where its own alignment says. Its region is the
|
||||
// whole of what this span was given either way, which is what its
|
||||
// fractions are of.
|
||||
let placed =
|
||||
painter.widget_at(child, region, axis.pair(Some(along(from, start)), None));
|
||||
if shrinks {
|
||||
let used = placed.len(!axis);
|
||||
// Choosing between a fixed and a relative length from the
|
||||
|
||||
@@ -13,31 +13,37 @@ impl Widget for Stack {
|
||||
StackSize::Default => None,
|
||||
StackSize::Child(i) => Some(i),
|
||||
};
|
||||
// Whichever child sizes the stack decides the box every child gets.
|
||||
// The stack reports that size, so a child given a longer box would
|
||||
// draw outside what the stack says it occupies.
|
||||
// This stack's own box, which is `FULL` until its answer is known.
|
||||
let placement = painter.placement();
|
||||
// Whichever child sizes the stack keeps the stack's whole region as
|
||||
// its own -- the stack is the length that child asked for, so taking
|
||||
// the fraction of the stack's box again would take it twice -- and is
|
||||
// put where the stack itself is put.
|
||||
let size = match sizing.and_then(|i| self.children.get(i).map(|c| (i, c))) {
|
||||
// On the layer that child ends up on, so the ask below is a reuse
|
||||
// rather than a second drawing of it somewhere else: a retained
|
||||
// drawing belongs to the layer it was made on.
|
||||
Some((i, child)) => {
|
||||
painter.child_layer_at(i);
|
||||
painter.widget(child).size()
|
||||
painter
|
||||
.widget_at(
|
||||
child,
|
||||
UiRegion::FULL,
|
||||
[Some(placement.x), Some(placement.y)],
|
||||
)
|
||||
.size()
|
||||
}
|
||||
None => Size::LEFTOVER,
|
||||
};
|
||||
let region = painter.box_of(size);
|
||||
for (i, child) in self.children.iter().enumerate() {
|
||||
if sizing == Some(i) {
|
||||
continue;
|
||||
}
|
||||
painter.child_layer_at(i);
|
||||
// The sizing child placed its own content in the box its answer
|
||||
// decided, and this box was derived from that answer, so applying
|
||||
// its alignment again here would place it twice. Every other
|
||||
// child is handed a box that owes nothing to its own answer, and
|
||||
// where it sits in one bigger than itself is its own business.
|
||||
match sizing == Some(i) {
|
||||
true => painter.widget_at(child, region, region.size(), [true; 2]),
|
||||
false => painter.widget_within(child, region),
|
||||
};
|
||||
// Every other child has the stack's own box for its region, since
|
||||
// the stack is what contains it, and where it sits in one bigger
|
||||
// than itself is its own business.
|
||||
painter.widget_within(child, placement);
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ impl TextView {
|
||||
// hair under that line, and the break made in it is not the break a
|
||||
// cold layout makes there.
|
||||
let size = Size::from_px(PxVec2::ceil_from_f32(tex.size));
|
||||
let within = region.within(&painter.region());
|
||||
let within = region.within(&painter.placement());
|
||||
painter.glyphs(tex, within);
|
||||
(region, size)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user