1 // Copyright 2014 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/overview/overview_grid.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <memory>
10 #include <utility>
11
12 #include "ash/frame_throttler/frame_throttling_controller.h"
13 #include "ash/metrics/histogram_macros.h"
14 #include "ash/public/cpp/ash_features.h"
15 #include "ash/public/cpp/metrics_util.h"
16 #include "ash/public/cpp/presentation_time_recorder.h"
17 #include "ash/public/cpp/shelf_config.h"
18 #include "ash/public/cpp/shelf_types.h"
19 #include "ash/public/cpp/window_properties.h"
20 #include "ash/root_window_controller.h"
21 #include "ash/root_window_settings.h"
22 #include "ash/rotator/screen_rotation_animator.h"
23 #include "ash/screen_util.h"
24 #include "ash/shell.h"
25 #include "ash/wallpaper/wallpaper_controller_impl.h"
26 #include "ash/wm/desks/desk_mini_view.h"
27 #include "ash/wm/desks/desk_name_view.h"
28 #include "ash/wm/desks/desks_bar_view.h"
29 #include "ash/wm/desks/desks_util.h"
30 #include "ash/wm/mru_window_tracker.h"
31 #include "ash/wm/overview/cleanup_animation_observer.h"
32 #include "ash/wm/overview/drop_target_view.h"
33 #include "ash/wm/overview/overview_constants.h"
34 #include "ash/wm/overview/overview_controller.h"
35 #include "ash/wm/overview/overview_delegate.h"
36 #include "ash/wm/overview/overview_grid_event_handler.h"
37 #include "ash/wm/overview/overview_highlight_controller.h"
38 #include "ash/wm/overview/overview_item.h"
39 #include "ash/wm/overview/overview_item_view.h"
40 #include "ash/wm/overview/overview_session.h"
41 #include "ash/wm/overview/overview_utils.h"
42 #include "ash/wm/overview/overview_window_drag_controller.h"
43 #include "ash/wm/overview/scoped_overview_animation_settings.h"
44 #include "ash/wm/splitview/split_view_constants.h"
45 #include "ash/wm/splitview/split_view_divider.h"
46 #include "ash/wm/splitview/split_view_utils.h"
47 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
48 #include "ash/wm/tablet_mode/tablet_mode_window_state.h"
49 #include "ash/wm/window_util.h"
50 #include "ash/wm/workspace/backdrop_controller.h"
51 #include "ash/wm/workspace/workspace_layout_manager.h"
52 #include "ash/wm/workspace_controller.h"
53 #include "base/bind.h"
54 #include "base/containers/unique_ptr_adapters.h"
55 #include "base/numerics/ranges.h"
56 #include "base/numerics/safe_conversions.h"
57 #include "base/strings/utf_string_conversions.h"
58 #include "ui/aura/client/aura_constants.h"
59 #include "ui/compositor/layer_animation_observer.h"
60 #include "ui/compositor/throughput_tracker.h"
61 #include "ui/gfx/geometry/vector2d_f.h"
62 #include "ui/gfx/transform_util.h"
63 #include "ui/views/view.h"
64 #include "ui/views/widget/widget.h"
65 #include "ui/wm/core/coordinate_conversion.h"
66 #include "ui/wm/core/window_animations.h"
67
68 namespace ash {
69 namespace {
70
71 // Windows are not allowed to get taller than this.
72 constexpr int kMaxHeight = 512;
73
74 // Margins reserved in the overview mode.
75 constexpr float kOverviewInsetRatio = 0.05f;
76
77 // Additional vertical inset reserved for windows in overview mode.
78 constexpr float kOverviewVerticalInset = 0.1f;
79
80 // Number of rows for windows in tablet overview mode.
81 constexpr int kTabletLayoutRow = 2;
82
83 constexpr int kMinimumItemsForNewLayout = 6;
84
85 // Wait a while before unpausing the occlusion tracker after a scroll has
86 // completed as the user may start another scroll.
87 constexpr base::TimeDelta kOcclusionUnpauseDurationForScroll =
88 base::TimeDelta::FromMilliseconds(500);
89
90 constexpr base::TimeDelta kOcclusionUnpauseDurationForRotation =
91 base::TimeDelta::FromMilliseconds(300);
92
93 // Histogram names for overview enter/exit smoothness in clamshell,
94 // tablet mode and splitview.
95 constexpr char kOverviewEnterClamshellHistogram[] =
96 "Ash.Overview.AnimationSmoothness.Enter.ClamshellMode";
97 constexpr char kOverviewEnterSingleClamshellHistogram[] =
98 "Ash.Overview.AnimationSmoothness.Enter.SingleClamshellMode";
99 constexpr char kOverviewEnterTabletHistogram[] =
100 "Ash.Overview.AnimationSmoothness.Enter.TabletMode";
101 constexpr char kOverviewEnterMinimizedTabletHistogram[] =
102 "Ash.Overview.AnimationSmoothness.Enter.MinimizedTabletMode";
103 constexpr char kOverviewEnterSplitViewHistogram[] =
104 "Ash.Overview.AnimationSmoothness.Enter.SplitView";
105
106 constexpr char kOverviewExitClamshellHistogram[] =
107 "Ash.Overview.AnimationSmoothness.Exit.ClamshellMode";
108 constexpr char kOverviewExitSingleClamshellHistogram[] =
109 "Ash.Overview.AnimationSmoothness.Exit.SingleClamshellMode";
110 constexpr char kOverviewExitTabletHistogram[] =
111 "Ash.Overview.AnimationSmoothness.Exit.TabletMode";
112 constexpr char kOverviewExitMinimizedTabletHistogram[] =
113 "Ash.Overview.AnimationSmoothness.Exit.MinimizedTabletMode";
114 constexpr char kOverviewExitSplitViewHistogram[] =
115 "Ash.Overview.AnimationSmoothness.Exit.SplitView";
116
117 // The UMA histogram that records presentation time for grid scrolling in the
118 // new overview layout.
119 constexpr char kOverviewScrollHistogram[] =
120 "Ash.Overview.Scroll.PresentationTime.TabletMode";
121 constexpr char kOverviewScrollMaxLatencyHistogram[] =
122 "Ash.Overview.Scroll.PresentationTime.MaxLatency.TabletMode";
123
124 template <const char* clamshell_single_name,
125 const char* clamshell_multi_name,
126 const char* tablet_name,
127 const char* splitview_name,
128 const char* tablet_minimized_name>
129 class OverviewMetricsTracker : public OverviewGrid::MetricsTracker {
130 public:
OverviewMetricsTracker(ui::Compositor * compositor,bool in_split_view,bool single_animation_in_clamshell,bool minimized_in_tablet)131 OverviewMetricsTracker(ui::Compositor* compositor,
132 bool in_split_view,
133 bool single_animation_in_clamshell,
134 bool minimized_in_tablet)
135 : tracker_(compositor->RequestNewThroughputTracker()) {
136 tracker_.Start(metrics_util::ForSmoothness(base::BindRepeating(
137 &OverviewMetricsTracker::ReportOverviewSmoothness, in_split_view,
138 single_animation_in_clamshell, minimized_in_tablet)));
139 }
140 OverviewMetricsTracker(const OverviewMetricsTracker&) = delete;
141 OverviewMetricsTracker& operator=(const OverviewMetricsTracker&) = delete;
~OverviewMetricsTracker()142 ~OverviewMetricsTracker() override { tracker_.Stop(); }
143
ReportOverviewSmoothness(bool in_split_view,bool single_animation_in_clamshell,bool minimized_in_tablet,int smoothness)144 static void ReportOverviewSmoothness(bool in_split_view,
145 bool single_animation_in_clamshell,
146 bool minimized_in_tablet,
147 int smoothness) {
148 if (single_animation_in_clamshell)
149 UMA_HISTOGRAM_PERCENTAGE_IN_CLAMSHELL(clamshell_single_name, smoothness);
150 else
151 UMA_HISTOGRAM_PERCENTAGE_IN_CLAMSHELL(clamshell_multi_name, smoothness);
152
153 if (minimized_in_tablet) {
154 UMA_HISTOGRAM_PERCENTAGE_IN_TABLET_NON_SPLITVIEW(
155 in_split_view, tablet_minimized_name, smoothness);
156 } else {
157 UMA_HISTOGRAM_PERCENTAGE_IN_TABLET_NON_SPLITVIEW(in_split_view,
158 tablet_name, smoothness);
159 }
160 UMA_HISTOGRAM_PERCENTAGE_IN_SPLITVIEW(in_split_view, splitview_name,
161 smoothness);
162 }
163
164 private:
165 ui::ThroughputTracker tracker_;
166 };
167
168 using OverviewEnterMetricsTracker =
169 OverviewMetricsTracker<kOverviewEnterSingleClamshellHistogram,
170 kOverviewEnterClamshellHistogram,
171 kOverviewEnterTabletHistogram,
172 kOverviewEnterSplitViewHistogram,
173 kOverviewEnterMinimizedTabletHistogram>;
174 using OverviewExitMetricsTracker =
175 OverviewMetricsTracker<kOverviewExitSingleClamshellHistogram,
176 kOverviewExitClamshellHistogram,
177 kOverviewExitTabletHistogram,
178 kOverviewExitSplitViewHistogram,
179 kOverviewExitMinimizedTabletHistogram>;
180
181 class ShutdownAnimationMetricsTrackerObserver : public OverviewObserver {
182 public:
ShutdownAnimationMetricsTrackerObserver(ui::Compositor * compositor,bool in_split_view,bool single_animation,bool minimized_in_tablet)183 ShutdownAnimationMetricsTrackerObserver(ui::Compositor* compositor,
184 bool in_split_view,
185 bool single_animation,
186 bool minimized_in_tablet)
187 : metrics_tracker_(compositor,
188 in_split_view,
189 single_animation,
190 minimized_in_tablet) {
191 Shell::Get()->overview_controller()->AddObserver(this);
192 }
193 ShutdownAnimationMetricsTrackerObserver(
194 const ShutdownAnimationMetricsTrackerObserver&) = delete;
195 ShutdownAnimationMetricsTrackerObserver& operator=(
196 const ShutdownAnimationMetricsTrackerObserver&) = delete;
~ShutdownAnimationMetricsTrackerObserver()197 ~ShutdownAnimationMetricsTrackerObserver() override {
198 Shell::Get()->overview_controller()->RemoveObserver(this);
199 }
200
201 // OverviewObserver:
OnOverviewModeEndingAnimationComplete(bool canceled)202 void OnOverviewModeEndingAnimationComplete(bool canceled) override {
203 delete this;
204 }
205
206 private:
207 OverviewExitMetricsTracker metrics_tracker_;
208 };
209
210 // Creates |drop_target_widget_|. It's created when a window or overview item is
211 // dragged around, and destroyed when the drag ends.
CreateDropTargetWidget(aura::Window * root_window,aura::Window * dragged_window)212 std::unique_ptr<views::Widget> CreateDropTargetWidget(
213 aura::Window* root_window,
214 aura::Window* dragged_window) {
215 views::Widget::InitParams params;
216 params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
217 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
218 params.activatable = views::Widget::InitParams::Activatable::ACTIVATABLE_NO;
219 params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
220 params.name = "OverviewDropTarget";
221 params.accept_events = false;
222 params.parent = desks_util::GetActiveDeskContainerForRoot(root_window);
223 params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);
224 auto widget = std::make_unique<views::Widget>();
225 widget->set_focus_on_creation(false);
226 widget->Init(std::move(params));
227 widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE);
228
229 // Show plus icon if drag a tab from a multi-tab window.
230 widget->SetContentsView(std::make_unique<DropTargetView>(
231 dragged_window->GetProperty(ash::kTabDraggingSourceWindowKey)));
232 aura::Window* drop_target_window = widget->GetNativeWindow();
233 drop_target_window->parent()->StackChildAtBottom(drop_target_window);
234 widget->Show();
235 return widget;
236 }
237
GetWantedDropTargetOpacity(SplitViewDragIndicators::WindowDraggingState window_dragging_state)238 float GetWantedDropTargetOpacity(
239 SplitViewDragIndicators::WindowDraggingState window_dragging_state) {
240 switch (window_dragging_state) {
241 case SplitViewDragIndicators::WindowDraggingState::kNoDrag:
242 case SplitViewDragIndicators::WindowDraggingState::kOtherDisplay:
243 case SplitViewDragIndicators::WindowDraggingState::kToSnapLeft:
244 case SplitViewDragIndicators::WindowDraggingState::kToSnapRight:
245 return 0.f;
246 case SplitViewDragIndicators::WindowDraggingState::kFromOverview:
247 case SplitViewDragIndicators::WindowDraggingState::kFromTop:
248 case SplitViewDragIndicators::WindowDraggingState::kFromShelf:
249 return 1.f;
250 }
251 }
252
GetGridInsets(const gfx::Rect & grid_bounds)253 gfx::Insets GetGridInsets(const gfx::Rect& grid_bounds) {
254 const int horizontal_inset =
255 base::ClampFloor(std::min(kOverviewInsetRatio * grid_bounds.width(),
256 kOverviewInsetRatio * grid_bounds.height()));
257 const int vertical_inset =
258 horizontal_inset +
259 kOverviewVerticalInset * (grid_bounds.height() - 2 * horizontal_inset);
260
261 return gfx::Insets(std::max(0, vertical_inset - kWindowMargin),
262 std::max(0, horizontal_inset - kWindowMargin));
263 }
264
ShouldExcludeItemFromGridLayout(OverviewItem * item,const base::flat_set<OverviewItem * > & ignored_items)265 bool ShouldExcludeItemFromGridLayout(
266 OverviewItem* item,
267 const base::flat_set<OverviewItem*>& ignored_items) {
268 return item->animating_to_close() || ignored_items.contains(item);
269 }
270
271 } // namespace
272
273 // The class to observe the overview window that the dragged tabs will merge
274 // into. After the dragged tabs merge into the overview window, and if the
275 // overview window represents a minimized window, we need to update the
276 // overview minimized widget's content view so that it reflects the merge.
277 class OverviewGrid::TargetWindowObserver : public aura::WindowObserver {
278 public:
279 TargetWindowObserver() = default;
~TargetWindowObserver()280 ~TargetWindowObserver() override { StopObserving(); }
281
StartObserving(aura::Window * window)282 void StartObserving(aura::Window* window) {
283 if (target_window_)
284 StopObserving();
285
286 target_window_ = window;
287 target_window_->AddObserver(this);
288 }
289
290 // aura::WindowObserver:
OnWindowPropertyChanged(aura::Window * window,const void * key,intptr_t old)291 void OnWindowPropertyChanged(aura::Window* window,
292 const void* key,
293 intptr_t old) override {
294 DCHECK_EQ(window, target_window_);
295 // When the property is cleared, the dragged window should have been merged
296 // into |target_window_|, update the corresponding window item in overview.
297 if (key == kIsDeferredTabDraggingTargetWindowKey &&
298 !window->GetProperty(kIsDeferredTabDraggingTargetWindowKey)) {
299 UpdateWindowItemInOverviewContaining(window);
300 StopObserving();
301 }
302 }
303
OnWindowDestroying(aura::Window * window)304 void OnWindowDestroying(aura::Window* window) override {
305 DCHECK_EQ(window, target_window_);
306 StopObserving();
307 }
308
309 private:
UpdateWindowItemInOverviewContaining(aura::Window * window)310 void UpdateWindowItemInOverviewContaining(aura::Window* window) {
311 OverviewController* overview_controller =
312 Shell::Get()->overview_controller();
313 if (!overview_controller->InOverviewSession())
314 return;
315
316 OverviewGrid* grid =
317 overview_controller->overview_session()->GetGridWithRootWindow(
318 window->GetRootWindow());
319 if (!grid)
320 return;
321
322 OverviewItem* item = grid->GetOverviewItemContaining(window);
323 if (!item)
324 return;
325
326 item->UpdateItemContentViewForMinimizedWindow();
327 }
328
StopObserving()329 void StopObserving() {
330 if (target_window_)
331 target_window_->RemoveObserver(this);
332 target_window_ = nullptr;
333 }
334
335 aura::Window* target_window_ = nullptr;
336
337 DISALLOW_COPY_AND_ASSIGN(TargetWindowObserver);
338 };
339
OverviewGrid(aura::Window * root_window,const std::vector<aura::Window * > & windows,OverviewSession * overview_session)340 OverviewGrid::OverviewGrid(aura::Window* root_window,
341 const std::vector<aura::Window*>& windows,
342 OverviewSession* overview_session)
343 : root_window_(root_window),
344 overview_session_(overview_session),
345 split_view_drag_indicators_(
346 ShouldAllowSplitView()
347 ? std::make_unique<SplitViewDragIndicators>(root_window)
348 : nullptr),
349 bounds_(GetGridBoundsInScreen(root_window)) {
350 for (auto* window : windows) {
351 if (window->GetRootWindow() != root_window)
352 continue;
353
354 // Stop ongoing animations before entering overview mode. Because we are
355 // deferring SetTransform of the windows beneath the window covering the
356 // available workspace, we need to set the correct transforms of these
357 // windows before entering overview mode again in the
358 // OnImplicitAnimationsCompleted() of the observer of the
359 // available-workspace-covering window's animation.
360 auto* animator = window->layer()->GetAnimator();
361 if (animator->is_animating())
362 window->layer()->GetAnimator()->StopAnimating();
363 window_list_.push_back(
364 std::make_unique<OverviewItem>(window, overview_session_, this));
365 }
366 }
367
368 OverviewGrid::~OverviewGrid() = default;
369
Shutdown()370 void OverviewGrid::Shutdown() {
371 EndNudge();
372
373 SplitViewController::Get(root_window_)->RemoveObserver(this);
374 ScreenRotationAnimator::GetForRootWindow(root_window_)->RemoveObserver(this);
375 Shell::Get()->wallpaper_controller()->RemoveObserver(this);
376 grid_event_handler_.reset();
377
378 bool has_non_cover_animating = false;
379 int animate_count = 0;
380
381 for (const auto& window : window_list_) {
382 if (window->should_animate_when_exiting() && !has_non_cover_animating) {
383 has_non_cover_animating |=
384 !CanCoverAvailableWorkspace(window->GetWindow());
385 animate_count++;
386 }
387 window->Shutdown();
388 }
389 bool single_animation_in_clamshell =
390 (animate_count == 1 && !has_non_cover_animating) &&
391 !Shell::Get()->tablet_mode_controller()->InTabletMode();
392
393 const bool in_split_view =
394 SplitViewController::Get(root_window_)->InSplitViewMode();
395 // OverviewGrid in splitscreen does not include the window to be activated.
396 if (!window_list_.empty() || in_split_view) {
397 bool minimized_in_tablet = overview_session_->enter_exit_overview_type() ==
398 OverviewEnterExitType::kFadeOutExit;
399 // The following instance self-destructs when shutdown animation ends.
400 new ShutdownAnimationMetricsTrackerObserver(
401 root_window_->layer()->GetCompositor(), in_split_view,
402 single_animation_in_clamshell, minimized_in_tablet);
403 }
404
405 window_list_.clear();
406 overview_session_ = nullptr;
407 }
408
PrepareForOverview()409 void OverviewGrid::PrepareForOverview() {
410 if (!ShouldAnimateWallpaper(root_window_))
411 MaybeInitDesksWidget();
412
413 for (const auto& window : window_list_)
414 window->PrepareForOverview();
415 SplitViewController::Get(root_window_)->AddObserver(this);
416 if (Shell::Get()->tablet_mode_controller()->InTabletMode())
417 ScreenRotationAnimator::GetForRootWindow(root_window_)->AddObserver(this);
418
419 grid_event_handler_ = std::make_unique<OverviewGridEventHandler>(this);
420 Shell::Get()->wallpaper_controller()->AddObserver(this);
421 }
422
PositionWindows(bool animate,const base::flat_set<OverviewItem * > & ignored_items,OverviewTransition transition)423 void OverviewGrid::PositionWindows(
424 bool animate,
425 const base::flat_set<OverviewItem*>& ignored_items,
426 OverviewTransition transition) {
427 if (!overview_session_ || suspend_reposition_ || window_list_.empty())
428 return;
429
430 DCHECK_NE(transition, OverviewTransition::kExit);
431
432 std::vector<gfx::RectF> rects =
433 ShouldUseTabletModeGridLayout() &&
434 (window_list_.size() - ignored_items.size() >=
435 kMinimumItemsForNewLayout)
436 ? GetWindowRectsForTabletModeLayout(ignored_items)
437 : GetWindowRects(ignored_items);
438
439 if (transition == OverviewTransition::kEnter) {
440 CalculateWindowListAnimationStates(/*selected_item=*/nullptr, transition,
441 rects);
442 }
443
444 // Position the windows centering the left-aligned rows vertically. Do not
445 // position items in |ignored_items|.
446 OverviewAnimationType animation_type = OVERVIEW_ANIMATION_NONE;
447 switch (transition) {
448 case OverviewTransition::kEnter: {
449 const bool entering_from_home =
450 overview_session_->enter_exit_overview_type() ==
451 OverviewEnterExitType::kFadeInEnter;
452 animation_type = entering_from_home
453 ? OVERVIEW_ANIMATION_ENTER_FROM_HOME_LAUNCHER
454 : OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_ON_ENTER;
455 break;
456 }
457 case OverviewTransition::kInOverview:
458 animation_type = OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW;
459 break;
460 case OverviewTransition::kExit:
461 NOTREACHED();
462 }
463
464 int animate_count = 0;
465 bool has_non_cover_animating = false;
466 std::vector<OverviewAnimationType> animation_types(rects.size());
467
468 const bool can_do_spawn_animation =
469 animate && transition == OverviewTransition::kInOverview;
470
471 for (size_t i = 0; i < window_list_.size(); ++i) {
472 OverviewItem* window_item = window_list_[i].get();
473 if (ShouldExcludeItemFromGridLayout(window_item, ignored_items)) {
474 rects[i].SetRect(0, 0, 0, 0);
475 continue;
476 }
477
478 // Calculate if each window item needs animation.
479 bool should_animate_item = animate;
480 // If we're in entering overview process, not all window items in the grid
481 // might need animation even if the grid needs animation.
482 if (animate && transition == OverviewTransition::kEnter)
483 should_animate_item = window_item->should_animate_when_entering();
484
485 if (animate && transition == OverviewTransition::kEnter) {
486 if (window_item->should_animate_when_entering() &&
487 !has_non_cover_animating) {
488 has_non_cover_animating |=
489 !CanCoverAvailableWorkspace(window_item->GetWindow());
490 ++animate_count;
491 }
492 }
493
494 if (can_do_spawn_animation && window_item->should_use_spawn_animation())
495 animation_type = OVERVIEW_ANIMATION_SPAWN_ITEM_IN_OVERVIEW;
496
497 animation_types[i] =
498 should_animate_item ? animation_type : OVERVIEW_ANIMATION_NONE;
499 }
500
501 if (animate && transition == OverviewTransition::kEnter &&
502 !window_list_.empty()) {
503 bool single_animation_in_clamshell =
504 animate_count == 1 && !has_non_cover_animating &&
505 !Shell::Get()->tablet_mode_controller()->InTabletMode();
506 bool minimized_in_tablet = overview_session_->enter_exit_overview_type() ==
507 OverviewEnterExitType::kFadeInEnter;
508 metrics_tracker_ = std::make_unique<OverviewEnterMetricsTracker>(
509 window_list_[0]->GetWindow()->layer()->GetCompositor(),
510 SplitViewController::Get(root_window_)->InSplitViewMode(),
511 single_animation_in_clamshell, minimized_in_tablet);
512 }
513
514 // Apply the animation after creating metrics_tracker_ so that unit test
515 // can correctly count the measure requests.
516 for (size_t i = 0; i < window_list_.size(); ++i) {
517 if (rects[i].IsEmpty())
518 continue;
519 OverviewItem* window_item = window_list_[i].get();
520 window_item->SetBounds(rects[i], animation_types[i]);
521 }
522 }
523
GetOverviewItemContaining(const aura::Window * window) const524 OverviewItem* OverviewGrid::GetOverviewItemContaining(
525 const aura::Window* window) const {
526 for (const auto& window_item : window_list_) {
527 if (window_item && window_item->Contains(window))
528 return window_item.get();
529 }
530 return nullptr;
531 }
532
AddItem(aura::Window * window,bool reposition,bool animate,const base::flat_set<OverviewItem * > & ignored_items,size_t index,bool use_spawn_animation,bool restack)533 void OverviewGrid::AddItem(aura::Window* window,
534 bool reposition,
535 bool animate,
536 const base::flat_set<OverviewItem*>& ignored_items,
537 size_t index,
538 bool use_spawn_animation,
539 bool restack) {
540 DCHECK(!GetOverviewItemContaining(window));
541 DCHECK_LE(index, window_list_.size());
542
543 window_list_.insert(
544 window_list_.begin() + index,
545 std::make_unique<OverviewItem>(window, overview_session_, this));
546
547 UpdateFrameThrottling();
548 auto* item = window_list_[index].get();
549 item->PrepareForOverview();
550
551 if (animate && use_spawn_animation && reposition) {
552 item->set_should_use_spawn_animation(true);
553 } else {
554 // The item is added after overview enter animation is complete, so
555 // just call OnStartingAnimationComplete() only if we won't animate it with
556 // with the spawn animation. Otherwise, OnStartingAnimationComplete() will
557 // be called when the spawn-item-animation completes (See
558 // OverviewItem::OnItemSpawnedAnimationCompleted()).
559 item->OnStartingAnimationComplete();
560 }
561
562 if (restack) {
563 if (reposition && animate)
564 item->set_should_restack_on_animation_end(true);
565 else
566 item->Restack();
567 }
568 if (reposition)
569 PositionWindows(animate, ignored_items);
570 }
571
AppendItem(aura::Window * window,bool reposition,bool animate,bool use_spawn_animation)572 void OverviewGrid::AppendItem(aura::Window* window,
573 bool reposition,
574 bool animate,
575 bool use_spawn_animation) {
576 AddItem(window, reposition, animate, /*ignored_items=*/{},
577 window_list_.size(), use_spawn_animation, /*restack=*/false);
578 }
579
AddItemInMruOrder(aura::Window * window,bool reposition,bool animate,bool restack)580 void OverviewGrid::AddItemInMruOrder(aura::Window* window,
581 bool reposition,
582 bool animate,
583 bool restack) {
584 AddItem(window, reposition, animate, /*ignored_items=*/{},
585 FindInsertionIndex(window), /*use_spawn_animation=*/false, restack);
586 }
587
RemoveItem(OverviewItem * overview_item,bool item_destroying,bool reposition)588 void OverviewGrid::RemoveItem(OverviewItem* overview_item,
589 bool item_destroying,
590 bool reposition) {
591 EndNudge();
592
593 // Use reverse iterator to be efficient when removing all.
594 auto iter = std::find_if(window_list_.rbegin(), window_list_.rend(),
595 base::MatchesUniquePtr(overview_item));
596 DCHECK(iter != window_list_.rend());
597
598 // This can also be called when shutting down |this|, at which the item will
599 // be cleaning up and its associated view may be nullptr. |overview_item|
600 // needs to still be in |window_list_| so we can compute what the deleted
601 // index is.
602 if (overview_session_ && (*iter)->overview_item_view()) {
603 overview_session_->highlight_controller()->OnViewDestroyingOrDisabling(
604 (*iter)->overview_item_view());
605 }
606
607 // Erase from the list first because deleting OverviewItem can lead to
608 // iterating through the |window_list_|.
609 std::unique_ptr<OverviewItem> tmp = std::move(*iter);
610 window_list_.erase(std::next(iter).base());
611 tmp.reset();
612
613 UpdateFrameThrottling();
614
615 if (!item_destroying)
616 return;
617
618 if (!overview_session_)
619 return;
620
621 if (empty()) {
622 overview_session_->OnGridEmpty();
623 return;
624 }
625
626 if (reposition) {
627 // Update the grid bounds if needed and reposition the windows minus the
628 // currently overview dragged window, if there is one. Note: this does not
629 // update the grid bounds if the window being dragged from the top or shelf,
630 // the former being handled in TabletModeWindowDragDelegate's destructor.
631 base::flat_set<OverviewItem*> ignored_items;
632 OverviewItem* dragged_item =
633 overview_session_->GetCurrentDraggedOverviewItem();
634 if (dragged_item)
635 ignored_items.insert(dragged_item);
636 const gfx::Rect grid_bounds = GetGridBoundsInScreen(
637 root_window_,
638 split_view_drag_indicators_
639 ? base::make_optional(
640 split_view_drag_indicators_->current_window_dragging_state())
641 : base::nullopt,
642 /*divider_changed=*/false,
643 /*account_for_hotseat=*/true);
644 SetBoundsAndUpdatePositions(grid_bounds, ignored_items, /*animate=*/true);
645 }
646 }
647
AddDropTargetForDraggingFromThisGrid(OverviewItem * dragged_item)648 void OverviewGrid::AddDropTargetForDraggingFromThisGrid(
649 OverviewItem* dragged_item) {
650 DCHECK(!drop_target_widget_);
651 drop_target_widget_ =
652 CreateDropTargetWidget(root_window_, dragged_item->GetWindow());
653 const size_t position = GetOverviewItemIndex(dragged_item) + 1u;
654 overview_session_->AddItem(drop_target_widget_->GetNativeWindow(),
655 /*reposition=*/true, /*animate=*/false,
656 /*ignored_items=*/{dragged_item}, position);
657 }
658
AddDropTargetNotForDraggingFromThisGrid(aura::Window * dragged_window,bool animate)659 void OverviewGrid::AddDropTargetNotForDraggingFromThisGrid(
660 aura::Window* dragged_window,
661 bool animate) {
662 DCHECK(!drop_target_widget_);
663 drop_target_widget_ = CreateDropTargetWidget(root_window_, dragged_window);
664 aura::Window* drop_target_window = drop_target_widget_->GetNativeWindow();
665 if (animate) {
666 drop_target_widget_->SetOpacity(0.f);
667 ScopedOverviewAnimationSettings settings(
668 OVERVIEW_ANIMATION_DROP_TARGET_FADE, drop_target_window);
669 drop_target_widget_->SetOpacity(1.f);
670 }
671 const size_t position = FindInsertionIndex(dragged_window);
672 overview_session_->AddItem(drop_target_window, /*reposition=*/true, animate,
673 /*ignored_items=*/{}, position);
674 }
675
RemoveDropTarget()676 void OverviewGrid::RemoveDropTarget() {
677 DCHECK(drop_target_widget_);
678 OverviewItem* drop_target = GetDropTarget();
679 overview_session_->RemoveItem(drop_target);
680 drop_target_widget_.reset();
681 }
682
SetBoundsAndUpdatePositions(const gfx::Rect & bounds_in_screen,const base::flat_set<OverviewItem * > & ignored_items,bool animate)683 void OverviewGrid::SetBoundsAndUpdatePositions(
684 const gfx::Rect& bounds_in_screen,
685 const base::flat_set<OverviewItem*>& ignored_items,
686 bool animate) {
687 bounds_ = bounds_in_screen;
688 MaybeUpdateDesksWidgetBounds();
689 PositionWindows(animate, ignored_items);
690 }
691
RearrangeDuringDrag(OverviewItem * dragged_item,SplitViewDragIndicators::WindowDraggingState window_dragging_state)692 void OverviewGrid::RearrangeDuringDrag(
693 OverviewItem* dragged_item,
694 SplitViewDragIndicators::WindowDraggingState window_dragging_state) {
695 OverviewItem* drop_target = GetDropTarget();
696
697 // Update the drop target visibility according to |window_dragging_state|.
698 if (drop_target) {
699 ScopedOverviewAnimationSettings settings(
700 OVERVIEW_ANIMATION_DROP_TARGET_FADE,
701 drop_target_widget_->GetNativeWindow());
702 drop_target->SetOpacity(GetWantedDropTargetOpacity(window_dragging_state));
703 }
704
705 // Update the grid's bounds.
706 const gfx::Rect wanted_grid_bounds = GetGridBoundsInScreen(
707 root_window_, base::make_optional(window_dragging_state),
708 /*divider_changed=*/false, /*account_for_hotseat=*/true);
709 if (bounds_ != wanted_grid_bounds) {
710 base::flat_set<OverviewItem*> ignored_items;
711 if (dragged_item)
712 ignored_items.insert(dragged_item);
713 SetBoundsAndUpdatePositions(wanted_grid_bounds, ignored_items,
714 /*animate=*/true);
715 }
716 }
717
SetSplitViewDragIndicatorsDraggedWindow(aura::Window * dragged_window)718 void OverviewGrid::SetSplitViewDragIndicatorsDraggedWindow(
719 aura::Window* dragged_window) {
720 DCHECK(split_view_drag_indicators_);
721 split_view_drag_indicators_->SetDraggedWindow(dragged_window);
722 }
723
SetSplitViewDragIndicatorsWindowDraggingState(SplitViewDragIndicators::WindowDraggingState window_dragging_state)724 void OverviewGrid::SetSplitViewDragIndicatorsWindowDraggingState(
725 SplitViewDragIndicators::WindowDraggingState window_dragging_state) {
726 DCHECK(split_view_drag_indicators_);
727 split_view_drag_indicators_->SetWindowDraggingState(window_dragging_state);
728 }
729
MaybeUpdateDesksWidgetBounds()730 bool OverviewGrid::MaybeUpdateDesksWidgetBounds() {
731 if (!desks_widget_)
732 return false;
733
734 const gfx::Rect desks_widget_bounds = GetDesksWidgetBounds();
735 if (desks_widget_bounds != desks_widget_->GetWindowBoundsInScreen()) {
736 // Note that the desks widget window is placed on the active desk container,
737 // which has the kUsesScreenCoordinatesKey property set to true, and hence
738 // we use the screen coordinates when positioning the desks widget.
739 //
740 // On certain display zooms, the requested |desks_widget_bounds| may differ
741 // than the current screen bounds of the desks widget by 1dp, but internally
742 // it will end up being the same and therefore a layout may not be
743 // triggered. This can cause mini views not to show up at all. We must
744 // guarantee that a layout will always occur by invalidating the layout.
745 // See https://crbug.com/1056371 for more details.
746 desks_bar_view_->InvalidateLayout();
747 desks_widget_->SetBounds(desks_widget_bounds);
748 return true;
749 }
750 return false;
751 }
752
UpdateDropTargetBackgroundVisibility(OverviewItem * dragged_item,const gfx::PointF & location_in_screen)753 void OverviewGrid::UpdateDropTargetBackgroundVisibility(
754 OverviewItem* dragged_item,
755 const gfx::PointF& location_in_screen) {
756 DCHECK(drop_target_widget_);
757 aura::Window* target_window =
758 GetTargetWindowOnLocation(location_in_screen, dragged_item);
759 DropTargetView* drop_target_view =
760 static_cast<DropTargetView*>(drop_target_widget_->GetContentsView());
761 DCHECK(drop_target_view);
762 drop_target_view->UpdateBackgroundVisibility(
763 target_window && IsDropTargetWindow(target_window));
764 }
765
OnSelectorItemDragStarted(OverviewItem * item)766 void OverviewGrid::OnSelectorItemDragStarted(OverviewItem* item) {
767 CommitDeskNameChanges();
768 for (auto& overview_mode_item : window_list_)
769 overview_mode_item->OnSelectorItemDragStarted(item);
770 }
771
OnSelectorItemDragEnded(bool snap)772 void OverviewGrid::OnSelectorItemDragEnded(bool snap) {
773 for (auto& overview_mode_item : window_list_)
774 overview_mode_item->OnSelectorItemDragEnded(snap);
775 }
776
OnWindowDragStarted(aura::Window * dragged_window,bool animate)777 void OverviewGrid::OnWindowDragStarted(aura::Window* dragged_window,
778 bool animate) {
779 dragged_window_ = dragged_window;
780 AddDropTargetNotForDraggingFromThisGrid(dragged_window, animate);
781 // Stack the |dragged_window| at top during drag.
782 dragged_window->parent()->StackChildAtTop(dragged_window);
783 // Called to set caption and title visibility during dragging.
784 OnSelectorItemDragStarted(/*item=*/nullptr);
785 }
786
OnWindowDragContinued(aura::Window * dragged_window,const gfx::PointF & location_in_screen,SplitViewDragIndicators::WindowDraggingState window_dragging_state)787 void OverviewGrid::OnWindowDragContinued(
788 aura::Window* dragged_window,
789 const gfx::PointF& location_in_screen,
790 SplitViewDragIndicators::WindowDraggingState window_dragging_state) {
791 DCHECK_EQ(dragged_window_, dragged_window);
792 DCHECK_EQ(dragged_window->GetRootWindow(), root_window_);
793
794 RearrangeDuringDrag(nullptr, window_dragging_state);
795 UpdateDropTargetBackgroundVisibility(nullptr, location_in_screen);
796
797 aura::Window* target_window =
798 GetTargetWindowOnLocation(location_in_screen, /*ignored_item=*/nullptr);
799
800 if (SplitViewDragIndicators::GetSnapPosition(window_dragging_state) !=
801 SplitViewController::NONE) {
802 // If the dragged window is currently dragged into preview window area,
803 // hide the highlight.
804 overview_session_->highlight_controller()->HideTabDragHighlight();
805
806 // Also clear ash::kIsDeferredTabDraggingTargetWindowKey key on the target
807 // overview item so that it can't merge into this overview item if the
808 // dragged window is currently in preview window area.
809 if (target_window && !IsDropTargetWindow(target_window))
810 target_window->ClearProperty(kIsDeferredTabDraggingTargetWindowKey);
811
812 return;
813 }
814
815 // Show the tab drag highlight if |location_in_screen| is contained by the
816 // browser windows' overview item in overview.
817 if (target_window &&
818 target_window->GetProperty(kIsDeferredTabDraggingTargetWindowKey)) {
819 auto* item = GetOverviewItemContaining(target_window);
820 if (!item)
821 return;
822
823 overview_session_->highlight_controller()->ShowTabDragHighlight(
824 item->overview_item_view());
825 return;
826 }
827
828 overview_session_->highlight_controller()->HideTabDragHighlight();
829 }
830
OnWindowDragEnded(aura::Window * dragged_window,const gfx::PointF & location_in_screen,bool should_drop_window_into_overview,bool snap)831 void OverviewGrid::OnWindowDragEnded(aura::Window* dragged_window,
832 const gfx::PointF& location_in_screen,
833 bool should_drop_window_into_overview,
834 bool snap) {
835 DCHECK_EQ(dragged_window_, dragged_window);
836 DCHECK_EQ(dragged_window->GetRootWindow(), root_window_);
837 DCHECK(drop_target_widget_.get());
838 dragged_window_ = nullptr;
839
840 // Add the dragged window into drop target in overview if
841 // |should_drop_window_into_overview| is true. Only consider add the dragged
842 // window into drop target if SelectedWindow is false since drop target will
843 // not be selected and tab dragging might drag a tab window to merge it into a
844 // browser window in overview.
845 if (overview_session_->highlight_controller()->IsTabDragHighlightVisible())
846 overview_session_->highlight_controller()->HideTabDragHighlight();
847 else if (should_drop_window_into_overview)
848 AddDraggedWindowIntoOverviewOnDragEnd(dragged_window);
849
850 RemoveDropTarget();
851
852 // Called to reset caption and title visibility after dragging.
853 OnSelectorItemDragEnded(snap);
854
855 // After drag ends, if the dragged window needs to merge into another window
856 // |target_window|, and we may need to update |minimized_widget_| that holds
857 // the contents of |target_window| if |target_window| is a minimized window
858 // in overview.
859 aura::Window* target_window =
860 GetTargetWindowOnLocation(location_in_screen, /*ignored_item=*/nullptr);
861 if (target_window &&
862 target_window->GetProperty(kIsDeferredTabDraggingTargetWindowKey)) {
863 // Create an window observer and update the minimized window widget after
864 // the dragged window merges into |target_window|.
865 if (!target_window_observer_)
866 target_window_observer_ = std::make_unique<TargetWindowObserver>();
867 target_window_observer_->StartObserving(target_window);
868 }
869
870 // Update the grid bounds and reposition windows. Since the grid bounds might
871 // be updated based on the preview area during drag, but the window finally
872 // didn't be snapped to the preview area.
873 SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_),
874 /*ignored_items=*/{},
875 /*animate=*/true);
876 }
877
SetVisibleDuringWindowDragging(bool visible,bool animate)878 void OverviewGrid::SetVisibleDuringWindowDragging(bool visible, bool animate) {
879 for (const auto& window_item : window_list_)
880 window_item->SetVisibleDuringWindowDragging(visible, animate);
881
882 // Update |desks_widget_|.
883 if (desks_widget_) {
884 ui::Layer* layer = desks_widget_->GetNativeWindow()->layer();
885 float new_opacity = visible ? 1.f : 0.f;
886 if (layer->GetTargetOpacity() == new_opacity)
887 return;
888
889 if (animate) {
890 ScopedOverviewAnimationSettings settings(
891 OVERVIEW_ANIMATION_OPACITY_ON_WINDOW_DRAG,
892 desks_widget_->GetNativeWindow());
893 layer->SetOpacity(new_opacity);
894 } else {
895 layer->SetOpacity(new_opacity);
896 }
897 }
898 }
899
IsDropTargetWindow(aura::Window * window) const900 bool OverviewGrid::IsDropTargetWindow(aura::Window* window) const {
901 return drop_target_widget_ &&
902 drop_target_widget_->GetNativeWindow() == window;
903 }
904
GetDropTarget()905 OverviewItem* OverviewGrid::GetDropTarget() {
906 return drop_target_widget_
907 ? GetOverviewItemContaining(drop_target_widget_->GetNativeWindow())
908 : nullptr;
909 }
910
OnDisplayMetricsChanged()911 void OverviewGrid::OnDisplayMetricsChanged() {
912 if (split_view_drag_indicators_)
913 split_view_drag_indicators_->OnDisplayBoundsChanged();
914
915 UpdateCannotSnapWarningVisibility();
916 // In case of split view mode, the grid bounds and item positions will be
917 // updated in |OnSplitViewDividerPositionChanged|.
918 if (SplitViewController::Get(root_window_)->InSplitViewMode())
919 return;
920 SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_),
921 /*ignored_items=*/{}, /*animate=*/false);
922 }
923
OnSplitViewStateChanged(SplitViewController::State previous_state,SplitViewController::State state)924 void OverviewGrid::OnSplitViewStateChanged(
925 SplitViewController::State previous_state,
926 SplitViewController::State state) {
927 // Do nothing if overview is being shutdown.
928 OverviewController* overview_controller = Shell::Get()->overview_controller();
929 if (!overview_controller->InOverviewSession())
930 return;
931
932 SplitViewController* split_view_controller =
933 SplitViewController::Get(root_window_);
934 const bool unsnappable_window_activated =
935 state == SplitViewController::State::kNoSnap &&
936 split_view_controller->end_reason() ==
937 SplitViewController::EndReason::kUnsnappableWindowActivated;
938
939 // Restore focus unless either a window was just snapped (and activated) or
940 // split view mode was ended by activating an unsnappable window.
941 if (state != SplitViewController::State::kNoSnap ||
942 unsnappable_window_activated) {
943 overview_session_->ResetFocusRestoreWindow(false);
944 }
945
946 // If two windows were snapped to both sides of the screen or an unsnappable
947 // window was just activated, or we're in single split mode in clamshell mode
948 // and there is no window in overview, end overview mode and bail out.
949 if (state == SplitViewController::State::kBothSnapped ||
950 unsnappable_window_activated ||
951 (split_view_controller->InClamshellSplitViewMode() &&
952 overview_session_->IsEmpty())) {
953 overview_controller->EndOverview();
954 return;
955 }
956
957 // Update the cannot snap warnings and adjust the grid bounds.
958 UpdateCannotSnapWarningVisibility();
959 SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_),
960 /*ignored_items=*/{}, /*animate=*/false);
961
962 // If split view mode was ended, then activate the overview focus window, to
963 // match the behavior of entering overview mode in the beginning.
964 if (state == SplitViewController::State::kNoSnap)
965 wm::ActivateWindow(overview_session_->GetOverviewFocusWindow());
966 }
967
OnSplitViewDividerPositionChanged()968 void OverviewGrid::OnSplitViewDividerPositionChanged() {
969 SetBoundsAndUpdatePositions(
970 GetGridBoundsInScreen(root_window_,
971 /*window_dragging_state=*/base::nullopt,
972 /*divider_changed=*/true,
973 /*account_for_hotseat=*/true),
974 /*ignored_items=*/{}, /*animate=*/false);
975 }
976
OnScreenCopiedBeforeRotation()977 void OverviewGrid::OnScreenCopiedBeforeRotation() {
978 Shell::Get()->overview_controller()->PauseOcclusionTracker();
979
980 for (auto& window : window_list()) {
981 window->set_disable_mask(true);
982 window->UpdateRoundedCornersAndShadow();
983 window->StopWidgetAnimation();
984 }
985 }
986
OnScreenRotationAnimationFinished(ScreenRotationAnimator * animator,bool canceled)987 void OverviewGrid::OnScreenRotationAnimationFinished(
988 ScreenRotationAnimator* animator,
989 bool canceled) {
990 for (auto& window : window_list())
991 window->set_disable_mask(false);
992 Shell::Get()->overview_controller()->DelayedUpdateRoundedCornersAndShadow();
993 Shell::Get()->overview_controller()->UnpauseOcclusionTracker(
994 kOcclusionUnpauseDurationForRotation);
995 }
996
OnWallpaperChanging()997 void OverviewGrid::OnWallpaperChanging() {
998 grid_event_handler_.reset();
999 }
1000
OnWallpaperChanged()1001 void OverviewGrid::OnWallpaperChanged() {
1002 grid_event_handler_ = std::make_unique<OverviewGridEventHandler>(this);
1003 }
1004
OnStartingAnimationComplete(bool canceled)1005 void OverviewGrid::OnStartingAnimationComplete(bool canceled) {
1006 metrics_tracker_.reset();
1007 if (canceled)
1008 return;
1009
1010 MaybeInitDesksWidget();
1011
1012 for (auto& window : window_list())
1013 window->OnStartingAnimationComplete();
1014
1015 }
1016
CalculateWindowListAnimationStates(OverviewItem * selected_item,OverviewTransition transition,const std::vector<gfx::RectF> & target_bounds)1017 void OverviewGrid::CalculateWindowListAnimationStates(
1018 OverviewItem* selected_item,
1019 OverviewTransition transition,
1020 const std::vector<gfx::RectF>& target_bounds) {
1021 using OverviewTransition = OverviewTransition;
1022
1023 // Sanity checks to enforce assumptions used in later codes.
1024 switch (transition) {
1025 case OverviewTransition::kEnter:
1026 DCHECK_EQ(target_bounds.size(), window_list_.size());
1027 break;
1028 case OverviewTransition::kExit:
1029 DCHECK(target_bounds.empty());
1030 break;
1031 default:
1032 NOTREACHED();
1033 }
1034
1035 // Create a copy of |window_list_| which has always on top windows in the
1036 // front.
1037 std::vector<OverviewItem*> items;
1038 std::transform(
1039 window_list_.begin(), window_list_.end(), std::back_inserter(items),
1040 [](const std::unique_ptr<OverviewItem>& item) -> OverviewItem* {
1041 return item.get();
1042 });
1043 // Sort items by:
1044 // 1) Selected items that are always on top windows.
1045 // 2) Other always on top windows.
1046 // 3) Selected items that are not always on top windows.
1047 // 4) Other not always on top windows.
1048 // Preserves ordering if the category is the same.
1049 std::sort(items.begin(), items.end(),
1050 [&selected_item](OverviewItem* a, OverviewItem* b) {
1051 // NB: This treats all non-normal z-ordered windows the same. If
1052 // Aura ever adopts z-order levels, this will need to be changed.
1053 const bool a_on_top =
1054 a->GetWindow()->GetProperty(aura::client::kZOrderingKey) !=
1055 ui::ZOrderLevel::kNormal;
1056 const bool b_on_top =
1057 b->GetWindow()->GetProperty(aura::client::kZOrderingKey) !=
1058 ui::ZOrderLevel::kNormal;
1059 if (selected_item && a_on_top && b_on_top)
1060 return a == selected_item;
1061 if (a_on_top)
1062 return true;
1063 if (b_on_top)
1064 return false;
1065 if (selected_item)
1066 return a == selected_item;
1067 return false;
1068 });
1069
1070 SkRegion occluded_region;
1071 auto* split_view_controller = SplitViewController::Get(root_window_);
1072 if (split_view_controller->InSplitViewMode()) {
1073 // Snapped windows and the split view divider are not included in
1074 // |target_bounds| or |window_list_|, but can occlude other windows, so add
1075 // them manually to |region| here.
1076 SkIRect snapped_window_bounds = gfx::RectToSkIRect(
1077 split_view_controller->GetDefaultSnappedWindow()->GetBoundsInScreen());
1078 occluded_region.op(snapped_window_bounds, SkRegion::kUnion_Op);
1079
1080 auto* divider = split_view_controller->split_view_divider();
1081 if (divider) {
1082 aura::Window* divider_window =
1083 divider->divider_widget()->GetNativeWindow();
1084 SkIRect divider_bounds =
1085 gfx::RectToSkIRect(divider_window->GetBoundsInScreen());
1086 occluded_region.op(divider_bounds, SkRegion::kUnion_Op);
1087 }
1088 }
1089
1090 gfx::Rect screen_bounds = GetGridEffectiveBounds();
1091 for (size_t i = 0; i < items.size(); ++i) {
1092 const bool minimized =
1093 WindowState::Get(items[i]->GetWindow())->IsMinimized();
1094 bool src_occluded = minimized;
1095 bool dst_occluded = false;
1096 gfx::Rect src_bounds_temp =
1097 minimized ? gfx::Rect()
1098 : items[i]->GetWindow()->GetBoundsInRootWindow();
1099 if (!src_bounds_temp.IsEmpty()) {
1100 if (transition == OverviewTransition::kEnter &&
1101 Shell::Get()->tablet_mode_controller()->InTabletMode()) {
1102 BackdropController* backdrop_controller =
1103 GetActiveWorkspaceController(root_window_)
1104 ->layout_manager()
1105 ->backdrop_controller();
1106 if (backdrop_controller->GetTopmostWindowWithBackdrop() ==
1107 items[i]->GetWindow()) {
1108 src_bounds_temp = screen_util::GetDisplayWorkAreaBoundsInParent(
1109 items[i]->GetWindow());
1110 }
1111 } else if (transition == OverviewTransition::kExit) {
1112 // On exiting overview, |GetBoundsInRootWindow()| will have the overview
1113 // translation applied to it, so use |bounds()| and
1114 // |ConvertRectToScreen()| to get the true target bounds.
1115 src_bounds_temp = items[i]->GetWindow()->bounds();
1116 ::wm::ConvertRectToScreen(items[i]->root_window(), &src_bounds_temp);
1117 }
1118 }
1119
1120 // The bounds of of the destination may be partially or fully offscreen.
1121 // Partially offscreen rects should be clipped so the onscreen portion is
1122 // treated normally. Fully offscreen rects (intersection with the screen
1123 // bounds is empty) should never be animated.
1124 gfx::Rect dst_bounds_temp = gfx::ToEnclosedRect(
1125 transition == OverviewTransition::kEnter ? target_bounds[i]
1126 : items[i]->target_bounds());
1127 dst_bounds_temp.Intersect(screen_bounds);
1128 if (dst_bounds_temp.IsEmpty()) {
1129 items[i]->set_should_animate_when_entering(false);
1130 items[i]->set_should_animate_when_exiting(false);
1131 continue;
1132 }
1133
1134 SkIRect src_bounds = gfx::RectToSkIRect(src_bounds_temp);
1135 SkIRect dst_bounds = gfx::RectToSkIRect(dst_bounds_temp);
1136 if (!occluded_region.isEmpty()) {
1137 src_occluded |=
1138 (!src_bounds.isEmpty() && occluded_region.contains(src_bounds));
1139 dst_occluded |= occluded_region.contains(dst_bounds);
1140 }
1141
1142 // Add |src_bounds| to our region if it is not empty (minimized window).
1143 if (!src_bounds.isEmpty())
1144 occluded_region.op(src_bounds, SkRegion::kUnion_Op);
1145
1146 const bool should_animate = !(src_occluded && dst_occluded);
1147 if (transition == OverviewTransition::kEnter)
1148 items[i]->set_should_animate_when_entering(should_animate);
1149 else if (transition == OverviewTransition::kExit)
1150 items[i]->set_should_animate_when_exiting(should_animate);
1151 }
1152 }
1153
SetWindowListNotAnimatedWhenExiting()1154 void OverviewGrid::SetWindowListNotAnimatedWhenExiting() {
1155 should_animate_when_exiting_ = false;
1156 for (const auto& item : window_list_)
1157 item->set_should_animate_when_exiting(false);
1158 }
1159
StartNudge(OverviewItem * item)1160 void OverviewGrid::StartNudge(OverviewItem* item) {
1161 // When there is one window left, there is no need to nudge.
1162 if (window_list_.size() <= 1) {
1163 nudge_data_.clear();
1164 return;
1165 }
1166
1167 // If any of the items are being animated to close, do not nudge any windows
1168 // otherwise we have to deal with potential items getting removed from
1169 // |window_list_| midway through a nudge.
1170 for (const auto& window_item : window_list_) {
1171 if (window_item->animating_to_close()) {
1172 nudge_data_.clear();
1173 return;
1174 }
1175 }
1176
1177 DCHECK(item);
1178
1179 // Get the bounds of the windows currently, and the bounds if |item| were to
1180 // be removed.
1181 std::vector<gfx::RectF> src_rects;
1182 for (const auto& window_item : window_list_)
1183 src_rects.push_back(window_item->target_bounds());
1184
1185 std::vector<gfx::RectF> dst_rects = GetWindowRects({item});
1186
1187 const size_t index = GetOverviewItemIndex(item);
1188
1189 // Returns a vector of integers indicating which row the item is in. |index|
1190 // is the index of the element which is going to be deleted and should not
1191 // factor into calculations. The call site should mark |index| as -1 if it
1192 // should not be used. The item at |index| is marked with a 0. The heights of
1193 // items are all set to the same value so a new row is determined if the y
1194 // value has changed from the previous item.
1195 auto get_rows = [](const std::vector<gfx::RectF>& bounds_list, size_t index) {
1196 std::vector<int> row_numbers;
1197 int current_row = 1;
1198 float last_y = 0;
1199 for (size_t i = 0; i < bounds_list.size(); ++i) {
1200 if (i == index) {
1201 row_numbers.push_back(0);
1202 continue;
1203 }
1204
1205 // Update |current_row| if the y position has changed (heights are all
1206 // equal in overview, so a new y position indicates a new row).
1207 if (last_y != 0 && last_y != bounds_list[i].y())
1208 ++current_row;
1209
1210 row_numbers.push_back(current_row);
1211 last_y = bounds_list[i].y();
1212 }
1213
1214 return row_numbers;
1215 };
1216
1217 std::vector<int> src_rows = get_rows(src_rects, -1);
1218 std::vector<int> dst_rows = get_rows(dst_rects, index);
1219
1220 // Do nothing if the number of rows change.
1221 if (dst_rows.back() != 0 && src_rows.back() != dst_rows.back())
1222 return;
1223 size_t second_last_index = src_rows.size() - 2;
1224 if (dst_rows.back() == 0 &&
1225 src_rows[second_last_index] != dst_rows[second_last_index]) {
1226 return;
1227 }
1228
1229 // Do nothing if the last item from the previous row will drop onto the
1230 // current row, this will cause the items in the current row to shift to the
1231 // right while the previous item stays in the previous row, which looks weird.
1232 if (src_rows[index] > 1) {
1233 // Find the last item from the previous row.
1234 size_t previous_row_last_index = index;
1235 while (src_rows[previous_row_last_index] == src_rows[index]) {
1236 --previous_row_last_index;
1237 }
1238
1239 // Early return if the last item in the previous row changes rows.
1240 if (src_rows[previous_row_last_index] != dst_rows[previous_row_last_index])
1241 return;
1242 }
1243
1244 // Helper to check whether the item at |item_index| will be nudged.
1245 auto should_nudge = [&src_rows, &dst_rows, &index](size_t item_index) {
1246 // Out of bounds.
1247 if (item_index >= src_rows.size())
1248 return false;
1249
1250 // Nudging happens when the item stays on the same row and is also on the
1251 // same row as the item to be deleted was.
1252 if (dst_rows[item_index] == src_rows[index] &&
1253 dst_rows[item_index] == src_rows[item_index]) {
1254 return true;
1255 }
1256
1257 return false;
1258 };
1259
1260 // Starting from |index| go up and down while the nudge condition returns
1261 // true.
1262 std::vector<int> affected_indexes;
1263 size_t loop_index;
1264
1265 if (index > 0) {
1266 loop_index = index - 1;
1267 while (should_nudge(loop_index)) {
1268 affected_indexes.push_back(loop_index);
1269 --loop_index;
1270 }
1271 }
1272
1273 loop_index = index + 1;
1274 while (should_nudge(loop_index)) {
1275 affected_indexes.push_back(loop_index);
1276 ++loop_index;
1277 }
1278
1279 // Populate |nudge_data_| with the indexes in |affected_indexes| and their
1280 // respective source and destination bounds.
1281 nudge_data_.resize(affected_indexes.size());
1282 for (size_t i = 0; i < affected_indexes.size(); ++i) {
1283 NudgeData data;
1284 data.index = affected_indexes[i];
1285 data.src = src_rects[data.index];
1286 data.dst = dst_rects[data.index];
1287 nudge_data_[i] = data;
1288 }
1289 }
1290
UpdateNudge(OverviewItem * item,double value)1291 void OverviewGrid::UpdateNudge(OverviewItem* item, double value) {
1292 for (const auto& data : nudge_data_) {
1293 DCHECK_LT(data.index, window_list_.size());
1294
1295 OverviewItem* nudged_item = window_list_[data.index].get();
1296 double nudge_param = value * value / 30.0;
1297 nudge_param = base::ClampToRange(nudge_param, 0.0, 1.0);
1298 gfx::RectF bounds =
1299 gfx::Tween::RectFValueBetween(nudge_param, data.src, data.dst);
1300 nudged_item->SetBounds(bounds, OVERVIEW_ANIMATION_NONE);
1301 }
1302 }
1303
EndNudge()1304 void OverviewGrid::EndNudge() {
1305 nudge_data_.clear();
1306 }
1307
GetTargetWindowOnLocation(const gfx::PointF & location_in_screen,OverviewItem * ignored_item)1308 aura::Window* OverviewGrid::GetTargetWindowOnLocation(
1309 const gfx::PointF& location_in_screen,
1310 OverviewItem* ignored_item) {
1311 for (std::unique_ptr<OverviewItem>& item : window_list_) {
1312 if (item.get() == ignored_item)
1313 continue;
1314 if (item->target_bounds().Contains(location_in_screen))
1315 return item->GetWindow();
1316 }
1317 return nullptr;
1318 }
1319
IsDesksBarViewActive() const1320 bool OverviewGrid::IsDesksBarViewActive() const {
1321 DCHECK(desks_util::ShouldDesksBarBeCreated());
1322
1323 // The desk bar view is not active if there is only a single desk when
1324 // overview is started. Once there are more than one desk, it should stay
1325 // active even if the 2nd to last desk is deleted.
1326 return DesksController::Get()->desks().size() > 1 ||
1327 (desks_bar_view_ && !desks_bar_view_->mini_views().empty());
1328 }
1329
GetGridEffectiveBounds() const1330 gfx::Rect OverviewGrid::GetGridEffectiveBounds() const {
1331 if (!desks_util::ShouldDesksBarBeCreated() || !IsDesksBarViewActive())
1332 return bounds_;
1333
1334 gfx::Rect effective_bounds = bounds_;
1335 effective_bounds.Inset(0,
1336 DesksBarView::GetBarHeightForWidth(
1337 root_window_, desks_bar_view_, bounds_.width()),
1338 0, 0);
1339 return effective_bounds;
1340 }
1341
IntersectsWithDesksBar(const gfx::Point & screen_location,bool update_desks_bar_drag_details,bool for_drop)1342 bool OverviewGrid::IntersectsWithDesksBar(const gfx::Point& screen_location,
1343 bool update_desks_bar_drag_details,
1344 bool for_drop) {
1345 DCHECK(desks_util::ShouldDesksBarBeCreated());
1346
1347 const bool dragged_item_over_bar =
1348 desks_widget_->GetWindowBoundsInScreen().Contains(screen_location);
1349 if (update_desks_bar_drag_details) {
1350 desks_bar_view_->SetDragDetails(screen_location,
1351 !for_drop && dragged_item_over_bar);
1352 }
1353 return dragged_item_over_bar;
1354 }
1355
MaybeDropItemOnDeskMiniView(const gfx::Point & screen_location,OverviewItem * drag_item)1356 bool OverviewGrid::MaybeDropItemOnDeskMiniView(
1357 const gfx::Point& screen_location,
1358 OverviewItem* drag_item) {
1359 DCHECK(desks_util::ShouldDesksBarBeCreated());
1360
1361 // End the drag for the DesksBarView.
1362 if (!IntersectsWithDesksBar(screen_location,
1363 /*update_desks_bar_drag_details=*/true,
1364 /*for_drop=*/true)) {
1365 return false;
1366 }
1367
1368 auto* desks_controller = DesksController::Get();
1369 for (auto* mini_view : desks_bar_view_->mini_views()) {
1370 if (!mini_view->IsPointOnMiniView(screen_location))
1371 continue;
1372
1373 aura::Window* const dragged_window = drag_item->GetWindow();
1374 Desk* const target_desk = mini_view->desk();
1375 if (target_desk == desks_controller->active_desk())
1376 return false;
1377
1378 return desks_controller->MoveWindowFromActiveDeskTo(
1379 dragged_window, target_desk, root_window_,
1380 DesksMoveWindowFromActiveDeskSource::kDragAndDrop);
1381 }
1382
1383 return false;
1384 }
1385
StartScroll()1386 void OverviewGrid::StartScroll() {
1387 Shell::Get()->overview_controller()->PauseOcclusionTracker();
1388
1389 // Users are not allowed to scroll past the leftmost or rightmost bounds of
1390 // the items on screen in the grid. |scroll_offset_min_| is the amount needed
1391 // to fit the rightmost window into |total_bounds|. The max is zero which is
1392 // default because windows are aligned to the left from the beginning.
1393 gfx::Rect total_bounds = GetGridEffectiveBounds();
1394 total_bounds.Inset(GetGridInsets(total_bounds));
1395
1396 float rightmost_window_right = 0;
1397 items_scrolling_bounds_.resize(window_list_.size());
1398 for (size_t i = 0; i < items_scrolling_bounds_.size(); ++i) {
1399 const gfx::RectF bounds = window_list_[i]->target_bounds();
1400 if (rightmost_window_right < bounds.right())
1401 rightmost_window_right = bounds.right();
1402
1403 items_scrolling_bounds_[i] = bounds;
1404 }
1405
1406 // |rightmost_window_right| may have been modified by an earlier scroll.
1407 // |scroll_offset_| is added to adjust for that.
1408 rightmost_window_right -= scroll_offset_;
1409 scroll_offset_min_ = total_bounds.right() - rightmost_window_right;
1410
1411 presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
1412 const_cast<ui::Compositor*>(root_window()->layer()->GetCompositor()),
1413 kOverviewScrollHistogram, kOverviewScrollMaxLatencyHistogram);
1414 }
1415
UpdateScrollOffset(float delta)1416 bool OverviewGrid::UpdateScrollOffset(float delta) {
1417 float new_scroll_offset = scroll_offset_;
1418 new_scroll_offset += delta;
1419 new_scroll_offset =
1420 base::ClampToRange(new_scroll_offset, scroll_offset_min_, 0.f);
1421
1422 // For flings, we want to return false if we hit one of the edges, which is
1423 // when |new_scroll_offset| is exactly 0.f or |scroll_offset_min_|.
1424 const bool in_range =
1425 new_scroll_offset < 0.f && new_scroll_offset > scroll_offset_min_;
1426 if (new_scroll_offset == scroll_offset_)
1427 return in_range;
1428
1429 // Update the bounds of the items which are currently visible on screen.
1430 DCHECK_EQ(items_scrolling_bounds_.size(), window_list_.size());
1431 for (size_t i = 0; i < items_scrolling_bounds_.size(); ++i) {
1432 const gfx::RectF previous_bounds = items_scrolling_bounds_[i];
1433 items_scrolling_bounds_[i].Offset(new_scroll_offset - scroll_offset_, 0.f);
1434 const gfx::RectF new_bounds = items_scrolling_bounds_[i];
1435 if (gfx::RectF(GetGridEffectiveBounds()).Intersects(new_bounds) ||
1436 gfx::RectF(GetGridEffectiveBounds()).Intersects(previous_bounds)) {
1437 window_list_[i]->SetBounds(new_bounds, OVERVIEW_ANIMATION_NONE);
1438 }
1439 }
1440
1441 scroll_offset_ = new_scroll_offset;
1442
1443 DCHECK(presentation_time_recorder_);
1444 presentation_time_recorder_->RequestNext();
1445 return in_range;
1446 }
1447
EndScroll()1448 void OverviewGrid::EndScroll() {
1449 Shell::Get()->overview_controller()->UnpauseOcclusionTracker(
1450 kOcclusionUnpauseDurationForScroll);
1451 items_scrolling_bounds_.clear();
1452 presentation_time_recorder_.reset();
1453
1454 if (!overview_session_->is_shutting_down())
1455 PositionWindows(/*animate=*/false);
1456 }
1457
CalculateWidthAndMaybeSetUnclippedBounds(OverviewItem * item,int height)1458 int OverviewGrid::CalculateWidthAndMaybeSetUnclippedBounds(OverviewItem* item,
1459 int height) {
1460 const gfx::Size item_size(0, height);
1461 gfx::SizeF target_size = item->GetTargetBoundsInScreen().size();
1462 float scale = item->GetItemScale(item_size);
1463 OverviewGridWindowFillMode grid_fill_mode = item->GetWindowDimensionsType();
1464
1465 // The drop target, unlike the other windows has its bounds set directly, so
1466 // |GetTargetBoundsInScreen()| won't return the value we want. Instead, get
1467 // the scale from the window it was meant to be a placeholder for.
1468 if (IsDropTargetWindow(item->GetWindow())) {
1469 aura::Window* dragged_window = nullptr;
1470 OverviewItem* grid_dragged_item =
1471 overview_session_->window_drag_controller()
1472 ? overview_session_->window_drag_controller()->item()
1473 : nullptr;
1474 if (grid_dragged_item)
1475 dragged_window = grid_dragged_item->GetWindow();
1476 else if (dragged_window_)
1477 dragged_window = dragged_window_;
1478 if (dragged_window && dragged_window->parent()) {
1479 const gfx::Size work_area_size =
1480 screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
1481 root_window_)
1482 .size();
1483 if (WindowState::Get(dragged_window)->IsMaximized()) {
1484 grid_fill_mode = ScopedOverviewTransformWindow::GetWindowDimensionsType(
1485 work_area_size);
1486 target_size = gfx::SizeF(work_area_size);
1487 } else {
1488 gfx::Size dragged_window_size = dragged_window->bounds().size();
1489 // If the drag started from a different root window, |dragged_window|
1490 // may not fit into the work area of |root_window_|. Then if
1491 // |dragged_window| is dropped into this grid, |dragged_window| will
1492 // shrink to fit into this work area. The drop target shall reflect
1493 // that.
1494 dragged_window_size.SetToMin(work_area_size);
1495 grid_fill_mode = ScopedOverviewTransformWindow::GetWindowDimensionsType(
1496 dragged_window_size);
1497 target_size = ::ash::GetTargetBoundsInScreen(dragged_window).size();
1498 target_size.SetToMin(gfx::SizeF(work_area_size));
1499 }
1500 const gfx::SizeF inset_size(0, height);
1501 scale = ScopedOverviewTransformWindow::GetItemScale(
1502 target_size, inset_size,
1503 dragged_window->GetProperty(aura::client::kTopViewInset),
1504 kHeaderHeightDp);
1505 }
1506 }
1507
1508 int width = std::max(1, base::ClampFloor(target_size.width() * scale));
1509 switch (grid_fill_mode) {
1510 case OverviewGridWindowFillMode::kLetterBoxed:
1511 width = kExtremeWindowRatioThreshold * height;
1512 break;
1513 case OverviewGridWindowFillMode::kPillarBoxed:
1514 width = height / kExtremeWindowRatioThreshold;
1515 break;
1516 default:
1517 break;
1518 }
1519
1520 // Get the bounds of the window if there is a snapped window or a window
1521 // about to be snapped.
1522 base::Optional<gfx::RectF> split_view_bounds =
1523 GetSplitviewBoundsMaintainingAspectRatio();
1524 if (!split_view_bounds) {
1525 item->set_unclipped_size(base::nullopt);
1526 return width;
1527 }
1528
1529 // Perform horizontal clipping if the window's aspect ratio is wider than the
1530 // split view bounds aspect ratio, and vertical clipping otherwise.
1531 const float aspect_ratio =
1532 target_size.width() /
1533 (target_size.height() -
1534 item->GetWindow()->GetProperty(aura::client::kTopViewInset));
1535 const float target_aspect_ratio =
1536 split_view_bounds->width() / split_view_bounds->height();
1537 const bool clip_horizontally = aspect_ratio > target_aspect_ratio;
1538 const int window_height = height - kHeaderHeightDp;
1539 gfx::Size unclipped_size;
1540 if (clip_horizontally) {
1541 unclipped_size.set_width(width);
1542 unclipped_size.set_height(height);
1543 // For horizontal clipping, shrink |width| so that the aspect ratio matches
1544 // that of |split_view_bounds|.
1545 width = std::max(1, base::ClampFloor(target_aspect_ratio * window_height));
1546 } else {
1547 // For vertical clipping, we want |height| to stay the same, so calculate
1548 // what the unclipped height would be based on |split_view_bounds|.
1549
1550 // Find the width so that it matches height and matches the aspect ratio of
1551 // |split_view_bounds|.
1552 width = split_view_bounds->width() * window_height /
1553 split_view_bounds->height();
1554 // The unclipped height is the height which matches |width| but keeps the
1555 // aspect ratio of |target_bounds|. Clipping takes the overview header into
1556 // account, so add that back in.
1557 const int unclipped_height =
1558 width * target_size.height() / target_size.width();
1559 unclipped_size.set_width(width);
1560 unclipped_size.set_height(unclipped_height + kHeaderHeightDp);
1561 }
1562
1563 DCHECK(!unclipped_size.IsEmpty());
1564 item->set_unclipped_size(base::make_optional(unclipped_size));
1565 return width;
1566 }
1567
OnDesksChanged()1568 void OverviewGrid::OnDesksChanged() {
1569 if (MaybeUpdateDesksWidgetBounds())
1570 PositionWindows(/*animate=*/false, /*ignored_items=*/{});
1571 else
1572 desks_bar_view_->Layout();
1573 }
1574
IsDeskNameBeingModified() const1575 bool OverviewGrid::IsDeskNameBeingModified() const {
1576 return desks_bar_view_ && desks_bar_view_->IsDeskNameBeingModified();
1577 }
1578
CommitDeskNameChanges()1579 void OverviewGrid::CommitDeskNameChanges() {
1580 // The desks bar widget may not be ready, since it is created asynchronously
1581 // later when the entering overview animations finish.
1582 if (desks_widget_)
1583 DeskNameView::CommitChanges(desks_widget_.get());
1584 }
1585
MaybeInitDesksWidget()1586 void OverviewGrid::MaybeInitDesksWidget() {
1587 if (!desks_util::ShouldDesksBarBeCreated() || desks_widget_)
1588 return;
1589
1590 desks_widget_ =
1591 DesksBarView::CreateDesksWidget(root_window_, GetDesksWidgetBounds());
1592
1593 // The following order of function calls is significant: SetContentsView()
1594 // must be called before DesksBarView:: Init(). This is needed because the
1595 // desks mini views need to access the widget to get the root window in order
1596 // to know how to layout themselves.
1597 desks_bar_view_ =
1598 desks_widget_->SetContentsView(std::make_unique<DesksBarView>(this));
1599 desks_bar_view_->Init();
1600
1601 desks_widget_->Show();
1602
1603 // TODO(afakhry): Check if we need to keep this as the bottom-most window in
1604 // the container.
1605 auto* window = desks_widget_->GetNativeWindow();
1606 window->parent()->StackChildAtBottom(window);
1607 }
1608
GetWindowRects(const base::flat_set<OverviewItem * > & ignored_items)1609 std::vector<gfx::RectF> OverviewGrid::GetWindowRects(
1610 const base::flat_set<OverviewItem*>& ignored_items) {
1611 gfx::Rect total_bounds = GetGridEffectiveBounds();
1612
1613 // Windows occupy vertically centered area with additional vertical insets.
1614 total_bounds.Inset(GetGridInsets(total_bounds));
1615 std::vector<gfx::RectF> rects;
1616
1617 // Keep track of the lowest coordinate.
1618 int max_bottom = total_bounds.y();
1619
1620 // Right bound of the narrowest row.
1621 int min_right = total_bounds.right();
1622 // Right bound of the widest row.
1623 int max_right = total_bounds.x();
1624
1625 // Keep track of the difference between the narrowest and the widest row.
1626 // Initially this is set to the worst it can ever be assuming the windows fit.
1627 int width_diff = total_bounds.width();
1628
1629 // Initially allow the windows to occupy all available width. Shrink this
1630 // available space horizontally to find the breakdown into rows that achieves
1631 // the minimal |width_diff|.
1632 int right_bound = total_bounds.right();
1633
1634 // Determine the optimal height bisecting between |low_height| and
1635 // |high_height|. Once this optimal height is known, |height_fixed| is set to
1636 // true and the rows are balanced by repeatedly squeezing the widest row to
1637 // cause windows to overflow to the subsequent rows.
1638 int low_height = 2 * kWindowMargin;
1639 int high_height = std::max(low_height, total_bounds.height() + 1);
1640 int height = 0.5 * (low_height + high_height);
1641 bool height_fixed = false;
1642
1643 // Repeatedly try to fit the windows |rects| within |right_bound|.
1644 // If a maximum |height| is found such that all window |rects| fit, this
1645 // fitting continues while shrinking the |right_bound| in order to balance the
1646 // rows. If the windows fit the |right_bound| would have been decremented at
1647 // least once so it needs to be incremented once before getting out of this
1648 // loop and one additional pass made to actually fit the |rects|.
1649 // If the |rects| cannot fit (e.g. there are too many windows) the bisection
1650 // will still finish and we might increment the |right_bound| once pixel extra
1651 // which is acceptable since there is an unused margin on the right.
1652 bool make_last_adjustment = false;
1653 while (true) {
1654 gfx::Rect overview_mode_bounds(total_bounds);
1655 overview_mode_bounds.set_width(right_bound - total_bounds.x());
1656 bool windows_fit = FitWindowRectsInBounds(
1657 overview_mode_bounds, std::min(kMaxHeight, height), ignored_items,
1658 &rects, &max_bottom, &min_right, &max_right);
1659
1660 if (height_fixed) {
1661 if (!windows_fit) {
1662 // Revert the previous change to |right_bound| and do one last pass.
1663 right_bound++;
1664 make_last_adjustment = true;
1665 break;
1666 }
1667 // Break if all the windows are zero-width at the current scale.
1668 if (max_right <= total_bounds.x())
1669 break;
1670 } else {
1671 // Find the optimal row height bisecting between |low_height| and
1672 // |high_height|.
1673 if (windows_fit)
1674 low_height = height;
1675 else
1676 high_height = height;
1677 height = 0.5 * (low_height + high_height);
1678 // When height can no longer be improved, start balancing the rows.
1679 if (height == low_height)
1680 height_fixed = true;
1681 }
1682
1683 if (windows_fit && height_fixed) {
1684 if (max_right - min_right <= width_diff) {
1685 // Row alignment is getting better. Try to shrink the |right_bound| in
1686 // order to squeeze the widest row.
1687 right_bound = max_right - 1;
1688 width_diff = max_right - min_right;
1689 } else {
1690 // Row alignment is getting worse.
1691 // Revert the previous change to |right_bound| and do one last pass.
1692 right_bound++;
1693 make_last_adjustment = true;
1694 break;
1695 }
1696 }
1697 }
1698 // Once the windows in |window_list_| no longer fit, the change to
1699 // |right_bound| was reverted. Perform one last pass to position the |rects|.
1700 if (make_last_adjustment) {
1701 gfx::Rect overview_mode_bounds(total_bounds);
1702 overview_mode_bounds.set_width(right_bound - total_bounds.x());
1703 FitWindowRectsInBounds(overview_mode_bounds, std::min(kMaxHeight, height),
1704 ignored_items, &rects, &max_bottom, &min_right,
1705 &max_right);
1706 }
1707
1708 gfx::Vector2dF offset(0, (total_bounds.bottom() - max_bottom) / 2.f);
1709 for (size_t i = 0; i < rects.size(); ++i)
1710 rects[i] += offset;
1711 return rects;
1712 }
1713
GetWindowRectsForTabletModeLayout(const base::flat_set<OverviewItem * > & ignored_items)1714 std::vector<gfx::RectF> OverviewGrid::GetWindowRectsForTabletModeLayout(
1715 const base::flat_set<OverviewItem*>& ignored_items) {
1716 gfx::Rect total_bounds = GetGridEffectiveBounds();
1717 // Windows occupy vertically centered area with additional vertical insets.
1718 total_bounds.Inset(GetGridInsets(total_bounds));
1719
1720 // |scroll_offset_min_| may be changed on positioning (either by closing
1721 // windows or display changes). Recalculate it and clamp |scroll_offset_|, so
1722 // that the items are always aligned left or right.
1723 float rightmost_window_right = 0;
1724 for (const auto& item : window_list_) {
1725 if (ShouldExcludeItemFromGridLayout(item.get(), ignored_items))
1726 continue;
1727 rightmost_window_right =
1728 std::max(rightmost_window_right, item->target_bounds().right());
1729 }
1730
1731 // |rightmost_window_right| may have been modified by an earlier scroll.
1732 // |scroll_offset_| is added to adjust for that.
1733 rightmost_window_right -= scroll_offset_;
1734 scroll_offset_min_ = total_bounds.right() - rightmost_window_right;
1735 scroll_offset_ = base::ClampToRange(scroll_offset_, scroll_offset_min_, 0.f);
1736
1737 // Map which contains up to |kTabletLayoutRow| entries with information on the
1738 // last items right bound per row. Used so we can place the next item directly
1739 // next to the last item. The key is the y-value of the row, and the value is
1740 // the rightmost x-value.
1741 base::flat_map<float, float> right_edge_map;
1742
1743 // Since the number of rows is limited, windows are laid out column-wise so
1744 // that the most recently used windows are displayed first. When the dragged
1745 // item becomes an |ignored_item|, move the other windows accordingly.
1746 // |window_position| matches the positions of the windows' indexes from
1747 // |window_list_|. However, if a window turns out to be an ignored item,
1748 // |window_position| remains where the item was as to then reposition the
1749 // other window's bounds in place of that item.
1750 const int height = total_bounds.height() / kTabletLayoutRow;
1751 int window_position = 0;
1752 std::vector<gfx::RectF> rects;
1753 for (size_t i = 0; i < window_list_.size(); ++i) {
1754 OverviewItem* item = window_list_[i].get();
1755 if (ShouldExcludeItemFromGridLayout(item, ignored_items)) {
1756 rects.push_back(gfx::RectF());
1757 continue;
1758 }
1759
1760 // Calculate the width and y position of the item.
1761 const int width =
1762 CalculateWidthAndMaybeSetUnclippedBounds(window_list_[i].get(), height);
1763 const int y =
1764 height * (window_position % kTabletLayoutRow) + total_bounds.y();
1765
1766 // Use the right bounds of the item next to in the row as the x position, if
1767 // that item exists.
1768 const int x = right_edge_map.contains(y)
1769 ? right_edge_map[y]
1770 : total_bounds.x() + scroll_offset_;
1771 right_edge_map[y] = x + width;
1772 DCHECK_LE(int{right_edge_map.size()}, kTabletLayoutRow);
1773
1774 const gfx::RectF bounds(x, y, width, height);
1775 rects.push_back(bounds);
1776 ++window_position;
1777 }
1778
1779 return rects;
1780 }
1781
FitWindowRectsInBounds(const gfx::Rect & bounds,int height,const base::flat_set<OverviewItem * > & ignored_items,std::vector<gfx::RectF> * out_rects,int * out_max_bottom,int * out_min_right,int * out_max_right)1782 bool OverviewGrid::FitWindowRectsInBounds(
1783 const gfx::Rect& bounds,
1784 int height,
1785 const base::flat_set<OverviewItem*>& ignored_items,
1786 std::vector<gfx::RectF>* out_rects,
1787 int* out_max_bottom,
1788 int* out_min_right,
1789 int* out_max_right) {
1790 const size_t window_count = window_list_.size();
1791 out_rects->resize(window_count);
1792
1793 // Start in the top-left corner of |bounds|.
1794 int left = bounds.x();
1795 int top = bounds.y();
1796
1797 // Keep track of the lowest coordinate.
1798 *out_max_bottom = bounds.y();
1799
1800 // Right bound of the narrowest row.
1801 *out_min_right = bounds.right();
1802 // Right bound of the widest row.
1803 *out_max_right = bounds.x();
1804
1805 // All elements are of same height and only the height is necessary to
1806 // determine each item's scale.
1807 for (size_t i = 0u; i < window_count; ++i) {
1808 if (ShouldExcludeItemFromGridLayout(window_list_[i].get(), ignored_items))
1809 continue;
1810
1811 int width = CalculateWidthAndMaybeSetUnclippedBounds(window_list_[i].get(),
1812 height) +
1813 2 * kWindowMargin;
1814 int height_with_margin = height + 2 * kWindowMargin;
1815
1816 if (left + width > bounds.right()) {
1817 // Move to the next row if possible.
1818 if (*out_min_right > left)
1819 *out_min_right = left;
1820 if (*out_max_right < left)
1821 *out_max_right = left;
1822 top += height_with_margin;
1823
1824 // Check if the new row reaches the bottom or if the first item in the new
1825 // row does not fit within the available width.
1826 if (top + height_with_margin > bounds.bottom() ||
1827 bounds.x() + width > bounds.right()) {
1828 return false;
1829 }
1830 left = bounds.x();
1831 }
1832
1833 // Position the current rect.
1834 (*out_rects)[i] = gfx::RectF(left, top, width, height_with_margin);
1835
1836 // Increment horizontal position using sanitized positive |width|.
1837 left += width;
1838
1839 *out_max_bottom = top + height_with_margin;
1840 }
1841
1842 // Update the narrowest and widest row width for the last row.
1843 if (*out_min_right > left)
1844 *out_min_right = left;
1845 if (*out_max_right < left)
1846 *out_max_right = left;
1847
1848 return true;
1849 }
1850
GetOverviewItemIndex(OverviewItem * item) const1851 size_t OverviewGrid::GetOverviewItemIndex(OverviewItem* item) const {
1852 auto iter = std::find_if(window_list_.begin(), window_list_.end(),
1853 base::MatchesUniquePtr(item));
1854 DCHECK(iter != window_list_.end());
1855 return iter - window_list_.begin();
1856 }
1857
FindInsertionIndex(const aura::Window * window)1858 size_t OverviewGrid::FindInsertionIndex(const aura::Window* window) {
1859 size_t index = 0u;
1860 for (aura::Window* mru_window :
1861 Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
1862 if (index == size() ||
1863 IsDropTargetWindow(window_list_[index]->GetWindow()) ||
1864 mru_window == window) {
1865 return index;
1866 }
1867 // As we iterate over the whole MRU window list, the windows in this grid
1868 // will be encountered in the same order, but possibly with other windows in
1869 // between. Ignore those other windows, and only increment |index| when we
1870 // reach the next window in this grid.
1871 if (mru_window == window_list_[index]->GetWindow())
1872 ++index;
1873 }
1874 NOTREACHED();
1875 return 0u;
1876 }
1877
AddDraggedWindowIntoOverviewOnDragEnd(aura::Window * dragged_window)1878 void OverviewGrid::AddDraggedWindowIntoOverviewOnDragEnd(
1879 aura::Window* dragged_window) {
1880 DCHECK(overview_session_);
1881 if (overview_session_->IsWindowInOverview(dragged_window))
1882 return;
1883
1884 // Update the dragged window's bounds before adding it to overview. The
1885 // dragged window might have resized to a smaller size if the drag
1886 // happens on tab(s).
1887 if (window_util::IsDraggingTabs(dragged_window)) {
1888 const gfx::Rect old_bounds = dragged_window->bounds();
1889 // We need to temporarily disable the dragged window's ability to merge
1890 // into another window when changing the dragged window's bounds, so
1891 // that the dragged window doesn't merge into another window because of
1892 // its changed bounds.
1893 dragged_window->SetProperty(kCanAttachToAnotherWindowKey, false);
1894 TabletModeWindowState::UpdateWindowPosition(
1895 WindowState::Get(dragged_window), /*animate=*/false);
1896 const gfx::Rect new_bounds = dragged_window->bounds();
1897 if (old_bounds != new_bounds) {
1898 // It's for smoother animation.
1899 const gfx::Transform transform = gfx::TransformBetweenRects(
1900 gfx::RectF(new_bounds), gfx::RectF(old_bounds));
1901 dragged_window->SetTransform(transform);
1902 }
1903 dragged_window->ClearProperty(kCanAttachToAnotherWindowKey);
1904 }
1905
1906 overview_session_->AddItemInMruOrder(dragged_window, /*reposition=*/false,
1907 /*animate=*/false, /*restack=*/true);
1908 }
1909
GetDesksWidgetBounds() const1910 gfx::Rect OverviewGrid::GetDesksWidgetBounds() const {
1911 gfx::Rect desks_widget_screen_bounds = bounds_;
1912 desks_widget_screen_bounds.set_height(DesksBarView::GetBarHeightForWidth(
1913 root_window_, desks_bar_view_, desks_widget_screen_bounds.width()));
1914 // Shift the widget down to make room for the splitview indicator guidance
1915 // when it's shown at the top of the screen and no other windows are snapped.
1916 if (split_view_drag_indicators_ &&
1917 split_view_drag_indicators_->current_window_dragging_state() ==
1918 SplitViewDragIndicators::WindowDraggingState::kFromOverview &&
1919 !SplitViewController::IsLayoutHorizontal() &&
1920 !SplitViewController::Get(root_window_)->InSplitViewMode()) {
1921 desks_widget_screen_bounds.Offset(
1922 0, split_view_drag_indicators_->GetLeftHighlightViewBounds().height() +
1923 2 * kHighlightScreenEdgePaddingDp);
1924 }
1925
1926 return screen_util::SnapBoundsToDisplayEdge(desks_widget_screen_bounds,
1927 root_window_);
1928 }
1929
UpdateCannotSnapWarningVisibility()1930 void OverviewGrid::UpdateCannotSnapWarningVisibility() {
1931 for (auto& overview_mode_item : window_list_)
1932 overview_mode_item->UpdateCannotSnapWarningVisibility();
1933 }
1934
UpdateFrameThrottling()1935 void OverviewGrid::UpdateFrameThrottling() {
1936 std::vector<aura::Window*> windows_to_throttle(window_list_.size(), nullptr);
1937 std::transform(
1938 window_list_.begin(), window_list_.end(), windows_to_throttle.begin(),
1939 [](std::unique_ptr<OverviewItem>& item) { return item->GetWindow(); });
1940 Shell::Get()->frame_throttling_controller()->StartThrottling(
1941 windows_to_throttle);
1942 }
1943 } // namespace ash
1944