Let a rule beat a hint, and name marking a widget for redraw
A ninth sweep, over the part no earlier round named -- the widget vocabulary and the builder methods, `Widgets`, the examples, the `util` additions and the manifests -- and once more over `77ed7a2`, the eighth sweep's own commit and so itself unreviewed. A hint overrode a rule. `declared_lens` asked `rules[axis].declared()` first and fell through to the widget's own `size_hint` whenever that answered `None` -- which it does for a share, since a share is not a declaration. So a widget carrying `width(leftover(1))` and hinting a pixel length of its own was handed a box of the hint, against the rule and against the comment inside the function: "a hint still narrows the box where no rule does". `Painter::size_hint` spells the same rule-else-hint step three hundred lines up and gets it right, with the reason written on it; both read `Widgets::exact_len` now, and `declared_lens` is the part of its answer that needs nobody to divide it. `Image` is the only widget here whose hint is a declared length, and neither the tests nor the generator builds one, so nothing in this repository could reach the difference -- which is why the dump is unchanged and why the test builds a widget of its own. It records the box it was asked in: 400 with the rule and 50 without, and 50 either way before this. Marking a widget for redraw had no name. Twenty-one sites under `tests/` said it as `widgets_mut().get_dyn_mut(id);` with the widget thrown away, five with a `let _ =` in front, one with a comment explaining what the line was for, and one wrapped in a local function called `mark`. `Widgets::mark_for_redraw` says it. `revision_cost.rs` keeps the long spelling and now says why in place: it is deliberately in the API subset an old worktree also has. `assert_same_regions` could not see the defect the eighth sweep had just fixed. It zips the warm and cold id lists, so a list naming one widget twice -- which is what `width`, `sized` and `align` giving back their own argument produces -- compares fewer boxes than it lists and says nothing about it. It now rejects a repeated id and two lists of different lengths, which also checks the nine fixtures that round left alone: all eighteen cases pass. Bare pairs where the framework has named ones. `random.rs`'s `Lens` and `Aligns` were `[Option<LayoutLen>; 2]` and `[Option<AxisAlign>; 2]`, read as `[0]`/`[1]` and zipped against a hand-written `[Axis::X, Axis::Y]`. They are `SizeRules` and `Align`; `Align` took the `Index<Axis>` every other per-axis pair on this branch has, and `RegionAlign::from` does the "an axis left out is centred" step two rigs were spelling per axis. The three sites that wrote the axis pair out say `Axis::BOTH`, which is what the rest of the layout code says. `BothAxis<T>`, `AxisT`, `XAxis` and `YAxis` -- 45 lines with a const trait, two marker types and three accessors -- have no user anywhere in the workspace. They are the mechanism `impl_axis_index!` replaced, in the file this branch took `Vec2::axis`/`axis_mut` out of. Deleted, which is a drive-by in a block the branch was already rewriting; drop it if the scope matters more. Smaller things, each in its own place: `Wrapper` arrived beside core's `WidgetWrapper`, one word for a widget that wraps a child and for a dynamic borrow guard, so the alias is gone and its two uses name `DynBorrower` -- which is what they are. `Wrapper::new`, `Wrapper::empty` and its `Default` were three names for one value, two of them unused. `Arena::get_mut` was the only `pub(crate)` among `pub` siblings on a public type. `Selector` rounded the pointer onto the pixel grid to do arithmetic on two values already there, losing the precision the platform gave it for nothing; the step between the regions is taken on the grid instead. And the two `debug` profile settings carry their reason where the next reader looks rather than only in the commit that made them, one of which was about renaming `rest`. Format, clippy with and without layout-diagnostics, and the suite (132 + 19 + 13 + 4) are clean. The cold dump over 400 depth-5 trees is byte-identical to `77ed7a2` across all 34,488 boxes, and all three seed scans pass: 400 at depth 5 in 63.27s, 1,000 at depth 6 in 160.45s, 2,000 at depth 4 in 302.52s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
77ed7a24c0
commit
c2b8bf83de
21 files changed
+190
-152
No files matched your search
@@ -25,6 +25,12 @@ tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"]
|
||||
[workspace]
|
||||
members = ["core", "macro", "rig-input"]
|
||||
|
||||
# Full debug info was the bulk of what the linker wrote here and almost none of
|
||||
# what anything read. `dev` keeps line tables and scopes, which is what stepping
|
||||
# through an example wants; the tests keep the line tables alone, which is what
|
||||
# a backtrace reads. Measured when the tests became one target: relinking them
|
||||
# went from 9.8 s to 7.7 s with these, and target/ from 45 GB to 13 GB with the
|
||||
# two changes together.
|
||||
[profile.dev]
|
||||
debug = 1
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Px, Rel};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Align {
|
||||
pub x: Option<AxisAlign>,
|
||||
pub y: Option<AxisAlign>,
|
||||
@@ -214,3 +214,4 @@ impl RegionAlign {
|
||||
}
|
||||
|
||||
impl_axis_index!(RegionAlign => AxisAlign);
|
||||
impl_axis_index!(Align => Option<AxisAlign>);
|
||||
@@ -71,50 +71,5 @@ impl Vec2 {
|
||||
}
|
||||
}
|
||||
|
||||
pub const trait AxisT {
|
||||
fn get() -> Axis;
|
||||
}
|
||||
|
||||
pub struct XAxis;
|
||||
const impl AxisT for XAxis {
|
||||
fn get() -> Axis {
|
||||
Axis::X
|
||||
}
|
||||
}
|
||||
|
||||
pub struct YAxis;
|
||||
const impl AxisT for YAxis {
|
||||
fn get() -> Axis {
|
||||
Axis::Y
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BothAxis<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T> BothAxis<T> {
|
||||
pub const fn axis<A: const AxisT>(&mut self) -> &mut T {
|
||||
match A::get() {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
pub fn take_axis<A: const AxisT>(self) -> T {
|
||||
match A::get() {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
pub fn axis_dyn(&mut self, axis: Axis) -> &mut T {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!({const SHIFT: u32} FixedVec2<SHIFT> => Fixed<SHIFT>);
|
||||
impl_axis_index!(Vec2 => f32);
|
||||
+22
-26
@@ -291,8 +291,8 @@ impl<'a> Painter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||
/// it resolves into its rel base. Reading them depends on nothing -- the box
|
||||
/// What a rule or a hint declares a widget's lengths to be, which whoever
|
||||
/// draws it resolves into its rel base. Reading them depends on nothing -- the box
|
||||
/// that comes of them is kept on the child, and `redraw` compares it
|
||||
/// there.
|
||||
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> Declared {
|
||||
@@ -304,14 +304,7 @@ 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> {
|
||||
let widgets = self.rsc.widgets();
|
||||
// A rule is the answer where there is one: it wins over whatever the
|
||||
// widget would draw, so it has to win over what the widget says too.
|
||||
let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
|
||||
widgets
|
||||
.get_dyn(id.id())
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
});
|
||||
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));
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
@@ -647,24 +640,27 @@ impl Painter<'_> {
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
/// What a widget's box is where a rule or its own hint says so outright.
|
||||
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
||||
let rules = self.size_rules(id);
|
||||
let widget = self.get_dyn(id);
|
||||
Declared::from_axes(|axis| {
|
||||
rules[axis].declared().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 rather than a declaration's, and this
|
||||
// falls away once a widget occupies its reported size inside
|
||||
// the box it was offered.
|
||||
widget
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
.and_then(|len| len.declared())
|
||||
})
|
||||
/// What says a widget's length on one axis without drawing it, if anything
|
||||
/// does. A rule is the answer where there is one: it wins over whatever the
|
||||
/// widget would draw, so it has to win over what the widget says too -- a
|
||||
/// share included, since a share is a length only to whoever divides one,
|
||||
/// and that is the parent rather than this widget.
|
||||
fn exact_len(&self, id: WidgetId, axis: Axis) -> Option<LayoutLen> {
|
||||
self.size_rules(id)[axis].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
|
||||
// rather than a declaration's, and this falls away once a widget
|
||||
// occupies its reported size inside the box it was offered.
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutLen {
|
||||
|
||||
@@ -35,7 +35,7 @@ impl<T, I: IdNum> Arena<T, I> {
|
||||
self.data[i]
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||
pub fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||
&mut self.data[id.idx()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,14 @@ impl Widgets {
|
||||
!self.needs_redraw.is_empty()
|
||||
}
|
||||
|
||||
/// Marks this widget for the next frame to draw again, with nothing about
|
||||
/// it changed. Taking a widget mutably marks it too, which is the ordinary
|
||||
/// content-change signal; this is for a change the borrow cannot express,
|
||||
/// and for asking for the same tree over again.
|
||||
pub fn mark_for_redraw(&mut self, id: impl IdLike) {
|
||||
self.needs_redraw.insert(id.id());
|
||||
}
|
||||
|
||||
pub fn get_dyn(&self, id: WidgetId) -> Option<&dyn Widget> {
|
||||
Some(self.vec.get(id)?.widget.as_ref())
|
||||
}
|
||||
@@ -41,14 +49,14 @@ impl Widgets {
|
||||
|
||||
/// get_dyn but dynamic borrow checking of widgets
|
||||
/// lets you do recursive (tree) operations, like the painter does
|
||||
pub(crate) fn get_dyn_dynamic<'a>(&self, id: WidgetId) -> WidgetWrapper<'a> {
|
||||
pub(crate) fn get_dyn_dynamic<'a>(&self, id: WidgetId) -> DynBorrower<'a, dyn Widget> {
|
||||
// SAFETY: must guarantee no other mutable references to this widget exist
|
||||
// done through the borrow variable
|
||||
let data = unsafe { forget_mut(to_mut(self.vec.get(id).unwrap())) };
|
||||
if data.borrowed {
|
||||
panic!("tried to mutably borrow the same widget twice");
|
||||
}
|
||||
WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed)
|
||||
DynBorrower::new(data.widget.as_mut(), &mut data.borrowed)
|
||||
}
|
||||
|
||||
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
|
||||
@@ -154,7 +162,7 @@ impl Widgets {
|
||||
self.needs_redraw.insert(id);
|
||||
}
|
||||
|
||||
/// Both axes at once, for a caller holding a pair.
|
||||
/// Both axes at once.
|
||||
pub fn set_size_rules(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
@@ -188,8 +196,6 @@ impl Default for Widgets {
|
||||
}
|
||||
}
|
||||
|
||||
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;
|
||||
|
||||
impl<I: IdLike> std::ops::Index<I> for Widgets
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
|
||||
+4
-3
@@ -15,9 +15,10 @@ where
|
||||
let region = ctx.data.render.window_region(&id).unwrap();
|
||||
let id_pos = region.top_left;
|
||||
let container_pos = ctx.data.render.window_region(&container).unwrap().top_left;
|
||||
// The pointer arrives from the platform in floats; everything
|
||||
// it is compared against is on the grid.
|
||||
let pos = (PxVec2::from_f32(ctx.data.pos) + container_pos - id_pos).to_f32();
|
||||
// The two regions are on the grid and the pointer is not, so the
|
||||
// step between them is taken there and the pointer keeps the
|
||||
// precision the platform gave it.
|
||||
let pos = ctx.data.pos + (container_pos - id_pos).to_f32();
|
||||
let size = region.size().to_f32();
|
||||
select(
|
||||
rsc,
|
||||
|
||||
+21
-22
@@ -8,23 +8,16 @@
|
||||
use crate::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The declared lengths of one widget carrying a size rule, by axis.
|
||||
pub type Lens = [Option<LayoutLen>; 2];
|
||||
|
||||
/// Where one widget carrying an alignment sits, by axis. `None` uses the
|
||||
/// centered default.
|
||||
pub type Aligns = [Option<AxisAlign>; 2];
|
||||
|
||||
/// What a test changes between two trees grown from the same seed, so the
|
||||
/// warm one can be mutated and the cold one grown that way to begin with.
|
||||
#[derive(Default)]
|
||||
pub struct Edits {
|
||||
/// Declared sizes, by the order the rules were put on.
|
||||
pub sizes: HashMap<usize, Lens>,
|
||||
pub sizes: HashMap<usize, SizeRules>,
|
||||
/// Which children a span has, by the order the spans were made.
|
||||
pub spans: HashMap<usize, SpanEdit>,
|
||||
/// Alignments, by the order they were put on.
|
||||
pub aligns: HashMap<usize, Aligns>,
|
||||
pub aligns: HashMap<usize, Align>,
|
||||
/// Which widgets own a movable region, by the order they were offered
|
||||
/// one. Region nodes change what a move writes and how deep a primitive's
|
||||
/// chain is, so a tree that never grows one leaves both untested.
|
||||
@@ -176,9 +169,11 @@ pub struct Plan {
|
||||
/// it one and the offer is taken or declined; a second offer to the same
|
||||
/// widget is dropped, because two rules on one widget would settle in the
|
||||
/// order they were applied rather than in grow order.
|
||||
pub size: Option<Lens>,
|
||||
/// The alignment it carries, under the same one-offer rule.
|
||||
pub align: Option<Aligns>,
|
||||
pub size: Option<SizeRules>,
|
||||
/// The alignment it carries, under the same one-offer rule. An axis left
|
||||
/// out takes the centered default, which is what [`RegionAlign`] reads it
|
||||
/// as.
|
||||
pub align: Option<Align>,
|
||||
/// Whether it was offered a movable region of its own and what it
|
||||
/// answered. `Some(false)` is an offer declined, which still uses up the
|
||||
/// one offer, where `None` is an offer never made.
|
||||
@@ -651,7 +646,7 @@ impl Sow<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn align(&mut self) -> Aligns {
|
||||
fn align(&mut self) -> Align {
|
||||
let axis = |s: &mut Self| match s.rng.below(4) {
|
||||
0 => None,
|
||||
1 => Some(AxisAlign::NEG),
|
||||
@@ -661,15 +656,21 @@ impl Sow<'_> {
|
||||
let (x, y) = (axis(self), axis(self));
|
||||
// Aligning on neither axis leaves the branch unexercised.
|
||||
match x.is_none() && y.is_none() {
|
||||
true => [Some(AxisAlign::CENTER), y],
|
||||
false => [x, y],
|
||||
true => Align {
|
||||
x: Some(AxisAlign::CENTER),
|
||||
y,
|
||||
},
|
||||
false => Align { x, y },
|
||||
}
|
||||
}
|
||||
|
||||
/// A declared size over half the tree, kept where a test can change it.
|
||||
fn sized(&mut self, inner: &mut Plan) {
|
||||
let take = self.rng.chance();
|
||||
let lens = [self.len(), self.len()];
|
||||
let lens = SizeRules {
|
||||
x: self.len().into(),
|
||||
y: self.len().into(),
|
||||
};
|
||||
if !take || inner.size.is_some() {
|
||||
return;
|
||||
}
|
||||
@@ -807,16 +808,14 @@ impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
||||
let built = self.kind(&plan.kind);
|
||||
let id = built.id();
|
||||
if let Some(lens) = plan.size {
|
||||
self.rsc
|
||||
.ui_mut()
|
||||
.widgets
|
||||
.set_size_rules(id, lens[0], lens[1]);
|
||||
self.rsc.ui_mut().widgets.set_size_rules(id, lens.x, lens.y);
|
||||
self.tree.sized.push(id);
|
||||
}
|
||||
if let Some(align) = plan.align {
|
||||
let resolved = RegionAlign::from(align);
|
||||
let widgets = &mut self.rsc.ui_mut().widgets;
|
||||
for (axis, align) in [Axis::X, Axis::Y].into_iter().zip(align) {
|
||||
widgets.set_alignment(id, axis, align.unwrap_or_default());
|
||||
for axis in Axis::BOTH {
|
||||
widgets.set_alignment(id, axis, resolved[axis]);
|
||||
}
|
||||
self.tree.aligned.push(id);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ widget_trait! {
|
||||
move |state| {
|
||||
let id = self.add(state);
|
||||
let widgets = &mut state.ui_mut().widgets;
|
||||
for (axis, align) in [(Axis::X, align.x), (Axis::Y, align.y)] {
|
||||
if let Some(align) = align {
|
||||
for axis in Axis::BOTH {
|
||||
if let Some(align) = align[axis] {
|
||||
widgets.set_alignment(id, axis, align);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-11
@@ -9,6 +9,7 @@ use std::marker::Unsize;
|
||||
///
|
||||
/// Its child is optional so it can also be the swappable slot a tab bar
|
||||
/// needs, which is what it was written for.
|
||||
#[derive(Default)]
|
||||
pub struct Wrapper {
|
||||
pub inner: Option<StrongWidget>,
|
||||
}
|
||||
@@ -26,11 +27,7 @@ impl Wrapper {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
inner: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: StrongWidget<W>) {
|
||||
self.inner = Some(to)
|
||||
}
|
||||
@@ -42,9 +39,3 @@ impl Wrapper {
|
||||
self.inner.replace(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Wrapper {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,8 @@ fn a_branch_taken_on_a_measurement_holds_across_repaints() {
|
||||
assert_ne!(first, (false, false), "threshold {threshold}: neither drew");
|
||||
|
||||
for frame in 0..4 {
|
||||
h.rsc.widgets_mut().get_dyn_mut(wide);
|
||||
h.rsc.widgets_mut().get_dyn_mut(narrow);
|
||||
h.rsc.widgets_mut().mark_for_redraw(wide);
|
||||
h.rsc.widgets_mut().mark_for_redraw(narrow);
|
||||
h.frame();
|
||||
assert_eq!(
|
||||
taken(&h, wide, narrow),
|
||||
@@ -88,7 +88,7 @@ fn a_branch_taken_on_a_measurement_is_the_one_a_cold_start_takes() {
|
||||
let (wide, narrow) = plant(&mut warm, threshold);
|
||||
warm.resize((640, 480));
|
||||
warm.frame();
|
||||
warm.rsc.widgets_mut().get_dyn_mut(wide);
|
||||
warm.rsc.widgets_mut().mark_for_redraw(wide);
|
||||
warm.frame();
|
||||
|
||||
let mut cold = Harness::new((640, 480));
|
||||
|
||||
@@ -18,7 +18,7 @@ fn a_wrapping_text_in_a_span_settles_on_one_width() {
|
||||
let r = h.region(&t.id()).unwrap();
|
||||
widths.push(r.bot_right.x - r.top_left.x);
|
||||
// Redrawing it changes nothing about the state, so nothing may move.
|
||||
h.rsc.widgets_mut().get_dyn_mut(t.id());
|
||||
h.rsc.widgets_mut().mark_for_redraw(t.id());
|
||||
h.frame();
|
||||
}
|
||||
println!("widths over six frames: {widths:?}");
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Where a frame puts things, with no window to put them in.
|
||||
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use iris::harness::{Harness, assert_corners};
|
||||
use iris::prelude::*;
|
||||
|
||||
@@ -219,6 +221,45 @@ fn an_empty_widget_takes_a_share_of_a_span() {
|
||||
assert_corners!(h, right, (300, 0), (400, 200));
|
||||
}
|
||||
|
||||
/// A widget with a natural pixel size, like an image, which records the box
|
||||
/// it was asked in so a test can see which length decided it.
|
||||
struct NaturalSize {
|
||||
len: f32,
|
||||
asked: Rc<Cell<f32>>,
|
||||
}
|
||||
|
||||
impl Widget for NaturalSize {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
self.asked.set(painter.px_len(Axis::X).to_f32());
|
||||
Size::px(Vec2::new(self.len, self.len))
|
||||
}
|
||||
|
||||
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
|
||||
Some(LayoutLen::px(self.len))
|
||||
}
|
||||
}
|
||||
|
||||
/// A rule wins over what the widget says about itself, and a share is a rule:
|
||||
/// it is a length only to whoever divides one, and nobody here does, so the
|
||||
/// widget is asked in the whole box rather than in the size it asked for.
|
||||
#[test]
|
||||
fn a_share_rule_beats_the_widgets_own_pixel_size() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let asked = Rc::new(Cell::new(0.0));
|
||||
let natural = NaturalSize {
|
||||
len: 50.0,
|
||||
asked: asked.clone(),
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.set_root(natural.wrapper());
|
||||
assert_eq!(asked.get(), 50.0, "its hint gives it its own size");
|
||||
|
||||
h.set_len(natural, Axis::X, LayoutLen::LEFTOVER);
|
||||
h.frame();
|
||||
|
||||
assert_eq!(asked.get(), 400.0, "the share is all of the box");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_child_drawn_twice_moves_once() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
|
||||
+18
-2
@@ -25,11 +25,27 @@ fn some_edits(seed: u64, of: &Plan) -> Edits {
|
||||
Edits {
|
||||
sizes: pick(sized, &mut rng)
|
||||
.into_iter()
|
||||
.map(|i| (i, [Some(LayoutLen::LEFTOVER), None]))
|
||||
.map(|i| {
|
||||
(
|
||||
i,
|
||||
SizeRules {
|
||||
x: SizeRule::Exact(LayoutLen::LEFTOVER),
|
||||
y: SizeRule::Free,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
aligns: pick(aligned, &mut rng)
|
||||
.into_iter()
|
||||
.map(|i| (i, [Some(AxisAlign::POS), None]))
|
||||
.map(|i| {
|
||||
(
|
||||
i,
|
||||
Align {
|
||||
x: Some(AxisAlign::POS),
|
||||
y: None,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
nodes: pick(nodes, &mut rng)
|
||||
.into_iter()
|
||||
|
||||
@@ -67,7 +67,7 @@ fn a_redrawn_layered_widget_keeps_the_layer_it_was_entered_on() {
|
||||
let root = Layered { children }.add(&mut h.rsc);
|
||||
h.set_root(root);
|
||||
|
||||
h.rsc.widgets_mut().get_dyn_mut(root.id());
|
||||
h.rsc.widgets_mut().mark_for_redraw(root.id());
|
||||
h.frame();
|
||||
|
||||
let label = h.rsc.widgets().label(root.id());
|
||||
@@ -176,9 +176,9 @@ fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
||||
h.set_root((first, second).span(Dir::RIGHT));
|
||||
let settled = draws.get();
|
||||
|
||||
// Taking mutable access is the ordinary content-change signal. This
|
||||
// widget returns the same size, so the parent has nothing to lay out.
|
||||
let _ = h.rsc.widgets_mut().get_dyn_mut(first.id());
|
||||
// Marked with nothing about it changed, and it reports the same size
|
||||
// either way, so the parent has nothing to lay out.
|
||||
h.rsc.widgets_mut().mark_for_redraw(first.id());
|
||||
h.frame();
|
||||
|
||||
assert_eq!(draws.get(), settled + 1);
|
||||
@@ -193,7 +193,7 @@ fn a_span_child_survives_the_next_frame() {
|
||||
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
||||
h.set_root((top, bottom).span(Dir::DOWN));
|
||||
|
||||
h.rsc.widgets_mut().get_dyn_mut(top.id());
|
||||
h.rsc.widgets_mut().mark_for_redraw(top.id());
|
||||
h.frame();
|
||||
|
||||
assert_corners!(h, top, (0, 0), (400, 80));
|
||||
@@ -655,7 +655,7 @@ fn a_masked_widget_redrawn_on_its_own_sets_its_mask_again() {
|
||||
let masked = inner.masked().add(&mut h.rsc);
|
||||
let other = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||
h.set_root((other, masked).span(Dir::RIGHT));
|
||||
h.rsc.widgets_mut().get_dyn_mut(masked.id());
|
||||
h.rsc.widgets_mut().mark_for_redraw(masked.id());
|
||||
h.frame();
|
||||
assert_corners!(h, inner, (100, 0), (400, 200));
|
||||
}
|
||||
@@ -995,7 +995,7 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
|
||||
assert_eq!(draws.get(), before);
|
||||
let retained = primitive_bounds(&h, text.id());
|
||||
assert!(!retained.is_empty());
|
||||
let _ = h.rsc.widgets_mut().get_dyn_mut(text.id());
|
||||
h.rsc.widgets_mut().mark_for_redraw(text.id());
|
||||
h.frame();
|
||||
assert!(draws.get() > before);
|
||||
assert_eq!(retained, primitive_bounds(&h, text.id()));
|
||||
@@ -1370,7 +1370,7 @@ fn a_redrawn_mask_keeps_reused_primitives_clipped_when_it_moves() {
|
||||
h.set_root((first, masked).span(Dir::DOWN));
|
||||
let mask = h.render.active[&masked.id()].mask;
|
||||
let settled = draws.get();
|
||||
h.rsc.widgets_mut().get_dyn_mut(masked.id());
|
||||
h.rsc.widgets_mut().mark_for_redraw(masked.id());
|
||||
h.frame();
|
||||
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
|
||||
assert_eq!(draws.get(), settled, "a mask repaint must reuse its child");
|
||||
|
||||
@@ -113,7 +113,7 @@ fn wrapping_content_beside_a_fixed_length_is_stable_warm_and_cold() {
|
||||
|
||||
let mut warm = Harness::new((900, 300));
|
||||
let (text, content) = plant(&mut warm);
|
||||
warm.rsc.widgets_mut().get_dyn_mut(text);
|
||||
warm.rsc.widgets_mut().mark_for_redraw(text);
|
||||
warm.frame();
|
||||
|
||||
let mut cold = Harness::new((900, 300));
|
||||
|
||||
@@ -10,12 +10,20 @@
|
||||
//! that node was given. The last is a wrapping text handed back the width
|
||||
//! it measured, rounded to a step below the line it measured there.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use iris::harness::Harness;
|
||||
use iris::prelude::*;
|
||||
use iris::random::Branch;
|
||||
|
||||
/// Every widget in the same place warm as cold, reported all at once: which
|
||||
/// of a dozen boxes moved is the whole of what a shrunk case has to say.
|
||||
///
|
||||
/// A list that names one widget twice is an error rather than a redundant
|
||||
/// check. `width`, `sized` and `align` give back the widget they were handed,
|
||||
/// so a fixture built through them can name one text three times, and then a
|
||||
/// case comparing six boxes compares four and says nothing about it. One
|
||||
/// fixture builds both lists, so checking the warm one checks both.
|
||||
#[track_caller]
|
||||
fn assert_same_regions(
|
||||
warm: &Harness,
|
||||
@@ -23,6 +31,17 @@ fn assert_same_regions(
|
||||
cold: &Harness,
|
||||
cold_ids: &[WidgetId],
|
||||
) {
|
||||
assert_eq!(
|
||||
warm_ids.len(),
|
||||
cold_ids.len(),
|
||||
"the warm and cold fixtures list different widgets"
|
||||
);
|
||||
let named: HashSet<&WidgetId> = warm_ids.iter().collect();
|
||||
assert_eq!(
|
||||
named.len(),
|
||||
warm_ids.len(),
|
||||
"a widget is listed twice: {warm_ids:?}"
|
||||
);
|
||||
let mut wrong = Vec::new();
|
||||
for (i, (&w, &c)) in warm_ids.iter().zip(cold_ids).enumerate() {
|
||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
||||
@@ -95,7 +114,7 @@ fn repainting_a_stack_uses_the_box_its_sizing_child_decided() {
|
||||
let mut warm = Harness::new((900, 1200));
|
||||
let ids = plant_stack_in_its_sizing_childs_box(&mut warm);
|
||||
for &id in &ids {
|
||||
warm.rsc.widgets_mut().get_dyn_mut(id);
|
||||
warm.rsc.widgets_mut().mark_for_redraw(id);
|
||||
}
|
||||
warm.frame();
|
||||
|
||||
@@ -228,7 +247,7 @@ fn one_frame_is_enough() {
|
||||
let first = h.region(&ids[1]).unwrap();
|
||||
for _ in 0..3 {
|
||||
for &id in &ids {
|
||||
h.rsc.widgets_mut().get_dyn_mut(id);
|
||||
h.rsc.widgets_mut().mark_for_redraw(id);
|
||||
}
|
||||
h.frame();
|
||||
}
|
||||
@@ -250,7 +269,7 @@ fn repainting_everything_moves_nothing() {
|
||||
let mut warm = Harness::new((640, 900));
|
||||
let ids = plant(&mut warm);
|
||||
for &id in &ids {
|
||||
warm.rsc.widgets_mut().get_dyn_mut(id);
|
||||
warm.rsc.widgets_mut().mark_for_redraw(id);
|
||||
}
|
||||
warm.frame();
|
||||
|
||||
@@ -562,7 +581,7 @@ fn plant_nested_scrolls(h: &mut Harness) -> Vec<WidgetId> {
|
||||
fn redrawing_one_widget_does_not_move_what_scrolls_around_it() {
|
||||
let mut warm = Harness::new((900, 1200));
|
||||
let ids = plant_nested_scrolls(&mut warm);
|
||||
warm.rsc.widgets_mut().get_dyn_mut(ids[0]);
|
||||
warm.rsc.widgets_mut().mark_for_redraw(ids[0]);
|
||||
warm.frame();
|
||||
|
||||
let mut cold = Harness::new((900, 1200));
|
||||
|
||||
@@ -61,8 +61,8 @@ fn a_selected_widget_retains_its_layout_events() {
|
||||
diagnostics::trace_widget(leaf.id());
|
||||
let _ = diagnostics::take();
|
||||
|
||||
let _ = harness.rsc.widgets_mut().get_dyn_mut(root.id());
|
||||
let _ = harness.rsc.widgets_mut().get_dyn_mut(leaf.id());
|
||||
harness.rsc.widgets_mut().mark_for_redraw(root.id());
|
||||
harness.rsc.widgets_mut().mark_for_redraw(leaf.id());
|
||||
harness.frame();
|
||||
|
||||
let report = diagnostics::take();
|
||||
@@ -226,7 +226,7 @@ fn layout_cost() {
|
||||
trace_selected(&tree);
|
||||
let leaf = tree.ids[0];
|
||||
run("repaint", frames, &mut harness, move |harness, _| {
|
||||
let _ = harness.rsc.widgets_mut().get_dyn_mut(leaf);
|
||||
harness.rsc.widgets_mut().mark_for_redraw(leaf);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ fn layout_cost() {
|
||||
println!("marking {} of {} widgets", dirty.len(), tree.ids.len());
|
||||
run("many", frames, &mut harness, move |harness, _| {
|
||||
for &id in &dirty {
|
||||
harness.rsc.widgets_mut().get_dyn_mut(id);
|
||||
harness.rsc.widgets_mut().mark_for_redraw(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -194,7 +194,9 @@ fn text_memory() {
|
||||
h.frame();
|
||||
}
|
||||
report("after 40 resizes");
|
||||
// Settled: the output holds still and one leaf repaints per frame.
|
||||
// Settled: the output holds still and one leaf repaints per frame. Marked
|
||||
// by taking it mutably because the revision at the top of this file has no
|
||||
// `mark_for_redraw`, and the same source has to build against both.
|
||||
for _ in 0..10 {
|
||||
let _ = h.rsc.widgets_mut().get_dyn_mut(paragraphs[0]);
|
||||
h.frame();
|
||||
|
||||
+16
-11
@@ -13,7 +13,7 @@
|
||||
|
||||
use iris::harness::Harness;
|
||||
use iris::prelude::*;
|
||||
use iris::random::{Aligns, Edits, Kind, Lens, Plan, Rng, SpanEdit, Tree, build};
|
||||
use iris::random::{Edits, Kind, Plan, Rng, SpanEdit, Tree, build};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A seed per thread but one, since a seed grows, lays out and drops its tree
|
||||
@@ -190,7 +190,7 @@ impl Case {
|
||||
|
||||
fn mark(warm: &mut Harness, tree: &Tree, step: usize) {
|
||||
for &id in tree.ids.iter().step_by(step) {
|
||||
warm.rsc.widgets_mut().get_dyn_mut(id);
|
||||
warm.rsc.widgets_mut().mark_for_redraw(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,27 +198,32 @@ fn a_len(rng: &mut Rng) -> Option<LayoutLen> {
|
||||
Some(LayoutLen::px(20.0 + rng.below(180) as f32))
|
||||
}
|
||||
|
||||
fn resize_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Lens {
|
||||
let lens = [a_len(rng), a_len(rng)];
|
||||
fn resize_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> SizeRules {
|
||||
let lens = SizeRules {
|
||||
x: a_len(rng).into(),
|
||||
y: a_len(rng).into(),
|
||||
};
|
||||
warm.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rules(tree.sized[idx], lens[0], lens[1]);
|
||||
.set_size_rules(tree.sized[idx], lens.x, lens.y);
|
||||
lens
|
||||
}
|
||||
|
||||
fn realign_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Aligns {
|
||||
fn realign_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Align {
|
||||
let side = |rng: &mut Rng| match rng.below(4) {
|
||||
0 => None,
|
||||
1 => Some(AxisAlign::NEG),
|
||||
2 => Some(AxisAlign::CENTER),
|
||||
_ => Some(AxisAlign::POS),
|
||||
};
|
||||
let align = [side(rng), side(rng)];
|
||||
let align = Align {
|
||||
x: side(rng),
|
||||
y: side(rng),
|
||||
};
|
||||
let id = tree.aligned[idx];
|
||||
for (axis, align) in [Axis::X, Axis::Y].into_iter().zip(align) {
|
||||
warm.rsc
|
||||
.widgets_mut()
|
||||
.set_alignment(id, axis, align.unwrap_or_default());
|
||||
let taken = RegionAlign::from(align);
|
||||
for axis in Axis::BOTH {
|
||||
warm.rsc.widgets_mut().set_alignment(id, axis, taken[axis]);
|
||||
}
|
||||
align
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ fn what_box_the_text_is_drawn_in() {
|
||||
|
||||
for _ in 0..2 {
|
||||
for &id in &ids {
|
||||
h.rsc.widgets_mut().get_dyn_mut(id);
|
||||
h.rsc.widgets_mut().mark_for_redraw(id);
|
||||
}
|
||||
let _ = diag::take();
|
||||
h.frame();
|
||||
|
||||
Reference in new issue
Block a user