Make alignment a widget property

This commit is contained in:
iris-ai committed 2026-09-15 23:12:16 -04:00
1 parent 8220a78d4a
commit d3b0ebf90c
21 files changed
+823 -300

No files matched your search

+68 -16
View File
@@ -11,6 +11,10 @@ use std::collections::HashMap;
/// The declared lengths of one widget carrying a size rule, by axis.
pub type Lens = [Option<Len>; 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)]
@@ -19,6 +23,12 @@ pub struct Edits {
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>,
}
#[derive(Default, Clone)]
@@ -76,6 +86,8 @@ const WORDS: &str = "Wrapping shapes one source into as many lines as the box \
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>>,
/// Children a `SpanEdit` took out, held so that dropping the last share
@@ -180,26 +192,32 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
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),
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.
// Aligning on neither axis leaves the branch unexercised.
if x.is_none() && y.is_none() {
x = Some(AxisAlign::Center);
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() {
// A rule is a property now, so a node already carrying one would take
// a second entry in `sized` -- and two edits naming one widget settle
// in the order they are applied, which is grow order cold and edit
// order warm. One entry per widget instead. Both draws are taken
// whatever is decided, and the decision is grow order alone, so the
// two trees consume the same random stream.
let take = self.rng.chance();
let lens = [self.len(), self.len()];
if !take || self.tree.sized.contains(&inner.id()) {
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 = inner.id();
self.rsc
@@ -210,6 +228,41 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
inner
}
/// An alignment over some of the tree, kept where a test can change it.
/// One entry per widget for the reason `sized` gives.
fn aligned(&mut self, inner: StrongWidget) -> StrongWidget {
let align = self.align();
let align = [align.x, align.y];
if self.tree.aligned.contains(&inner.id()) {
return inner;
}
let idx = self.tree.aligned.len();
let align = self.edits.aligns.get(&idx).copied().unwrap_or(align);
let id = inner.id();
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);
inner
}
/// 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: StrongWidget) -> StrongWidget {
let take = self.rng.below(4) == 0;
if self.tree.nodes.contains(&inner.id()) {
return inner;
}
let idx = self.tree.nodes.len();
let take = self.edits.nodes.get(&idx).copied().unwrap_or(take);
let id = inner.id();
self.rsc.ui_mut().widgets.set_region_node(id, take);
self.tree.nodes.push(id);
inner
}
fn node(&mut self, depth: usize) -> StrongWidget {
if depth == 0 {
return self.leaf();
@@ -220,6 +273,7 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
// 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 inner = self.noded(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);
@@ -246,17 +300,13 @@ impl<Rsc: UiRsc + 'static> Grow<'_, 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;
let inner = self.noded(inner);
return self.aligned(inner);
}
if self.rng.below(4) == 0 {
let inner = self.node(depth - 1);
let inner = self.sized(inner);
let inner = self.noded(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;
@@ -274,7 +324,9 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
let mut children = Vec::with_capacity(grown);
for _ in 0..grown {
let child = self.node(depth - 1);
children.push(self.sized(child));
let child = self.sized(child);
let child = self.noded(child);
children.push(child);
}
if self.rng.chance() {
let id = Stack {