diff --git a/pdf/pdf_ink_module.cc b/pdf/pdf_ink_module.cc index 28bb181225319..2af8a96904298 100644 --- a/pdf/pdf_ink_module.cc +++ b/pdf/pdf_ink_module.cc @@ -54,6 +54,7 @@ #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/base/cursor/cursor.h" +#include "ui/base/cursor/mojom/cursor_type.mojom.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/point_f.h" @@ -370,6 +371,10 @@ bool PdfInkModule::OnMessage(const base::Value::Dict& message) { } void PdfInkModule::OnGeometryChanged() { + // If the highlighter tool is selected, and zooming moves the cursor onto + // text, the cursor should be an I-beam, but it will instead be the drawing + // cursor until a mousemove event occurs. There is not a way to get the new + // mouse position on geometry change. MaybeSetCursor(); } @@ -470,11 +475,11 @@ bool PdfInkModule::OnMouseUp(const blink::WebMouseEvent& event) { return false; } + gfx::PointF position = event.PositionInWidget(); if (features::kPdfInk2TextHighlighting.Get() && is_text_highlighting()) { - return FinishTextHighlight(); + return FinishTextHighlight(position); } - gfx::PointF position = event.PositionInWidget(); return is_drawing_stroke() ? FinishStroke(position, event.TimeStamp(), ink::StrokeInput::ToolType::kMouse) @@ -484,10 +489,8 @@ bool PdfInkModule::OnMouseUp(const blink::WebMouseEvent& event) { bool PdfInkModule::OnMouseMove(const blink::WebMouseEvent& event) { CHECK(enabled()); - // TODO(crbug.com/342445982): Set the cursor for hovering over text with the - // highlighter brush while not drawing. - gfx::PointF position = event.PositionInWidget(); + bool still_interacting_with_ink = event.GetModifiers() & blink::WebInputEvent::kLeftButtonDown; if (still_interacting_with_ink) { @@ -507,6 +510,7 @@ bool PdfInkModule::OnMouseMove(const blink::WebMouseEvent& event) { // that now, and compensate by synthesizing a mouse-up input event at the // last known input position. Intentionally do not use `position`. if (is_drawing_stroke()) { + MaybeSetCursorOnMouseMove(position); DrawingStrokeState& state = drawing_stroke_state(); if (!state.input_last_event.has_value()) { // Ignore when not drawing. @@ -772,7 +776,12 @@ bool PdfInkModule::FinishStroke(const gfx::PointF& position, state.page_index = -1; state.input_last_event.reset(); - if (MaybeSetDrawingBrush()) { + bool set_drawing_brush = MaybeSetDrawingBrush(); + if (features::kPdfInk2TextHighlighting.Get() && + state.brush_type == PdfInkBrush::Type::kHighlighter && + client_->IsSelectableTextOrLinkArea(position)) { + client_->UpdateInkCursor(ui::mojom::CursorType::kIBeam); + } else if (set_drawing_brush) { MaybeSetCursor(); } @@ -984,7 +993,7 @@ bool PdfInkModule::ContinueTextHighlight(const gfx::PointF& position) { return true; } -bool PdfInkModule::FinishTextHighlight() { +bool PdfInkModule::FinishTextHighlight(const gfx::PointF& position) { CHECK(is_text_highlighting()); auto& highlight_strokes = text_highlight_state().highlight_strokes; @@ -1018,6 +1027,10 @@ bool PdfInkModule::FinishTextHighlight() { drawing_stroke_state().brush_type = PdfInkBrush::Type::kHighlighter; client_->ClearSelection(); + + if (!client_->IsSelectableTextOrLinkArea(position)) { + MaybeSetCursor(); + } return true; } @@ -1609,7 +1622,6 @@ void PdfInkModule::MaybeSetCursor() { } if (features::kPdfInk2TextHighlighting.Get() && is_text_highlighting()) { - // TODO(crbug.com/342445982): Set the cursor for text highlighting. return; } @@ -1633,6 +1645,25 @@ void PdfInkModule::MaybeSetCursor() { ui::Cursor::NewCustom(std::move(bitmap), std::move(hotspot))); } +void PdfInkModule::MaybeSetCursorOnMouseMove(const gfx::PointF& position) { + if (!features::kPdfInk2TextHighlighting.Get()) { + return; + } + + CHECK(is_drawing_stroke()); + if (drawing_stroke_state().brush_type != PdfInkBrush::Type::kHighlighter || + !client_->IsSelectableTextOrLinkArea(position)) { + if (client_->GetCursor().type() == ui::mojom::CursorType::kIBeam) { + MaybeSetCursor(); + } + return; + } + + if (client_->GetCursor().type() != ui::mojom::CursorType::kIBeam) { + client_->UpdateInkCursor(ui::mojom::CursorType::kIBeam); + } +} + PdfInkModule::DrawingStrokeState::DrawingStrokeState() = default; PdfInkModule::DrawingStrokeState::~DrawingStrokeState() = default; diff --git a/pdf/pdf_ink_module.h b/pdf/pdf_ink_module.h index 14ff977dd265e..45adf9d766e4d 100644 --- a/pdf/pdf_ink_module.h +++ b/pdf/pdf_ink_module.h @@ -356,7 +356,7 @@ class PdfInkModule { int click_count, base::TimeTicks timestamp); bool ContinueTextHighlight(const gfx::PointF& position); - bool FinishTextHighlight(); + bool FinishTextHighlight(const gfx::PointF& position); // Returns a highlighter stroke that matches the position and size of // `selection_rect`. `selection_rect` must be in screen coordinates. @@ -455,8 +455,14 @@ class PdfInkModule { void ApplyUndoRedoDiscards( const PdfInkUndoRedoModel::DiscardedDrawCommands& discards); + // Sets the cursor to a drawing/erasing brush cursor when necessary. void MaybeSetCursor(); + // Handles setting the cursor only for mousemove events at `position`. This + // differs from `MaybeSetCursor()` in that it may also set the cursor to an + // I-beam for text highlighting. + void MaybeSetCursorOnMouseMove(const gfx::PointF& position); + // Returns whether the drawing brush was set or not. bool MaybeSetDrawingBrush(); diff --git a/pdf/pdf_ink_module_client.h b/pdf/pdf_ink_module_client.h index 731e5b2feb4eb..a4a280766f796 100644 --- a/pdf/pdf_ink_module_client.h +++ b/pdf/pdf_ink_module_client.h @@ -12,6 +12,7 @@ #include "pdf/pdf_ink_ids.h" #include "pdf/ui/thumbnail.h" #include "third_party/ink/src/ink/geometry/partitioned_mesh.h" +#include "ui/base/cursor/cursor.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/vector2d.h" @@ -55,6 +56,9 @@ class PdfInkModuleClient { // `point`. `point` must be in device coordinates. virtual void ExtendSelectionByPoint(const gfx::PointF& point) {} + // Returns the current cursor. + virtual ui::Cursor GetCursor() = 0; + // Gets the current page orientation. virtual PageOrientation GetOrientation() const = 0; diff --git a/pdf/pdf_ink_module_unittest.cc b/pdf/pdf_ink_module_unittest.cc index 5a3225d339f5a..434cdba603060 100644 --- a/pdf/pdf_ink_module_unittest.cc +++ b/pdf/pdf_ink_module_unittest.cc @@ -199,6 +199,13 @@ std::map<int, std::vector<raw_ref<const ink::Stroke>>> CollectVisibleStrokes( return visible_stroke_shapes; } +blink::WebMouseEvent CreateMouseMoveEventAtPoint(const gfx::PointF& point) { + return MouseEventBuilder() + .SetType(blink::WebInputEvent::Type::kMouseMove) + .SetPosition(point) + .Build(); +} + blink::WebMouseEvent CreateMouseMoveWithLeftButtonEventAtPoint( const gfx::PointF& point) { return MouseEventBuilder() @@ -261,6 +268,8 @@ class FakeClient : public PdfInkModuleClient { (const gfx::PointF& point), (override)); + MOCK_METHOD(ui::Cursor, GetCursor, (), (override)); + PageOrientation GetOrientation() const override { return orientation_; } MOCK_METHOD(std::vector<gfx::Rect>, GetSelectionRects, (), (override)); @@ -3098,6 +3107,8 @@ class PdfInkModuleTextHighlightTest : public PdfInkModuleStrokeTest { .WillRepeatedly(Return(selection_rects)); EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kStartPointInsidePage0)) .WillRepeatedly(Return(true)); + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kEndPointInsidePage0)) + .WillRepeatedly(Return(true)); EXPECT_CALL(client(), OnTextOrLinkAreaClick(kStartPointInsidePage0, /*click_count=*/1)); @@ -3330,10 +3341,12 @@ TEST_P(PdfInkModuleTextHighlightTest, MultipleSelection) { .WillRepeatedly(Return(selection_rects)); EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kStartPointInsidePage0)) .WillRepeatedly(Return(true)); + constexpr gfx::PointF kEndPoint2InsidePage0{25.0, 30.0}; + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kEndPoint2InsidePage0)) + .WillRepeatedly(Return(true)); EXPECT_CALL(client(), OnTextOrLinkAreaClick(kStartPointInsidePage0, /*click_count=*/1)); - constexpr gfx::PointF kEndPoint2InsidePage0{25.0, 30.0}; EXPECT_CALL(client(), ExtendSelectionByPoint(kEndPoint2InsidePage0)); // Apply a text highlight stroke at the given points. @@ -3663,7 +3676,7 @@ TEST_P(PdfInkModuleTextHighlightTest, std::vector<gfx::Rect> selection_rects{gfx::Rect(9, 14, 5, 10)}; EXPECT_CALL(client(), GetSelectionRects()) .WillRepeatedly(Return(selection_rects)); - EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kMouseDownPoint)) + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(_)) .WillRepeatedly(Return(true)); // There should not be any text selection extension after the miss, as the @@ -3673,6 +3686,173 @@ TEST_P(PdfInkModuleTextHighlightTest, RunStrokeMissedEndEventThenMouseMoveTest(); } +TEST_P(PdfInkModuleTextHighlightTest, CursorOnMouseMove) { + EnableAnnotationMode(); + InitializeSimpleSinglePageBasicLayout(); + + // Select the pen tool with a "Light Red" color. The cursor should be the + // custom pen cursor. + EXPECT_CALL(client(), + UpdateInkCursor(CursorBitmapImageSizeEq(SkISize(8, 8)))); + + TestAnnotationBrushMessageParams params = {/*color_r=*/0xF2, /*color_g=*/0x8B, + /*color_b=*/0x82, /*size=*/6.0f}; + SelectBrushTool(PdfInkBrush::Type::kPen, params); + + // `kStartPointInsidePage0` will be the selectable text area position, while + // all other positions will be non-text areas. + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(_)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kStartPointInsidePage0)) + .WillRepeatedly(Return(true)); + + // Move to a text position. The cursor should remain as the custom pen cursor. + blink::WebMouseEvent mouse_move_event = + CreateMouseMoveEventAtPoint(kEndPointInsidePage0); + // The event will be considered not handled, but the cursor will still update. + EXPECT_FALSE(ink_module().HandleInputEvent(mouse_move_event)); + + VerifyAndClearExpectations(); + + // Select the highlighter tool. The cursor should be the custom highlighter + // cursor. + EXPECT_CALL(client(), + UpdateInkCursor(CursorBitmapImageSizeEq(SkISize(10, 10)))); + params.size = 8.0f; + SelectBrushTool(PdfInkBrush::Type::kHighlighter, params); + + VerifyAndClearExpectations(); + + // Move to a text position. The cursor should be an I-beam. + EXPECT_CALL(client(), + UpdateInkCursor(ui::Cursor(ui::mojom::CursorType::kIBeam))); + mouse_move_event = CreateMouseMoveEventAtPoint(kStartPointInsidePage0); + EXPECT_FALSE(ink_module().HandleInputEvent(mouse_move_event)); + + VerifyAndClearExpectations(); + + // Move to a non-text position. The cursor should restore to the custom + // highlighter cursor. + { + InSequence seq; + EXPECT_CALL(client(), GetCursor()) + .WillOnce(Return(ui::mojom::CursorType::kIBeam)); + EXPECT_CALL(client(), + UpdateInkCursor(CursorBitmapImageSizeEq(SkISize(10, 10)))); + } + mouse_move_event = CreateMouseMoveEventAtPoint(kEndPointInsidePage0); + EXPECT_FALSE(ink_module().HandleInputEvent(mouse_move_event)); +} + +TEST_P(PdfInkModuleTextHighlightTest, CursorOnMouseMoveWhileTextSelecting) { + EnableAnnotationMode(); + InitializeSimpleSinglePageBasicLayout(); + + // Select the highlighter tool. The cursor should be the custom highlighter + // cursor. + EXPECT_CALL(client(), + UpdateInkCursor(CursorBitmapImageSizeEq(SkISize(10, 10)))); + TestAnnotationBrushMessageParams params = {/*color_r=*/0xF2, /*color_g=*/0x8B, + /*color_b=*/0x82, /*size=*/8.0f}; + SelectBrushTool(PdfInkBrush::Type::kHighlighter, params); + + VerifyAndClearExpectations(); + + // `kStartPointInsidePage0` will be the selectable text area position, while + // all other positions will be non-text areas. + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(_)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kStartPointInsidePage0)) + .WillRepeatedly(Return(true)); + + // Move to a text position. The cursor should be an I-beam. + EXPECT_CALL(client(), + UpdateInkCursor(ui::Cursor(ui::mojom::CursorType::kIBeam))); + blink::WebMouseEvent mouse_move_event = + CreateMouseMoveEventAtPoint(kStartPointInsidePage0); + EXPECT_FALSE(ink_module().HandleInputEvent(mouse_move_event)); + + VerifyAndClearExpectations(); + + // Start text highlighting and move to a non-text position. The cursor should + // remain as an I-beam. + EXPECT_CALL(client(), UpdateInkCursor(_)).Times(0); + blink::WebMouseEvent mouse_down_event = + MouseEventBuilder() + .CreateLeftClickAtPosition(kStartPointInsidePage0) + .Build(); + EXPECT_TRUE(ink_module().HandleInputEvent(mouse_down_event)); + mouse_move_event = + CreateMouseMoveWithLeftButtonEventAtPoint(kEndPointInsidePage0); + EXPECT_TRUE(ink_module().HandleInputEvent(mouse_move_event)); + + VerifyAndClearExpectations(); + + // End text highlighting. The cursor should restore to the custom highlighter + // cursor. + EXPECT_CALL(client(), + UpdateInkCursor(CursorBitmapImageSizeEq(SkISize(10, 10)))); + blink::WebMouseEvent mouse_up_event = + MouseEventBuilder() + .CreateLeftMouseUpAtPosition(kEndPointInsidePage0) + .Build(); + EXPECT_TRUE(ink_module().HandleInputEvent(mouse_up_event)); +} + +TEST_P(PdfInkModuleTextHighlightTest, CursorOnMouseMoveWhileBrushDrawing) { + EnableAnnotationMode(); + InitializeSimpleSinglePageBasicLayout(); + + // Select the highlighter tool. The cursor should be the custom highlighter + // cursor. + EXPECT_CALL(client(), + UpdateInkCursor(CursorBitmapImageSizeEq(SkISize(10, 10)))); + TestAnnotationBrushMessageParams params = {/*color_r=*/0xF2, /*color_g=*/0x8B, + /*color_b=*/0x82, /*size=*/8.0f}; + SelectBrushTool(PdfInkBrush::Type::kHighlighter, params); + + VerifyAndClearExpectations(); + + // `kEndPointInsidePage0` will be the selectable text area position, while + // all other positions will be non-text areas. + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(_)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(client(), IsSelectableTextOrLinkArea(kEndPointInsidePage0)) + .WillRepeatedly(Return(true)); + + // Move to a non-text position. The cursor should remain as the custom + // highlighter cursor. + EXPECT_CALL(client(), UpdateInkCursor(_)).Times(0); + blink::WebMouseEvent mouse_move_event = + CreateMouseMoveEventAtPoint(kStartPointInsidePage0); + EXPECT_FALSE(ink_module().HandleInputEvent(mouse_move_event)); + + VerifyAndClearExpectations(); + + // Start drawing and move to a text position. The cursor should remain as the + // custom highlighter cursor. + EXPECT_CALL(client(), UpdateInkCursor(_)).Times(0); + blink::WebMouseEvent mouse_down_event = + MouseEventBuilder() + .CreateLeftClickAtPosition(kStartPointInsidePage0) + .Build(); + EXPECT_TRUE(ink_module().HandleInputEvent(mouse_down_event)); + mouse_move_event = + CreateMouseMoveWithLeftButtonEventAtPoint(kEndPointInsidePage0); + EXPECT_TRUE(ink_module().HandleInputEvent(mouse_move_event)); + + VerifyAndClearExpectations(); + + // End drawing. The cursor should be an I-beam. + EXPECT_CALL(client(), + UpdateInkCursor(ui::Cursor(ui::mojom::CursorType::kIBeam))); + blink::WebMouseEvent mouse_up_event = + MouseEventBuilder() + .CreateLeftMouseUpAtPosition(kEndPointInsidePage0) + .Build(); + EXPECT_TRUE(ink_module().HandleInputEvent(mouse_up_event)); +} + INSTANTIATE_TEST_SUITE_P(All, PdfInkModuleTest, testing::ValuesIn(GetAllInkTestVariations())); diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc index 7eed5a29b126b..bcb7097e3252b 100644 --- a/pdf/pdf_view_web_plugin.cc +++ b/pdf/pdf_view_web_plugin.cc @@ -300,6 +300,8 @@ class PdfViewWebPlugin::PdfInkModuleClientImpl : public PdfInkModuleClient { plugin_->engine_->ExtendSelectionByPoint(point); } + ui::Cursor GetCursor() override { return plugin_->cursor_; } + PageOrientation GetOrientation() const override { return plugin_->engine_->GetCurrentOrientation(); } diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc index 9d6d226d0a8df..87ade8d6e05b0 100644 --- a/pdf/pdf_view_web_plugin_unittest.cc +++ b/pdf/pdf_view_web_plugin_unittest.cc @@ -3049,6 +3049,16 @@ TEST_P(PdfViewWebPluginInkTest, SendThumbnailWithNoStrokes) { SendThumbnail(/*message_id=*/"foo", /*page_size=*/{50, 25}); } +TEST_P(PdfViewWebPluginInkTest, GetCursor) { + plugin_->set_cursor_type_for_testing(ui::mojom::CursorType::kPointer); + EXPECT_EQ(ui::mojom::CursorType::kPointer, + plugin_->ink_module_client_for_testing()->GetCursor().type()); + + plugin_->set_cursor_type_for_testing(ui::mojom::CursorType::kIBeam); + EXPECT_EQ(ui::mojom::CursorType::kIBeam, + plugin_->ink_module_client_for_testing()->GetCursor().type()); +} + TEST_P(PdfViewWebPluginInkTest, UpdateCursor) { UpdatePluginGeometryWithoutWaiting(2.0f, {0, 0, 20, 20});