1use crate::op::LayoutUnit;
2use serde::{Deserialize, Serialize};
3use std::hash::Hash;
4
5#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
9pub struct ViewportTransform {
10 pub translation: [LayoutUnit; 2],
12 pub scale: f32,
14}
15
16impl ViewportTransform {
17 pub const IDENTITY: Self = Self {
18 translation: [0.0, 0.0],
19 scale: 1.0,
20 };
21
22 pub fn new(translation_x: LayoutUnit, translation_y: LayoutUnit, scale: f32) -> Self {
23 Self {
24 translation: [translation_x, translation_y],
25 scale,
26 }
27 .normalized()
28 }
29
30 pub fn normalized(self) -> Self {
31 Self {
32 translation: [
33 finite_or_zero(self.translation[0]),
34 finite_or_zero(self.translation[1]),
35 ],
36 scale: if self.scale.is_finite() && self.scale > 0.0 {
37 self.scale
38 } else {
39 1.0
40 },
41 }
42 }
43
44 pub fn world_to_screen(self, world: [LayoutUnit; 2]) -> [LayoutUnit; 2] {
45 let transform = self.normalized();
46 [
47 world[0] * transform.scale + transform.translation[0],
48 world[1] * transform.scale + transform.translation[1],
49 ]
50 }
51
52 pub fn screen_to_world(self, screen: [LayoutUnit; 2]) -> [LayoutUnit; 2] {
53 let transform = self.normalized();
54 [
55 (screen[0] - transform.translation[0]) / transform.scale,
56 (screen[1] - transform.translation[1]) / transform.scale,
57 ]
58 }
59
60 pub fn with_scale_around(self, screen_focal_point: [LayoutUnit; 2], scale: f32) -> Self {
63 let current = self.normalized();
64 let world_focal_point = current.screen_to_world(screen_focal_point);
65 let next_scale = if scale.is_finite() && scale > 0.0 {
66 scale
67 } else {
68 current.scale
69 };
70 Self {
71 translation: [
72 screen_focal_point[0] - world_focal_point[0] * next_scale,
73 screen_focal_point[1] - world_focal_point[1] * next_scale,
74 ],
75 scale: next_scale,
76 }
77 }
78}
79
80impl Default for ViewportTransform {
81 fn default() -> Self {
82 Self::IDENTITY
83 }
84}
85
86impl Hash for ViewportTransform {
87 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
88 self.translation[0].to_bits().hash(state);
89 self.translation[1].to_bits().hash(state);
90 self.scale.to_bits().hash(state);
91 }
92}
93
94fn finite_or_zero(value: LayoutUnit) -> LayoutUnit {
95 if value.is_finite() {
96 value
97 } else {
98 0.0
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
103pub enum ViewportPanAxis {
104 None,
106 Horizontal,
108 Vertical,
110 #[default]
112 Both,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
116pub struct ViewportMargin {
117 pub left: LayoutUnit,
118 pub right: LayoutUnit,
119 pub top: LayoutUnit,
120 pub bottom: LayoutUnit,
121}
122
123impl ViewportMargin {
124 pub const ZERO: Self = Self {
125 left: 0.0,
126 right: 0.0,
127 top: 0.0,
128 bottom: 0.0,
129 };
130
131 pub fn all(value: LayoutUnit) -> Self {
132 let value = non_negative_finite(value);
133 Self {
134 left: value,
135 right: value,
136 top: value,
137 bottom: value,
138 }
139 }
140
141 pub fn normalized(self) -> Self {
142 Self {
143 left: non_negative_finite(self.left),
144 right: non_negative_finite(self.right),
145 top: non_negative_finite(self.top),
146 bottom: non_negative_finite(self.bottom),
147 }
148 }
149}
150
151impl Hash for ViewportMargin {
152 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
153 self.left.to_bits().hash(state);
154 self.right.to_bits().hash(state);
155 self.top.to_bits().hash(state);
156 self.bottom.to_bits().hash(state);
157 }
158}
159
160fn non_negative_finite(value: LayoutUnit) -> LayoutUnit {
161 if value.is_finite() {
162 value.max(0.0)
163 } else {
164 0.0
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
169pub enum ViewportBoundary {
170 #[default]
172 Unbounded,
173 Finite {
175 min_x: LayoutUnit,
176 min_y: LayoutUnit,
177 max_x: LayoutUnit,
178 max_y: LayoutUnit,
179 margin: ViewportMargin,
180 },
181}
182
183impl ViewportBoundary {
184 pub fn finite(
185 min_x: LayoutUnit,
186 min_y: LayoutUnit,
187 max_x: LayoutUnit,
188 max_y: LayoutUnit,
189 margin: ViewportMargin,
190 ) -> Self {
191 Self::Finite {
192 min_x,
193 min_y,
194 max_x,
195 max_y,
196 margin,
197 }
198 .normalized()
199 }
200
201 pub fn normalized(self) -> Self {
202 match self {
203 Self::Unbounded => Self::Unbounded,
204 Self::Finite {
205 min_x,
206 min_y,
207 max_x,
208 max_y,
209 margin,
210 } if min_x.is_finite()
211 && min_y.is_finite()
212 && max_x.is_finite()
213 && max_y.is_finite() =>
214 {
215 Self::Finite {
216 min_x: min_x.min(max_x),
217 min_y: min_y.min(max_y),
218 max_x: min_x.max(max_x),
219 max_y: min_y.max(max_y),
220 margin: margin.normalized(),
221 }
222 }
223 Self::Finite { .. } => Self::Unbounded,
224 }
225 }
226}
227
228impl Hash for ViewportBoundary {
229 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
230 match self {
231 Self::Unbounded => 0_u8.hash(state),
232 Self::Finite {
233 min_x,
234 min_y,
235 max_x,
236 max_y,
237 margin,
238 } => {
239 1_u8.hash(state);
240 min_x.to_bits().hash(state);
241 min_y.to_bits().hash(state);
242 max_x.to_bits().hash(state);
243 max_y.to_bits().hash(state);
244 margin.hash(state);
245 }
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
251pub enum ViewportClip {
252 None,
254 #[default]
256 HardEdge,
257 AntiAlias,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
262pub enum ViewportZoomPolicy {
263 Disabled,
265 PinchOnly,
267 #[default]
269 WheelWithModifier,
270 WheelAndTrackpad,
272}