iris: make span compaction explicit

This commit is contained in:
iris committed 2026-09-12 21:18:09 -04:00
1 parent 9097744a7c
commit 9fe6aca1f1
6 files changed
+100 -20

No files matched your search

+1 -1
View File
@@ -331,7 +331,7 @@ where
None => Span::empty(Dir::DOWN).add(rsc),
};
let widget = (header, column.width(rest(1)))
let widget = (header, column)
.span(Dir::DOWN)
.gap(dp(4))
.pad(dp(10))
+9
View File
@@ -847,6 +847,15 @@ such as corner radius and text's position within horizontal overflow use
`Len`. Flexible padding participates in the same proportional allocation as a
span rather than silently discarding its `rest` component.
**A `Span` leaves its children in the offered orthogonal region unless
explicitly compacted** (2026-09-12). It still reports the widest orthogonal
child as its intrinsic size, so a row nested in a column keeps its content
height rather than claiming the column's remaining height. By default it does
not squeeze child regions to that intrinsic size, because doing so makes child
alignment operate inside the widest child rather than a known-width container.
`.compact()` opts into that shrink-to-widest-child placement for deliberately
dense groups.
**Text overflow is treatment plus position, not alignment** (2026-09-12).
`TextOverflow` selects `Visible`, `Wrap`, `Hidden`, or the single `Ellipsis`
treatment. Unwrapped hidden and ellipsized text retains one canonical shaped
+2
View File
@@ -100,10 +100,12 @@ where
rect(PaintId::PURPLE).sized((50, 50)).align(Align::TOP),
)
.span(Dir::RIGHT)
.compact()
.center(),
wtext("pretty cool right?").size(50),
)
.span(Dir::DOWN)
.compact()
.add(rsc);
let texts = Span::empty(Dir::DOWN).gap(10).add(rsc);
+10 -18
View File
@@ -38,24 +38,17 @@ where
.add(Srgba8::new(53, 57, 66, 255).to_linear());
let styled = "Bold, italic, underlined, and colored spans";
let styled = wtext(styled)
.size(20)
.spans(vec![
SpanStyle::new(0..4).bold(),
SpanStyle::new(6..12).italic(),
SpanStyle::new(14..24).underline(),
SpanStyle::new(30..37).color(PaintId::SKY),
])
.width(rest(1));
let styled = wtext(styled).size(20).spans(vec![
SpanStyle::new(0..4).bold(),
SpanStyle::new(6..12).italic(),
SpanStyle::new(14..24).underline(),
SpanStyle::new(30..37).color(PaintId::SKY),
]);
let aligned = (
wtext("Left aligned")
.text_align(Align::CENTER_LEFT)
.width(rest(1)),
wtext("Centered").text_align(Align::CENTER).width(rest(1)),
wtext("Right aligned")
.text_align(Align::CENTER_RIGHT)
.width(rest(1)),
wtext("Left aligned").text_align(Align::CENTER_LEFT),
wtext("Centered").text_align(Align::CENTER),
wtext("Right aligned").text_align(Align::CENTER_RIGHT),
)
.span(Dir::DOWN)
.gap(dp(4))
@@ -84,8 +77,7 @@ where
.spans(vec![SpanStyle::new(0..9).bold()]),
wtext("Drag across display text to select it. The overflow markers select hidden source text, but are never copied.")
.overflow(TextOverflow::Wrap)
.color(PaintId::GRAY)
.width(rest(1)),
.color(PaintId::GRAY),
)
.span(Dir::DOWN)
.gap(dp(6))
+57
View File
@@ -204,6 +204,7 @@ fn a_span_reuses_unchanged_sibling_sizes_when_only_its_along_extent_changes() {
children: vec![changed.any(), sibling.any()],
dir: Dir::DOWN,
gap: LayoutLen::ZERO,
compact_orthogonal: false,
});
let root = rsc
.ui
@@ -231,6 +232,61 @@ fn a_span_reuses_unchanged_sibling_sizes_when_only_its_along_extent_changes() {
);
}
#[test]
fn a_span_leaves_children_in_its_offered_orthogonal_axis_by_default() {
let mut rsc = TestRsc { ui: Ui::default() };
let short = wtext("short").add(&mut rsc);
let root = (short, wtext("a much wider line"))
.span(Dir::DOWN)
.add_strong(&mut rsc);
let root_id = root.id();
let root = root.any();
let mut render = UiRenderState::new();
render.resize((200.0, 100.0));
render.update(&root, &mut rsc);
let width = render.active[&root_id].size.x;
assert_eq!(width.rel, 0.0);
assert_eq!(width.rest, 0.0);
assert!(width.abs > 0.0 && width.abs < 200.0);
assert_eq!(
render.active[&short.id()]
.region
.to_px(vec2(200.0, 100.0))
.size()
.x,
200.0,
);
}
#[test]
fn a_compact_span_shrinks_its_orthogonal_axis_to_its_widest_child() {
let mut rsc = TestRsc { ui: Ui::default() };
let short = wtext("short").add(&mut rsc);
let root = (short, wtext("a much wider line"))
.span(Dir::DOWN)
.compact()
.add_strong(&mut rsc);
let root_id = root.id();
let root = root.any();
let mut render = UiRenderState::new();
render.resize((200.0, 100.0));
render.update(&root, &mut rsc);
let width = render.active[&root_id].size.x;
assert_eq!(width.rel, 0.0);
assert_eq!(width.rest, 0.0);
assert!(width.abs > 0.0 && width.abs < 200.0);
assert_eq!(
render.active[&short.id()]
.region
.to_px(vec2(200.0, 100.0))
.size()
.x,
width.abs,
);
}
#[test]
fn a_child_coordinate_offset_moves_only_the_child_subtree() {
let mut rsc = TestRsc { ui: Ui::default() };
@@ -1077,6 +1133,7 @@ fn a_span_of_padded_children_inside_a_span_draws_each_where_its_box_is() {
children: vec![header.any(), inner.any()],
dir: Dir::DOWN,
gap: LayoutLen::ZERO,
compact_orthogonal: false,
});
let mut list = LazySpan::new(Dir::DOWN, Pin::End);
list.push_back(LazyItem::new(0, outer.any()));
+21 -1
View File
@@ -5,6 +5,7 @@ pub struct Span {
pub children: Vec<StrongWidget>,
pub dir: Dir,
pub gap: LayoutLen,
pub compact_orthogonal: bool,
}
impl Widget for Span {
@@ -85,7 +86,7 @@ impl Widget for Span {
}
if ortho_mixed {
ortho_len = LayoutLen::default();
} else {
} else if self.compact_orthogonal {
let ortho = ortho_len
.apply_rest(painter.density())
.align(AxisAlign::Neg);
@@ -111,9 +112,18 @@ impl Span {
children: Vec::new(),
dir,
gap: LayoutLen::ZERO,
compact_orthogonal: false,
}
}
/// Shrink the span and its children to the widest orthogonal child
/// instead of leaving children in the orthogonal space offered by its
/// parent.
pub fn compact(mut self) -> Self {
self.compact_orthogonal = true;
self
}
pub fn gap(mut self, gap: impl Into<LayoutLen>) -> Self {
self.gap = gap.into();
self
@@ -132,6 +142,7 @@ pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Ta
pub children: Wa,
pub dir: Dir,
pub gap: LayoutLen,
pub compact_orthogonal: bool,
_pd: PhantomData<(State, Tag)>,
}
@@ -146,6 +157,7 @@ impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait
children: self.children.add(rsc).arr.into_iter().collect(),
dir: self.dir,
gap: self.gap,
compact_orthogonal: self.compact_orthogonal,
}
}
}
@@ -158,6 +170,7 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
children,
dir,
gap: LayoutLen::ZERO,
compact_orthogonal: false,
_pd: PhantomData,
}
}
@@ -166,6 +179,13 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
self.gap = gap.into();
self
}
/// Shrink the span and its children to the widest orthogonal child
/// instead of filling the orthogonal space offered by its parent.
pub fn compact(mut self) -> Self {
self.compact_orthogonal = true;
self
}
}
impl std::ops::Deref for Span {