Show shadow for the dragging window.
[chromium-blink-merge.git] / ash / wm / workspace / workspace_window_resizer.cc
bloba70367d60736021926d6f135c1b389a3eb8f0a90
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"
7 #include <algorithm>
8 #include <cmath>
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"
29 namespace ash {
30 namespace internal {
32 namespace {
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)
56 return NULL;
57 DCHECK_EQ(2U, root_windows.size());
58 if (root_windows[0] == root_window)
59 return root_windows[1];
60 return root_windows[0];
63 } // namespace
65 // static
66 const int WorkspaceWindowResizer::kMinOnscreenSize = 20;
68 // static
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
77 // |layer_|.
78 snap_phantom_window_controller_.reset();
79 drag_phantom_window_controller_.reset();
81 if (layer_)
82 wm::DeepDeleteLayers(layer_);
85 // static
86 WorkspaceWindowResizer* WorkspaceWindowResizer::Create(
87 aura::Window* window,
88 const gfx::Point& location_in_parent,
89 int window_component,
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,
102 window()->parent(),
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_)
122 RestackWindows();
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);
130 else
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);
136 else
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)
151 return;
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());
157 return;
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);
176 return;
179 if (grid_size <= 1 || bounds == window()->bounds())
180 return;
182 if (bounds.size() != window()->bounds().size()) {
183 // Don't attempt to animate a size change.
184 window()->SetBounds(bounds);
185 return;
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_)
202 return;
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();
214 } else {
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)
229 : details_(details),
230 attached_windows_(attached_windows),
231 did_move_or_resize_(false),
232 total_min_(0),
233 total_initial_size_(0),
234 snap_type_(SNAP_NONE),
235 num_mouse_moves_since_bounds_change_(0),
236 layer_(NULL) {
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));
284 } else {
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,
302 int grid_size) {
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()),
310 grid_size,
311 &sizes);
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]);
319 } else {
320 attached_bounds.set_y(last);
321 attached_bounds.set_height(sizes[i]);
323 attached_windows_[i]->SetBounds(attached_bounds);
324 last += sizes[i];
328 void WorkspaceWindowResizer::CalculateAttachedSizes(
329 int initial_size,
330 int current_size,
331 int start,
332 int end,
333 int grid_size,
334 std::vector<int>* sizes) const {
335 sizes->clear();
336 if (current_size < initial_size) {
337 // If the primary window is sized smaller, resize the attached windows.
338 int current = start;
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,
343 grid_size);
344 if (i == attached_windows_.size())
345 next = end;
346 sizes->push_back(next - current);
347 current = next;
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]);
353 } else {
354 DCHECK_NE(total_initial_size_, total_min_);
355 int delta = total_initial_size_ - (end - start);
356 int current = 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;
363 current += size;
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.
373 gfx::Rect work_area(
374 ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
375 int max_y = AlignToGridRoundUp(work_area.bottom() - kMinOnscreenHeight,
376 grid_size);
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())
393 return;
395 if (details_.window_component == HTRIGHT) {
396 bounds->set_width(std::min(bounds->width(),
397 work_area.right() - total_min_ - bounds->x()));
398 } else {
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,
407 gfx::Rect* bounds,
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(),
413 grid_size);
414 if (ShouldSnapToEdge(bounds->x() - left_edge, grid_size)) {
415 bounds->set_x(left_edge);
416 } else if (ShouldSnapToEdge(right_edge - bounds->right(),
417 grid_size)) {
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 {
432 gfx::Rect work_area(
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) {
446 case HTRIGHT:
447 return x;
448 case HTBOTTOM:
449 return y;
450 default:
451 NOTREACHED();
453 return 0;
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()) {
460 return;
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()));
489 if (!layer_)
490 RecreateWindowLayers();
491 drag_phantom_window_controller_->Show(bounds_in_screen, layer_);
492 } else {
493 // No animation.
494 drag_phantom_window_controller_->SetBounds(bounds_in_screen);
496 drag_phantom_window_controller_->SetOpacity(phantom_opacity);
497 window()->layer()->SetOpacity(window_opacity);
498 } else {
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,
506 int grid_size) {
507 if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
508 return;
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();
514 snap_sizer_.reset();
515 if (snap_type_ == SNAP_NONE)
516 return;
518 if (!snap_sizer_.get()) {
519 SnapSizer::Edge edge = (snap_type_ == SNAP_LEFT_EDGE) ?
520 SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE;
521 snap_sizer_.reset(
522 new SnapSizer(window(), location, edge, grid_size));
523 } else {
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())
536 return;
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)
548 return;
549 size_t index =
550 std::find(windows.begin(), windows.end(), *i) - windows.begin();
551 map[index] = *i;
554 // Reorder the windows starting at the topmost.
555 parent->StackChildAtTop(map.rbegin()->second);
556 for (IndexToWindowMap::const_reverse_iterator i = map.rbegin();
557 i != map.rend(); ) {
558 aura::Window* window = i->second;
559 ++i;
560 if (i != map.rend())
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
568 // display.
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;
574 return SNAP_NONE;
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() {
584 DCHECK(!layer_);
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
597 } // namespace ash