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