Windows should animate when they are about to get docked at screen edges.
[chromium-blink-merge.git] / ash / wm / workspace / workspace_window_resizer.cc
blobd1f8492b75d23974262c777470e6b08ea28e6971
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>
9 #include <utility>
10 #include <vector>
12 #include "ash/ash_switches.h"
13 #include "ash/root_window_controller.h"
14 #include "ash/screen_ash.h"
15 #include "ash/shell.h"
16 #include "ash/shell_window_ids.h"
17 #include "ash/wm/coordinate_conversion.h"
18 #include "ash/wm/default_window_resizer.h"
19 #include "ash/wm/dock/docked_window_resizer.h"
20 #include "ash/wm/drag_window_resizer.h"
21 #include "ash/wm/panels/panel_window_resizer.h"
22 #include "ash/wm/property_util.h"
23 #include "ash/wm/window_properties.h"
24 #include "ash/wm/window_util.h"
25 #include "ash/wm/workspace/phantom_window_controller.h"
26 #include "ash/wm/workspace/snap_sizer.h"
27 #include "base/command_line.h"
28 #include "ui/aura/client/aura_constants.h"
29 #include "ui/aura/client/screen_position_client.h"
30 #include "ui/aura/client/window_types.h"
31 #include "ui/aura/root_window.h"
32 #include "ui/aura/window.h"
33 #include "ui/aura/window_delegate.h"
34 #include "ui/base/hit_test.h"
35 #include "ui/compositor/layer.h"
36 #include "ui/gfx/screen.h"
37 #include "ui/gfx/transform.h"
39 namespace ash {
41 scoped_ptr<WindowResizer> CreateWindowResizer(
42 aura::Window* window,
43 const gfx::Point& point_in_parent,
44 int window_component,
45 aura::client::WindowMoveSource source) {
46 DCHECK(window);
47 // No need to return a resizer when the window cannot get resized.
48 if (!wm::CanResizeWindow(window) && window_component != HTCAPTION)
49 return scoped_ptr<WindowResizer>();
51 // TODO(varkha): The chaining of window resizers causes some of the logic
52 // to be repeated and the logic flow difficult to control. With some windows
53 // classes using reparenting during drag operations it becomes challenging to
54 // implement proper transition from one resizer to another during or at the
55 // end of the drag. This also causes http://crbug.com/247085.
56 // It seems the only thing the panel or dock resizer needs to do is notify the
57 // layout manager when a docked window is being dragged. We should have a
58 // better way of doing this, perhaps by having a way of observing drags or
59 // having a generic drag window wrapper which informs a layout manager that a
60 // drag has started or stopped.
61 // It may be possible to refactor and eliminate chaining.
62 WindowResizer* window_resizer = NULL;
63 if (window->parent() &&
64 (window->parent()->id() == internal::kShellWindowId_DefaultContainer ||
65 window->parent()->id() == internal::kShellWindowId_DockedContainer ||
66 window->parent()->id() == internal::kShellWindowId_PanelContainer)) {
67 // Allow dragging maximized windows if it's not tracked by workspace. This
68 // is set by tab dragging code.
69 if (!wm::IsWindowNormal(window) &&
70 (window_component != HTCAPTION || GetTrackedByWorkspace(window)))
71 return scoped_ptr<WindowResizer>();
72 window_resizer = internal::WorkspaceWindowResizer::Create(
73 window,
74 point_in_parent,
75 window_component,
76 source,
77 std::vector<aura::Window*>());
78 } else if (wm::IsWindowNormal(window)) {
79 window_resizer = DefaultWindowResizer::Create(
80 window, point_in_parent, window_component, source);
82 if (window_resizer) {
83 window_resizer = internal::DragWindowResizer::Create(
84 window_resizer, window, point_in_parent, window_component, source);
86 if (window_resizer && window->type() == aura::client::WINDOW_TYPE_PANEL) {
87 window_resizer = PanelWindowResizer::Create(
88 window_resizer, window, point_in_parent, window_component, source);
90 if (CommandLine::ForCurrentProcess()->HasSwitch(
91 switches::kAshEnableDockedWindows) &&
92 window_resizer && window->parent() &&
93 (window->parent()->id() == internal::kShellWindowId_DefaultContainer ||
94 window->parent()->id() == internal::kShellWindowId_DockedContainer ||
95 window->parent()->id() == internal::kShellWindowId_PanelContainer)) {
96 window_resizer = internal::DockedWindowResizer::Create(
97 window_resizer, window, point_in_parent, window_component, source);
99 return make_scoped_ptr<WindowResizer>(window_resizer);
102 namespace internal {
104 namespace {
106 // Snapping distance used instead of WorkspaceWindowResizer::kScreenEdgeInset
107 // when resizing a window using touchscreen.
108 const int kScreenEdgeInsetForTouchResize = 32;
110 // Returns true if the window should stick to the edge.
111 bool ShouldStickToEdge(int distance_from_edge, int sticky_size) {
112 if (CommandLine::ForCurrentProcess()->HasSwitch(
113 switches::kAshEnableStickyEdges) ||
114 CommandLine::ForCurrentProcess()->HasSwitch(
115 switches::kAshEnableDockedWindows)) {
116 return distance_from_edge < 0 &&
117 distance_from_edge > -sticky_size;
119 return distance_from_edge < sticky_size &&
120 distance_from_edge > -sticky_size * 2;
123 // Returns the coordinate along the secondary axis to snap to.
124 int CoordinateAlongSecondaryAxis(SecondaryMagnetismEdge edge,
125 int leading,
126 int trailing,
127 int none) {
128 switch (edge) {
129 case SECONDARY_MAGNETISM_EDGE_LEADING:
130 return leading;
131 case SECONDARY_MAGNETISM_EDGE_TRAILING:
132 return trailing;
133 case SECONDARY_MAGNETISM_EDGE_NONE:
134 return none;
136 NOTREACHED();
137 return none;
140 // Returns the origin for |src| when magnetically attaching to |attach_to| along
141 // the edges |edges|. |edges| is a bitmask of the MagnetismEdges.
142 gfx::Point OriginForMagneticAttach(const gfx::Rect& src,
143 const gfx::Rect& attach_to,
144 const MatchedEdge& edge) {
145 int x = 0, y = 0;
146 switch (edge.primary_edge) {
147 case MAGNETISM_EDGE_TOP:
148 y = attach_to.bottom();
149 break;
150 case MAGNETISM_EDGE_LEFT:
151 x = attach_to.right();
152 break;
153 case MAGNETISM_EDGE_BOTTOM:
154 y = attach_to.y() - src.height();
155 break;
156 case MAGNETISM_EDGE_RIGHT:
157 x = attach_to.x() - src.width();
158 break;
160 switch (edge.primary_edge) {
161 case MAGNETISM_EDGE_TOP:
162 case MAGNETISM_EDGE_BOTTOM:
163 x = CoordinateAlongSecondaryAxis(
164 edge.secondary_edge, attach_to.x(), attach_to.right() - src.width(),
165 src.x());
166 break;
167 case MAGNETISM_EDGE_LEFT:
168 case MAGNETISM_EDGE_RIGHT:
169 y = CoordinateAlongSecondaryAxis(
170 edge.secondary_edge, attach_to.y(), attach_to.bottom() - src.height(),
171 src.y());
172 break;
174 return gfx::Point(x, y);
177 // Returns the bounds for a magnetic attach when resizing. |src| is the bounds
178 // of window being resized, |attach_to| the bounds of the window to attach to
179 // and |edge| identifies the edge to attach to.
180 gfx::Rect BoundsForMagneticResizeAttach(const gfx::Rect& src,
181 const gfx::Rect& attach_to,
182 const MatchedEdge& edge) {
183 int x = src.x();
184 int y = src.y();
185 int w = src.width();
186 int h = src.height();
187 gfx::Point attach_origin(OriginForMagneticAttach(src, attach_to, edge));
188 switch (edge.primary_edge) {
189 case MAGNETISM_EDGE_LEFT:
190 x = attach_origin.x();
191 w = src.right() - x;
192 break;
193 case MAGNETISM_EDGE_RIGHT:
194 w += attach_origin.x() - src.x();
195 break;
196 case MAGNETISM_EDGE_TOP:
197 y = attach_origin.y();
198 h = src.bottom() - y;
199 break;
200 case MAGNETISM_EDGE_BOTTOM:
201 h += attach_origin.y() - src.y();
202 break;
204 switch (edge.primary_edge) {
205 case MAGNETISM_EDGE_LEFT:
206 case MAGNETISM_EDGE_RIGHT:
207 if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) {
208 y = attach_origin.y();
209 h = src.bottom() - y;
210 } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) {
211 h += attach_origin.y() - src.y();
213 break;
214 case MAGNETISM_EDGE_TOP:
215 case MAGNETISM_EDGE_BOTTOM:
216 if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) {
217 x = attach_origin.x();
218 w = src.right() - x;
219 } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) {
220 w += attach_origin.x() - src.x();
222 break;
224 return gfx::Rect(x, y, w, h);
227 // Converts a window component edge to the magnetic edge to snap to.
228 uint32 WindowComponentToMagneticEdge(int window_component) {
229 switch (window_component) {
230 case HTTOPLEFT:
231 return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_TOP;
232 case HTTOPRIGHT:
233 return MAGNETISM_EDGE_TOP | MAGNETISM_EDGE_RIGHT;
234 case HTBOTTOMLEFT:
235 return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_BOTTOM;
236 case HTBOTTOMRIGHT:
237 return MAGNETISM_EDGE_RIGHT | MAGNETISM_EDGE_BOTTOM;
238 case HTTOP:
239 return MAGNETISM_EDGE_TOP;
240 case HTBOTTOM:
241 return MAGNETISM_EDGE_BOTTOM;
242 case HTRIGHT:
243 return MAGNETISM_EDGE_RIGHT;
244 case HTLEFT:
245 return MAGNETISM_EDGE_LEFT;
246 default:
247 break;
249 return 0;
252 } // namespace
254 // static
255 const int WorkspaceWindowResizer::kMinOnscreenSize = 20;
257 // static
258 const int WorkspaceWindowResizer::kMinOnscreenHeight = 32;
260 // static
261 const int WorkspaceWindowResizer::kScreenEdgeInset = 8;
263 // static
264 const int WorkspaceWindowResizer::kStickyDistancePixels = 64;
266 // Represents the width or height of a window with constraints on its minimum
267 // and maximum size. 0 represents a lack of a constraint.
268 class WindowSize {
269 public:
270 WindowSize(int size, int min, int max)
271 : size_(size),
272 min_(min),
273 max_(max) {
274 // Grow the min/max bounds to include the starting size.
275 if (is_underflowing())
276 min_ = size_;
277 if (is_overflowing())
278 max_ = size_;
281 bool is_at_capacity(bool shrinking) {
282 return size_ == (shrinking ? min_ : max_);
285 int size() const {
286 return size_;
289 bool has_min() const {
290 return min_ != 0;
293 bool has_max() const {
294 return max_ != 0;
297 bool is_valid() const {
298 return !is_overflowing() && !is_underflowing();
301 bool is_overflowing() const {
302 return has_max() && size_ > max_;
305 bool is_underflowing() const {
306 return has_min() && size_ < min_;
309 // Add |amount| to this WindowSize not exceeding min or max size constraints.
310 // Returns by how much |size_| + |amount| exceeds the min/max constraints.
311 int Add(int amount) {
312 DCHECK(is_valid());
313 int new_value = size_ + amount;
315 if (has_min() && new_value < min_) {
316 size_ = min_;
317 return new_value - min_;
320 if (has_max() && new_value > max_) {
321 size_ = max_;
322 return new_value - max_;
325 size_ = new_value;
326 return 0;
329 private:
330 int size_;
331 int min_;
332 int max_;
335 WorkspaceWindowResizer::~WorkspaceWindowResizer() {
336 Shell* shell = Shell::GetInstance();
337 shell->cursor_manager()->UnlockCursor();
340 // static
341 WorkspaceWindowResizer* WorkspaceWindowResizer::Create(
342 aura::Window* window,
343 const gfx::Point& location_in_parent,
344 int window_component,
345 aura::client::WindowMoveSource source,
346 const std::vector<aura::Window*>& attached_windows) {
347 Details details(window, location_in_parent, window_component, source);
348 return details.is_resizable ?
349 new WorkspaceWindowResizer(details, attached_windows) : NULL;
352 void WorkspaceWindowResizer::Drag(const gfx::Point& location_in_parent,
353 int event_flags) {
354 last_mouse_location_ = location_in_parent;
356 int sticky_size;
357 if (event_flags & ui::EF_CONTROL_DOWN) {
358 sticky_size = 0;
359 } else if (CommandLine::ForCurrentProcess()->HasSwitch(
360 switches::kAshEnableStickyEdges) ||
361 CommandLine::ForCurrentProcess()->HasSwitch(
362 switches::kAshEnableDockedWindows)) {
363 sticky_size = kStickyDistancePixels;
364 } else if ((details_.bounds_change & kBoundsChange_Resizes) &&
365 details_.source == aura::client::WINDOW_MOVE_SOURCE_TOUCH) {
366 sticky_size = kScreenEdgeInsetForTouchResize;
367 } else {
368 sticky_size = kScreenEdgeInset;
370 // |bounds| is in |window()->parent()|'s coordinates.
371 gfx::Rect bounds = CalculateBoundsForDrag(details_, location_in_parent);
373 if (wm::IsWindowNormal(window()))
374 AdjustBoundsForMainWindow(sticky_size, &bounds);
376 if (bounds != window()->bounds()) {
377 if (!did_move_or_resize_) {
378 if (!details_.restore_bounds.IsEmpty())
379 ClearRestoreBounds(window());
380 RestackWindows();
382 did_move_or_resize_ = true;
385 gfx::Point location_in_screen = location_in_parent;
386 wm::ConvertPointToScreen(window()->parent(), &location_in_screen);
387 const bool in_original_root =
388 wm::GetRootWindowAt(location_in_screen) == window()->GetRootWindow();
389 // Hide a phantom window for snapping if the cursor is in another root window.
390 if (in_original_root && wm::CanResizeWindow(window())) {
391 UpdateSnapPhantomWindow(location_in_parent, bounds);
392 } else {
393 snap_type_ = SNAP_NONE;
394 snap_phantom_window_controller_.reset();
397 if (!attached_windows_.empty())
398 LayoutAttachedWindows(&bounds);
399 if (bounds != window()->bounds())
400 window()->SetBounds(bounds);
403 void WorkspaceWindowResizer::CompleteDrag(int event_flags) {
404 wm::SetUserHasChangedWindowPositionOrSize(details_.window, true);
405 snap_phantom_window_controller_.reset();
406 if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
407 return;
409 // When the window is not in the normal show state, we do not snap the window.
410 // This happens when the user minimizes or maximizes the window by keyboard
411 // shortcut while dragging it. If the window is the result of dragging a tab
412 // out of a maximized window, it's already in the normal show state when this
413 // is called, so it does not matter.
414 if (wm::IsWindowNormal(window()) &&
415 (window()->type() != aura::client::WINDOW_TYPE_PANEL ||
416 !window()->GetProperty(kPanelAttachedKey)) &&
417 (snap_type_ == SNAP_LEFT_EDGE || snap_type_ == SNAP_RIGHT_EDGE)) {
418 if (!GetRestoreBoundsInScreen(window())) {
419 gfx::Rect initial_bounds = ScreenAsh::ConvertRectToScreen(
420 window()->parent(), details_.initial_bounds_in_parent);
421 SetRestoreBoundsInScreen(window(), details_.restore_bounds.IsEmpty() ?
422 initial_bounds :
423 details_.restore_bounds);
425 window()->SetBounds(snap_sizer_->target_bounds());
426 return;
430 void WorkspaceWindowResizer::RevertDrag() {
431 snap_phantom_window_controller_.reset();
433 if (!did_move_or_resize_)
434 return;
436 window()->SetBounds(details_.initial_bounds_in_parent);
437 if (!details_.restore_bounds.IsEmpty())
438 SetRestoreBoundsInScreen(details_.window, details_.restore_bounds);
440 if (details_.window_component == HTRIGHT) {
441 int last_x = details_.initial_bounds_in_parent.right();
442 for (size_t i = 0; i < attached_windows_.size(); ++i) {
443 gfx::Rect bounds(attached_windows_[i]->bounds());
444 bounds.set_x(last_x);
445 bounds.set_width(initial_size_[i]);
446 attached_windows_[i]->SetBounds(bounds);
447 last_x = attached_windows_[i]->bounds().right();
449 } else {
450 int last_y = details_.initial_bounds_in_parent.bottom();
451 for (size_t i = 0; i < attached_windows_.size(); ++i) {
452 gfx::Rect bounds(attached_windows_[i]->bounds());
453 bounds.set_y(last_y);
454 bounds.set_height(initial_size_[i]);
455 attached_windows_[i]->SetBounds(bounds);
456 last_y = attached_windows_[i]->bounds().bottom();
461 aura::Window* WorkspaceWindowResizer::GetTarget() {
462 return details_.window;
465 const gfx::Point& WorkspaceWindowResizer::GetInitialLocation() const {
466 return details_.initial_location_in_parent;
469 WorkspaceWindowResizer::WorkspaceWindowResizer(
470 const Details& details,
471 const std::vector<aura::Window*>& attached_windows)
472 : details_(details),
473 attached_windows_(attached_windows),
474 did_move_or_resize_(false),
475 total_min_(0),
476 total_initial_size_(0),
477 snap_type_(SNAP_NONE),
478 num_mouse_moves_since_bounds_change_(0),
479 magnetism_window_(NULL) {
480 DCHECK(details_.is_resizable);
482 Shell* shell = Shell::GetInstance();
483 shell->cursor_manager()->LockCursor();
485 // Only support attaching to the right/bottom.
486 DCHECK(attached_windows_.empty() ||
487 (details.window_component == HTRIGHT ||
488 details.window_component == HTBOTTOM));
490 // TODO: figure out how to deal with window going off the edge.
492 // Calculate sizes so that we can maintain the ratios if we need to resize.
493 int total_available = 0;
494 for (size_t i = 0; i < attached_windows_.size(); ++i) {
495 gfx::Size min(attached_windows_[i]->delegate()->GetMinimumSize());
496 int initial_size = PrimaryAxisSize(attached_windows_[i]->bounds().size());
497 initial_size_.push_back(initial_size);
498 // If current size is smaller than the min, use the current size as the min.
499 // This way we don't snap on resize.
500 int min_size = std::min(initial_size,
501 std::max(PrimaryAxisSize(min), kMinOnscreenSize));
502 total_min_ += min_size;
503 total_initial_size_ += initial_size;
504 total_available += std::max(min_size, initial_size) - min_size;
508 gfx::Rect WorkspaceWindowResizer::GetFinalBounds(
509 const gfx::Rect& bounds) const {
510 if (snap_phantom_window_controller_.get() &&
511 snap_phantom_window_controller_->IsShowing()) {
512 return snap_phantom_window_controller_->bounds();
514 return bounds;
517 void WorkspaceWindowResizer::LayoutAttachedWindows(
518 gfx::Rect* bounds) {
519 gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
520 int initial_size = PrimaryAxisSize(details_.initial_bounds_in_parent.size());
521 int current_size = PrimaryAxisSize(bounds->size());
522 int start = PrimaryAxisCoordinate(bounds->right(), bounds->bottom());
523 int end = PrimaryAxisCoordinate(work_area.right(), work_area.bottom());
525 int delta = current_size - initial_size;
526 int available_size = end - start;
527 std::vector<int> sizes;
528 int leftovers = CalculateAttachedSizes(delta, available_size, &sizes);
530 // leftovers > 0 means that the attached windows can't grow to compensate for
531 // the shrinkage of the main window. This line causes the attached windows to
532 // be moved so they are still flush against the main window, rather than the
533 // main window being prevented from shrinking.
534 leftovers = std::min(0, leftovers);
535 // Reallocate any leftover pixels back into the main window. This is
536 // necessary when, for example, the main window shrinks, but none of the
537 // attached windows can grow without exceeding their max size constraints.
538 // Adding the pixels back to the main window effectively prevents the main
539 // window from resizing too far.
540 if (details_.window_component == HTRIGHT)
541 bounds->set_width(bounds->width() + leftovers);
542 else
543 bounds->set_height(bounds->height() + leftovers);
545 DCHECK_EQ(attached_windows_.size(), sizes.size());
546 int last = PrimaryAxisCoordinate(bounds->right(), bounds->bottom());
547 for (size_t i = 0; i < attached_windows_.size(); ++i) {
548 gfx::Rect attached_bounds(attached_windows_[i]->bounds());
549 if (details_.window_component == HTRIGHT) {
550 attached_bounds.set_x(last);
551 attached_bounds.set_width(sizes[i]);
552 } else {
553 attached_bounds.set_y(last);
554 attached_bounds.set_height(sizes[i]);
556 attached_windows_[i]->SetBounds(attached_bounds);
557 last += sizes[i];
561 int WorkspaceWindowResizer::CalculateAttachedSizes(
562 int delta,
563 int available_size,
564 std::vector<int>* sizes) const {
565 std::vector<WindowSize> window_sizes;
566 CreateBucketsForAttached(&window_sizes);
568 // How much we need to grow the attached by (collectively).
569 int grow_attached_by = 0;
570 if (delta > 0) {
571 // If the attached windows don't fit when at their initial size, we will
572 // have to shrink them by how much they overflow.
573 if (total_initial_size_ >= available_size)
574 grow_attached_by = available_size - total_initial_size_;
575 } else {
576 // If we're shrinking, we grow the attached so the total size remains
577 // constant.
578 grow_attached_by = -delta;
581 int leftover_pixels = 0;
582 while (grow_attached_by != 0) {
583 int leftovers = GrowFairly(grow_attached_by, window_sizes);
584 if (leftovers == grow_attached_by) {
585 leftover_pixels = leftovers;
586 break;
588 grow_attached_by = leftovers;
591 for (size_t i = 0; i < window_sizes.size(); ++i)
592 sizes->push_back(window_sizes[i].size());
594 return leftover_pixels;
597 int WorkspaceWindowResizer::GrowFairly(
598 int pixels,
599 std::vector<WindowSize>& sizes) const {
600 bool shrinking = pixels < 0;
601 std::vector<WindowSize*> nonfull_windows;
602 for (size_t i = 0; i < sizes.size(); ++i) {
603 if (!sizes[i].is_at_capacity(shrinking))
604 nonfull_windows.push_back(&sizes[i]);
606 std::vector<float> ratios;
607 CalculateGrowthRatios(nonfull_windows, &ratios);
609 int remaining_pixels = pixels;
610 bool add_leftover_pixels_to_last = true;
611 for (size_t i = 0; i < nonfull_windows.size(); ++i) {
612 int grow_by = pixels * ratios[i];
613 // Put any leftover pixels into the last window.
614 if (i == nonfull_windows.size() - 1 && add_leftover_pixels_to_last)
615 grow_by = remaining_pixels;
616 int remainder = nonfull_windows[i]->Add(grow_by);
617 int consumed = grow_by - remainder;
618 remaining_pixels -= consumed;
619 if (nonfull_windows[i]->is_at_capacity(shrinking) && remainder > 0) {
620 // Because this window overflowed, some of the pixels in
621 // |remaining_pixels| aren't there due to rounding errors. Rather than
622 // unfairly giving all those pixels to the last window, we refrain from
623 // allocating them so that this function can be called again to distribute
624 // the pixels fairly.
625 add_leftover_pixels_to_last = false;
628 return remaining_pixels;
631 void WorkspaceWindowResizer::CalculateGrowthRatios(
632 const std::vector<WindowSize*>& sizes,
633 std::vector<float>* out_ratios) const {
634 DCHECK(out_ratios->empty());
635 int total_value = 0;
636 for (size_t i = 0; i < sizes.size(); ++i)
637 total_value += sizes[i]->size();
639 for (size_t i = 0; i < sizes.size(); ++i)
640 out_ratios->push_back(
641 (static_cast<float>(sizes[i]->size())) / total_value);
644 void WorkspaceWindowResizer::CreateBucketsForAttached(
645 std::vector<WindowSize>* sizes) const {
646 for (size_t i = 0; i < attached_windows_.size(); i++) {
647 int initial_size = initial_size_[i];
648 aura::WindowDelegate* delegate = attached_windows_[i]->delegate();
649 int min = PrimaryAxisSize(delegate->GetMinimumSize());
650 int max = PrimaryAxisSize(delegate->GetMaximumSize());
652 sizes->push_back(WindowSize(initial_size, min, max));
656 void WorkspaceWindowResizer::MagneticallySnapToOtherWindows(gfx::Rect* bounds) {
657 if (UpdateMagnetismWindow(*bounds, kAllMagnetismEdges)) {
658 gfx::Point point = OriginForMagneticAttach(
659 ScreenAsh::ConvertRectToScreen(window()->parent(), *bounds),
660 magnetism_window_->GetBoundsInScreen(),
661 magnetism_edge_);
662 aura::client::GetScreenPositionClient(window()->GetRootWindow())->
663 ConvertPointFromScreen(window()->parent(), &point);
664 bounds->set_origin(point);
668 void WorkspaceWindowResizer::MagneticallySnapResizeToOtherWindows(
669 gfx::Rect* bounds) {
670 const uint32 edges = WindowComponentToMagneticEdge(details_.window_component);
671 if (UpdateMagnetismWindow(*bounds, edges)) {
672 *bounds = ScreenAsh::ConvertRectFromScreen(
673 window()->parent(),
674 BoundsForMagneticResizeAttach(
675 ScreenAsh::ConvertRectToScreen(window()->parent(), *bounds),
676 magnetism_window_->GetBoundsInScreen(),
677 magnetism_edge_));
681 bool WorkspaceWindowResizer::UpdateMagnetismWindow(const gfx::Rect& bounds,
682 uint32 edges) {
683 // |bounds| are in coordinates of original window's parent.
684 gfx::Rect bounds_in_screen =
685 ScreenAsh::ConvertRectToScreen(window()->parent(), bounds);
686 MagnetismMatcher matcher(bounds_in_screen, edges);
688 // If we snapped to a window then check it first. That way we don't bounce
689 // around when close to multiple edges.
690 if (magnetism_window_) {
691 if (window_tracker_.Contains(magnetism_window_) &&
692 matcher.ShouldAttach(magnetism_window_->GetBoundsInScreen(),
693 &magnetism_edge_)) {
694 return true;
696 window_tracker_.Remove(magnetism_window_);
697 magnetism_window_ = NULL;
700 // Avoid magnetically snapping to popups, menus, tooltips, controls and
701 // windows that are not tracked by workspace.
702 if (!wm::CanResizeWindow(window()) || !GetTrackedByWorkspace(window()))
703 return false;
705 Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
706 for (Shell::RootWindowList::iterator iter = root_windows.begin();
707 iter != root_windows.end(); ++iter) {
708 const aura::RootWindow* root_window = *iter;
709 // Test all children from the desktop in each root window.
710 const aura::Window::Windows& children = Shell::GetContainer(
711 root_window, kShellWindowId_DefaultContainer)->children();
712 for (aura::Window::Windows::const_reverse_iterator i = children.rbegin();
713 i != children.rend() && !matcher.AreEdgesObscured(); ++i) {
714 aura::Window* other = *i;
715 if (other == window() ||
716 !other->IsVisible() ||
717 !wm::IsWindowNormal(other) ||
718 !wm::CanResizeWindow(other)) {
719 continue;
721 if (matcher.ShouldAttach(other->GetBoundsInScreen(), &magnetism_edge_)) {
722 magnetism_window_ = other;
723 window_tracker_.Add(magnetism_window_);
724 return true;
728 return false;
731 void WorkspaceWindowResizer::AdjustBoundsForMainWindow(
732 int sticky_size,
733 gfx::Rect* bounds) {
734 gfx::Point last_mouse_location_in_screen = last_mouse_location_;
735 wm::ConvertPointToScreen(window()->parent(), &last_mouse_location_in_screen);
736 gfx::Display display = Shell::GetScreen()->GetDisplayNearestPoint(
737 last_mouse_location_in_screen);
738 gfx::Rect work_area =
739 ScreenAsh::ConvertRectFromScreen(window()->parent(), display.work_area());
740 if (details_.window_component == HTCAPTION) {
741 // Adjust the bounds to the work area where the mouse cursor is located.
742 // Always keep kMinOnscreenHeight on the bottom.
743 int max_y = work_area.bottom() - kMinOnscreenHeight;
744 if (bounds->y() > max_y) {
745 bounds->set_y(max_y);
746 } else if (bounds->y() <= work_area.y()) {
747 // Don't allow dragging above the top of the display until the mouse
748 // cursor reaches the work area above if any.
749 bounds->set_y(work_area.y());
752 if (sticky_size > 0) {
753 // Possibly stick to edge except when a mouse pointer is outside the
754 // work area.
755 if (!(display.work_area().Contains(last_mouse_location_in_screen) &&
756 StickToWorkAreaOnMove(work_area, sticky_size, bounds))) {
757 MagneticallySnapToOtherWindows(bounds);
760 } else if (sticky_size > 0) {
761 MagneticallySnapResizeToOtherWindows(bounds);
762 if (!magnetism_window_ && sticky_size > 0)
763 StickToWorkAreaOnResize(work_area, sticky_size, bounds);
766 if (attached_windows_.empty())
767 return;
769 if (details_.window_component == HTRIGHT) {
770 bounds->set_width(std::min(bounds->width(),
771 work_area.right() - total_min_ - bounds->x()));
772 } else {
773 DCHECK_EQ(HTBOTTOM, details_.window_component);
774 bounds->set_height(std::min(bounds->height(),
775 work_area.bottom() - total_min_ - bounds->y()));
779 bool WorkspaceWindowResizer::StickToWorkAreaOnMove(
780 const gfx::Rect& work_area,
781 int sticky_size,
782 gfx::Rect* bounds) const {
783 const int left_edge = work_area.x();
784 const int right_edge = work_area.right();
785 const int top_edge = work_area.y();
786 const int bottom_edge = work_area.bottom();
787 if (ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) {
788 bounds->set_x(left_edge);
789 return true;
790 } else if (ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) {
791 bounds->set_x(right_edge - bounds->width());
792 return true;
794 if (ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) {
795 bounds->set_y(top_edge);
796 return true;
797 } else if (ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size) &&
798 bounds->height() < (bottom_edge - top_edge)) {
799 // Only snap to the bottom if the window is smaller than the work area.
800 // Doing otherwise can lead to window snapping in weird ways as it bounces
801 // between snapping to top then bottom.
802 bounds->set_y(bottom_edge - bounds->height());
803 return true;
805 return false;
808 void WorkspaceWindowResizer::StickToWorkAreaOnResize(
809 const gfx::Rect& work_area,
810 int sticky_size,
811 gfx::Rect* bounds) const {
812 const uint32 edges = WindowComponentToMagneticEdge(details_.window_component);
813 const int left_edge = work_area.x();
814 const int right_edge = work_area.right();
815 const int top_edge = work_area.y();
816 const int bottom_edge = work_area.bottom();
817 if (edges & MAGNETISM_EDGE_TOP &&
818 ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) {
819 bounds->set_height(bounds->bottom() - top_edge);
820 bounds->set_y(top_edge);
822 if (edges & MAGNETISM_EDGE_LEFT &&
823 ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) {
824 bounds->set_width(bounds->right() - left_edge);
825 bounds->set_x(left_edge);
827 if (edges & MAGNETISM_EDGE_BOTTOM &&
828 ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size)) {
829 bounds->set_height(bottom_edge - bounds->y());
831 if (edges & MAGNETISM_EDGE_RIGHT &&
832 ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) {
833 bounds->set_width(right_edge - bounds->x());
837 int WorkspaceWindowResizer::PrimaryAxisSize(const gfx::Size& size) const {
838 return PrimaryAxisCoordinate(size.width(), size.height());
841 int WorkspaceWindowResizer::PrimaryAxisCoordinate(int x, int y) const {
842 switch (details_.window_component) {
843 case HTRIGHT:
844 return x;
845 case HTBOTTOM:
846 return y;
847 default:
848 NOTREACHED();
850 return 0;
853 void WorkspaceWindowResizer::UpdateSnapPhantomWindow(const gfx::Point& location,
854 const gfx::Rect& bounds) {
855 if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
856 return;
858 if (!wm::CanSnapWindow(window()))
859 return;
861 if (window()->type() == aura::client::WINDOW_TYPE_PANEL &&
862 window()->GetProperty(kPanelAttachedKey)) {
863 return;
866 SnapType last_type = snap_type_;
867 snap_type_ = GetSnapType(location);
868 if (snap_type_ == SNAP_NONE || snap_type_ != last_type) {
869 snap_phantom_window_controller_.reset();
870 snap_sizer_.reset();
871 if (snap_type_ == SNAP_NONE)
872 return;
874 if (!snap_sizer_) {
875 SnapSizer::Edge edge = (snap_type_ == SNAP_LEFT_EDGE) ?
876 SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE;
877 snap_sizer_.reset(new SnapSizer(window(),
878 location,
879 edge,
880 internal::SnapSizer::OTHER_INPUT));
881 } else {
882 snap_sizer_->Update(location);
884 if (!snap_phantom_window_controller_) {
885 snap_phantom_window_controller_.reset(
886 new PhantomWindowController(window()));
888 snap_phantom_window_controller_->Show(ScreenAsh::ConvertRectToScreen(
889 window()->parent(), snap_sizer_->target_bounds()));
892 void WorkspaceWindowResizer::RestackWindows() {
893 if (attached_windows_.empty())
894 return;
895 // Build a map from index in children to window, returning if there is a
896 // window with a different parent.
897 typedef std::map<size_t, aura::Window*> IndexToWindowMap;
898 IndexToWindowMap map;
899 aura::Window* parent = window()->parent();
900 const aura::Window::Windows& windows(parent->children());
901 map[std::find(windows.begin(), windows.end(), window()) -
902 windows.begin()] = window();
903 for (std::vector<aura::Window*>::const_iterator i =
904 attached_windows_.begin(); i != attached_windows_.end(); ++i) {
905 if ((*i)->parent() != parent)
906 return;
907 size_t index =
908 std::find(windows.begin(), windows.end(), *i) - windows.begin();
909 map[index] = *i;
912 // Reorder the windows starting at the topmost.
913 parent->StackChildAtTop(map.rbegin()->second);
914 for (IndexToWindowMap::const_reverse_iterator i = map.rbegin();
915 i != map.rend(); ) {
916 aura::Window* window = i->second;
917 ++i;
918 if (i != map.rend())
919 parent->StackChildBelow(i->second, window);
923 WorkspaceWindowResizer::SnapType WorkspaceWindowResizer::GetSnapType(
924 const gfx::Point& location) const {
925 // TODO: this likely only wants total display area, not the area of a single
926 // display.
927 gfx::Rect area(ScreenAsh::GetDisplayBoundsInParent(window()));
928 if (location.x() <= area.x())
929 return SNAP_LEFT_EDGE;
930 if (location.x() >= area.right() - 1)
931 return SNAP_RIGHT_EDGE;
932 return SNAP_NONE;
935 } // namespace internal
936 } // namespace ash