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/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 
     22 namespace ash {
     23 namespace internal {
     24 namespace {
     25 
     26 // Maximum size on the display edge that initiate snapping phantom window,
     27 // from the corner of the display.
     28 const 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.
     33 const int kMinimumIndicatorHeight = 200;
     34 
     35 const int kIndicatorThickness = 1;
     36 }
     37 
     38 MouseCursorEventFilter::MouseCursorEventFilter()
     39     : mouse_warp_mode_(WARP_ALWAYS),
     40       drag_source_root_(NULL),
     41       shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) {
     42 }
     43 
     44 MouseCursorEventFilter::~MouseCursorEventFilter() {
     45   HideSharedEdgeIndicator();
     46 }
     47 
     48 void 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 
     70 void MouseCursorEventFilter::HideSharedEdgeIndicator() {
     71   shared_display_edge_indicator_->Hide();
     72 }
     73 
     74 void 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 
     92 bool 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 
    153 void 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 
    182 void 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