1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ash/display/mouse_cursor_event_filter.h"
7 #include "ash/display/cursor_window_controller.h"
8 #include "ash/display/display_controller.h"
9 #include "ash/display/display_manager.h"
10 #include "ash/display/shared_display_edge_indicator.h"
11 #include "ash/screen_util.h"
12 #include "ash/shell.h"
13 #include "ash/wm/coordinate_conversion.h"
14 #include "ash/wm/window_util.h"
15 #include "ui/aura/env.h"
16 #include "ui/aura/root_window.h"
17 #include "ui/aura/window.h"
18 #include "ui/base/layout.h"
19 #include "ui/compositor/dip_util.h"
20 #include "ui/events/event.h"
21 #include "ui/gfx/screen.h"
27 // Maximum size on the display edge that initiate snapping phantom window,
28 // from the corner of the display.
29 const int kMaximumSnapHeight = 16;
31 // Minimum height of an indicator on the display edge that allows
32 // dragging a window. If two displays shares the edge smaller than
33 // this, entire edge will be used as a draggable space.
34 const int kMinimumIndicatorHeight = 200;
36 const int kIndicatorThickness = 1;
39 MouseCursorEventFilter::MouseCursorEventFilter()
40 : mouse_warp_mode_(WARP_ALWAYS),
41 was_mouse_warped_(false),
42 drag_source_root_(NULL),
43 scale_when_drag_started_(1.0f),
44 shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) {
47 MouseCursorEventFilter::~MouseCursorEventFilter() {
48 HideSharedEdgeIndicator();
51 void MouseCursorEventFilter::ShowSharedEdgeIndicator(
52 const aura::Window* from) {
53 HideSharedEdgeIndicator();
54 if (Shell::GetScreen()->GetNumDisplays() <= 1 || from == NULL) {
55 src_indicator_bounds_.SetRect(0, 0, 0, 0);
56 dst_indicator_bounds_.SetRect(0, 0, 0, 0);
57 drag_source_root_ = NULL;
60 drag_source_root_ = from;
62 DisplayLayout::Position position = Shell::GetInstance()->
63 display_manager()->GetCurrentDisplayLayout().position;
64 if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM)
65 UpdateHorizontalIndicatorWindowBounds();
67 UpdateVerticalIndicatorWindowBounds();
69 shared_display_edge_indicator_->Show(src_indicator_bounds_,
70 dst_indicator_bounds_);
73 void MouseCursorEventFilter::HideSharedEdgeIndicator() {
74 shared_display_edge_indicator_->Hide();
77 void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) {
78 if (event->type() == ui::ET_MOUSE_PRESSED) {
79 aura::Window* target = static_cast<aura::Window*>(event->target());
80 scale_when_drag_started_ = ui::GetDeviceScaleFactor(target->layer());
81 } else if (event->type() == ui::ET_MOUSE_RELEASED) {
82 scale_when_drag_started_ = 1.0f;
85 // Handle both MOVED and DRAGGED events here because when the mouse pointer
86 // enters the other root window while dragging, the underlying window system
87 // (at least X11) stops generating a ui::ET_MOUSE_MOVED event.
88 if (event->type() != ui::ET_MOUSE_MOVED &&
89 event->type() != ui::ET_MOUSE_DRAGGED) {
93 if (!(event->flags() & ui::EF_IS_SYNTHESIZED)) {
94 Shell::GetInstance()->display_controller()->
95 cursor_window_controller()->UpdateLocation();
98 gfx::Point point_in_screen(event->location());
99 aura::Window* target = static_cast<aura::Window*>(event->target());
100 wm::ConvertPointToScreen(target, &point_in_screen);
101 if (WarpMouseCursorIfNecessary(target->GetRootWindow(), point_in_screen))
102 event->StopPropagation();
105 bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(
106 aura::Window* target_root,
107 const gfx::Point& point_in_screen) {
108 if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
109 mouse_warp_mode_ == WARP_NONE)
112 // Do not warp again right after the cursor was warped. Sometimes the offset
113 // is not long enough and the cursor moves at the edge of the destination
114 // display. See crbug.com/278885
115 // TODO(mukai): simplify the offset calculation below, it would not be
116 // necessary anymore with this flag.
117 if (was_mouse_warped_) {
118 was_mouse_warped_ = false;
122 aura::Window* root_at_point = wm::GetRootWindowAt(point_in_screen);
123 gfx::Point point_in_root = point_in_screen;
124 wm::ConvertPointFromScreen(root_at_point, &point_in_root);
125 gfx::Rect root_bounds = root_at_point->bounds();
129 // If the window is dragged between 2x display and 1x display,
130 // staring from 2x display, pointer location is rounded by the
131 // source scale factor (2x) so it will never reach the edge (which
132 // is odd). Shrink by scale factor of the display where the dragging
133 // started instead. Only integral scale factor is supported for now.
134 int shrink = scale_when_drag_started_;
135 // Make the bounds inclusive to detect the edge.
136 root_bounds.Inset(0, 0, shrink, shrink);
137 gfx::Rect src_indicator_bounds = src_indicator_bounds_;
138 src_indicator_bounds.Inset(-shrink, -shrink, -shrink, -shrink);
140 if (point_in_root.x() <= root_bounds.x()) {
141 // Use -2, not -1, to avoid infinite loop of pointer warp.
142 offset_x = -2 * scale_when_drag_started_;
143 } else if (point_in_root.x() >= root_bounds.right()) {
144 offset_x = 2 * scale_when_drag_started_;
145 } else if (point_in_root.y() <= root_bounds.y()) {
146 offset_y = -2 * scale_when_drag_started_;
147 } else if (point_in_root.y() >= root_bounds.bottom()) {
148 offset_y = 2 * scale_when_drag_started_;
153 gfx::Point point_in_dst_screen(point_in_screen);
154 point_in_dst_screen.Offset(offset_x, offset_y);
155 aura::Window* dst_root = wm::GetRootWindowAt(point_in_dst_screen);
157 // Warp the mouse cursor only if the location is in the indicator bounds
158 // or the mouse pointer is in the destination root.
159 if (mouse_warp_mode_ == WARP_DRAG &&
160 dst_root != drag_source_root_ &&
161 !src_indicator_bounds.Contains(point_in_screen)) {
165 wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen);
167 if (dst_root->bounds().Contains(point_in_dst_screen)) {
168 DCHECK_NE(dst_root, root_at_point);
169 was_mouse_warped_ = true;
170 dst_root->MoveCursorTo(point_in_dst_screen);
176 void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() {
177 bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
178 // GetPrimaryDisplay returns an object on stack, so copy the bounds
179 // instead of using reference.
180 const gfx::Rect primary_bounds =
181 Shell::GetScreen()->GetPrimaryDisplay().bounds();
182 const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds();
183 DisplayLayout::Position position = Shell::GetInstance()->
184 display_manager()->GetCurrentDisplayLayout().position;
186 src_indicator_bounds_.set_x(
187 std::max(primary_bounds.x(), secondary_bounds.x()));
188 src_indicator_bounds_.set_width(
189 std::min(primary_bounds.right(), secondary_bounds.right()) -
190 src_indicator_bounds_.x());
191 src_indicator_bounds_.set_height(kIndicatorThickness);
192 src_indicator_bounds_.set_y(
193 position == DisplayLayout::TOP ?
194 primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) :
195 primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0));
197 dst_indicator_bounds_ = src_indicator_bounds_;
198 dst_indicator_bounds_.set_height(kIndicatorThickness);
199 dst_indicator_bounds_.set_y(
200 position == DisplayLayout::TOP ?
201 primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) :
202 primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness));
205 void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() {
206 bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
207 // GetPrimaryDisplay returns an object on stack, so copy the bounds
208 // instead of using reference.
209 const gfx::Rect primary_bounds =
210 Shell::GetScreen()->GetPrimaryDisplay().bounds();
211 const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds();
212 DisplayLayout::Position position = Shell::GetInstance()->
213 display_manager()->GetCurrentDisplayLayout().position;
215 int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y());
216 int lower_shared_y = std::min(primary_bounds.bottom(),
217 secondary_bounds.bottom());
218 int shared_height = lower_shared_y - upper_shared_y;
220 int dst_x = position == DisplayLayout::LEFT ?
221 primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) :
222 primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness);
223 dst_indicator_bounds_.SetRect(
224 dst_x, upper_shared_y, kIndicatorThickness, shared_height);
226 // The indicator on the source display.
227 src_indicator_bounds_.set_width(kIndicatorThickness);
228 src_indicator_bounds_.set_x(
229 position == DisplayLayout::LEFT ?
230 primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) :
231 primary_bounds.right() - (in_primary ? kIndicatorThickness : 0));
233 const gfx::Rect& source_bounds =
234 in_primary ? primary_bounds : secondary_bounds;
235 int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight;
236 int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y);
238 // This gives a hight that can be used without sacrifying the snap space.
239 int available_space = lower_indicator_y -
240 std::max(upper_shared_y, upper_indicator_y);
242 if (shared_height < kMinimumIndicatorHeight) {
243 // If the shared height is smaller than minimum height, use the
245 upper_indicator_y = upper_shared_y;
246 } else if (available_space < kMinimumIndicatorHeight) {
247 // Snap to the bottom.
249 std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight);
251 upper_indicator_y = std::max(upper_indicator_y, upper_shared_y);
253 src_indicator_bounds_.set_y(upper_indicator_y);
254 src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y);
257 } // namespace internal