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.
5 #include "ash/wm/workspace/workspace_window_resizer.h"
10 #include "ash/display/display_controller.h"
11 #include "ash/screen_ash.h"
12 #include "ash/shell.h"
13 #include "ash/wm/coordinate_conversion.h"
14 #include "ash/wm/cursor_manager.h"
15 #include "ash/wm/property_util.h"
16 #include "ash/wm/window_util.h"
17 #include "ash/wm/workspace/phantom_window_controller.h"
18 #include "ash/wm/workspace/snap_sizer.h"
19 #include "ui/aura/client/aura_constants.h"
20 #include "ui/aura/root_window.h"
21 #include "ui/aura/window.h"
22 #include "ui/aura/window_delegate.h"
23 #include "ui/base/hit_test.h"
24 #include "ui/compositor/layer.h"
25 #include "ui/compositor/scoped_layer_animation_settings.h"
26 #include "ui/gfx/screen.h"
27 #include "ui/gfx/transform.h"
34 // Duration of the animation when snapping the window into place.
35 const int kSnapDurationMS
= 100;
37 // The maximum opacity of the drag phantom window.
38 const float kMaxOpacity
= 0.8f
;
40 // Returns true if should snap to the edge.
41 bool ShouldSnapToEdge(int distance_from_edge
, int grid_size
) {
42 return distance_from_edge
<= grid_size
/ 2 &&
43 distance_from_edge
> -grid_size
* 2;
46 // Returns true if Ash has more than one root window.
47 bool HasSecondaryRootWindow() {
48 return Shell::GetAllRootWindows().size() > 1;
51 // When there are two root windows, returns one of the root windows which is not
52 // |root_window|. Returns NULL if only one root window exists.
53 aura::RootWindow
* GetAnotherRootWindow(aura::RootWindow
* root_window
) {
54 Shell::RootWindowList root_windows
= Shell::GetAllRootWindows();
55 if (root_windows
.size() < 2)
57 DCHECK_EQ(2U, root_windows
.size());
58 if (root_windows
[0] == root_window
)
59 return root_windows
[1];
60 return root_windows
[0];
66 const int WorkspaceWindowResizer::kMinOnscreenSize
= 20;
69 const int WorkspaceWindowResizer::kMinOnscreenHeight
= 32;
71 WorkspaceWindowResizer::~WorkspaceWindowResizer() {
72 Shell
* shell
= Shell::GetInstance();
73 shell
->display_controller()->set_dont_warp_mouse(false);
74 shell
->cursor_manager()->UnlockCursor();
76 // Delete phantom controllers first so that they will never see the deleted
78 snap_phantom_window_controller_
.reset();
79 drag_phantom_window_controller_
.reset();
82 wm::DeepDeleteLayers(layer_
);
86 WorkspaceWindowResizer
* WorkspaceWindowResizer::Create(
88 const gfx::Point
& location_in_parent
,
90 const std::vector
<aura::Window
*>& attached_windows
) {
91 Details
details(window
, location_in_parent
, window_component
);
92 return details
.is_resizable
?
93 new WorkspaceWindowResizer(details
, attached_windows
) : NULL
;
96 void WorkspaceWindowResizer::Drag(const gfx::Point
& location
, int event_flags
) {
97 std::pair
<aura::RootWindow
*, gfx::Point
> actual_location
=
98 wm::GetRootWindowRelativeToWindow(window()->parent(), location
);
99 aura::RootWindow
* current_root
= actual_location
.first
;
100 gfx::Point location_in_parent
= actual_location
.second
;
101 aura::Window::ConvertPointToTarget(current_root
,
103 &location_in_parent
);
104 last_mouse_location_
= location_in_parent
;
106 // Do not use |location| below this point, use |location_in_parent| instead.
107 // When the pointer is on |window()->GetRootWindow()|, |location| and
108 // |location_in_parent| have the same value and both of them are in
109 // |window()->parent()|'s coordinates, but once the pointer enters the
110 // other root window, you will see an unexpected value on the former. See
111 // comments in wm::GetRootWindowRelativeToWindow() for details.
113 int grid_size
= event_flags
& ui::EF_CONTROL_DOWN
?
114 0 : ash::Shell::GetInstance()->GetGridSize();
115 gfx::Rect bounds
= // in |window()->parent()|'s coordinates.
116 CalculateBoundsForDrag(details_
, location_in_parent
, grid_size
);
118 if (wm::IsWindowNormal(window()))
119 AdjustBoundsForMainWindow(&bounds
, grid_size
);
120 if (bounds
!= window()->bounds()) {
121 if (!did_move_or_resize_
)
123 did_move_or_resize_
= true;
126 const bool in_original_root
= (window()->GetRootWindow() == current_root
);
127 // Hide a phantom window for snapping if the cursor is in another root window.
128 if (in_original_root
)
129 UpdateSnapPhantomWindow(location_in_parent
, bounds
, grid_size
);
131 snap_phantom_window_controller_
.reset();
133 // Show a phantom window for dragging in another root window.
134 if (HasSecondaryRootWindow())
135 UpdateDragPhantomWindow(bounds
, in_original_root
);
137 drag_phantom_window_controller_
.reset();
139 if (!attached_windows_
.empty())
140 LayoutAttachedWindows(bounds
, grid_size
);
141 if (bounds
!= window()->bounds())
142 window()->SetBounds(bounds
);
143 // WARNING: we may have been deleted.
146 void WorkspaceWindowResizer::CompleteDrag(int event_flags
) {
147 window()->layer()->SetOpacity(details_
.initial_opacity
);
148 drag_phantom_window_controller_
.reset();
149 snap_phantom_window_controller_
.reset();
150 if (!did_move_or_resize_
|| details_
.window_component
!= HTCAPTION
)
153 if (snap_type_
== SNAP_LEFT_EDGE
|| snap_type_
== SNAP_RIGHT_EDGE
) {
154 if (!GetRestoreBoundsInScreen(window()))
155 SetRestoreBoundsInParent(window(), details_
.initial_bounds
);
156 window()->SetBounds(snap_sizer_
->target_bounds());
160 int grid_size
= event_flags
& ui::EF_CONTROL_DOWN
?
161 0 : ash::Shell::GetInstance()->GetGridSize();
162 gfx::Rect
bounds(GetFinalBounds(window()->bounds(), grid_size
));
164 // Check if the destination is another display.
165 gfx::Point last_mouse_location_in_screen
= last_mouse_location_
;
166 wm::ConvertPointToScreen(window()->parent(), &last_mouse_location_in_screen
);
167 const gfx::Display dst_display
=
168 gfx::Screen::GetDisplayNearestPoint(last_mouse_location_in_screen
);
170 if (dst_display
.id() !=
171 gfx::Screen::GetDisplayNearestWindow(window()->GetRootWindow()).id()) {
172 // Don't animate when moving to another display.
173 const gfx::Rect dst_bounds
=
174 ScreenAsh::ConvertRectToScreen(window()->parent(), bounds
);
175 window()->SetBoundsInScreen(dst_bounds
, dst_display
);
179 if (grid_size
<= 1 || bounds
== window()->bounds())
182 if (bounds
.size() != window()->bounds().size()) {
183 // Don't attempt to animate a size change.
184 window()->SetBounds(bounds
);
188 ui::ScopedLayerAnimationSettings
scoped_setter(
189 window()->layer()->GetAnimator());
190 // Use a small duration since the grid is small.
191 scoped_setter
.SetTransitionDuration(
192 base::TimeDelta::FromMilliseconds(kSnapDurationMS
));
193 window()->SetBounds(bounds
);
196 void WorkspaceWindowResizer::RevertDrag() {
197 window()->layer()->SetOpacity(details_
.initial_opacity
);
198 drag_phantom_window_controller_
.reset();
199 snap_phantom_window_controller_
.reset();
201 if (!did_move_or_resize_
)
204 window()->SetBounds(details_
.initial_bounds
);
205 if (details_
.window_component
== HTRIGHT
) {
206 int last_x
= details_
.initial_bounds
.right();
207 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
) {
208 gfx::Rect
bounds(attached_windows_
[i
]->bounds());
209 bounds
.set_x(last_x
);
210 bounds
.set_width(initial_size_
[i
]);
211 attached_windows_
[i
]->SetBounds(bounds
);
212 last_x
= attached_windows_
[i
]->bounds().right();
215 int last_y
= details_
.initial_bounds
.bottom();
216 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
) {
217 gfx::Rect
bounds(attached_windows_
[i
]->bounds());
218 bounds
.set_y(last_y
);
219 bounds
.set_height(initial_size_
[i
]);
220 attached_windows_
[i
]->SetBounds(bounds
);
221 last_y
= attached_windows_
[i
]->bounds().bottom();
226 WorkspaceWindowResizer::WorkspaceWindowResizer(
227 const Details
& details
,
228 const std::vector
<aura::Window
*>& attached_windows
)
230 attached_windows_(attached_windows
),
231 did_move_or_resize_(false),
233 total_initial_size_(0),
234 snap_type_(SNAP_NONE
),
235 num_mouse_moves_since_bounds_change_(0),
237 DCHECK(details_
.is_resizable
);
239 Shell
* shell
= Shell::GetInstance();
240 shell
->cursor_manager()->LockCursor();
242 // The pointer should be confined in one display during resizing a window
243 // because the window cannot span two displays at the same time anyway. The
244 // exception is window/tab dragging operation. During that operation,
245 // |dont_warp_mouse_| should be set to false so that the user could move a
246 // window/tab to another display.
247 shell
->display_controller()->set_dont_warp_mouse(!ShouldAllowMouseWarp());
249 // Only support attaching to the right/bottom.
250 DCHECK(attached_windows_
.empty() ||
251 (details
.window_component
== HTRIGHT
||
252 details
.window_component
== HTBOTTOM
));
254 // TODO: figure out how to deal with window going off the edge.
256 // Calculate sizes so that we can maintain the ratios if we need to resize.
257 int total_available
= 0;
258 int grid_size
= ash::Shell::GetInstance()->GetGridSize();
259 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
) {
260 gfx::Size
min(attached_windows_
[i
]->delegate()->GetMinimumSize());
261 int initial_size
= PrimaryAxisSize(attached_windows_
[i
]->bounds().size());
262 initial_size_
.push_back(initial_size
);
263 // If current size is smaller than the min, use the current size as the min.
264 // This way we don't snap on resize.
265 int min_size
= std::min(initial_size
,
266 std::max(PrimaryAxisSize(min
), kMinOnscreenSize
));
267 // Make sure the min size falls on the grid.
268 if (grid_size
> 1 && min_size
% grid_size
!= 0)
269 min_size
= (min_size
/ grid_size
+ 1) * grid_size
;
270 min_size_
.push_back(min_size
);
271 total_min_
+= min_size
;
272 total_initial_size_
+= initial_size
;
273 total_available
+= std::max(min_size
, initial_size
) - min_size
;
276 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
) {
277 expand_fraction_
.push_back(
278 static_cast<float>(initial_size_
[i
]) /
279 static_cast<float>(total_initial_size_
));
280 if (total_initial_size_
!= total_min_
) {
281 compress_fraction_
.push_back(
282 static_cast<float>(initial_size_
[i
] - min_size_
[i
]) /
283 static_cast<float>(total_available
));
285 compress_fraction_
.push_back(0.0f
);
290 gfx::Rect
WorkspaceWindowResizer::GetFinalBounds(
291 const gfx::Rect
& bounds
,
292 int grid_size
) const {
293 if (snap_phantom_window_controller_
.get() &&
294 snap_phantom_window_controller_
->IsShowing()) {
295 return snap_phantom_window_controller_
->bounds();
297 return AdjustBoundsToGrid(bounds
, grid_size
);
300 void WorkspaceWindowResizer::LayoutAttachedWindows(
301 const gfx::Rect
& bounds
,
303 gfx::Rect
work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
304 std::vector
<int> sizes
;
305 CalculateAttachedSizes(
306 PrimaryAxisSize(details_
.initial_bounds
.size()),
307 PrimaryAxisSize(bounds
.size()),
308 PrimaryAxisCoordinate(bounds
.right(), bounds
.bottom()),
309 PrimaryAxisCoordinate(work_area
.right(), work_area
.bottom()),
312 DCHECK_EQ(attached_windows_
.size(), sizes
.size());
313 int last
= PrimaryAxisCoordinate(bounds
.right(), bounds
.bottom());
314 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
) {
315 gfx::Rect
attached_bounds(attached_windows_
[i
]->bounds());
316 if (details_
.window_component
== HTRIGHT
) {
317 attached_bounds
.set_x(last
);
318 attached_bounds
.set_width(sizes
[i
]);
320 attached_bounds
.set_y(last
);
321 attached_bounds
.set_height(sizes
[i
]);
323 attached_windows_
[i
]->SetBounds(attached_bounds
);
328 void WorkspaceWindowResizer::CalculateAttachedSizes(
334 std::vector
<int>* sizes
) const {
336 if (current_size
< initial_size
) {
337 // If the primary window is sized smaller, resize the attached windows.
339 int delta
= initial_size
- current_size
;
340 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
) {
341 int next
= AlignToGrid(
342 current
+ initial_size_
[i
] + expand_fraction_
[i
] * delta
,
344 if (i
== attached_windows_
.size())
346 sizes
->push_back(next
- current
);
349 } else if (start
<= end
- total_initial_size_
) {
350 // All the windows fit at their initial size; tile them horizontally.
351 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
)
352 sizes
->push_back(initial_size_
[i
]);
354 DCHECK_NE(total_initial_size_
, total_min_
);
355 int delta
= total_initial_size_
- (end
- start
);
357 for (size_t i
= 0; i
< attached_windows_
.size(); ++i
) {
358 int size
= initial_size_
[i
] -
359 static_cast<int>(compress_fraction_
[i
] * delta
);
360 size
= AlignToGrid(size
, grid_size
);
361 if (i
== attached_windows_
.size())
362 size
= end
- current
;
364 sizes
->push_back(size
);
369 void WorkspaceWindowResizer::AdjustBoundsForMainWindow(
370 gfx::Rect
* bounds
, int grid_size
) const {
371 // Always keep kMinOnscreenHeight on the bottom except when an extended
372 // display is available and a window is being dragged.
374 ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
375 int max_y
= AlignToGridRoundUp(work_area
.bottom() - kMinOnscreenHeight
,
377 if ((details_
.window_component
!= HTCAPTION
|| !HasSecondaryRootWindow()) &&
378 bounds
->y() > max_y
) {
379 bounds
->set_y(max_y
);
382 // Don't allow dragging above the top of the display except when an extended
383 // display is available and a window is being dragged.
384 if ((details_
.window_component
!= HTCAPTION
|| !HasSecondaryRootWindow()) &&
385 bounds
->y() <= work_area
.y()) {
386 bounds
->set_y(work_area
.y());
389 if (grid_size
>= 0 && details_
.window_component
== HTCAPTION
)
390 SnapToWorkAreaEdges(work_area
, bounds
, grid_size
);
392 if (attached_windows_
.empty())
395 if (details_
.window_component
== HTRIGHT
) {
396 bounds
->set_width(std::min(bounds
->width(),
397 work_area
.right() - total_min_
- bounds
->x()));
399 DCHECK_EQ(HTBOTTOM
, details_
.window_component
);
400 bounds
->set_height(std::min(bounds
->height(),
401 work_area
.bottom() - total_min_
- bounds
->y()));
405 void WorkspaceWindowResizer::SnapToWorkAreaEdges(
406 const gfx::Rect
& work_area
,
408 int grid_size
) const {
409 int left_edge
= AlignToGridRoundUp(work_area
.x(), grid_size
);
410 int right_edge
= AlignToGridRoundDown(work_area
.right(), grid_size
);
411 int top_edge
= AlignToGridRoundUp(work_area
.y(), grid_size
);
412 int bottom_edge
= AlignToGridRoundDown(work_area
.bottom(),
414 if (ShouldSnapToEdge(bounds
->x() - left_edge
, grid_size
)) {
415 bounds
->set_x(left_edge
);
416 } else if (ShouldSnapToEdge(right_edge
- bounds
->right(),
418 bounds
->set_x(right_edge
- bounds
->width());
420 if (ShouldSnapToEdge(bounds
->y() - top_edge
, grid_size
)) {
421 bounds
->set_y(top_edge
);
422 } else if (ShouldSnapToEdge(bottom_edge
- bounds
->bottom(), grid_size
) &&
423 bounds
->height() < (bottom_edge
- top_edge
)) {
424 // Only snap to the bottom if the window is smaller than the work area.
425 // Doing otherwise can lead to window snapping in weird ways as it bounces
426 // between snapping to top then bottom.
427 bounds
->set_y(bottom_edge
- bounds
->height());
431 bool WorkspaceWindowResizer::TouchesBottomOfScreen() const {
433 ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
434 return (attached_windows_
.empty() &&
435 window()->bounds().bottom() == work_area
.bottom()) ||
436 (!attached_windows_
.empty() &&
437 attached_windows_
.back()->bounds().bottom() == work_area
.bottom());
440 int WorkspaceWindowResizer::PrimaryAxisSize(const gfx::Size
& size
) const {
441 return PrimaryAxisCoordinate(size
.width(), size
.height());
444 int WorkspaceWindowResizer::PrimaryAxisCoordinate(int x
, int y
) const {
445 switch (details_
.window_component
) {
456 void WorkspaceWindowResizer::UpdateDragPhantomWindow(const gfx::Rect
& bounds
,
457 bool in_original_root
) {
458 if (!did_move_or_resize_
|| details_
.window_component
!= HTCAPTION
||
459 !ShouldAllowMouseWarp()) {
463 // It's available. Show a phantom window on the display if needed.
464 aura::RootWindow
* another_root
=
465 GetAnotherRootWindow(window()->GetRootWindow());
466 const gfx::Rect
root_bounds_in_screen(another_root
->GetBoundsInScreen());
467 const gfx::Rect bounds_in_screen
=
468 ScreenAsh::ConvertRectToScreen(window()->parent(), bounds
);
469 const gfx::Rect bounds_in_another_root
=
470 root_bounds_in_screen
.Intersect(bounds_in_screen
);
472 const float fraction_in_another_window
=
473 (bounds_in_another_root
.width() * bounds_in_another_root
.height()) /
474 static_cast<float>(bounds
.width() * bounds
.height());
475 const float phantom_opacity
=
476 !in_original_root
? 1 : (kMaxOpacity
* fraction_in_another_window
);
477 const float window_opacity
=
478 in_original_root
? 1 : (kMaxOpacity
* (1 - fraction_in_another_window
));
480 if (fraction_in_another_window
> 0) {
481 if (!drag_phantom_window_controller_
.get()) {
482 drag_phantom_window_controller_
.reset(
483 new PhantomWindowController(window()));
484 drag_phantom_window_controller_
->set_style(
485 PhantomWindowController::STYLE_DRAGGING
);
486 // Always show the drag phantom on the |another_root| window.
487 drag_phantom_window_controller_
->SetDestinationDisplay(
488 gfx::Screen::GetDisplayMatching(another_root
->GetBoundsInScreen()));
490 RecreateWindowLayers();
491 drag_phantom_window_controller_
->Show(bounds_in_screen
, layer_
);
494 drag_phantom_window_controller_
->SetBounds(bounds_in_screen
);
496 drag_phantom_window_controller_
->SetOpacity(phantom_opacity
);
497 window()->layer()->SetOpacity(window_opacity
);
499 drag_phantom_window_controller_
.reset();
500 window()->layer()->SetOpacity(1.0f
);
504 void WorkspaceWindowResizer::UpdateSnapPhantomWindow(const gfx::Point
& location
,
505 const gfx::Rect
& bounds
,
507 if (!did_move_or_resize_
|| details_
.window_component
!= HTCAPTION
)
510 SnapType last_type
= snap_type_
;
511 snap_type_
= GetSnapType(location
);
512 if (snap_type_
== SNAP_NONE
|| snap_type_
!= last_type
) {
513 snap_phantom_window_controller_
.reset();
515 if (snap_type_
== SNAP_NONE
)
518 if (!snap_sizer_
.get()) {
519 SnapSizer::Edge edge
= (snap_type_
== SNAP_LEFT_EDGE
) ?
520 SnapSizer::LEFT_EDGE
: SnapSizer::RIGHT_EDGE
;
522 new SnapSizer(window(), location
, edge
, grid_size
));
524 snap_sizer_
->Update(location
);
526 if (!snap_phantom_window_controller_
.get()) {
527 snap_phantom_window_controller_
.reset(
528 new PhantomWindowController(window()));
530 snap_phantom_window_controller_
->Show(ScreenAsh::ConvertRectToScreen(
531 window()->parent(), snap_sizer_
->target_bounds()), NULL
);
534 void WorkspaceWindowResizer::RestackWindows() {
535 if (attached_windows_
.empty())
537 // Build a map from index in children to window, returning if there is a
538 // window with a different parent.
539 typedef std::map
<size_t, aura::Window
*> IndexToWindowMap
;
540 IndexToWindowMap map
;
541 aura::Window
* parent
= window()->parent();
542 const aura::Window::Windows
& windows(parent
->children());
543 map
[std::find(windows
.begin(), windows
.end(), window()) -
544 windows
.begin()] = window();
545 for (std::vector
<aura::Window
*>::const_iterator i
=
546 attached_windows_
.begin(); i
!= attached_windows_
.end(); ++i
) {
547 if ((*i
)->parent() != parent
)
550 std::find(windows
.begin(), windows
.end(), *i
) - windows
.begin();
554 // Reorder the windows starting at the topmost.
555 parent
->StackChildAtTop(map
.rbegin()->second
);
556 for (IndexToWindowMap::const_reverse_iterator i
= map
.rbegin();
558 aura::Window
* window
= i
->second
;
561 parent
->StackChildBelow(i
->second
, window
);
565 WorkspaceWindowResizer::SnapType
WorkspaceWindowResizer::GetSnapType(
566 const gfx::Point
& location
) const {
567 // TODO: this likely only wants total display area, not the area of a single
569 gfx::Rect
area(ScreenAsh::GetDisplayBoundsInParent(window()));
570 if (location
.x() <= area
.x())
571 return SNAP_LEFT_EDGE
;
572 if (location
.x() >= area
.right() - 1)
573 return SNAP_RIGHT_EDGE
;
577 bool WorkspaceWindowResizer::ShouldAllowMouseWarp() const {
578 return (details_
.window_component
== HTCAPTION
) &&
579 (window()->GetProperty(aura::client::kModalKey
) == ui::MODAL_TYPE_NONE
) &&
580 (window()->type() == aura::client::WINDOW_TYPE_NORMAL
);
583 void WorkspaceWindowResizer::RecreateWindowLayers() {
585 layer_
= wm::RecreateWindowLayers(window());
586 layer_
->set_delegate(window()->layer()->delegate());
587 // Place the layer at (0, 0) of the PhantomWindowController's window.
588 gfx::Rect layer_bounds
= layer_
->bounds();
589 layer_bounds
.set_origin(gfx::Point(0, 0));
590 layer_
->SetBounds(layer_bounds
);
591 layer_
->SetVisible(false);
592 // Detach it from the current container.
593 layer_
->parent()->Remove(layer_
);
596 } // namespace internal