Commit d6985724 authored by Ahmed Fakhry's avatar Ahmed Fakhry Committed by Commit Bot

Docked Magnifier: Confine mouse cursor outside the viewport

We observed that users who need this feature get very confused and
lost when the mouse cursor goes into the magnifier viewport. This
CL makes it work as expected by confining the cursor outside the
viewport.

BUG=815208
TEST=Added test coverage

Change-Id: If93ea59a2d0cca7f48f9da47dd28dc395c25bc85
Reviewed-on: https://chromium-review.googlesource.com/940261Reviewed-by: default avatarkylechar <kylechar@chromium.org>
Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Commit-Queue: Ahmed Fakhry <afakhry@chromium.org>
Cr-Commit-Position: refs/heads/master@{#539999}
parent 16fdb6c5
......@@ -16,6 +16,7 @@ class WindowTreeHost;
namespace gfx {
class Insets;
class Rect;
}
namespace ui {
......@@ -38,6 +39,13 @@ class ASH_EXPORT AshWindowTreeHost {
// nothing if allow_confine_cursor() returns false.
virtual void ConfineCursorToRootWindow() = 0;
// Clips the cursor to the given |bounds_in_root|.
virtual void ConfineCursorToBoundsInRoot(const gfx::Rect& bounds_in_root) = 0;
// Returns the last used bounds to confine the mouse cursor in the host's
// window's pixels.
virtual gfx::Rect GetLastCursorConfineBoundsInPixels() const = 0;
virtual void SetRootWindowTransformer(
std::unique_ptr<RootWindowTransformer> transformer) = 0;
......
......@@ -15,6 +15,8 @@
#include "ui/aura/window.h"
#include "ui/events/event_sink.h"
#include "ui/events/null_event_targeter.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
namespace ash {
......@@ -33,9 +35,25 @@ void AshWindowTreeHostMus::ConfineCursorToRootWindow() {
gfx::Rect confined_bounds(GetBoundsInPixels().size());
confined_bounds.Inset(transformer_helper_->GetHostInsets());
last_cursor_confine_bounds_in_pixels_ = confined_bounds;
ConfineCursorToBounds(confined_bounds);
}
void AshWindowTreeHostMus::ConfineCursorToBoundsInRoot(
const gfx::Rect& bounds_in_root) {
if (!allow_confine_cursor())
return;
gfx::RectF bounds_f(bounds_in_root);
GetRootTransform().TransformRect(&bounds_f);
last_cursor_confine_bounds_in_pixels_ = gfx::ToEnclosingRect(bounds_f);
ConfineCursorToBounds(last_cursor_confine_bounds_in_pixels_);
}
gfx::Rect AshWindowTreeHostMus::GetLastCursorConfineBoundsInPixels() const {
return last_cursor_confine_bounds_in_pixels_;
}
void AshWindowTreeHostMus::SetRootWindowTransformer(
std::unique_ptr<RootWindowTransformer> transformer) {
transformer_helper_->SetRootWindowTransformer(std::move(transformer));
......
......@@ -21,6 +21,8 @@ class AshWindowTreeHostMus : public AshWindowTreeHost,
// AshWindowTreeHost:
void ConfineCursorToRootWindow() override;
void ConfineCursorToBoundsInRoot(const gfx::Rect& bounds_in_root) override;
gfx::Rect GetLastCursorConfineBoundsInPixels() const override;
void SetRootWindowTransformer(
std::unique_ptr<RootWindowTransformer> transformer) override;
gfx::Insets GetHostInsets() const override;
......@@ -42,6 +44,8 @@ class AshWindowTreeHostMus : public AshWindowTreeHost,
private:
std::unique_ptr<TransformerHelper> transformer_helper_;
gfx::Rect last_cursor_confine_bounds_in_pixels_;
DISALLOW_COPY_AND_ASSIGN(AshWindowTreeHostMus);
};
......
......@@ -19,6 +19,8 @@
#include "ui/events/null_event_targeter.h"
#include "ui/events/ozone/chromeos/cursor_controller.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/transform.h"
#include "ui/platform_window/platform_window.h"
......@@ -44,9 +46,27 @@ void AshWindowTreeHostPlatform::ConfineCursorToRootWindow() {
gfx::Rect confined_bounds(GetBoundsInPixels().size());
confined_bounds.Inset(transformer_helper_.GetHostInsets());
last_cursor_confine_bounds_in_pixels_ = confined_bounds;
platform_window()->ConfineCursorToBounds(confined_bounds);
}
void AshWindowTreeHostPlatform::ConfineCursorToBoundsInRoot(
const gfx::Rect& bounds_in_root) {
if (!allow_confine_cursor())
return;
gfx::RectF bounds_f(bounds_in_root);
GetRootTransform().TransformRect(&bounds_f);
last_cursor_confine_bounds_in_pixels_ = gfx::ToEnclosingRect(bounds_f);
platform_window()->ConfineCursorToBounds(
last_cursor_confine_bounds_in_pixels_);
}
gfx::Rect AshWindowTreeHostPlatform::GetLastCursorConfineBoundsInPixels()
const {
return last_cursor_confine_bounds_in_pixels_;
}
void AshWindowTreeHostPlatform::SetCursorConfig(
const display::Display& display,
display::Display::Rotation rotation) {
......
......@@ -26,6 +26,8 @@ class ASH_EXPORT AshWindowTreeHostPlatform
// AshWindowTreeHost:
void ConfineCursorToRootWindow() override;
void ConfineCursorToBoundsInRoot(const gfx::Rect& bounds_in_root) override;
gfx::Rect GetLastCursorConfineBoundsInPixels() const override;
void SetRootWindowTransformer(
std::unique_ptr<RootWindowTransformer> transformer) override;
gfx::Insets GetHostInsets() const override;
......@@ -51,6 +53,8 @@ class ASH_EXPORT AshWindowTreeHostPlatform
TransformerHelper transformer_helper_;
gfx::Rect last_cursor_confine_bounds_in_pixels_;
DISALLOW_COPY_AND_ASSIGN(AshWindowTreeHostPlatform);
};
......
......@@ -2,4 +2,7 @@ specific_include_rules = {
"magnification_controller\.cc": [
"+ash/host"
],
"docked_magnifier_controller.*\.cc": [
"+ash/host"
],
}
......@@ -6,6 +6,7 @@
#include <algorithm>
#include "ash/host/ash_window_tree_host.h"
#include "ash/public/cpp/ash_pref_names.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
......@@ -341,6 +342,11 @@ void DockedMagnifierController::OnDisplayConfigurationChanged() {
SeparatorBoundsFromViewportBounds(viewport_bounds));
SetViewportHeightInWorkArea(current_source_root_window_,
separator_layer_->bounds().bottom());
// Resolution changes, screen rotation, etc. can reset the host to confine
// the mouse cursor inside the root window. We want to make sure the cursor
// is confined properly outside the viewport.
ConfineMouseCursorOutsideViewport();
}
// A change in display configuration, such as resolution, rotation, ... etc.
......@@ -408,6 +414,11 @@ void DockedMagnifierController::SwitchCurrentSourceRootWindowIfNeeded(
GetInputMethodForWindow(old_root_window);
if (old_input_method)
old_input_method->RemoveObserver(this);
// Reset mouse cursor confinement to default.
RootWindowController::ForWindow(old_root_window)
->ash_host()
->ConfineCursorToRootWindow();
}
// A change in the current root window means we must clear the existing
......@@ -585,6 +596,9 @@ void DockedMagnifierController::CreateMagnifierViewport() {
// screen.
SetViewportHeightInWorkArea(current_source_root_window_,
separator_layer_->bounds().bottom());
// 6- Confine the mouse cursor within the remaining part of the display.
ConfineMouseCursorOutsideViewport();
}
void DockedMagnifierController::MaybeCachePointOfInterestMinimumHeight(
......@@ -675,4 +689,17 @@ void DockedMagnifierController::MaybeCachePointOfInterestMinimumHeight(
is_minimum_point_of_interest_height_valid_ = true;
}
void DockedMagnifierController::ConfineMouseCursorOutsideViewport() {
DCHECK(current_source_root_window_);
gfx::Rect confine_bounds =
current_source_root_window_->GetBoundsInRootWindow();
const int docked_height = separator_layer_->bounds().bottom();
confine_bounds.Offset(0, docked_height);
confine_bounds.set_height(confine_bounds.height() - docked_height);
RootWindowController::ForWindow(current_source_root_window_)
->ash_host()
->ConfineCursorToBoundsInRoot(confine_bounds);
}
} // namespace ash
......@@ -150,6 +150,10 @@ class ASH_EXPORT DockedMagnifierController
// viewport shows a magnified version of itself.
void MaybeCachePointOfInterestMinimumHeight(aura::WindowTreeHost* host);
// Prevents the mouse cursor from being able to enter inside the magnifier
// viewport.
void ConfineMouseCursorOutsideViewport();
// The current root window of the source display from which we are reflecting
// and magnifying into the viewport. It is set to |nullptr| when the magnifier
// is disabled. The viewport is placed on the same display.
......
......@@ -8,9 +8,12 @@
#include <vector>
#include "ash/display/display_util.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/host/ash_window_tree_host.h"
#include "ash/magnifier/magnifier_test_utils.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/ash_pref_names.h"
#include "ash/public/cpp/ash_switches.h"
#include "ash/public/cpp/config.h"
#include "ash/public/interfaces/docked_magnifier_controller.mojom.h"
#include "ash/session/session_controller.h"
......@@ -19,6 +22,7 @@
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "base/command_line.h"
#include "base/test/scoped_feature_list.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/session_manager_types.h"
......@@ -91,6 +95,11 @@ class DockedMagnifierTest : public NoSessionAshTestBase {
// Explicitly enable the Docked Magnifier feature for the tests.
scoped_feature_list_.InitAndEnableFeature(features::kDockedMagnifier);
// Explicitly enable --ash-constrain-pointer-to-root to be able to test
// mouse cursor confinement outside the magnifier viewport.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kAshConstrainPointerToRoot);
NoSessionAshTestBase::SetUp();
// Create user 1 session and simulate its login.
......@@ -232,6 +241,13 @@ TEST_F(DockedMagnifierTest, DisplaysWorkAreas) {
gfx::Rect disp_1_workarea_no_magnifier = disp_1_bounds;
disp_1_workarea_no_magnifier.Inset(0, 0, 0, kShelfSize);
EXPECT_EQ(disp_1_workarea_no_magnifier, display_1.work_area());
// At this point, normal mouse cursor confinement should be used.
AshWindowTreeHost* host1 =
Shell::Get()
->window_tree_host_manager()
->GetAshWindowTreeHostForDisplayId(display_1.id());
EXPECT_EQ(host1->GetLastCursorConfineBoundsInPixels(),
gfx::Rect(gfx::Point(0, 0), disp_1_bounds.size()));
const display::Display& display_2 = display_manager()->GetDisplayAt(1);
const gfx::Rect disp_2_bounds(800, 0, 400, 300);
......@@ -239,6 +255,12 @@ TEST_F(DockedMagnifierTest, DisplaysWorkAreas) {
gfx::Rect disp_2_workarea_no_magnifier = disp_2_bounds;
disp_2_workarea_no_magnifier.Inset(0, 0, 0, kShelfSize);
EXPECT_EQ(disp_2_workarea_no_magnifier, display_2.work_area());
AshWindowTreeHost* host2 =
Shell::Get()
->window_tree_host_manager()
->GetAshWindowTreeHostForDisplayId(display_2.id());
EXPECT_EQ(host2->GetLastCursorConfineBoundsInPixels(),
gfx::Rect(gfx::Point(0, 0), disp_2_bounds.size()));
// Enable the magnifier and the check the workareas.
controller()->SetEnabled(true);
......@@ -252,17 +274,25 @@ TEST_F(DockedMagnifierTest, DisplaysWorkAreas) {
// be further shrunk from the top by 1/4th of its full height + the height of
// the separator layer.
gfx::Rect disp_1_workspace_with_magnifier = disp_1_workarea_no_magnifier;
disp_1_workspace_with_magnifier.Inset(
0,
const int disp_1_magnifier_height =
(disp_1_bounds.height() /
DockedMagnifierController::kScreenHeightDevisor) +
DockedMagnifierController::kSeparatorHeight,
0, 0);
DockedMagnifierController::kSeparatorHeight;
disp_1_workspace_with_magnifier.Inset(0, disp_1_magnifier_height, 0, 0);
EXPECT_EQ(disp_1_bounds, display_1.bounds());
EXPECT_EQ(disp_1_workspace_with_magnifier, display_1.work_area());
// The first display should confine the mouse movement outside of the
// viewport.
const gfx::Rect disp_1_confine_bounds(
0, disp_1_magnifier_height, disp_1_bounds.width(),
disp_1_bounds.height() - disp_1_magnifier_height);
EXPECT_EQ(host1->GetLastCursorConfineBoundsInPixels(), disp_1_confine_bounds);
// The second display should remain unaffected.
EXPECT_EQ(disp_2_bounds, display_2.bounds());
EXPECT_EQ(disp_2_workarea_no_magnifier, display_2.work_area());
EXPECT_EQ(host2->GetLastCursorConfineBoundsInPixels(),
gfx::Rect(gfx::Point(0, 0), disp_2_bounds.size()));
// Now, move mouse cursor to display 2, and expect that the workarea of
// display 1 is restored to its original value, while that of display 2 is
......@@ -276,15 +306,22 @@ TEST_F(DockedMagnifierTest, DisplaysWorkAreas) {
viewport_2_widget->GetNativeView()->GetRootWindow());
EXPECT_EQ(disp_1_bounds, display_1.bounds());
EXPECT_EQ(disp_1_workarea_no_magnifier, display_1.work_area());
// Display 1 goes back to the normal mouse confinement.
EXPECT_EQ(host1->GetLastCursorConfineBoundsInPixels(),
gfx::Rect(gfx::Point(0, 0), disp_1_bounds.size()));
EXPECT_EQ(disp_2_bounds, display_2.bounds());
gfx::Rect disp_2_workspace_with_magnifier = disp_2_workarea_no_magnifier;
disp_2_workspace_with_magnifier.Inset(
0,
const int disp_2_magnifier_height =
(disp_2_bounds.height() /
DockedMagnifierController::kScreenHeightDevisor) +
DockedMagnifierController::kSeparatorHeight,
0, 0);
DockedMagnifierController::kSeparatorHeight;
disp_2_workspace_with_magnifier.Inset(0, disp_2_magnifier_height, 0, 0);
EXPECT_EQ(disp_2_workspace_with_magnifier, display_2.work_area());
// Display 2's mouse is confined outside the viewport.
const gfx::Rect disp_2_confine_bounds(
0, disp_2_magnifier_height, disp_2_bounds.width(),
disp_2_bounds.height() - disp_2_magnifier_height);
EXPECT_EQ(host2->GetLastCursorConfineBoundsInPixels(), disp_2_confine_bounds);
// Now, disable the magnifier, and expect both displays to return back to
// their original state.
......@@ -294,6 +331,11 @@ TEST_F(DockedMagnifierTest, DisplaysWorkAreas) {
EXPECT_EQ(disp_1_workarea_no_magnifier, display_1.work_area());
EXPECT_EQ(disp_2_bounds, display_2.bounds());
EXPECT_EQ(disp_2_workarea_no_magnifier, display_2.work_area());
// Normal mouse confinement for both displays.
EXPECT_EQ(host1->GetLastCursorConfineBoundsInPixels(),
gfx::Rect(gfx::Point(0, 0), disp_1_bounds.size()));
EXPECT_EQ(host2->GetLastCursorConfineBoundsInPixels(),
gfx::Rect(gfx::Point(0, 0), disp_2_bounds.size()));
}
// Tests the behavior of the magnifier when displays are added or removed.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment