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