Constrain offered boxes with independent widget size bounds

This commit is contained in:
iris-ai committed 2026-09-20 19:47:32 -04:00
1 parent 4cb6f6882a
commit 2ac0843cb2
18 files changed
+554 -379

No files matched your search

+1 -1
View File
@@ -168,7 +168,7 @@ impl Harness {
pub fn set_len(&mut self, id: impl IdLike, axis: Axis, len: impl Into<LayoutLen>) {
self.rsc
.widgets_mut()
.set_size_rule(id, axis, SizeRule::Exact(len.into()));
.set_size_rule(id, axis, SizeRule::from(len.into()));
}
/// Sets the root and lays it out, so a pointer event has something to hit.
+5 -10
View File
@@ -350,9 +350,7 @@ impl Plan {
self.walk_mut(&mut |node| {
let Some(rules) = &mut node.size else { return };
for axis in Axis::BOTH {
if rules[axis].bound() != Bound::ANY {
rules[axis] = SizeRule::Free;
}
rules[axis].bound = Bound::ANY;
}
});
}
@@ -682,8 +680,8 @@ impl Sow<'_> {
match self.rng.below(8) {
0 | 1 => self.len().into(),
2 => LayoutLen::LEFTOVER.into(),
3 => SizeRule::Min(self.bound()),
4 => SizeRule::Max(self.bound()),
3 => SizeRule::min(self.bound()),
4 => SizeRule::max(self.bound()),
// Both in pixels, so one can be put under the other: a floor and
// a cap that change sides with the window bound nothing, which
// is a caller's bug rather than a tree to grow.
@@ -692,12 +690,9 @@ impl Sow<'_> {
Px::from_f32(20.0 + self.rng.below(180) as f32),
Px::from_f32(20.0 + self.rng.below(180) as f32),
);
SizeRule::Clamp {
min: Len::px(a.min(b).to_f32()),
max: Len::px(a.max(b).to_f32()),
}
SizeRule::clamp(Len::px(a.min(b).to_f32()), Len::px(a.max(b).to_f32()))
}
_ => SizeRule::Free,
_ => SizeRule::FREE,
}
}
-68
View File
@@ -1,68 +0,0 @@
use crate::prelude::*;
/// Asks its child in the shorter of a cap and the box this widget was given,
/// and answers what the child used, held to the same cap.
///
/// A cap on the box is a widget rather than a [`SizeRule`] because a box is
/// whoever asked's to decide: a rule that read the box it was given would be
/// decided again by every path that hands a widget one, including the ones
/// that re-place a drawing without asking it anything, and the decision would
/// then depend on which path arrived last. A widget is drawn again whenever
/// its own box changes, so the comparison is made where the answer can be
/// kept -- `longer_than` narrows the windows this drawing holds for, and
/// `holds` says the box lengths.
///
/// The box is what a text wraps at and what a scroll takes its viewport from,
/// which is why capping the answer alone is not the same thing.
pub struct MaxSize {
pub inner: StrongWidget,
pub x: Option<Len>,
pub y: Option<Len>,
}
impl MaxSize {
fn max(&self, axis: Axis) -> Option<Len> {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
}
impl Widget for MaxSize {
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
let inner = requests.widget(&self.inner, axis)?;
Some(match self.max(axis) {
Some(max) => requests.min(inner, max.into()),
None => inner,
})
}
fn draw(&mut self, painter: &mut Painter) -> Size {
let align = painter.alignment();
let mut region = UiRegion::FULL;
for axis in Axis::BOTH {
let Some(max) = self.max(axis) else {
continue;
};
let own = painter.region_len(axis);
if painter.longer_than(own, max, axis) {
region[axis] = max.align(align[axis]);
}
}
let mut size = painter.widget_at(&self.inner, region).size();
for axis in Axis::BOTH {
// The child may draw past the box it was given -- a text too tall
// for it -- and the cap is a promise about the length as well. A
// share passes through: it is a length only to whoever divides
// one, and that is this widget's parent rather than this widget,
// which has already given the share the box the cap allows.
if let Some(max) = self.max(axis)
&& painter.longer_than(size[axis].without_leftover(), max, axis)
{
size[axis] = max.into();
}
}
size
}
}
-2
View File
@@ -1,5 +1,4 @@
mod layer;
mod max_size;
mod offset;
mod pad;
mod scroll;
@@ -7,7 +6,6 @@ mod span;
mod stack;
pub use layer::*;
pub use max_size::*;
pub use offset::*;
pub use pad::*;
pub use scroll::*;
+16 -22
View File
@@ -53,8 +53,8 @@ widget_trait! {
move |state| {
let id = self.add(state);
let widgets = &mut state.ui_mut().widgets;
widgets.set_size_rule(id, Axis::X, SizeRule::Exact(size.x));
widgets.set_size_rule(id, Axis::Y, SizeRule::Exact(size.y));
widgets.set_len(id, Axis::X, size.x);
widgets.set_len(id, Axis::Y, size.y);
id
}
}
@@ -66,15 +66,12 @@ widget_trait! {
state
.ui_mut()
.widgets
.set_size_rule(id, Axis::X, SizeRule::from(len));
.set_len(id, Axis::X, len);
id
}
}
/// Answers at least this wide, whatever it drew: a rule beside the
/// widget, so what a row gives it is at least this even where the widget
/// itself wanted less. The box it draws in is untouched -- for that, see
/// [`MaxSize`].
/// Sets a floor on this widget's offered width and reported width.
fn min_width(self, len: impl Into<Len>) -> impl WidgetIdFn<Rsc, WL::Widget> {
let len = len.into();
move |state| {
@@ -93,25 +90,22 @@ widget_trait! {
}
}
/// Puts this in a [`MaxSize`]: it is asked in the shorter of the cap and
/// the box that widget was given, and is as long as it used, held to the
/// cap. A widget rather than a rule because the box is whoever asked's to
/// decide -- see [`MaxSize`].
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> {
/// Caps this widget's offered width and reported width.
fn max_width(self, len: impl Into<Len>) -> impl WidgetIdFn<Rsc, WL::Widget> {
let len = len.into();
move |state| MaxSize {
inner: self.add_strong(state),
x: Some(len),
y: None,
move |state| {
let id = self.add(state);
state.ui_mut().widgets.set_max_len(id, Axis::X, len);
id
}
}
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> {
fn max_height(self, len: impl Into<Len>) -> impl WidgetIdFn<Rsc, WL::Widget> {
let len = len.into();
move |state| MaxSize {
inner: self.add_strong(state),
x: None,
y: Some(len),
move |state| {
let id = self.add(state);
state.ui_mut().widgets.set_max_len(id, Axis::Y, len);
id
}
}
@@ -122,7 +116,7 @@ widget_trait! {
state
.ui_mut()
.widgets
.set_size_rule(id, Axis::Y, SizeRule::from(len));
.set_len(id, Axis::Y, len);
id
}
}