OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "ash/common/wm/workspace/workspace_window_resizer.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cmath> | |
9 #include <utility> | |
10 #include <vector> | |
11 | |
12 #include "ash/common/ash_switches.h" | |
13 #include "ash/common/metrics/user_metrics_action.h" | |
14 #include "ash/common/wm/default_window_resizer.h" | |
15 #include "ash/common/wm/dock/docked_window_layout_manager.h" | |
16 #include "ash/common/wm/dock/docked_window_resizer.h" | |
17 #include "ash/common/wm/panels/panel_window_resizer.h" | |
18 #include "ash/common/wm/window_positioning_utils.h" | |
19 #include "ash/common/wm/window_state.h" | |
20 #include "ash/common/wm/wm_event.h" | |
21 #include "ash/common/wm/wm_screen_util.h" | |
22 #include "ash/common/wm/workspace/phantom_window_controller.h" | |
23 #include "ash/common/wm/workspace/two_step_edge_cycler.h" | |
24 #include "ash/common/wm_shell.h" | |
25 #include "ash/common/wm_window.h" | |
26 #include "ash/public/cpp/shell_window_ids.h" | |
27 #include "ash/root_window_controller.h" | |
28 #include "ash/shell.h" | |
29 #include "base/memory/ptr_util.h" | |
30 #include "base/memory/weak_ptr.h" | |
31 #include "ui/base/hit_test.h" | |
32 #include "ui/compositor/layer.h" | |
33 #include "ui/display/display.h" | |
34 #include "ui/display/screen.h" | |
35 #include "ui/gfx/transform.h" | |
36 #include "ui/wm/public/window_types.h" | |
37 | |
38 namespace ash { | |
39 | |
40 std::unique_ptr<WindowResizer> CreateWindowResizer( | |
41 WmWindow* window, | |
42 const gfx::Point& point_in_parent, | |
43 int window_component, | |
44 aura::client::WindowMoveSource source) { | |
45 DCHECK(window); | |
46 wm::WindowState* window_state = window->GetWindowState(); | |
47 // No need to return a resizer when the window cannot get resized or when a | |
48 // resizer already exists for this window. | |
49 if ((!window_state->CanResize() && window_component != HTCAPTION) || | |
50 window_state->drag_details()) { | |
51 return nullptr; | |
52 } | |
53 | |
54 if (window_component == HTCAPTION && !window_state->can_be_dragged()) | |
55 return nullptr; | |
56 | |
57 // TODO(varkha): The chaining of window resizers causes some of the logic | |
58 // to be repeated and the logic flow difficult to control. With some windows | |
59 // classes using reparenting during drag operations it becomes challenging to | |
60 // implement proper transition from one resizer to another during or at the | |
61 // end of the drag. This also causes http://crbug.com/247085. | |
62 // It seems the only thing the panel or dock resizer needs to do is notify the | |
63 // layout manager when a docked window is being dragged. We should have a | |
64 // better way of doing this, perhaps by having a way of observing drags or | |
65 // having a generic drag window wrapper which informs a layout manager that a | |
66 // drag has started or stopped. | |
67 // It may be possible to refactor and eliminate chaining. | |
68 std::unique_ptr<WindowResizer> window_resizer; | |
69 | |
70 if (!window_state->IsNormalOrSnapped() && !window_state->IsDocked()) | |
71 return std::unique_ptr<WindowResizer>(); | |
72 | |
73 int bounds_change = | |
74 WindowResizer::GetBoundsChangeForWindowComponent(window_component); | |
75 if (bounds_change == WindowResizer::kBoundsChangeDirection_None) | |
76 return std::unique_ptr<WindowResizer>(); | |
77 | |
78 window_state->CreateDragDetails(point_in_parent, window_component, source); | |
79 const int parent_shell_window_id = | |
80 window->GetParent() ? window->GetParent()->GetShellWindowId() : -1; | |
81 if (window->GetParent() && | |
82 (parent_shell_window_id == kShellWindowId_DefaultContainer || | |
83 parent_shell_window_id == kShellWindowId_DockedContainer || | |
84 parent_shell_window_id == kShellWindowId_PanelContainer)) { | |
85 window_resizer.reset( | |
86 WorkspaceWindowResizer::Create(window_state, std::vector<WmWindow*>())); | |
87 } else { | |
88 window_resizer.reset(DefaultWindowResizer::Create(window_state)); | |
89 } | |
90 window_resizer = window->GetShell()->CreateDragWindowResizer( | |
91 std::move(window_resizer), window_state); | |
92 if (window->GetType() == ui::wm::WINDOW_TYPE_PANEL) | |
93 window_resizer.reset( | |
94 PanelWindowResizer::Create(window_resizer.release(), window_state)); | |
95 if (window_resizer && window->GetParent() && !window->GetTransientParent() && | |
96 (parent_shell_window_id == kShellWindowId_DefaultContainer || | |
97 parent_shell_window_id == kShellWindowId_DockedContainer || | |
98 parent_shell_window_id == kShellWindowId_PanelContainer)) { | |
99 window_resizer.reset( | |
100 DockedWindowResizer::Create(window_resizer.release(), window_state)); | |
101 } | |
102 return window_resizer; | |
103 } | |
104 | |
105 namespace { | |
106 | |
107 // Snapping distance used instead of WorkspaceWindowResizer::kScreenEdgeInset | |
108 // when resizing a window using touchscreen. | |
109 const int kScreenEdgeInsetForTouchDrag = 32; | |
110 | |
111 // Current instance for use by the WorkspaceWindowResizerTest. | |
112 WorkspaceWindowResizer* instance = NULL; | |
113 | |
114 // Returns true if the window should stick to the edge. | |
115 bool ShouldStickToEdge(int distance_from_edge, int sticky_size) { | |
116 return distance_from_edge < sticky_size && | |
117 distance_from_edge > -sticky_size * 2; | |
118 } | |
119 | |
120 // Returns the coordinate along the secondary axis to snap to. | |
121 int CoordinateAlongSecondaryAxis(SecondaryMagnetismEdge edge, | |
122 int leading, | |
123 int trailing, | |
124 int none) { | |
125 switch (edge) { | |
126 case SECONDARY_MAGNETISM_EDGE_LEADING: | |
127 return leading; | |
128 case SECONDARY_MAGNETISM_EDGE_TRAILING: | |
129 return trailing; | |
130 case SECONDARY_MAGNETISM_EDGE_NONE: | |
131 return none; | |
132 } | |
133 NOTREACHED(); | |
134 return none; | |
135 } | |
136 | |
137 // Returns the origin for |src| when magnetically attaching to |attach_to| along | |
138 // the edges |edges|. |edges| is a bitmask of the MagnetismEdges. | |
139 gfx::Point OriginForMagneticAttach(const gfx::Rect& src, | |
140 const gfx::Rect& attach_to, | |
141 const MatchedEdge& edge) { | |
142 int x = 0, y = 0; | |
143 switch (edge.primary_edge) { | |
144 case MAGNETISM_EDGE_TOP: | |
145 y = attach_to.bottom(); | |
146 break; | |
147 case MAGNETISM_EDGE_LEFT: | |
148 x = attach_to.right(); | |
149 break; | |
150 case MAGNETISM_EDGE_BOTTOM: | |
151 y = attach_to.y() - src.height(); | |
152 break; | |
153 case MAGNETISM_EDGE_RIGHT: | |
154 x = attach_to.x() - src.width(); | |
155 break; | |
156 } | |
157 switch (edge.primary_edge) { | |
158 case MAGNETISM_EDGE_TOP: | |
159 case MAGNETISM_EDGE_BOTTOM: | |
160 x = CoordinateAlongSecondaryAxis(edge.secondary_edge, attach_to.x(), | |
161 attach_to.right() - src.width(), | |
162 src.x()); | |
163 break; | |
164 case MAGNETISM_EDGE_LEFT: | |
165 case MAGNETISM_EDGE_RIGHT: | |
166 y = CoordinateAlongSecondaryAxis(edge.secondary_edge, attach_to.y(), | |
167 attach_to.bottom() - src.height(), | |
168 src.y()); | |
169 break; | |
170 } | |
171 return gfx::Point(x, y); | |
172 } | |
173 | |
174 // Returns the bounds for a magnetic attach when resizing. |src| is the bounds | |
175 // of window being resized, |attach_to| the bounds of the window to attach to | |
176 // and |edge| identifies the edge to attach to. | |
177 gfx::Rect BoundsForMagneticResizeAttach(const gfx::Rect& src, | |
178 const gfx::Rect& attach_to, | |
179 const MatchedEdge& edge) { | |
180 int x = src.x(); | |
181 int y = src.y(); | |
182 int w = src.width(); | |
183 int h = src.height(); | |
184 gfx::Point attach_origin(OriginForMagneticAttach(src, attach_to, edge)); | |
185 switch (edge.primary_edge) { | |
186 case MAGNETISM_EDGE_LEFT: | |
187 x = attach_origin.x(); | |
188 w = src.right() - x; | |
189 break; | |
190 case MAGNETISM_EDGE_RIGHT: | |
191 w += attach_origin.x() - src.x(); | |
192 break; | |
193 case MAGNETISM_EDGE_TOP: | |
194 y = attach_origin.y(); | |
195 h = src.bottom() - y; | |
196 break; | |
197 case MAGNETISM_EDGE_BOTTOM: | |
198 h += attach_origin.y() - src.y(); | |
199 break; | |
200 } | |
201 switch (edge.primary_edge) { | |
202 case MAGNETISM_EDGE_LEFT: | |
203 case MAGNETISM_EDGE_RIGHT: | |
204 if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) { | |
205 y = attach_origin.y(); | |
206 h = src.bottom() - y; | |
207 } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) { | |
208 h += attach_origin.y() - src.y(); | |
209 } | |
210 break; | |
211 case MAGNETISM_EDGE_TOP: | |
212 case MAGNETISM_EDGE_BOTTOM: | |
213 if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) { | |
214 x = attach_origin.x(); | |
215 w = src.right() - x; | |
216 } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) { | |
217 w += attach_origin.x() - src.x(); | |
218 } | |
219 break; | |
220 } | |
221 return gfx::Rect(x, y, w, h); | |
222 } | |
223 | |
224 // Converts a window component edge to the magnetic edge to snap to. | |
225 uint32_t WindowComponentToMagneticEdge(int window_component) { | |
226 switch (window_component) { | |
227 case HTTOPLEFT: | |
228 return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_TOP; | |
229 case HTTOPRIGHT: | |
230 return MAGNETISM_EDGE_TOP | MAGNETISM_EDGE_RIGHT; | |
231 case HTBOTTOMLEFT: | |
232 return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_BOTTOM; | |
233 case HTBOTTOMRIGHT: | |
234 return MAGNETISM_EDGE_RIGHT | MAGNETISM_EDGE_BOTTOM; | |
235 case HTTOP: | |
236 return MAGNETISM_EDGE_TOP; | |
237 case HTBOTTOM: | |
238 return MAGNETISM_EDGE_BOTTOM; | |
239 case HTRIGHT: | |
240 return MAGNETISM_EDGE_RIGHT; | |
241 case HTLEFT: | |
242 return MAGNETISM_EDGE_LEFT; | |
243 default: | |
244 break; | |
245 } | |
246 return 0; | |
247 } | |
248 | |
249 } // namespace | |
250 | |
251 // static | |
252 const int WorkspaceWindowResizer::kMinOnscreenSize = 20; | |
253 | |
254 // static | |
255 const int WorkspaceWindowResizer::kMinOnscreenHeight = 32; | |
256 | |
257 // static | |
258 const int WorkspaceWindowResizer::kScreenEdgeInset = 8; | |
259 | |
260 WorkspaceWindowResizer* WorkspaceWindowResizer::GetInstanceForTest() { | |
261 return instance; | |
262 } | |
263 | |
264 // Represents the width or height of a window with constraints on its minimum | |
265 // and maximum size. 0 represents a lack of a constraint. | |
266 class WindowSize { | |
267 public: | |
268 WindowSize(int size, int min, int max) : size_(size), min_(min), max_(max) { | |
269 // Grow the min/max bounds to include the starting size. | |
270 if (is_underflowing()) | |
271 min_ = size_; | |
272 if (is_overflowing()) | |
273 max_ = size_; | |
274 } | |
275 | |
276 bool is_at_capacity(bool shrinking) const { | |
277 return size_ == (shrinking ? min_ : max_); | |
278 } | |
279 | |
280 int size() const { return size_; } | |
281 | |
282 bool has_min() const { return min_ != 0; } | |
283 | |
284 bool has_max() const { return max_ != 0; } | |
285 | |
286 bool is_valid() const { return !is_overflowing() && !is_underflowing(); } | |
287 | |
288 bool is_overflowing() const { return has_max() && size_ > max_; } | |
289 | |
290 bool is_underflowing() const { return has_min() && size_ < min_; } | |
291 | |
292 // Add |amount| to this WindowSize not exceeding min or max size constraints. | |
293 // Returns by how much |size_| + |amount| exceeds the min/max constraints. | |
294 int Add(int amount) { | |
295 DCHECK(is_valid()); | |
296 int new_value = size_ + amount; | |
297 | |
298 if (has_min() && new_value < min_) { | |
299 size_ = min_; | |
300 return new_value - min_; | |
301 } | |
302 | |
303 if (has_max() && new_value > max_) { | |
304 size_ = max_; | |
305 return new_value - max_; | |
306 } | |
307 | |
308 size_ = new_value; | |
309 return 0; | |
310 } | |
311 | |
312 private: | |
313 int size_; | |
314 int min_; | |
315 int max_; | |
316 }; | |
317 | |
318 WorkspaceWindowResizer::~WorkspaceWindowResizer() { | |
319 if (did_lock_cursor_) | |
320 shell_->UnlockCursor(); | |
321 | |
322 if (instance == this) | |
323 instance = NULL; | |
324 } | |
325 | |
326 // static | |
327 WorkspaceWindowResizer* WorkspaceWindowResizer::Create( | |
328 wm::WindowState* window_state, | |
329 const std::vector<WmWindow*>& attached_windows) { | |
330 return new WorkspaceWindowResizer(window_state, attached_windows); | |
331 } | |
332 | |
333 void WorkspaceWindowResizer::Drag(const gfx::Point& location_in_parent, | |
334 int event_flags) { | |
335 last_mouse_location_ = location_in_parent; | |
336 | |
337 int sticky_size; | |
338 if (event_flags & ui::EF_CONTROL_DOWN) { | |
339 sticky_size = 0; | |
340 } else if ((details().bounds_change & kBoundsChange_Resizes) && | |
341 details().source == aura::client::WINDOW_MOVE_SOURCE_TOUCH) { | |
342 sticky_size = kScreenEdgeInsetForTouchDrag; | |
343 } else { | |
344 sticky_size = kScreenEdgeInset; | |
345 } | |
346 // |bounds| is in |GetTarget()->parent()|'s coordinates. | |
347 gfx::Rect bounds = CalculateBoundsForDrag(location_in_parent); | |
348 AdjustBoundsForMainWindow(sticky_size, &bounds); | |
349 | |
350 if (bounds != GetTarget()->GetBounds()) { | |
351 if (!did_move_or_resize_) { | |
352 if (!details().restore_bounds.IsEmpty()) | |
353 window_state()->ClearRestoreBounds(); | |
354 RestackWindows(); | |
355 } | |
356 did_move_or_resize_ = true; | |
357 } | |
358 | |
359 gfx::Point location_in_screen = | |
360 GetTarget()->GetParent()->ConvertPointToScreen(location_in_parent); | |
361 | |
362 WmWindow* root = nullptr; | |
363 display::Display display = | |
364 display::Screen::GetScreen()->GetDisplayNearestPoint(location_in_screen); | |
365 // Track the last screen that the pointer was on to keep the snap phantom | |
366 // window there. | |
367 if (display.bounds().Contains(location_in_screen)) { | |
368 root = | |
369 Shell::GetRootWindowControllerWithDisplayId(display.id())->GetWindow(); | |
370 } | |
371 if (!attached_windows_.empty()) | |
372 LayoutAttachedWindows(&bounds); | |
373 if (bounds != GetTarget()->GetBounds()) { | |
374 // SetBounds needs to be called to update the layout which affects where the | |
375 // phantom window is drawn. Keep track if the window was destroyed during | |
376 // the drag and quit early if so. | |
377 base::WeakPtr<WorkspaceWindowResizer> resizer( | |
378 weak_ptr_factory_.GetWeakPtr()); | |
379 GetTarget()->SetBounds(bounds); | |
380 if (!resizer) | |
381 return; | |
382 } | |
383 const bool in_original_root = !root || root == GetTarget()->GetRootWindow(); | |
384 // Hide a phantom window for snapping if the cursor is in another root window. | |
385 if (in_original_root) { | |
386 UpdateSnapPhantomWindow(location_in_parent, bounds); | |
387 } else { | |
388 snap_type_ = SNAP_NONE; | |
389 snap_phantom_window_controller_.reset(); | |
390 edge_cycler_.reset(); | |
391 SetDraggedWindowDocked(false); | |
392 } | |
393 } | |
394 | |
395 void WorkspaceWindowResizer::CompleteDrag() { | |
396 if (!did_move_or_resize_) | |
397 return; | |
398 | |
399 window_state()->set_bounds_changed_by_user(true); | |
400 snap_phantom_window_controller_.reset(); | |
401 | |
402 // If the window's state type changed over the course of the drag do not snap | |
403 // the window. This happens when the user minimizes or maximizes the window | |
404 // using a keyboard shortcut while dragging it. | |
405 if (window_state()->GetStateType() != details().initial_state_type) | |
406 return; | |
407 | |
408 bool snapped = false; | |
409 if (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT) { | |
410 if (!window_state()->HasRestoreBounds()) { | |
411 gfx::Rect initial_bounds = GetTarget()->GetParent()->ConvertRectToScreen( | |
412 details().initial_bounds_in_parent); | |
413 window_state()->SetRestoreBoundsInScreen( | |
414 details().restore_bounds.IsEmpty() ? initial_bounds | |
415 : details().restore_bounds); | |
416 } | |
417 if (!dock_layout_->is_dragged_window_docked()) { | |
418 // TODO(oshima): Add event source type to WMEvent and move | |
419 // metrics recording inside WindowState::OnWMEvent. | |
420 const wm::WMEvent event(snap_type_ == SNAP_LEFT | |
421 ? wm::WM_EVENT_SNAP_LEFT | |
422 : wm::WM_EVENT_SNAP_RIGHT); | |
423 window_state()->OnWMEvent(&event); | |
424 shell_->RecordUserMetricsAction(snap_type_ == SNAP_LEFT | |
425 ? UMA_DRAG_MAXIMIZE_LEFT | |
426 : UMA_DRAG_MAXIMIZE_RIGHT); | |
427 snapped = true; | |
428 } | |
429 } | |
430 | |
431 if (!snapped) { | |
432 if (window_state()->IsSnapped()) { | |
433 // Keep the window snapped if the user resizes the window such that the | |
434 // window has valid bounds for a snapped window. Always unsnap the window | |
435 // if the user dragged the window via the caption area because doing this | |
436 // is slightly less confusing. | |
437 if (details().window_component == HTCAPTION || | |
438 !AreBoundsValidSnappedBounds(window_state()->GetStateType(), | |
439 GetTarget()->GetBounds())) { | |
440 // Set the window to WINDOW_STATE_TYPE_NORMAL but keep the | |
441 // window at the bounds that the user has moved/resized the | |
442 // window to. | |
443 window_state()->SaveCurrentBoundsForRestore(); | |
444 window_state()->Restore(); | |
445 } | |
446 } else if (!dock_layout_->is_dragged_window_docked()) { | |
447 // The window was not snapped and is not snapped. This is a user | |
448 // resize/drag and so the current bounds should be maintained, clearing | |
449 // any prior restore bounds. When the window is docked the restore bound | |
450 // must be kept so the docked state can be reverted properly. | |
451 window_state()->ClearRestoreBounds(); | |
452 } | |
453 } | |
454 } | |
455 | |
456 void WorkspaceWindowResizer::RevertDrag() { | |
457 window_state()->set_bounds_changed_by_user(initial_bounds_changed_by_user_); | |
458 snap_phantom_window_controller_.reset(); | |
459 | |
460 if (!did_move_or_resize_) | |
461 return; | |
462 | |
463 GetTarget()->SetBounds(details().initial_bounds_in_parent); | |
464 if (!details().restore_bounds.IsEmpty()) | |
465 window_state()->SetRestoreBoundsInScreen(details().restore_bounds); | |
466 | |
467 if (details().window_component == HTRIGHT) { | |
468 int last_x = details().initial_bounds_in_parent.right(); | |
469 for (size_t i = 0; i < attached_windows_.size(); ++i) { | |
470 gfx::Rect bounds(attached_windows_[i]->GetBounds()); | |
471 bounds.set_x(last_x); | |
472 bounds.set_width(initial_size_[i]); | |
473 attached_windows_[i]->SetBounds(bounds); | |
474 last_x = attached_windows_[i]->GetBounds().right(); | |
475 } | |
476 } else { | |
477 int last_y = details().initial_bounds_in_parent.bottom(); | |
478 for (size_t i = 0; i < attached_windows_.size(); ++i) { | |
479 gfx::Rect bounds(attached_windows_[i]->GetBounds()); | |
480 bounds.set_y(last_y); | |
481 bounds.set_height(initial_size_[i]); | |
482 attached_windows_[i]->SetBounds(bounds); | |
483 last_y = attached_windows_[i]->GetBounds().bottom(); | |
484 } | |
485 } | |
486 } | |
487 | |
488 WorkspaceWindowResizer::WorkspaceWindowResizer( | |
489 wm::WindowState* window_state, | |
490 const std::vector<WmWindow*>& attached_windows) | |
491 : WindowResizer(window_state), | |
492 attached_windows_(attached_windows), | |
493 shell_(window_state->window()->GetShell()), | |
494 did_lock_cursor_(false), | |
495 did_move_or_resize_(false), | |
496 initial_bounds_changed_by_user_(window_state_->bounds_changed_by_user()), | |
497 total_min_(0), | |
498 total_initial_size_(0), | |
499 snap_type_(SNAP_NONE), | |
500 num_mouse_moves_since_bounds_change_(0), | |
501 magnetism_window_(NULL), | |
502 weak_ptr_factory_(this) { | |
503 DCHECK(details().is_resizable); | |
504 | |
505 // A mousemove should still show the cursor even if the window is | |
506 // being moved or resized with touch, so do not lock the cursor. | |
507 if (details().source != aura::client::WINDOW_MOVE_SOURCE_TOUCH) { | |
508 shell_->LockCursor(); | |
509 did_lock_cursor_ = true; | |
510 } | |
511 | |
512 dock_layout_ = DockedWindowLayoutManager::Get(GetTarget()); | |
513 | |
514 // Only support attaching to the right/bottom. | |
515 DCHECK(attached_windows_.empty() || (details().window_component == HTRIGHT || | |
516 details().window_component == HTBOTTOM)); | |
517 | |
518 // TODO: figure out how to deal with window going off the edge. | |
519 | |
520 // Calculate sizes so that we can maintain the ratios if we need to resize. | |
521 int total_available = 0; | |
522 for (size_t i = 0; i < attached_windows_.size(); ++i) { | |
523 gfx::Size min(attached_windows_[i]->GetMinimumSize()); | |
524 int initial_size = | |
525 PrimaryAxisSize(attached_windows_[i]->GetBounds().size()); | |
526 initial_size_.push_back(initial_size); | |
527 // If current size is smaller than the min, use the current size as the min. | |
528 // This way we don't snap on resize. | |
529 int min_size = std::min(initial_size, | |
530 std::max(PrimaryAxisSize(min), kMinOnscreenSize)); | |
531 total_min_ += min_size; | |
532 total_initial_size_ += initial_size; | |
533 total_available += std::max(min_size, initial_size) - min_size; | |
534 } | |
535 instance = this; | |
536 } | |
537 | |
538 void WorkspaceWindowResizer::LayoutAttachedWindows(gfx::Rect* bounds) { | |
539 gfx::Rect work_area(wm::GetDisplayWorkAreaBoundsInParent(GetTarget())); | |
540 int initial_size = PrimaryAxisSize(details().initial_bounds_in_parent.size()); | |
541 int current_size = PrimaryAxisSize(bounds->size()); | |
542 int start = PrimaryAxisCoordinate(bounds->right(), bounds->bottom()); | |
543 int end = PrimaryAxisCoordinate(work_area.right(), work_area.bottom()); | |
544 | |
545 int delta = current_size - initial_size; | |
546 int available_size = end - start; | |
547 std::vector<int> sizes; | |
548 int leftovers = CalculateAttachedSizes(delta, available_size, &sizes); | |
549 | |
550 // leftovers > 0 means that the attached windows can't grow to compensate for | |
551 // the shrinkage of the main window. This line causes the attached windows to | |
552 // be moved so they are still flush against the main window, rather than the | |
553 // main window being prevented from shrinking. | |
554 leftovers = std::min(0, leftovers); | |
555 // Reallocate any leftover pixels back into the main window. This is | |
556 // necessary when, for example, the main window shrinks, but none of the | |
557 // attached windows can grow without exceeding their max size constraints. | |
558 // Adding the pixels back to the main window effectively prevents the main | |
559 // window from resizing too far. | |
560 if (details().window_component == HTRIGHT) | |
561 bounds->set_width(bounds->width() + leftovers); | |
562 else | |
563 bounds->set_height(bounds->height() + leftovers); | |
564 | |
565 DCHECK_EQ(attached_windows_.size(), sizes.size()); | |
566 int last = PrimaryAxisCoordinate(bounds->right(), bounds->bottom()); | |
567 for (size_t i = 0; i < attached_windows_.size(); ++i) { | |
568 gfx::Rect attached_bounds(attached_windows_[i]->GetBounds()); | |
569 if (details().window_component == HTRIGHT) { | |
570 attached_bounds.set_x(last); | |
571 attached_bounds.set_width(sizes[i]); | |
572 } else { | |
573 attached_bounds.set_y(last); | |
574 attached_bounds.set_height(sizes[i]); | |
575 } | |
576 attached_windows_[i]->SetBounds(attached_bounds); | |
577 last += sizes[i]; | |
578 } | |
579 } | |
580 | |
581 int WorkspaceWindowResizer::CalculateAttachedSizes( | |
582 int delta, | |
583 int available_size, | |
584 std::vector<int>* sizes) const { | |
585 std::vector<WindowSize> window_sizes; | |
586 CreateBucketsForAttached(&window_sizes); | |
587 | |
588 // How much we need to grow the attached by (collectively). | |
589 int grow_attached_by = 0; | |
590 if (delta > 0) { | |
591 // If the attached windows don't fit when at their initial size, we will | |
592 // have to shrink them by how much they overflow. | |
593 if (total_initial_size_ >= available_size) | |
594 grow_attached_by = available_size - total_initial_size_; | |
595 } else { | |
596 // If we're shrinking, we grow the attached so the total size remains | |
597 // constant. | |
598 grow_attached_by = -delta; | |
599 } | |
600 | |
601 int leftover_pixels = 0; | |
602 while (grow_attached_by != 0) { | |
603 int leftovers = GrowFairly(grow_attached_by, &window_sizes); | |
604 if (leftovers == grow_attached_by) { | |
605 leftover_pixels = leftovers; | |
606 break; | |
607 } | |
608 grow_attached_by = leftovers; | |
609 } | |
610 | |
611 for (size_t i = 0; i < window_sizes.size(); ++i) | |
612 sizes->push_back(window_sizes[i].size()); | |
613 | |
614 return leftover_pixels; | |
615 } | |
616 | |
617 int WorkspaceWindowResizer::GrowFairly(int pixels, | |
618 std::vector<WindowSize>* sizes) const { | |
619 bool shrinking = pixels < 0; | |
620 std::vector<WindowSize*> nonfull_windows; | |
621 for (size_t i = 0; i < sizes->size(); ++i) { | |
622 WindowSize& current_window_size = (*sizes)[i]; | |
623 if (!current_window_size.is_at_capacity(shrinking)) | |
624 nonfull_windows.push_back(¤t_window_size); | |
625 } | |
626 std::vector<float> ratios; | |
627 CalculateGrowthRatios(nonfull_windows, &ratios); | |
628 | |
629 int remaining_pixels = pixels; | |
630 bool add_leftover_pixels_to_last = true; | |
631 for (size_t i = 0; i < nonfull_windows.size(); ++i) { | |
632 int grow_by = pixels * ratios[i]; | |
633 // Put any leftover pixels into the last window. | |
634 if (i == nonfull_windows.size() - 1 && add_leftover_pixels_to_last) | |
635 grow_by = remaining_pixels; | |
636 int remainder = nonfull_windows[i]->Add(grow_by); | |
637 int consumed = grow_by - remainder; | |
638 remaining_pixels -= consumed; | |
639 if (nonfull_windows[i]->is_at_capacity(shrinking) && remainder > 0) { | |
640 // Because this window overflowed, some of the pixels in | |
641 // |remaining_pixels| aren't there due to rounding errors. Rather than | |
642 // unfairly giving all those pixels to the last window, we refrain from | |
643 // allocating them so that this function can be called again to distribute | |
644 // the pixels fairly. | |
645 add_leftover_pixels_to_last = false; | |
646 } | |
647 } | |
648 return remaining_pixels; | |
649 } | |
650 | |
651 void WorkspaceWindowResizer::CalculateGrowthRatios( | |
652 const std::vector<WindowSize*>& sizes, | |
653 std::vector<float>* out_ratios) const { | |
654 DCHECK(out_ratios->empty()); | |
655 int total_value = 0; | |
656 for (size_t i = 0; i < sizes.size(); ++i) | |
657 total_value += sizes[i]->size(); | |
658 | |
659 for (size_t i = 0; i < sizes.size(); ++i) | |
660 out_ratios->push_back((static_cast<float>(sizes[i]->size())) / total_value); | |
661 } | |
662 | |
663 void WorkspaceWindowResizer::CreateBucketsForAttached( | |
664 std::vector<WindowSize>* sizes) const { | |
665 for (size_t i = 0; i < attached_windows_.size(); i++) { | |
666 int initial_size = initial_size_[i]; | |
667 int min = PrimaryAxisSize(attached_windows_[i]->GetMinimumSize()); | |
668 int max = PrimaryAxisSize(attached_windows_[i]->GetMaximumSize()); | |
669 | |
670 sizes->push_back(WindowSize(initial_size, min, max)); | |
671 } | |
672 } | |
673 | |
674 void WorkspaceWindowResizer::MagneticallySnapToOtherWindows(gfx::Rect* bounds) { | |
675 if (UpdateMagnetismWindow(*bounds, kAllMagnetismEdges)) { | |
676 gfx::Point point = OriginForMagneticAttach( | |
677 GetTarget()->GetParent()->ConvertRectToScreen(*bounds), | |
678 magnetism_window_->GetBoundsInScreen(), magnetism_edge_); | |
679 point = GetTarget()->GetParent()->ConvertPointFromScreen(point); | |
680 bounds->set_origin(point); | |
681 } | |
682 } | |
683 | |
684 void WorkspaceWindowResizer::MagneticallySnapResizeToOtherWindows( | |
685 gfx::Rect* bounds) { | |
686 const uint32_t edges = | |
687 WindowComponentToMagneticEdge(details().window_component); | |
688 if (UpdateMagnetismWindow(*bounds, edges)) { | |
689 *bounds = GetTarget()->GetParent()->ConvertRectFromScreen( | |
690 BoundsForMagneticResizeAttach( | |
691 GetTarget()->GetParent()->ConvertRectToScreen(*bounds), | |
692 magnetism_window_->GetBoundsInScreen(), magnetism_edge_)); | |
693 } | |
694 } | |
695 | |
696 bool WorkspaceWindowResizer::UpdateMagnetismWindow(const gfx::Rect& bounds, | |
697 uint32_t edges) { | |
698 // |bounds| are in coordinates of original window's parent. | |
699 gfx::Rect bounds_in_screen = | |
700 GetTarget()->GetParent()->ConvertRectToScreen(bounds); | |
701 MagnetismMatcher matcher(bounds_in_screen, edges); | |
702 | |
703 // If we snapped to a window then check it first. That way we don't bounce | |
704 // around when close to multiple edges. | |
705 if (magnetism_window_) { | |
706 if (window_tracker_.Contains(magnetism_window_->aura_window()) && | |
707 matcher.ShouldAttach(magnetism_window_->GetBoundsInScreen(), | |
708 &magnetism_edge_)) { | |
709 return true; | |
710 } | |
711 window_tracker_.Remove(magnetism_window_->aura_window()); | |
712 magnetism_window_ = NULL; | |
713 } | |
714 | |
715 // Avoid magnetically snapping windows that are not resizable. | |
716 // TODO(oshima): change this to window.type() == TYPE_NORMAL. | |
717 if (!window_state()->CanResize()) | |
718 return false; | |
719 | |
720 for (WmWindow* root_window : shell_->GetAllRootWindows()) { | |
721 // Test all children from the desktop in each root window. | |
722 const std::vector<WmWindow*> children = | |
723 root_window->GetChildByShellWindowId(kShellWindowId_DefaultContainer) | |
724 ->GetChildren(); | |
725 for (auto i = children.rbegin(); | |
726 i != children.rend() && !matcher.AreEdgesObscured(); ++i) { | |
727 wm::WindowState* other_state = (*i)->GetWindowState(); | |
728 if (other_state->window() == GetTarget() || | |
729 !other_state->window()->IsVisible() || | |
730 !other_state->IsNormalOrSnapped() || !other_state->CanResize()) { | |
731 continue; | |
732 } | |
733 if (matcher.ShouldAttach(other_state->window()->GetBoundsInScreen(), | |
734 &magnetism_edge_)) { | |
735 magnetism_window_ = other_state->window(); | |
736 window_tracker_.Add(magnetism_window_->aura_window()); | |
737 return true; | |
738 } | |
739 } | |
740 } | |
741 return false; | |
742 } | |
743 | |
744 void WorkspaceWindowResizer::AdjustBoundsForMainWindow(int sticky_size, | |
745 gfx::Rect* bounds) { | |
746 gfx::Point last_mouse_location_in_screen = | |
747 GetTarget()->GetParent()->ConvertPointToScreen(last_mouse_location_); | |
748 display::Display display = | |
749 display::Screen::GetScreen()->GetDisplayNearestPoint( | |
750 last_mouse_location_in_screen); | |
751 gfx::Rect work_area = | |
752 GetTarget()->GetParent()->ConvertRectFromScreen(display.work_area()); | |
753 if (details().window_component == HTCAPTION) { | |
754 // Adjust the bounds to the work area where the mouse cursor is located. | |
755 // Always keep kMinOnscreenHeight or the window height (whichever is less) | |
756 // on the bottom. | |
757 int max_y = | |
758 work_area.bottom() - std::min(kMinOnscreenHeight, bounds->height()); | |
759 if (bounds->y() > max_y) { | |
760 bounds->set_y(max_y); | |
761 } else if (bounds->y() <= work_area.y()) { | |
762 // Don't allow dragging above the top of the display until the mouse | |
763 // cursor reaches the work area above if any. | |
764 bounds->set_y(work_area.y()); | |
765 } | |
766 | |
767 if (sticky_size > 0) { | |
768 // Possibly stick to edge except when a mouse pointer is outside the | |
769 // work area. | |
770 if (display.work_area().Contains(last_mouse_location_in_screen)) | |
771 StickToWorkAreaOnMove(work_area, sticky_size, bounds); | |
772 MagneticallySnapToOtherWindows(bounds); | |
773 } | |
774 } else if (sticky_size > 0) { | |
775 MagneticallySnapResizeToOtherWindows(bounds); | |
776 if (!magnetism_window_ && sticky_size > 0) | |
777 StickToWorkAreaOnResize(work_area, sticky_size, bounds); | |
778 } | |
779 | |
780 if (attached_windows_.empty()) | |
781 return; | |
782 | |
783 if (details().window_component == HTRIGHT) { | |
784 bounds->set_width(std::min(bounds->width(), | |
785 work_area.right() - total_min_ - bounds->x())); | |
786 } else { | |
787 DCHECK_EQ(HTBOTTOM, details().window_component); | |
788 bounds->set_height(std::min(bounds->height(), | |
789 work_area.bottom() - total_min_ - bounds->y())); | |
790 } | |
791 } | |
792 | |
793 bool WorkspaceWindowResizer::StickToWorkAreaOnMove(const gfx::Rect& work_area, | |
794 int sticky_size, | |
795 gfx::Rect* bounds) const { | |
796 const int left_edge = work_area.x(); | |
797 const int right_edge = work_area.right(); | |
798 const int top_edge = work_area.y(); | |
799 const int bottom_edge = work_area.bottom(); | |
800 bool updated = false; | |
801 if (ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) { | |
802 bounds->set_x(left_edge); | |
803 updated = true; | |
804 } else if (ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) { | |
805 bounds->set_x(right_edge - bounds->width()); | |
806 updated = true; | |
807 } | |
808 if (ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) { | |
809 bounds->set_y(top_edge); | |
810 updated = true; | |
811 } else if (ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size) && | |
812 bounds->height() < (bottom_edge - top_edge)) { | |
813 // Only snap to the bottom if the window is smaller than the work area. | |
814 // Doing otherwise can lead to window snapping in weird ways as it bounces | |
815 // between snapping to top then bottom. | |
816 bounds->set_y(bottom_edge - bounds->height()); | |
817 updated = true; | |
818 } | |
819 return updated; | |
820 } | |
821 | |
822 void WorkspaceWindowResizer::StickToWorkAreaOnResize(const gfx::Rect& work_area, | |
823 int sticky_size, | |
824 gfx::Rect* bounds) const { | |
825 const uint32_t edges = | |
826 WindowComponentToMagneticEdge(details().window_component); | |
827 const int left_edge = work_area.x(); | |
828 const int right_edge = work_area.right(); | |
829 const int top_edge = work_area.y(); | |
830 const int bottom_edge = work_area.bottom(); | |
831 if (edges & MAGNETISM_EDGE_TOP && | |
832 ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) { | |
833 bounds->set_height(bounds->bottom() - top_edge); | |
834 bounds->set_y(top_edge); | |
835 } | |
836 if (edges & MAGNETISM_EDGE_LEFT && | |
837 ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) { | |
838 bounds->set_width(bounds->right() - left_edge); | |
839 bounds->set_x(left_edge); | |
840 } | |
841 if (edges & MAGNETISM_EDGE_BOTTOM && | |
842 ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size)) { | |
843 bounds->set_height(bottom_edge - bounds->y()); | |
844 } | |
845 if (edges & MAGNETISM_EDGE_RIGHT && | |
846 ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) { | |
847 bounds->set_width(right_edge - bounds->x()); | |
848 } | |
849 } | |
850 | |
851 int WorkspaceWindowResizer::PrimaryAxisSize(const gfx::Size& size) const { | |
852 return PrimaryAxisCoordinate(size.width(), size.height()); | |
853 } | |
854 | |
855 int WorkspaceWindowResizer::PrimaryAxisCoordinate(int x, int y) const { | |
856 switch (details().window_component) { | |
857 case HTRIGHT: | |
858 return x; | |
859 case HTBOTTOM: | |
860 return y; | |
861 default: | |
862 NOTREACHED(); | |
863 } | |
864 return 0; | |
865 } | |
866 | |
867 void WorkspaceWindowResizer::UpdateSnapPhantomWindow(const gfx::Point& location, | |
868 const gfx::Rect& bounds) { | |
869 if (!did_move_or_resize_ || details().window_component != HTCAPTION) | |
870 return; | |
871 | |
872 SnapType last_type = snap_type_; | |
873 snap_type_ = GetSnapType(location); | |
874 if (snap_type_ == SNAP_NONE || snap_type_ != last_type) { | |
875 snap_phantom_window_controller_.reset(); | |
876 edge_cycler_.reset(); | |
877 if (snap_type_ == SNAP_NONE) { | |
878 SetDraggedWindowDocked(false); | |
879 return; | |
880 } | |
881 } | |
882 | |
883 DCHECK(snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT); | |
884 DockedAlignment desired_alignment = (snap_type_ == SNAP_LEFT) | |
885 ? DOCKED_ALIGNMENT_LEFT | |
886 : DOCKED_ALIGNMENT_RIGHT; | |
887 const bool can_dock = | |
888 ash::switches::DockedWindowsEnabled() && | |
889 dock_layout_->CanDockWindow(GetTarget(), desired_alignment) && | |
890 dock_layout_->GetAlignmentOfWindow(GetTarget()) != DOCKED_ALIGNMENT_NONE; | |
891 if (!can_dock) { | |
892 // If the window cannot be docked, undock the window. This may change the | |
893 // workspace bounds and hence |snap_type_|. | |
894 SetDraggedWindowDocked(false); | |
895 snap_type_ = GetSnapType(location); | |
896 } | |
897 const bool can_snap = snap_type_ != SNAP_NONE && window_state()->CanSnap(); | |
898 if (!can_snap && !can_dock) { | |
899 snap_type_ = SNAP_NONE; | |
900 snap_phantom_window_controller_.reset(); | |
901 edge_cycler_.reset(); | |
902 return; | |
903 } | |
904 if (!edge_cycler_) { | |
905 edge_cycler_.reset(new TwoStepEdgeCycler( | |
906 location, snap_type_ == SNAP_LEFT | |
907 ? TwoStepEdgeCycler::DIRECTION_LEFT | |
908 : TwoStepEdgeCycler::DIRECTION_RIGHT)); | |
909 } else { | |
910 edge_cycler_->OnMove(location); | |
911 } | |
912 | |
913 // Update phantom window with snapped or docked guide bounds. | |
914 // Windows that cannot be snapped or are less wide than kMaxDockWidth can get | |
915 // docked without going through a snapping sequence. | |
916 gfx::Rect phantom_bounds; | |
917 const bool should_dock = | |
918 can_dock && (!can_snap || | |
919 GetTarget()->GetBounds().width() <= | |
920 DockedWindowLayoutManager::kMaxDockWidth || | |
921 edge_cycler_->use_second_mode() || | |
922 dock_layout_->is_dragged_window_docked()); | |
923 if (should_dock) { | |
924 SetDraggedWindowDocked(true); | |
925 phantom_bounds = GetTarget()->GetParent()->ConvertRectFromScreen( | |
926 dock_layout_->dragged_bounds()); | |
927 } else { | |
928 phantom_bounds = | |
929 (snap_type_ == SNAP_LEFT) | |
930 ? wm::GetDefaultLeftSnappedWindowBoundsInParent(GetTarget()) | |
931 : wm::GetDefaultRightSnappedWindowBoundsInParent(GetTarget()); | |
932 } | |
933 | |
934 if (!snap_phantom_window_controller_) { | |
935 snap_phantom_window_controller_.reset( | |
936 new PhantomWindowController(GetTarget())); | |
937 } | |
938 snap_phantom_window_controller_->Show( | |
939 GetTarget()->GetParent()->ConvertRectToScreen(phantom_bounds)); | |
940 } | |
941 | |
942 void WorkspaceWindowResizer::RestackWindows() { | |
943 if (attached_windows_.empty()) | |
944 return; | |
945 // Build a map from index in children to window, returning if there is a | |
946 // window with a different parent. | |
947 using IndexToWindowMap = std::map<size_t, WmWindow*>; | |
948 IndexToWindowMap map; | |
949 WmWindow* parent = GetTarget()->GetParent(); | |
950 const std::vector<WmWindow*> windows(parent->GetChildren()); | |
951 map[std::find(windows.begin(), windows.end(), GetTarget()) - | |
952 windows.begin()] = GetTarget(); | |
953 for (auto i = attached_windows_.begin(); i != attached_windows_.end(); ++i) { | |
954 if ((*i)->GetParent() != parent) | |
955 return; | |
956 size_t index = | |
957 std::find(windows.begin(), windows.end(), *i) - windows.begin(); | |
958 map[index] = *i; | |
959 } | |
960 | |
961 // Reorder the windows starting at the topmost. | |
962 parent->StackChildAtTop(map.rbegin()->second); | |
963 for (auto i = map.rbegin(); i != map.rend();) { | |
964 WmWindow* window = i->second; | |
965 ++i; | |
966 if (i != map.rend()) | |
967 parent->StackChildBelow(i->second, window); | |
968 } | |
969 } | |
970 | |
971 WorkspaceWindowResizer::SnapType WorkspaceWindowResizer::GetSnapType( | |
972 const gfx::Point& location) const { | |
973 // TODO: this likely only wants total display area, not the area of a single | |
974 // display. | |
975 gfx::Rect area(wm::GetDisplayWorkAreaBoundsInParent(GetTarget())); | |
976 if (details().source == aura::client::WINDOW_MOVE_SOURCE_TOUCH) { | |
977 // Increase tolerance for touch-snapping near the screen edges. This is only | |
978 // necessary when the work area left or right edge is same as screen edge. | |
979 gfx::Rect display_bounds(wm::GetDisplayBoundsInParent(GetTarget())); | |
980 int inset_left = 0; | |
981 if (area.x() == display_bounds.x()) | |
982 inset_left = kScreenEdgeInsetForTouchDrag; | |
983 int inset_right = 0; | |
984 if (area.right() == display_bounds.right()) | |
985 inset_right = kScreenEdgeInsetForTouchDrag; | |
986 area.Inset(inset_left, 0, inset_right, 0); | |
987 } | |
988 if (location.x() <= area.x()) | |
989 return SNAP_LEFT; | |
990 if (location.x() >= area.right() - 1) | |
991 return SNAP_RIGHT; | |
992 return SNAP_NONE; | |
993 } | |
994 | |
995 void WorkspaceWindowResizer::SetDraggedWindowDocked(bool should_dock) { | |
996 if (should_dock) { | |
997 if (!dock_layout_->is_dragged_window_docked()) { | |
998 window_state()->set_bounds_changed_by_user(false); | |
999 dock_layout_->DockDraggedWindow(GetTarget()); | |
1000 } | |
1001 } else { | |
1002 if (dock_layout_->is_dragged_window_docked()) { | |
1003 dock_layout_->UndockDraggedWindow(); | |
1004 window_state()->set_bounds_changed_by_user(true); | |
1005 } | |
1006 } | |
1007 } | |
1008 | |
1009 bool WorkspaceWindowResizer::AreBoundsValidSnappedBounds( | |
1010 wm::WindowStateType snapped_type, | |
1011 const gfx::Rect& bounds_in_parent) const { | |
1012 DCHECK(snapped_type == wm::WINDOW_STATE_TYPE_LEFT_SNAPPED || | |
1013 snapped_type == wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED); | |
1014 gfx::Rect snapped_bounds = wm::GetDisplayWorkAreaBoundsInParent(GetTarget()); | |
1015 if (snapped_type == wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED) | |
1016 snapped_bounds.set_x(snapped_bounds.right() - bounds_in_parent.width()); | |
1017 snapped_bounds.set_width(bounds_in_parent.width()); | |
1018 return bounds_in_parent == snapped_bounds; | |
1019 } | |
1020 | |
1021 } // namespace ash | |
OLD | NEW |