1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "remoting/client/gesture_interpreter.h"
6 
7 #include "base/bind.h"
8 #include "base/time/time.h"
9 #include "remoting/client/chromoting_session.h"
10 #include "remoting/client/display/renderer_proxy.h"
11 #include "remoting/client/input/direct_touch_input_strategy.h"
12 #include "remoting/client/input/trackpad_input_strategy.h"
13 
14 namespace {
15 
16 const float kOneFingerFlingTimeConstant = 180.f;
17 const float kScrollFlingTimeConstant = 250.f;
18 
19 }  // namespace
20 
21 namespace remoting {
GestureInterpreter()22 GestureInterpreter::GestureInterpreter()
23     // TODO(yuweih): These animations are better to take GetWeakPtr().
24     : pan_animation_(
25           kOneFingerFlingTimeConstant,
26           base::BindRepeating(&GestureInterpreter::PanWithoutAbortAnimations,
27                               base::Unretained(this))),
28       scroll_animation_(
29           kScrollFlingTimeConstant,
30           base::BindRepeating(&GestureInterpreter::ScrollWithoutAbortAnimations,
31                               base::Unretained(this))) {}
32 
33 GestureInterpreter::~GestureInterpreter() = default;
34 
SetContext(RendererProxy * renderer,ChromotingSession * input_stub)35 void GestureInterpreter::SetContext(RendererProxy* renderer,
36                                     ChromotingSession* input_stub) {
37   renderer_ = renderer;
38   input_stub_ = input_stub;
39   auto transformation_callback =
40       renderer_ ? base::BindRepeating(&RendererProxy::SetTransformation,
41                                       base::Unretained(renderer_))
42                 : DesktopViewport::TransformationCallback();
43   viewport_.RegisterOnTransformationChangedCallback(transformation_callback,
44                                                     true);
45 }
46 
SetInputMode(InputMode mode)47 void GestureInterpreter::SetInputMode(InputMode mode) {
48   switch (mode) {
49     case DIRECT_INPUT_MODE:
50       input_strategy_.reset(new DirectTouchInputStrategy());
51       break;
52     case TRACKPAD_INPUT_MODE:
53       input_strategy_.reset(new TrackpadInputStrategy(viewport_));
54       break;
55     default:
56       NOTREACHED();
57   }
58   input_mode_ = mode;
59   if (!renderer_) {
60     return;
61   }
62   renderer_->SetCursorVisibility(input_strategy_->IsCursorVisible());
63   ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
64   renderer_->SetCursorPosition(cursor_position.x, cursor_position.y);
65 }
66 
GetInputMode() const67 GestureInterpreter::InputMode GestureInterpreter::GetInputMode() const {
68   return input_mode_;
69 }
70 
Zoom(float pivot_x,float pivot_y,float scale,GestureState state)71 void GestureInterpreter::Zoom(float pivot_x,
72                               float pivot_y,
73                               float scale,
74                               GestureState state) {
75   AbortAnimations();
76   SetGestureInProgress(TouchInputStrategy::ZOOM, state != GESTURE_ENDED);
77   if (viewport_.IsViewportReady()) {
78     input_strategy_->HandleZoom({pivot_x, pivot_y}, scale, &viewport_);
79   }
80 }
81 
Pan(float translation_x,float translation_y)82 void GestureInterpreter::Pan(float translation_x, float translation_y) {
83   AbortAnimations();
84   PanWithoutAbortAnimations(translation_x, translation_y);
85 }
86 
Tap(float x,float y)87 void GestureInterpreter::Tap(float x, float y) {
88   AbortAnimations();
89 
90   InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_LEFT);
91 }
92 
TwoFingerTap(float x,float y)93 void GestureInterpreter::TwoFingerTap(float x, float y) {
94   AbortAnimations();
95 
96   InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_RIGHT);
97 }
98 
ThreeFingerTap(float x,float y)99 void GestureInterpreter::ThreeFingerTap(float x, float y) {
100   AbortAnimations();
101 
102   InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_MIDDLE);
103 }
104 
Drag(float x,float y,GestureState state)105 void GestureInterpreter::Drag(float x, float y, GestureState state) {
106   AbortAnimations();
107 
108   bool is_dragging_mode = state != GESTURE_ENDED;
109   SetGestureInProgress(TouchInputStrategy::DRAG, is_dragging_mode);
110 
111   if (!input_stub_ || !viewport_.IsViewportReady() ||
112       !input_strategy_->TrackTouchInput({x, y}, viewport_)) {
113     return;
114   }
115   ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
116 
117   switch (state) {
118     case GESTURE_BEGAN:
119       StartInputFeedback(cursor_position.x, cursor_position.y,
120                          TouchInputStrategy::DRAG_FEEDBACK);
121       input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y,
122                                   protocol::MouseEvent_MouseButton_BUTTON_LEFT,
123                                   true);
124       break;
125     case GESTURE_CHANGED:
126       InjectCursorPosition(cursor_position.x, cursor_position.y);
127       break;
128     case GESTURE_ENDED:
129       input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y,
130                                   protocol::MouseEvent_MouseButton_BUTTON_LEFT,
131                                   false);
132       break;
133     default:
134       NOTREACHED();
135   }
136 }
137 
OneFingerFling(float velocity_x,float velocity_y)138 void GestureInterpreter::OneFingerFling(float velocity_x, float velocity_y) {
139   AbortAnimations();
140   pan_animation_.SetVelocity(velocity_x, velocity_y);
141   pan_animation_.Tick();
142 }
143 
Scroll(float x,float y,float dx,float dy)144 void GestureInterpreter::Scroll(float x, float y, float dx, float dy) {
145   AbortAnimations();
146 
147   if (!viewport_.IsViewportReady() ||
148       !input_strategy_->TrackTouchInput({x, y}, viewport_)) {
149     return;
150   }
151   ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
152 
153   // Inject the cursor position to the host so that scrolling can happen on the
154   // right place.
155   InjectCursorPosition(cursor_position.x, cursor_position.y);
156 
157   ScrollWithoutAbortAnimations(dx, dy);
158 }
159 
ScrollWithVelocity(float velocity_x,float velocity_y)160 void GestureInterpreter::ScrollWithVelocity(float velocity_x,
161                                             float velocity_y) {
162   AbortAnimations();
163 
164   scroll_animation_.SetVelocity(velocity_x, velocity_y);
165   scroll_animation_.Tick();
166 }
167 
ProcessAnimations()168 void GestureInterpreter::ProcessAnimations() {
169   pan_animation_.Tick();
170 
171   // TODO(yuweih): It's probably not right to handle host side virtual scroll
172   // momentum in the renderer's callback.
173   scroll_animation_.Tick();
174 }
175 
OnSurfaceSizeChanged(int width,int height)176 void GestureInterpreter::OnSurfaceSizeChanged(int width, int height) {
177   viewport_.SetSurfaceSize(width, height);
178   if (viewport_.IsViewportReady()) {
179     input_strategy_->FocusViewportOnCursor(&viewport_);
180   }
181 }
182 
OnDesktopSizeChanged(int width,int height)183 void GestureInterpreter::OnDesktopSizeChanged(int width, int height) {
184   viewport_.SetDesktopSize(width, height);
185   if (viewport_.IsViewportReady()) {
186     input_strategy_->FocusViewportOnCursor(&viewport_);
187   }
188 }
189 
OnSafeInsetsChanged(int left,int top,int right,int bottom)190 void GestureInterpreter::OnSafeInsetsChanged(int left,
191                                              int top,
192                                              int right,
193                                              int bottom) {
194   viewport_.SetSafeInsets(left, top, right, bottom);
195   if (viewport_.IsViewportReady()) {
196     input_strategy_->FocusViewportOnCursor(&viewport_);
197   }
198 }
199 
GetWeakPtr()200 base::WeakPtr<GestureInterpreter> GestureInterpreter::GetWeakPtr() {
201   return weak_factory_.GetWeakPtr();
202 }
203 
PanWithoutAbortAnimations(float translation_x,float translation_y)204 void GestureInterpreter::PanWithoutAbortAnimations(float translation_x,
205                                                    float translation_y) {
206   if (viewport_.IsViewportReady() &&
207       input_strategy_->HandlePan({translation_x, translation_y},
208                                  gesture_in_progress_, &viewport_)) {
209     // Cursor position changed.
210     ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
211     if (gesture_in_progress_ != TouchInputStrategy::DRAG) {
212       // Drag() will inject the position so don't need to do that in that case.
213       InjectCursorPosition(cursor_position.x, cursor_position.y);
214     }
215     if (renderer_) {
216       renderer_->SetCursorPosition(cursor_position.x, cursor_position.y);
217     }
218   }
219 }
220 
InjectCursorPosition(float x,float y)221 void GestureInterpreter::InjectCursorPosition(float x, float y) {
222   if (!input_stub_) {
223     return;
224   }
225   input_stub_->SendMouseEvent(
226       x, y, protocol::MouseEvent_MouseButton_BUTTON_UNDEFINED, false);
227 }
228 
ScrollWithoutAbortAnimations(float dx,float dy)229 void GestureInterpreter::ScrollWithoutAbortAnimations(float dx, float dy) {
230   if (!input_stub_ || !viewport_.IsViewportReady()) {
231     return;
232   }
233   ViewMatrix::Point desktopDelta =
234       input_strategy_->MapScreenVectorToDesktop({dx, dy}, viewport_);
235   input_stub_->SendMouseWheelEvent(desktopDelta.x, desktopDelta.y);
236 }
237 
AbortAnimations()238 void GestureInterpreter::AbortAnimations() {
239   pan_animation_.Abort();
240   scroll_animation_.Abort();
241 }
242 
InjectMouseClick(float touch_x,float touch_y,protocol::MouseEvent_MouseButton button)243 void GestureInterpreter::InjectMouseClick(
244     float touch_x,
245     float touch_y,
246     protocol::MouseEvent_MouseButton button) {
247   if (!input_stub_ || !viewport_.IsViewportReady() ||
248       !input_strategy_->TrackTouchInput({touch_x, touch_y}, viewport_)) {
249     return;
250   }
251   ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
252   StartInputFeedback(cursor_position.x, cursor_position.y,
253                      TouchInputStrategy::TAP_FEEDBACK);
254 
255   input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, button,
256                               true);
257   input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, button,
258                               false);
259 }
260 
SetGestureInProgress(TouchInputStrategy::Gesture gesture,bool is_in_progress)261 void GestureInterpreter::SetGestureInProgress(
262     TouchInputStrategy::Gesture gesture,
263     bool is_in_progress) {
264   if (!is_in_progress && gesture_in_progress_ == gesture) {
265     gesture_in_progress_ = TouchInputStrategy::NONE;
266     return;
267   }
268   gesture_in_progress_ = gesture;
269 }
270 
StartInputFeedback(float cursor_x,float cursor_y,TouchInputStrategy::TouchFeedbackType feedback_type)271 void GestureInterpreter::StartInputFeedback(
272     float cursor_x,
273     float cursor_y,
274     TouchInputStrategy::TouchFeedbackType feedback_type) {
275   // This radius is on the view's coordinates. Need to be converted to desktop
276   // coordinate.
277   float feedback_radius = input_strategy_->GetFeedbackRadius(feedback_type);
278   if (feedback_radius > 0) {
279     // TODO(yuweih): The renderer takes diameter as parameter. Consider moving
280     // the *2 logic inside the renderer.
281     float diameter_on_desktop =
282         2.f * feedback_radius / viewport_.GetTransformation().GetScale();
283     if (renderer_) {
284       renderer_->StartInputFeedback(cursor_x, cursor_y, diameter_on_desktop);
285     }
286   }
287 }
288 
289 }  // namespace remoting
290