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 set_zoom(&mut self, new_factor: f32, new_center: DevicePoint) {
79 if new_factor <= 1.0 {
80 self.zoom_factor = 1.0; self.transform = Transform2D::identity();
82 return;
83 }
84
85 let old_factor = std::mem::replace(&mut self.zoom_factor, new_factor);
86
87 let magnification = self.zoom_factor / old_factor;
88 let transform = self
89 .transform
90 .then_translate(Vector2D::new(-new_center.x, -new_center.y))
91 .then_scale(magnification, magnification)
92 .then_translate(Vector2D::new(new_center.x, new_center.y));
93 self.set_transform(transform);
94 }
95
96 pub(crate) fn pan(&mut self, scroll: &mut Scroll, scale: Scale<f32, CSSPixel, DevicePixel>) {
99 let remaining = self.pan_with_device_scroll(*scroll, scale);
100
101 if let Scroll::Delta(delta) = scroll {
102 *delta = remaining.into();
103 }
104 }
105
106 pub(crate) fn pan_with_device_scroll(
109 &mut self,
110 scroll: Scroll,
111 scale: Scale<f32, CSSPixel, DevicePixel>,
112 ) -> DeviceVector2D {
113 let current_viewport = Rect::new(
114 Point2D::origin(),
115 self.unscaled_viewport_size.to_vector().to_size(),
116 );
117 let layout_viewport_in_device_pixels =
118 self.transform.outer_transformed_rect(¤t_viewport);
119 let max_viewport_offset = -(layout_viewport_in_device_pixels.size -
120 self.unscaled_viewport_size.to_vector().to_size());
121 let max_delta = layout_viewport_in_device_pixels.origin - max_viewport_offset;
122
123 let delta = match scroll {
124 Scroll::Delta(delta) => delta.as_device_vector(scale),
125 Scroll::Start => DeviceVector2D::new(0.0, max_delta.y),
126 Scroll::End => DeviceVector2D::new(0.0, -layout_viewport_in_device_pixels.origin.y),
127 };
128
129 let mut remaining = Vector2D::zero();
130 if delta.x < 0.0 {
131 remaining.x = (delta.x - layout_viewport_in_device_pixels.origin.x).min(0.0);
132 }
133 if delta.y < 0.0 {
134 remaining.y = (delta.y - layout_viewport_in_device_pixels.origin.y).min(0.0);
135 }
136 if delta.x > 0.0 {
137 remaining.x = (delta.x - max_delta.x).max(0.0);
138 }
139 if delta.y > 0.0 {
140 remaining.y = (delta.y - max_delta.y).max(0.0);
141 }
142
143 self.set_transform(
144 self.transform
145 .then_translate(Vector2D::new(-delta.x, -delta.y)),
146 );
147
148 remaining
149 }
150
151 pub(crate) fn get_pinch_zoom_infos_for_script(
153 &self,
154 viewport_scale: Scale<f32, CSSPixel, DevicePixel>,
155 ) -> PinchZoomInfos {
156 PinchZoomInfos {
157 zoom_factor: Scale::new(self.zoom_factor),
158 rect: self.pinch_zoom_rect_relative_to_unscaled_viewport() / viewport_scale,
159 }
160 }
161}