blob: ec84a99579dba769e4958b33c83482510293bc3b [file] [log] [blame]
// 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 "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::Bind(&GestureInterpreter::PanWithoutAbortAnimations,
base::Unretained(this))),
scroll_animation_(
kScrollFlingTimeConstant,
base::Bind(&GestureInterpreter::ScrollWithoutAbortAnimations,
base::Unretained(this))),
weak_factory_(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_.reset(new DirectTouchInputStrategy());
break;
case TRACKPAD_INPUT_MODE:
input_strategy_.reset(new 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