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