1use core::any::Any;
32use core::fmt;
33use fmt::Debug;
34
35use alloc::{rc::Rc, vec::Vec};
36use nami::watcher::BoxWatcherGuard;
37use smallvec::SmallVec;
38
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub enum LayoutDirection {
45 #[default]
47 LeftToRight,
48 RightToLeft,
50}
51
52#[doc(hidden)]
54#[derive(Clone, Debug)]
55pub struct AutomaticLayoutDirection(pub nami::Computed<LayoutDirection>);
56
57impl LayoutDirection {
58 #[must_use]
60 pub const fn is_right_to_left(self) -> bool {
61 matches!(self, Self::RightToLeft)
62 }
63}
64
65#[must_use]
70pub fn layout_direction(environment: &crate::Environment) -> nami::Computed<LayoutDirection> {
71 if let Some(direction) = environment.get::<LayoutDirection>() {
72 return nami::Computed::constant(*direction);
73 }
74 if let Some(direction) = environment.get::<nami::Binding<LayoutDirection>>() {
75 return direction.clone().into();
76 }
77 if let Some(direction) = environment.get::<nami::Computed<LayoutDirection>>() {
78 return direction.clone();
79 }
80 environment.get::<AutomaticLayoutDirection>().map_or_else(
81 || nami::Computed::constant(LayoutDirection::default()),
82 |direction| direction.0.clone(),
83 )
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
92pub enum StretchAxis {
93 #[default]
95 None,
96 Horizontal,
98 Vertical,
100 Both,
102 MainAxis,
106 CrossAxis,
109}
110
111impl StretchAxis {
112 #[must_use]
114 pub const fn stretches_horizontal(&self) -> bool {
115 matches!(self, Self::Horizontal | Self::Both)
116 }
117
118 #[must_use]
120 pub const fn stretches_vertical(&self) -> bool {
121 matches!(self, Self::Vertical | Self::Both)
122 }
123
124 #[must_use]
126 pub const fn stretches_any(&self) -> bool {
127 !matches!(self, Self::None)
128 }
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
138pub struct LayoutPriority(i32);
139
140impl LayoutPriority {
141 #[must_use]
143 pub const fn new(priority: i32) -> Self {
144 Self(priority)
145 }
146
147 #[must_use]
149 pub const fn get(self) -> i32 {
150 self.0
151 }
152}
153
154impl crate::components::metadata::MetadataKey for LayoutPriority {}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
162pub struct AlignmentKeyId {
163 low: u64,
164 high: u64,
165}
166
167impl AlignmentKeyId {
168 #[must_use]
170 pub const fn new(low: u64, high: u64) -> Self {
171 Self { low, high }
172 }
173
174 #[must_use]
176 pub const fn low(self) -> u64 {
177 self.low
178 }
179
180 #[must_use]
182 pub const fn high(self) -> u64 {
183 self.high
184 }
185
186 #[must_use]
188 pub const fn from_name(name: &str) -> Self {
189 let hash = fnv1a_128(name.as_bytes());
190 let bytes = hash.to_le_bytes();
191 Self {
192 low: u64::from_le_bytes([
193 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
194 ]),
195 high: u64::from_le_bytes([
196 bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
197 bytes[15],
198 ]),
199 }
200 }
201}
202
203const fn fnv1a_128(bytes: &[u8]) -> u128 {
204 const FNV_OFFSET: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
205 const FNV_PRIME: u128 = 0x0000_0000_0100_0000_0000_0000_0000_013b;
206
207 let mut hash = FNV_OFFSET;
208 let mut i = 0;
209 while i < bytes.len() {
210 hash ^= bytes[i] as u128;
211 hash = hash.wrapping_mul(FNV_PRIME);
212 i += 1;
213 }
214 hash
215}
216
217#[derive(Clone, Copy)]
218pub struct HorizontalAlignment {
220 stable_id: AlignmentKeyId,
221 default_value: fn(&ViewDimensions) -> f32,
222}
223
224impl HorizontalAlignment {
225 #[allow(non_upper_case_globals)]
227 pub const Leading: Self = Self {
228 stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.leading"),
229 default_value: leading_alignment_default,
230 };
231
232 #[allow(non_upper_case_globals)]
234 pub const Center: Self = Self {
235 stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.center"),
236 default_value: center_horizontal_alignment_default,
237 };
238
239 #[allow(non_upper_case_globals)]
241 pub const Trailing: Self = Self {
242 stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.trailing"),
243 default_value: trailing_alignment_default,
244 };
245
246 #[must_use]
247 pub const fn stable_id(self) -> AlignmentKeyId {
249 self.stable_id
250 }
251
252 #[must_use]
253 pub(crate) fn default_value(self, dimensions: &ViewDimensions) -> f32 {
254 (self.default_value)(dimensions)
255 }
256}
257
258impl Default for HorizontalAlignment {
259 fn default() -> Self {
260 Self::Center
261 }
262}
263
264impl PartialEq for HorizontalAlignment {
265 fn eq(&self, other: &Self) -> bool {
266 self.stable_id == other.stable_id
267 }
268}
269
270impl Eq for HorizontalAlignment {}
271
272impl Debug for HorizontalAlignment {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 f.debug_struct("HorizontalAlignment")
275 .field("stable_id", &self.stable_id)
276 .finish_non_exhaustive()
277 }
278}
279
280#[derive(Clone, Copy)]
281pub struct VerticalAlignment {
283 stable_id: AlignmentKeyId,
284 default_value: fn(&ViewDimensions) -> f32,
285}
286
287impl VerticalAlignment {
288 #[allow(non_upper_case_globals)]
290 pub const Top: Self = Self {
291 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.top"),
292 default_value: top_alignment_default,
293 };
294
295 #[allow(non_upper_case_globals)]
297 pub const Center: Self = Self {
298 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.center"),
299 default_value: center_vertical_alignment_default,
300 };
301
302 #[allow(non_upper_case_globals)]
304 pub const Bottom: Self = Self {
305 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.bottom"),
306 default_value: bottom_alignment_default,
307 };
308
309 #[allow(non_upper_case_globals)]
311 pub const FirstBaseline: Self = Self {
312 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.first_baseline"),
313 default_value: first_baseline_alignment_default,
314 };
315
316 #[allow(non_upper_case_globals)]
318 pub const LastBaseline: Self = Self {
319 stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.last_baseline"),
320 default_value: last_baseline_alignment_default,
321 };
322
323 #[must_use]
324 pub const fn stable_id(self) -> AlignmentKeyId {
326 self.stable_id
327 }
328
329 #[must_use]
330 pub(crate) fn default_value(self, dimensions: &ViewDimensions) -> f32 {
331 (self.default_value)(dimensions)
332 }
333}
334
335impl Default for VerticalAlignment {
336 fn default() -> Self {
337 Self::Center
338 }
339}
340
341impl PartialEq for VerticalAlignment {
342 fn eq(&self, other: &Self) -> bool {
343 self.stable_id == other.stable_id
344 }
345}
346
347impl Eq for VerticalAlignment {}
348
349impl Debug for VerticalAlignment {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 f.debug_struct("VerticalAlignment")
352 .field("stable_id", &self.stable_id)
353 .finish_non_exhaustive()
354 }
355}
356
357#[derive(Clone, Copy, Debug, PartialEq, Eq)]
359pub struct Alignment {
360 horizontal: HorizontalAlignment,
361 vertical: VerticalAlignment,
362}
363
364impl Alignment {
365 #[allow(non_upper_case_globals)]
367 pub const Top: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Top);
368
369 #[allow(non_upper_case_globals)]
371 pub const TopLeading: Self = Self::new(HorizontalAlignment::Leading, VerticalAlignment::Top);
372
373 #[allow(non_upper_case_globals)]
375 pub const TopTrailing: Self = Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Top);
376
377 #[allow(non_upper_case_globals)]
379 pub const Center: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Center);
380
381 #[allow(non_upper_case_globals)]
383 pub const Leading: Self = Self::new(HorizontalAlignment::Leading, VerticalAlignment::Center);
384
385 #[allow(non_upper_case_globals)]
387 pub const Trailing: Self = Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Center);
388
389 #[allow(non_upper_case_globals)]
391 pub const Bottom: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Bottom);
392
393 #[allow(non_upper_case_globals)]
395 pub const BottomLeading: Self =
396 Self::new(HorizontalAlignment::Leading, VerticalAlignment::Bottom);
397
398 #[allow(non_upper_case_globals)]
400 pub const BottomTrailing: Self =
401 Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Bottom);
402
403 #[must_use]
405 pub const fn new(horizontal: HorizontalAlignment, vertical: VerticalAlignment) -> Self {
406 Self {
407 horizontal,
408 vertical,
409 }
410 }
411
412 #[must_use]
414 pub const fn horizontal(&self) -> HorizontalAlignment {
415 self.horizontal
416 }
417
418 #[must_use]
420 pub const fn vertical(&self) -> VerticalAlignment {
421 self.vertical
422 }
423}
424
425impl Default for Alignment {
426 fn default() -> Self {
427 Self::Center
428 }
429}
430
431#[derive(Clone, Debug, PartialEq, Default)]
433pub struct ViewDimensions {
434 pub size: Size,
436 explicit_horizontal_guides: Vec<(HorizontalAlignment, f32)>,
437 explicit_vertical_guides: Vec<(VerticalAlignment, f32)>,
438}
439
440impl ViewDimensions {
441 #[must_use]
443 pub const fn new(size: Size) -> Self {
444 Self {
445 size,
446 explicit_horizontal_guides: Vec::new(),
447 explicit_vertical_guides: Vec::new(),
448 }
449 }
450
451 #[must_use]
453 pub fn horizontal(&self, alignment: HorizontalAlignment) -> f32 {
454 self.explicit_horizontal(alignment)
455 .unwrap_or_else(|| alignment.default_value(self))
456 }
457
458 #[must_use]
460 pub fn vertical(&self, alignment: VerticalAlignment) -> f32 {
461 self.explicit_vertical(alignment)
462 .unwrap_or_else(|| alignment.default_value(self))
463 }
464
465 #[must_use]
467 pub fn explicit_horizontal(&self, alignment: HorizontalAlignment) -> Option<f32> {
468 self.explicit_horizontal_guides
469 .iter()
470 .rev()
471 .find_map(|(guide, value)| (*guide == alignment).then_some(*value))
472 }
473
474 #[must_use]
476 pub fn explicit_vertical(&self, alignment: VerticalAlignment) -> Option<f32> {
477 self.explicit_vertical_guides
478 .iter()
479 .rev()
480 .find_map(|(guide, value)| (*guide == alignment).then_some(*value))
481 }
482
483 pub fn explicit_horizontal_guides(
485 &self,
486 ) -> impl Iterator<Item = (HorizontalAlignment, f32)> + '_ {
487 self.explicit_horizontal_guides.iter().copied()
488 }
489
490 pub fn explicit_vertical_guides(&self) -> impl Iterator<Item = (VerticalAlignment, f32)> + '_ {
492 self.explicit_vertical_guides.iter().copied()
493 }
494
495 pub fn set_horizontal(&mut self, alignment: HorizontalAlignment, value: f32) {
497 self.explicit_horizontal_guides.push((alignment, value));
498 }
499
500 pub fn set_vertical(&mut self, alignment: VerticalAlignment, value: f32) {
502 self.explicit_vertical_guides.push((alignment, value));
503 }
504
505 #[must_use]
507 pub fn with_horizontal(mut self, alignment: HorizontalAlignment, value: f32) -> Self {
508 self.set_horizontal(alignment, value);
509 self
510 }
511
512 #[must_use]
514 pub fn with_vertical(mut self, alignment: VerticalAlignment, value: f32) -> Self {
515 self.set_vertical(alignment, value);
516 self
517 }
518}
519
520#[derive(Clone, Copy, Debug, PartialEq)]
522pub struct SubviewPlacement {
523 pub frame: Rect,
525 pub proposal: ProposalSize,
527}
528
529impl SubviewPlacement {
530 #[must_use]
532 pub const fn new(frame: Rect, proposal: ProposalSize) -> Self {
533 Self { frame, proposal }
534 }
535}
536
537#[derive(Clone, Copy)]
539pub struct PlacedSubview<'a> {
540 pub view: &'a dyn SubView,
542 pub frame: Rect,
544 pub proposal: ProposalSize,
546}
547
548impl Debug for PlacedSubview<'_> {
549 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550 f.debug_struct("PlacedSubview")
551 .field("frame", &self.frame)
552 .field("proposal", &self.proposal)
553 .finish_non_exhaustive()
554 }
555}
556
557impl<'a> PlacedSubview<'a> {
558 #[must_use]
560 pub const fn new(view: &'a dyn SubView, placement: SubviewPlacement) -> Self {
561 Self {
562 view,
563 frame: placement.frame,
564 proposal: placement.proposal,
565 }
566 }
567
568 #[must_use]
570 pub fn dimensions(&self) -> ViewDimensions {
571 self.view.measure(self.proposal)
572 }
573
574 #[must_use]
576 pub fn horizontal(&self, alignment: HorizontalAlignment) -> f32 {
577 self.frame.x() + self.dimensions().horizontal(alignment)
578 }
579
580 #[must_use]
582 pub fn vertical(&self, alignment: VerticalAlignment) -> f32 {
583 self.frame.y() + self.dimensions().vertical(alignment)
584 }
585
586 #[must_use]
588 pub fn explicit_horizontal(&self, alignment: HorizontalAlignment) -> Option<f32> {
589 self.dimensions()
590 .explicit_horizontal(alignment)
591 .map(|value| self.frame.x() + value)
592 }
593
594 #[must_use]
596 pub fn explicit_vertical(&self, alignment: VerticalAlignment) -> Option<f32> {
597 self.dimensions()
598 .explicit_vertical(alignment)
599 .map(|value| self.frame.y() + value)
600 }
601}
602
603const fn leading_alignment_default(dimensions: &ViewDimensions) -> f32 {
604 let _ = dimensions;
605 0.0
606}
607
608const fn center_horizontal_alignment_default(dimensions: &ViewDimensions) -> f32 {
609 dimensions.size.width * 0.5
610}
611
612const fn trailing_alignment_default(dimensions: &ViewDimensions) -> f32 {
613 dimensions.size.width
614}
615
616const fn top_alignment_default(dimensions: &ViewDimensions) -> f32 {
617 let _ = dimensions;
618 0.0
619}
620
621const fn center_vertical_alignment_default(dimensions: &ViewDimensions) -> f32 {
622 dimensions.size.height * 0.5
623}
624
625const fn bottom_alignment_default(dimensions: &ViewDimensions) -> f32 {
626 dimensions.size.height
627}
628
629const fn first_baseline_alignment_default(dimensions: &ViewDimensions) -> f32 {
630 dimensions.size.height
631}
632
633const fn last_baseline_alignment_default(dimensions: &ViewDimensions) -> f32 {
634 dimensions.size.height
635}
636
637pub trait SubView {
672 #[must_use]
695 fn measure(&self, proposal: ProposalSize) -> ViewDimensions;
696
697 fn stretch_axis(&self) -> StretchAxis;
708
709 fn priority(&self) -> i32;
713}
714
715pub struct MemoizedSubView<'a> {
732 inner: &'a dyn SubView,
733 cache: core::cell::RefCell<[Option<(ProposalSize, ViewDimensions)>; MEMOIZED_PROPOSALS]>,
734}
735
736pub const MEMOIZED_PROPOSALS: usize = 4;
742
743impl<'a> MemoizedSubView<'a> {
744 #[must_use]
746 pub fn new(inner: &'a dyn SubView) -> Self {
747 Self {
748 inner,
749 cache: core::cell::RefCell::new([const { None }; MEMOIZED_PROPOSALS]),
750 }
751 }
752}
753
754impl Debug for MemoizedSubView<'_> {
755 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
756 f.debug_struct("MemoizedSubView").finish_non_exhaustive()
757 }
758}
759
760const fn proposal_axis_bits(axis: Option<f32>) -> Option<u32> {
761 match axis {
762 Some(value) => Some(value.to_bits()),
763 None => None,
764 }
765}
766
767fn same_proposal(left: ProposalSize, right: ProposalSize) -> bool {
768 proposal_axis_bits(left.width) == proposal_axis_bits(right.width)
769 && proposal_axis_bits(left.height) == proposal_axis_bits(right.height)
770}
771
772impl SubView for MemoizedSubView<'_> {
773 fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
774 if let Some((_, dimensions)) = self
775 .cache
776 .borrow()
777 .iter()
778 .flatten()
779 .find(|(cached, _)| same_proposal(*cached, proposal))
780 {
781 return dimensions.clone();
782 }
783 let dimensions = self.inner.measure(proposal);
784 if let Some(slot) = self
785 .cache
786 .borrow_mut()
787 .iter_mut()
788 .find(|slot| slot.is_none())
789 {
790 *slot = Some((proposal, dimensions.clone()));
791 }
792 dimensions
793 }
794
795 fn stretch_axis(&self) -> StretchAxis {
796 self.inner.stretch_axis()
797 }
798
799 fn priority(&self) -> i32 {
800 self.inner.priority()
801 }
802}
803
804pub fn with_memoized_children<R>(
811 children: &[&dyn SubView],
812 pass: impl FnOnce(&[&dyn SubView]) -> R,
813) -> R {
814 let memoized: SmallVec<[MemoizedSubView<'_>; 4]> =
815 children.iter().copied().map(MemoizedSubView::new).collect();
816 let refs: SmallVec<[&dyn SubView; 4]> =
817 memoized.iter().map(|child| child as &dyn SubView).collect();
818 pass(&refs)
819}
820
821#[doc(hidden)]
827pub type LayoutInvalidationCallback = Rc<dyn Fn() + 'static>;
828
829pub trait Layout: Debug + Any {
847 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size;
857
858 fn place(
873 &self,
874 bounds: Rect,
875 proposal: ProposalSize,
876 children: &[&dyn SubView],
877 ) -> Vec<SubviewPlacement>;
878
879 fn explicit_horizontal(
881 &self,
882 _alignment: HorizontalAlignment,
883 _bounds: Rect,
884 _children: &[PlacedSubview<'_>],
885 ) -> Option<f32> {
886 None
887 }
888
889 fn explicit_vertical(
891 &self,
892 _alignment: VerticalAlignment,
893 _bounds: Rect,
894 _children: &[PlacedSubview<'_>],
895 ) -> Option<f32> {
896 None
897 }
898
899 fn explicit_horizontal_alignments(&self) -> Vec<HorizontalAlignment> {
901 Vec::new()
902 }
903
904 fn explicit_vertical_alignments(&self) -> Vec<VerticalAlignment> {
906 Vec::new()
907 }
908
909 fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
926 let _ = children;
927 StretchAxis::None
928 }
929
930 #[doc(hidden)]
936 fn watch_invalidation(&self, _invalidate: LayoutInvalidationCallback) -> Vec<BoxWatcherGuard> {
937 Vec::new()
938 }
939}
940
941#[must_use]
952pub fn measure_layout(
953 layout: &dyn Layout,
954 proposal: ProposalSize,
955 children: &[&dyn SubView],
956) -> ViewDimensions {
957 with_memoized_children(children, |children| {
958 measure_layout_memoized(layout, proposal, children)
959 })
960}
961
962fn measure_layout_memoized(
963 layout: &dyn Layout,
964 proposal: ProposalSize,
965 children: &[&dyn SubView],
966) -> ViewDimensions {
967 let size = layout.size_that_fits(proposal, children);
968 if size.width.is_infinite() || size.height.is_infinite() {
969 return ViewDimensions::new(size);
970 }
971 let bounds = Rect::from_size(size);
972 let child_placements = layout.place(bounds, proposal, children);
973 let placed_subviews: SmallVec<[PlacedSubview<'_>; 4]> = children
974 .iter()
975 .zip(child_placements.iter().copied())
976 .map(|(view, placement)| PlacedSubview::new(*view, placement))
977 .collect();
978
979 let mut dimensions = ViewDimensions::new(size);
980 let mut horizontal_keys = layout.explicit_horizontal_alignments();
981 let mut vertical_keys = layout.explicit_vertical_alignments();
982
983 for child in &placed_subviews {
984 let child_dimensions = child.dimensions();
985 for (alignment, _) in child_dimensions.explicit_horizontal_guides() {
986 if !horizontal_keys.contains(&alignment) {
987 horizontal_keys.push(alignment);
988 }
989 }
990 for (alignment, _) in child_dimensions.explicit_vertical_guides() {
991 if !vertical_keys.contains(&alignment) {
992 vertical_keys.push(alignment);
993 }
994 }
995 }
996
997 for alignment in horizontal_keys {
998 if let Some(value) = layout.explicit_horizontal(alignment, bounds, &placed_subviews) {
999 dimensions.set_horizontal(alignment, value);
1000 }
1001 }
1002 for alignment in vertical_keys {
1003 if let Some(value) = layout.explicit_vertical(alignment, bounds, &placed_subviews) {
1004 dimensions.set_vertical(alignment, value);
1005 }
1006 }
1007
1008 dimensions
1009}
1010
1011#[derive(Clone, Copy, Debug, PartialEq)]
1017pub struct Rect {
1018 origin: Point,
1019 size: Size,
1020}
1021
1022impl Rect {
1023 #[must_use]
1025 pub const fn new(origin: Point, size: Size) -> Self {
1026 Self { origin, size }
1027 }
1028
1029 #[must_use]
1031 pub const fn from_size(size: Size) -> Self {
1032 Self {
1033 origin: Point::zero(),
1034 size,
1035 }
1036 }
1037
1038 #[must_use]
1040 pub const fn origin(&self) -> Point {
1041 self.origin
1042 }
1043
1044 #[must_use]
1046 pub const fn size(&self) -> &Size {
1047 &self.size
1048 }
1049
1050 #[must_use]
1052 pub const fn x(&self) -> f32 {
1053 self.origin.x
1054 }
1055
1056 #[must_use]
1058 pub const fn y(&self) -> f32 {
1059 self.origin.y
1060 }
1061
1062 #[must_use]
1064 pub const fn width(&self) -> f32 {
1065 self.size.width
1066 }
1067
1068 #[must_use]
1070 pub const fn height(&self) -> f32 {
1071 self.size.height
1072 }
1073
1074 #[must_use]
1076 pub const fn min_x(&self) -> f32 {
1077 self.origin.x
1078 }
1079
1080 #[must_use]
1082 pub const fn min_y(&self) -> f32 {
1083 self.origin.y
1084 }
1085
1086 #[must_use]
1088 pub const fn max_x(&self) -> f32 {
1089 self.origin.x + self.size.width
1090 }
1091
1092 #[must_use]
1094 pub const fn max_y(&self) -> f32 {
1095 self.origin.y + self.size.height
1096 }
1097
1098 #[must_use]
1100 pub const fn mid_x(&self) -> f32 {
1101 self.origin.x + self.size.width / 2.0
1102 }
1103
1104 #[must_use]
1106 pub const fn mid_y(&self) -> f32 {
1107 self.origin.y + self.size.height / 2.0
1108 }
1109
1110 #[must_use]
1112 pub const fn center(&self) -> Point {
1113 Point::new(self.mid_x(), self.mid_y())
1114 }
1115
1116 #[must_use]
1118 pub fn inset(&self, top: f32, bottom: f32, leading: f32, trailing: f32) -> Self {
1119 Self::new(
1120 Point::new(self.origin.x + leading, self.origin.y + top),
1121 Size::new(
1122 (self.size.width - leading - trailing).max(0.0),
1123 (self.size.height - top - bottom).max(0.0),
1124 ),
1125 )
1126 }
1127}
1128
1129#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
1135pub struct Size {
1136 pub width: f32,
1138 pub height: f32,
1140}
1141
1142impl Size {
1143 #[must_use]
1145 pub const fn new(width: f32, height: f32) -> Self {
1146 Self { width, height }
1147 }
1148
1149 #[must_use]
1151 pub const fn zero() -> Self {
1152 Self {
1153 width: 0.0,
1154 height: 0.0,
1155 }
1156 }
1157
1158 #[must_use]
1160 pub const fn is_zero(&self) -> bool {
1161 self.width == 0.0 && self.height == 0.0
1162 }
1163}
1164
1165#[derive(Clone, Copy, Debug, PartialEq, Default)]
1171pub struct Point {
1172 pub x: f32,
1174 pub y: f32,
1176}
1177
1178impl Point {
1179 #[must_use]
1181 pub const fn new(x: f32, y: f32) -> Self {
1182 Self { x, y }
1183 }
1184
1185 #[must_use]
1187 pub const fn zero() -> Self {
1188 Self { x: 0.0, y: 0.0 }
1189 }
1190}
1191
1192impl From<(f32, f32)> for Point {
1193 fn from((x, y): (f32, f32)) -> Self {
1194 Self { x, y }
1195 }
1196}
1197
1198impl From<[f32; 2]> for Point {
1199 fn from([x, y]: [f32; 2]) -> Self {
1200 Self { x, y }
1201 }
1202}
1203
1204impl From<(f32, f32)> for Size {
1205 fn from((width, height): (f32, f32)) -> Self {
1206 Self { width, height }
1207 }
1208}
1209
1210impl From<[f32; 2]> for Size {
1211 fn from([width, height]: [f32; 2]) -> Self {
1212 Self { width, height }
1213 }
1214}
1215
1216#[derive(Clone, Copy, Debug, PartialEq, Default)]
1226pub struct Vec2 {
1227 pub dx: f32,
1229 pub dy: f32,
1231}
1232
1233impl Vec2 {
1234 #[must_use]
1236 pub const fn new(dx: f32, dy: f32) -> Self {
1237 Self { dx, dy }
1238 }
1239
1240 pub const ZERO: Self = Self { dx: 0.0, dy: 0.0 };
1242}
1243
1244impl From<(f32, f32)> for Vec2 {
1245 fn from((dx, dy): (f32, f32)) -> Self {
1246 Self { dx, dy }
1247 }
1248}
1249
1250impl From<[f32; 2]> for Vec2 {
1251 fn from([dx, dy]: [f32; 2]) -> Self {
1252 Self { dx, dy }
1253 }
1254}
1255
1256#[derive(Debug, Clone, Copy, PartialEq, Default)]
1265pub struct UnitPoint {
1266 pub x: f32,
1268 pub y: f32,
1270}
1271
1272impl UnitPoint {
1273 pub const TOP_LEADING: Self = Self { x: 0.0, y: 0.0 };
1275 pub const TOP: Self = Self { x: 0.5, y: 0.0 };
1277 pub const TOP_TRAILING: Self = Self { x: 1.0, y: 0.0 };
1279 pub const LEADING: Self = Self { x: 0.0, y: 0.5 };
1281 pub const CENTER: Self = Self { x: 0.5, y: 0.5 };
1283 pub const TRAILING: Self = Self { x: 1.0, y: 0.5 };
1285 pub const BOTTOM_LEADING: Self = Self { x: 0.0, y: 1.0 };
1287 pub const BOTTOM: Self = Self { x: 0.5, y: 1.0 };
1289 pub const BOTTOM_TRAILING: Self = Self { x: 1.0, y: 1.0 };
1291
1292 #[must_use]
1294 pub const fn new(x: f32, y: f32) -> Self {
1295 Self { x, y }
1296 }
1297}
1298
1299impl From<(f32, f32)> for UnitPoint {
1300 fn from((x, y): (f32, f32)) -> Self {
1301 Self { x, y }
1302 }
1303}
1304
1305impl From<[f32; 2]> for UnitPoint {
1306 fn from([x, y]: [f32; 2]) -> Self {
1307 Self { x, y }
1308 }
1309}
1310
1311impl From<Alignment> for UnitPoint {
1312 fn from(alignment: Alignment) -> Self {
1313 let horizontal = alignment.horizontal();
1314 let vertical = alignment.vertical();
1315 if horizontal == HorizontalAlignment::Leading && vertical == VerticalAlignment::Top {
1316 Self::TOP_LEADING
1317 } else if horizontal == HorizontalAlignment::Trailing && vertical == VerticalAlignment::Top
1318 {
1319 Self::TOP_TRAILING
1320 } else if horizontal == HorizontalAlignment::Leading
1321 && vertical == VerticalAlignment::Bottom
1322 {
1323 Self::BOTTOM_LEADING
1324 } else if horizontal == HorizontalAlignment::Trailing
1325 && vertical == VerticalAlignment::Bottom
1326 {
1327 Self::BOTTOM_TRAILING
1328 } else if horizontal == HorizontalAlignment::Leading {
1329 Self::LEADING
1330 } else if horizontal == HorizontalAlignment::Trailing {
1331 Self::TRAILING
1332 } else if vertical == VerticalAlignment::Top {
1333 Self::TOP
1334 } else if vertical == VerticalAlignment::Bottom {
1335 Self::BOTTOM
1336 } else {
1337 Self::CENTER
1338 }
1339 }
1340}
1341
1342#[derive(Clone, Copy, Debug, PartialEq, Default)]
1359pub struct Affine2 {
1360 pub a: f32,
1362 pub b: f32,
1364 pub c: f32,
1366 pub d: f32,
1368 pub e: f32,
1370 pub f: f32,
1372}
1373
1374impl Affine2 {
1375 pub const IDENTITY: Self = Self {
1377 a: 1.0,
1378 b: 0.0,
1379 c: 0.0,
1380 d: 1.0,
1381 e: 0.0,
1382 f: 0.0,
1383 };
1384
1385 #[must_use]
1387 pub const fn new(
1388 scale_x: f32,
1389 shear_y: f32,
1390 shear_x: f32,
1391 scale_y: f32,
1392 translate_x: f32,
1393 translate_y: f32,
1394 ) -> Self {
1395 Self {
1396 a: scale_x,
1397 b: shear_y,
1398 c: shear_x,
1399 d: scale_y,
1400 e: translate_x,
1401 f: translate_y,
1402 }
1403 }
1404
1405 #[must_use]
1407 pub const fn translate(tx: f32, ty: f32) -> Self {
1408 Self {
1409 a: 1.0,
1410 b: 0.0,
1411 c: 0.0,
1412 d: 1.0,
1413 e: tx,
1414 f: ty,
1415 }
1416 }
1417
1418 #[must_use]
1420 pub const fn scale(sx: f32, sy: f32) -> Self {
1421 Self {
1422 a: sx,
1423 b: 0.0,
1424 c: 0.0,
1425 d: sy,
1426 e: 0.0,
1427 f: 0.0,
1428 }
1429 }
1430
1431 #[must_use]
1433 pub fn rotate(radians: f32) -> Self {
1434 let (s, c) = radians.sin_cos();
1435 Self {
1436 a: c,
1437 b: s,
1438 c: -s,
1439 d: c,
1440 e: 0.0,
1441 f: 0.0,
1442 }
1443 }
1444}
1445
1446impl From<[f32; 6]> for Affine2 {
1447 fn from(coefficients: [f32; 6]) -> Self {
1448 Self {
1449 a: coefficients[0],
1450 b: coefficients[1],
1451 c: coefficients[2],
1452 d: coefficients[3],
1453 e: coefficients[4],
1454 f: coefficients[5],
1455 }
1456 }
1457}
1458
1459impl From<Affine2> for [f32; 6] {
1460 fn from(t: Affine2) -> Self {
1461 [t.a, t.b, t.c, t.d, t.e, t.f]
1462 }
1463}
1464
1465macro_rules! impl_layout_signal_constant {
1466 ($($ty:ty),+ $(,)?) => {
1467 $(
1468 impl nami::Signal for $ty {
1469 type Output = Self;
1470 type Guard = ();
1471
1472 fn get(&self) -> Self::Output {
1473 *self
1474 }
1475
1476 fn watch(
1477 &self,
1478 _watcher: impl Fn(nami::watcher::Context<Self::Output>) + 'static,
1479 ) {
1480 }
1481 }
1482 )+
1483 };
1484}
1485
1486impl_layout_signal_constant!(
1487 LayoutDirection,
1488 Point,
1489 Size,
1490 Rect,
1491 Vec2,
1492 UnitPoint,
1493 Affine2,
1494 HorizontalAlignment,
1495 VerticalAlignment,
1496 Alignment
1497);
1498
1499#[derive(Clone, Copy, Debug, PartialEq, Default)]
1513pub struct ProposalSize {
1514 pub width: Option<f32>,
1516 pub height: Option<f32>,
1518}
1519
1520impl ProposalSize {
1521 #[must_use]
1523 pub fn new(width: impl Into<Option<f32>>, height: impl Into<Option<f32>>) -> Self {
1524 Self {
1525 width: width.into(),
1526 height: height.into(),
1527 }
1528 }
1529
1530 pub const UNSPECIFIED: Self = Self {
1532 width: None,
1533 height: None,
1534 };
1535
1536 pub const ZERO: Self = Self {
1538 width: Some(0.0),
1539 height: Some(0.0),
1540 };
1541
1542 pub const INFINITY: Self = Self {
1544 width: Some(f32::INFINITY),
1545 height: Some(f32::INFINITY),
1546 };
1547
1548 #[must_use]
1550 pub fn width_or(&self, default: f32) -> f32 {
1551 self.width.unwrap_or(default)
1552 }
1553
1554 #[must_use]
1556 pub fn height_or(&self, default: f32) -> f32 {
1557 self.height.unwrap_or(default)
1558 }
1559
1560 #[must_use]
1562 pub const fn with_width(self, width: Option<f32>) -> Self {
1563 Self {
1564 width,
1565 height: self.height,
1566 }
1567 }
1568
1569 #[must_use]
1571 pub const fn with_height(self, height: Option<f32>) -> Self {
1572 Self {
1573 width: self.width,
1574 height,
1575 }
1576 }
1577}
1578
1579#[cfg(test)]
1584#[allow(clippy::float_cmp)]
1585mod tests {
1586 use super::*;
1587
1588 #[test]
1589 fn test_rect_geometry() {
1590 let rect = Rect::new(Point::new(10.0, 20.0), Size::new(100.0, 50.0));
1591
1592 assert_eq!(rect.min_x(), 10.0);
1593 assert_eq!(rect.min_y(), 20.0);
1594 assert_eq!(rect.max_x(), 110.0);
1595 assert_eq!(rect.max_y(), 70.0);
1596 assert_eq!(rect.mid_x(), 60.0);
1597 assert_eq!(rect.mid_y(), 45.0);
1598 assert_eq!(rect.width(), 100.0);
1599 assert_eq!(rect.height(), 50.0);
1600 }
1601
1602 #[test]
1603 fn test_rect_inset() {
1604 let rect = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
1605 let inset = rect.inset(10.0, 10.0, 20.0, 20.0);
1606
1607 assert_eq!(inset.x(), 20.0);
1608 assert_eq!(inset.y(), 10.0);
1609 assert_eq!(inset.width(), 60.0);
1610 assert_eq!(inset.height(), 80.0);
1611 }
1612
1613 #[test]
1614 fn test_proposal_size() {
1615 let proposal = ProposalSize::new(Some(100.0), None);
1616
1617 assert_eq!(proposal.width_or(0.0), 100.0);
1618 assert_eq!(proposal.height_or(50.0), 50.0);
1619
1620 let with_height = proposal.with_height(Some(200.0));
1621 assert_eq!(with_height.width, Some(100.0));
1622 assert_eq!(with_height.height, Some(200.0));
1623 }
1624
1625 struct CountingSubView {
1627 measures: core::cell::Cell<usize>,
1628 }
1629
1630 impl SubView for CountingSubView {
1631 fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
1632 self.measures.set(self.measures.get() + 1);
1633 ViewDimensions::new(Size::new(proposal.width_or(10.0), proposal.height_or(20.0)))
1634 }
1635
1636 fn stretch_axis(&self) -> StretchAxis {
1637 StretchAxis::None
1638 }
1639
1640 fn priority(&self) -> i32 {
1641 0
1642 }
1643 }
1644
1645 #[test]
1646 fn memoized_subview_measures_once_per_distinct_proposal() {
1647 let inner = CountingSubView {
1648 measures: core::cell::Cell::new(0),
1649 };
1650 let memo = MemoizedSubView::new(&inner);
1651
1652 let ideal = ProposalSize::UNSPECIFIED;
1653 let constrained = ProposalSize::new(Some(80.0), None);
1654
1655 for _ in 0..5 {
1656 assert_eq!(memo.measure(ideal).size, Size::new(10.0, 20.0));
1657 assert_eq!(memo.measure(constrained).size, Size::new(80.0, 20.0));
1658 }
1659
1660 assert_eq!(
1661 inner.measures.get(),
1662 2,
1663 "ten probes over two distinct proposals must reach the child twice"
1664 );
1665 }
1666
1667 #[test]
1668 fn memoized_subview_forwards_priority_and_stretch() {
1669 let inner = CountingSubView {
1670 measures: core::cell::Cell::new(0),
1671 };
1672 let memo = MemoizedSubView::new(&inner);
1673
1674 assert_eq!(memo.priority(), inner.priority());
1675 assert_eq!(memo.stretch_axis(), inner.stretch_axis());
1676 }
1677
1678 struct GuideProbe;
1682
1683 impl SubView for GuideProbe {
1684 fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
1685 let marker = if proposal.width.is_none() { 1.0 } else { 2.0 };
1686 ViewDimensions::new(Size::new(160.0, 20.0))
1687 .with_horizontal(HorizontalAlignment::Leading, marker)
1688 }
1689
1690 fn stretch_axis(&self) -> StretchAxis {
1691 StretchAxis::None
1692 }
1693
1694 fn priority(&self) -> i32 {
1695 0
1696 }
1697 }
1698
1699 #[test]
1700 fn placed_subview_measures_selected_proposal_not_frame() {
1701 let frame = Rect::new(Point::zero(), Size::new(160.0, 20.0));
1702 let unspecified = ProposalSize::UNSPECIFIED;
1703 let fixed = ProposalSize::new(Some(160.0), Some(20.0));
1704
1705 let placed_unspecified =
1706 PlacedSubview::new(&GuideProbe, SubviewPlacement::new(frame, unspecified));
1707 let placed_fixed = PlacedSubview::new(&GuideProbe, SubviewPlacement::new(frame, fixed));
1708
1709 assert_eq!(placed_unspecified.proposal, unspecified);
1710 assert_eq!(placed_fixed.proposal, fixed);
1711 assert_eq!(
1712 placed_unspecified
1713 .dimensions()
1714 .explicit_horizontal(HorizontalAlignment::Leading),
1715 Some(1.0),
1716 "the same frame must not re-derive a proposal: the unspecified one was selected"
1717 );
1718 assert_eq!(
1719 placed_fixed
1720 .dimensions()
1721 .explicit_horizontal(HorizontalAlignment::Leading),
1722 Some(2.0),
1723 "the same frame must not re-derive a proposal: the fixed one was selected"
1724 );
1725 }
1726
1727 #[derive(Debug)]
1730 struct RecordingLayout {
1731 proposals: core::cell::RefCell<Vec<ProposalSize>>,
1732 }
1733
1734 impl Layout for RecordingLayout {
1735 fn size_that_fits(&self, _proposal: ProposalSize, _children: &[&dyn SubView]) -> Size {
1736 Size::new(160.0, 20.0)
1737 }
1738
1739 fn place(
1740 &self,
1741 bounds: Rect,
1742 proposal: ProposalSize,
1743 children: &[&dyn SubView],
1744 ) -> Vec<SubviewPlacement> {
1745 self.proposals.borrow_mut().push(proposal);
1746 children
1747 .iter()
1748 .map(|_| SubviewPlacement::new(bounds, proposal))
1749 .collect()
1750 }
1751
1752 fn explicit_horizontal(
1753 &self,
1754 alignment: HorizontalAlignment,
1755 _bounds: Rect,
1756 children: &[PlacedSubview<'_>],
1757 ) -> Option<f32> {
1758 children.first()?.explicit_horizontal(alignment)
1759 }
1760 }
1761
1762 #[test]
1763 fn measure_layout_forwards_selected_proposal_to_placement() {
1764 let layout = RecordingLayout {
1765 proposals: core::cell::RefCell::new(Vec::new()),
1766 };
1767 let child = GuideProbe;
1768 let children: [&dyn SubView; 1] = [&child];
1769
1770 let proposals = [
1771 ProposalSize::UNSPECIFIED,
1772 ProposalSize::new(Some(160.0), Some(20.0)),
1773 ProposalSize::UNSPECIFIED,
1774 ];
1775 let guides: Vec<Option<f32>> = proposals
1776 .iter()
1777 .map(|proposal| {
1778 measure_layout(&layout, *proposal, &children)
1779 .explicit_horizontal(HorizontalAlignment::Leading)
1780 })
1781 .collect();
1782
1783 assert_eq!(guides, [Some(1.0), Some(2.0), Some(1.0)]);
1784 assert_eq!(*layout.proposals.borrow(), proposals.to_vec());
1785 }
1786
1787 #[derive(Debug)]
1790 struct InfiniteLayout;
1791
1792 impl Layout for InfiniteLayout {
1793 fn size_that_fits(&self, _proposal: ProposalSize, _children: &[&dyn SubView]) -> Size {
1794 Size::new(f32::INFINITY, 20.0)
1795 }
1796
1797 fn place(
1798 &self,
1799 _bounds: Rect,
1800 _proposal: ProposalSize,
1801 _children: &[&dyn SubView],
1802 ) -> Vec<SubviewPlacement> {
1803 panic!("an infinite measurement must never reach placement")
1804 }
1805 }
1806
1807 #[test]
1808 fn maximum_measurement_does_not_place_unbounded_geometry() {
1809 let dimensions = measure_layout(&InfiniteLayout, ProposalSize::INFINITY, &[]);
1810
1811 assert_eq!(dimensions.size, Size::new(f32::INFINITY, 20.0));
1812 assert!(dimensions.explicit_horizontal_guides().next().is_none());
1813 assert!(dimensions.explicit_vertical_guides().next().is_none());
1814 }
1815}