1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 extern crate app_units;
6 extern crate euclid;
7 extern crate gleam;
8 extern crate glutin;
9 extern crate webrender;
10 
11 #[path = "common/boilerplate.rs"]
12 mod boilerplate;
13 
14 use boilerplate::{Example, HandyDandyRectBuilder};
15 use euclid::vec2;
16 use glutin::TouchPhase;
17 use std::collections::HashMap;
18 use webrender::api::*;
19 
20 #[derive(Debug)]
21 enum Gesture {
22     None,
23     Pan,
24     Zoom,
25 }
26 
27 #[derive(Debug)]
28 struct Touch {
29     id: u64,
30     start_x: f32,
31     start_y: f32,
32     current_x: f32,
33     current_y: f32,
34 }
35 
dist(x0: f32, y0: f32, x1: f32, y1: f32) -> f3236 fn dist(x0: f32, y0: f32, x1: f32, y1: f32) -> f32 {
37     let dx = x0 - x1;
38     let dy = y0 - y1;
39     ((dx * dx) + (dy * dy)).sqrt()
40 }
41 
42 impl Touch {
distance_from_start(&self) -> f3243     fn distance_from_start(&self) -> f32 {
44         dist(self.start_x, self.start_y, self.current_x, self.current_y)
45     }
46 
initial_distance_from_other(&self, other: &Touch) -> f3247     fn initial_distance_from_other(&self, other: &Touch) -> f32 {
48         dist(self.start_x, self.start_y, other.start_x, other.start_y)
49     }
50 
current_distance_from_other(&self, other: &Touch) -> f3251     fn current_distance_from_other(&self, other: &Touch) -> f32 {
52         dist(
53             self.current_x,
54             self.current_y,
55             other.current_x,
56             other.current_y,
57         )
58     }
59 }
60 
61 struct TouchState {
62     active_touches: HashMap<u64, Touch>,
63     current_gesture: Gesture,
64     start_zoom: f32,
65     current_zoom: f32,
66     start_pan: DeviceIntPoint,
67     current_pan: DeviceIntPoint,
68 }
69 
70 enum TouchResult {
71     None,
72     Pan(DeviceIntPoint),
73     Zoom(f32),
74 }
75 
76 impl TouchState {
new() -> TouchState77     fn new() -> TouchState {
78         TouchState {
79             active_touches: HashMap::new(),
80             current_gesture: Gesture::None,
81             start_zoom: 1.0,
82             current_zoom: 1.0,
83             start_pan: DeviceIntPoint::zero(),
84             current_pan: DeviceIntPoint::zero(),
85         }
86     }
87 
handle_event(&mut self, touch: glutin::Touch) -> TouchResult88     fn handle_event(&mut self, touch: glutin::Touch) -> TouchResult {
89         match touch.phase {
90             TouchPhase::Started => {
91                 debug_assert!(!self.active_touches.contains_key(&touch.id));
92                 self.active_touches.insert(
93                     touch.id,
94                     Touch {
95                         id: touch.id,
96                         start_x: touch.location.0 as f32,
97                         start_y: touch.location.1 as f32,
98                         current_x: touch.location.0 as f32,
99                         current_y: touch.location.1 as f32,
100                     },
101                 );
102                 self.current_gesture = Gesture::None;
103             }
104             TouchPhase::Moved => {
105                 match self.active_touches.get_mut(&touch.id) {
106                     Some(active_touch) => {
107                         active_touch.current_x = touch.location.0 as f32;
108                         active_touch.current_y = touch.location.1 as f32;
109                     }
110                     None => panic!("move touch event with unknown touch id!"),
111                 }
112 
113                 match self.current_gesture {
114                     Gesture::None => {
115                         let mut over_threshold_count = 0;
116                         let active_touch_count = self.active_touches.len();
117 
118                         for (_, touch) in &self.active_touches {
119                             if touch.distance_from_start() > 8.0 {
120                                 over_threshold_count += 1;
121                             }
122                         }
123 
124                         if active_touch_count == over_threshold_count {
125                             if active_touch_count == 1 {
126                                 self.start_pan = self.current_pan;
127                                 self.current_gesture = Gesture::Pan;
128                             } else if active_touch_count == 2 {
129                                 self.start_zoom = self.current_zoom;
130                                 self.current_gesture = Gesture::Zoom;
131                             }
132                         }
133                     }
134                     Gesture::Pan => {
135                         let keys: Vec<u64> = self.active_touches.keys().cloned().collect();
136                         debug_assert!(keys.len() == 1);
137                         let active_touch = &self.active_touches[&keys[0]];
138                         let x = active_touch.current_x - active_touch.start_x;
139                         let y = active_touch.current_y - active_touch.start_y;
140                         self.current_pan.x = self.start_pan.x + x.round() as i32;
141                         self.current_pan.y = self.start_pan.y + y.round() as i32;
142                         return TouchResult::Pan(self.current_pan);
143                     }
144                     Gesture::Zoom => {
145                         let keys: Vec<u64> = self.active_touches.keys().cloned().collect();
146                         debug_assert!(keys.len() == 2);
147                         let touch0 = &self.active_touches[&keys[0]];
148                         let touch1 = &self.active_touches[&keys[1]];
149                         let initial_distance = touch0.initial_distance_from_other(touch1);
150                         let current_distance = touch0.current_distance_from_other(touch1);
151                         self.current_zoom = self.start_zoom * current_distance / initial_distance;
152                         return TouchResult::Zoom(self.current_zoom);
153                     }
154                 }
155             }
156             TouchPhase::Ended | TouchPhase::Cancelled => {
157                 self.active_touches.remove(&touch.id).unwrap();
158                 self.current_gesture = Gesture::None;
159             }
160         }
161 
162         TouchResult::None
163     }
164 }
165 
main()166 fn main() {
167     let mut app = App {
168         touch_state: TouchState::new(),
169     };
170     boilerplate::main_wrapper(&mut app, None);
171 }
172 
173 struct App {
174     touch_state: TouchState,
175 }
176 
177 impl Example for App {
178     // Make this the only example to test all shaders for compile errors.
179     const PRECACHE_SHADERS: bool = true;
180 
render( &mut self, api: &RenderApi, builder: &mut DisplayListBuilder, resources: &mut ResourceUpdates, _: DeviceUintSize, _pipeline_id: PipelineId, _document_id: DocumentId, )181     fn render(
182         &mut self,
183         api: &RenderApi,
184         builder: &mut DisplayListBuilder,
185         resources: &mut ResourceUpdates,
186         _: DeviceUintSize,
187         _pipeline_id: PipelineId,
188         _document_id: DocumentId,
189     ) {
190         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
191         let info = LayoutPrimitiveInfo::new(bounds);
192         builder.push_stacking_context(
193             &info,
194             ScrollPolicy::Scrollable,
195             None,
196             TransformStyle::Flat,
197             None,
198             MixBlendMode::Normal,
199             Vec::new(),
200         );
201 
202         let image_mask_key = api.generate_image_key();
203         resources.add_image(
204             image_mask_key,
205             ImageDescriptor::new(2, 2, ImageFormat::R8, true),
206             ImageData::new(vec![0, 80, 180, 255]),
207             None,
208         );
209         let mask = ImageMask {
210             image: image_mask_key,
211             rect: (75, 75).by(100, 100),
212             repeat: false,
213         };
214         let complex = ComplexClipRegion::new(
215             (50, 50).to(150, 150),
216             BorderRadius::uniform(20.0),
217             ClipMode::Clip
218         );
219         let id = builder.define_clip(bounds, vec![complex], Some(mask));
220         builder.push_clip_id(id);
221 
222         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
223         builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
224 
225         let info = LayoutPrimitiveInfo::new((250, 100).to(350, 200));
226         builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
227         let border_side = BorderSide {
228             color: ColorF::new(0.0, 0.0, 1.0, 1.0),
229             style: BorderStyle::Groove,
230         };
231         let border_widths = BorderWidths {
232             top: 10.0,
233             left: 10.0,
234             bottom: 10.0,
235             right: 10.0,
236         };
237         let border_details = BorderDetails::Normal(NormalBorder {
238             top: border_side,
239             right: border_side,
240             bottom: border_side,
241             left: border_side,
242             radius: BorderRadius::uniform(20.0),
243         });
244 
245         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
246         builder.push_border(&info, border_widths, border_details);
247         builder.pop_clip_id();
248 
249         if false {
250             // draw box shadow?
251             let rect = LayoutRect::zero();
252             let simple_box_bounds = (20, 200).by(50, 50);
253             let offset = vec2(10.0, 10.0);
254             let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
255             let blur_radius = 0.0;
256             let spread_radius = 0.0;
257             let simple_border_radius = 8.0;
258             let box_shadow_type = BoxShadowClipMode::Inset;
259             let info = LayoutPrimitiveInfo::with_clip_rect(rect, bounds);
260 
261             builder.push_box_shadow(
262                 &info,
263                 simple_box_bounds,
264                 offset,
265                 color,
266                 blur_radius,
267                 spread_radius,
268                 BorderRadius::uniform(simple_border_radius),
269                 box_shadow_type,
270             );
271         }
272 
273         builder.pop_stacking_context();
274     }
275 
on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool276     fn on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
277         let mut txn = Transaction::new();
278         match event {
279             glutin::WindowEvent::Touch(touch) => match self.touch_state.handle_event(touch) {
280                 TouchResult::Pan(pan) => {
281                     txn.set_pan(pan);
282                 }
283                 TouchResult::Zoom(zoom) => {
284                     txn.set_pinch_zoom(ZoomFactor::new(zoom));
285                 }
286                 TouchResult::None => {}
287             },
288             _ => (),
289         }
290 
291         if !txn.is_empty() {
292             txn.generate_frame();
293             api.send_transaction(document_id, txn);
294         }
295 
296         false
297     }
298 }
299