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

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

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

Powered by Google App Engine
This is Rietveld 408576698