waterui_layout/
padding.rs1use alloc::{vec, vec::Vec};
4use waterui_core::{AnyView, View};
5
6use crate::{Layout, Point, ProposalSize, Rect, Size, SubView, container::FixedContainer};
7
8#[derive(Debug, Clone)]
10pub struct PaddingLayout {
11 edges: EdgeInsets,
12}
13
14impl Layout for PaddingLayout {
15 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size {
16 let horizontal_padding = self.edges.leading + self.edges.trailing;
18 let vertical_padding = self.edges.top + self.edges.bottom;
19
20 let child_proposal = ProposalSize {
22 width: proposal.width.map(|w| (w - horizontal_padding).max(0.0)),
23 height: proposal.height.map(|h| (h - vertical_padding).max(0.0)),
24 };
25
26 let child_size = children
28 .first()
29 .map_or(Size::zero(), |c| c.size_that_fits(child_proposal));
30
31 let child_width = if child_size.width.is_infinite() {
33 proposal.width.unwrap_or(0.0) - horizontal_padding
34 } else {
35 child_size.width
36 };
37
38 let child_height = if child_size.height.is_infinite() {
39 proposal.height.unwrap_or(0.0) - vertical_padding
40 } else {
41 child_size.height
42 };
43
44 Size::new(
46 child_width + horizontal_padding,
47 child_height + vertical_padding,
48 )
49 }
50
51 fn place(&self, bounds: Rect, children: &[&dyn SubView]) -> Vec<Rect> {
52 if children.is_empty() {
53 return vec![];
54 }
55
56 let child_origin = Point::new(bounds.x() + self.edges.leading, bounds.y() + self.edges.top);
58
59 let horizontal_padding = self.edges.leading + self.edges.trailing;
60 let vertical_padding = self.edges.top + self.edges.bottom;
61
62 let child_size = Size::new(
63 (bounds.width() - horizontal_padding).max(0.0),
64 (bounds.height() - vertical_padding).max(0.0),
65 );
66
67 vec![Rect::new(child_origin, child_size)]
68 }
69}
70
71#[derive(Debug, Clone, PartialEq)]
73pub struct EdgeInsets {
74 top: f32,
75 bottom: f32,
76 leading: f32,
77 trailing: f32,
78}
79
80#[allow(clippy::cast_possible_truncation)]
81impl<T: Into<f64>> From<T> for EdgeInsets {
82 fn from(value: T) -> Self {
83 let v = value.into() as f32;
84 Self::all(v)
85 }
86}
87
88impl Default for EdgeInsets {
89 fn default() -> Self {
90 Self::all(0.0)
91 }
92}
93
94impl EdgeInsets {
95 #[must_use]
97 pub const fn new(top: f32, bottom: f32, leading: f32, trailing: f32) -> Self {
98 Self {
99 top,
100 bottom,
101 leading,
102 trailing,
103 }
104 }
105
106 #[must_use]
108 pub const fn all(value: f32) -> Self {
109 Self {
110 top: value,
111 bottom: value,
112 leading: value,
113 trailing: value,
114 }
115 }
116
117 #[must_use]
119 pub const fn symmetric(vertical: f32, horizontal: f32) -> Self {
120 Self {
121 top: vertical,
122 bottom: vertical,
123 leading: horizontal,
124 trailing: horizontal,
125 }
126 }
127}
128
129#[derive(Debug)]
131pub struct Padding {
132 layout: PaddingLayout,
133 content: AnyView,
134}
135
136impl Padding {
137 pub fn new(edges: EdgeInsets, content: impl View + 'static) -> Self {
139 Self {
140 layout: PaddingLayout { edges },
141 content: AnyView::new(content),
142 }
143 }
144}
145
146impl View for Padding {
147 fn body(self, _env: &waterui_core::Environment) -> impl View {
148 FixedContainer::new(self.layout, vec![self.content])
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::StretchAxis;
156
157 struct MockSubView {
158 size: Size,
159 }
160
161 impl SubView for MockSubView {
162 fn size_that_fits(&self, _proposal: ProposalSize) -> Size {
163 self.size
164 }
165 fn stretch_axis(&self) -> StretchAxis {
166 StretchAxis::None
167 }
168 fn priority(&self) -> i32 {
169 0
170 }
171 }
172
173 #[test]
174 fn test_padding_size() {
175 let layout = PaddingLayout {
176 edges: EdgeInsets::all(10.0),
177 };
178
179 let mut child = MockSubView {
180 size: Size::new(50.0, 30.0),
181 };
182 let children: Vec<&dyn SubView> = vec![&mut child];
183
184 let size = layout.size_that_fits(ProposalSize::UNSPECIFIED, &children);
185
186 assert!((size.width - 70.0).abs() < f32::EPSILON); assert!((size.height - 50.0).abs() < f32::EPSILON); }
190
191 #[test]
192 fn test_padding_placement() {
193 let layout = PaddingLayout {
194 edges: EdgeInsets::new(10.0, 20.0, 15.0, 25.0),
195 };
196
197 let mut child = MockSubView {
198 size: Size::new(50.0, 30.0),
199 };
200 let children: Vec<&dyn SubView> = vec![&mut child];
201
202 let bounds = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
203 let rects = layout.place(bounds, &children);
204
205 assert!((rects[0].x() - 15.0).abs() < f32::EPSILON);
207 assert!((rects[0].y() - 10.0).abs() < f32::EPSILON);
208
209 assert!((rects[0].width() - 60.0).abs() < f32::EPSILON); assert!((rects[0].height() - 70.0).abs() < f32::EPSILON); }
213}