waterui_layout/containers/
container.rs1use core::fmt;
5use fmt::Debug;
6
7use alloc::{boxed::Box, vec::Vec};
8use nami::{Computed, Signal};
9use waterui_core::{
10 AnyView, Native, NativeView, View,
11 layout::{
12 HorizontalAlignment, LayoutDirection, LayoutInvalidationCallback, PlacedSubview,
13 ProposalSize, Rect, Size, SubView, SubviewPlacement, VerticalAlignment,
14 },
15 view::TupleViews,
16 views::{AnyViews, Views, ViewsExt},
17};
18
19use crate::{Layout, StretchAxis};
20
21struct DirectionalLayout {
22 inner: Box<dyn Layout>,
23 direction: Computed<LayoutDirection>,
24}
25
26impl DirectionalLayout {
27 fn new(inner: Box<dyn Layout>, direction: Computed<LayoutDirection>) -> Self {
28 Self { inner, direction }
29 }
30
31 fn mirror(&self, bounds: Rect, frame: Rect) -> Rect {
32 if self.direction.get().is_right_to_left() {
33 Rect::new(
34 waterui_core::layout::Point::new(
35 bounds.min_x() + bounds.max_x() - frame.max_x(),
36 frame.y(),
37 ),
38 *frame.size(),
39 )
40 } else {
41 frame
42 }
43 }
44}
45
46impl Debug for DirectionalLayout {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.debug_struct("DirectionalLayout")
49 .field("inner", &self.inner)
50 .field("direction", &self.direction.get())
51 .finish()
52 }
53}
54
55impl Layout for DirectionalLayout {
56 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size {
57 self.inner.size_that_fits(proposal, children)
58 }
59
60 fn place(
61 &self,
62 bounds: Rect,
63 proposal: ProposalSize,
64 children: &[&dyn SubView],
65 ) -> Vec<SubviewPlacement> {
66 self.inner
67 .place(bounds, proposal, children)
68 .into_iter()
69 .map(|placement| {
70 SubviewPlacement::new(self.mirror(bounds, placement.frame), placement.proposal)
71 })
72 .collect()
73 }
74
75 fn explicit_horizontal(
76 &self,
77 alignment: HorizontalAlignment,
78 bounds: Rect,
79 children: &[PlacedSubview<'_>],
80 ) -> Option<f32> {
81 if !self.direction.get().is_right_to_left() {
82 return self.inner.explicit_horizontal(alignment, bounds, children);
83 }
84 let unmirrored = children
85 .iter()
86 .map(|child| {
87 PlacedSubview::new(
88 child.view,
89 SubviewPlacement::new(self.mirror(bounds, child.frame), child.proposal),
90 )
91 })
92 .collect::<Vec<_>>();
93 self.inner
94 .explicit_horizontal(alignment, bounds, &unmirrored)
95 .map(|guide| bounds.min_x() + bounds.max_x() - guide)
96 }
97
98 fn explicit_vertical(
99 &self,
100 alignment: VerticalAlignment,
101 bounds: Rect,
102 children: &[PlacedSubview<'_>],
103 ) -> Option<f32> {
104 if !self.direction.get().is_right_to_left() {
105 return self.inner.explicit_vertical(alignment, bounds, children);
106 }
107 let unmirrored = children
108 .iter()
109 .map(|child| {
110 PlacedSubview::new(
111 child.view,
112 SubviewPlacement::new(self.mirror(bounds, child.frame), child.proposal),
113 )
114 })
115 .collect::<Vec<_>>();
116 self.inner.explicit_vertical(alignment, bounds, &unmirrored)
117 }
118
119 fn explicit_horizontal_alignments(&self) -> Vec<HorizontalAlignment> {
120 self.inner.explicit_horizontal_alignments()
121 }
122
123 fn explicit_vertical_alignments(&self) -> Vec<VerticalAlignment> {
124 self.inner.explicit_vertical_alignments()
125 }
126
127 fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
128 self.inner.stretch_axis(children)
129 }
130
131 fn watch_invalidation(
132 &self,
133 invalidate: LayoutInvalidationCallback,
134 ) -> Vec<nami::watcher::BoxWatcherGuard> {
135 let mut guards = self.inner.watch_invalidation(invalidate.clone());
136 guards.push(self.direction.watch(move |_| invalidate()));
137 guards
138 }
139}
140
141pub struct FixedContainer {
144 layout: Box<dyn Layout>,
145 contents: Vec<AnyView>,
146}
147
148impl Debug for FixedContainer {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.debug_struct("Container")
151 .field("layout", &"Box<dyn Layout>")
152 .field("contents", &self.contents)
153 .finish()
154 }
155}
156
157impl FixedContainer {
158 pub fn new(layout: impl Layout + 'static, contents: impl TupleViews) -> Self {
161 Self {
162 layout: Box::new(layout),
163 contents: contents.into_views(),
164 }
165 }
166
167 #[must_use]
169 pub fn into_inner(self) -> (Box<dyn Layout>, Vec<AnyView>) {
170 (self.layout, self.contents)
171 }
172
173 #[must_use]
175 pub fn from_parts(layout: Box<dyn Layout>, contents: Vec<AnyView>) -> Self {
176 Self { layout, contents }
177 }
178
179 #[must_use = "this borrows the container's parts without consuming it"]
181 pub fn as_parts(&self) -> (&dyn Layout, &[AnyView]) {
182 (self.layout.as_ref(), &self.contents)
183 }
184}
185
186impl FixedContainer {
187 fn child_stretch_axes(&self) -> Vec<StretchAxis> {
189 self.contents.iter().map(View::stretch_axis).collect()
190 }
191}
192
193impl NativeView for FixedContainer {
194 fn stretch_axis(&self) -> StretchAxis {
195 self.layout.stretch_axis(&self.child_stretch_axes())
196 }
197}
198
199impl View for FixedContainer {
200 fn body(mut self, env: &waterui_core::Environment) -> impl View {
201 self.layout = Box::new(DirectionalLayout::new(
202 self.layout,
203 waterui_core::layout::layout_direction(env),
204 ));
205 Native::new(self)
206 }
207
208 fn stretch_axis(&self) -> StretchAxis {
209 self.layout.stretch_axis(&self.child_stretch_axes())
210 }
211}
212
213#[derive(Debug)]
220pub struct LazyContainer {
221 layout: Box<dyn Layout>,
222 contents: AnyViews<AnyView>,
223 direction: Computed<LayoutDirection>,
224}
225
226impl LazyContainer {
227 pub fn new<V: View>(
229 layout: impl Layout + 'static,
230 contents: impl Views<View = V> + 'static,
231 ) -> Self {
232 Self {
233 layout: Box::new(layout),
234 contents: AnyViews::new(contents.map(|v| AnyView::new(v))),
235 direction: Computed::constant(LayoutDirection::default()),
236 }
237 }
238 #[must_use]
240 pub fn into_inner(self) -> (Box<dyn Layout>, AnyViews<AnyView>) {
241 (self.layout, self.contents)
242 }
243
244 #[must_use]
246 pub fn as_parts(&self) -> (&dyn Layout, &AnyViews<AnyView>) {
247 (self.layout.as_ref(), &self.contents)
248 }
249
250 #[must_use]
252 pub fn direction(&self) -> Computed<LayoutDirection> {
253 self.direction.clone()
254 }
255}
256
257impl LazyContainer {
258 const fn child_stretch_axes() -> &'static [StretchAxis] {
263 &[]
264 }
265}
266
267impl NativeView for LazyContainer {
268 fn stretch_axis(&self) -> StretchAxis {
269 self.layout.stretch_axis(Self::child_stretch_axes())
270 }
271}
272
273impl View for LazyContainer {
274 fn body(mut self, env: &waterui_core::Environment) -> impl View {
275 self.direction = waterui_core::layout::layout_direction(env);
279 Native::new(self)
280 }
281
282 fn stretch_axis(&self) -> StretchAxis {
283 self.layout.stretch_axis(Self::child_stretch_axes())
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use alloc::vec;
290
291 use super::*;
292 use waterui_core::layout::Point;
293
294 #[derive(Debug)]
295 struct OffsetLayout;
296
297 impl Layout for OffsetLayout {
298 fn size_that_fits(&self, _proposal: ProposalSize, _children: &[&dyn SubView]) -> Size {
299 Size::new(100.0, 40.0)
300 }
301
302 fn place(
303 &self,
304 _bounds: Rect,
305 proposal: ProposalSize,
306 _children: &[&dyn SubView],
307 ) -> Vec<SubviewPlacement> {
308 vec![SubviewPlacement::new(
309 Rect::new(Point::new(10.0, 4.0), Size::new(20.0, 12.0)),
310 proposal,
311 )]
312 }
313 }
314
315 #[test]
316 fn right_to_left_direction_mirrors_horizontal_placement_bounded_proposal() {
317 let layout = DirectionalLayout::new(
318 Box::new(OffsetLayout),
319 Computed::constant(LayoutDirection::RightToLeft),
320 );
321 let bounds = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 40.0));
322 let proposal = ProposalSize::new(Some(bounds.width()), Some(bounds.height()));
323 let placements = layout.place(bounds, proposal, &[]);
324
325 assert_eq!(
326 placements,
327 vec![SubviewPlacement::new(
328 Rect::new(Point::new(70.0, 4.0), Size::new(20.0, 12.0)),
329 proposal,
330 )]
331 );
332 }
333}