Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: ash/common/wm/workspace/workspace_window_resizer.cc

Issue 2734653002: chromeos: Move files in //ash/common to //ash (Closed)
Patch Set: fix a11y tests, fix docs Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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(&current_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
OLDNEW
« no previous file with comments | « ash/common/wm/workspace/workspace_window_resizer.h ('k') | ash/common/wm/workspace_controller.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698