Compare commits
3
Commits
2ed5503717
...
7601aa2a5d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7601aa2a5d | ||
|
|
c330ecec2b | ||
|
|
39f7b08c6c |
No files matched your search
@@ -52,6 +52,9 @@ impl Holds {
|
|||||||
/// allowance: inverting it is two divisions and nothing else, and the
|
/// allowance: inverting it is two divisions and nothing else, and the
|
||||||
/// whole of a box maps back to itself.
|
/// whole of a box maps back to itself.
|
||||||
pub const fn through(self, len: Len) -> Self {
|
pub const fn through(self, len: Len) -> Self {
|
||||||
|
if self.lo.raw() == Px::MIN.raw() && self.hi.raw() == Px::MAX.raw() {
|
||||||
|
return Self::ANY;
|
||||||
|
}
|
||||||
let rel = len.rel.raw() as i64;
|
let rel = len.rel.raw() as i64;
|
||||||
if rel == 0 {
|
if rel == 0 {
|
||||||
return Self::ANY;
|
return Self::ANY;
|
||||||
@@ -92,6 +95,16 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::Rel;
|
use crate::Rel;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_unrestricted_range_stays_unrestricted_through_any_length() {
|
||||||
|
for rel in [-2.0, -0.5, 0.0, 0.5, 1.0, 2.0] {
|
||||||
|
for px in [-8, 0, 8] {
|
||||||
|
let len = Len::from_parts(Rel::from_f32(rel), Px::from_int(px));
|
||||||
|
assert_eq!(Holds::ANY.through(len), Holds::ANY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn through_reverses_a_range_for_a_negative_fraction() {
|
fn through_reverses_a_range_for_a_negative_fraction() {
|
||||||
// `10 - box / 2` is between 20 and 40 for boxes from -60 to -20.
|
// `10 - box / 2` is between 20 and 40 for boxes from -60 to -20.
|
||||||
|
|||||||
+25
-30
@@ -139,7 +139,7 @@ impl<'a> Painter<'a> {
|
|||||||
/// around one child wants, since its box is the child's.
|
/// around one child wants, since its box is the child's.
|
||||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||||
let own = self.placement;
|
let own = self.placement;
|
||||||
self.widget_at_inner(id, UiRegion::FULL, [Some(own.x), Some(own.y)], true)
|
self.widget_at_inner(id, UiRegion::FULL, [Some(own.x), Some(own.y)], true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||||
@@ -191,7 +191,7 @@ impl<'a> Painter<'a> {
|
|||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
placement: [Option<UiSpan>; 2],
|
placement: [Option<UiSpan>; 2],
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
self.widget_at_inner(id, region, placement, false)
|
self.widget_at_inner(id, region, placement, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_at_inner<'s, W: ?Sized>(
|
fn widget_at_inner<'s, W: ?Sized>(
|
||||||
@@ -200,6 +200,7 @@ impl<'a> Painter<'a> {
|
|||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
placement: [Option<UiSpan>; 2],
|
placement: [Option<UiSpan>; 2],
|
||||||
inherited: bool,
|
inherited: bool,
|
||||||
|
measuring: bool,
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
if inherited {
|
if inherited {
|
||||||
if !self.inherited_children.contains(&id.id()) {
|
if !self.inherited_children.contains(&id.id()) {
|
||||||
@@ -266,6 +267,7 @@ impl<'a> Painter<'a> {
|
|||||||
placement,
|
placement,
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
|
measuring,
|
||||||
self.rsc,
|
self.rsc,
|
||||||
);
|
);
|
||||||
// Whatever the child's answer holds for keeps this one to the boxes
|
// Whatever the child's answer holds for keeps this one to the boxes
|
||||||
@@ -321,32 +323,34 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A child's length in the region it is about to be offered, if it can
|
/// Measures a child's length from its hint, a retained answer, or `draw`.
|
||||||
/// be had without drawing it: from its hint, or from a drawing it already
|
/// A fresh draw evaluates the offer without placing its answer. The caller
|
||||||
/// has that holds for that box.
|
/// must later place or undraw the child.
|
||||||
pub fn known_len<W: ?Sized>(
|
pub fn measure_len<W: ?Sized>(
|
||||||
&mut self,
|
&mut self,
|
||||||
child: &StrongWidget<W>,
|
child: &StrongWidget<W>,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
placement: [Option<UiSpan>; 2],
|
placement: [Option<UiSpan>; 2],
|
||||||
) -> Option<LayoutLen> {
|
) -> LayoutLen {
|
||||||
|
let offered = placement;
|
||||||
let declared = self.declared_lens(child);
|
let declared = self.declared_lens(child);
|
||||||
let align = self.rsc.widgets().alignment(child.id());
|
let align = self.rsc.widgets().alignment(child.id());
|
||||||
let (local, placement) = ask_box(region, declared, align, placement);
|
let (local, placement) = ask_box(region, declared, align, placement);
|
||||||
let first_ask = self.at_offer && !self.offered.contains(&child.id());
|
let first_ask = self.at_offer && !self.offered.contains(&child.id());
|
||||||
|
|
||||||
if let Some(hint) = self.size_hint(child, axis) {
|
if let Some(hint) = self.size_hint(child, axis) {
|
||||||
return Some(hint);
|
return hint;
|
||||||
}
|
}
|
||||||
let px = local.size().to_px(self.px);
|
let px = local.size().to_px(self.px);
|
||||||
let (size, holds) = self.state.retained_size(
|
let retained =
|
||||||
child.id(),
|
self.state
|
||||||
px,
|
.retained_size(child.id(), px, placement, self.move_idx, self.rsc.widgets());
|
||||||
placement,
|
let Some((size, holds)) = retained else {
|
||||||
self.move_idx,
|
return self
|
||||||
self.rsc.widgets(),
|
.widget_at_inner(child, region, offered, false, true)
|
||||||
)?;
|
.len(axis);
|
||||||
|
};
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::RetainedSizeHits);
|
diag::bump(Counter::RetainedSizeHits);
|
||||||
self.depend_on(child);
|
self.depend_on(child);
|
||||||
@@ -364,7 +368,7 @@ impl<'a> Painter<'a> {
|
|||||||
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
||||||
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
||||||
}
|
}
|
||||||
Some(in_parent_frame(size, local.size(), declared).axis(axis))
|
in_parent_frame(size, local.size(), declared).axis(axis)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this is the first box a child is asked about in during a draw
|
/// Whether this is the first box a child is asked about in during a draw
|
||||||
@@ -712,16 +716,9 @@ pub(crate) fn placed_box(region: UiRegion, lens: UiVec2, align: RegionAlign) ->
|
|||||||
placed
|
placed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A child's own region and where in it its drawing goes, from the box it is
|
/// A declared axis gets a frame of that length, aligned within the parent's
|
||||||
/// offered, the lengths its rules declare, and what its parent chose.
|
/// slot (or the offer). Undeclared axes keep the offered frame and chosen
|
||||||
///
|
/// placement, so their reported fractions retain that reference.
|
||||||
/// A rule gives the region its length outright -- that is what makes a rule
|
|
||||||
/// win, and it is why a widget under one never learns of it -- and the region
|
|
||||||
/// then sits where its parent placed it, or where its alignment says if its
|
|
||||||
/// parent left the axis open. With no rule the region is the whole of what
|
|
||||||
/// was offered, since that is the area a fraction under it is a fraction of,
|
|
||||||
/// and what the parent chose is where in it the drawing goes. So the two
|
|
||||||
/// coordinate spaces are the same one wherever a placement survives.
|
|
||||||
pub(crate) fn ask_box(
|
pub(crate) fn ask_box(
|
||||||
mut region: UiRegion,
|
mut region: UiRegion,
|
||||||
declared: [Option<LayoutLen>; 2],
|
declared: [Option<LayoutLen>; 2],
|
||||||
@@ -736,10 +733,8 @@ pub(crate) fn ask_box(
|
|||||||
};
|
};
|
||||||
let span = region.axis_mut(axis);
|
let span = region.axis_mut(axis);
|
||||||
let len = Len::from_parts(len.rel, len.px);
|
let len = Len::from_parts(len.rel, len.px);
|
||||||
span.start = match chosen {
|
let slot = chosen.unwrap_or(*span);
|
||||||
Some(chosen) => chosen.start,
|
span.start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
|
||||||
None => span.start + (span.len() - len).scale(align.axis(axis).rel()),
|
|
||||||
};
|
|
||||||
span.end = span.start + len;
|
span.end = span.start + len;
|
||||||
}
|
}
|
||||||
(region, placed)
|
(region, placed)
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ impl UiRenderState {
|
|||||||
if let Some(id) = root {
|
if let Some(id) = root {
|
||||||
let region = Self::root_region(id.id(), rsc.widgets());
|
let region = Self::root_region(id.id(), rsc.widgets());
|
||||||
let info = self.root_info(region);
|
let info = self.root_info(region);
|
||||||
self.draw_inner(id.id(), region, info, None, rsc);
|
self.draw_inner(id.id(), region, info, None, false, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +213,7 @@ impl UiRenderState {
|
|||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
info: DrawInfo,
|
info: DrawInfo,
|
||||||
mut old: Option<ActiveData>,
|
mut old: Option<ActiveData>,
|
||||||
|
measuring: bool,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
) -> (Size, LayoutHolds) {
|
) -> (Size, LayoutHolds) {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
@@ -239,16 +240,20 @@ impl UiRenderState {
|
|||||||
self.draw_at(id, region, info.offered_placement(), info, old.take(), rsc)
|
self.draw_at(id, region, info.offered_placement(), info, old.take(), rsc)
|
||||||
});
|
});
|
||||||
|
|
||||||
let declared = declared_lens(rsc.widgets(), id);
|
|
||||||
// Where the drawing goes, in the region's own coordinates: what the
|
// Where the drawing goes, in the region's own coordinates: what the
|
||||||
// parent chose, and on any axis it left open, what the answer took of
|
// parent chose, and on any axis it left open, what the answer took of
|
||||||
// the region placed by the widget's alignment. The region itself does
|
// the region placed by the widget's alignment. The region itself does
|
||||||
// not change, so nothing under it resolves a fraction a second time.
|
// not change, so nothing under it resolves a fraction a second time.
|
||||||
|
let placement = if measuring {
|
||||||
|
info.offered_placement()
|
||||||
|
} else {
|
||||||
|
let declared = declared_lens(rsc.widgets(), id);
|
||||||
let lens = placed_lens(answer.0, declared, info.decided());
|
let lens = placed_lens(answer.0, declared, info.decided());
|
||||||
let own = placed_box(UiRegion::FULL, lens, align);
|
let own = placed_box(UiRegion::FULL, lens, align);
|
||||||
let placement = UiRegion {
|
UiRegion {
|
||||||
x: info.placement[0].unwrap_or(own.x),
|
x: info.placement[0].unwrap_or(own.x),
|
||||||
y: info.placement[1].unwrap_or(own.y),
|
y: info.placement[1].unwrap_or(own.y),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.place(id, region, placement, info, rsc);
|
self.place(id, region, placement, info, rsc);
|
||||||
|
|
||||||
@@ -1137,7 +1142,7 @@ impl UiRenderState {
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::LocalRedraws);
|
diag::bump(Counter::LocalRedraws);
|
||||||
let old = self.remove(id, false, rsc);
|
let old = self.remove(id, false, rsc);
|
||||||
self.draw_inner(id, region, info, old, rsc);
|
self.draw_inner(id, region, info, old, false, rsc);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
let (given_px, offered_px) = self.asked_px(id);
|
let (given_px, offered_px) = self.asked_px(id);
|
||||||
@@ -1179,9 +1184,9 @@ impl UiRenderState {
|
|||||||
placement: info.offer_placement,
|
placement: info.offer_placement,
|
||||||
..info
|
..info
|
||||||
};
|
};
|
||||||
let answer = self.draw_inner(id, given, offered, old, rsc);
|
let answer = self.draw_inner(id, given, offered, old, false, rsc);
|
||||||
if info.placement != offered.placement {
|
if info.placement != offered.placement {
|
||||||
self.draw_inner(id, given, info, None, rsc);
|
self.draw_inner(id, given, info, None, false, rsc);
|
||||||
}
|
}
|
||||||
if Some(answer) != was_answer {
|
if Some(answer) != was_answer {
|
||||||
// Its parent chose its box knowing the old answer, so it lays out
|
// Its parent chose its box knowing the old answer, so it lays out
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ impl Widget for Scroll {
|
|||||||
let whole = UiRegion::FULL;
|
let whole = UiRegion::FULL;
|
||||||
let own = painter.placement();
|
let own = painter.placement();
|
||||||
let answer_len =
|
let answer_len =
|
||||||
match painter.known_len(&self.inner, self.axis, whole, [Some(own.x), Some(own.y)]) {
|
painter.measure_len(&self.inner, self.axis, whole, [Some(own.x), Some(own.y)]);
|
||||||
Some(len) => len,
|
|
||||||
None => painter.widget(&self.inner).size().axis(self.axis),
|
|
||||||
};
|
|
||||||
let content = answer_len.apply_leftover();
|
let content = answer_len.apply_leftover();
|
||||||
self.container_len = container_len;
|
self.container_len = container_len;
|
||||||
self.content_len = content.to_px(container_len);
|
self.content_len = content.to_px(container_len);
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ impl Widget for Span {
|
|||||||
// from the cursor, because a text has to wrap at the width
|
// from the cursor, because a text has to wrap at the width
|
||||||
// actually there.
|
// actually there.
|
||||||
let room = axis.pair(Some(along(cursor, far)), None);
|
let room = axis.pair(Some(along(cursor, far)), None);
|
||||||
let len = match painter.known_len(child, axis, region, room) {
|
let len = painter.measure_len(child, axis, region, room);
|
||||||
Some(len) => len,
|
|
||||||
None => painter.widget_at(child, region, room).len(axis),
|
|
||||||
};
|
|
||||||
cursor.px += len.px + self.gap;
|
cursor.px += len.px + self.gap;
|
||||||
cursor.rel += len.rel;
|
cursor.rel += len.rel;
|
||||||
lens.push(len);
|
lens.push(len);
|
||||||
|
|||||||
@@ -716,3 +716,24 @@ fn a_stack_sized_by_a_child_does_not_take_that_childs_fraction_twice() {
|
|||||||
assert_corners!(h, half, (0, 0), (200, 200));
|
assert_corners!(h, half, (0, 0), (200, 200));
|
||||||
assert_corners!(h, behind, (0, 0), (200, 200));
|
assert_corners!(h, behind, (0, 0), (200, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_fixed_child_is_centered_in_its_wrappers_share() {
|
||||||
|
let mut h = Harness::new((600, 300));
|
||||||
|
let leaf = rect(Color::RED).sized((100, 100)).center().add(&mut h.rsc);
|
||||||
|
let wrapper = leaf
|
||||||
|
.wrapper()
|
||||||
|
.width(leftover(2))
|
||||||
|
.height(rel(1.0))
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let other = rect(Color::BLUE).width(200).add(&mut h.rsc);
|
||||||
|
h.set_root((other, wrapper).span(Dir::RIGHT));
|
||||||
|
|
||||||
|
assert_corners!(h, wrapper, (200, 0), (600, 300));
|
||||||
|
assert_corners!(h, leaf, (350, 100), (450, 200));
|
||||||
|
|
||||||
|
h.resize((900, 400));
|
||||||
|
h.frame();
|
||||||
|
assert_corners!(h, wrapper, (200, 0), (900, 400));
|
||||||
|
assert_corners!(h, leaf, (500, 150), (600, 250));
|
||||||
|
}
|
||||||
@@ -914,3 +914,40 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
|
|||||||
assert_eq!(mask(&warm, leaf.id()), mask(&cold, other.id()));
|
assert_eq!(mask(&warm, leaf.id()), mask(&cold, other.id()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_span_does_not_place_its_measurement_before_assigning_the_childs_slot() {
|
||||||
|
struct MeasuredBox(Rc<Cell<usize>>);
|
||||||
|
|
||||||
|
impl Widget for MeasuredBox {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
self.0.set(self.0.get() + 1);
|
||||||
|
painter.px_size();
|
||||||
|
painter.primitive(RectPrimitive::color(Color::BLUE));
|
||||||
|
Size::from((100, 50))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let draws = Rc::new(Cell::new(0));
|
||||||
|
let leaf = MeasuredBox(draws.clone()).add(&mut h.rsc);
|
||||||
|
h.set_root((leaf,).span(Dir::RIGHT).width(rel(1.0)).height(rel(1.0)));
|
||||||
|
|
||||||
|
assert_eq!(draws.get(), 3);
|
||||||
|
assert_corners!(h, leaf, (0, 75), (100, 125));
|
||||||
|
assert_eq!(
|
||||||
|
primitive_bounds(&h, leaf.id()),
|
||||||
|
vec![h.region(&leaf.id()).unwrap()]
|
||||||
|
);
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(draws.get(), 3);
|
||||||
|
|
||||||
|
h.resize((600, 300));
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(draws.get(), 6);
|
||||||
|
assert_corners!(h, leaf, (0, 125), (100, 175));
|
||||||
|
assert_eq!(
|
||||||
|
primitive_bounds(&h, leaf.id()),
|
||||||
|
vec![h.region(&leaf.id()).unwrap()]
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,11 +5,11 @@
|
|||||||
//! cargo test --release --features layout-diagnostics \
|
//! cargo test --release --features layout-diagnostics \
|
||||||
//! --test layout_diagnostics -- --ignored --nocapture
|
//! --test layout_diagnostics -- --ignored --nocapture
|
||||||
//!
|
//!
|
||||||
//! Uninstrumented hardware totals for one phase:
|
//! Build the uninstrumented test with `cargo test --release --test
|
||||||
|
//! layout_diagnostics --no-run`, then run the emitted executable directly:
|
||||||
//!
|
//!
|
||||||
//! IRIS_PHASE=resize IRIS_FRAMES=1000 perf stat \
|
//! IRIS_PHASE=resize IRIS_FRAMES=10000 perf stat -r 7 \
|
||||||
//! -e cycles:u,instructions:u cargo test --release \
|
//! -e cycles:u,instructions:u /path/to/layout_diagnostics --ignored --nocapture
|
||||||
//! --test layout_diagnostics -- --ignored --nocapture
|
|
||||||
//!
|
//!
|
||||||
//! `IRIS_PHASE` is `cold`, `repaint`, `many`, `size`, `scroll`, `resize`, or
|
//! `IRIS_PHASE` is `cold`, `repaint`, `many`, `size`, `scroll`, `resize`, or
|
||||||
//! `all`. `IRIS_SEED`, `IRIS_DEPTH`, and `IRIS_FRAMES` select the load, and
|
//! `all`. `IRIS_SEED`, `IRIS_DEPTH`, and `IRIS_FRAMES` select the load, and
|
||||||
@@ -134,6 +134,9 @@ fn report(label: &str, mut elapsed: Vec<f64>, _harness: &Harness) {
|
|||||||
{
|
{
|
||||||
let diagnostics = iris::core::layout_diagnostics::take();
|
let diagnostics = iris::core::layout_diagnostics::take();
|
||||||
print!("{}", diagnostics.per_frame(frames));
|
print!("{}", diagnostics.per_frame(frames));
|
||||||
|
for event in diagnostics.traces() {
|
||||||
|
println!(" {event:?}");
|
||||||
|
}
|
||||||
for callsite in diagnostics.hot_text().iter().take(3) {
|
for callsite in diagnostics.hot_text().iter().take(3) {
|
||||||
let mut ancestry = Vec::new();
|
let mut ancestry = Vec::new();
|
||||||
let mut id = Some(callsite.id);
|
let mut id = Some(callsite.id);
|
||||||
|
|||||||
Reference in new issue
Block a user