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