mouse_cursor_event_filter.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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.
4
5#include "ash/display/mouse_cursor_event_filter.h"
6
7#include "ash/display/display_controller.h"
8#include "ash/display/shared_display_edge_indicator.h"
9#include "ash/screen_ash.h"
10#include "ash/shell.h"
11#include "ash/wm/coordinate_conversion.h"
12#include "ash/wm/window_util.h"
13#include "ui/aura/env.h"
14#include "ui/aura/root_window.h"
15#include "ui/aura/window.h"
16#include "ui/base/events/event.h"
17#include "ui/base/layout.h"
18#include "ui/compositor/dip_util.h"
19#include "ui/gfx/screen.h"
20
21namespace ash {
22namespace internal {
23namespace {
24
25// Maximum size on the display edge that initiate snapping phantom window,
26// from the corner of the display.
27const int kMaximumSnapHeight = 16;
28
29// Minimum height of an indicator on the display edge that allows
30// dragging a window.  If two displays shares the edge smaller than
31// this, entire edge will be used as a draggable space.
32const int kMinimumIndicatorHeight = 200;
33
34const int kIndicatorThickness = 1;
35}
36
37MouseCursorEventFilter::MouseCursorEventFilter()
38    : mouse_warp_mode_(WARP_ALWAYS),
39      drag_source_root_(NULL),
40      shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) {
41}
42
43MouseCursorEventFilter::~MouseCursorEventFilter() {
44  HideSharedEdgeIndicator();
45}
46
47void MouseCursorEventFilter::ShowSharedEdgeIndicator(
48    const aura::RootWindow* from) {
49  HideSharedEdgeIndicator();
50  if (Shell::GetScreen()->GetNumDisplays() <= 1 || from == NULL) {
51    src_indicator_bounds_.SetRect(0, 0, 0, 0);
52    dst_indicator_bounds_.SetRect(0, 0, 0, 0);
53    drag_source_root_ = NULL;
54    return;
55  }
56  drag_source_root_ = from;
57
58  DisplayLayout::Position position = Shell::GetInstance()->
59      display_controller()->GetCurrentDisplayLayout().position;
60  if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM)
61    UpdateHorizontalIndicatorWindowBounds();
62  else
63    UpdateVerticalIndicatorWindowBounds();
64
65  shared_display_edge_indicator_->Show(src_indicator_bounds_,
66                                       dst_indicator_bounds_);
67}
68
69void MouseCursorEventFilter::HideSharedEdgeIndicator() {
70  shared_display_edge_indicator_->Hide();
71}
72
73void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) {
74  // Handle both MOVED and DRAGGED events here because when the mouse pointer
75  // enters the other root window while dragging, the underlying window system
76  // (at least X11) stops generating a ui::ET_MOUSE_MOVED event.
77  if (event->type() != ui::ET_MOUSE_MOVED &&
78      event->type() != ui::ET_MOUSE_DRAGGED) {
79      return;
80  }
81
82  gfx::Point point_in_screen(event->location());
83  aura::Window* target = static_cast<aura::Window*>(event->target());
84  wm::ConvertPointToScreen(target, &point_in_screen);
85  if (WarpMouseCursorIfNecessary(target->GetRootWindow(), point_in_screen))
86    event->StopPropagation();
87}
88
89bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(
90    aura::RootWindow* target_root,
91    const gfx::Point& point_in_screen) {
92  if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
93      mouse_warp_mode_ == WARP_NONE)
94    return false;
95  const float scale_at_target = ui::GetDeviceScaleFactor(target_root->layer());
96
97  aura::RootWindow* root_at_point = wm::GetRootWindowAt(point_in_screen);
98  gfx::Point point_in_root = point_in_screen;
99  wm::ConvertPointFromScreen(root_at_point, &point_in_root);
100  gfx::Rect root_bounds = root_at_point->bounds();
101  int offset_x = 0;
102  int offset_y = 0;
103
104  const float scale_at_point = ui::GetDeviceScaleFactor(root_at_point->layer());
105  // If the window is dragged from 2x display to 1x display, the
106  // pointer location is rounded by the source scale factor (2x) so
107  // it will never reach the edge (which is odd). Shrink by scale
108  // factor instead.  Only integral scale factor is supported.
109  int shrink =
110      target_root != root_at_point && scale_at_target != scale_at_point ?
111      static_cast<int>(scale_at_target) : 1;
112  // Make the bounds inclusive to detect the edge.
113  root_bounds.Inset(0, 0, shrink, shrink);
114
115  if (point_in_root.x() <= root_bounds.x()) {
116    // Use -2, not -1, to avoid infinite loop of pointer warp.
117    offset_x = -2 * scale_at_target;
118  } else if (point_in_root.x() >= root_bounds.right()) {
119    offset_x = 2 * scale_at_target;
120  } else if (point_in_root.y() <= root_bounds.y()) {
121    offset_y = -2 * scale_at_target;
122  } else if (point_in_root.y() >= root_bounds.bottom()) {
123    offset_y = 2 * scale_at_target;
124  } else {
125    return false;
126  }
127
128  gfx::Point point_in_dst_screen(point_in_screen);
129  point_in_dst_screen.Offset(offset_x, offset_y);
130  aura::RootWindow* dst_root = wm::GetRootWindowAt(point_in_dst_screen);
131
132  // Warp the mouse cursor only if the location is in the indicator bounds
133  // or the mouse pointer is in the destination root.
134  if (mouse_warp_mode_ == WARP_DRAG &&
135      dst_root != drag_source_root_ &&
136      !src_indicator_bounds_.Contains(point_in_screen)) {
137    return false;
138  }
139
140  wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen);
141
142  if (dst_root->bounds().Contains(point_in_dst_screen)) {
143    DCHECK_NE(dst_root, root_at_point);
144    dst_root->MoveCursorTo(point_in_dst_screen);
145    return true;
146  }
147  return false;
148}
149
150void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() {
151  bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
152  // GetPrimaryDisplay returns an object on stack, so copy the bounds
153  // instead of using reference.
154  const gfx::Rect primary_bounds =
155      Shell::GetScreen()->GetPrimaryDisplay().bounds();
156  const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
157  DisplayLayout::Position position = Shell::GetInstance()->
158      display_controller()->GetCurrentDisplayLayout().position;
159
160  src_indicator_bounds_.set_x(
161      std::max(primary_bounds.x(), secondary_bounds.x()));
162  src_indicator_bounds_.set_width(
163      std::min(primary_bounds.right(), secondary_bounds.right()) -
164      src_indicator_bounds_.x());
165  src_indicator_bounds_.set_height(kIndicatorThickness);
166  src_indicator_bounds_.set_y(
167      position == DisplayLayout::TOP ?
168      primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) :
169      primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0));
170
171  dst_indicator_bounds_ = src_indicator_bounds_;
172  dst_indicator_bounds_.set_height(kIndicatorThickness);
173  dst_indicator_bounds_.set_y(
174      position == DisplayLayout::TOP ?
175      primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) :
176      primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness));
177}
178
179void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() {
180  bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
181  // GetPrimaryDisplay returns an object on stack, so copy the bounds
182  // instead of using reference.
183  const gfx::Rect primary_bounds =
184      Shell::GetScreen()->GetPrimaryDisplay().bounds();
185  const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
186  DisplayLayout::Position position = Shell::GetInstance()->
187      display_controller()->GetCurrentDisplayLayout().position;
188
189  int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y());
190  int lower_shared_y = std::min(primary_bounds.bottom(),
191                                secondary_bounds.bottom());
192  int shared_height = lower_shared_y - upper_shared_y;
193
194  int dst_x = position == DisplayLayout::LEFT ?
195      primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) :
196      primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness);
197  dst_indicator_bounds_.SetRect(
198      dst_x, upper_shared_y, kIndicatorThickness, shared_height);
199
200  // The indicator on the source display.
201  src_indicator_bounds_.set_width(kIndicatorThickness);
202  src_indicator_bounds_.set_x(
203      position == DisplayLayout::LEFT ?
204      primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) :
205      primary_bounds.right() - (in_primary ? kIndicatorThickness : 0));
206
207  const gfx::Rect& source_bounds =
208      in_primary ? primary_bounds : secondary_bounds;
209  int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight;
210  int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y);
211
212  // This gives a hight that can be used without sacrifying the snap space.
213  int available_space = lower_indicator_y -
214      std::max(upper_shared_y, upper_indicator_y);
215
216  if (shared_height < kMinimumIndicatorHeight) {
217    // If the shared height is smaller than minimum height, use the
218    // entire height.
219    upper_indicator_y = upper_shared_y;
220  } else if (available_space < kMinimumIndicatorHeight) {
221    // Snap to the bottom.
222    upper_indicator_y =
223        std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight);
224  } else {
225    upper_indicator_y = std::max(upper_indicator_y, upper_shared_y);
226  }
227  src_indicator_bounds_.set_y(upper_indicator_y);
228  src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y);
229}
230
231}  // namespace internal
232}  // namespace ash
233