| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/client/gesture_interpreter.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/time/time.h" |
| #include "remoting/client/chromoting_session.h" |
| #include "remoting/client/display/renderer_proxy.h" |
| #include "remoting/client/input/direct_touch_input_strategy.h" |
| #include "remoting/client/input/trackpad_input_strategy.h" |
| |
| namespace { |
| |
| const float kOneFingerFlingTimeConstant = 180.f; |
| const float kScrollFlingTimeConstant = 250.f; |
| |
| } // namespace |
| |
| namespace remoting { |
| GestureInterpreter::GestureInterpreter() |
| // TODO(yuweih): These animations are better to take GetWeakPtr(). |
| : pan_animation_( |
| kOneFingerFlingTimeConstant, |
| base::BindRepeating(&GestureInterpreter::PanWithoutAbortAnimations, |
| base::Unretained(this))), |
| scroll_animation_( |
| kScrollFlingTimeConstant, |
| base::BindRepeating(&GestureInterpreter::ScrollWithoutAbortAnimations, |
| base::Unretained(this))) {} |
| |
| GestureInterpreter::~GestureInterpreter() = default; |
| |
| void GestureInterpreter::SetContext(RendererProxy* renderer, |
| ChromotingSession* input_stub) { |
| renderer_ = renderer; |
| input_stub_ = input_stub; |
| auto transformation_callback = |
| renderer_ ? base::BindRepeating(&RendererProxy::SetTransformation, |
| base::Unretained(renderer_)) |
| : DesktopViewport::TransformationCallback(); |
| viewport_.RegisterOnTransformationChangedCallback(transformation_callback, |
| true); |
| } |
| |
| void GestureInterpreter::SetInputMode(InputMode mode) { |
| switch (mode) { |
| case DIRECT_INPUT_MODE: |
| input_strategy_ = std::make_unique<DirectTouchInputStrategy>(); |
| break; |
| case TRACKPAD_INPUT_MODE: |
| input_strategy_ = std::make_unique<TrackpadInputStrategy>(viewport_); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| input_mode_ = mode; |
| if (!renderer_) { |
| return; |
| } |
| renderer_->SetCursorVisibility(input_strategy_->IsCursorVisible()); |
| ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition(); |
| renderer_->SetCursorPosition(cursor_position.x, cursor_position.y); |
| } |
| |
| GestureInterpreter::InputMode GestureInterpreter::GetInputMode() const { |
| return input_mode_; |
| } |
| |
| void GestureInterpreter::Zoom(float pivot_x, |
| float pivot_y, |
| float scale, |
| GestureState state) { |
| AbortAnimations(); |
| SetGestureInProgress(TouchInputStrategy::ZOOM, state != GESTURE_ENDED); |
| if (viewport_.IsViewportReady()) { |
| input_strategy_->HandleZoom({pivot_x, pivot_y}, scale, &viewport_); |
| } |
| } |
| |
| void GestureInterpreter::Pan(float translation_x, float translation_y) { |
| AbortAnimations(); |
| PanWithoutAbortAnimations(translation_x, translation_y); |
| } |
| |
| void GestureInterpreter::Tap(float x, float y) { |
| AbortAnimations(); |
| |
| InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_LEFT); |
| } |
| |
| void GestureInterpreter::TwoFingerTap(float x, float y) { |
| AbortAnimations(); |
| |
| InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_RIGHT); |
| } |
| |
| void GestureInterpreter::ThreeFingerTap(float x, float y) { |
| AbortAnimations(); |
| |
| InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_MIDDLE); |
| } |
| |
| void GestureInterpreter::Drag(float x, float y, GestureState state) { |
| AbortAnimations(); |
| |
| bool is_dragging_mode = state != GESTURE_ENDED; |
| SetGestureInProgress(TouchInputStrategy::DRAG, is_dragging_mode); |
| |
| if (!input_stub_ || !viewport_.IsViewportReady() || |
| !input_strategy_->TrackTouchInput({x, y}, viewport_)) { |
| return; |
| } |
| ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition(); |
| |
| switch (state) { |
| case GESTURE_BEGAN: |
| StartInputFeedback(cursor_position.x, cursor_position.y, |
| TouchInputStrategy::DRAG_FEEDBACK); |
| input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, |
| protocol::MouseEvent_MouseButton_BUTTON_LEFT, |
| true); |
| break; |
| case GESTURE_CHANGED: |
| InjectCursorPosition(cursor_position.x, cursor_position.y); |
| break; |
| case GESTURE_ENDED: |
| input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, |
| protocol::MouseEvent_MouseButton_BUTTON_LEFT, |
| false); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void GestureInterpreter::OneFingerFling(float velocity_x, float velocity_y) { |
| AbortAnimations(); |
| pan_animation_.SetVelocity(velocity_x, velocity_y); |
| pan_animation_.Tick(); |
| } |
| |
| void GestureInterpreter::Scroll(float x, float y, float dx, float dy) { |
| AbortAnimations(); |
| |
| if (!viewport_.IsViewportReady() || |
| !input_strategy_->TrackTouchInput({x, y}, viewport_)) { |
| return; |
| } |
| ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition(); |
| |
| // Inject the cursor position to the host so that scrolling can happen on the |
| // right place. |
| InjectCursorPosition(cursor_position.x, cursor_position.y); |
| |
| ScrollWithoutAbortAnimations(dx, dy); |
| } |
| |
| void GestureInterpreter::ScrollWithVelocity(float velocity_x, |
| float velocity_y) { |
| AbortAnimations(); |
| |
| scroll_animation_.SetVelocity(velocity_x, velocity_y); |
| scroll_animation_.Tick(); |
| } |
| |
| void GestureInterpreter::ProcessAnimations() { |
| pan_animation_.Tick(); |
| |
| // TODO(yuweih): It's probably not right to handle host side virtual scroll |
| // momentum in the renderer's callback. |
| scroll_animation_.Tick(); |
| } |
| |
| void GestureInterpreter::OnSurfaceSizeChanged(int width, int height) { |
| viewport_.SetSurfaceSize(width, height); |
| if (viewport_.IsViewportReady()) { |
| input_strategy_->FocusViewportOnCursor(&viewport_); |
| } |
| } |
| |
| void GestureInterpreter::OnDesktopSizeChanged(int width, int height) { |
| viewport_.SetDesktopSize(width, height); |
| if (viewport_.IsViewportReady()) { |
| input_strategy_->FocusViewportOnCursor(&viewport_); |
| } |
| } |
| |
| void GestureInterpreter::OnSafeInsetsChanged(int left, |
| int top, |
| int right, |
| int bottom) { |
| viewport_.SetSafeInsets(left, top, right, bottom); |
| if (viewport_.IsViewportReady()) { |
| input_strategy_->FocusViewportOnCursor(&viewport_); |
| } |
| } |
| |
| base::WeakPtr<GestureInterpreter> GestureInterpreter::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void GestureInterpreter::PanWithoutAbortAnimations(float translation_x, |
| float translation_y) { |
| if (viewport_.IsViewportReady() && |
| input_strategy_->HandlePan({translation_x, translation_y}, |
| gesture_in_progress_, &viewport_)) { |
| // Cursor position changed. |
| ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition(); |
| if (gesture_in_progress_ != TouchInputStrategy::DRAG) { |
| // Drag() will inject the position so don't need to do that in that case. |
| InjectCursorPosition(cursor_position.x, cursor_position.y); |
| } |
| if (renderer_) { |
| renderer_->SetCursorPosition(cursor_position.x, cursor_position.y); |
| } |
| } |
| } |
| |
| void GestureInterpreter::InjectCursorPosition(float x, float y) { |
| if (!input_stub_) { |
| return; |
| } |
| input_stub_->SendMouseEvent( |
| x, y, protocol::MouseEvent_MouseButton_BUTTON_UNDEFINED, false); |
| } |
| |
| void GestureInterpreter::ScrollWithoutAbortAnimations(float dx, float dy) { |
| if (!input_stub_ || !viewport_.IsViewportReady()) { |
| return; |
| } |
| ViewMatrix::Point desktopDelta = |
| input_strategy_->MapScreenVectorToDesktop({dx, dy}, viewport_); |
| input_stub_->SendMouseWheelEvent(desktopDelta.x, desktopDelta.y); |
| } |
| |
| void GestureInterpreter::AbortAnimations() { |
| pan_animation_.Abort(); |
| scroll_animation_.Abort(); |
| } |
| |
| void GestureInterpreter::InjectMouseClick( |
| float touch_x, |
| float touch_y, |
| protocol::MouseEvent_MouseButton button) { |
| if (!input_stub_ || !viewport_.IsViewportReady() || |
| !input_strategy_->TrackTouchInput({touch_x, touch_y}, viewport_)) { |
| return; |
| } |
| ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition(); |
| StartInputFeedback(cursor_position.x, cursor_position.y, |
| TouchInputStrategy::TAP_FEEDBACK); |
| |
| input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, button, |
| true); |
| input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, button, |
| false); |
| } |
| |
| void GestureInterpreter::SetGestureInProgress( |
| TouchInputStrategy::Gesture gesture, |
| bool is_in_progress) { |
| if (!is_in_progress && gesture_in_progress_ == gesture) { |
| gesture_in_progress_ = TouchInputStrategy::NONE; |
| return; |
| } |
| gesture_in_progress_ = gesture; |
| } |
| |
| void GestureInterpreter::StartInputFeedback( |
| float cursor_x, |
| float cursor_y, |
| TouchInputStrategy::TouchFeedbackType feedback_type) { |
| // This radius is on the view's coordinates. Need to be converted to desktop |
| // coordinate. |
| float feedback_radius = input_strategy_->GetFeedbackRadius(feedback_type); |
| if (feedback_radius > 0) { |
| // TODO(yuweih): The renderer takes diameter as parameter. Consider moving |
| // the *2 logic inside the renderer. |
| float diameter_on_desktop = |
| 2.f * feedback_radius / viewport_.GetTransformation().GetScale(); |
| if (renderer_) { |
| renderer_->StartInputFeedback(cursor_x, cursor_y, diameter_on_desktop); |
| } |
| } |
| } |
| |
| } // namespace remoting |