1use crate::UiRound::{self, Ceil, Floor, Inward, Nearest, Outward};
7use crate::{Cmp, Lunit, RegionS2, UiNum, UiRect};
8
9#[doc = crate::_tags!(ui layout quant)]
10#[doc = crate::_doc_meta! {
12 location("ui/view/scale", struct UiCellMetric),
13 test_size_of(UiCellMetric = 8|64; niche !Option),
14}]
15#[must_use]
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub struct UiCellMetric {
22 width: Lunit,
23 height: Lunit,
24}
25crate::_impl_init![Self::ONE => UiCellMetric];
26impl Default for UiCellMetric {
27 fn default() -> Self {
28 Self::ONE
29 }
30}
31#[rustfmt::skip]
32impl UiCellMetric {
33 pub const ONE: Self = Self::new_unchecked(Lunit::px(1), Lunit::px(1));
35
36 const fn new_unchecked(width: Lunit, height: Lunit) -> Self { Self { width, height } }
37
38 pub const fn new(width: Lunit, height: Lunit) -> Option<Self> {
44 if width.quanta() <= 0 || height.quanta() <= 0 { None }
45 else { Some(Self { width, height }) }
46 }
47 pub const fn from_px(width: i32, height: i32) -> Option<Self> {
51 Self::new(Lunit::px(width), Lunit::px(height))
52 }
53
54 pub const fn width(self) -> Lunit { self.width }
58
59 pub const fn height(self) -> Lunit { self.height }
61
62 pub const fn x_to_col(self, x: Lunit, round: UiRound) -> i32 {
69 UiNum::round_div_scalar_i64_to_i32(x.quanta() as i64, self.width.quanta() as i64, round)
70 }
71
72 pub const fn y_to_row(self, y: Lunit, round: UiRound) -> i32 {
77 UiNum::round_div_scalar_i64_to_i32(y.quanta() as i64, self.height.quanta() as i64, round)
78 }
79 pub const fn col_to_x(self, col: i32) -> Lunit {
81 Lunit::new_saturated_up((col as i64).saturating_mul(self.width.quanta() as i64))
82 }
83 pub const fn row_to_y(self, row: i32) -> Lunit {
85 Lunit::new_saturated_up((row as i64).saturating_mul(self.height.quanta() as i64))
86 }
87
88 pub const fn rect_to_cells(self, rect: UiRect, round: UiRound) -> RegionS2<i32> {
92 match round {
93 Outward => {
94 let x0 = self.x_to_col(rect.left(), Floor);
95 let y0 = self.y_to_row(rect.top(), Floor);
96 let x1 = self.x_to_col(rect.right(), Ceil);
97 let y1 = self.y_to_row(rect.bottom(), Ceil);
98 RegionS2::from_xy_wh(x0, y0,
99 Cmp(x1.saturating_sub(x0)).max(0), Cmp(y1.saturating_sub(y0)).max(0))
100 }
101 Inward => {
102 let x0 = self.x_to_col(rect.left(), Ceil);
103 let y0 = self.y_to_row(rect.top(), Ceil);
104 let x1 = self.x_to_col(rect.right(), Floor);
105 let y1 = self.y_to_row(rect.bottom(), Floor);
106 RegionS2::from_xy_wh(x0, y0,
107 Cmp(x1.saturating_sub(x0)).max(0), Cmp(y1.saturating_sub(y0)).max(0))
108 }
109 Floor | Ceil | Nearest => RegionS2::from_xy_wh(
110 self.x_to_col(rect.x(), round),
111 self.y_to_row(rect.y(), round),
112 Cmp(self.x_to_col(rect.w(), round)).max(0),
113 Cmp(self.y_to_row(rect.h(), round)).max(0),
114 ),
115 }
116 }
117 pub const fn cells_to_rect(self, cells: RegionS2<i32>) -> UiRect {
119 UiRect::from_xy_wh(
120 self.col_to_x(cells.x()),
121 self.row_to_y(cells.y()),
122 self.col_to_x(cells.w()).max_zero(),
123 self.row_to_y(cells.h()).max_zero(),
124 )
125 }
126}
127#[cfg(test)]
128mod _test {
129 use super::*;
130
131 #[test]
132 fn rejects_nonpositive_dimensions() {
133 assert!(UiCellMetric::new(Lunit::ZERO, Lunit::px(16)).is_none());
134 assert!(UiCellMetric::new(Lunit::px(8), Lunit::ZERO).is_none());
135 assert!(UiCellMetric::new(Lunit::px(-8), Lunit::px(16)).is_none());
136 }
137 #[test]
138 fn projects_independent_cell_dimensions() {
139 let metric = UiCellMetric::from_px(8, 16).unwrap();
140 assert_eq!(metric.x_to_col(Lunit::px(24), UiRound::Nearest), 3,);
141 assert_eq!(metric.y_to_row(Lunit::px(48), UiRound::Nearest), 3,);
142 }
143 #[test]
144 fn outward_projection_covers_partial_cells() {
145 let metric = UiCellMetric::from_px(8, 16).unwrap();
146 let rect = UiRect::from_xy_wh(Lunit::px(4), Lunit::px(8), Lunit::px(8), Lunit::px(16));
147 let cells = metric.rect_to_cells(rect, UiRound::Outward);
148 assert_eq!(cells, RegionS2::from_xy_wh(0, 0, 2, 2));
149 }
150 #[test]
151 fn cell_rectangle_roundtrips_exactly() {
152 let metric = UiCellMetric::from_px(8, 16).unwrap();
153 let cells = RegionS2::from_xy_wh(2, 3, 10, 4);
154 assert_eq!(metric.rect_to_cells(metric.cells_to_rect(cells), UiRound::Nearest,), cells,);
155 }
156}