layout/stack: opt-in Stack::fit_content() so a wrapping container can adopt the stack's intrinsic size
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

`Stack::preferred_size` unconditionally reported `(max_width, max_h)` — every stack claimed the full width its parent offered, regardless of what its children actually needed. That is the right default for a FrameLayout-style overlay (the existing callers all rely on it) but it makes the natural "pin a stack to a fixed-size child (e.g. a spacer of `card_w × card_h`) and centre other children on top of it" pattern impossible: a container wrapping such a stack always inherited the full parent width and ignored the spacer's footprint. The new `Stack::fit_content()` builder mirrors the `Column::fit_content` flag — when set, `preferred_size` returns the max of children's intrinsic widths and heights instead of claiming the parent's `max_width`, with the same "skip filler widgets that themselves claim `max_width`" exclusion list (`Spacer` with no `fixed_width`, `Separator`, `Scroll`, `ProgressBar`, `Slider`, `TextEdit` without `fixed_width`) so the flag is not defeated by a flexible child slipping into the stack. Default behaviour is unchanged.
This commit is contained in:
2026-05-20 15:24:34 +02:00
parent 640db23de2
commit 757063694e

View File

@@ -54,6 +54,7 @@ pub struct Stack<Msg: Clone>
/// Children with their alignment, margin, and extra `(x, y)` translation /// Children with their alignment, margin, and extra `(x, y)` translation
/// applied after alignment. Drawn in order — last child is on top. /// applied after alignment. Drawn in order — last child is on top.
pub children: Vec<( Element<Msg>, HAlign, VAlign, f32, f32, f32 )>, pub children: Vec<( Element<Msg>, HAlign, VAlign, f32, f32, f32 )>,
pub fit_content: bool,
} }
impl<Msg: Clone> Stack<Msg> impl<Msg: Clone> Stack<Msg>
@@ -61,7 +62,13 @@ impl<Msg: Clone> Stack<Msg>
/// Create an empty stack. /// Create an empty stack.
pub fn new() -> Self pub fn new() -> Self
{ {
Self { children: Vec::new() } Self { children: Vec::new(), fit_content: false }
}
pub fn fit_content( mut self ) -> Self
{
self.fit_content = true;
self
} }
/// Append a child that fills the entire Stack rect (Android FrameLayout default). /// Append a child that fills the entire Stack rect (Android FrameLayout default).
@@ -111,9 +118,31 @@ impl<Msg: Clone> Stack<Msg>
self self
} }
/// Return the preferred `(width, height)` — the maximum height among children.
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32) pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
{ {
if self.fit_content
{
let content_w = self.children.iter()
.map( |( c, _, _, _, _, _ )| match c
{
Element::Spacer( s ) => s.fixed_width.unwrap_or( 0.0 ),
Element::Separator( _ ) => 0.0,
Element::Scroll( _ ) => 0.0,
Element::ProgressBar( _ ) => 0.0,
Element::Slider( _ ) => 0.0,
Element::TextEdit( t ) => if t.fixed_width.is_some()
{
t.preferred_size( max_width, canvas ).0
} else { 0.0 },
other => other.preferred_size( max_width, canvas ).0,
} )
.fold( 0.0_f32, f32::max );
let max_h = self.children.iter()
.map( |( c, _, _, _, _, _ )| c.preferred_size( max_width, canvas ).1 )
.fold( 0.0_f32, f32::max );
return ( content_w.min( max_width ), max_h );
}
let max_h = self.children.iter() let max_h = self.children.iter()
.map( |( c, _, _, _, _, _ )| c.preferred_size( max_width, canvas ).1 ) .map( |( c, _, _, _, _, _ )| c.preferred_size( max_width, canvas ).1 )
.fold( 0.0_f32, f32::max ); .fold( 0.0_f32, f32::max );
@@ -163,6 +192,7 @@ impl<Msg: Clone> Stack<Msg>
.map( |( child, ha, va, margin, ox, oy )| .map( |( child, ha, va, margin, ox, oy )|
( child.map_arc( f ), ha, va, margin, ox, oy ) ) ( child.map_arc( f ), ha, va, margin, ox, oy ) )
.collect(), .collect(),
fit_content: self.fit_content,
} }
} }
} }