0

Enable magnifier and haptic feedback for swipe to move cursor

Design doc (internal): go/cursor-control-magnifier

1) Plumbing to forward cursor control gesture to RWHVA.
2) Temporarily hide insertion handles when magnifier is showing.
   Note that we are not calling SetAlpha() since OnInsertionChanged()
   will reset the alpha before the cursor control ends.
3) Reuse existing logic for selection popup controller to enable
   magnifier and haptic feedback.

Bug: 1134492
Change-Id: I2312283a4baa775ac70d89431d444d48fff7b242
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2424900
Reviewed-by: Bo <boliu@chromium.org>
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Reviewed-by: Robert Flack <flackr@chromium.org>
Reviewed-by: Mohsen Izadi <mohsen@chromium.org>
Reviewed-by: Shimi Zhang <ctzsm@chromium.org>
Commit-Queue: Changwan Ryu <changwan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824618}
This commit is contained in:
Changwan Ryu
2020-11-05 22:37:18 +00:00
committed by Commit Bot
parent c0d4846273
commit 4ef8e41182
5 changed files with 129 additions and 0 deletions

@ -99,6 +99,7 @@
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/touch_selection/selection_event_type.h"
#include "ui/touch_selection/touch_selection_controller.h"
namespace content {
@ -1633,6 +1634,9 @@ void RenderWidgetHostViewAndroid::GestureEventAck(
if (gesture_listener_manager_)
gesture_listener_manager_->GestureEventAck(event, ack_result);
if (event.data.scroll_begin.cursor_control || swipe_to_move_cursor_activated_)
OnSwipeToMoveCursorGestureAck(event);
}
void RenderWidgetHostViewAndroid::ChildDidAckGestureEvent(
@ -2440,6 +2444,37 @@ void RenderWidgetHostViewAndroid::SetDisplayFeatureForTesting(
NOTREACHED();
}
void RenderWidgetHostViewAndroid::OnSwipeToMoveCursorGestureAck(
const blink::WebGestureEvent& event) {
if (!touch_selection_controller_ || !selection_popup_controller_) {
swipe_to_move_cursor_activated_ = false;
return;
}
switch (event.GetType()) {
case blink::WebInputEvent::Type::kGestureScrollBegin: {
swipe_to_move_cursor_activated_ = true;
touch_selection_controller_->OnSwipeToMoveCursorBegin();
OnSelectionEvent(ui::INSERTION_HANDLE_DRAG_STARTED);
break;
}
case blink::WebInputEvent::Type::kGestureScrollUpdate: {
gfx::RectF rect = touch_selection_controller_->GetRectBetweenBounds();
selection_popup_controller_->OnDragUpdate(
gfx::PointF(event.PositionInWidget().x(), rect.right_center().y()));
break;
}
case blink::WebInputEvent::Type::kGestureScrollEnd: {
swipe_to_move_cursor_activated_ = false;
touch_selection_controller_->OnSwipeToMoveCursorEnd();
OnSelectionEvent(ui::INSERTION_HANDLE_DRAG_STOPPED);
break;
}
default:
break;
}
}
void RenderWidgetHostViewAndroid::WasEvicted() {
// Eviction can occur when the CompositorFrameSink has changed. This can
// occur either from a lost connection, as well as from the initial conneciton

@ -456,6 +456,8 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid
void OnUpdateScopedSelectionHandles();
void OnSwipeToMoveCursorGestureAck(const blink::WebGestureEvent& event);
bool is_showing_;
// Window-specific bits that affect widget visibility.
@ -558,6 +560,9 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid
// TODO(ericrk): Make this more robust.
bool in_sync_copy_contents_ = false;
// Whether swipe-to-move-cursor gesture is activated.
bool swipe_to_move_cursor_activated_ = false;
// A cached copy of the most up to date RenderFrameMetadata.
base::Optional<cc::RenderFrameMetadata> last_render_frame_metadata_;

@ -366,6 +366,23 @@ bool TouchSelectionController::WillHandleTouchEventImpl(
return false;
}
void TouchSelectionController::OnSwipeToMoveCursorBegin() {
if (config_.hide_active_handle) {
// Hide the handle when magnifier is showing since it can confuse the user.
SetTemporarilyHidden(true);
// If the user has typed something, the insertion handle might be hidden.
// Prepare to show touch handles on end.
show_touch_handles_ = true;
}
}
void TouchSelectionController::OnSwipeToMoveCursorEnd() {
// Show the handle at the end if magnifier was showing.
if (config_.hide_active_handle)
SetTemporarilyHidden(false);
}
void TouchSelectionController::OnDragBegin(
const TouchSelectionDraggable& draggable,
const gfx::PointF& drag_position) {

@ -139,6 +139,11 @@ class UI_TOUCH_SELECTION_EXPORT TouchSelectionController
const gfx::PointF& GetStartPosition() const;
const gfx::PointF& GetEndPosition() const;
// To be called when swipe-to-move-cursor motion begins.
void OnSwipeToMoveCursorBegin();
// To be called when swipe-to-move-cursor motion ends.
void OnSwipeToMoveCursorEnd();
const gfx::SelectionBound& start() const { return start_; }
const gfx::SelectionBound& end() const { return end_; }

@ -1658,4 +1658,71 @@ TEST_F(TouchSelectionControllerTest, HideActiveSelectionHandle) {
EXPECT_EQ(1.f, test_controller.GetEndAlpha());
}
TEST_F(TouchSelectionControllerTest, SwipeToMoveCursor_HideHandlesIfShown) {
// Step 1: Extra set-up.
// For Android P+, we need to hide handles while showing magnifier.
SetHideActiveHandle(true);
TouchSelectionControllerTestApi test_controller(&controller());
gfx::RectF insertion_rect(5, 5, 0, 10);
bool visible = true;
OnTapEvent();
ChangeInsertion(insertion_rect, visible);
EXPECT_THAT(GetAndResetEvents(), ElementsAre(INSERTION_HANDLE_SHOWN));
EXPECT_EQ(insertion_rect.bottom_left(), GetLastEventStart());
EXPECT_TRUE(test_controller.GetStartVisible());
// Step 2: Swipe-to-move-cursor begins: hide handles.
controller().OnSwipeToMoveCursorBegin();
EXPECT_FALSE(test_controller.GetStartVisible());
// Step 3: Move insertion: still hidden.
gfx::RectF new_insertion_rect(10, 5, 0, 10);
ChangeInsertion(new_insertion_rect, visible);
EXPECT_FALSE(test_controller.GetStartVisible());
// Step 4: Swipe-to-move-cursor ends: show handles.
controller().OnSwipeToMoveCursorEnd();
EXPECT_TRUE(test_controller.GetStartVisible());
}
TEST_F(TouchSelectionControllerTest, SwipeToMoveCursor_HandleWasNotShown) {
// Step 1: Extra set-up.
// For Android P+, we need to hide handles while showing magnifier.
SetHideActiveHandle(true);
TouchSelectionControllerTestApi test_controller(&controller());
gfx::RectF insertion_rect(5, 5, 0, 10);
bool visible = true;
OnTapEvent();
ChangeInsertion(insertion_rect, visible);
EXPECT_THAT(GetAndResetEvents(), ElementsAre(INSERTION_HANDLE_SHOWN));
EXPECT_EQ(insertion_rect.bottom_left(), GetLastEventStart());
EXPECT_TRUE(test_controller.GetStartVisible());
// Step 2: Handle is initially hidden, i.e., due to user typing.
controller().HideAndDisallowShowingAutomatically();
EXPECT_FALSE(test_controller.GetStartVisible());
// Step 3: Swipe-to-move-cursor begins: hide handles.
controller().OnSwipeToMoveCursorBegin();
EXPECT_FALSE(test_controller.GetStartVisible());
// Step 4: Move insertion.
// Note that this step is needed to show handle at the end since
// OnInsertionChanged() should activate start_ again, although it will stay
// temporarily hidden.
gfx::RectF new_insertion_rect(10, 5, 0, 10);
ChangeInsertion(new_insertion_rect, visible);
EXPECT_FALSE(test_controller.GetStartVisible());
// Step 5: Swipe-to-move-cursor ends: show handles.
controller().OnSwipeToMoveCursorEnd();
EXPECT_TRUE(test_controller.GetStartVisible());
}
} // namespace ui