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