1pub use cvkg_core::layout::EdgeInsets;
2use cvkg_core::{LayoutCache, LayoutView, Rect, Size, SizeProposal};
3
4pub struct Padding {
6 pub insets: EdgeInsets,
7}
8
9impl Padding {
10 pub fn new(insets: EdgeInsets) -> Self {
12 Self { insets }
13 }
14
15 pub fn uniform(value: f32) -> Self {
17 Self {
18 insets: EdgeInsets::all(value),
19 }
20 }
21
22 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
24 Self {
25 insets: EdgeInsets {
26 top: vertical,
27 bottom: vertical,
28 leading: horizontal,
29 trailing: horizontal,
30 },
31 }
32 }
33}
34
35impl LayoutView for Padding {
36 fn size_that_fits(
37 &self,
38 proposal: SizeProposal,
39 subviews: &[&dyn LayoutView],
40 cache: &mut LayoutCache,
41 ) -> Size {
42 let inner_proposal = SizeProposal::new(
43 proposal
44 .width
45 .map(|w| (w - self.insets.leading - self.insets.trailing).max(0.0)),
46 proposal
47 .height
48 .map(|h| (h - self.insets.top - self.insets.bottom).max(0.0)),
49 );
50 let self_hash = self.view_hash();
51 let child_size = if subviews.is_empty() {
52 Size::ZERO
53 } else {
54 let child_hash = subviews[0].view_hash();
55 if self_hash != 0 && child_hash != 0 {
56 cache.register_parent(child_hash, self_hash);
57 }
58 crate::with_layout_cycle_guard(child_hash, Size::ZERO, || {
59 subviews[0].size_that_fits(inner_proposal, &[], cache)
60 })
61 };
62 Size {
63 width: child_size.width + self.insets.leading + self.insets.trailing,
64 height: child_size.height + self.insets.top + self.insets.bottom,
65 }
66 }
67
68 fn place_subviews(
69 &self,
70 bounds: Rect,
71 subviews: &mut [&mut dyn LayoutView],
72 cache: &mut LayoutCache,
73 ) {
74 let inner = Rect {
75 x: bounds.x + self.insets.leading,
76 y: bounds.y + self.insets.top,
77 width: (bounds.width - self.insets.leading - self.insets.trailing).max(0.0),
78 height: (bounds.height - self.insets.top - self.insets.bottom).max(0.0),
79 };
80 let self_hash = self.view_hash();
81 for child in subviews.iter_mut() {
82 let child_hash = child.view_hash();
83 if self_hash != 0 && child_hash != 0 {
84 cache.register_parent(child_hash, self_hash);
85 }
86 let is_visible = if let Some(viewport) = cache.viewport {
87 inner.intersects(&viewport)
88 } else {
89 true
90 };
91 if is_visible {
92 crate::with_layout_cycle_guard_void(child_hash, || {
93 child.place_subviews(inner, &mut [], cache);
94 });
95 }
96 }
97 }
98}
99
100pub struct SafeArea {
102 pub edges: SafeAreaEdges,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub struct SafeAreaEdges {
108 pub top: bool,
109 pub bottom: bool,
110 pub leading: bool,
111 pub trailing: bool,
112}
113
114impl Default for SafeAreaEdges {
115 fn default() -> Self {
116 Self {
117 top: true,
118 bottom: true,
119 leading: false,
120 trailing: false,
121 }
122 }
123}
124
125impl SafeArea {
126 pub fn all() -> Self {
128 Self {
129 edges: SafeAreaEdges {
130 top: true,
131 bottom: true,
132 leading: true,
133 trailing: true,
134 },
135 }
136 }
137
138 pub fn vertical() -> Self {
140 Self {
141 edges: SafeAreaEdges::default(),
142 }
143 }
144
145 fn insets(&self) -> EdgeInsets {
146 EdgeInsets {
147 top: if self.edges.top { 44.0 } else { 0.0 },
148 bottom: if self.edges.bottom { 34.0 } else { 0.0 },
149 leading: 0.0,
150 trailing: 0.0,
151 }
152 }
153}
154
155impl LayoutView for SafeArea {
156 fn size_that_fits(
157 &self,
158 proposal: SizeProposal,
159 subviews: &[&dyn LayoutView],
160 cache: &mut LayoutCache,
161 ) -> Size {
162 Padding::new(self.insets()).size_that_fits(proposal, subviews, cache)
163 }
164
165 fn place_subviews(
166 &self,
167 bounds: Rect,
168 subviews: &mut [&mut dyn LayoutView],
169 cache: &mut LayoutCache,
170 ) {
171 Padding::new(self.insets()).place_subviews(bounds, subviews, cache);
172 }
173}
174
175pub struct AspectRatio {
177 pub ratio: f32,
178}
179
180impl AspectRatio {
181 pub fn new(ratio: f32) -> Self {
183 Self {
184 ratio: ratio.max(0.01),
185 }
186 }
187
188 pub fn square() -> Self {
190 Self::new(1.0)
191 }
192
193 pub fn widescreen() -> Self {
195 Self::new(16.0 / 9.0)
196 }
197
198 pub fn portrait() -> Self {
200 Self::new(9.0 / 16.0)
201 }
202
203 fn fitted_size(&self, proposal: SizeProposal) -> Size {
204 let max_w = proposal.width.unwrap_or(f32::MAX);
205 let max_h = proposal.height.unwrap_or(f32::MAX);
206 let w = max_w;
207 let h = w / self.ratio;
208 if h <= max_h {
209 return Size {
210 width: w,
211 height: h,
212 };
213 }
214 Size {
215 width: max_h * self.ratio,
216 height: max_h,
217 }
218 }
219}
220
221impl LayoutView for AspectRatio {
222 fn size_that_fits(
223 &self,
224 proposal: SizeProposal,
225 subviews: &[&dyn LayoutView],
226 cache: &mut LayoutCache,
227 ) -> Size {
228 if subviews.is_empty() {
229 return self.fitted_size(proposal);
230 }
231 let self_hash = self.view_hash();
232 let child = subviews[0];
233 let child_hash = child.view_hash();
234 if self_hash != 0 && child_hash != 0 {
235 cache.register_parent(child_hash, self_hash);
236 }
237 let child_size = crate::with_layout_cycle_guard(child_hash, Size::ZERO, || {
238 child.size_that_fits(
239 SizeProposal::new(Some(f32::MAX), Some(f32::MAX)),
240 &[],
241 cache,
242 )
243 });
244 let intrinsic_ratio = child_size.width / child_size.height.max(0.01);
245 if (intrinsic_ratio - self.ratio).abs() < 0.01 {
246 return self.fitted_size(proposal);
247 }
248 let fit = self.fitted_size(proposal);
249 let child_w = fit.width.min(child_size.width);
250 let child_h = child_w / intrinsic_ratio;
251 let final_h = child_h.min(fit.height);
252 let final_w = final_h * intrinsic_ratio;
253 Size {
254 width: final_w,
255 height: final_h,
256 }
257 }
258
259 fn place_subviews(
260 &self,
261 bounds: Rect,
262 subviews: &mut [&mut dyn LayoutView],
263 cache: &mut LayoutCache,
264 ) {
265 let fit = self.fitted_size(SizeProposal::new(Some(bounds.width), Some(bounds.height)));
266 let x = bounds.x + (bounds.width - fit.width) * 0.5;
267 let y = bounds.y + (bounds.height - fit.height) * 0.0;
268 let inner = Rect {
269 x,
270 y,
271 width: fit.width,
272 height: fit.height,
273 };
274 let self_hash = self.view_hash();
275 for child in subviews.iter_mut() {
276 let child_hash = child.view_hash();
277 if self_hash != 0 && child_hash != 0 {
278 cache.register_parent(child_hash, self_hash);
279 }
280 let is_visible = if let Some(viewport) = cache.viewport {
281 inner.intersects(&viewport)
282 } else {
283 true
284 };
285 if is_visible {
286 crate::with_layout_cycle_guard_void(child_hash, || {
287 child.place_subviews(inner, &mut [], cache);
288 });
289 }
290 }
291 }
292}