layer_shika_domain/
dimensions.rs1use crate::errors::DomainError;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct LogicalSize {
6 width: f32,
7 height: f32,
8}
9
10impl LogicalSize {
11 pub fn new(width: f32, height: f32) -> Result<Self, DomainError> {
12 if width <= 0.0 || height <= 0.0 {
13 return Err(DomainError::InvalidInput {
14 message: format!("Dimensions must be positive, got width={width}, height={height}"),
15 });
16 }
17 if !width.is_finite() || !height.is_finite() {
18 return Err(DomainError::InvalidInput {
19 message: "Dimensions must be finite values".to_string(),
20 });
21 }
22 Ok(Self { width, height })
23 }
24
25 pub const fn from_raw(width: f32, height: f32) -> Self {
26 Self { width, height }
27 }
28
29 pub const fn width(&self) -> f32 {
30 self.width
31 }
32
33 pub const fn height(&self) -> f32 {
34 self.height
35 }
36
37 pub fn to_physical(&self, scale_factor: ScaleFactor) -> PhysicalSize {
38 scale_factor.to_physical(*self)
39 }
40
41 pub fn as_tuple(&self) -> (f32, f32) {
42 (self.width, self.height)
43 }
44
45 pub fn clamp_position(
46 &self,
47 position: LogicalPosition,
48 bounds: LogicalSize,
49 ) -> LogicalPosition {
50 let max_x = (bounds.width - self.width).max(0.0);
51 let max_y = (bounds.height - self.height).max(0.0);
52
53 LogicalPosition::new(
54 position.x().max(0.0).min(max_x),
55 position.y().max(0.0).min(max_y),
56 )
57 }
58}
59
60impl Default for LogicalSize {
61 fn default() -> Self {
62 Self {
63 width: 120.0,
64 height: 120.0,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct PhysicalSize {
72 width: u32,
73 height: u32,
74}
75
76impl PhysicalSize {
77 pub fn new(width: u32, height: u32) -> Result<Self, DomainError> {
78 if width == 0 || height == 0 {
79 return Err(DomainError::InvalidDimensions { width, height });
80 }
81 Ok(Self { width, height })
82 }
83
84 pub const fn from_raw(width: u32, height: u32) -> Self {
85 Self { width, height }
86 }
87
88 pub const fn width(&self) -> u32 {
89 self.width
90 }
91
92 pub const fn height(&self) -> u32 {
93 self.height
94 }
95
96 pub fn to_logical(&self, scale_factor: ScaleFactor) -> LogicalSize {
97 scale_factor.to_logical(*self)
98 }
99
100 pub fn as_tuple(&self) -> (u32, u32) {
101 (self.width, self.height)
102 }
103}
104
105impl Default for PhysicalSize {
106 fn default() -> Self {
107 Self {
108 width: 120,
109 height: 120,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq)]
116pub struct ScaleFactor(f32);
117
118impl ScaleFactor {
119 pub fn new(factor: f32) -> Result<Self, DomainError> {
120 if factor <= 0.0 {
121 return Err(DomainError::InvalidInput {
122 message: format!("Scale factor must be positive, got {factor}"),
123 });
124 }
125 if !factor.is_finite() {
126 return Err(DomainError::InvalidInput {
127 message: "Scale factor must be a finite value".to_string(),
128 });
129 }
130 Ok(Self(factor))
131 }
132
133 pub const fn from_raw(factor: f32) -> Self {
134 Self(factor)
135 }
136
137 #[allow(clippy::cast_precision_loss)]
138 pub fn from_120ths(scale_120ths: u32) -> Self {
139 Self(scale_120ths as f32 / 120.0)
140 }
141
142 pub const fn value(&self) -> f32 {
143 self.0
144 }
145
146 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
147 pub fn to_physical(&self, logical: LogicalSize) -> PhysicalSize {
148 let width = (logical.width * self.0).round() as u32;
149 let height = (logical.height * self.0).round() as u32;
150 PhysicalSize::from_raw(width.max(1), height.max(1))
151 }
152
153 #[allow(clippy::cast_precision_loss)]
154 pub fn to_logical(&self, physical: PhysicalSize) -> LogicalSize {
155 let width = physical.width as f32 / self.0;
156 let height = physical.height as f32 / self.0;
157 LogicalSize::from_raw(width, height)
158 }
159
160 #[allow(clippy::cast_possible_truncation)]
161 pub fn buffer_scale(&self) -> i32 {
162 self.0.round() as i32
163 }
164
165 pub fn scale_coordinate(&self, logical_coord: f32) -> f32 {
166 logical_coord * self.0
167 }
168
169 pub fn unscale_coordinate(&self, physical_coord: f32) -> f32 {
170 physical_coord / self.0
171 }
172}
173
174impl Default for ScaleFactor {
175 fn default() -> Self {
176 Self(1.0)
177 }
178}
179
180impl TryFrom<f32> for ScaleFactor {
181 type Error = DomainError;
182
183 fn try_from(factor: f32) -> Result<Self, Self::Error> {
184 Self::new(factor)
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq)]
190pub struct LogicalPosition {
191 x: f32,
192 y: f32,
193}
194
195impl LogicalPosition {
196 pub fn new(x: f32, y: f32) -> Self {
197 Self { x, y }
198 }
199
200 pub const fn x(&self) -> f32 {
201 self.x
202 }
203
204 pub const fn y(&self) -> f32 {
205 self.y
206 }
207
208 #[allow(clippy::cast_possible_truncation)]
209 pub fn to_physical(&self, scale_factor: ScaleFactor) -> PhysicalPosition {
210 PhysicalPosition::new(
211 (self.x * scale_factor.value()).round() as i32,
212 (self.y * scale_factor.value()).round() as i32,
213 )
214 }
215
216 pub fn as_tuple(&self) -> (f32, f32) {
217 (self.x, self.y)
218 }
219}
220
221impl Default for LogicalPosition {
222 fn default() -> Self {
223 Self { x: 0.0, y: 0.0 }
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct PhysicalPosition {
229 x: i32,
230 y: i32,
231}
232
233impl PhysicalPosition {
234 pub const fn new(x: i32, y: i32) -> Self {
235 Self { x, y }
236 }
237
238 pub const fn x(&self) -> i32 {
239 self.x
240 }
241
242 pub const fn y(&self) -> i32 {
243 self.y
244 }
245
246 #[allow(clippy::cast_precision_loss)]
247 pub fn to_logical(&self, scale_factor: ScaleFactor) -> LogicalPosition {
248 LogicalPosition::new(
249 self.x as f32 / scale_factor.value(),
250 self.y as f32 / scale_factor.value(),
251 )
252 }
253
254 pub fn as_tuple(&self) -> (i32, i32) {
255 (self.x, self.y)
256 }
257}
258
259#[allow(clippy::derivable_impls)]
260impl Default for PhysicalPosition {
261 fn default() -> Self {
262 Self { x: 0, y: 0 }
263 }
264}