1use embedder_traits::Scroll;
6use euclid::{Point2D, Rect, Scale, Transform2D, Vector2D};
7use paint_api::PinchZoomInfos;
8use style_traits::CSSPixel;
9use webrender_api::units::{DevicePixel, DevicePoint, DeviceRect, DeviceSize, DeviceVector2D};
10
11#[derive(Clone, Copy, Debug, PartialEq)]
15pub(crate) struct PinchZoom {
16 zoom_factor: f32,
17 transform: Transform2D<f32, DevicePixel, DevicePixel>,
18 unscaled_viewport_size: DeviceSize,
19}
20
21impl PinchZoom {
22 pub(crate) fn new(webview_rect: DeviceRect) -> Self {
23 Self {
24 zoom_factor: 1.0,
25 unscaled_viewport_size: webview_rect.size(),
26 transform: Transform2D::identity(),
27 }
28 }
29
30 pub(crate) fn transform(&self) -> Transform2D<f32, DevicePixel, DevicePixel> {
31 self.transform
32 }
33
34 pub(crate) fn zoom_factor(&self) -> Scale<f32, DevicePixel, DevicePixel> {
35 Scale::new(self.zoom_factor)
36 }
37
38 pub(crate) fn resize_unscaled_viewport(&mut self, webview_rect: DeviceRect) {
39 self.unscaled_viewport_size = webview_rect.size();
40 }
41
42 fn pinch_zoom_rect_relative_to_unscaled_viewport(&self) -> Rect<f32, DevicePixel> {
45 let rect = Rect::new(
46 Point2D::origin(),
47 self.unscaled_viewport_size.to_vector().to_size(),
48 )
49 .cast_unit();
50 self.transform
51 .inverse()
52 .expect("Should always be able to invert provided transform")
53 .outer_transformed_rect(&rect)
54 }
55
56 fn set_transform(&mut self, transform: Transform2D<f32, DevicePixel, DevicePixel>) {
57 let rect = Rect::new(
58 Point2D::origin(),
59 self.unscaled_viewport_size.to_vector().to_size(),
60 )
61 .cast_unit();
62 let mut rect = transform
63 .inverse()
64 .expect("Should always be able to invert provided transform")
65 .outer_transformed_rect(&rect);
66 rect.origin = rect.origin.clamp(
67 Point2D::origin(),
68 (self.unscaled_viewport_size - rect.size)
69 .to_vector()
70 .to_point(),
71 );
72 let scale = self.unscaled_viewport_size.width / rect.width();
73 self.transform = Transform2D::identity()
74 .then_translate(Vector2D::new(-rect.origin.x, -rect.origin.y))
75 .then_scale(scale, scale);
76 }
77
78 pub(crate) fn zoom(&mut self, magnification: f32, new_center: DevicePoint) {
79 const MINIMUM_PINCH_ZOOM: f32 = 1.0;
80 const MAXIMUM_PINCH_ZOOM: f32 = 10.0;
81 let new_factor =
82 (self.zoom_factor * magnification).clamp(MINIMUM_PINCH_ZOOM, MAXIMUM_PINCH_ZOOM);
83 let old_factor = std::mem::replace(&mut self.zoom_factor, new_factor);
84
85 if self.zoom_factor <= 1.0 {
86 self.transform = Transform2D::identity();
87 return;
88 }
89
90 let magnification = self.zoom_factor / old_factor;
91 let transform = self
92 .transform
93 .then_translate(Vector2D::new(-new_center.x, -new_center.y))
94 .then_scale(magnification, magnification)
95 .then_translate(Vector2D::new(new_center.x, new_center.y));
96 self.set_transform(transform);
97 }
98
99 pub(crate) fn pan(&mut self, scroll: &mut Scroll, scale: Scale<f32, CSSPixel, DevicePixel>) {
102 let remaining = self.pan_with_device_scroll(*scroll, scale);
103
104 if let Scroll::Delta(delta) = scroll {
105 *delta = remaining.into();
106 }
107 }
108
109 pub(crate) fn pan_with_device_scroll(
112 &mut self,
113 scroll: Scroll,
114 scale: Scale<f32, CSSPixel, DevicePixel>,
115 ) -> DeviceVector2D {
116 let current_viewport = Rect::new(
117 Point2D::origin(),
118 self.unscaled_viewport_size.to_vector().to_size(),
119 );
120 let layout_viewport_in_device_pixels =
121 self.transform.outer_transformed_rect(¤t_viewport);
122 let max_viewport_offset = -(layout_viewport_in_device_pixels.size -
123 self.unscaled_viewport_size.to_vector().to_size());
124 let max_delta = layout_viewport_in_device_pixels.origin - max_viewport_offset;
125
126 let delta = match scroll {
127 Scroll::Delta(delta) => delta.as_device_vector(scale),
128 Scroll::Start => DeviceVector2D::new(0.0, max_delta.y),
129 Scroll::End => DeviceVector2D::new(0.0, -layout_viewport_in_device_pixels.origin.y),
130 };
131
132 let mut remaining = Vector2D::zero();
133 if delta.x < 0.0 {
134 remaining.x = (delta.x - layout_viewport_in_device_pixels.origin.x).min(0.0);
135 }
136 if delta.y < 0.0 {
137 remaining.y = (delta.y - layout_viewport_in_device_pixels.origin.y).min(0.0);
138 }
139 if delta.x > 0.0 {
140 remaining.x = (delta.x - max_delta.x).max(0.0);
141 }
142 if delta.y > 0.0 {
143 remaining.y = (delta.y - max_delta.y).max(0.0);
144 }
145
146 self.set_transform(
147 self.transform
148 .then_translate(Vector2D::new(-delta.x, -delta.y)),
149 );
150
151 remaining
152 }
153
154 pub(crate) fn get_pinch_zoom_infos_for_script(
156 &self,
157 viewport_scale: Scale<f32, CSSPixel, DevicePixel>,
158 ) -> PinchZoomInfos {
159 PinchZoomInfos {
160 zoom_factor: Scale::new(self.zoom_factor),
161 rect: self.pinch_zoom_rect_relative_to_unscaled_viewport() / viewport_scale,
162 }
163 }
164}