//! A seeded random widget tree, for tests and for looking at. //! //! One seed is one tree, on any machine and after any upgrade, so a test can //! grow the same tree twice and a failing seed is reproduced by its number. //! `examples/random.rs` draws one; `tests/generated.rs` checks that laying one //! out again lands where growing it from scratch would. use crate::prelude::*; use std::collections::HashMap; /// The declared lengths of one `SetSize`, by axis. pub type Lens = [Option; 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 `SetSize` wrappers were made. pub sizes: HashMap, /// Which children a span has, by the order the spans were made. pub spans: HashMap, } #[derive(Default, Clone)] pub struct SpanEdit { /// Children to leave out, by index among the ones grown. pub detach: Vec, /// How many of the span's spares are in it, appended in order. pub attach: usize, } /// xorshift64, written out rather than taken from a crate so that a seed /// keeps meaning the same tree. pub struct Rng(u64); impl Rng { pub fn new(seed: u64) -> Self { Self(seed | 1) } pub fn bits(&mut self) -> u64 { self.0 ^= self.0 << 13; self.0 ^= self.0 >> 7; self.0 ^= self.0 << 17; self.0 } pub fn below(&mut self, n: usize) -> usize { (self.bits() % n as u64) as usize } pub fn chance(&mut self) -> bool { self.bits() & 1 == 0 } } const COLORS: [UiColor; 6] = [ UiColor::RED, UiColor::GREEN, UiColor::BLUE, UiColor::YELLOW, UiColor::CYAN, UiColor::MAGENTA, ]; /// Leaves grown beside every span, for a test to put into it. const SPARES: usize = 3; const WORDS: &str = "Wrapping shapes one source into as many lines as the box \ leaves room for, so a paragraph's height is an answer and not a setting."; /// What growing a tree gives back: every widget in creation order, so two /// trees from one seed line up index for index, and the declared sizes, which /// are what a test changes to watch the change propagate. #[derive(Default)] pub struct Tree { pub ids: Vec, pub sized: Vec>, pub spans: Vec, pub scrolls: Vec>, /// Children a `SpanEdit` took out, held so that dropping the last share /// of one does not free its id for the next widget to be given -- which /// would put the two trees' `ids` out of step. pub detached: Vec, } /// Branches on a child's measured length. Comparing boxes catches a widget /// that moved; this catches one that believed a measurement a cold start /// would not have given it, by turning that into a different tree. Its own /// configuration never changes, so which side draws is a property of the /// layout alone. pub struct Branch { pub probe: StrongWidget, pub wide: StrongWidget, pub narrow: StrongWidget, pub threshold: f32, } impl Widget for Branch { fn draw(&mut self, painter: &mut Painter) -> Size { let mut top = UiRegion::FULL; top.y.end = top.y.start.offset(40.0); let measured = painter.place(&self.probe, top).len(Axis::X); let px = measured.apply_rest().to_px(painter.px_len(Axis::X)); let mut rest = UiRegion::FULL; rest.y.start = rest.y.start.offset(40.0); match px > self.threshold { true => painter.place(&self.wide, rest), false => painter.place(&self.narrow, rest), }; Size::REST } } pub struct Spanned { pub id: WeakWidget, /// Leaves grown with the span whether or not they end up in it, so both /// trees make the same widgets in the same order either way. Attaching /// one moves it out of here: a widget belongs to one parent, and one that /// belongs to nobody still has to be held or it reads as a leak. pub spares: Vec, /// How many children it was grown with, before any edit. pub grown: usize, } /// Grows the tree `seed` describes, `edits` replacing the declared sizes it /// would otherwise have given those wrappers. pub fn grow( rsc: &mut Rsc, seed: u64, depth: usize, edits: &Edits, ) -> (StrongWidget, Tree) { let mut grow = Grow { rsc, rng: Rng::new(seed), tree: Tree::default(), edits, }; let root = grow.node(depth); (root, grow.tree) } struct Grow<'a, Rsc> { rsc: &'a mut Rsc, rng: Rng, tree: Tree, edits: &'a Edits, } impl Grow<'_, Rsc> { fn leaf(&mut self) -> StrongWidget { let id: StrongWidget = match self.rng.below(4) { // Wrapped and unwrapped, because only one of them reads the width // it is given and so only one has to be drawn again for a new one. 0 => wtext(WORDS).size(16).wrap(true).add_strong(self.rsc), 1 => wtext("one line, overflowing whatever it is given") .size(16) .wrap(false) .add_strong(self.rsc), _ => { let color = COLORS[self.rng.below(COLORS.len())]; let alpha = (self.rng.below(5) * 63) as u8; rect(color.alpha(alpha)).add_strong(self.rsc) } }; self.tree.ids.push(id.id()); id } fn len(&mut self) -> Option { match self.rng.below(4) { 0 => Some(Len::px(20.0 + self.rng.below(180) as f32)), 1 => Some(Len::REST), _ => None, } } fn align(&mut self) -> Align { let mut axis = || match self.rng.below(4) { 0 => None, 1 => Some(AxisAlign::Neg), 2 => Some(AxisAlign::Center), _ => Some(AxisAlign::Pos), }; let (mut x, y) = (axis(), axis()); // Aligning on neither axis is just another transparent wrapper and // would leave this branch unexercised. if x.is_none() && y.is_none() { x = Some(AxisAlign::Center); } Align { x, y } } /// A declared size over half the tree, kept where a test can change it. fn sized(&mut self, inner: StrongWidget) -> StrongWidget { if !self.rng.chance() { return inner; } let idx = self.tree.sized.len(); let lens = [self.len(), self.len()]; let lens = self.edits.sizes.get(&idx).copied().unwrap_or(lens); let id = SetSize { inner, x: lens[0], y: lens[1], } .add(self.rsc); self.tree.sized.push(id); self.tree.ids.push(id.id()); id.add_strong(self.rsc) } fn node(&mut self, depth: usize) -> StrongWidget { if depth == 0 { return self.leaf(); } let positioned = self.rng.below(6); if positioned == 0 { // Scrolling reads the pixel length of its box, which nothing // else here does, and gives its child a box longer than its own. let inner = self.node(depth - 1); let inner = self.sized(inner); let axis = if self.rng.chance() { Axis::X } else { Axis::Y }; let id = Scroll::new(inner, axis).add(self.rsc); self.tree.scrolls.push(id); self.tree.ids.push(id.id()); return id.add_strong(self.rsc); } if positioned == 2 { // Both sides are grown either way, so a tree that draws one has // the same ids as a tree that draws the other. let probe = self.node(depth - 1); let wide = self.node(depth - 1); let narrow = self.node(depth - 1); let threshold = self.rng.below(500) as f32; let id = Branch { probe, wide, narrow, threshold, } .add(self.rsc); self.tree.ids.push(id.id()); return id.add_strong(self.rsc); } if positioned == 1 { let inner = self.node(depth - 1); let inner = self.sized(inner); let id = Aligned { inner, align: self.align(), } .add_strong(self.rsc); self.tree.ids.push(id.id()); return id; } if self.rng.below(4) == 0 { let inner = self.node(depth - 1); let inner = self.sized(inner); // Each side its own, since a padding that is the same all round // hides anything that treats one edge differently from another. let mut side = || self.rng.below(24) as f32; let padding = Padding { left: side(), right: side(), top: side(), bottom: side(), }; let id = Pad { padding, inner }.add_strong(self.rsc); self.tree.ids.push(id.id()); return id; } let grown = 2 + self.rng.below(3); let mut children = Vec::with_capacity(grown); for _ in 0..grown { let child = self.node(depth - 1); children.push(self.sized(child)); } if self.rng.chance() { let id = Stack { children, size: StackSize::Child(0), } .add_strong(self.rsc); self.tree.ids.push(id.id()); return id; } // Grown either way, so the widget after them has the same id in a // tree that leaves them out as in one that puts them in. let mut spares: Vec = (0..SPARES).map(|_| self.leaf()).collect(); let idx = self.tree.spans.len(); let edit = self.edits.spans.get(&idx).cloned().unwrap_or_default(); // Highest first, so an index means the same child however many of its // neighbours are going too. let mut detach = edit.detach.clone(); detach.sort_unstable(); for j in detach.into_iter().rev() { if j < children.len() { self.tree.detached.push(children.remove(j)); } } let attach = edit.attach.min(spares.len()); children.extend(spares.drain(..attach)); let dir = [Dir::RIGHT, Dir::DOWN, Dir::LEFT, Dir::UP][self.rng.below(4)]; let id = Span { children, dir, gap: self.rng.below(3) as f32 * 4.0, } .add(self.rsc); self.tree.ids.push(id.id()); self.tree.spans.push(Spanned { id, spares, grown }); id.add_strong(self.rsc) } }