| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/common/input/synthetic_touchscreen_pinch_gesture.h" |
| |
| #include <stdint.h> |
| |
| #include <cmath> |
| |
| #include "base/check_op.h" |
| #include "base/notreached.h" |
| #include "base/time/time.h" |
| #include "ui/latency/latency_info.h" |
| |
| namespace content { |
| |
| SyntheticTouchscreenPinchGesture::SyntheticTouchscreenPinchGesture( |
| const SyntheticPinchGestureParams& gesture_params) |
| : SyntheticGestureBase(gesture_params), |
| start_y_0_(0.0f), |
| start_y_1_(0.0f), |
| max_pointer_delta_0_(0.0f), |
| gesture_source_type_(content::mojom::GestureSourceType::kDefaultInput), |
| state_(SETUP) { |
| CHECK_EQ(SyntheticGestureParams::PINCH_GESTURE, |
| gesture_params.GetGestureType()); |
| DCHECK_GT(params().scale_factor, 0.0f); |
| if (params().gesture_source_type != |
| content::mojom::GestureSourceType::kTouchInput) { |
| DCHECK_EQ(params().gesture_source_type, |
| content::mojom::GestureSourceType::kDefaultInput); |
| params_->gesture_source_type = |
| content::mojom::GestureSourceType::kTouchInput; |
| } |
| } |
| |
| SyntheticTouchscreenPinchGesture::~SyntheticTouchscreenPinchGesture() {} |
| |
| SyntheticGesture::Result SyntheticTouchscreenPinchGesture::ForwardInputEvents( |
| const base::TimeTicks& timestamp, |
| SyntheticGestureTarget* target) { |
| CHECK(dispatching_controller_); |
| // Keep this on the stack so we can check if the forwarded event caused the |
| // deletion of the controller (which owns `this`). |
| base::WeakPtr<SyntheticGestureController> weak_controller = |
| dispatching_controller_; |
| if (state_ == SETUP) { |
| gesture_source_type_ = params().gesture_source_type; |
| if (gesture_source_type_ == |
| content::mojom::GestureSourceType::kDefaultInput) |
| gesture_source_type_ = target->GetDefaultSyntheticGestureSourceType(); |
| |
| state_ = STARTED; |
| start_time_ = timestamp; |
| } |
| |
| DCHECK_NE(gesture_source_type_, |
| content::mojom::GestureSourceType::kDefaultInput); |
| |
| if (!synthetic_pointer_driver_) |
| synthetic_pointer_driver_ = SyntheticPointerDriver::Create( |
| gesture_source_type_, params().from_devtools_debugger); |
| |
| if (gesture_source_type_ == content::mojom::GestureSourceType::kTouchInput) { |
| ForwardTouchInputEvents(timestamp, target); |
| // A pinch gesture cannot cause `this` to be destroyed. |
| CHECK(weak_controller); |
| } else { |
| return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED; |
| } |
| |
| return (state_ == DONE) ? SyntheticGesture::GESTURE_FINISHED |
| : SyntheticGesture::GESTURE_RUNNING; |
| } |
| |
| void SyntheticTouchscreenPinchGesture::WaitForTargetAck( |
| base::OnceClosure callback, |
| SyntheticGestureTarget* target) const { |
| target->WaitForTargetAck(params().GetGestureType(), gesture_source_type_, |
| std::move(callback)); |
| } |
| |
| void SyntheticTouchscreenPinchGesture::ForwardTouchInputEvents( |
| const base::TimeTicks& timestamp, |
| SyntheticGestureTarget* target) { |
| switch (state_) { |
| case STARTED: |
| // Check for an early finish. |
| if (params().scale_factor == 1.0f) { |
| state_ = DONE; |
| break; |
| } |
| SetupCoordinatesAndStopTime(target); |
| PressTouchPoints(target, timestamp); |
| state_ = MOVING; |
| break; |
| case MOVING: { |
| base::TimeTicks event_timestamp = ClampTimestamp(timestamp); |
| float delta = GetDeltaForPointer0AtTime(event_timestamp); |
| MoveTouchPoints(target, delta, event_timestamp); |
| if (HasReachedTarget(event_timestamp)) { |
| ReleaseTouchPoints(target, event_timestamp); |
| state_ = DONE; |
| } |
| } break; |
| case SETUP: |
| NOTREACHED() << "State SETUP invalid for synthetic pinch."; |
| case DONE: |
| NOTREACHED() << "State DONE invalid for synthetic pinch."; |
| } |
| } |
| |
| void SyntheticTouchscreenPinchGesture::PressTouchPoints( |
| SyntheticGestureTarget* target, |
| const base::TimeTicks& timestamp) { |
| synthetic_pointer_driver_->Press(params().anchor.x(), start_y_0_, 0); |
| synthetic_pointer_driver_->DispatchEvent(target, timestamp); |
| synthetic_pointer_driver_->Press(params().anchor.x(), start_y_1_, 1); |
| synthetic_pointer_driver_->DispatchEvent(target, timestamp); |
| } |
| |
| void SyntheticTouchscreenPinchGesture::MoveTouchPoints( |
| SyntheticGestureTarget* target, |
| float delta, |
| const base::TimeTicks& timestamp) { |
| // The two pointers move in opposite directions. |
| float current_y_0 = start_y_0_ + delta; |
| float current_y_1 = start_y_1_ - delta; |
| |
| synthetic_pointer_driver_->Move(params().anchor.x(), current_y_0, 0); |
| synthetic_pointer_driver_->Move(params().anchor.x(), current_y_1, 1); |
| synthetic_pointer_driver_->DispatchEvent(target, timestamp); |
| } |
| |
| void SyntheticTouchscreenPinchGesture::ReleaseTouchPoints( |
| SyntheticGestureTarget* target, |
| const base::TimeTicks& timestamp) { |
| synthetic_pointer_driver_->Release(1); |
| synthetic_pointer_driver_->DispatchEvent(target, timestamp); |
| synthetic_pointer_driver_->Release(0); |
| synthetic_pointer_driver_->DispatchEvent(target, timestamp); |
| } |
| |
| void SyntheticTouchscreenPinchGesture::SetupCoordinatesAndStopTime( |
| SyntheticGestureTarget* target) { |
| // To achieve the specified scaling factor, the ratio of the final to the |
| // initial span (distance between the pointers) has to be equal to the scaling |
| // factor. Since we're moving both pointers at the same speed, each pointer's |
| // distance to the anchor is half the span. |
| float initial_distance_to_anchor, final_distance_to_anchor; |
| const float single_point_slop = target->GetSpanSlopInDips() / 2.0f; |
| if (params().scale_factor > 1.0f) { // zooming in |
| initial_distance_to_anchor = target->GetMinScalingSpanInDips() / 2.0f; |
| final_distance_to_anchor = |
| (initial_distance_to_anchor + single_point_slop) * |
| params().scale_factor; |
| } else { // zooming out |
| final_distance_to_anchor = target->GetMinScalingSpanInDips() / 2.0f; |
| initial_distance_to_anchor = |
| (final_distance_to_anchor / params().scale_factor) + single_point_slop; |
| } |
| |
| start_y_0_ = params().anchor.y() - initial_distance_to_anchor; |
| start_y_1_ = params().anchor.y() + initial_distance_to_anchor; |
| |
| max_pointer_delta_0_ = initial_distance_to_anchor - final_distance_to_anchor; |
| |
| const auto duration = |
| base::Seconds(double{std::abs(2 * max_pointer_delta_0_)} / |
| params().relative_pointer_speed_in_pixels_s); |
| stop_time_ = start_time_ + duration; |
| } |
| |
| float SyntheticTouchscreenPinchGesture::GetDeltaForPointer0AtTime( |
| const base::TimeTicks& timestamp) const { |
| // Make sure the final delta is correct. Using the computation below can lead |
| // to issues with floating point precision. |
| if (HasReachedTarget(timestamp)) |
| return max_pointer_delta_0_; |
| |
| float total_abs_delta = params().relative_pointer_speed_in_pixels_s * |
| (timestamp - start_time_).InSecondsF(); |
| float abs_delta_pointer_0 = total_abs_delta / 2.0f; |
| return (params().scale_factor > 1.0f) ? -abs_delta_pointer_0 |
| : abs_delta_pointer_0; |
| } |
| |
| base::TimeTicks SyntheticTouchscreenPinchGesture::ClampTimestamp( |
| const base::TimeTicks& timestamp) const { |
| return std::min(timestamp, stop_time_); |
| } |
| |
| bool SyntheticTouchscreenPinchGesture::HasReachedTarget( |
| const base::TimeTicks& timestamp) const { |
| return timestamp >= stop_time_; |
| } |
| |
| } // namespace content |