summaryrefslogtreecommitdiffstats
path: root/ash/display
diff options
context:
space:
mode:
authoroshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-01 00:40:40 +0000
committeroshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-01 00:40:40 +0000
commit8b7ba87b074c458fe7b1b6aa3e4a2a663729621a (patch)
treefee2d4aa25547fef5a42fa340dd8e7ea7023092e /ash/display
parenteb7956341bd8835c5f9254e62638f8635f7c268f (diff)
downloadchromium_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.cc79
-rw-r--r--ash/display/display_controller.h18
-rw-r--r--ash/display/display_controller_unittest.cc115
-rw-r--r--ash/display/mouse_cursor_event_filter.cc204
-rw-r--r--ash/display/mouse_cursor_event_filter.h64
-rw-r--r--ash/display/mouse_cursor_event_filter_unittest.cc268
-rw-r--r--ash/display/shared_display_edge_indicator.cc94
-rw-r--r--ash/display/shared_display_edge_indicator.h61
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_