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