diff options
author | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-01 00:40:40 +0000 |
---|---|---|
committer | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-01 00:40:40 +0000 |
commit | 8b7ba87b074c458fe7b1b6aa3e4a2a663729621a (patch) | |
tree | fee2d4aa25547fef5a42fa340dd8e7ea7023092e /ash/display | |
parent | eb7956341bd8835c5f9254e62638f8635f7c268f (diff) | |
download | chromium_src-8b7ba87b074c458fe7b1b6aa3e4a2a663729621a.zip chromium_src-8b7ba87b074c458fe7b1b6aa3e4a2a663729621a.tar.gz chromium_src-8b7ba87b074c458fe7b1b6aa3e4a2a663729621a.tar.bz2 |
Allow "snapping" while dragging a window to another display
by reserving the corner that blocks pass through.
Cleanup:
Move the code that handle warp from DisplayCotnroller to MouseCursorEventFilter.
Removed unnecesary namespace, dead code.
BUG=143289
TEST=added new tests. manual=connect external monitor and drag a window to another display. a warp hole will be shown at the edge where you can drag a window into. Dragging to other place should start snapping operation.
Review URL: https://chromiumcodereview.appspot.com/10899034
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@154551 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/display')
-rw-r--r-- | ash/display/display_controller.cc | 79 | ||||
-rw-r--r-- | ash/display/display_controller.h | 18 | ||||
-rw-r--r-- | ash/display/display_controller_unittest.cc | 115 | ||||
-rw-r--r-- | ash/display/mouse_cursor_event_filter.cc | 204 | ||||
-rw-r--r-- | ash/display/mouse_cursor_event_filter.h | 64 | ||||
-rw-r--r-- | ash/display/mouse_cursor_event_filter_unittest.cc | 268 | ||||
-rw-r--r-- | ash/display/shared_display_edge_indicator.cc | 94 | ||||
-rw-r--r-- | ash/display/shared_display_edge_indicator.h | 61 |
8 files changed, 706 insertions, 197 deletions
diff --git a/ash/display/display_controller.cc b/ash/display/display_controller.cc index d32b4db..1a2ed63 100644 --- a/ash/display/display_controller.cc +++ b/ash/display/display_controller.cc @@ -39,8 +39,7 @@ const int kMinimumOverlapForInvalidOffset = 50; DisplayController::DisplayController() : secondary_display_layout_(RIGHT), - secondary_display_offset_(0), - dont_warp_mouse_(false) { + secondary_display_offset_(0) { aura::Env::GetInstance()->display_manager()->AddObserver(this); } @@ -73,6 +72,24 @@ void DisplayController::InitSecondaryDisplays() { aura::RootWindow* root = AddRootWindowForDisplay(*display); Shell::GetInstance()->InitRootWindowForSecondaryDisplay(root); } + CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kAshSecondaryDisplayLayout)) { + std::string value = command_line->GetSwitchValueASCII( + switches::kAshSecondaryDisplayLayout); + char layout; + int offset; + if (sscanf(value.c_str(), "%c,%d", &layout, &offset) == 2) { + if (layout == 't') + secondary_display_layout_ = TOP; + else if (layout == 'b') + secondary_display_layout_ = BOTTOM; + else if (layout == 'r') + secondary_display_layout_ = RIGHT; + else if (layout == 'l') + secondary_display_layout_ = LEFT; + secondary_display_offset_ = offset; + } + } UpdateDisplayBoundsForLayout(); } @@ -139,52 +156,6 @@ void DisplayController::SetSecondaryDisplayOffset(int offset) { UpdateDisplayBoundsForLayout(); } -bool DisplayController::WarpMouseCursorIfNecessary( - aura::RootWindow* current_root, - const gfx::Point& point_in_root) { - if (root_windows_.size() < 2 || dont_warp_mouse_) - return false; - const float scale = ui::GetDeviceScaleFactor(current_root->layer()); - - // The pointer might be outside the |current_root|. Get the root window where - // the pointer is currently on. - std::pair<aura::RootWindow*, gfx::Point> actual_location = - wm::GetRootWindowRelativeToWindow(current_root, point_in_root); - current_root = actual_location.first; - // Don't use |point_in_root| below. Instead, use |actual_location.second| - // which is in |actual_location.first|'s coordinates. - - gfx::Rect root_bounds = current_root->bounds(); - int offset_x = 0; - int offset_y = 0; - if (actual_location.second.x() <= root_bounds.x()) { - // Use -2, not -1, to avoid infinite loop of pointer warp. - offset_x = -2 * scale; - } else if (actual_location.second.x() >= root_bounds.right() - 1) { - offset_x = 2 * scale; - } else if (actual_location.second.y() <= root_bounds.y()) { - offset_y = -2 * scale; - } else if (actual_location.second.y() >= root_bounds.bottom() - 1) { - offset_y = 2 * scale; - } else { - return false; - } - - gfx::Point point_in_screen(actual_location.second); - wm::ConvertPointToScreen(current_root, &point_in_screen); - point_in_screen.Offset(offset_x, offset_y); - - aura::RootWindow* dst_root = wm::GetRootWindowAt(point_in_screen); - gfx::Point point_in_dst_root(point_in_screen); - wm::ConvertPointFromScreen(dst_root, &point_in_dst_root); - - if (dst_root->bounds().Contains(point_in_dst_root)) { - DCHECK_NE(dst_root, current_root); - dst_root->MoveCursorTo(point_in_dst_root); - return true; - } - return false; -} void DisplayController::OnDisplayBoundsChanged(const gfx::Display& display) { root_windows_[display.id()]->SetHostBounds(display.bounds_in_pixel()); @@ -192,11 +163,7 @@ void DisplayController::OnDisplayBoundsChanged(const gfx::Display& display) { } void DisplayController::OnDisplayAdded(const gfx::Display& display) { - if (root_windows_.empty()) { - root_windows_[display.id()] = Shell::GetPrimaryRootWindow(); - Shell::GetPrimaryRootWindow()->SetHostBounds(display.bounds_in_pixel()); - return; - } + DCHECK(!root_windows_.empty()); aura::RootWindow* root = AddRootWindowForDisplay(display); Shell::GetInstance()->InitRootWindowForSecondaryDisplay(root); UpdateDisplayBoundsForLayout(); @@ -206,15 +173,15 @@ void DisplayController::OnDisplayRemoved(const gfx::Display& display) { aura::RootWindow* root = root_windows_[display.id()]; DCHECK(root); // Primary display should never be removed by DisplayManager. - DCHECK(root != Shell::GetPrimaryRootWindow()); + DCHECK(root != GetPrimaryRootWindow()); // Display for root window will be deleted when the Primary RootWindow // is deleted by the Shell. - if (root != Shell::GetPrimaryRootWindow()) { + if (root != GetPrimaryRootWindow()) { root_windows_.erase(display.id()); internal::RootWindowController* controller = GetRootWindowController(root); if (controller) { - controller->MoveWindowsTo(Shell::GetPrimaryRootWindow()); + controller->MoveWindowsTo(GetPrimaryRootWindow()); delete controller; } else { delete root; diff --git a/ash/display/display_controller.h b/ash/display/display_controller.h index 1d466a7..324214f 100644 --- a/ash/display/display_controller.h +++ b/ash/display/display_controller.h @@ -11,7 +11,6 @@ #include "ash/ash_export.h" #include "base/basictypes.h" #include "base/compiler_specific.h" -#include "base/gtest_prod_util.h" #include "ui/aura/display_observer.h" #include "ui/aura/display_manager.h" @@ -72,18 +71,6 @@ class ASH_EXPORT DisplayController : public aura::DisplayObserver { } void SetSecondaryDisplayOffset(int offset); - void set_dont_warp_mouse(bool dont_warp_mouse) { - dont_warp_mouse_ = dont_warp_mouse; - } - - // Warps the mouse cursor to an alternate root window when the - // |point_in_root|, which is the location of the mouse cursor, - // hits or exceeds the edge of the |root_window| and the mouse cursor - // is considered to be in an alternate display. Returns true if - // the cursor was moved. - bool WarpMouseCursorIfNecessary(aura::RootWindow* root_window, - const gfx::Point& point_in_root); - // aura::DisplayObserver overrides: virtual void OnDisplayBoundsChanged( const gfx::Display& display) OVERRIDE; @@ -91,8 +78,6 @@ class ASH_EXPORT DisplayController : public aura::DisplayObserver { virtual void OnDisplayRemoved(const gfx::Display& display) OVERRIDE; private: - FRIEND_TEST_ALL_PREFIXES(WorkspaceWindowResizerTest, WarpMousePointer); - // Creates a root window for |display| and stores it in the |root_windows_| // map. aura::RootWindow* AddRootWindowForDisplay(const gfx::Display& display); @@ -108,9 +93,6 @@ class ASH_EXPORT DisplayController : public aura::DisplayObserver { // based on the top/left edge of the primary display. int secondary_display_offset_; - // If true, the mouse pointer can't move from one display to another. - bool dont_warp_mouse_; - DISALLOW_COPY_AND_ASSIGN(DisplayController); }; diff --git a/ash/display/display_controller_unittest.cc b/ash/display/display_controller_unittest.cc index 055ca4b..7f2d461 100644 --- a/ash/display/display_controller_unittest.cc +++ b/ash/display/display_controller_unittest.cc @@ -109,120 +109,5 @@ TEST_F(DisplayControllerTest, DISABLED_BoundsUpdated) { EXPECT_EQ("0,700 1000x1000", GetSecondaryDisplay().bounds().ToString()); } -// Verifies if the mouse pointer correctly moves to another display when there -// are two displays. -TEST_F(DisplayControllerTest, WarpMouse) { - UpdateDisplay("500x500,500x500"); - - ash::internal::DisplayController* controller = - Shell::GetInstance()->display_controller(); - EXPECT_EQ(internal::DisplayController::RIGHT, - controller->secondary_display_layout()); - - Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); - bool is_warped = controller->WarpMouseCursorIfNecessary(root_windows[0], - gfx::Point(11, 11)); - EXPECT_FALSE(is_warped); - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[1], - gfx::Point(11, 11)); - EXPECT_FALSE(is_warped); - - // Touch the right edge of the primary root window. Pointer should warp. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[0], - gfx::Point(499, 11)); - EXPECT_TRUE(is_warped); - EXPECT_EQ("501,11", // by 2px. - aura::Env::GetInstance()->last_mouse_location().ToString()); - - // Touch the left edge of the secondary root window. Pointer should warp. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[1], - gfx::Point(0, 11)); - EXPECT_TRUE(is_warped); - EXPECT_EQ("498,11", // by 2px. - aura::Env::GetInstance()->last_mouse_location().ToString()); - - // Touch the left edge of the primary root window. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[0], - gfx::Point(0, 11)); - EXPECT_FALSE(is_warped); - // Touch the top edge of the primary root window. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[0], - gfx::Point(11, 0)); - EXPECT_FALSE(is_warped); - // Touch the bottom edge of the primary root window. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[0], - gfx::Point(11, 499)); - EXPECT_FALSE(is_warped); - // Touch the right edge of the secondary root window. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[1], - gfx::Point(499, 11)); - EXPECT_FALSE(is_warped); - // Touch the top edge of the secondary root window. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[1], - gfx::Point(11, 0)); - EXPECT_FALSE(is_warped); - // Touch the bottom edge of the secondary root window. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[1], - gfx::Point(11, 499)); - EXPECT_FALSE(is_warped); -} - -// Verifies if the mouse pointer correctly moves to another display even when -// two displays are not the same size. -TEST_F(DisplayControllerTest, WarpMouseDifferentSizeDisplays) { - UpdateDisplay("500x500,600x600"); // the second one is larger. - - ash::internal::DisplayController* controller = - Shell::GetInstance()->display_controller(); - EXPECT_EQ(internal::DisplayController::RIGHT, - controller->secondary_display_layout()); - - Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); - aura::Env::GetInstance()->SetLastMouseLocation(*root_windows[1], - gfx::Point(123, 123)); - - // Touch the left edge of the secondary root window. Pointer should NOT warp - // because 1px left of (0, 500) is outside the primary root window. - bool is_warped = controller->WarpMouseCursorIfNecessary(root_windows[1], - gfx::Point(0, 500)); - EXPECT_FALSE(is_warped); - EXPECT_EQ("623,123", // by 2px. - aura::Env::GetInstance()->last_mouse_location().ToString()); - - // Touch the left edge of the secondary root window. Pointer should warp - // because 1px left of (0, 499) is inside the primary root window. - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[1], - gfx::Point(0, 499)); - EXPECT_TRUE(is_warped); - EXPECT_EQ("498,499", // by 2px. - aura::Env::GetInstance()->last_mouse_location().ToString()); -} - -// Verifies if DisplayController::dont_warp_mouse() works as expected. -TEST_F(DisplayControllerTest, SetUnsetDontWarpMousedFlag) { - UpdateDisplay("500x500,500x500"); - - ash::internal::DisplayController* controller = - Shell::GetInstance()->display_controller(); - - Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); - aura::Env::GetInstance()->SetLastMouseLocation(*root_windows[0], - gfx::Point(1, 1)); - - controller->set_dont_warp_mouse(true); - bool is_warped = controller->WarpMouseCursorIfNecessary(root_windows[0], - gfx::Point(499, 11)); - EXPECT_FALSE(is_warped); - EXPECT_EQ("1,1", - aura::Env::GetInstance()->last_mouse_location().ToString()); - - controller->set_dont_warp_mouse(false); - is_warped = controller->WarpMouseCursorIfNecessary(root_windows[0], - gfx::Point(499, 11)); - EXPECT_TRUE(is_warped); - EXPECT_EQ("501,11", - aura::Env::GetInstance()->last_mouse_location().ToString()); -} - } // namespace test } // namespace ash diff --git a/ash/display/mouse_cursor_event_filter.cc b/ash/display/mouse_cursor_event_filter.cc index d9ec8b5..d2ad3e4 100644 --- a/ash/display/mouse_cursor_event_filter.cc +++ b/ash/display/mouse_cursor_event_filter.cc @@ -5,23 +5,69 @@ #include "ash/display/mouse_cursor_event_filter.h" #include "ash/display/display_controller.h" +#include "ash/display/shared_display_edge_indicator.h" #include "ash/shell.h" +#include "ash/wm/coordinate_conversion.h" #include "ash/wm/cursor_manager.h" +#include "ash/wm/window_util.h" #include "ui/aura/env.h" #include "ui/aura/root_window.h" #include "ui/aura/window.h" #include "ui/base/event.h" +#include "ui/base/layout.h" +#include "ui/compositor/dip_util.h" +#include "ui/gfx/screen.h" namespace ash { namespace internal { +namespace { -MouseCursorEventFilter::MouseCursorEventFilter( - DisplayController* display_controller) - : display_controller_(display_controller) { - DCHECK(display_controller_); +// Maximum size on the display edge that initiate snapping phantom window, +// from the corner of the display. +const int kMaximumSnapHeight = 100; + +// Minimum hight of an indicator on the display edge that allows +// dragging a window. If two displays shares the edge smaller than +// this, entire edge will be used as a draggable space. +const int kMinimumIndicatorHeight = 200; + +const int kIndicatorThickness = 10; +} + +MouseCursorEventFilter::MouseCursorEventFilter() + : mouse_warp_mode_(WARP_ALWAYS), + drag_source_root_(NULL), + shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) { } MouseCursorEventFilter::~MouseCursorEventFilter() { + HideSharedEdgeIndicator(); +} + +void MouseCursorEventFilter::ShowSharedEdgeIndicator( + const aura::RootWindow* from) { + HideSharedEdgeIndicator(); + if (gfx::Screen::GetNumDisplays() <= 1 || from == NULL) { + src_indicator_bounds_.SetRect(0, 0, 0, 0); + dst_indicator_bounds_.SetRect(0, 0, 0, 0); + drag_source_root_ = NULL; + return; + } + drag_source_root_ = from; + + DisplayController::SecondaryDisplayLayout layout = + Shell::GetInstance()->display_controller()->secondary_display_layout(); + if (layout == DisplayController::TOP || layout == DisplayController::BOTTOM) + UpdateHorizontalIndicatorWindowBounds(); + else + UpdateVerticalIndicatorWindowBounds(); + + shared_display_edge_indicator_->Show(src_indicator_bounds_, + dst_indicator_bounds_); +} + +void MouseCursorEventFilter::HideSharedEdgeIndicator() { + shared_display_edge_indicator_->Hide(); } bool MouseCursorEventFilter::PreHandleKeyEvent(aura::Window* target, @@ -42,8 +88,7 @@ bool MouseCursorEventFilter::PreHandleMouseEvent(aura::Window* target, aura::RootWindow* current_root = target->GetRootWindow(); gfx::Point location_in_root(event->location()); aura::Window::ConvertPointToTarget(target, current_root, &location_in_root); - return display_controller_->WarpMouseCursorIfNecessary( - current_root, location_in_root); + return WarpMouseCursorIfNecessary(current_root, location_in_root); } ui::TouchStatus MouseCursorEventFilter::PreHandleTouchEvent( @@ -58,5 +103,152 @@ ui::GestureStatus MouseCursorEventFilter::PreHandleGestureEvent( return ui::GESTURE_STATUS_UNKNOWN; } +bool MouseCursorEventFilter::WarpMouseCursorIfNecessary( + aura::RootWindow* current_root, + const gfx::Point& point_in_root) { + if (gfx::Screen::GetNumDisplays() <= 1 || mouse_warp_mode_ == WARP_NONE) + return false; + const float scale = ui::GetDeviceScaleFactor(current_root->layer()); + + // The pointer might be outside the |current_root|. Get the root window where + // the pointer is currently on. + std::pair<aura::RootWindow*, gfx::Point> actual_location = + wm::GetRootWindowRelativeToWindow(current_root, point_in_root); + current_root = actual_location.first; + // Don't use |point_in_root| below. Instead, use |actual_location.second| + // which is in |actual_location.first|'s coordinates. + + gfx::Rect root_bounds = current_root->bounds(); + int offset_x = 0; + int offset_y = 0; + if (actual_location.second.x() <= root_bounds.x()) { + // Use -2, not -1, to avoid infinite loop of pointer warp. + offset_x = -2 * scale; + } else if (actual_location.second.x() >= root_bounds.right() - 1) { + offset_x = 2 * scale; + } else if (actual_location.second.y() <= root_bounds.y()) { + offset_y = -2 * scale; + } else if (actual_location.second.y() >= root_bounds.bottom() - 1) { + offset_y = 2 * scale; + } else { + return false; + } + + gfx::Point point_in_screen(actual_location.second); + wm::ConvertPointToScreen(current_root, &point_in_screen); + gfx::Point point_in_dst_screen(point_in_screen); + point_in_dst_screen.Offset(offset_x, offset_y); + aura::RootWindow* dst_root = wm::GetRootWindowAt(point_in_dst_screen); + + // Warp the mouse cursor only if the location is in the indicator bounds. + if (mouse_warp_mode_ == WARP_DRAG && + dst_root != drag_source_root_ && + !src_indicator_bounds_.Contains(point_in_screen)) { + return false; + } + + wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen); + + if (dst_root->bounds().Contains(point_in_dst_screen)) { + DCHECK_NE(dst_root, current_root); + dst_root->MoveCursorTo(point_in_dst_screen); + return true; + } + return false; +} + +void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() { + bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; + + aura::DisplayManager* display_manager = + aura::Env::GetInstance()->display_manager(); + const gfx::Rect& primary_bounds = display_manager->GetDisplayAt(0)->bounds(); + const gfx::Rect& secondary_bounds = + display_manager->GetDisplayAt(1)->bounds(); + DisplayController::SecondaryDisplayLayout layout = + Shell::GetInstance()->display_controller()->secondary_display_layout(); + + src_indicator_bounds_.set_x( + std::max(primary_bounds.x(), secondary_bounds.x())); + src_indicator_bounds_.set_width( + std::min(primary_bounds.right(), secondary_bounds.right()) - + src_indicator_bounds_.x()); + src_indicator_bounds_.set_height(kIndicatorThickness); + src_indicator_bounds_.set_y( + layout == DisplayController::TOP ? + primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) : + primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0)); + + dst_indicator_bounds_ = src_indicator_bounds_; + dst_indicator_bounds_.set_height(kIndicatorThickness); + dst_indicator_bounds_.set_y( + layout == DisplayController::TOP ? + primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) : + primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness)); +} + +void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() { + bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; + aura::DisplayManager* display_manager = + aura::Env::GetInstance()->display_manager(); + const gfx::Rect& primary_bounds = display_manager->GetDisplayAt(0)->bounds(); + const gfx::Rect& secondary_bounds = + display_manager->GetDisplayAt(1)->bounds(); + DisplayController::SecondaryDisplayLayout layout = + Shell::GetInstance()->display_controller()->secondary_display_layout(); + + int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y()); + int lower_shared_y = std::min(primary_bounds.bottom(), + secondary_bounds.bottom()); + int shared_height = lower_shared_y - upper_shared_y; + + int dst_x = layout == DisplayController::LEFT ? + primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) : + primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness); + dst_indicator_bounds_.SetRect( + dst_x, upper_shared_y, kIndicatorThickness, shared_height); + + // The indicator on the source display. + src_indicator_bounds_.set_width(kIndicatorThickness); + src_indicator_bounds_.set_x( + layout == DisplayController::LEFT ? + primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) : + primary_bounds.right() - (in_primary ? kIndicatorThickness : 0)); + + const gfx::Rect& source_bounds = + in_primary ? primary_bounds : secondary_bounds; + int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight; + int lower_indicator_y = source_bounds.bottom() - kMaximumSnapHeight; + + // This gives a hight that can be used without sacrifying the snap space. + int available_space = std::min(lower_shared_y, lower_indicator_y) - + std::max(upper_shared_y, upper_indicator_y); + + if (shared_height < kMinimumIndicatorHeight) { + // If the shared height is smaller than minimum height, use the + // entire height; + upper_indicator_y = upper_shared_y; + lower_indicator_y = lower_shared_y; + } else if (available_space < kMinimumIndicatorHeight) { + // Need to shrink the snappnig space. + int diff = (kMinimumIndicatorHeight - available_space) / 2; + upper_indicator_y -= diff; // expand to upwards. + if (upper_indicator_y < upper_shared_y) { + diff += (upper_shared_y - upper_indicator_y); + upper_indicator_y = upper_shared_y; + } + lower_indicator_y += diff; // expand to downwards. + if (lower_indicator_y > lower_shared_y) { + upper_indicator_y -= (lower_indicator_y - lower_shared_y); + lower_indicator_y = lower_shared_y; + } + } else { + upper_indicator_y = std::max(upper_indicator_y, upper_shared_y); + lower_indicator_y = std::min(lower_shared_y, lower_indicator_y); + } + src_indicator_bounds_.set_y(upper_indicator_y); + src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y); +} + } // namespace internal } // namespace ash diff --git a/ash/display/mouse_cursor_event_filter.h b/ash/display/mouse_cursor_event_filter.h index 937d73d..03e67bc 100644 --- a/ash/display/mouse_cursor_event_filter.h +++ b/ash/display/mouse_cursor_event_filter.h @@ -7,19 +7,44 @@ #include "ash/ash_export.h" #include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" #include "ui/aura/event_filter.h" +#include "ui/gfx/rect.h" + +namespace aura { +class RootWindow; +} namespace ash { namespace internal { class DisplayController; +class SharedDisplayEdgeIndicator; // An event filter that controls mouse location in extended desktop // environment. class ASH_EXPORT MouseCursorEventFilter : public aura::EventFilter { public: - MouseCursorEventFilter(DisplayController* display_controller); + enum MouseWarpMode { + WARP_ALWAYS, // Always warp the mouse when possible. + WARP_DRAG, // Used when dragging a window. Top and bottom + // corner of the shared edge is reserved for window + // snapping. + WARP_NONE, // No mouse warping. Used when resizing the window. + }; + + MouseCursorEventFilter(); virtual ~MouseCursorEventFilter(); + void set_mouse_warp_mode(MouseWarpMode mouse_warp_mode) { + mouse_warp_mode_ = mouse_warp_mode; + } + + // Shows/Hide the indicator for window dragging. The |from| + // is the window where the dragging started. + void ShowSharedEdgeIndicator(const aura::RootWindow* from); + void HideSharedEdgeIndicator(); + // Overridden from aura::EventFilter: virtual bool PreHandleKeyEvent(aura::Window* target, ui::KeyEvent* event) OVERRIDE; @@ -33,7 +58,42 @@ class ASH_EXPORT MouseCursorEventFilter : public aura::EventFilter { ui::GestureEvent* event) OVERRIDE; private: - DisplayController* display_controller_; + FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, SetMouseWarpModeFlag); + FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, WarpMouse); + FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, + WarpMouseDifferentSizeDisplays); + FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, + IndicatorBoundsTestOnRight); + FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, + IndicatorBoundsTestOnLeft); + FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, + IndicatorBoundsTestOnTopBottom); + FRIEND_TEST_ALL_PREFIXES(WorkspaceWindowResizerTest, WarpMousePointer); + + // Warps the mouse cursor to an alternate root window when the + // |point_in_root|, which is the location of the mouse cursor, + // hits or exceeds the edge of the |root_window| and the mouse cursor + // is considered to be in an alternate display. Returns true if + // the cursor was moved. + bool WarpMouseCursorIfNecessary(aura::RootWindow* root_window, + const gfx::Point& point_in_root); + + void UpdateHorizontalIndicatorWindowBounds(); + void UpdateVerticalIndicatorWindowBounds(); + + MouseWarpMode mouse_warp_mode_; + + // The bounds for warp hole windows. |dst_indicator_bounds_| is kept + // in the instance for testing. + gfx::Rect src_indicator_bounds_; + gfx::Rect dst_indicator_bounds_; + + // The root window in which the dragging started. + const aura::RootWindow* drag_source_root_; + + // Shows the area where a window can be dragged in to/out from + // another display. + scoped_ptr<SharedDisplayEdgeIndicator> shared_display_edge_indicator_; DISALLOW_COPY_AND_ASSIGN(MouseCursorEventFilter); }; diff --git a/ash/display/mouse_cursor_event_filter_unittest.cc b/ash/display/mouse_cursor_event_filter_unittest.cc new file mode 100644 index 0000000..b7421e1 --- /dev/null +++ b/ash/display/mouse_cursor_event_filter_unittest.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2012 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 "ash/display/mouse_cursor_event_filter.h" + +#include "ash/shell.h" +#include "ash/test/ash_test_base.h" +#include "ash/display/display_controller.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/gfx/display.h" +#include "ui/gfx/screen.h" + +namespace ash { +namespace internal { +namespace { + +gfx::Display GetPrimaryDisplay() { + return gfx::Screen::GetDisplayNearestWindow( + Shell::GetAllRootWindows()[0]); +} + +gfx::Display GetSecondaryDisplay() { + return gfx::Screen::GetDisplayNearestWindow( + Shell::GetAllRootWindows()[1]); +} + +} // namespace + +typedef test::AshTestBase MouseCursorEventFilterTest; + +// Verifies if the mouse pointer correctly moves to another display when there +// are two displays. +TEST_F(MouseCursorEventFilterTest, WarpMouse) { + UpdateDisplay("500x500,500x500"); + + MouseCursorEventFilter* event_filter = + Shell::GetInstance()->mouse_cursor_filter(); + ASSERT_EQ( + DisplayController::RIGHT, + Shell::GetInstance()->display_controller()->secondary_display_layout()); + + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + bool is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0], + gfx::Point(11, 11)); + EXPECT_FALSE(is_warped); + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1], + gfx::Point(11, 11)); + EXPECT_FALSE(is_warped); + + // Touch the right edge of the primary root window. Pointer should warp. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0], + gfx::Point(499, 11)); + EXPECT_TRUE(is_warped); + EXPECT_EQ("501,11", // by 2px. + aura::Env::GetInstance()->last_mouse_location().ToString()); + + // Touch the left edge of the secondary root window. Pointer should warp. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1], + gfx::Point(0, 11)); + EXPECT_TRUE(is_warped); + EXPECT_EQ("498,11", // by 2px. + aura::Env::GetInstance()->last_mouse_location().ToString()); + + // Touch the left edge of the primary root window. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0], + gfx::Point(0, 11)); + EXPECT_FALSE(is_warped); + // Touch the top edge of the primary root window. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0], + gfx::Point(11, 0)); + EXPECT_FALSE(is_warped); + // Touch the bottom edge of the primary root window. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0], + gfx::Point(11, 499)); + EXPECT_FALSE(is_warped); + // Touch the right edge of the secondary root window. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1], + gfx::Point(499, 11)); + EXPECT_FALSE(is_warped); + // Touch the top edge of the secondary root window. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1], + gfx::Point(11, 0)); + EXPECT_FALSE(is_warped); + // Touch the bottom edge of the secondary root window. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1], + gfx::Point(11, 499)); + EXPECT_FALSE(is_warped); +} + +// Verifies if the mouse pointer correctly moves to another display even when +// two displays are not the same size. +TEST_F(MouseCursorEventFilterTest, WarpMouseDifferentSizeDisplays) { + UpdateDisplay("500x500,600x600"); // the second one is larger. + + MouseCursorEventFilter* event_filter = + Shell::GetInstance()->mouse_cursor_filter(); + ASSERT_EQ( + DisplayController::RIGHT, + Shell::GetInstance()->display_controller()->secondary_display_layout()); + + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + aura::Env::GetInstance()->SetLastMouseLocation(*root_windows[1], + gfx::Point(123, 123)); + + // Touch the left edge of the secondary root window. Pointer should NOT warp + // because 1px left of (0, 500) is outside the primary root window. + bool is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1], + gfx::Point(0, 500)); + EXPECT_FALSE(is_warped); + EXPECT_EQ("623,123", // by 2px. + aura::Env::GetInstance()->last_mouse_location().ToString()); + + // Touch the left edge of the secondary root window. Pointer should warp + // because 1px left of (0, 499) is inside the primary root window. + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1], + gfx::Point(0, 499)); + EXPECT_TRUE(is_warped); + EXPECT_EQ("498,499", // by 2px. + aura::Env::GetInstance()->last_mouse_location().ToString()); +} + +// Verifies if MouseCursorEventFilter::set_mouse_warp_mode() works as expected. +TEST_F(MouseCursorEventFilterTest, SetMouseWarpModeFlag) { + UpdateDisplay("500x500,500x500"); + + MouseCursorEventFilter* event_filter = + Shell::GetInstance()->mouse_cursor_filter(); + + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + aura::Env::GetInstance()->SetLastMouseLocation(*root_windows[0], + gfx::Point(1, 1)); + + event_filter->set_mouse_warp_mode(MouseCursorEventFilter::WARP_NONE); + bool is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0], + gfx::Point(499, 11)); + EXPECT_FALSE(is_warped); + EXPECT_EQ("1,1", + aura::Env::GetInstance()->last_mouse_location().ToString()); + + event_filter->set_mouse_warp_mode(MouseCursorEventFilter::WARP_ALWAYS); + is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0], + gfx::Point(499, 11)); + EXPECT_TRUE(is_warped); + EXPECT_EQ("501,11", + aura::Env::GetInstance()->last_mouse_location().ToString()); +} + +// Verifies if MouseCursorEventFilter's bounds calculation works correctly. +TEST_F(MouseCursorEventFilterTest, IndicatorBoundsTestOnRight) { + UpdateDisplay("360x360,700x700"); + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + + DisplayController* controller = + Shell::GetInstance()->display_controller(); + controller->SetSecondaryDisplayLayout(DisplayController::RIGHT); + controller->SetSecondaryDisplayOffset(0); + ash::internal::MouseCursorEventFilter* event_filter = + Shell::GetInstance()->mouse_cursor_filter(); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("350,80 10x200", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("360,0 10x360", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("360,100 10x260", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("350,0 10x360", event_filter->dst_indicator_bounds_.ToString()); + + // Move 2nd display downwards a bit. + controller->SetSecondaryDisplayOffset(50); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + // This is same as before because the 2nd display's y is above + // the warp hole's x. + EXPECT_EQ("350,80 10x200", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("360,50 10x310", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("360,150 10x210", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("350,50 10x310", event_filter->dst_indicator_bounds_.ToString()); + + // Move it down further so that the shared edge is shorter than + // minimum hole size (160). + controller->SetSecondaryDisplayOffset(200); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("350,200 10x160", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("360,200 10x160", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("360,200 10x160", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("350,200 10x160", event_filter->dst_indicator_bounds_.ToString()); + + // Now move 2nd display upwards + controller->SetSecondaryDisplayOffset(-80); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("350,80 10x200", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("360,0 10x360", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + // 100 px are reserved on 2nd display from top, so y must be + // (100 - 80) = 20 + EXPECT_EQ("360,20 10x340", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("350,0 10x360", event_filter->dst_indicator_bounds_.ToString()); + + event_filter->HideSharedEdgeIndicator(); +} + +TEST_F(MouseCursorEventFilterTest, IndicatorBoundsTestOnLeft) { + UpdateDisplay("360x360,700x700"); + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + + DisplayController* controller = + Shell::GetInstance()->display_controller(); + controller->SetSecondaryDisplayLayout(DisplayController::LEFT); + controller->SetSecondaryDisplayOffset(0); + ash::internal::MouseCursorEventFilter* event_filter = + Shell::GetInstance()->mouse_cursor_filter(); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("0,80 10x200", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("-10,0 10x360", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("-10,100 10x260", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("0,0 10x360", event_filter->dst_indicator_bounds_.ToString()); + + controller->SetSecondaryDisplayOffset(300); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("0,300 10x60", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("-10,300 10x60", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("-10,300 10x60", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("0,300 10x60", event_filter->dst_indicator_bounds_.ToString()); + event_filter->HideSharedEdgeIndicator(); +} + +TEST_F(MouseCursorEventFilterTest, IndicatorBoundsTestOnTopBottom) { + UpdateDisplay("360x360,700x700"); + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + + DisplayController* controller = + Shell::GetInstance()->display_controller(); + controller->SetSecondaryDisplayLayout(DisplayController::TOP); + controller->SetSecondaryDisplayOffset(0); + ash::internal::MouseCursorEventFilter* event_filter = + Shell::GetInstance()->mouse_cursor_filter(); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("0,0 360x10", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("0,-10 360x10", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("0,-10 360x10", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("0,0 360x10", event_filter->dst_indicator_bounds_.ToString()); + + controller->SetSecondaryDisplayOffset(300); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("300,0 60x10", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("300,-10 60x10", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("300,-10 60x10", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("300,0 60x10", event_filter->dst_indicator_bounds_.ToString()); + + controller->SetSecondaryDisplayLayout(DisplayController::BOTTOM); + controller->SetSecondaryDisplayOffset(0); + event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */); + EXPECT_EQ("0,350 360x10", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("0,360 360x10", event_filter->dst_indicator_bounds_.ToString()); + event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */); + EXPECT_EQ("0,360 360x10", event_filter->src_indicator_bounds_.ToString()); + EXPECT_EQ("0,350 360x10", event_filter->dst_indicator_bounds_.ToString()); + + event_filter->HideSharedEdgeIndicator(); +} + +} // namespace internal +} // namespace ash diff --git a/ash/display/shared_display_edge_indicator.cc b/ash/display/shared_display_edge_indicator.cc new file mode 100644 index 0000000..1f596d0 --- /dev/null +++ b/ash/display/shared_display_edge_indicator.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2012 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 "ash/display/shared_display_edge_indicator.h" + +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/coordinate_conversion.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/aura/client/screen_position_client.h" +#include "ui/base/animation/throb_animation.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/display.h" +#include "ui/gfx/screen.h" +#include "ui/views/painter.h" +#include "ui/views/widget/widget.h" + +namespace ash { +namespace internal { +namespace { + +const int kIndicatorAnimationDurationMs = 1000; + +views::Widget* CreateWidget(const gfx::Rect& bounds) { + // This is just a placeholder and we'll use an image. + views::Painter* painter = views::Painter::CreateHorizontalGradient( + SK_ColorWHITE, SK_ColorWHITE); + + views::Widget* widget = new views::Widget; + views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); + params.transparent = true; + params.can_activate = false; + params.keep_on_top = true; + widget->set_focus_on_creation(false); + widget->Init(params); + widget->SetVisibilityChangedAnimationsEnabled(false); + widget->GetNativeWindow()->SetName("SharedEdgeIndicator"); + views::View* content_view = new views::View; + content_view->set_background( + views::Background::CreateBackgroundPainter(true, painter)); + widget->SetContentsView(content_view); + gfx::Display display = gfx::Screen::GetDisplayMatching(bounds); + aura::Window* window = widget->GetNativeWindow(); + aura::client::ScreenPositionClient* screen_position_client = + aura::client::GetScreenPositionClient(window->GetRootWindow()); + screen_position_client->SetBounds(window, bounds, display); + widget->SetOpacity(0); + widget->Show(); + return widget; +} + +} // namespace + +SharedDisplayEdgeIndicator::SharedDisplayEdgeIndicator() + : src_widget_(NULL), + dst_widget_(NULL) { +} + +SharedDisplayEdgeIndicator::~SharedDisplayEdgeIndicator() { + Hide(); +} + +void SharedDisplayEdgeIndicator::Show(const gfx::Rect& src_bounds, + const gfx::Rect& dst_bounds) { + DCHECK(!src_widget_); + DCHECK(!dst_widget_); + src_widget_ = CreateWidget(src_bounds); + dst_widget_ = CreateWidget(dst_bounds); + animation_.reset(new ui::ThrobAnimation(this)); + animation_->SetThrobDuration(kIndicatorAnimationDurationMs); + animation_->StartThrobbing(-1 /* infinite */); +} + +void SharedDisplayEdgeIndicator::Hide() { + if (src_widget_) + src_widget_->Close(); + src_widget_ = NULL; + if (dst_widget_) + dst_widget_->Close(); + dst_widget_ = NULL; +} + +void SharedDisplayEdgeIndicator::AnimationProgressed( + const ui::Animation* animation) { + int opacity = animation->CurrentValueBetween(0, 255); + if (src_widget_) + src_widget_->SetOpacity(opacity); + if (dst_widget_) + dst_widget_->SetOpacity(opacity); +} + +} // namespace internal +} // namespace ash diff --git a/ash/display/shared_display_edge_indicator.h b/ash/display/shared_display_edge_indicator.h new file mode 100644 index 0000000..1913e8a --- /dev/null +++ b/ash/display/shared_display_edge_indicator.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012 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. + +#ifndef ASH_DISPLAY_SHARED_DISPLAY_EDGE_INDICATOR_H_ +#define ASH_DISPLAY_SHARED_DISPLAY_EDGE_INDICATOR_H_ + +#include "ash/ash_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "ui/base/animation/animation_delegate.h" +#include "ui/gfx/display.h" + +namespace gfx { +class Rect; +} + +namespace ui { +class ThrobAnimation; +} + +namespace views { +class Widget; +} + +namespace ash { +namespace internal { + +// SharedDisplayEdgeIndicator is responsible for showing a window that indicates +// the edge that a window can be dragged into another display. +class ASH_EXPORT SharedDisplayEdgeIndicator : public ui::AnimationDelegate { + public: + SharedDisplayEdgeIndicator(); + virtual ~SharedDisplayEdgeIndicator(); + + // Shows/Hides the indicator window. The |src_bounds| is for the window on + // the source display, and the |dst_bounds| is for the window on the other + // display. + void Show(const gfx::Rect& src_bounds, const gfx::Rect& dst_bounds); + void Hide(); + + // ui::AnimationDelegate overrides: + virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; + + private: + // Used to show the displays' shared edge where a window can be moved across. + // |src_widget_| is for the display where drag starts and |dst_widget_| is + // for the other display. + views::Widget* src_widget_; + views::Widget* dst_widget_; + + // Used to transition the opacity. + scoped_ptr<ui::ThrobAnimation> animation_; + + DISALLOW_COPY_AND_ASSIGN(SharedDisplayEdgeIndicator); +}; + +} // namespace internal +} // namespace ash + +#endif // ASH_DISPLAY_SHARED_DISPLAY_EDGE_INDICATOR_H_ |