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, 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(&self, bounds: Rect, children: &[&dyn SubView]) -> Vec<Rect> {
61 self.inner
62 .place(bounds, children)
63 .into_iter()
64 .map(|frame| self.mirror(bounds, frame))
65 .collect()
66 }
67
68 fn explicit_horizontal(
69 &self,
70 alignment: HorizontalAlignment,
71 bounds: Rect,
72 children: &[PlacedSubview<'_>],
73 ) -> Option<f32> {
74 if !self.direction.get().is_right_to_left() {
75 return self.inner.explicit_horizontal(alignment, bounds, children);
76 }
77 let unmirrored = children
78 .iter()
79 .map(|child| PlacedSubview::new(child.view, self.mirror(bounds, child.frame)))
80 .collect::<Vec<_>>();
81 self.inner
82 .explicit_horizontal(alignment, bounds, &unmirrored)
83 .map(|guide| bounds.min_x() + bounds.max_x() - guide)
84 }
85
86 fn explicit_vertical(
87 &self,
88 alignment: VerticalAlignment,
89 bounds: Rect,
90 children: &[PlacedSubview<'_>],
91 ) -> Option<f32> {
92 if !self.direction.get().is_right_to_left() {
93 return self.inner.explicit_vertical(alignment, bounds, children);
94 }
95 let unmirrored = children
96 .iter()
97 .map(|child| PlacedSubview::new(child.view, self.mirror(bounds, child.frame)))
98 .collect::<Vec<_>>();
99 self.inner.explicit_vertical(alignment, bounds, &unmirrored)
100 }
101
102 fn explicit_horizontal_alignments(&self) -> Vec<HorizontalAlignment> {
103 self.inner.explicit_horizontal_alignments()
104 }
105
106 fn explicit_vertical_alignments(&self) -> Vec<VerticalAlignment> {
107 self.inner.explicit_vertical_alignments()
108 }
109
110 fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
111 self.inner.stretch_axis(children)
112 }
113
114 fn watch_invalidation(
115 &self,
116 invalidate: LayoutInvalidationCallback,
117 ) -> Vec<nami::watcher::BoxWatcherGuard> {
118 let mut guards = self.inner.watch_invalidation(invalidate.clone());
119 guards.push(self.direction.watch(move |_| invalidate()));
120 guards
121 }
122}
123
124pub struct FixedContainer {
127 layout: Box<dyn Layout>,
128 contents: Vec<AnyView>,
129}
130
131impl Debug for FixedContainer {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 f.debug_struct("Container")
134 .field("layout", &"Box<dyn Layout>")
135 .field("contents", &self.contents)
136 .finish()
137 }
138}
139
140impl FixedContainer {
141 pub fn new(layout: impl Layout + 'static, contents: impl TupleViews) -> Self {
144 Self {
145 layout: Box::new(layout),
146 contents: contents.into_views(),
147 }
148 }
149
150 #[must_use]
152 pub fn into_inner(self) -> (Box<dyn Layout>, Vec<AnyView>) {
153 (self.layout, self.contents)
154 }
155
156 #[must_use]
158 pub fn from_parts(layout: Box<dyn Layout>, contents: Vec<AnyView>) -> Self {
159 Self { layout, contents }
160 }
161
162 #[must_use = "this borrows the container's parts without consuming it"]
164 pub fn as_parts(&self) -> (&dyn Layout, &[AnyView]) {
165 (self.layout.as_ref(), &self.contents)
166 }
167}
168
169impl FixedContainer {
170 fn child_stretch_axes(&self) -> Vec<StretchAxis> {
172 self.contents.iter().map(View::stretch_axis).collect()
173 }
174}
175
176impl NativeView for FixedContainer {
177 fn stretch_axis(&self) -> StretchAxis {
178 self.layout.stretch_axis(&self.child_stretch_axes())
179 }
180}
181
182impl View for FixedContainer {
183 fn body(mut self, env: &waterui_core::Environment) -> impl View {
184 self.layout = Box::new(DirectionalLayout::new(
185 self.layout,
186 waterui_core::layout::layout_direction(env),
187 ));
188 Native::new(self)
189 }
190
191 fn stretch_axis(&self) -> StretchAxis {
192 self.layout.stretch_axis(&self.child_stretch_axes())
193 }
194}
195
196#[derive(Debug)]
203pub struct LazyContainer {
204 layout: Box<dyn Layout>,
205 contents: AnyViews<AnyView>,
206 direction: Computed<LayoutDirection>,
207}
208
209impl LazyContainer {
210 pub fn new<V: View>(
212 layout: impl Layout + 'static,
213 contents: impl Views<View = V> + 'static,
214 ) -> Self {
215 Self {
216 layout: Box::new(layout),
217 contents: AnyViews::new(contents.map(|v| AnyView::new(v))),
218 direction: Computed::constant(LayoutDirection::default()),
219 }
220 }
221 #[must_use]
223 pub fn into_inner(self) -> (Box<dyn Layout>, AnyViews<AnyView>) {
224 (self.layout, self.contents)
225 }
226
227 #[must_use]
229 pub fn as_parts(&self) -> (&dyn Layout, &AnyViews<AnyView>) {
230 (self.layout.as_ref(), &self.contents)
231 }
232
233 #[must_use]
235 pub fn direction(&self) -> Computed<LayoutDirection> {
236 self.direction.clone()
237 }
238}
239
240impl LazyContainer {
241 const fn child_stretch_axes() -> &'static [StretchAxis] {
246 &[]
247 }
248}
249
250impl NativeView for LazyContainer {
251 fn stretch_axis(&self) -> StretchAxis {
252 self.layout.stretch_axis(Self::child_stretch_axes())
253 }
254}
255
256impl View for LazyContainer {
257 fn body(mut self, env: &waterui_core::Environment) -> impl View {
258 self.direction = waterui_core::layout::layout_direction(env);
262 Native::new(self)
263 }
264
265 fn stretch_axis(&self) -> StretchAxis {
266 self.layout.stretch_axis(Self::child_stretch_axes())
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use alloc::vec;
273
274 use super::*;
275 use waterui_core::layout::Point;
276
277 #[derive(Debug)]
278 struct OffsetLayout;
279
280 impl Layout for OffsetLayout {
281 fn size_that_fits(&self, _proposal: ProposalSize, _children: &[&dyn SubView]) -> Size {
282 Size::new(100.0, 40.0)
283 }
284
285 fn place(&self, _bounds: Rect, _children: &[&dyn SubView]) -> Vec<Rect> {
286 vec![Rect::new(Point::new(10.0, 4.0), Size::new(20.0, 12.0))]
287 }
288 }
289
290 #[test]
291 fn right_to_left_direction_mirrors_horizontal_placement() {
292 let layout = DirectionalLayout::new(
293 Box::new(OffsetLayout),
294 Computed::constant(LayoutDirection::RightToLeft),
295 );
296 let frames = layout.place(Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 40.0)), &[]);
297
298 assert_eq!(
299 frames,
300 vec![Rect::new(Point::new(70.0, 4.0), Size::new(20.0, 12.0))]
301 );
302 }
303}