use core::fmt;
use alloc::vec::Vec;
use waterui_core::View;
use crate::{
HorizontalAlignment, Layout, PlacedSubview, ProposalSize, Rect, Size, StretchAxis, SubView,
SubviewPlacement, VerticalAlignment, container::FixedContainer,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct BackgroundLayout;
impl Layout for BackgroundLayout {
fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
children.get(1).copied().unwrap_or_default()
}
fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size {
children
.get(1)
.expect("BackgroundLayout requires a content child at index 1")
.measure(proposal)
.size
}
fn place(
&self,
bounds: Rect,
_proposal: ProposalSize,
children: &[&dyn SubView],
) -> Vec<SubviewPlacement> {
assert!(
children.len() >= 2,
"BackgroundLayout requires at least 2 children: background and content"
);
let mut placements = Vec::with_capacity(children.len());
placements.push(SubviewPlacement::new(
Rect::new(bounds.origin(), Size::new(bounds.width(), bounds.height())),
ProposalSize::new(bounds.width(), bounds.height()),
));
placements.push(SubviewPlacement::new(
Rect::new(bounds.origin(), Size::new(bounds.width(), bounds.height())),
ProposalSize::new(bounds.width(), bounds.height()),
));
placements
}
fn explicit_horizontal(
&self,
alignment: HorizontalAlignment,
_bounds: Rect,
children: &[PlacedSubview<'_>],
) -> Option<f32> {
children
.get(1)
.and_then(|child| child.explicit_horizontal(alignment))
}
fn explicit_vertical(
&self,
alignment: VerticalAlignment,
_bounds: Rect,
children: &[PlacedSubview<'_>],
) -> Option<f32> {
children
.get(1)
.and_then(|child| child.explicit_vertical(alignment))
}
}
pub struct BackgroundView<Content, Bg> {
content: Content,
background: Bg,
}
impl<Content: View, Bg: View> BackgroundView<Content, Bg> {
#[must_use]
pub const fn new(content: Content, background: Bg) -> Self {
Self {
content,
background,
}
}
}
impl<Content, Bg> fmt::Debug for BackgroundView<Content, Bg> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BackgroundView").finish_non_exhaustive()
}
}
impl<Content, Bg> View for BackgroundView<Content, Bg>
where
Content: View + 'static,
Bg: View + 'static,
{
fn body(self, _env: &waterui_core::Environment) -> impl View {
let Self {
content,
background,
} = self;
FixedContainer::new(BackgroundLayout, (background, content))
}
fn stretch_axis(&self) -> StretchAxis {
BackgroundLayout
.stretch_axis(&[self.background.stretch_axis(), self.content.stretch_axis()])
}
}
#[must_use]
pub const fn background<Content: View, Bg: View>(
content: Content,
background: Bg,
) -> BackgroundView<Content, Bg> {
BackgroundView::new(content, background)
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use waterui_core::layout::Point;
use waterui_core::layout::ViewDimensions;
use super::*;
use alloc::vec;
struct MockSubView {
size: Size,
}
impl SubView for MockSubView {
fn measure(&self, _proposal: ProposalSize) -> ViewDimensions {
ViewDimensions::new(self.size)
}
fn stretch_axis(&self) -> StretchAxis {
StretchAxis::None
}
fn priority(&self) -> i32 {
0
}
}
#[test]
fn test_background_size_from_content() {
let layout = BackgroundLayout;
let mut bg = MockSubView {
size: Size::new(200.0, 200.0),
};
let mut content = MockSubView {
size: Size::new(100.0, 50.0),
};
let children: Vec<&dyn SubView> = vec![&mut bg, &mut content];
let size = layout.size_that_fits(ProposalSize::UNSPECIFIED, &children);
assert_eq!(size.width, 100.0);
assert_eq!(size.height, 50.0);
}
#[test]
fn test_background_fills_bounds_bounded_proposal() {
let layout = BackgroundLayout;
let mut bg = MockSubView {
size: Size::new(50.0, 50.0),
};
let mut content = MockSubView {
size: Size::new(100.0, 100.0),
};
let children: Vec<&dyn SubView> = vec![&mut bg, &mut content];
let bounds = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
let proposal = ProposalSize::new(Some(bounds.width()), Some(bounds.height()));
let placements = layout.place(bounds, proposal, &children);
assert_eq!(placements[0].frame.width(), 100.0);
assert_eq!(placements[0].frame.height(), 100.0);
assert_eq!(placements[1].frame.width(), 100.0);
assert_eq!(placements[1].frame.height(), 100.0);
}
#[test]
fn test_background_layout_reports_its_content_stretch_axis() {
assert_eq!(
BackgroundLayout.stretch_axis(&[StretchAxis::Both, StretchAxis::Horizontal]),
StretchAxis::Horizontal
);
}
}