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