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"
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"
41 scoped_ptr
<WindowResizer
> CreateWindowResizer(
43 const gfx::Point
& point_in_parent
,
45 aura::client::WindowMoveSource source
) {
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(
77 std::vector
<aura::Window
*>());
78 } else if (wm::IsWindowNormal(window
)) {
79 window_resizer
= DefaultWindowResizer::Create(
80 window
, point_in_parent
, window_component
, source
);
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
);
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
,
129 case SECONDARY_MAGNETISM_EDGE_LEADING
:
131 case SECONDARY_MAGNETISM_EDGE_TRAILING
:
133 case SECONDARY_MAGNETISM_EDGE_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
) {
146 switch (edge
.primary_edge
) {
147 case MAGNETISM_EDGE_TOP
:
148 y
= attach_to
.bottom();
150 case MAGNETISM_EDGE_LEFT
:
151 x
= attach_to
.right();
153 case MAGNETISM_EDGE_BOTTOM
:
154 y
= attach_to
.y() - src
.height();
156 case MAGNETISM_EDGE_RIGHT
:
157 x
= attach_to
.x() - src
.width();
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(),
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(),
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
) {
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();
193 case MAGNETISM_EDGE_RIGHT
:
194 w
+= attach_origin
.x() - src
.x();
196 case MAGNETISM_EDGE_TOP
:
197 y
= attach_origin
.y();
198 h
= src
.bottom() - y
;
200 case MAGNETISM_EDGE_BOTTOM
:
201 h
+= attach_origin
.y() - src
.y();
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();
214 case MAGNETISM_EDGE_TOP
:
215 case MAGNETISM_EDGE_BOTTOM
:
216 if (edge
.secondary_edge
== SECONDARY_MAGNETISM_EDGE_LEADING
) {
217 x
= attach_origin
.x();
219 } else if (edge
.secondary_edge
== SECONDARY_MAGNETISM_EDGE_TRAILING
) {
220 w
+= attach_origin
.x() - src
.x();
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
) {
231 return MAGNETISM_EDGE_LEFT
| MAGNETISM_EDGE_TOP
;
233 return MAGNETISM_EDGE_TOP
| MAGNETISM_EDGE_RIGHT
;
235 return MAGNETISM_EDGE_LEFT
| MAGNETISM_EDGE_BOTTOM
;
237 return MAGNETISM_EDGE_RIGHT
| MAGNETISM_EDGE_BOTTOM
;
239 return MAGNETISM_EDGE_TOP
;
241 return MAGNETISM_EDGE_BOTTOM
;
243 return MAGNETISM_EDGE_RIGHT
;
245 return MAGNETISM_EDGE_LEFT
;
255 const int WorkspaceWindowResizer::kMinOnscreenSize
= 20;
258 const int WorkspaceWindowResizer::kMinOnscreenHeight
= 32;
261 const int WorkspaceWindowResizer::kScreenEdgeInset
= 8;
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.
270 WindowSize(int size
, int min
, int max
)
274 // Grow the min/max bounds to include the starting size.
275 if (is_underflowing())
277 if (is_overflowing())
281 bool is_at_capacity(bool shrinking
) {
282 return size_
== (shrinking
? min_
: max_
);
289 bool has_min() const {
293 bool has_max() const {
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
) {
313 int new_value
= size_
+ amount
;
315 if (has_min() && new_value
< min_
) {
317 return new_value
- min_
;
320 if (has_max() && new_value
> max_
) {
322 return new_value
- max_
;
335 WorkspaceWindowResizer::~WorkspaceWindowResizer() {
336 Shell
* shell
= Shell::GetInstance();
337 shell
->cursor_manager()->UnlockCursor();
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
,
354 last_mouse_location_
= location_in_parent
;
357 if (event_flags
& ui::EF_CONTROL_DOWN
) {
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
;
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());
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
);
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
)
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() ?
423 details_
.restore_bounds
);
425 window()->SetBounds(snap_sizer_
->target_bounds());
430 void WorkspaceWindowResizer::RevertDrag() {
431 snap_phantom_window_controller_
.reset();
433 if (!did_move_or_resize_
)
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();
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
)
473 attached_windows_(attached_windows
),
474 did_move_or_resize_(false),
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();
517 void WorkspaceWindowResizer::LayoutAttachedWindows(
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
);
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
]);
553 attached_bounds
.set_y(last
);
554 attached_bounds
.set_height(sizes
[i
]);
556 attached_windows_
[i
]->SetBounds(attached_bounds
);
561 int WorkspaceWindowResizer::CalculateAttachedSizes(
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;
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_
;
576 // If we're shrinking, we grow the attached so the total size remains
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
;
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(
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());
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(),
662 aura::client::GetScreenPositionClient(window()->GetRootWindow())->
663 ConvertPointFromScreen(window()->parent(), &point
);
664 bounds
->set_origin(point
);
668 void WorkspaceWindowResizer::MagneticallySnapResizeToOtherWindows(
670 const uint32 edges
= WindowComponentToMagneticEdge(details_
.window_component
);
671 if (UpdateMagnetismWindow(*bounds
, edges
)) {
672 *bounds
= ScreenAsh::ConvertRectFromScreen(
674 BoundsForMagneticResizeAttach(
675 ScreenAsh::ConvertRectToScreen(window()->parent(), *bounds
),
676 magnetism_window_
->GetBoundsInScreen(),
681 bool WorkspaceWindowResizer::UpdateMagnetismWindow(const gfx::Rect
& bounds
,
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(),
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()))
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
)) {
721 if (matcher
.ShouldAttach(other
->GetBoundsInScreen(), &magnetism_edge_
)) {
722 magnetism_window_
= other
;
723 window_tracker_
.Add(magnetism_window_
);
731 void WorkspaceWindowResizer::AdjustBoundsForMainWindow(
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
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())
769 if (details_
.window_component
== HTRIGHT
) {
770 bounds
->set_width(std::min(bounds
->width(),
771 work_area
.right() - total_min_
- bounds
->x()));
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
,
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
);
790 } else if (ShouldStickToEdge(right_edge
- bounds
->right(), sticky_size
)) {
791 bounds
->set_x(right_edge
- bounds
->width());
794 if (ShouldStickToEdge(bounds
->y() - top_edge
, sticky_size
)) {
795 bounds
->set_y(top_edge
);
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());
808 void WorkspaceWindowResizer::StickToWorkAreaOnResize(
809 const gfx::Rect
& work_area
,
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
) {
853 void WorkspaceWindowResizer::UpdateSnapPhantomWindow(const gfx::Point
& location
,
854 const gfx::Rect
& bounds
) {
855 if (!did_move_or_resize_
|| details_
.window_component
!= HTCAPTION
)
858 if (!wm::CanSnapWindow(window()))
861 if (window()->type() == aura::client::WINDOW_TYPE_PANEL
&&
862 window()->GetProperty(kPanelAttachedKey
)) {
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();
871 if (snap_type_
== SNAP_NONE
)
875 SnapSizer::Edge edge
= (snap_type_
== SNAP_LEFT_EDGE
) ?
876 SnapSizer::LEFT_EDGE
: SnapSizer::RIGHT_EDGE
;
877 snap_sizer_
.reset(new SnapSizer(window(),
880 internal::SnapSizer::OTHER_INPUT
));
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())
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
)
908 std::find(windows
.begin(), windows
.end(), *i
) - windows
.begin();
912 // Reorder the windows starting at the topmost.
913 parent
->StackChildAtTop(map
.rbegin()->second
);
914 for (IndexToWindowMap::const_reverse_iterator i
= map
.rbegin();
916 aura::Window
* window
= i
->second
;
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
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
;
935 } // namespace internal