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