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()->display_controller()->
83      mirror_window_controller()->UpdateCursorLocation();
84
85  gfx::Point point_in_screen(event->location());
86  aura::Window* target = static_cast<aura::Window*>(event->target());
87  wm::ConvertPointToScreen(target, &point_in_screen);
88  if (WarpMouseCursorIfNecessary(target->GetRootWindow(), point_in_screen))
89    event->StopPropagation();
90}
91
92bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(
93    aura::RootWindow* target_root,
94    const gfx::Point& point_in_screen) {
95  if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
96      mouse_warp_mode_ == WARP_NONE)
97    return false;
98  const float scale_at_target = ui::GetDeviceScaleFactor(target_root->layer());
99
100  aura::RootWindow* root_at_point = wm::GetRootWindowAt(point_in_screen);
101  gfx::Point point_in_root = point_in_screen;
102  wm::ConvertPointFromScreen(root_at_point, &point_in_root);
103  gfx::Rect root_bounds = root_at_point->bounds();
104  int offset_x = 0;
105  int offset_y = 0;
106
107  const float scale_at_point = ui::GetDeviceScaleFactor(root_at_point->layer());
108  // If the window is dragged from 2x display to 1x display, the
109  // pointer location is rounded by the source scale factor (2x) so
110  // it will never reach the edge (which is odd). Shrink by scale
111  // factor instead.  Only integral scale factor is supported.
112  int shrink =
113      target_root != root_at_point && scale_at_target != scale_at_point ?
114      static_cast<int>(scale_at_target) : 1;
115  // Make the bounds inclusive to detect the edge.
116  root_bounds.Inset(0, 0, shrink, shrink);
117
118  if (point_in_root.x() <= root_bounds.x()) {
119    // Use -2, not -1, to avoid infinite loop of pointer warp.
120    offset_x = -2 * scale_at_target;
121  } else if (point_in_root.x() >= root_bounds.right()) {
122    offset_x = 2 * scale_at_target;
123  } else if (point_in_root.y() <= root_bounds.y()) {
124    offset_y = -2 * scale_at_target;
125  } else if (point_in_root.y() >= root_bounds.bottom()) {
126    offset_y = 2 * scale_at_target;
127  } else {
128    return false;
129  }
130
131  gfx::Point point_in_dst_screen(point_in_screen);
132  point_in_dst_screen.Offset(offset_x, offset_y);
133  aura::RootWindow* dst_root = wm::GetRootWindowAt(point_in_dst_screen);
134
135  // Warp the mouse cursor only if the location is in the indicator bounds
136  // or the mouse pointer is in the destination root.
137  if (mouse_warp_mode_ == WARP_DRAG &&
138      dst_root != drag_source_root_ &&
139      !src_indicator_bounds_.Contains(point_in_screen)) {
140    return false;
141  }
142
143  wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen);
144
145  if (dst_root->bounds().Contains(point_in_dst_screen)) {
146    DCHECK_NE(dst_root, root_at_point);
147    dst_root->MoveCursorTo(point_in_dst_screen);
148    return true;
149  }
150  return false;
151}
152
153void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() {
154  bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
155  // GetPrimaryDisplay returns an object on stack, so copy the bounds
156  // instead of using reference.
157  const gfx::Rect primary_bounds =
158      Shell::GetScreen()->GetPrimaryDisplay().bounds();
159  const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
160  DisplayLayout::Position position = Shell::GetInstance()->
161      display_controller()->GetCurrentDisplayLayout().position;
162
163  src_indicator_bounds_.set_x(
164      std::max(primary_bounds.x(), secondary_bounds.x()));
165  src_indicator_bounds_.set_width(
166      std::min(primary_bounds.right(), secondary_bounds.right()) -
167      src_indicator_bounds_.x());
168  src_indicator_bounds_.set_height(kIndicatorThickness);
169  src_indicator_bounds_.set_y(
170      position == DisplayLayout::TOP ?
171      primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) :
172      primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0));
173
174  dst_indicator_bounds_ = src_indicator_bounds_;
175  dst_indicator_bounds_.set_height(kIndicatorThickness);
176  dst_indicator_bounds_.set_y(
177      position == DisplayLayout::TOP ?
178      primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) :
179      primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness));
180}
181
182void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() {
183  bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
184  // GetPrimaryDisplay returns an object on stack, so copy the bounds
185  // instead of using reference.
186  const gfx::Rect primary_bounds =
187      Shell::GetScreen()->GetPrimaryDisplay().bounds();
188  const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
189  DisplayLayout::Position position = Shell::GetInstance()->
190      display_controller()->GetCurrentDisplayLayout().position;
191
192  int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y());
193  int lower_shared_y = std::min(primary_bounds.bottom(),
194                                secondary_bounds.bottom());
195  int shared_height = lower_shared_y - upper_shared_y;
196
197  int dst_x = position == DisplayLayout::LEFT ?
198      primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) :
199      primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness);
200  dst_indicator_bounds_.SetRect(
201      dst_x, upper_shared_y, kIndicatorThickness, shared_height);
202
203  // The indicator on the source display.
204  src_indicator_bounds_.set_width(kIndicatorThickness);
205  src_indicator_bounds_.set_x(
206      position == DisplayLayout::LEFT ?
207      primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) :
208      primary_bounds.right() - (in_primary ? kIndicatorThickness : 0));
209
210  const gfx::Rect& source_bounds =
211      in_primary ? primary_bounds : secondary_bounds;
212  int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight;
213  int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y);
214
215  // This gives a hight that can be used without sacrifying the snap space.
216  int available_space = lower_indicator_y -
217      std::max(upper_shared_y, upper_indicator_y);
218
219  if (shared_height < kMinimumIndicatorHeight) {
220    // If the shared height is smaller than minimum height, use the
221    // entire height.
222    upper_indicator_y = upper_shared_y;
223  } else if (available_space < kMinimumIndicatorHeight) {
224    // Snap to the bottom.
225    upper_indicator_y =
226        std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight);
227  } else {
228    upper_indicator_y = std::max(upper_indicator_y, upper_shared_y);
229  }
230  src_indicator_bounds_.set_y(upper_indicator_y);
231  src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y);
232}
233
234}  // namespace internal
235}  // namespace ash
236