Home | History | Annotate | Download | only in wm
      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/wm/drag_window_resizer.h"
      6 
      7 #include "ash/display/mouse_cursor_event_filter.h"
      8 #include "ash/root_window_controller.h"
      9 #include "ash/screen_ash.h"
     10 #include "ash/shell.h"
     11 #include "ash/system/tray/system_tray.h"
     12 #include "ash/system/user/tray_user.h"
     13 #include "ash/wm/coordinate_conversion.h"
     14 #include "ash/wm/drag_window_controller.h"
     15 #include "ash/wm/window_state.h"
     16 #include "base/memory/weak_ptr.h"
     17 #include "ui/aura/client/aura_constants.h"
     18 #include "ui/aura/env.h"
     19 #include "ui/aura/root_window.h"
     20 #include "ui/aura/window.h"
     21 #include "ui/aura/window_delegate.h"
     22 #include "ui/base/hit_test.h"
     23 #include "ui/base/ui_base_types.h"
     24 #include "ui/gfx/screen.h"
     25 
     26 namespace ash {
     27 namespace internal {
     28 
     29 namespace {
     30 
     31 // The maximum opacity of the drag phantom window.
     32 const float kMaxOpacity = 0.8f;
     33 
     34 // The opacity of the window when dragging it over a user item in the tray.
     35 const float kOpacityWhenDraggedOverUserIcon = 0.4f;
     36 
     37 // Returns true if Ash has more than one root window.
     38 bool HasSecondaryRootWindow() {
     39   return Shell::GetAllRootWindows().size() > 1;
     40 }
     41 
     42 // When there are two root windows, returns one of the root windows which is not
     43 // |root_window|. Returns NULL if only one root window exists.
     44 aura::Window* GetAnotherRootWindow(aura::Window* root_window) {
     45   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
     46   if (root_windows.size() < 2)
     47     return NULL;
     48   DCHECK_EQ(2U, root_windows.size());
     49   if (root_windows[0] == root_window)
     50     return root_windows[1];
     51   return root_windows[0];
     52 }
     53 
     54 }  // namespace
     55 
     56 // static
     57 DragWindowResizer* DragWindowResizer::instance_ = NULL;
     58 
     59 DragWindowResizer::~DragWindowResizer() {
     60   if (GetTarget())
     61     wm::GetWindowState(GetTarget())->set_window_resizer_(NULL);
     62   Shell* shell = Shell::GetInstance();
     63   shell->mouse_cursor_filter()->set_mouse_warp_mode(
     64       MouseCursorEventFilter::WARP_ALWAYS);
     65   shell->mouse_cursor_filter()->HideSharedEdgeIndicator();
     66   if (instance_ == this)
     67     instance_ = NULL;
     68 }
     69 
     70 // static
     71 DragWindowResizer* DragWindowResizer::Create(
     72     WindowResizer* next_window_resizer,
     73     aura::Window* window,
     74     const gfx::Point& location,
     75     int window_component,
     76     aura::client::WindowMoveSource source) {
     77   Details details(window, location, window_component, source);
     78   return details.is_resizable ?
     79       new DragWindowResizer(next_window_resizer, details) : NULL;
     80 }
     81 
     82 void DragWindowResizer::Drag(const gfx::Point& location, int event_flags) {
     83   base::WeakPtr<DragWindowResizer> resizer(weak_ptr_factory_.GetWeakPtr());
     84 
     85   // If we are on top of a window to desktop transfer button, we move the window
     86   // temporarily back to where it was initially and make it semi-transparent.
     87   GetTarget()->layer()->SetOpacity(
     88       GetTrayUserItemAtPoint(location) ? kOpacityWhenDraggedOverUserIcon :
     89                                          details_.initial_opacity);
     90 
     91   next_window_resizer_->Drag(location, event_flags);
     92 
     93   if (!resizer)
     94     return;
     95 
     96   last_mouse_location_ = location;
     97   // Show a phantom window for dragging in another root window.
     98   if (HasSecondaryRootWindow()) {
     99     gfx::Point location_in_screen = location;
    100     wm::ConvertPointToScreen(GetTarget()->parent(), &location_in_screen);
    101     const bool in_original_root =
    102         wm::GetRootWindowAt(location_in_screen) == GetTarget()->GetRootWindow();
    103     UpdateDragWindow(GetTarget()->bounds(), in_original_root);
    104   } else {
    105     drag_window_controller_.reset();
    106   }
    107 }
    108 
    109 void DragWindowResizer::CompleteDrag(int event_flags) {
    110   if (TryDraggingToNewUser())
    111     return;
    112 
    113   next_window_resizer_->CompleteDrag(event_flags);
    114 
    115   GetTarget()->layer()->SetOpacity(details_.initial_opacity);
    116   drag_window_controller_.reset();
    117 
    118   // Check if the destination is another display.
    119   gfx::Point last_mouse_location_in_screen = last_mouse_location_;
    120   wm::ConvertPointToScreen(GetTarget()->parent(),
    121                            &last_mouse_location_in_screen);
    122   gfx::Screen* screen = Shell::GetScreen();
    123   const gfx::Display dst_display =
    124       screen->GetDisplayNearestPoint(last_mouse_location_in_screen);
    125 
    126   if (dst_display.id() !=
    127       screen->GetDisplayNearestWindow(GetTarget()->GetRootWindow()).id()) {
    128     const gfx::Rect dst_bounds =
    129         ScreenAsh::ConvertRectToScreen(GetTarget()->parent(),
    130                                        GetTarget()->bounds());
    131     GetTarget()->SetBoundsInScreen(dst_bounds, dst_display);
    132   }
    133 }
    134 
    135 void DragWindowResizer::RevertDrag() {
    136   next_window_resizer_->RevertDrag();
    137 
    138   drag_window_controller_.reset();
    139   GetTarget()->layer()->SetOpacity(details_.initial_opacity);
    140 }
    141 
    142 aura::Window* DragWindowResizer::GetTarget() {
    143   return next_window_resizer_->GetTarget();
    144 }
    145 
    146 const gfx::Point& DragWindowResizer::GetInitialLocation() const {
    147   return details_.initial_location_in_parent;
    148 }
    149 
    150 DragWindowResizer::DragWindowResizer(WindowResizer* next_window_resizer,
    151                                      const Details& details)
    152     : next_window_resizer_(next_window_resizer),
    153       details_(details),
    154       weak_ptr_factory_(this) {
    155   // The pointer should be confined in one display during resizing a window
    156   // because the window cannot span two displays at the same time anyway. The
    157   // exception is window/tab dragging operation. During that operation,
    158   // |mouse_warp_mode_| should be set to WARP_DRAG so that the user could move a
    159   // window/tab to another display.
    160   MouseCursorEventFilter* mouse_cursor_filter =
    161       Shell::GetInstance()->mouse_cursor_filter();
    162   mouse_cursor_filter->set_mouse_warp_mode(
    163       ShouldAllowMouseWarp() ?
    164       MouseCursorEventFilter::WARP_DRAG : MouseCursorEventFilter::WARP_NONE);
    165   if (ShouldAllowMouseWarp()) {
    166     mouse_cursor_filter->ShowSharedEdgeIndicator(
    167         details.window->GetRootWindow());
    168   }
    169   instance_ = this;
    170 }
    171 
    172 void DragWindowResizer::UpdateDragWindow(const gfx::Rect& bounds,
    173                                          bool in_original_root) {
    174   if (details_.window_component != HTCAPTION || !ShouldAllowMouseWarp())
    175     return;
    176 
    177   // It's available. Show a phantom window on the display if needed.
    178   aura::Window* another_root =
    179       GetAnotherRootWindow(GetTarget()->GetRootWindow());
    180   const gfx::Rect root_bounds_in_screen(another_root->GetBoundsInScreen());
    181   const gfx::Rect bounds_in_screen =
    182       ScreenAsh::ConvertRectToScreen(GetTarget()->parent(), bounds);
    183   gfx::Rect bounds_in_another_root =
    184       gfx::IntersectRects(root_bounds_in_screen, bounds_in_screen);
    185   const float fraction_in_another_window =
    186       (bounds_in_another_root.width() * bounds_in_another_root.height()) /
    187       static_cast<float>(bounds.width() * bounds.height());
    188 
    189   if (fraction_in_another_window > 0) {
    190     if (!drag_window_controller_) {
    191       drag_window_controller_.reset(
    192           new DragWindowController(GetTarget()));
    193       // Always show the drag phantom on the |another_root| window.
    194       drag_window_controller_->SetDestinationDisplay(
    195           Shell::GetScreen()->GetDisplayNearestWindow(another_root));
    196       drag_window_controller_->Show();
    197     } else {
    198       // No animation.
    199       drag_window_controller_->SetBounds(bounds_in_screen);
    200     }
    201     const float phantom_opacity =
    202       !in_original_root ? 1 : (kMaxOpacity * fraction_in_another_window);
    203     const float window_opacity =
    204         in_original_root ? 1 : (kMaxOpacity * (1 - fraction_in_another_window));
    205     drag_window_controller_->SetOpacity(phantom_opacity);
    206     GetTarget()->layer()->SetOpacity(window_opacity);
    207   } else {
    208     drag_window_controller_.reset();
    209     GetTarget()->layer()->SetOpacity(1.0f);
    210   }
    211 }
    212 
    213 bool DragWindowResizer::ShouldAllowMouseWarp() {
    214   return (details_.window_component == HTCAPTION) &&
    215       !GetTarget()->transient_parent() &&
    216       (GetTarget()->type() == aura::client::WINDOW_TYPE_NORMAL ||
    217        GetTarget()->type() == aura::client::WINDOW_TYPE_PANEL);
    218 }
    219 
    220 TrayUser* DragWindowResizer::GetTrayUserItemAtPoint(
    221     const gfx::Point& point_in_screen) {
    222   // Unit tests might not have an ash shell.
    223   if (!ash::Shell::GetInstance())
    224     return NULL;
    225 
    226   // Check that this is a drag move operation from a suitable window.
    227   if (details_.window_component != HTCAPTION ||
    228       GetTarget()->transient_parent() ||
    229       (GetTarget()->type() != aura::client::WINDOW_TYPE_NORMAL &&
    230        GetTarget()->type() != aura::client::WINDOW_TYPE_PANEL &&
    231        GetTarget()->type() != aura::client::WINDOW_TYPE_POPUP))
    232     return NULL;
    233 
    234   // We only allow to drag the window onto a tray of it's own RootWindow.
    235   SystemTray* tray = internal::GetRootWindowController(
    236       details_.window->GetRootWindow())->GetSystemTray();
    237 
    238   // Again - unit tests might not have a tray.
    239   if (!tray)
    240     return NULL;
    241 
    242   const std::vector<internal::TrayUser*> tray_users = tray->GetTrayUserItems();
    243   if (tray_users.size() <= 1)
    244     return NULL;
    245 
    246   std::vector<internal::TrayUser*>::const_iterator it = tray_users.begin();
    247   for (; it != tray_users.end(); ++it) {
    248     if ((*it)->CanDropWindowHereToTransferToUser(point_in_screen))
    249       return *it;
    250   }
    251   return NULL;
    252 }
    253 
    254 bool DragWindowResizer::TryDraggingToNewUser() {
    255   TrayUser* tray_user = GetTrayUserItemAtPoint(last_mouse_location_);
    256   // No need to try dragging if there is no user.
    257   if (!tray_user)
    258     return false;
    259 
    260   // We have to avoid a brief flash caused by the RevertDrag operation.
    261   // To do this, we first set the opacity of our target window to 0, so that no
    262   // matter what the RevertDrag does the window will stay hidden. Then transfer
    263   // the window to the new owner (which will hide it). RevertDrag will then do
    264   // it's thing and return the transparency to its original value.
    265   int old_opacity = GetTarget()->layer()->opacity();
    266   GetTarget()->layer()->SetOpacity(0);
    267   GetTarget()->SetBounds(details_.initial_bounds_in_parent);
    268   if (!tray_user->TransferWindowToUser(details_.window)) {
    269     GetTarget()->layer()->SetOpacity(old_opacity);
    270     return false;
    271   }
    272   RevertDrag();
    273   return true;
    274 }
    275 
    276 }  // namespace internal
    277 }  // namespace ash
    278