1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/wm/dock/docked_window_resizer.h"
6
7 #include "ash/display/display_controller.h"
8 #include "ash/launcher/launcher.h"
9 #include "ash/root_window_controller.h"
10 #include "ash/screen_ash.h"
11 #include "ash/shelf/shelf_types.h"
12 #include "ash/shelf/shelf_widget.h"
13 #include "ash/shell.h"
14 #include "ash/shell_window_ids.h"
15 #include "ash/wm/coordinate_conversion.h"
16 #include "ash/wm/dock/docked_window_layout_manager.h"
17 #include "ash/wm/window_state.h"
18 #include "ash/wm/window_util.h"
19 #include "ash/wm/workspace/magnetism_matcher.h"
20 #include "ash/wm/workspace/workspace_window_resizer.h"
21 #include "base/command_line.h"
22 #include "base/memory/weak_ptr.h"
23 #include "ui/aura/client/aura_constants.h"
24 #include "ui/aura/client/window_tree_client.h"
25 #include "ui/aura/env.h"
26 #include "ui/aura/root_window.h"
27 #include "ui/aura/window.h"
28 #include "ui/aura/window_delegate.h"
29 #include "ui/base/hit_test.h"
30 #include "ui/base/ui_base_types.h"
31 #include "ui/gfx/screen.h"
32 #include "ui/views/widget/widget.h"
33
34 namespace ash {
35 namespace internal {
36
37 namespace {
38
GetDockedLayoutManagerAtPoint(const gfx::Point & point)39 DockedWindowLayoutManager* GetDockedLayoutManagerAtPoint(
40 const gfx::Point& point) {
41 gfx::Display display = ScreenAsh::FindDisplayContainingPoint(point);
42 if (!display.is_valid())
43 return NULL;
44 aura::Window* root = Shell::GetInstance()->display_controller()->
45 GetRootWindowForDisplayId(display.id());
46 aura::Window* dock_container = Shell::GetContainer(
47 root, kShellWindowId_DockedContainer);
48 return static_cast<DockedWindowLayoutManager*>(
49 dock_container->layout_manager());
50 }
51
52 } // namespace
53
~DockedWindowResizer()54 DockedWindowResizer::~DockedWindowResizer() {
55 }
56
57 // static
58 DockedWindowResizer*
Create(WindowResizer * next_window_resizer,aura::Window * window,const gfx::Point & location,int window_component,aura::client::WindowMoveSource source)59 DockedWindowResizer::Create(WindowResizer* next_window_resizer,
60 aura::Window* window,
61 const gfx::Point& location,
62 int window_component,
63 aura::client::WindowMoveSource source) {
64 Details details(window, location, window_component, source);
65 return details.is_resizable ?
66 new DockedWindowResizer(next_window_resizer, details) : NULL;
67 }
68
Drag(const gfx::Point & location,int event_flags)69 void DockedWindowResizer::Drag(const gfx::Point& location, int event_flags) {
70 last_location_ = location;
71 wm::ConvertPointToScreen(GetTarget()->parent(), &last_location_);
72 if (!did_move_or_resize_) {
73 did_move_or_resize_ = true;
74 StartedDragging();
75 }
76 gfx::Point offset;
77 gfx::Rect bounds(CalculateBoundsForDrag(details_, location));
78 MaybeSnapToEdge(bounds, &offset);
79 gfx::Point modified_location(location);
80 modified_location.Offset(offset.x(), offset.y());
81
82 base::WeakPtr<DockedWindowResizer> resizer(weak_ptr_factory_.GetWeakPtr());
83 next_window_resizer_->Drag(modified_location, event_flags);
84 if (!resizer)
85 return;
86
87 DockedWindowLayoutManager* new_dock_layout =
88 GetDockedLayoutManagerAtPoint(last_location_);
89 if (new_dock_layout && new_dock_layout != dock_layout_) {
90 // The window is being dragged to a new display. If the previous
91 // container is the current parent of the window it will be informed of
92 // the end of drag when the window is reparented, otherwise let the
93 // previous container know the drag is complete. If we told the
94 // window's parent that the drag was complete it would begin
95 // positioning the window.
96 if (is_docked_ && dock_layout_->is_dragged_window_docked())
97 dock_layout_->UndockDraggedWindow();
98 if (dock_layout_ != initial_dock_layout_)
99 dock_layout_->FinishDragging(
100 DOCKED_ACTION_NONE,
101 details_.source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
102 DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
103 is_docked_ = false;
104 dock_layout_ = new_dock_layout;
105 // The window's initial layout manager already knows that the drag is
106 // in progress for this window.
107 if (new_dock_layout != initial_dock_layout_)
108 new_dock_layout->StartDragging(GetTarget());
109 }
110 // Window could get docked by the WorkspaceWindowResizer, update the state.
111 is_docked_ = dock_layout_->is_dragged_window_docked();
112 // Whenever a window is dragged out of the dock it will be auto-sized
113 // in the dock if it gets docked again.
114 if (!is_docked_)
115 was_bounds_changed_by_user_ = false;
116 }
117
CompleteDrag(int event_flags)118 void DockedWindowResizer::CompleteDrag(int event_flags) {
119 // The root window can change when dragging into a different screen.
120 next_window_resizer_->CompleteDrag(event_flags);
121 FinishedDragging();
122 }
123
RevertDrag()124 void DockedWindowResizer::RevertDrag() {
125 next_window_resizer_->RevertDrag();
126 // Restore docked state to what it was before the drag if necessary.
127 if (is_docked_ != was_docked_) {
128 is_docked_ = was_docked_;
129 if (is_docked_)
130 dock_layout_->DockDraggedWindow(GetTarget());
131 else
132 dock_layout_->UndockDraggedWindow();
133 }
134 FinishedDragging();
135 }
136
GetTarget()137 aura::Window* DockedWindowResizer::GetTarget() {
138 return next_window_resizer_->GetTarget();
139 }
140
GetInitialLocation() const141 const gfx::Point& DockedWindowResizer::GetInitialLocation() const {
142 return details_.initial_location_in_parent;
143 }
144
DockedWindowResizer(WindowResizer * next_window_resizer,const Details & details)145 DockedWindowResizer::DockedWindowResizer(WindowResizer* next_window_resizer,
146 const Details& details)
147 : details_(details),
148 next_window_resizer_(next_window_resizer),
149 dock_layout_(NULL),
150 initial_dock_layout_(NULL),
151 did_move_or_resize_(false),
152 was_docked_(false),
153 is_docked_(false),
154 was_bounds_changed_by_user_(
155 wm::GetWindowState(details.window)->bounds_changed_by_user()),
156 weak_ptr_factory_(this) {
157 DCHECK(details_.is_resizable);
158 aura::Window* dock_container = Shell::GetContainer(
159 details.window->GetRootWindow(),
160 kShellWindowId_DockedContainer);
161 dock_layout_ = static_cast<DockedWindowLayoutManager*>(
162 dock_container->layout_manager());
163 initial_dock_layout_ = dock_layout_;
164 was_docked_ = details.window->parent() == dock_container;
165 is_docked_ = was_docked_;
166 }
167
MaybeSnapToEdge(const gfx::Rect & bounds,gfx::Point * offset)168 void DockedWindowResizer::MaybeSnapToEdge(const gfx::Rect& bounds,
169 gfx::Point* offset) {
170 // Windows only snap magnetically when they were previously docked.
171 if (!was_docked_)
172 return;
173 DockedAlignment dock_alignment = dock_layout_->CalculateAlignment();
174 gfx::Rect dock_bounds = ScreenAsh::ConvertRectFromScreen(
175 GetTarget()->parent(),
176 dock_layout_->dock_container()->GetBoundsInScreen());
177
178 // Short-range magnetism when retaining docked state. Same constant as in
179 // MagnetismMatcher is used for consistency.
180 const int kSnapToDockDistance = MagnetismMatcher::kMagneticDistance;
181
182 if (dock_alignment == DOCKED_ALIGNMENT_LEFT ||
183 dock_alignment == DOCKED_ALIGNMENT_NONE) {
184 const int distance = bounds.x() - dock_bounds.x();
185 if (distance < kSnapToDockDistance && distance > 0) {
186 offset->set_x(-distance);
187 return;
188 }
189 }
190 if (dock_alignment == DOCKED_ALIGNMENT_RIGHT ||
191 dock_alignment == DOCKED_ALIGNMENT_NONE) {
192 const int distance = dock_bounds.right() - bounds.right();
193 if (distance < kSnapToDockDistance && distance > 0)
194 offset->set_x(distance);
195 }
196 }
197
StartedDragging()198 void DockedWindowResizer::StartedDragging() {
199 // During resizing the window width is preserved by DockedwindowLayoutManager.
200 wm::WindowState* window_state = wm::GetWindowState(GetTarget());
201 if (is_docked_ &&
202 (details_.bounds_change & WindowResizer::kBoundsChange_Resizes)) {
203 window_state->set_bounds_changed_by_user(true);
204 }
205
206 // Tell the dock layout manager that we are dragging this window.
207 // At this point we are not yet animating the window as it may not be
208 // inside the docked area.
209 dock_layout_->StartDragging(GetTarget());
210 // Reparent workspace windows during the drag to elevate them above workspace.
211 // Other windows for which the DockedWindowResizer is instantiated include
212 // panels and windows that are already docked. Those do not need reparenting.
213 if (GetTarget()->type() != aura::client::WINDOW_TYPE_PANEL &&
214 GetTarget()->parent()->id() == kShellWindowId_DefaultContainer) {
215 // The window is going to be reparented - avoid completing the drag.
216 window_state->set_continue_drag_after_reparent(true);
217
218 // Reparent the window into the docked windows container in order to get it
219 // on top of other docked windows.
220 aura::Window* docked_container = Shell::GetContainer(
221 GetTarget()->GetRootWindow(),
222 kShellWindowId_DockedContainer);
223 wm::ReparentChildWithTransientChildren(GetTarget(),
224 GetTarget()->parent(),
225 docked_container);
226 }
227 if (is_docked_)
228 dock_layout_->DockDraggedWindow(GetTarget());
229 }
230
FinishedDragging()231 void DockedWindowResizer::FinishedDragging() {
232 if (!did_move_or_resize_)
233 return;
234 did_move_or_resize_ = false;
235 aura::Window* window = GetTarget();
236 wm::WindowState* window_state = wm::GetWindowState(window);
237 const bool is_attached_panel =
238 window->type() == aura::client::WINDOW_TYPE_PANEL &&
239 window_state->panel_attached();
240 const bool is_resized =
241 (details_.bounds_change & WindowResizer::kBoundsChange_Resizes) != 0;
242
243 // When drag is completed the dragged docked window is resized to the bounds
244 // calculated by the layout manager that conform to other docked windows.
245 if (!is_attached_panel && is_docked_ && !is_resized) {
246 gfx::Rect bounds = ScreenAsh::ConvertRectFromScreen(
247 window->parent(), dock_layout_->dragged_bounds());
248 if (!bounds.IsEmpty() && bounds.width() != window->bounds().width()) {
249 window->SetBounds(bounds);
250 }
251 }
252 // If a window has restore bounds, update the restore origin and width but not
253 // the height (since the height is auto-calculated for the docked windows).
254 if (is_resized && is_docked_ && window_state->HasRestoreBounds()) {
255 gfx::Rect restore_bounds = window->GetBoundsInScreen();
256 restore_bounds.set_height(
257 window_state->GetRestoreBoundsInScreen().height());
258 window_state->SetRestoreBoundsInScreen(restore_bounds);
259 }
260
261 // Check if the window needs to be docked or returned to workspace.
262 DockedAction action = MaybeReparentWindowOnDragCompletion(is_resized,
263 is_attached_panel);
264 dock_layout_->FinishDragging(
265 action,
266 details_.source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
267 DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
268
269 // If we started the drag in one root window and moved into another root
270 // but then canceled the drag we may need to inform the original layout
271 // manager that the drag is finished.
272 if (initial_dock_layout_ != dock_layout_)
273 initial_dock_layout_->FinishDragging(
274 DOCKED_ACTION_NONE,
275 details_.source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
276 DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
277 is_docked_ = false;
278 }
279
MaybeReparentWindowOnDragCompletion(bool is_resized,bool is_attached_panel)280 DockedAction DockedWindowResizer::MaybeReparentWindowOnDragCompletion(
281 bool is_resized, bool is_attached_panel) {
282 aura::Window* window = GetTarget();
283
284 // Check if the window needs to be docked or returned to workspace.
285 DockedAction action = DOCKED_ACTION_NONE;
286 aura::Window* dock_container = Shell::GetContainer(
287 window->GetRootWindow(),
288 kShellWindowId_DockedContainer);
289 if ((is_resized || !is_attached_panel) &&
290 is_docked_ != (window->parent() == dock_container)) {
291 if (is_docked_) {
292 wm::ReparentChildWithTransientChildren(window,
293 window->parent(),
294 dock_container);
295 action = DOCKED_ACTION_DOCK;
296 } else if (window->parent()->id() == kShellWindowId_DockedContainer) {
297 // Reparent the window back to workspace.
298 // We need to be careful to give ParentWindowWithContext a location in
299 // the right root window (matching the logic in DragWindowResizer) based
300 // on which root window a mouse pointer is in. We want to undock into the
301 // right screen near the edge of a multiscreen setup (based on where the
302 // mouse is).
303 gfx::Rect near_last_location(last_location_, gfx::Size());
304 // Reparenting will cause Relayout and possible dock shrinking.
305 aura::Window* previous_parent = window->parent();
306 aura::client::ParentWindowWithContext(window, window, near_last_location);
307 if (window->parent() != previous_parent) {
308 wm::ReparentTransientChildrenOfChild(window,
309 previous_parent,
310 window->parent());
311 }
312 action = was_docked_ ? DOCKED_ACTION_UNDOCK : DOCKED_ACTION_NONE;
313 }
314 } else {
315 // Docked state was not changed but still need to record a UMA action.
316 if (is_resized && is_docked_ && was_docked_)
317 action = DOCKED_ACTION_RESIZE;
318 else if (is_docked_ && was_docked_)
319 action = DOCKED_ACTION_REORDER;
320 else if (is_docked_ && !was_docked_)
321 action = DOCKED_ACTION_DOCK;
322 else
323 action = DOCKED_ACTION_NONE;
324 }
325 // When a window is newly docked it is auto-sized by docked layout adjusting
326 // to other windows. If it is just dragged (but not resized) while being
327 // docked it is auto-sized unless it has been resized while being docked
328 // before.
329 if (is_docked_) {
330 wm::GetWindowState(window)->set_bounds_changed_by_user(
331 was_docked_ && (is_resized || was_bounds_changed_by_user_));
332 }
333 return action;
334 }
335
336 } // namespace internal
337 } // namespace ash
338