Home | History | Annotate | Download | only in display
      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/layout.h"
     19 #include "ui/compositor/dip_util.h"
     20 #include "ui/events/event.h"
     21 #include "ui/gfx/screen.h"
     22 
     23 namespace ash {
     24 namespace internal {
     25 namespace {
     26 
     27 // Maximum size on the display edge that initiate snapping phantom window,
     28 // from the corner of the display.
     29 const 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.
     34 const int kMinimumIndicatorHeight = 200;
     35 
     36 const int kIndicatorThickness = 1;
     37 }
     38 
     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) {
     45 }
     46 
     47 MouseCursorEventFilter::~MouseCursorEventFilter() {
     48   HideSharedEdgeIndicator();
     49 }
     50 
     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;
     58     return;
     59   }
     60   drag_source_root_ = from;
     61 
     62   DisplayLayout::Position position = Shell::GetInstance()->
     63       display_manager()->GetCurrentDisplayLayout().position;
     64   if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM)
     65     UpdateHorizontalIndicatorWindowBounds();
     66   else
     67     UpdateVerticalIndicatorWindowBounds();
     68 
     69   shared_display_edge_indicator_->Show(src_indicator_bounds_,
     70                                        dst_indicator_bounds_);
     71 }
     72 
     73 void MouseCursorEventFilter::HideSharedEdgeIndicator() {
     74   shared_display_edge_indicator_->Hide();
     75 }
     76 
     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;
     83   }
     84 
     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) {
     90       return;
     91   }
     92   Shell::GetInstance()->display_controller()->
     93       mirror_window_controller()->UpdateCursorLocation();
     94 
     95   gfx::Point point_in_screen(event->location());
     96   aura::Window* target = static_cast<aura::Window*>(event->target());
     97   wm::ConvertPointToScreen(target, &point_in_screen);
     98   if (WarpMouseCursorIfNecessary(target->GetRootWindow(), point_in_screen))
     99     event->StopPropagation();
    100 }
    101 
    102 bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(
    103     aura::Window* target_root,
    104     const gfx::Point& point_in_screen) {
    105   if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
    106       mouse_warp_mode_ == WARP_NONE)
    107     return false;
    108 
    109   // Do not warp again right after the cursor was warped. Sometimes the offset
    110   // is not long enough and the cursor moves at the edge of the destination
    111   // display. See crbug.com/278885
    112   // TODO(mukai): simplify the offset calculation below, it would not be
    113   // necessary anymore with this flag.
    114   if (was_mouse_warped_) {
    115     was_mouse_warped_ = false;
    116     return false;
    117   }
    118 
    119   aura::Window* root_at_point = wm::GetRootWindowAt(point_in_screen);
    120   gfx::Point point_in_root = point_in_screen;
    121   wm::ConvertPointFromScreen(root_at_point, &point_in_root);
    122   gfx::Rect root_bounds = root_at_point->bounds();
    123   int offset_x = 0;
    124   int offset_y = 0;
    125 
    126   // If the window is dragged between 2x display and 1x display,
    127   // staring from 2x display, pointer location is rounded by the
    128   // source scale factor (2x) so it will never reach the edge (which
    129   // is odd). Shrink by scale factor of the display where the dragging
    130   // started instead.  Only integral scale factor is supported for now.
    131   int shrink = scale_when_drag_started_;
    132   // Make the bounds inclusive to detect the edge.
    133   root_bounds.Inset(0, 0, shrink, shrink);
    134   gfx::Rect src_indicator_bounds = src_indicator_bounds_;
    135   src_indicator_bounds.Inset(-shrink, -shrink, -shrink, -shrink);
    136 
    137   if (point_in_root.x() <= root_bounds.x()) {
    138     // Use -2, not -1, to avoid infinite loop of pointer warp.
    139     offset_x = -2 * scale_when_drag_started_;
    140   } else if (point_in_root.x() >= root_bounds.right()) {
    141     offset_x = 2 * scale_when_drag_started_;
    142   } else if (point_in_root.y() <= root_bounds.y()) {
    143     offset_y = -2 * scale_when_drag_started_;
    144   } else if (point_in_root.y() >= root_bounds.bottom()) {
    145     offset_y = 2 * scale_when_drag_started_;
    146   } else {
    147     return false;
    148   }
    149 
    150   gfx::Point point_in_dst_screen(point_in_screen);
    151   point_in_dst_screen.Offset(offset_x, offset_y);
    152   aura::Window* dst_root = wm::GetRootWindowAt(point_in_dst_screen);
    153 
    154   // Warp the mouse cursor only if the location is in the indicator bounds
    155   // or the mouse pointer is in the destination root.
    156   if (mouse_warp_mode_ == WARP_DRAG &&
    157       dst_root != drag_source_root_ &&
    158       !src_indicator_bounds.Contains(point_in_screen)) {
    159     return false;
    160   }
    161 
    162   wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen);
    163 
    164   if (dst_root->bounds().Contains(point_in_dst_screen)) {
    165     DCHECK_NE(dst_root, root_at_point);
    166     was_mouse_warped_ = true;
    167     dst_root->MoveCursorTo(point_in_dst_screen);
    168     return true;
    169   }
    170   return false;
    171 }
    172 
    173 void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() {
    174   bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
    175   // GetPrimaryDisplay returns an object on stack, so copy the bounds
    176   // instead of using reference.
    177   const gfx::Rect primary_bounds =
    178       Shell::GetScreen()->GetPrimaryDisplay().bounds();
    179   const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
    180   DisplayLayout::Position position = Shell::GetInstance()->
    181       display_manager()->GetCurrentDisplayLayout().position;
    182 
    183   src_indicator_bounds_.set_x(
    184       std::max(primary_bounds.x(), secondary_bounds.x()));
    185   src_indicator_bounds_.set_width(
    186       std::min(primary_bounds.right(), secondary_bounds.right()) -
    187       src_indicator_bounds_.x());
    188   src_indicator_bounds_.set_height(kIndicatorThickness);
    189   src_indicator_bounds_.set_y(
    190       position == DisplayLayout::TOP ?
    191       primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) :
    192       primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0));
    193 
    194   dst_indicator_bounds_ = src_indicator_bounds_;
    195   dst_indicator_bounds_.set_height(kIndicatorThickness);
    196   dst_indicator_bounds_.set_y(
    197       position == DisplayLayout::TOP ?
    198       primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) :
    199       primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness));
    200 }
    201 
    202 void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() {
    203   bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
    204   // GetPrimaryDisplay returns an object on stack, so copy the bounds
    205   // instead of using reference.
    206   const gfx::Rect primary_bounds =
    207       Shell::GetScreen()->GetPrimaryDisplay().bounds();
    208   const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
    209   DisplayLayout::Position position = Shell::GetInstance()->
    210       display_manager()->GetCurrentDisplayLayout().position;
    211 
    212   int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y());
    213   int lower_shared_y = std::min(primary_bounds.bottom(),
    214                                 secondary_bounds.bottom());
    215   int shared_height = lower_shared_y - upper_shared_y;
    216 
    217   int dst_x = position == DisplayLayout::LEFT ?
    218       primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) :
    219       primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness);
    220   dst_indicator_bounds_.SetRect(
    221       dst_x, upper_shared_y, kIndicatorThickness, shared_height);
    222 
    223   // The indicator on the source display.
    224   src_indicator_bounds_.set_width(kIndicatorThickness);
    225   src_indicator_bounds_.set_x(
    226       position == DisplayLayout::LEFT ?
    227       primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) :
    228       primary_bounds.right() - (in_primary ? kIndicatorThickness : 0));
    229 
    230   const gfx::Rect& source_bounds =
    231       in_primary ? primary_bounds : secondary_bounds;
    232   int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight;
    233   int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y);
    234 
    235   // This gives a hight that can be used without sacrifying the snap space.
    236   int available_space = lower_indicator_y -
    237       std::max(upper_shared_y, upper_indicator_y);
    238 
    239   if (shared_height < kMinimumIndicatorHeight) {
    240     // If the shared height is smaller than minimum height, use the
    241     // entire height.
    242     upper_indicator_y = upper_shared_y;
    243   } else if (available_space < kMinimumIndicatorHeight) {
    244     // Snap to the bottom.
    245     upper_indicator_y =
    246         std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight);
    247   } else {
    248     upper_indicator_y = std::max(upper_indicator_y, upper_shared_y);
    249   }
    250   src_indicator_bounds_.set_y(upper_indicator_y);
    251   src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y);
    252 }
    253 
    254 }  // namespace internal
    255 }  // namespace ash
    256