• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
HasSecondaryRootWindow()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.
GetAnotherRootWindow(aura::Window * root_window)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 
~DragWindowResizer()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
Create(WindowResizer * next_window_resizer,aura::Window * window,const gfx::Point & location,int window_component,aura::client::WindowMoveSource source)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 
Drag(const gfx::Point & location,int event_flags)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 
CompleteDrag(int event_flags)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 
RevertDrag()135 void DragWindowResizer::RevertDrag() {
136   next_window_resizer_->RevertDrag();
137 
138   drag_window_controller_.reset();
139   GetTarget()->layer()->SetOpacity(details_.initial_opacity);
140 }
141 
GetTarget()142 aura::Window* DragWindowResizer::GetTarget() {
143   return next_window_resizer_->GetTarget();
144 }
145 
GetInitialLocation() const146 const gfx::Point& DragWindowResizer::GetInitialLocation() const {
147   return details_.initial_location_in_parent;
148 }
149 
DragWindowResizer(WindowResizer * next_window_resizer,const Details & details)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 
UpdateDragWindow(const gfx::Rect & bounds,bool in_original_root)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 
ShouldAllowMouseWarp()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 
GetTrayUserItemAtPoint(const gfx::Point & point_in_screen)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 
TryDraggingToNewUser()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