Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2cb4f05da |
No files matched your search
+12
-10
@@ -168,10 +168,20 @@ impl<'a> Painter<'a> {
|
|||||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||||
let declared = self.declared_lens(id);
|
let declared = self.declared_lens(id);
|
||||||
let align = self.rsc.widgets().alignment(id.id());
|
let align = self.rsc.widgets().alignment(id.id());
|
||||||
|
// A rule this box was already chosen from is not resolved into it a
|
||||||
|
// second time. The box is that rule's length already, so resolving
|
||||||
|
// it again takes the fraction twice -- a widget declaring half of a
|
||||||
|
// stack, in the stack its own answer made half a row, is a quarter
|
||||||
|
// of the row. Pixels survive it, being the same length wherever they
|
||||||
|
// are taken from, which is why only a share ever shrank.
|
||||||
|
let resolve = AXES.map(|axis| match decided[axis as usize] {
|
||||||
|
true => None,
|
||||||
|
false => declared[axis as usize],
|
||||||
|
});
|
||||||
// Composing `FULL` through a box is not quite the identity in f32,
|
// Composing `FULL` through a box is not quite the identity in f32,
|
||||||
// so a child with nothing declared keeps the box it would have had.
|
// so a child with nothing declared keeps the box it would have had.
|
||||||
let local = match declared.iter().any(Option::is_some) {
|
let local = match resolve.iter().any(Option::is_some) {
|
||||||
true => declared_box(region, declared, align),
|
true => declared_box(region, resolve, align),
|
||||||
false => region,
|
false => region,
|
||||||
};
|
};
|
||||||
let within = match local == UiRegion::FULL {
|
let within = match local == UiRegion::FULL {
|
||||||
@@ -393,14 +403,6 @@ impl<'a> Painter<'a> {
|
|||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The part of this widget's box that something of `size` takes, at the
|
|
||||||
/// near edge. A container that reports one child's size gives every child
|
|
||||||
/// this, so what it draws is inside what it says it occupies.
|
|
||||||
pub fn box_of(&self, size: Size) -> UiRegion {
|
|
||||||
let lens = placed_lens(size, [None; 2], [false; 2]);
|
|
||||||
placed_box(UiRegion::FULL, lens, RegionAlign::NEAR)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This widget's box in pixels. Reading it makes the drawing one that
|
/// This widget's box in pixels. Reading it makes the drawing one that
|
||||||
/// holds for this box only, until `holds` says how far it goes.
|
/// holds for this box only, until `holds` says how far it goes.
|
||||||
pub fn px_size(&mut self) -> PxVec2 {
|
pub fn px_size(&mut self) -> PxVec2 {
|
||||||
|
|||||||
@@ -202,14 +202,6 @@ impl UiRenderState {
|
|||||||
// that marks a reader is in `redraw`, which is not what asked here.
|
// that marks a reader is in `redraw`, which is not what asked here.
|
||||||
// Both retained routes are an answer, so the question is asked once
|
// Both retained routes are an answer, so the question is asked once
|
||||||
// rather than by each of them.
|
// rather than by each of them.
|
||||||
//
|
|
||||||
// Since `a92c6ac` settles a frame strictly bottom-up, no fuzzer can
|
|
||||||
// tell whether the second half of this still does anything: dropping
|
|
||||||
// `dirty_size_under` passes the suite, the shrinker at 400 seeds of
|
|
||||||
// depth 5, the oracle at 1000 of depth 6 and 2000 seeds at depth 4.
|
|
||||||
// It stays because `update` draws the root for a resize before
|
|
||||||
// `redraw_updates` runs at all, which that ordering does not reach --
|
|
||||||
// a hole that is reasoned rather than measured.
|
|
||||||
let stale =
|
let stale =
|
||||||
rsc.widgets().needs_redraw.contains(&id) || self.dirty_size_under(id, rsc.widgets());
|
rsc.widgets().needs_redraw.contains(&id) || self.dirty_size_under(id, rsc.widgets());
|
||||||
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ impl DefaultAppState for State {
|
|||||||
.wrap(true)
|
.wrap(true)
|
||||||
.text_align(Align::LEFT)
|
.text_align(Align::LEFT)
|
||||||
.pad(16)
|
.pad(16)
|
||||||
|
.width(rel(1.0))
|
||||||
.background(panel());
|
.background(panel());
|
||||||
|
|
||||||
// Each one takes the whole width, because `text_align` puts the
|
// Each one takes the whole width, because `text_align` puts the
|
||||||
|
|||||||
+34
-16
@@ -46,26 +46,43 @@ impl Widget for Span {
|
|||||||
|sum, len| sum + *len,
|
|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);
|
|
||||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
// 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`
|
// beside 300 px is full at 600 and overfull at 400. The room to
|
||||||
// itself, and answered back through the same expression, so the
|
// divide is `len * fixed - total.px`, and the length where it runs
|
||||||
// boundary is the drawing's own and not a second way of finding it:
|
// out is exactly the box a parent sizing itself from this answer
|
||||||
// the three cases a rounded division needed -- the fixed parts
|
// hands back -- which is why this used to need a margin either side
|
||||||
// growing slower than the box, faster, or exactly with it -- are the
|
// of the boundary, and why it does not now: that box and this sum are
|
||||||
// sign of `room.rel`, which `through` already reads. What the
|
// whole counts of the same step, and both routes to it land on the
|
||||||
// generated oracle checks is the consequence, since which children
|
// same count. What the generated oracle checks is the consequence,
|
||||||
// exist at all turns on this.
|
// since which children exist at all turns on this.
|
||||||
|
let fixed = Rel::ONE - total.rel;
|
||||||
let mut shares = false;
|
let mut shares = false;
|
||||||
if total.leftover > Weight::ZERO {
|
if total.leftover > Weight::ZERO {
|
||||||
shares = room.to_px(painter.px_len(axis)) > Px::ZERO;
|
let current = painter.px_len(axis);
|
||||||
let holds = match shares {
|
let holds = if fixed > Rel::ZERO {
|
||||||
true => Holds::from(Px::STEP..=Px::MAX),
|
// The box length the fixed parts alone fill.
|
||||||
false => Holds::from(Px::MIN..=Px::ZERO),
|
let full = total.px.div(fixed);
|
||||||
|
shares = current > full;
|
||||||
|
match shares {
|
||||||
|
true => Holds::from(full.next_up()..=Px::MAX),
|
||||||
|
false => Holds::from(Px::MIN..=full),
|
||||||
|
}
|
||||||
|
} else if fixed < Rel::ZERO {
|
||||||
|
// The relative parts grow faster than the box does, so here
|
||||||
|
// a shorter box is the one that leaves room.
|
||||||
|
let full = total.px.div(fixed);
|
||||||
|
shares = current < full;
|
||||||
|
match shares {
|
||||||
|
true => Holds::from(Px::MIN..=full.next_down()),
|
||||||
|
false => Holds::from(full..=Px::MAX),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The relative parts take exactly the box, whatever it is, so
|
||||||
|
// the only room is what negative pixels leave.
|
||||||
|
shares = total.px < Px::ZERO;
|
||||||
|
Holds::ANY
|
||||||
};
|
};
|
||||||
painter.holds(axis, holds.through(room));
|
painter.holds(axis, holds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Across itself a span is as long as its longest child -- unless a
|
// Across itself a span is as long as its longest child -- unless a
|
||||||
@@ -82,6 +99,7 @@ impl Widget for Span {
|
|||||||
// row.
|
// row.
|
||||||
let mut fixed = Len::rel_min();
|
let mut fixed = Len::rel_min();
|
||||||
let mut taken = Weight::ZERO;
|
let mut taken = Weight::ZERO;
|
||||||
|
let room = Len::rel_max() - Len::from_parts(total.rel, total.px);
|
||||||
let mut start = Len::rel_min();
|
let mut start = Len::rel_min();
|
||||||
let mut ortho = LayoutLen::ZERO;
|
let mut ortho = LayoutLen::ZERO;
|
||||||
for (child, len) in self.children.iter().zip(&lens) {
|
for (child, len) in self.children.iter().zip(&lens) {
|
||||||
|
|||||||
@@ -13,31 +13,42 @@ impl Widget for Stack {
|
|||||||
StackSize::Default => None,
|
StackSize::Default => None,
|
||||||
StackSize::Child(i) => Some(i),
|
StackSize::Child(i) => Some(i),
|
||||||
};
|
};
|
||||||
// Whichever child sizes the stack decides the box every child gets.
|
// Every child gets the whole of this stack's box, the sizing one
|
||||||
// The stack reports that size, so a child given a longer box would
|
// included, and the stack is then handed a box of the length that
|
||||||
// draw outside what the stack says it occupies.
|
// child asked for. Not the part of the box that length takes: the
|
||||||
|
// stack's own box becomes that length, and taking the fraction of it
|
||||||
|
// again is the fraction twice -- a child asking for half of a stack
|
||||||
|
// that is already half a row would have a quarter of the row.
|
||||||
|
//
|
||||||
|
// It cannot be told apart by asking whether this box is the answer
|
||||||
|
// yet, either. A drawing has to be a function of the box alone, since
|
||||||
|
// moving the stack into the box it asked for reuses the drawing by
|
||||||
|
// scaling it, and a drawing made a fraction of one box is right in
|
||||||
|
// any other. So: fractions of this box throughout, and the move is
|
||||||
|
// the whole of the difference.
|
||||||
|
let region = UiRegion::FULL;
|
||||||
|
// Whichever child sizes the stack is asked here and not again below,
|
||||||
|
// on the layer it ends up on: a retained drawing belongs to the layer
|
||||||
|
// it was made on, so measuring it anywhere else costs a second
|
||||||
|
// drawing of it. Its box is its own answer, so the answer is not
|
||||||
|
// placed inside it again.
|
||||||
let size = match sizing.and_then(|i| self.children.get(i).map(|c| (i, c))) {
|
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)) => {
|
Some((i, child)) => {
|
||||||
painter.child_layer_at(i);
|
painter.child_layer_at(i);
|
||||||
painter.widget(child).size()
|
painter
|
||||||
|
.widget_at(child, region, region.size(), [true; 2])
|
||||||
|
.size()
|
||||||
}
|
}
|
||||||
None => Size::LEFTOVER,
|
None => Size::LEFTOVER,
|
||||||
};
|
};
|
||||||
let region = painter.box_of(size);
|
|
||||||
for (i, child) in self.children.iter().enumerate() {
|
for (i, child) in self.children.iter().enumerate() {
|
||||||
|
if sizing == Some(i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
painter.child_layer_at(i);
|
painter.child_layer_at(i);
|
||||||
// The sizing child placed its own content in the box its answer
|
// A box that owes nothing to this child's own answer: where it
|
||||||
// decided, and this box was derived from that answer, so applying
|
// sits in one bigger than itself is its own business.
|
||||||
// its alignment again here would place it twice. Every other
|
painter.widget_within(child, region);
|
||||||
// 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),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,31 @@ fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() {
|
|||||||
assert!(crowded > whole_row, "{crowded} against {whole_row}");
|
assert!(crowded > whole_row, "{crowded} against {whole_row}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A stack takes its size from one child and gives every child that size, so
|
||||||
|
/// a child asking for half of it is asking for half of what it is itself the
|
||||||
|
/// size of. Once the stack has been placed at the length it reported that
|
||||||
|
/// length is the box, and taking the fraction of it again takes it twice:
|
||||||
|
/// half a row became a quarter, and a further stack around it a further half.
|
||||||
|
/// Nothing pinned it because a pixel is the same length wherever it is taken
|
||||||
|
/// from, so only a share ever shrank -- and warm and cold shrink alike, so no
|
||||||
|
/// oracle saw it either.
|
||||||
|
#[test]
|
||||||
|
fn a_stack_sized_by_a_child_does_not_take_that_childs_fraction_twice() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let half = rect(Color::RED).width(rel(0.5)).add(&mut h.rsc);
|
||||||
|
let behind = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let stack = Stack {
|
||||||
|
children: vec![behind.add_strong(&mut h.rsc), half.add_strong(&mut h.rsc)],
|
||||||
|
size: StackSize::Child(1),
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root((stack,).span(Dir::RIGHT).width(rel(1.0)));
|
||||||
|
|
||||||
|
assert_corners!(h, stack, (0, 0), (200, 200));
|
||||||
|
assert_corners!(h, half, (0, 0), (200, 200));
|
||||||
|
assert_corners!(h, behind, (0, 0), (200, 200));
|
||||||
|
}
|
||||||
|
|
||||||
/// The same reading through a pad: its inset is the whole box less the
|
/// The same reading through a pad: its inset is the whole box less the
|
||||||
/// padding, so half of the inset plus the padding is half the box plus one
|
/// padding, so half of the inset plus the padding is half the box plus one
|
||||||
/// padding, not two.
|
/// padding, not two.
|
||||||
|
|||||||
Reference in new issue
Block a user