`PlaceDescAxis::axis(axis)` shared its name with `PlaceDesc`'s extraction, which is now `Index<Axis>` and reads `place[axis]`. The two go opposite directions, so they get different words: `on_axis` pairs with the `from_axis` it is the shorthand for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
932 lines
34 KiB
Rust
932 lines
34 KiB
Rust
//! 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 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>,
|
|
/// 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>,
|
|
/// 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.
|
|
pub nodes: HashMap<usize, bool>,
|
|
/// Whether a [`Branch`] takes the side it would take at any measurement,
|
|
/// rather than the side the one it made says. The oracle wants the
|
|
/// measured side -- that is the whole point of a branch, and how a widget
|
|
/// believing a measurement a cold start would not have given it becomes a
|
|
/// different tree. A rig measuring cost wants this instead: a fixture
|
|
/// whose shape moves with the thing being measured cannot be compared
|
|
/// with itself across a change to it, and seed 1 at depth 8 went from 88
|
|
/// drawn widgets and 2,298 primitive writes a frame to 115 and 8,209
|
|
/// across fixed point, which is three and a half times the work behind a
|
|
/// number read as three and a half times the cost.
|
|
pub fixed_branches: bool,
|
|
}
|
|
|
|
#[derive(Default, Clone)]
|
|
pub struct SpanEdit {
|
|
/// Children to leave out, by index among the ones grown.
|
|
pub detach: Vec<usize>,
|
|
/// 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<WidgetId>,
|
|
pub sized: Vec<WidgetId>,
|
|
pub aligned: Vec<WidgetId>,
|
|
pub nodes: Vec<WidgetId>,
|
|
pub spans: Vec<Spanned>,
|
|
pub scrolls: Vec<WeakWidget<Scroll>>,
|
|
}
|
|
|
|
/// 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 cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
|
let top = UiSpan::new(Len::ZERO, cut).shifted_desc();
|
|
let measured = painter
|
|
.widget_at(&self.probe, top.on_axis(Axis::Y))
|
|
.len(Axis::X);
|
|
let len = measured.apply_leftover();
|
|
let px = painter.to_px(len, Axis::X);
|
|
// The range it actually branched on, said the way a container says
|
|
// one: pinning the window instead would redraw this widget on every
|
|
// resize, which is a fixture that never exercises reuse.
|
|
let threshold = Px::from_f32(self.threshold);
|
|
let holds = match px > threshold {
|
|
true => Holds::from(threshold + Px::STEP..=Px::MAX),
|
|
false => Holds::from(Px::MIN..=threshold),
|
|
};
|
|
painter.window_holds(Axis::X, holds.through(len));
|
|
|
|
let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
|
|
let place = below.on_axis(Axis::Y);
|
|
match px > threshold {
|
|
true => painter.widget_at(&self.wide, place),
|
|
false => painter.widget_at(&self.narrow, place),
|
|
};
|
|
Size::LEFTOVER
|
|
}
|
|
|
|
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
|
|
Some(LayoutLen::LEFTOVER)
|
|
}
|
|
}
|
|
|
|
pub struct Spanned {
|
|
pub id: WeakWidget<Span>,
|
|
/// Everything made for this span that it does not hold -- spares never
|
|
/// attached and children detached alike. A widget belongs to one parent,
|
|
/// and one that belongs to nobody still has to be held here: dropping
|
|
/// the last share of it frees its id for the next widget to be given,
|
|
/// which puts two trees out of step.
|
|
pub spares: Vec<StrongWidget>,
|
|
/// How many children it was grown with, before any edit.
|
|
pub grown: usize,
|
|
}
|
|
|
|
/// A tree described rather than built: [`plan`] turns a seed into one of
|
|
/// these and [`build`] turns it into widgets, where growing did both at once.
|
|
///
|
|
/// The split is what makes a counterexample readable. A failing seed used to
|
|
/// be the entire record of one, because a grower that makes widgets as it
|
|
/// draws leaves nothing to take apart -- a shrinker could only grow its own
|
|
/// trees and hope to meet the same shape, which in practice it does not. A
|
|
/// plan is reduced by [`Plan::smaller`] and built again, so any seed that
|
|
/// fails can be cut down until what is left is small enough to read.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct Plan {
|
|
pub kind: Kind,
|
|
/// The declared size this widget carries. Whoever grows a widget offers
|
|
/// 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>,
|
|
/// 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.
|
|
pub region_node: Option<bool>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum Kind {
|
|
/// Wrapped and unwrapped text, because only one of them reads the width
|
|
/// it is given and so only one has to be drawn again for a new one.
|
|
Wrapped,
|
|
OneLine,
|
|
Rect {
|
|
color: usize,
|
|
alpha: u8,
|
|
},
|
|
/// Scrolling reads the pixel length of its box, which nothing else here
|
|
/// does, and gives its child a box longer than its own.
|
|
Scroll {
|
|
axis: Axis,
|
|
inner: Box<Plan>,
|
|
},
|
|
/// All three sides are grown either way, so a tree that draws one has the
|
|
/// same ids as a tree that draws another.
|
|
Branch {
|
|
probe: Box<Plan>,
|
|
wide: Box<Plan>,
|
|
narrow: Box<Plan>,
|
|
threshold: f32,
|
|
},
|
|
/// Each side its own, since a padding that is the same all round hides
|
|
/// anything that treats one edge differently from another.
|
|
Pad {
|
|
padding: [i32; 4],
|
|
inner: Box<Plan>,
|
|
},
|
|
Stack {
|
|
children: Vec<Plan>,
|
|
},
|
|
Span {
|
|
dir: usize,
|
|
gap: i32,
|
|
/// Grown for this span, in the order they are made.
|
|
children: Vec<Plan>,
|
|
/// Grown beside it whether or not they end up in it, so the widget
|
|
/// after them has the same id in a tree that leaves them out as in
|
|
/// one that puts them in.
|
|
spares: Vec<Plan>,
|
|
/// Which of `children` then `spares` are actually in the span, and
|
|
/// in what order -- kept apart from the two lists above so that a
|
|
/// tree which detaches, attaches or reorders its children still
|
|
/// makes the same widgets in the same order, and two builds line up
|
|
/// index for index. Anything not named here is built and held
|
|
/// rather than dropped, since freeing an id hands it to the next
|
|
/// widget and puts two trees out of step.
|
|
order: Vec<usize>,
|
|
},
|
|
}
|
|
|
|
impl Plan {
|
|
/// A widget carrying nothing anybody has offered it yet.
|
|
fn bare(kind: Kind) -> Self {
|
|
Self {
|
|
kind,
|
|
size: None,
|
|
align: None,
|
|
region_node: None,
|
|
}
|
|
}
|
|
|
|
/// How many widgets building it makes, spares and detached children
|
|
/// included, since those are made either way.
|
|
pub fn size(&self) -> usize {
|
|
1 + match &self.kind {
|
|
Kind::Scroll { inner, .. } | Kind::Pad { inner, .. } => inner.size(),
|
|
Kind::Branch {
|
|
probe,
|
|
wide,
|
|
narrow,
|
|
..
|
|
} => probe.size() + wide.size() + narrow.size(),
|
|
Kind::Stack { children } => children.iter().map(Plan::size).sum(),
|
|
Kind::Span {
|
|
children, spares, ..
|
|
} => children.iter().chain(spares).map(Plan::size).sum(),
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
/// The trees to try instead of this one when reducing a counterexample,
|
|
/// biggest cut first: a shrinker takes the first that still fails, so
|
|
/// offering "this subtree alone" before "this subtree with one child
|
|
/// fewer" is what gets from six hundred widgets to six rather than to
|
|
/// five hundred and ninety.
|
|
///
|
|
/// Every one of these is a tree the generator could have grown, so a
|
|
/// reduced plan is a counterexample in its own right rather than a
|
|
/// special case only the shrinker can make.
|
|
pub fn smaller(&self) -> Vec<Plan> {
|
|
let mut out = Vec::new();
|
|
// Standing in for the whole of it, which is the largest cut there is.
|
|
for kid in self.kids() {
|
|
out.push(kid.clone());
|
|
}
|
|
// Then what it carries, which costs nothing to put back if it was
|
|
// not the thing that mattered.
|
|
for dropped in [
|
|
self.region_node.map(|_| Plan {
|
|
region_node: None,
|
|
..self.clone()
|
|
}),
|
|
self.align.map(|_| Plan {
|
|
align: None,
|
|
..self.clone()
|
|
}),
|
|
self.size.map(|_| Plan {
|
|
size: None,
|
|
..self.clone()
|
|
}),
|
|
]
|
|
.into_iter()
|
|
.flatten()
|
|
{
|
|
out.push(dropped);
|
|
}
|
|
out.extend(self.kind.smaller().into_iter().map(|kind| Plan {
|
|
kind,
|
|
..self.clone()
|
|
}));
|
|
out
|
|
}
|
|
|
|
/// Visits every widget in the order [`build`] makes them, so a count
|
|
/// kept by the visitor indexes the same widget as the matching [`Tree`]
|
|
/// vector does.
|
|
pub fn walk_mut(&mut self, at: &mut impl FnMut(&mut Plan)) {
|
|
match &mut self.kind {
|
|
Kind::Scroll { inner, .. } | Kind::Pad { inner, .. } => inner.walk_mut(at),
|
|
Kind::Branch {
|
|
probe,
|
|
wide,
|
|
narrow,
|
|
..
|
|
} => {
|
|
probe.walk_mut(at);
|
|
wide.walk_mut(at);
|
|
narrow.walk_mut(at);
|
|
}
|
|
Kind::Stack { children } => {
|
|
for child in children {
|
|
child.walk_mut(at);
|
|
}
|
|
}
|
|
Kind::Span {
|
|
children, spares, ..
|
|
} => {
|
|
for child in children.iter_mut().chain(spares) {
|
|
child.walk_mut(at);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
at(self);
|
|
}
|
|
|
|
/// The same tree with `edits` applied, by the indices the generator would
|
|
/// have used for them.
|
|
///
|
|
/// [`plan`] resolves edits while drawing, which needs a seed. A scenario
|
|
/// needs them applied to a tree that already exists -- one it has built,
|
|
/// and one a shrinker may already have cut down, where no seed grows it
|
|
/// any more. Both routes take the same [`Edits`], so a case written
|
|
/// against one reads the same against the other.
|
|
pub fn edited(&self, edits: &Edits) -> Plan {
|
|
let mut out = self.clone();
|
|
let (mut sized, mut aligned, mut nodes, mut spans) = (0, 0, 0, 0);
|
|
out.walk_mut(&mut |plan| {
|
|
if let Kind::Span {
|
|
children,
|
|
spares,
|
|
order,
|
|
..
|
|
} = &mut plan.kind
|
|
{
|
|
if let Some(edit) = edits.spans.get(&spans) {
|
|
*order = span_edited(order, children.len(), spares.len(), edit);
|
|
}
|
|
spans += 1;
|
|
}
|
|
if let Kind::Branch { threshold, .. } = &mut plan.kind
|
|
&& edits.fixed_branches
|
|
{
|
|
*threshold = f32::MIN;
|
|
}
|
|
if plan.size.is_some() {
|
|
if let Some(lens) = edits.sizes.get(&sized) {
|
|
plan.size = Some(*lens);
|
|
}
|
|
sized += 1;
|
|
}
|
|
if plan.align.is_some() {
|
|
if let Some(align) = edits.aligns.get(&aligned) {
|
|
plan.align = Some(*align);
|
|
}
|
|
aligned += 1;
|
|
}
|
|
if plan.region_node.is_some() {
|
|
if let Some(take) = edits.nodes.get(&nodes) {
|
|
plan.region_node = Some(*take);
|
|
}
|
|
nodes += 1;
|
|
}
|
|
});
|
|
out
|
|
}
|
|
|
|
fn kids(&self) -> Vec<&Plan> {
|
|
match &self.kind {
|
|
Kind::Scroll { inner, .. } | Kind::Pad { inner, .. } => vec![inner],
|
|
Kind::Branch {
|
|
probe,
|
|
wide,
|
|
narrow,
|
|
..
|
|
} => vec![probe, wide, narrow],
|
|
Kind::Stack { children } => children.iter().collect(),
|
|
Kind::Span { children, .. } => children.iter().collect(),
|
|
_ => Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Kind {
|
|
/// Simplifications of the shape alone, leaving what the widget carries to
|
|
/// [`Plan::smaller`]. Replacing a node with one of its children is there
|
|
/// rather than here, since it answers with a whole `Plan`.
|
|
fn smaller(&self) -> Vec<Kind> {
|
|
let mut out = Vec::new();
|
|
/// One child reduced at a time, rebuilt into the same shape. Every
|
|
/// answer has the same number of children as it was given, so it is
|
|
/// for the shapes whose child count is part of what they are.
|
|
fn reduced(kids: &[Plan], rebuild: &dyn Fn(Vec<Plan>) -> Kind) -> Vec<Kind> {
|
|
let mut out = Vec::new();
|
|
for (i, kid) in kids.iter().enumerate() {
|
|
for small in kid.smaller() {
|
|
let mut next = kids.to_vec();
|
|
next[i] = small;
|
|
out.push(rebuild(next));
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
/// One child dropped, then [`reduced`]. For the shapes that hold any
|
|
/// number of children, where dropping one is the cut that matters.
|
|
fn each(kids: &[Plan], rebuild: &dyn Fn(Vec<Plan>) -> Kind) -> Vec<Kind> {
|
|
let mut out = Vec::new();
|
|
for i in 0..kids.len() {
|
|
if kids.len() > 1 {
|
|
let mut less = kids.to_vec();
|
|
less.remove(i);
|
|
out.push(rebuild(less));
|
|
}
|
|
}
|
|
out.extend(reduced(kids, rebuild));
|
|
out
|
|
}
|
|
match self {
|
|
// The one leaf that reads the width it is given, then the one
|
|
// that does not, then the one that measures nothing at all.
|
|
Kind::Wrapped => out.push(Kind::OneLine),
|
|
Kind::OneLine => out.push(Kind::Rect {
|
|
color: 0,
|
|
alpha: 255,
|
|
}),
|
|
Kind::Rect { .. } => {}
|
|
Kind::Scroll { axis, inner } => {
|
|
let axis = *axis;
|
|
out.extend(each(std::slice::from_ref(inner), &|mut k| Kind::Scroll {
|
|
axis,
|
|
inner: Box::new(k.remove(0)),
|
|
}));
|
|
}
|
|
Kind::Branch {
|
|
probe,
|
|
wide,
|
|
narrow,
|
|
threshold,
|
|
} => {
|
|
let threshold = *threshold;
|
|
// All three sides stay: a branch is the widget that draws
|
|
// one of two on a measurement, and one with a side missing
|
|
// is a different widget rather than a smaller one. Dropping
|
|
// the branch for a side is offered by `Plan::smaller`.
|
|
let sides = [(**probe).clone(), (**wide).clone(), (**narrow).clone()];
|
|
out.extend(reduced(&sides, &|k| Kind::Branch {
|
|
probe: Box::new(k[0].clone()),
|
|
wide: Box::new(k[1].clone()),
|
|
narrow: Box::new(k[2].clone()),
|
|
threshold,
|
|
}));
|
|
}
|
|
Kind::Pad { padding, inner } => {
|
|
let padding = *padding;
|
|
if padding != [0; 4] {
|
|
out.push(Kind::Pad {
|
|
padding: [0; 4],
|
|
inner: inner.clone(),
|
|
});
|
|
}
|
|
out.extend(each(std::slice::from_ref(inner), &|mut k| Kind::Pad {
|
|
padding,
|
|
inner: Box::new(k.remove(0)),
|
|
}));
|
|
}
|
|
Kind::Stack { children } => {
|
|
out.extend(each(children, &|children| Kind::Stack { children }))
|
|
}
|
|
Kind::Span {
|
|
dir,
|
|
gap,
|
|
children,
|
|
spares,
|
|
order,
|
|
} => {
|
|
let (dir, gap, n) = (*dir, *gap, children.len());
|
|
let span = |children: Vec<Plan>, spares: Vec<Plan>, order: Vec<usize>| Kind::Span {
|
|
dir,
|
|
gap,
|
|
children,
|
|
spares,
|
|
order,
|
|
};
|
|
let identity: Vec<usize> = (0..n).collect();
|
|
// An order the generator did not choose is part of the tree,
|
|
// so take that off before taking the tree apart.
|
|
if *order != identity {
|
|
out.push(span(children.clone(), spares.clone(), identity));
|
|
}
|
|
// Spares exist to be attached; with none attached they are
|
|
// widgets the span never holds.
|
|
if !spares.is_empty() && order.iter().all(|&i| i < n) {
|
|
out.push(span(children.clone(), Vec::new(), order.clone()));
|
|
}
|
|
if gap != 0 {
|
|
out.push(Kind::Span {
|
|
dir,
|
|
gap: 0,
|
|
children: children.clone(),
|
|
spares: spares.clone(),
|
|
order: order.clone(),
|
|
});
|
|
}
|
|
for k in 0..n {
|
|
if n > 1 {
|
|
let mut less = children.clone();
|
|
less.remove(k);
|
|
// Everything after it shifts down, spares included,
|
|
// since they are indexed past the children.
|
|
let order = order
|
|
.iter()
|
|
.filter(|&&i| i != k)
|
|
.map(|&i| if i > k { i - 1 } else { i })
|
|
.collect();
|
|
out.push(span(less, spares.clone(), order));
|
|
}
|
|
}
|
|
for (i, kid) in children.iter().enumerate() {
|
|
for small in kid.smaller() {
|
|
let mut next = children.clone();
|
|
next[i] = small;
|
|
out.push(span(next, spares.clone(), order.clone()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
out
|
|
}
|
|
}
|
|
|
|
/// A [`SpanEdit`] applied to the order a span already holds its children in.
|
|
///
|
|
/// `detach` names positions in that order and `attach` takes from the front
|
|
/// of what the span is not holding, both of which is what a test changing a
|
|
/// live span does -- so an edit means the same thing said to a tree and said
|
|
/// to the plan it was built from. On a span nobody has edited the order is
|
|
/// the children in the order they were grown, and this is then "leave these
|
|
/// out and put that many spares on the end".
|
|
fn span_edited(order: &[usize], children: usize, spares: usize, edit: &SpanEdit) -> Vec<usize> {
|
|
let mut detach = edit.detach.clone();
|
|
detach.sort_unstable();
|
|
detach.dedup();
|
|
let mut next: Vec<usize> = order
|
|
.iter()
|
|
.enumerate()
|
|
.filter(|(at, _)| !detach.contains(at))
|
|
.map(|(_, &which)| which)
|
|
.collect();
|
|
// What the span is not holding, in the order it hands them back: what it
|
|
// was already not holding first, in the order the widgets were made, and
|
|
// what this edit takes out after that, highest position first. A child
|
|
// just detached goes to the back rather than straight back in, which is
|
|
// what makes detaching one and attaching one a trade.
|
|
let mut free: Vec<usize> = (0..children + spares)
|
|
.filter(|i| !order.contains(i))
|
|
.collect();
|
|
free.extend(detach.iter().rev().filter_map(|&at| order.get(at).copied()));
|
|
next.extend(free.into_iter().take(edit.attach));
|
|
next
|
|
}
|
|
|
|
/// Plans the tree `seed` describes, `edits` replacing what it would otherwise
|
|
/// have given the widgets that carry them.
|
|
///
|
|
/// The edits are resolved here rather than at build time, so that a plan is
|
|
/// the whole of what a tree is and building one has nothing left to decide.
|
|
pub fn plan(seed: u64, depth: usize, edits: &Edits) -> Plan {
|
|
let mut sow = Sow {
|
|
rng: Rng::new(seed),
|
|
edits,
|
|
sized: 0,
|
|
aligned: 0,
|
|
nodes: 0,
|
|
spans: 0,
|
|
};
|
|
sow.node(depth)
|
|
}
|
|
|
|
/// Grows the tree `seed` describes, `edits` replacing the declared sizes it
|
|
/// would otherwise have given those wrappers.
|
|
pub fn grow<Rsc: UiRsc + 'static>(
|
|
rsc: &mut Rsc,
|
|
seed: u64,
|
|
depth: usize,
|
|
edits: &Edits,
|
|
) -> (StrongWidget, Tree) {
|
|
build(rsc, &plan(seed, depth, edits))
|
|
}
|
|
|
|
/// Draws a plan out of the random stream. Every draw happens in the order it
|
|
/// always has and before the decision it feeds, including the decisions that
|
|
/// are then dropped, because a seed has to keep meaning the same tree.
|
|
struct Sow<'a> {
|
|
rng: Rng,
|
|
edits: &'a Edits,
|
|
sized: usize,
|
|
aligned: usize,
|
|
nodes: usize,
|
|
spans: usize,
|
|
}
|
|
|
|
impl Sow<'_> {
|
|
fn leaf(&mut self) -> Plan {
|
|
Plan::bare(match self.rng.below(4) {
|
|
0 => Kind::Wrapped,
|
|
1 => Kind::OneLine,
|
|
_ => {
|
|
let color = self.rng.below(COLORS.len());
|
|
let alpha = (self.rng.below(5) * 63) as u8;
|
|
Kind::Rect { color, alpha }
|
|
}
|
|
})
|
|
}
|
|
|
|
fn len(&mut self) -> Option<LayoutLen> {
|
|
match self.rng.below(4) {
|
|
0 => Some(LayoutLen::px(20.0 + self.rng.below(180) as f32)),
|
|
1 => Some(LayoutLen::LEFTOVER),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn align(&mut self) -> Aligns {
|
|
let axis = |s: &mut Self| match s.rng.below(4) {
|
|
0 => None,
|
|
1 => Some(AxisAlign::NEG),
|
|
2 => Some(AxisAlign::CENTER),
|
|
_ => Some(AxisAlign::POS),
|
|
};
|
|
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],
|
|
}
|
|
}
|
|
|
|
/// 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()];
|
|
if !take || inner.size.is_some() {
|
|
return;
|
|
}
|
|
let idx = self.sized;
|
|
self.sized += 1;
|
|
inner.size = Some(self.edits.sizes.get(&idx).copied().unwrap_or(lens));
|
|
}
|
|
|
|
/// An alignment over some of the tree, kept where a test can change it.
|
|
fn aligned(&mut self, inner: &mut Plan) {
|
|
let align = self.align();
|
|
if inner.align.is_some() {
|
|
return;
|
|
}
|
|
let idx = self.aligned;
|
|
self.aligned += 1;
|
|
inner.align = Some(self.edits.aligns.get(&idx).copied().unwrap_or(align));
|
|
}
|
|
|
|
/// A movable region of its own over some of the tree. What it changes is
|
|
/// how a move is written and how long a primitive's chain is, neither of
|
|
/// which any other branch here varies.
|
|
fn noded(&mut self, inner: &mut Plan) {
|
|
let take = self.rng.below(4) == 0;
|
|
if inner.region_node.is_some() {
|
|
return;
|
|
}
|
|
let idx = self.nodes;
|
|
self.nodes += 1;
|
|
inner.region_node = Some(self.edits.nodes.get(&idx).copied().unwrap_or(take));
|
|
}
|
|
|
|
fn offered(&mut self, inner: &mut Plan) {
|
|
self.sized(inner);
|
|
self.noded(inner);
|
|
}
|
|
|
|
fn node(&mut self, depth: usize) -> Plan {
|
|
if depth == 0 {
|
|
return self.leaf();
|
|
}
|
|
let positioned = self.rng.below(6);
|
|
if positioned == 0 {
|
|
let mut inner = self.node(depth - 1);
|
|
self.offered(&mut inner);
|
|
let axis = if self.rng.chance() { Axis::X } else { Axis::Y };
|
|
return Plan::bare(Kind::Scroll {
|
|
axis,
|
|
inner: Box::new(inner),
|
|
});
|
|
}
|
|
if positioned == 2 {
|
|
let probe = self.node(depth - 1);
|
|
let wide = self.node(depth - 1);
|
|
let narrow = self.node(depth - 1);
|
|
// Drawn either way, so the side a fixed branch takes is still a
|
|
// side the generator chose -- and it consumes the same randomness
|
|
// as a measured one, so the two grow the same ids.
|
|
let measured = self.rng.below(500) as f32;
|
|
let threshold = match self.edits.fixed_branches {
|
|
true => f32::MIN,
|
|
false => measured,
|
|
};
|
|
return Plan::bare(Kind::Branch {
|
|
probe: Box::new(probe),
|
|
wide: Box::new(wide),
|
|
narrow: Box::new(narrow),
|
|
threshold,
|
|
});
|
|
}
|
|
if positioned == 1 {
|
|
// Carries an alignment and makes no widget of its own, so the
|
|
// plan for it is the child it aligned.
|
|
let mut inner = self.node(depth - 1);
|
|
self.offered(&mut inner);
|
|
self.aligned(&mut inner);
|
|
return inner;
|
|
}
|
|
if self.rng.below(4) == 0 {
|
|
let mut inner = self.node(depth - 1);
|
|
self.offered(&mut inner);
|
|
let side = |s: &mut Self| s.rng.below(24) as i32;
|
|
let padding = [side(self), side(self), side(self), side(self)];
|
|
return Plan::bare(Kind::Pad {
|
|
padding,
|
|
inner: Box::new(inner),
|
|
});
|
|
}
|
|
let grown = 2 + self.rng.below(3);
|
|
let mut children = Vec::with_capacity(grown);
|
|
for _ in 0..grown {
|
|
let mut child = self.node(depth - 1);
|
|
self.offered(&mut child);
|
|
children.push(child);
|
|
}
|
|
if self.rng.chance() {
|
|
return Plan::bare(Kind::Stack { children });
|
|
}
|
|
let spares: Vec<Plan> = (0..SPARES).map(|_| self.leaf()).collect();
|
|
let idx = self.spans;
|
|
self.spans += 1;
|
|
let edit = self.edits.spans.get(&idx).cloned().unwrap_or_default();
|
|
let dir = self.rng.below(4);
|
|
// A row takes the height it is given rather than its tallest child,
|
|
// which is a rule beside it. Derived from an existing choice and
|
|
// consuming no randomness: a seed must keep growing the same tree
|
|
// when the generator gains another configuration.
|
|
let gap = self.rng.below(3) as i32 * 4;
|
|
let grown: Vec<usize> = (0..children.len()).collect();
|
|
let order = span_edited(&grown, children.len(), spares.len(), &edit);
|
|
Plan::bare(Kind::Span {
|
|
dir,
|
|
gap,
|
|
children,
|
|
spares,
|
|
order,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Builds a plan's widgets in the order it describes them, so two builds of
|
|
/// one plan line up index for index and their boxes can be compared.
|
|
pub fn build<Rsc: UiRsc + 'static>(rsc: &mut Rsc, plan: &Plan) -> (StrongWidget, Tree) {
|
|
let mut build = Build {
|
|
rsc,
|
|
tree: Tree::default(),
|
|
};
|
|
let root = build.node(plan);
|
|
(root, build.tree)
|
|
}
|
|
|
|
struct Build<'a, Rsc> {
|
|
rsc: &'a mut Rsc,
|
|
tree: Tree,
|
|
}
|
|
|
|
impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
|
fn node(&mut self, plan: &Plan) -> StrongWidget {
|
|
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.tree.sized.push(id);
|
|
}
|
|
if let Some(align) = plan.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());
|
|
}
|
|
self.tree.aligned.push(id);
|
|
}
|
|
if let Some(take) = plan.region_node {
|
|
self.rsc.ui_mut().widgets.set_region_node(id, take);
|
|
self.tree.nodes.push(id);
|
|
}
|
|
built
|
|
}
|
|
|
|
fn kind(&mut self, kind: &Kind) -> StrongWidget {
|
|
let id: StrongWidget = match kind {
|
|
Kind::Wrapped => wtext(WORDS).size(16).wrap(true).add_strong(self.rsc),
|
|
Kind::OneLine => wtext("one line, overflowing whatever it is given")
|
|
.size(16)
|
|
.wrap(false)
|
|
.add_strong(self.rsc),
|
|
Kind::Rect { color, alpha } => rect(COLORS[*color].alpha(*alpha)).add_strong(self.rsc),
|
|
Kind::Scroll { axis, inner } => {
|
|
let inner = self.node(inner);
|
|
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);
|
|
}
|
|
Kind::Branch {
|
|
probe,
|
|
wide,
|
|
narrow,
|
|
threshold,
|
|
} => {
|
|
let probe = self.node(probe);
|
|
let wide = self.node(wide);
|
|
let narrow = self.node(narrow);
|
|
let id = Branch {
|
|
probe,
|
|
wide,
|
|
narrow,
|
|
threshold: *threshold,
|
|
}
|
|
.add(self.rsc);
|
|
self.tree.ids.push(id.id());
|
|
return id.add_strong(self.rsc);
|
|
}
|
|
Kind::Pad { padding, inner } => {
|
|
let inner = self.node(inner);
|
|
let [left, right, top, bottom] = padding.map(Px::from_int);
|
|
let padding = Padding {
|
|
left,
|
|
right,
|
|
top,
|
|
bottom,
|
|
};
|
|
Pad { padding, inner }.add_strong(self.rsc)
|
|
}
|
|
Kind::Stack { children } => {
|
|
let children = children.iter().map(|c| self.node(c)).collect();
|
|
Stack {
|
|
children,
|
|
size: StackSize::Child(0),
|
|
}
|
|
.add_strong(self.rsc)
|
|
}
|
|
Kind::Span {
|
|
dir,
|
|
gap,
|
|
children,
|
|
spares,
|
|
order,
|
|
} => {
|
|
let grown = children.len();
|
|
// Every one of them is made, in this order, whether or not
|
|
// the span ends up holding it.
|
|
let made: Vec<StrongWidget> = children
|
|
.iter()
|
|
.chain(spares)
|
|
.map(|c| self.node(c))
|
|
.collect();
|
|
let mut left: Vec<Option<StrongWidget>> = made.into_iter().map(Some).collect();
|
|
let children: Vec<StrongWidget> = order
|
|
.iter()
|
|
.filter_map(|&i| left.get_mut(i).and_then(Option::take))
|
|
.collect();
|
|
// What the span does not hold is still held here: dropping
|
|
// the last share of a widget frees its id for the next one
|
|
// to be given, which puts two trees out of step.
|
|
let spares: Vec<StrongWidget> = left.into_iter().flatten().collect();
|
|
let dir = [Dir::RIGHT, Dir::DOWN, Dir::LEFT, Dir::UP][*dir % 4];
|
|
let id = Span {
|
|
children,
|
|
dir,
|
|
gap: Px::from_int(*gap),
|
|
}
|
|
.add(self.rsc);
|
|
if dir.axis == Axis::X {
|
|
self.rsc
|
|
.widgets_mut()
|
|
.set_size_rules(id, None, Some(LayoutLen::rel(1.0)));
|
|
}
|
|
self.tree.ids.push(id.id());
|
|
self.tree.spans.push(Spanned { id, spares, grown });
|
|
return id.add_strong(self.rsc);
|
|
}
|
|
};
|
|
self.tree.ids.push(id.id());
|
|
id
|
|
}
|
|
}
|