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

+113 -15
View File
@@ -13,7 +13,7 @@ use std::collections::HashMap;
use iris::harness::Harness;
use iris::prelude::*;
use iris::random::{Edits, Lens, Rng, SpanEdit, Tree, grow};
use iris::random::{Aligns, Edits, Lens, Rng, SpanEdit, Tree, grow};
/// How deep the generator branches. The generator widens two to four ways per
/// level, so depth is exponential in width and a deep narrow tree is not
@@ -179,12 +179,28 @@ fn describe(id: WidgetId, h: &Harness) -> String {
Some(len) => format!("{len}"),
None => "-".into(),
};
// A size rule is a property of whatever carries it, so it prints with
// that widget rather than as one of its own.
match (rules.x, rules.y) {
(SizeRule::Free, SizeRule::Free) => describe_widget(id, h),
(x, y) => format!("{}[x:{},y:{}]", describe_widget(id, h), rule(x), rule(y)),
let align = h.rsc.widgets().alignment(id);
let side = |a: AxisAlign| {
if a == AxisAlign::NEG {
"neg".into()
} else if a == AxisAlign::CENTER {
"mid".into()
} else if a == AxisAlign::POS {
"pos".into()
} else {
format!("{:.2}", a.rel())
}
};
// A rule and an alignment are properties of whatever carries them, so
// they print with that widget rather than as widgets of their own.
let mut out = describe_widget(id, h);
if (rules.x, rules.y) != (SizeRule::Free, SizeRule::Free) {
out += &format!("[x:{},y:{}]", rule(rules.x), rule(rules.y));
}
if align != RegionAlign::default() {
out += &format!("@{},{}", side(align.x), side(align.y));
}
out
}
fn describe_widget(id: WidgetId, h: &Harness) -> String {
@@ -210,15 +226,6 @@ fn describe_widget(id: WidgetId, h: &Harness) -> String {
p.left, p.right, p.top, p.bottom
);
}
if let Some(w) = any.downcast_ref::<Aligned>() {
let a = |v: Option<AxisAlign>| match v {
None => "-",
Some(AxisAlign::Neg) => "neg",
Some(AxisAlign::Center) => "mid",
Some(AxisAlign::Pos) => "pos",
};
return format!("Aligned{{x:{},y:{}}}", a(w.align.x), a(w.align.y));
}
if let Some(w) = any.downcast_ref::<Stack>() {
return format!("Stack{{n:{}}}", w.children.len());
}
@@ -290,6 +297,87 @@ fn changed_size(seed: u64) {
assert_same(seed, "a size change", (&warm, &grown), (&cold, &same));
}
/// Moves one widget to a different corner of the box it is given.
fn realign_one(h: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Aligns {
let mut side = || match rng.below(4) {
0 => None,
1 => Some(AxisAlign::NEG),
2 => Some(AxisAlign::CENTER),
_ => Some(AxisAlign::POS),
};
let aligns = [side(), side()];
for (axis, align) in [Axis::X, Axis::Y].into_iter().zip(aligns) {
h.rsc
.widgets_mut()
.set_alignment(tree.aligned[idx], axis, align.unwrap_or_default());
}
aligns
}
fn changed_alignment(seed: u64) {
let mut warm = Harness::new((900, 1200));
let grown = plant(&mut warm, seed, &Edits::default());
if grown.aligned.is_empty() {
return;
}
let mut rng = Rng::new(seed ^ 0xa11);
let aligns = (0..grown.aligned.len())
.step_by(3)
.map(|idx| (idx, realign_one(&mut warm, &grown, idx, &mut rng)))
.collect();
warm.frame();
let mut cold = Harness::new((900, 1200));
let same = plant(
&mut cold,
seed,
&Edits {
aligns,
..Default::default()
},
);
assert_same(seed, "an alignment change", (&warm, &grown), (&cold, &same));
}
/// Giving a widget a movable region of its own, or taking it away, is a
/// structural change: every primitive under it changes which chain resolves
/// it. A cold tree built that way is what says the rebuild was complete.
fn changed_region_node(seed: u64) {
let mut warm = Harness::new((900, 1200));
let grown = plant(&mut warm, seed, &Edits::default());
if grown.nodes.is_empty() {
return;
}
let nodes: HashMap<usize, bool> = (0..grown.nodes.len())
.step_by(2)
.map(|idx| {
let id = grown.nodes[idx];
let was = warm.rsc.widgets().is_region_node(id);
warm.rsc.widgets_mut().set_region_node(id, !was);
(idx, !was)
})
.collect();
warm.frame();
let mut cold = Harness::new((900, 1200));
let same = plant(
&mut cold,
seed,
&Edits {
nodes,
..Default::default()
},
);
assert_same(
seed,
"a region-node change",
(&warm, &grown),
(&cold, &same),
);
}
fn reshuffled(seed: u64, shuffle: Shuffle) {
let mut warm = Harness::new((900, 1200));
let mut grown = plant(&mut warm, seed, &Edits::default());
@@ -411,6 +499,16 @@ fn a_changed_size_lands_where_growing_it_that_way_would() {
SEEDS.into_iter().for_each(changed_size);
}
#[test]
fn a_changed_alignment_lands_where_growing_it_that_way_would() {
SEEDS.into_iter().for_each(changed_alignment);
}
#[test]
fn a_toggled_region_node_lands_where_growing_it_that_way_would() {
SEEDS.into_iter().for_each(changed_region_node);
}
#[test]
fn every_size_changing_at_once_lands_where_growing_it_that_way_would() {
SEEDS.into_iter().for_each(changed_every_size);