1use core::any::Any;
32use core::fmt;
33use fmt::Debug;
34
35use alloc::{rc::Rc, vec::Vec};
36use nami::watcher::BoxWatcherGuard;
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
43pub enum LayoutDirection {
44 #[default]
46 LeftToRight,
47 RightToLeft,
49}
50
51#[doc(hidden)]
53#[derive(Clone, Debug)]
54pub struct AutomaticLayoutDirection(pub nami::Computed<LayoutDirection>);
55
56impl LayoutDirection {
57 #[must_use]
59 pub const fn is_right_to_left(self) -> bool {
60 matches!(self, Self::RightToLeft)
61 }
62}
63
64#[must_use]
69pub fn layout_direction(environment: &crate::Environment) -> nami::Computed<LayoutDirection> {
70 if let Some(direction) = environment.get::<LayoutDirection>() {
71 return nami::Computed::constant(*direction);
72 }
73 if let Some(direction) = environment.get::<nami::Binding<LayoutDirection>>() {
74 return direction.clone().into();
75 }
76 if let Some(direction) = environment.get::<nami::Computed<LayoutDirection>>() {
77 return direction.clone();
78 }
79 environment.get::<AutomaticLayoutDirection>().map_or_else(
80 || nami::Computed::constant(LayoutDirection::default()),
81 |direction| direction.0.clone(),
82 )
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
91pub enum StretchAxis {
92 #[default]
94 None,
95 Horizontal,
97 Vertical,
99 Both,
101 MainAxis,
105 CrossAxis,
108}
109
110impl StretchAxis {
111 #[must_use]
113 pub const fn stretches_horizontal(&self) -> bool {
114 matches!(self, Self::Horizontal | Self::Both)
115 }
116
117 #[must_use]
119 pub const fn stretches_vertical(&self) -> bool {
120 matches!(self, Self::Vertical | Self::Both)
121 }
122
123 #[must_use]
125 pub const fn stretches_any(&self) -> bool {
126 !matches!(self, Self::None)
127 }
128}
129
130#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
137pub struct LayoutPriority(i32);
138
139impl LayoutPriority {
140 #[must_use]
142 pub const fn new(priority: i32) -> Self {
143 Self(priority)
144 }
145
146 #[must_use]
148 pub const fn get(self) -> i32 {
149 self.0
150 }
151}
152
153impl crate::components::metadata::MetadataKey for LayoutPriority {}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
161pub struct AlignmentKeyId {
162 low: u64,
163 high: u64,
164}
165
166impl AlignmentKeyId {
167 #[must_use]
169 pub const fn new(low: u64, high: u64) -> Self {
170 Self { low, high }
171 }
172
173 #[must_use]
175 pub const fn low(self) -> u64 {
176 self.low
177 }
178
179 #[must_use]
181 pub const fn high(self) -> u64 {
182 self.high
183 }
184
185 #[must_use]
187 pub const fn from_name(name: &str) -> Self {
188 let hash = fnv1a_128(name.as_bytes());
189 let bytes = hash.to_le_bytes();
190 Self {
191 low: u64::from_le_bytes([
192 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
193 ]),
194 high: u64::from_le_bytes([
195 bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
196 bytes[15],
197 ]),
198 }
199 }
200}
201
202const fn fnv1a_128(bytes: &[u8]) -> u128 {
203 const FNV_OFFSET: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
204 const FNV_PRIME: u128 = 0x0000_0000_0100_0000_0000_0000_0000_013b;
205
206 let mut hash = FNV_OFFSET;
207 let mut i = 0;
208 while i < bytes.len() {
209 hash ^= bytes[i] as u128;
210 hash = hash.wrapping_mul(FNV_PRIME);
211 i += 1;
212 }
213 hash
214}
215
216#[derive(Clone, Copy)]
217pub struct HorizontalAlignment {
219 stable_id: AlignmentKeyId,
220 default_value: fn(&ViewDimensions) -> f32,
221}
222
223impl HorizontalAlignment {
224 #[allow(non_upper_case_globals)]
226 pub const Leading: Self = Self {
227 stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.leading"),
228 default_value: leading_alignment_default,
229 };
230
231 #[allow(non_upper_case_globals)]
233 pub const Center: Self = Self {
234 stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.center"),
235 default_value: center_horizontal_alignment_default,
236 };
237
238 #[allow(non_upper_case_globals)]
240 pub const Trailing: Self = Self {
241 stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.trailing"),
242 default_value: trailing_alignment_default,
243 };
244
245 #[must_use]
246 pub const fn stable_id(self) -> AlignmentKeyId {
248 self.stable_id
249 }
250
251 #[must_use]
252 pub(crate) fn default_value(self, dimensions: &ViewDimensions) -> f32 {
253 (self.default_value)(dimensions)
254 }
255}
256
257impl Default for HorizontalAlignment {
258 fn default() -> Self {
259 Self::Center
260 }
261}
262
263impl PartialEq for HorizontalAlignment {
264 fn eq(&self, other: &Self) -> bool {
265 self.stable_id == other.stable_id
266 }
267}
268
269impl Eq for HorizontalAlignment {}
270
271impl Debug for HorizontalAlignment {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 f.debug_struct("HorizontalAlignment")
274 .field("stable_id", &self.stable_id)
275 .finish_non_exhaustive()
276 }
277}
278
279#[derive(Clone, Copy)]
280pub struct VerticalAlignment {
282 stable_id: AlignmentKeyId,
283 default_value: fn(&ViewDimensions) -> f32,
284}
285
286impl VerticalAlignment {
287 #[allow(non_upper_case_globals)]
289 pub const Top: Self = Self {
290 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.top"),
291 default_value: top_alignment_default,
292 };
293
294 #[allow(non_upper_case_globals)]
296 pub const Center: Self = Self {
297 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.center"),
298 default_value: center_vertical_alignment_default,
299 };
300
301 #[allow(non_upper_case_globals)]
303 pub const Bottom: Self = Self {
304 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.bottom"),
305 default_value: bottom_alignment_default,
306 };
307
308 #[allow(non_upper_case_globals)]
310 pub const FirstBaseline: Self = Self {
311 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.first_baseline"),
312 default_value: first_baseline_alignment_default,
313 };
314
315 #[allow(non_upper_case_globals)]
317 pub const LastBaseline: Self = Self {
318 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.last_baseline"),
319 default_value: last_baseline_alignment_default,
320 };
321
322 #[must_use]
323 pub const fn stable_id(self) -> AlignmentKeyId {
325 self.stable_id
326 }
327
328 #[must_use]
329 pub(crate) fn default_value(self, dimensions: &ViewDimensions) -> f32 {
330 (self.default_value)(dimensions)
331 }
332}
333
334impl Default for VerticalAlignment {
335 fn default() -> Self {
336 Self::Center
337 }
338}
339
340impl PartialEq for VerticalAlignment {
341 fn eq(&self, other: &Self) -> bool {
342 self.stable_id == other.stable_id
343 }
344}
345
346impl Eq for VerticalAlignment {}
347
348impl Debug for VerticalAlignment {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 f.debug_struct("VerticalAlignment")
351 .field("stable_id", &self.stable_id)
352 .finish_non_exhaustive()
353 }
354}
355
356#[derive(Clone, Copy, Debug, PartialEq, Eq)]
358pub struct Alignment {
359 horizontal: HorizontalAlignment,
360 vertical: VerticalAlignment,
361}
362
363impl Alignment {
364 #[allow(non_upper_case_globals)]
366 pub const Top: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Top);
367
368 #[allow(non_upper_case_globals)]
370 pub const TopLeading: Self = Self::new(HorizontalAlignment::Leading, VerticalAlignment::Top);
371
372 #[allow(non_upper_case_globals)]
374 pub const TopTrailing: Self = Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Top);
375
376 #[allow(non_upper_case_globals)]
378 pub const Center: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Center);
379
380 #[allow(non_upper_case_globals)]
382 pub const Leading: Self = Self::new(HorizontalAlignment::Leading, VerticalAlignment::Center);
383
384 #[allow(non_upper_case_globals)]
386 pub const Trailing: Self = Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Center);
387
388 #[allow(non_upper_case_globals)]
390 pub const Bottom: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Bottom);
391
392 #[allow(non_upper_case_globals)]
394 pub const BottomLeading: Self =
395 Self::new(HorizontalAlignment::Leading, VerticalAlignment::Bottom);
396
397 #[allow(non_upper_case_globals)]
399 pub const BottomTrailing: Self =
400 Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Bottom);
401
402 #[must_use]
404 pub const fn new(horizontal: HorizontalAlignment, vertical: VerticalAlignment) -> Self {
405 Self {
406 horizontal,
407 vertical,
408 }
409 }
410
411 #[must_use]
413 pub const fn horizontal(&self) -> HorizontalAlignment {
414 self.horizontal
415 }
416
417 #[must_use]
419 pub const fn vertical(&self) -> VerticalAlignment {
420 self.vertical
421 }
422}
423
424impl Default for Alignment {
425 fn default() -> Self {
426 Self::Center
427 }
428}
429
430#[derive(Clone, Debug, PartialEq, Default)]
432pub struct ViewDimensions {
433 pub size: Size,
435 explicit_horizontal_guides: Vec<(HorizontalAlignment, f32)>,
436 explicit_vertical_guides: Vec<(VerticalAlignment, f32)>,
437}
438
439impl ViewDimensions {
440 #[must_use]
442 pub const fn new(size: Size) -> Self {
443 Self {
444 size,
445 explicit_horizontal_guides: Vec::new(),
446 explicit_vertical_guides: Vec::new(),
447 }
448 }
449
450 #[must_use]
452 pub fn horizontal(&self, alignment: HorizontalAlignment) -> f32 {
453 self.explicit_horizontal(alignment)
454 .unwrap_or_else(|| alignment.default_value(self))
455 }
456
457 #[must_use]
459 pub fn vertical(&self, alignment: VerticalAlignment) -> f32 {
460 self.explicit_vertical(alignment)
461 .unwrap_or_else(|| alignment.default_value(self))
462 }
463
464 #[must_use]
466 pub fn explicit_horizontal(&self, alignment: HorizontalAlignment) -> Option<f32> {
467 self.explicit_horizontal_guides
468 .iter()
469 .rev()
470 .find_map(|(guide, value)| (*guide == alignment).then_some(*value))
471 }
472
473 #[must_use]
475 pub fn explicit_vertical(&self, alignment: VerticalAlignment) -> Option<f32> {
476 self.explicit_vertical_guides
477 .iter()
478 .rev()
479 .find_map(|(guide, value)| (*guide == alignment).then_some(*value))
480 }
481
482 pub fn explicit_horizontal_guides(
484 &self,
485 ) -> impl Iterator<Item = (HorizontalAlignment, f32)> + '_ {
486 self.explicit_horizontal_guides.iter().copied()
487 }
488
489 pub fn explicit_vertical_guides(&self) -> impl Iterator<Item = (VerticalAlignment, f32)> + '_ {
491 self.explicit_vertical_guides.iter().copied()
492 }
493
494 pub fn set_horizontal(&mut self, alignment: HorizontalAlignment, value: f32) {
496 self.explicit_horizontal_guides.push((alignment, value));
497 }
498
499 pub fn set_vertical(&mut self, alignment: VerticalAlignment, value: f32) {
501 self.explicit_vertical_guides.push((alignment, value));
502 }
503
504 #[must_use]
506 pub fn with_horizontal(mut self, alignment: HorizontalAlignment, value: f32) -> Self {
507 self.set_horizontal(alignment, value);
508 self
509 }
510
511 #[must_use]
513 pub fn with_vertical(mut self, alignment: VerticalAlignment, value: f32) -> Self {
514 self.set_vertical(alignment, value);
515 self
516 }
517}
518
519#[derive(Clone, Copy)]
521pub struct PlacedSubview<'a> {
522 pub view: &'a dyn SubView,
524 pub frame: Rect,
526}
527
528impl Debug for PlacedSubview<'_> {
529 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530 f.debug_struct("PlacedSubview")
531 .field("frame", &self.frame)
532 .finish_non_exhaustive()
533 }
534}
535
536impl<'a> PlacedSubview<'a> {
537 #[must_use]
539 pub const fn new(view: &'a dyn SubView, frame: Rect) -> Self {
540 Self { view, frame }
541 }
542
543 #[must_use]
545 pub fn dimensions(&self) -> ViewDimensions {
546 self.view.measure(ProposalSize::new(
547 Some(self.frame.width()),
548 Some(self.frame.height()),
549 ))
550 }
551
552 #[must_use]
554 pub fn horizontal(&self, alignment: HorizontalAlignment) -> f32 {
555 self.frame.x() + self.dimensions().horizontal(alignment)
556 }
557
558 #[must_use]
560 pub fn vertical(&self, alignment: VerticalAlignment) -> f32 {
561 self.frame.y() + self.dimensions().vertical(alignment)
562 }
563
564 #[must_use]
566 pub fn explicit_horizontal(&self, alignment: HorizontalAlignment) -> Option<f32> {
567 self.dimensions()
568 .explicit_horizontal(alignment)
569 .map(|value| self.frame.x() + value)
570 }
571
572 #[must_use]
574 pub fn explicit_vertical(&self, alignment: VerticalAlignment) -> Option<f32> {
575 self.dimensions()
576 .explicit_vertical(alignment)
577 .map(|value| self.frame.y() + value)
578 }
579}
580
581const fn leading_alignment_default(dimensions: &ViewDimensions) -> f32 {
582 let _ = dimensions;
583 0.0
584}
585
586const fn center_horizontal_alignment_default(dimensions: &ViewDimensions) -> f32 {
587 dimensions.size.width * 0.5
588}
589
590const fn trailing_alignment_default(dimensions: &ViewDimensions) -> f32 {
591 dimensions.size.width
592}
593
594const fn top_alignment_default(dimensions: &ViewDimensions) -> f32 {
595 let _ = dimensions;
596 0.0
597}
598
599const fn center_vertical_alignment_default(dimensions: &ViewDimensions) -> f32 {
600 dimensions.size.height * 0.5
601}
602
603const fn bottom_alignment_default(dimensions: &ViewDimensions) -> f32 {
604 dimensions.size.height
605}
606
607const fn first_baseline_alignment_default(dimensions: &ViewDimensions) -> f32 {
608 dimensions.size.height
609}
610
611const fn last_baseline_alignment_default(dimensions: &ViewDimensions) -> f32 {
612 dimensions.size.height
613}
614
615pub trait SubView {
650 #[must_use]
673 fn measure(&self, proposal: ProposalSize) -> ViewDimensions;
674
675 fn stretch_axis(&self) -> StretchAxis;
686
687 fn priority(&self) -> i32;
691}
692
693pub struct MemoizedSubView<'a> {
710 inner: &'a dyn SubView,
711 cache: core::cell::RefCell<[Option<(ProposalSize, ViewDimensions)>; MEMOIZED_PROPOSALS]>,
712}
713
714pub const MEMOIZED_PROPOSALS: usize = 4;
720
721impl<'a> MemoizedSubView<'a> {
722 #[must_use]
724 pub fn new(inner: &'a dyn SubView) -> Self {
725 Self {
726 inner,
727 cache: core::cell::RefCell::new([const { None }; MEMOIZED_PROPOSALS]),
728 }
729 }
730}
731
732impl Debug for MemoizedSubView<'_> {
733 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734 f.debug_struct("MemoizedSubView").finish_non_exhaustive()
735 }
736}
737
738const fn proposal_axis_bits(axis: Option<f32>) -> Option<u32> {
739 match axis {
740 Some(value) => Some(value.to_bits()),
741 None => None,
742 }
743}
744
745fn same_proposal(left: ProposalSize, right: ProposalSize) -> bool {
746 proposal_axis_bits(left.width) == proposal_axis_bits(right.width)
747 && proposal_axis_bits(left.height) == proposal_axis_bits(right.height)
748}
749
750impl SubView for MemoizedSubView<'_> {
751 fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
752 if let Some((_, dimensions)) = self
753 .cache
754 .borrow()
755 .iter()
756 .flatten()
757 .find(|(cached, _)| same_proposal(*cached, proposal))
758 {
759 return dimensions.clone();
760 }
761 let dimensions = self.inner.measure(proposal);
762 if let Some(slot) = self
763 .cache
764 .borrow_mut()
765 .iter_mut()
766 .find(|slot| slot.is_none())
767 {
768 *slot = Some((proposal, dimensions.clone()));
769 }
770 dimensions
771 }
772
773 fn stretch_axis(&self) -> StretchAxis {
774 self.inner.stretch_axis()
775 }
776
777 fn priority(&self) -> i32 {
778 self.inner.priority()
779 }
780}
781
782pub fn with_memoized_children<R>(
789 children: &[&dyn SubView],
790 pass: impl FnOnce(&[&dyn SubView]) -> R,
791) -> R {
792 let memoized: Vec<MemoizedSubView<'_>> =
793 children.iter().copied().map(MemoizedSubView::new).collect();
794 let refs: Vec<&dyn SubView> = memoized.iter().map(|child| child as &dyn SubView).collect();
795 pass(&refs)
796}
797
798#[doc(hidden)]
804pub type LayoutInvalidationCallback = Rc<dyn Fn() + 'static>;
805
806pub trait Layout: Debug + Any {
824 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size;
834
835 fn place(&self, bounds: Rect, children: &[&dyn SubView]) -> Vec<Rect>;
845
846 fn explicit_horizontal(
848 &self,
849 _alignment: HorizontalAlignment,
850 _bounds: Rect,
851 _children: &[PlacedSubview<'_>],
852 ) -> Option<f32> {
853 None
854 }
855
856 fn explicit_vertical(
858 &self,
859 _alignment: VerticalAlignment,
860 _bounds: Rect,
861 _children: &[PlacedSubview<'_>],
862 ) -> Option<f32> {
863 None
864 }
865
866 fn explicit_horizontal_alignments(&self) -> Vec<HorizontalAlignment> {
868 Vec::new()
869 }
870
871 fn explicit_vertical_alignments(&self) -> Vec<VerticalAlignment> {
873 Vec::new()
874 }
875
876 fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
893 let _ = children;
894 StretchAxis::None
895 }
896
897 #[doc(hidden)]
903 fn watch_invalidation(&self, _invalidate: LayoutInvalidationCallback) -> Vec<BoxWatcherGuard> {
904 Vec::new()
905 }
906}
907
908#[must_use]
914pub fn measure_layout(
915 layout: &dyn Layout,
916 proposal: ProposalSize,
917 children: &[&dyn SubView],
918) -> ViewDimensions {
919 with_memoized_children(children, |children| {
920 measure_layout_memoized(layout, proposal, children)
921 })
922}
923
924fn measure_layout_memoized(
925 layout: &dyn Layout,
926 proposal: ProposalSize,
927 children: &[&dyn SubView],
928) -> ViewDimensions {
929 let size = layout.size_that_fits(proposal, children);
930 let bounds = Rect::from_size(size);
931 let child_rects = layout.place(bounds, children);
932 let placed_subviews: Vec<PlacedSubview<'_>> = children
933 .iter()
934 .zip(child_rects.iter().copied())
935 .map(|(view, frame)| PlacedSubview::new(*view, frame))
936 .collect();
937
938 let mut dimensions = ViewDimensions::new(size);
939 let mut horizontal_keys = layout.explicit_horizontal_alignments();
940 let mut vertical_keys = layout.explicit_vertical_alignments();
941
942 for child in &placed_subviews {
943 let child_dimensions = child.dimensions();
944 for (alignment, _) in child_dimensions.explicit_horizontal_guides() {
945 if !horizontal_keys.contains(&alignment) {
946 horizontal_keys.push(alignment);
947 }
948 }
949 for (alignment, _) in child_dimensions.explicit_vertical_guides() {
950 if !vertical_keys.contains(&alignment) {
951 vertical_keys.push(alignment);
952 }
953 }
954 }
955
956 for alignment in horizontal_keys {
957 if let Some(value) = layout.explicit_horizontal(alignment, bounds, &placed_subviews) {
958 dimensions.set_horizontal(alignment, value);
959 }
960 }
961 for alignment in vertical_keys {
962 if let Some(value) = layout.explicit_vertical(alignment, bounds, &placed_subviews) {
963 dimensions.set_vertical(alignment, value);
964 }
965 }
966
967 dimensions
968}
969
970#[derive(Clone, Copy, Debug, PartialEq)]
976pub struct Rect {
977 origin: Point,
978 size: Size,
979}
980
981impl Rect {
982 #[must_use]
984 pub const fn new(origin: Point, size: Size) -> Self {
985 Self { origin, size }
986 }
987
988 #[must_use]
990 pub const fn from_size(size: Size) -> Self {
991 Self {
992 origin: Point::zero(),
993 size,
994 }
995 }
996
997 #[must_use]
999 pub const fn origin(&self) -> Point {
1000 self.origin
1001 }
1002
1003 #[must_use]
1005 pub const fn size(&self) -> &Size {
1006 &self.size
1007 }
1008
1009 #[must_use]
1011 pub const fn x(&self) -> f32 {
1012 self.origin.x
1013 }
1014
1015 #[must_use]
1017 pub const fn y(&self) -> f32 {
1018 self.origin.y
1019 }
1020
1021 #[must_use]
1023 pub const fn width(&self) -> f32 {
1024 self.size.width
1025 }
1026
1027 #[must_use]
1029 pub const fn height(&self) -> f32 {
1030 self.size.height
1031 }
1032
1033 #[must_use]
1035 pub const fn min_x(&self) -> f32 {
1036 self.origin.x
1037 }
1038
1039 #[must_use]
1041 pub const fn min_y(&self) -> f32 {
1042 self.origin.y
1043 }
1044
1045 #[must_use]
1047 pub const fn max_x(&self) -> f32 {
1048 self.origin.x + self.size.width
1049 }
1050
1051 #[must_use]
1053 pub const fn max_y(&self) -> f32 {
1054 self.origin.y + self.size.height
1055 }
1056
1057 #[must_use]
1059 pub const fn mid_x(&self) -> f32 {
1060 self.origin.x + self.size.width / 2.0
1061 }
1062
1063 #[must_use]
1065 pub const fn mid_y(&self) -> f32 {
1066 self.origin.y + self.size.height / 2.0
1067 }
1068
1069 #[must_use]
1071 pub const fn center(&self) -> Point {
1072 Point::new(self.mid_x(), self.mid_y())
1073 }
1074
1075 #[must_use]
1077 pub fn inset(&self, top: f32, bottom: f32, leading: f32, trailing: f32) -> Self {
1078 Self::new(
1079 Point::new(self.origin.x + leading, self.origin.y + top),
1080 Size::new(
1081 (self.size.width - leading - trailing).max(0.0),
1082 (self.size.height - top - bottom).max(0.0),
1083 ),
1084 )
1085 }
1086}
1087
1088#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
1094pub struct Size {
1095 pub width: f32,
1097 pub height: f32,
1099}
1100
1101impl Size {
1102 #[must_use]
1104 pub const fn new(width: f32, height: f32) -> Self {
1105 Self { width, height }
1106 }
1107
1108 #[must_use]
1110 pub const fn zero() -> Self {
1111 Self {
1112 width: 0.0,
1113 height: 0.0,
1114 }
1115 }
1116
1117 #[must_use]
1119 pub const fn is_zero(&self) -> bool {
1120 self.width == 0.0 && self.height == 0.0
1121 }
1122}
1123
1124#[derive(Clone, Copy, Debug, PartialEq, Default)]
1130pub struct Point {
1131 pub x: f32,
1133 pub y: f32,
1135}
1136
1137impl Point {
1138 #[must_use]
1140 pub const fn new(x: f32, y: f32) -> Self {
1141 Self { x, y }
1142 }
1143
1144 #[must_use]
1146 pub const fn zero() -> Self {
1147 Self { x: 0.0, y: 0.0 }
1148 }
1149}
1150
1151impl From<(f32, f32)> for Point {
1152 fn from((x, y): (f32, f32)) -> Self {
1153 Self { x, y }
1154 }
1155}
1156
1157impl From<[f32; 2]> for Point {
1158 fn from([x, y]: [f32; 2]) -> Self {
1159 Self { x, y }
1160 }
1161}
1162
1163impl From<(f32, f32)> for Size {
1164 fn from((width, height): (f32, f32)) -> Self {
1165 Self { width, height }
1166 }
1167}
1168
1169impl From<[f32; 2]> for Size {
1170 fn from([width, height]: [f32; 2]) -> Self {
1171 Self { width, height }
1172 }
1173}
1174
1175#[derive(Clone, Copy, Debug, PartialEq, Default)]
1185pub struct Vec2 {
1186 pub dx: f32,
1188 pub dy: f32,
1190}
1191
1192impl Vec2 {
1193 #[must_use]
1195 pub const fn new(dx: f32, dy: f32) -> Self {
1196 Self { dx, dy }
1197 }
1198
1199 pub const ZERO: Self = Self { dx: 0.0, dy: 0.0 };
1201}
1202
1203impl From<(f32, f32)> for Vec2 {
1204 fn from((dx, dy): (f32, f32)) -> Self {
1205 Self { dx, dy }
1206 }
1207}
1208
1209impl From<[f32; 2]> for Vec2 {
1210 fn from([dx, dy]: [f32; 2]) -> Self {
1211 Self { dx, dy }
1212 }
1213}
1214
1215#[derive(Debug, Clone, Copy, PartialEq, Default)]
1224pub struct UnitPoint {
1225 pub x: f32,
1227 pub y: f32,
1229}
1230
1231impl UnitPoint {
1232 pub const TOP_LEADING: Self = Self { x: 0.0, y: 0.0 };
1234 pub const TOP: Self = Self { x: 0.5, y: 0.0 };
1236 pub const TOP_TRAILING: Self = Self { x: 1.0, y: 0.0 };
1238 pub const LEADING: Self = Self { x: 0.0, y: 0.5 };
1240 pub const CENTER: Self = Self { x: 0.5, y: 0.5 };
1242 pub const TRAILING: Self = Self { x: 1.0, y: 0.5 };
1244 pub const BOTTOM_LEADING: Self = Self { x: 0.0, y: 1.0 };
1246 pub const BOTTOM: Self = Self { x: 0.5, y: 1.0 };
1248 pub const BOTTOM_TRAILING: Self = Self { x: 1.0, y: 1.0 };
1250
1251 #[must_use]
1253 pub const fn new(x: f32, y: f32) -> Self {
1254 Self { x, y }
1255 }
1256}
1257
1258impl From<(f32, f32)> for UnitPoint {
1259 fn from((x, y): (f32, f32)) -> Self {
1260 Self { x, y }
1261 }
1262}
1263
1264impl From<[f32; 2]> for UnitPoint {
1265 fn from([x, y]: [f32; 2]) -> Self {
1266 Self { x, y }
1267 }
1268}
1269
1270impl From<Alignment> for UnitPoint {
1271 fn from(alignment: Alignment) -> Self {
1272 let horizontal = alignment.horizontal();
1273 let vertical = alignment.vertical();
1274 if horizontal == HorizontalAlignment::Leading && vertical == VerticalAlignment::Top {
1275 Self::TOP_LEADING
1276 } else if horizontal == HorizontalAlignment::Trailing && vertical == VerticalAlignment::Top
1277 {
1278 Self::TOP_TRAILING
1279 } else if horizontal == HorizontalAlignment::Leading
1280 && vertical == VerticalAlignment::Bottom
1281 {
1282 Self::BOTTOM_LEADING
1283 } else if horizontal == HorizontalAlignment::Trailing
1284 && vertical == VerticalAlignment::Bottom
1285 {
1286 Self::BOTTOM_TRAILING
1287 } else if horizontal == HorizontalAlignment::Leading {
1288 Self::LEADING
1289 } else if horizontal == HorizontalAlignment::Trailing {
1290 Self::TRAILING
1291 } else if vertical == VerticalAlignment::Top {
1292 Self::TOP
1293 } else if vertical == VerticalAlignment::Bottom {
1294 Self::BOTTOM
1295 } else {
1296 Self::CENTER
1297 }
1298 }
1299}
1300
1301#[derive(Clone, Copy, Debug, PartialEq, Default)]
1318pub struct Affine2 {
1319 pub a: f32,
1321 pub b: f32,
1323 pub c: f32,
1325 pub d: f32,
1327 pub e: f32,
1329 pub f: f32,
1331}
1332
1333impl Affine2 {
1334 pub const IDENTITY: Self = Self {
1336 a: 1.0,
1337 b: 0.0,
1338 c: 0.0,
1339 d: 1.0,
1340 e: 0.0,
1341 f: 0.0,
1342 };
1343
1344 #[must_use]
1346 pub const fn new(
1347 scale_x: f32,
1348 shear_y: f32,
1349 shear_x: f32,
1350 scale_y: f32,
1351 translate_x: f32,
1352 translate_y: f32,
1353 ) -> Self {
1354 Self {
1355 a: scale_x,
1356 b: shear_y,
1357 c: shear_x,
1358 d: scale_y,
1359 e: translate_x,
1360 f: translate_y,
1361 }
1362 }
1363
1364 #[must_use]
1366 pub const fn translate(tx: f32, ty: f32) -> Self {
1367 Self {
1368 a: 1.0,
1369 b: 0.0,
1370 c: 0.0,
1371 d: 1.0,
1372 e: tx,
1373 f: ty,
1374 }
1375 }
1376
1377 #[must_use]
1379 pub const fn scale(sx: f32, sy: f32) -> Self {
1380 Self {
1381 a: sx,
1382 b: 0.0,
1383 c: 0.0,
1384 d: sy,
1385 e: 0.0,
1386 f: 0.0,
1387 }
1388 }
1389
1390 #[must_use]
1392 pub fn rotate(radians: f32) -> Self {
1393 let (s, c) = radians.sin_cos();
1394 Self {
1395 a: c,
1396 b: s,
1397 c: -s,
1398 d: c,
1399 e: 0.0,
1400 f: 0.0,
1401 }
1402 }
1403}
1404
1405impl From<[f32; 6]> for Affine2 {
1406 fn from(coefficients: [f32; 6]) -> Self {
1407 Self {
1408 a: coefficients[0],
1409 b: coefficients[1],
1410 c: coefficients[2],
1411 d: coefficients[3],
1412 e: coefficients[4],
1413 f: coefficients[5],
1414 }
1415 }
1416}
1417
1418impl From<Affine2> for [f32; 6] {
1419 fn from(t: Affine2) -> Self {
1420 [t.a, t.b, t.c, t.d, t.e, t.f]
1421 }
1422}
1423
1424macro_rules! impl_layout_signal_constant {
1425 ($($ty:ty),+ $(,)?) => {
1426 $(
1427 impl nami::Signal for $ty {
1428 type Output = Self;
1429 type Guard = ();
1430
1431 fn get(&self) -> Self::Output {
1432 *self
1433 }
1434
1435 fn watch(
1436 &self,
1437 _watcher: impl Fn(nami::watcher::Context<Self::Output>) + 'static,
1438 ) {
1439 }
1440 }
1441 )+
1442 };
1443}
1444
1445impl_layout_signal_constant!(
1446 LayoutDirection,
1447 Point,
1448 Size,
1449 Rect,
1450 Vec2,
1451 UnitPoint,
1452 Affine2,
1453 HorizontalAlignment,
1454 VerticalAlignment,
1455 Alignment
1456);
1457
1458#[derive(Clone, Copy, Debug, PartialEq, Default)]
1472pub struct ProposalSize {
1473 pub width: Option<f32>,
1475 pub height: Option<f32>,
1477}
1478
1479impl ProposalSize {
1480 #[must_use]
1482 pub fn new(width: impl Into<Option<f32>>, height: impl Into<Option<f32>>) -> Self {
1483 Self {
1484 width: width.into(),
1485 height: height.into(),
1486 }
1487 }
1488
1489 pub const UNSPECIFIED: Self = Self {
1491 width: None,
1492 height: None,
1493 };
1494
1495 pub const ZERO: Self = Self {
1497 width: Some(0.0),
1498 height: Some(0.0),
1499 };
1500
1501 pub const INFINITY: Self = Self {
1503 width: Some(f32::INFINITY),
1504 height: Some(f32::INFINITY),
1505 };
1506
1507 #[must_use]
1509 pub fn width_or(&self, default: f32) -> f32 {
1510 self.width.unwrap_or(default)
1511 }
1512
1513 #[must_use]
1515 pub fn height_or(&self, default: f32) -> f32 {
1516 self.height.unwrap_or(default)
1517 }
1518
1519 #[must_use]
1521 pub const fn with_width(self, width: Option<f32>) -> Self {
1522 Self {
1523 width,
1524 height: self.height,
1525 }
1526 }
1527
1528 #[must_use]
1530 pub const fn with_height(self, height: Option<f32>) -> Self {
1531 Self {
1532 width: self.width,
1533 height,
1534 }
1535 }
1536}
1537
1538#[cfg(test)]
1543#[allow(clippy::float_cmp)]
1544mod tests {
1545 use super::*;
1546
1547 #[test]
1548 fn test_rect_geometry() {
1549 let rect = Rect::new(Point::new(10.0, 20.0), Size::new(100.0, 50.0));
1550
1551 assert_eq!(rect.min_x(), 10.0);
1552 assert_eq!(rect.min_y(), 20.0);
1553 assert_eq!(rect.max_x(), 110.0);
1554 assert_eq!(rect.max_y(), 70.0);
1555 assert_eq!(rect.mid_x(), 60.0);
1556 assert_eq!(rect.mid_y(), 45.0);
1557 assert_eq!(rect.width(), 100.0);
1558 assert_eq!(rect.height(), 50.0);
1559 }
1560
1561 #[test]
1562 fn test_rect_inset() {
1563 let rect = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
1564 let inset = rect.inset(10.0, 10.0, 20.0, 20.0);
1565
1566 assert_eq!(inset.x(), 20.0);
1567 assert_eq!(inset.y(), 10.0);
1568 assert_eq!(inset.width(), 60.0);
1569 assert_eq!(inset.height(), 80.0);
1570 }
1571
1572 #[test]
1573 fn test_proposal_size() {
1574 let proposal = ProposalSize::new(Some(100.0), None);
1575
1576 assert_eq!(proposal.width_or(0.0), 100.0);
1577 assert_eq!(proposal.height_or(50.0), 50.0);
1578
1579 let with_height = proposal.with_height(Some(200.0));
1580 assert_eq!(with_height.width, Some(100.0));
1581 assert_eq!(with_height.height, Some(200.0));
1582 }
1583
1584 struct CountingSubView {
1586 measures: core::cell::Cell<usize>,
1587 }
1588
1589 impl SubView for CountingSubView {
1590 fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
1591 self.measures.set(self.measures.get() + 1);
1592 ViewDimensions::new(Size::new(proposal.width_or(10.0), proposal.height_or(20.0)))
1593 }
1594
1595 fn stretch_axis(&self) -> StretchAxis {
1596 StretchAxis::None
1597 }
1598
1599 fn priority(&self) -> i32 {
1600 0
1601 }
1602 }
1603
1604 #[test]
1605 fn memoized_subview_measures_once_per_distinct_proposal() {
1606 let inner = CountingSubView {
1607 measures: core::cell::Cell::new(0),
1608 };
1609 let memo = MemoizedSubView::new(&inner);
1610
1611 let ideal = ProposalSize::UNSPECIFIED;
1612 let constrained = ProposalSize::new(Some(80.0), None);
1613
1614 for _ in 0..5 {
1615 assert_eq!(memo.measure(ideal).size, Size::new(10.0, 20.0));
1616 assert_eq!(memo.measure(constrained).size, Size::new(80.0, 20.0));
1617 }
1618
1619 assert_eq!(
1620 inner.measures.get(),
1621 2,
1622 "ten probes over two distinct proposals must reach the child twice"
1623 );
1624 }
1625
1626 #[test]
1627 fn memoized_subview_forwards_priority_and_stretch() {
1628 let inner = CountingSubView {
1629 measures: core::cell::Cell::new(0),
1630 };
1631 let memo = MemoizedSubView::new(&inner);
1632
1633 assert_eq!(memo.priority(), inner.priority());
1634 assert_eq!(memo.stretch_axis(), inner.stretch_axis());
1635 }
1636}