| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/wm/overview/overview_grid.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <utility> |
| |
| #include "ash/kiosk_next/kiosk_next_shell_controller.h" |
| #include "ash/metrics/histogram_macros.h" |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/public/cpp/fps_counter.h" |
| #include "ash/public/cpp/shelf_types.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/public/cpp/window_state_type.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/rotator/screen_rotation_animator.h" |
| #include "ash/screen_util.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_constants.h" |
| #include "ash/shell.h" |
| #include "ash/wm/desks/desks_bar_view.h" |
| #include "ash/wm/overview/cleanup_animation_observer.h" |
| #include "ash/wm/overview/drop_target_view.h" |
| #include "ash/wm/overview/overview_constants.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_delegate.h" |
| #include "ash/wm/overview/overview_item.h" |
| #include "ash/wm/overview/overview_session.h" |
| #include "ash/wm/overview/overview_utils.h" |
| #include "ash/wm/overview/rounded_rect_view.h" |
| #include "ash/wm/overview/scoped_overview_animation_settings.h" |
| #include "ash/wm/resize_shadow_controller.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_drag_indicators.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm/tablet_mode/tablet_mode_window_state.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "base/containers/unique_ptr_adapters.h" |
| #include "base/i18n/string_search.h" |
| #include "base/numerics/ranges.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor_extra/shadow.h" |
| #include "ui/gfx/geometry/safe_integer_conversions.h" |
| #include "ui/gfx/geometry/vector2d.h" |
| #include "ui/gfx/geometry/vector2d_conversions.h" |
| #include "ui/views/background.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/core/window_animations.h" |
| |
| namespace ash { |
| namespace { |
| |
| // The color and opacity of the overview selector. |
| constexpr SkColor kWindowSelectionColor = SkColorSetARGB(36, 255, 255, 255); |
| |
| // Corner radius and shadow applied to the overview selector border. |
| constexpr int kWindowSelectionRadius = 9; |
| constexpr int kWindowSelectionShadowElevation = 24; |
| |
| // Windows are not allowed to get taller than this. |
| constexpr int kMaxHeight = 512; |
| |
| // Margins reserved in the overview mode. |
| constexpr float kOverviewInsetRatio = 0.05f; |
| |
| // Additional vertical inset reserved for windows in overview mode. |
| constexpr float kOverviewVerticalInset = 0.1f; |
| |
| // Histogram names for overview enter/exit smoothness in clamshell, |
| // tablet mode and splitview. |
| constexpr char kOverviewEnterClamshellHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Enter.ClamshellMode"; |
| constexpr char kOverviewEnterSingleClamshellHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Enter.SingleClamshellMode"; |
| constexpr char kOverviewEnterTabletHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Enter.TabletMode"; |
| constexpr char kOverviewEnterSplitViewHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Enter.SplitView"; |
| |
| constexpr char kOverviewExitClamshellHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Exit.ClamshellMode"; |
| constexpr char kOverviewExitSingleClamshellHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Exit.SingleClamshellMode"; |
| constexpr char kOverviewExitTabletHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Exit.TabletMode"; |
| constexpr char kOverviewExitSplitViewHistogram[] = |
| "Ash.Overview.AnimationSmoothness.Exit.SplitView"; |
| |
| // Returns the vector for the fade in animation. |
| gfx::Vector2d GetSlideVectorForFadeIn(OverviewSession::Direction direction, |
| const gfx::Rect& bounds) { |
| gfx::Vector2d vector; |
| switch (direction) { |
| case OverviewSession::UP: |
| case OverviewSession::LEFT: |
| vector.set_x(-bounds.width()); |
| break; |
| case OverviewSession::DOWN: |
| case OverviewSession::RIGHT: |
| vector.set_x(bounds.width()); |
| break; |
| } |
| return vector; |
| } |
| |
| template <const char* clamshell_single_name, |
| const char* clamshell_multi_name, |
| const char* tablet_name, |
| const char* splitview_name> |
| class OverviewFpsCounter : public FpsCounter { |
| public: |
| OverviewFpsCounter(ui::Compositor* compositor, |
| bool single_animation_in_clamshell) |
| : FpsCounter(compositor), |
| single_animation_in_clamshell_(single_animation_in_clamshell) {} |
| ~OverviewFpsCounter() override { |
| int smoothness = ComputeSmoothness(); |
| if (smoothness < 0) |
| return; |
| if (single_animation_in_clamshell_) |
| UMA_HISTOGRAM_PERCENTAGE_IN_CLAMSHELL(clamshell_single_name, smoothness); |
| else |
| UMA_HISTOGRAM_PERCENTAGE_IN_CLAMSHELL(clamshell_multi_name, smoothness); |
| UMA_HISTOGRAM_PERCENTAGE_IN_TABLET_NON_SPLITVIEW(tablet_name, smoothness); |
| UMA_HISTOGRAM_PERCENTAGE_IN_SPLITVIEW(splitview_name, smoothness); |
| } |
| |
| private: |
| // True if only top window animates upon enter/exit overview in clamshell. |
| bool single_animation_in_clamshell_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OverviewFpsCounter); |
| }; |
| |
| using OverviewEnterFpsCounter = |
| OverviewFpsCounter<kOverviewEnterSingleClamshellHistogram, |
| kOverviewEnterClamshellHistogram, |
| kOverviewEnterTabletHistogram, |
| kOverviewEnterSplitViewHistogram>; |
| using OverviewExitFpsCounter = |
| OverviewFpsCounter<kOverviewExitSingleClamshellHistogram, |
| kOverviewExitClamshellHistogram, |
| kOverviewExitTabletHistogram, |
| kOverviewExitSplitViewHistogram>; |
| |
| class ShutdownAnimationFpsCounterObserver : public OverviewObserver { |
| public: |
| ShutdownAnimationFpsCounterObserver(ui::Compositor* compositor, |
| bool single_animation) |
| : fps_counter_(compositor, single_animation) { |
| Shell::Get()->overview_controller()->AddObserver(this); |
| } |
| ~ShutdownAnimationFpsCounterObserver() override { |
| Shell::Get()->overview_controller()->RemoveObserver(this); |
| } |
| |
| // OverviewObserver: |
| void OnOverviewModeEndingAnimationComplete(bool canceled) override { |
| delete this; |
| } |
| |
| private: |
| OverviewExitFpsCounter fps_counter_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ShutdownAnimationFpsCounterObserver); |
| }; |
| |
| // Creates |drop_target_widget_|. It's created when a window or overview item is |
| // dragged around, and destroyed when the drag ends. If |animate| is true, then |
| // the drop target will fade in. |
| std::unique_ptr<views::Widget> CreateDropTargetWidget( |
| aura::Window* dragged_window, |
| bool animate) { |
| aura::Window* parent = dragged_window->parent(); |
| gfx::Rect bounds = dragged_window->bounds(); |
| ::wm::ConvertRectToScreen(parent, &bounds); |
| views::Widget::InitParams params; |
| params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.activatable = views::Widget::InitParams::Activatable::ACTIVATABLE_NO; |
| params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| params.accept_events = false; |
| params.parent = parent; |
| params.bounds = bounds; |
| auto widget = std::make_unique<views::Widget>(); |
| widget->set_focus_on_creation(false); |
| widget->Init(params); |
| |
| // Show plus icon if drag a tab from a multi-tab window. |
| widget->SetContentsView(new DropTargetView( |
| dragged_window->GetProperty(ash::kTabDraggingSourceWindowKey))); |
| aura::Window* drop_target_window = widget->GetNativeWindow(); |
| drop_target_window->SetProperty(kHideInDeskMiniViewKey, true); |
| drop_target_window->parent()->StackChildAtBottom(drop_target_window); |
| widget->Show(); |
| |
| if (animate) { |
| widget->SetOpacity(0.f); |
| ScopedOverviewAnimationSettings settings( |
| OVERVIEW_ANIMATION_DROP_TARGET_FADE_IN, drop_target_window); |
| widget->SetOpacity(1.f); |
| } else { |
| widget->SetOpacity(1.f); |
| } |
| return widget; |
| } |
| |
| // Gets the expected grid bounds according to current splitview state. |
| gfx::Rect GetGridBoundsInScreenAfterDragging(aura::Window* dragged_window) { |
| SplitViewController* split_view_controller = |
| Shell::Get()->split_view_controller(); |
| switch (split_view_controller->state()) { |
| case SplitViewState::kLeftSnapped: |
| return split_view_controller->GetSnappedWindowBoundsInScreen( |
| dragged_window, SplitViewController::RIGHT); |
| case SplitViewState::kRightSnapped: |
| return split_view_controller->GetSnappedWindowBoundsInScreen( |
| dragged_window, SplitViewController::LEFT); |
| default: |
| return screen_util:: |
| GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| dragged_window); |
| } |
| } |
| |
| // When split view mode is not active, the grid bounds are updated according to |
| // |indicator_state|, to achieve the effect that the overview windows get out of |
| // the way of a split view drag indicator when it expands into a preview area. |
| // When split view mode is active, instead of keeping the overview windows away |
| // from the preview area, they are kept away from the already snapped window (by |
| // just forwarding |dragged_window| to GetGridBoundsInScreenAfterDragging()). |
| gfx::Rect GetGridBoundsInScreenDuringDragging(aura::Window* dragged_window, |
| IndicatorState indicator_state) { |
| SplitViewController* split_view_controller = |
| Shell::Get()->split_view_controller(); |
| if (split_view_controller->InSplitViewMode()) |
| return GetGridBoundsInScreenAfterDragging(dragged_window); |
| switch (indicator_state) { |
| case IndicatorState::kPreviewAreaLeft: |
| return split_view_controller->GetSnappedWindowBoundsInScreen( |
| dragged_window, SplitViewController::RIGHT); |
| case IndicatorState::kPreviewAreaRight: |
| return split_view_controller->GetSnappedWindowBoundsInScreen( |
| dragged_window, SplitViewController::LEFT); |
| default: |
| return screen_util:: |
| GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| dragged_window); |
| } |
| } |
| |
| // Returns the desks widget bounds in root, given the screen bounds of the |
| // overview grid. |
| gfx::Rect GetDesksWidgetBounds(aura::Window* root, |
| const gfx::Rect& overview_grid_screen_bounds) { |
| gfx::Rect desks_widget_root_bounds = overview_grid_screen_bounds; |
| ::wm::ConvertRectFromScreen(root, &desks_widget_root_bounds); |
| desks_widget_root_bounds.set_height(DesksBarView::GetBarHeight()); |
| return screen_util::SnapBoundsToDisplayEdge(desks_widget_root_bounds, root); |
| } |
| |
| } // namespace |
| |
| // The class to observe the overview window that the dragged tabs will merge |
| // into. After the dragged tabs merge into the overview window, and if the |
| // overview window represents a minimized window, we need to update the |
| // overview minimized widget's content view so that it reflects the merge. |
| class OverviewGrid::TargetWindowObserver : public aura::WindowObserver { |
| public: |
| TargetWindowObserver() = default; |
| ~TargetWindowObserver() override { StopObserving(); } |
| |
| void StartObserving(aura::Window* window) { |
| if (target_window_) |
| StopObserving(); |
| |
| target_window_ = window; |
| target_window_->AddObserver(this); |
| } |
| |
| // aura::WindowObserver: |
| void OnWindowPropertyChanged(aura::Window* window, |
| const void* key, |
| intptr_t old) override { |
| DCHECK_EQ(window, target_window_); |
| // When the property is cleared, the dragged window should have been merged |
| // into |target_window_|, update the corresponding window item in overview. |
| if (key == ash::kIsDeferredTabDraggingTargetWindowKey && |
| !window->GetProperty(ash::kIsDeferredTabDraggingTargetWindowKey)) { |
| UpdateWindowItemInOverviewContaining(window); |
| StopObserving(); |
| } |
| } |
| |
| void OnWindowDestroying(aura::Window* window) override { |
| DCHECK_EQ(window, target_window_); |
| StopObserving(); |
| } |
| |
| private: |
| void UpdateWindowItemInOverviewContaining(aura::Window* window) { |
| OverviewController* overview_controller = |
| Shell::Get()->overview_controller(); |
| if (!overview_controller->InOverviewSession()) |
| return; |
| |
| OverviewGrid* grid = |
| overview_controller->overview_session()->GetGridWithRootWindow( |
| window->GetRootWindow()); |
| if (!grid) |
| return; |
| |
| OverviewItem* item = grid->GetOverviewItemContaining(window); |
| if (!item) |
| return; |
| |
| item->UpdateItemContentViewForMinimizedWindow(); |
| } |
| |
| void StopObserving() { |
| if (target_window_) |
| target_window_->RemoveObserver(this); |
| target_window_ = nullptr; |
| } |
| |
| aura::Window* target_window_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(TargetWindowObserver); |
| }; |
| |
| OverviewGrid::OverviewGrid(aura::Window* root_window, |
| const std::vector<aura::Window*>& windows, |
| OverviewSession* overview_session, |
| const gfx::Rect& bounds_in_screen) |
| : root_window_(root_window), |
| overview_session_(overview_session), |
| window_observer_(this), |
| window_state_observer_(this), |
| bounds_(bounds_in_screen) { |
| for (auto* window : windows) { |
| if (window->GetRootWindow() != root_window) |
| continue; |
| |
| // Hide the drag shadow if it is visible. |
| Shell::Get()->resize_shadow_controller()->HideShadow(window); |
| |
| // Stop ongoing animations before entering overview mode. Because we are |
| // deferring SetTransform of the windows beneath the window covering the |
| // available workspace, we need to set the correct transforms of these |
| // windows before entering overview mode again in the |
| // OnImplicitAnimationsCompleted() of the observer of the |
| // available-workspace-covering window's animation. |
| auto* animator = window->layer()->GetAnimator(); |
| if (animator->is_animating()) |
| window->layer()->GetAnimator()->StopAnimating(); |
| window_observer_.Add(window); |
| window_state_observer_.Add(wm::GetWindowState(window)); |
| window_list_.push_back( |
| std::make_unique<OverviewItem>(window, overview_session_, this)); |
| } |
| } |
| |
| OverviewGrid::~OverviewGrid() = default; |
| |
| void OverviewGrid::Shutdown() { |
| ScreenRotationAnimator::GetForRootWindow(root_window_)->RemoveObserver(this); |
| |
| bool has_non_cover_animating = false; |
| int animate_count = 0; |
| |
| for (const auto& window : window_list_) { |
| if (window->should_animate_when_exiting() && !has_non_cover_animating) { |
| has_non_cover_animating |= |
| !CanCoverAvailableWorkspace(window->GetWindow()); |
| animate_count++; |
| } |
| window->Shutdown(); |
| } |
| bool single_animation_in_clamshell = |
| (animate_count == 1 && !has_non_cover_animating) && |
| !Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled(); |
| |
| // OverviewGrid in splitscreen does not include the window to be activated. |
| if (!window_list_.empty() || |
| Shell::Get()->split_view_controller()->InSplitViewMode()) { |
| // The following instance self-destructs when shutdown animation ends. |
| new ShutdownAnimationFpsCounterObserver( |
| root_window_->layer()->GetCompositor(), single_animation_in_clamshell); |
| } |
| |
| overview_session_ = nullptr; |
| |
| while (!window_list_.empty()) |
| RemoveItem(window_list_.back().get()); |
| } |
| |
| void OverviewGrid::PrepareForOverview() { |
| if (!ShouldAnimateWallpaper()) |
| MaybeInitDesksWidget(); |
| |
| for (const auto& window : window_list_) |
| window->PrepareForOverview(); |
| prepared_for_overview_ = true; |
| if (Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled()) { |
| ScreenRotationAnimator::GetForRootWindow(root_window_)->AddObserver(this); |
| } |
| } |
| |
| void OverviewGrid::PositionWindows( |
| bool animate, |
| const base::flat_set<OverviewItem*>& ignored_items, |
| OverviewSession::OverviewTransition transition) { |
| if (!overview_session_ || suspend_reposition_ || window_list_.empty()) |
| return; |
| |
| DCHECK_NE(transition, OverviewSession::OverviewTransition::kExit); |
| |
| std::vector<gfx::RectF> rects = GetWindowRects(ignored_items); |
| if (transition == OverviewSession::OverviewTransition::kEnter) { |
| CalculateWindowListAnimationStates(/*selected_item=*/nullptr, transition, |
| rects); |
| } |
| |
| // Position the windows centering the left-aligned rows vertically. Do not |
| // position items in |ignored_items|. |
| OverviewAnimationType animation_type = |
| transition == OverviewSession::OverviewTransition::kEnter |
| ? OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_ON_ENTER |
| : OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW; |
| |
| int animate_count = 0; |
| bool has_non_cover_animating = false; |
| std::vector<OverviewAnimationType> animation_types(rects.size()); |
| |
| for (size_t i = 0; i < window_list_.size(); ++i) { |
| OverviewItem* window_item = window_list_[i].get(); |
| if (window_item->animating_to_close() || |
| ignored_items.contains(window_item)) { |
| rects[i].SetRect(0, 0, 0, 0); |
| continue; |
| } |
| |
| // Calculate if each window item needs animation. |
| bool should_animate_item = animate; |
| // If we're in entering overview process, not all window items in the grid |
| // might need animation even if the grid needs animation. |
| if (animate && transition == OverviewSession::OverviewTransition::kEnter) |
| should_animate_item = window_item->should_animate_when_entering(); |
| // Do not do the bounds animation for the drop target. We'll do the opacity |
| // animation by ourselves. |
| if (IsDropTargetWindow(window_item->GetWindow())) |
| should_animate_item = false; |
| if (animate && transition == OverviewSession::OverviewTransition::kEnter) { |
| if (window_item->should_animate_when_entering() && |
| !has_non_cover_animating) { |
| has_non_cover_animating |= |
| !CanCoverAvailableWorkspace(window_item->GetWindow()); |
| animate_count++; |
| } |
| } |
| animation_types[i] = |
| should_animate_item ? animation_type : OVERVIEW_ANIMATION_NONE; |
| } |
| |
| if (animate && transition == OverviewSession::OverviewTransition::kEnter && |
| !window_list_.empty()) { |
| bool single_animation_in_clamshell = |
| animate_count == 1 && !has_non_cover_animating && |
| !Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled(); |
| fps_counter_ = std::make_unique<OverviewEnterFpsCounter>( |
| window_list_[0]->GetWindow()->layer()->GetCompositor(), |
| single_animation_in_clamshell); |
| } |
| |
| // Apply the animation after creating fps_counter_ so that unit test |
| // can correctly count the measure requests. |
| for (size_t i = 0; i < window_list_.size(); ++i) { |
| if (rects[i].IsEmpty()) |
| continue; |
| OverviewItem* window_item = window_list_[i].get(); |
| window_item->SetBounds(rects[i], animation_types[i]); |
| } |
| |
| // If the selection widget is active, reposition it without any animation. |
| if (selection_widget_) |
| MoveSelectionWidgetToTarget(animate); |
| } |
| |
| bool OverviewGrid::Move(OverviewSession::Direction direction, bool animate) { |
| if (empty()) |
| return true; |
| |
| bool recreate_selection_widget = false; |
| bool out_of_bounds = false; |
| bool changed_selection_index = false; |
| gfx::RectF old_bounds; |
| if (SelectedWindow()) { |
| old_bounds = SelectedWindow()->target_bounds(); |
| // Make the old selected window header non-transparent first. |
| SelectedWindow()->set_selected(false); |
| } |
| |
| // [up] key is equivalent to [left] key and [down] key is equivalent to |
| // [right] key. |
| if (!selection_widget_) { |
| switch (direction) { |
| case OverviewSession::UP: |
| case OverviewSession::LEFT: |
| selected_index_ = window_list_.size() - 1; |
| break; |
| case OverviewSession::DOWN: |
| case OverviewSession::RIGHT: |
| selected_index_ = 0; |
| break; |
| } |
| changed_selection_index = true; |
| } |
| while (!changed_selection_index) { |
| switch (direction) { |
| case OverviewSession::UP: |
| case OverviewSession::LEFT: |
| if (selected_index_ == 0) |
| out_of_bounds = true; |
| selected_index_--; |
| break; |
| case OverviewSession::DOWN: |
| case OverviewSession::RIGHT: |
| if (selected_index_ >= window_list_.size() - 1) |
| out_of_bounds = true; |
| selected_index_++; |
| break; |
| } |
| if (!out_of_bounds && SelectedWindow()) { |
| if (SelectedWindow()->target_bounds().y() != old_bounds.y()) |
| recreate_selection_widget = true; |
| } |
| changed_selection_index = true; |
| } |
| MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds, |
| animate); |
| |
| // Make the new selected window header fully transparent. |
| if (SelectedWindow()) |
| SelectedWindow()->set_selected(true); |
| return out_of_bounds; |
| } |
| |
| OverviewItem* OverviewGrid::SelectedWindow() const { |
| if (!selection_widget_) |
| return nullptr; |
| CHECK(selected_index_ < window_list_.size()); |
| return window_list_[selected_index_].get(); |
| } |
| |
| OverviewItem* OverviewGrid::GetOverviewItemContaining( |
| const aura::Window* window) const { |
| for (const auto& window_item : window_list_) { |
| if (window_item && window_item->Contains(window)) |
| return window_item.get(); |
| } |
| return nullptr; |
| } |
| |
| void OverviewGrid::AddItem(aura::Window* window, |
| bool reposition, |
| bool animate, |
| const base::flat_set<OverviewItem*>& ignored_items, |
| size_t index) { |
| DCHECK(!GetOverviewItemContaining(window)); |
| DCHECK_LE(index, window_list_.size()); |
| |
| window_observer_.Add(window); |
| window_state_observer_.Add(wm::GetWindowState(window)); |
| window_list_.insert( |
| window_list_.begin() + index, |
| std::make_unique<OverviewItem>(window, overview_session_, this)); |
| window_list_[index]->PrepareForOverview(); |
| // The item is added after overview enter animation is complete, so |
| // just call OnStartingAnimationComplete. |
| window_list_[index]->OnStartingAnimationComplete(); |
| |
| if (reposition) |
| PositionWindows(animate, ignored_items); |
| } |
| |
| void OverviewGrid::AppendItem(aura::Window* window, |
| bool reposition, |
| bool animate) { |
| AddItem(window, reposition, animate, /*ignored_items=*/{}, |
| window_list_.size()); |
| } |
| |
| void OverviewGrid::RemoveItem(OverviewItem* overview_item) { |
| auto* window = overview_item->GetWindow(); |
| // Use reverse iterator to be efficiently when removing all. |
| auto iter = std::find_if(window_list_.rbegin(), window_list_.rend(), |
| [window](std::unique_ptr<OverviewItem>& item) { |
| return item->GetWindow() == window; |
| }); |
| DCHECK(iter != window_list_.rend()); |
| window_observer_.Remove(window); |
| window_state_observer_.Remove(wm::GetWindowState(window)); |
| // Erase from the list first because deleting OverviewItem can lead to |
| // iterating through the |window_list_|. |
| std::unique_ptr<OverviewItem> tmp = std::move(*iter); |
| window_list_.erase(std::next(iter).base()); |
| } |
| |
| void OverviewGrid::AddDropTargetForDraggingFromOverview( |
| OverviewItem* dragged_item) { |
| DCHECK_EQ(dragged_item->GetWindow()->GetRootWindow(), root_window_); |
| DCHECK(!drop_target_widget_); |
| drop_target_widget_ = |
| CreateDropTargetWidget(dragged_item->GetWindow(), /*animate=*/false); |
| const size_t position = GetOverviewItemIndex(dragged_item) + 1u; |
| overview_session_->AddItem(drop_target_widget_->GetNativeWindow(), |
| /*reposition=*/true, /*animate=*/false, |
| /*ignored_items=*/{dragged_item}, position); |
| // This part is necessary because |OverviewItem::OnSelectorItemDragStarted| is |
| // called on all overview items before the drop target exists among them. That |
| // is because |AddDropTargetForDraggingFromOverview| is only called for drag |
| // to snap, but |OnSelectorItemDragStarted| is called before the drag has been |
| // disambiguated between drag to close and drag to snap. |
| GetDropTarget()->OnSelectorItemDragStarted(dragged_item); |
| } |
| |
| void OverviewGrid::RemoveDropTarget() { |
| DCHECK(drop_target_widget_); |
| overview_session_->RemoveItem(GetDropTarget()); |
| drop_target_widget_.reset(); |
| } |
| |
| void OverviewGrid::SetBoundsAndUpdatePositions( |
| const gfx::Rect& bounds_in_screen, |
| const base::flat_set<OverviewItem*>& ignored_items) { |
| bounds_ = bounds_in_screen; |
| if (desks_widget_) |
| desks_widget_->SetBounds(GetDesksWidgetBounds(root_window_, bounds_)); |
| PositionWindows(/*animate=*/true, ignored_items); |
| } |
| |
| void OverviewGrid::RearrangeDuringDrag(aura::Window* dragged_window, |
| const gfx::PointF& location_in_screen, |
| IndicatorState indicator_state) { |
| OverviewItem* drop_target = GetDropTarget(); |
| |
| // Update the drop target visibility according to |indicator_state|. |
| const bool wanted_drop_target_visibility = |
| indicator_state != IndicatorState::kPreviewAreaLeft && |
| indicator_state != IndicatorState::kPreviewAreaRight; |
| const bool update_drop_target_visibility = |
| drop_target && |
| (drop_target_widget_->IsVisible() != wanted_drop_target_visibility); |
| if (update_drop_target_visibility) { |
| drop_target_widget_->GetLayer()->SetVisible(wanted_drop_target_visibility); |
| drop_target->SetOpacity(wanted_drop_target_visibility ? 1.f : 0.f); |
| } |
| |
| OverviewItem* dragged_item = GetOverviewItemContaining(dragged_window); |
| |
| // Update the grid's bounds. |
| const gfx::Rect wanted_grid_bounds = |
| GetGridBoundsInScreenDuringDragging(dragged_window, indicator_state); |
| if (update_drop_target_visibility || bounds_ != wanted_grid_bounds) { |
| base::flat_set<OverviewItem*> ignored_items; |
| if (dragged_item) |
| ignored_items.insert(dragged_item); |
| if (drop_target && !wanted_drop_target_visibility) |
| ignored_items.insert(drop_target); |
| SetBoundsAndUpdatePositions(wanted_grid_bounds, ignored_items); |
| } |
| |
| // If the drop target is on another grid, let that grid handle what follows. |
| if (!drop_target) |
| return; |
| |
| // Visually indicate when |dragged_window| is dragged over the drop target. |
| aura::Window* target_window = |
| GetTargetWindowOnLocation(location_in_screen, dragged_item); |
| DropTargetView* drop_target_view = |
| static_cast<DropTargetView*>(drop_target_widget_->GetContentsView()); |
| DCHECK(drop_target_view); |
| drop_target_view->UpdateBackgroundVisibility( |
| target_window && IsDropTargetWindow(target_window)); |
| } |
| |
| void OverviewGrid::SetSelectionWidgetVisibility(bool visible) { |
| if (!selection_widget_) |
| return; |
| |
| if (visible) |
| selection_widget_->Show(); |
| else |
| selection_widget_->Hide(); |
| } |
| |
| void OverviewGrid::UpdateCannotSnapWarningVisibility() { |
| for (auto& overview_mode_item : window_list_) |
| overview_mode_item->UpdateCannotSnapWarningVisibility(); |
| } |
| |
| void OverviewGrid::OnSelectorItemDragStarted(OverviewItem* item) { |
| for (auto& overview_mode_item : window_list_) |
| overview_mode_item->OnSelectorItemDragStarted(item); |
| } |
| |
| void OverviewGrid::OnSelectorItemDragEnded(bool snap) { |
| for (auto& overview_mode_item : window_list_) |
| overview_mode_item->OnSelectorItemDragEnded(snap); |
| } |
| |
| void OverviewGrid::OnWindowDragStarted(aura::Window* dragged_window, |
| bool animate) { |
| DCHECK_EQ(dragged_window->GetRootWindow(), root_window_); |
| DCHECK(!drop_target_widget_); |
| drop_target_widget_ = CreateDropTargetWidget(dragged_window, animate); |
| overview_session_->AddItem(drop_target_widget_->GetNativeWindow(), |
| /*reposition=*/true, animate); |
| |
| // Stack the |dragged_window| at top during drag. |
| dragged_window->parent()->StackChildAtTop(dragged_window); |
| |
| // Called to set caption and title visibility during dragging. |
| OnSelectorItemDragStarted(/*item=*/nullptr); |
| } |
| |
| void OverviewGrid::OnWindowDragContinued(aura::Window* dragged_window, |
| const gfx::PointF& location_in_screen, |
| IndicatorState indicator_state) { |
| DCHECK_EQ(dragged_window->GetRootWindow(), root_window_); |
| |
| RearrangeDuringDrag(dragged_window, location_in_screen, indicator_state); |
| |
| aura::Window* target_window = |
| GetTargetWindowOnLocation(location_in_screen, /*ignored_item=*/nullptr); |
| |
| if (indicator_state == IndicatorState::kPreviewAreaLeft || |
| indicator_state == IndicatorState::kPreviewAreaRight) { |
| // If the dragged window is currently dragged into preview window area, |
| // clear the selection widget. |
| if (SelectedWindow()) { |
| SelectedWindow()->set_selected(false); |
| selection_widget_.reset(); |
| } |
| |
| // Also clear ash::kIsDeferredTabDraggingTargetWindowKey key on the target |
| // overview item so that it can't merge into this overview item if the |
| // dragged window is currently in preview window area. |
| if (target_window && !IsDropTargetWindow(target_window)) |
| target_window->ClearProperty(ash::kIsDeferredTabDraggingTargetWindowKey); |
| |
| return; |
| } |
| |
| // Show the selection widget if |location_in_screen| is contained by the |
| // browser windows' overview item in overview. |
| if (target_window && |
| target_window->GetProperty(ash::kIsDeferredTabDraggingTargetWindowKey)) { |
| size_t previous_selected_index = selected_index_; |
| selected_index_ = GetOverviewItemIterContainingWindow(target_window) - |
| window_list_.begin(); |
| if (previous_selected_index == selected_index_ && selection_widget_) |
| return; |
| |
| if (previous_selected_index != selected_index_) |
| selection_widget_.reset(); |
| |
| const OverviewSession::Direction direction = |
| (selected_index_ - previous_selected_index > 0) ? OverviewSession::RIGHT |
| : OverviewSession::LEFT; |
| MoveSelectionWidget(direction, |
| /*recreate_selection_widget=*/true, |
| /*out_of_bounds=*/false, |
| /*animate=*/false); |
| return; |
| } |
| |
| if (SelectedWindow()) { |
| SelectedWindow()->set_selected(false); |
| selection_widget_.reset(); |
| } |
| } |
| |
| void OverviewGrid::OnWindowDragEnded(aura::Window* dragged_window, |
| const gfx::PointF& location_in_screen, |
| bool should_drop_window_into_overview, |
| bool snap) { |
| DCHECK_EQ(dragged_window->GetRootWindow(), root_window_); |
| DCHECK(drop_target_widget_.get()); |
| |
| // Add the dragged window into drop target in overview if |
| // |should_drop_window_into_overview| is true. Only consider add the dragged |
| // window into drop target if SelectedWindow is false since drop target will |
| // not be selected and tab dragging might drag a tab window to merge it into a |
| // browser window in overview. |
| if (SelectedWindow()) { |
| SelectedWindow()->set_selected(false); |
| selection_widget_.reset(); |
| } else if (should_drop_window_into_overview) { |
| AddDraggedWindowIntoOverviewOnDragEnd(dragged_window); |
| } |
| |
| RemoveDropTarget(); |
| |
| // Called to reset caption and title visibility after dragging. |
| OnSelectorItemDragEnded(snap); |
| |
| // After drag ends, if the dragged window needs to merge into another window |
| // |target_window|, and we may need to update |minimized_widget_| that holds |
| // the contents of |target_window| if |target_window| is a minimized window |
| // in overview. |
| aura::Window* target_window = |
| GetTargetWindowOnLocation(location_in_screen, /*ignored_item=*/nullptr); |
| if (target_window && |
| target_window->GetProperty(ash::kIsDeferredTabDraggingTargetWindowKey)) { |
| // Create an window observer and update the minimized window widget after |
| // the dragged window merges into |target_window|. |
| if (!target_window_observer_) |
| target_window_observer_ = std::make_unique<TargetWindowObserver>(); |
| target_window_observer_->StartObserving(target_window); |
| } |
| |
| // Update the grid bounds and reposition windows. Since the grid bounds might |
| // be updated based on the preview area during drag, but the window finally |
| // didn't be snapped to the preview area. |
| SetBoundsAndUpdatePositions( |
| GetGridBoundsInScreenAfterDragging(dragged_window), /*ignored_items=*/{}); |
| } |
| |
| bool OverviewGrid::IsDropTargetWindow(aura::Window* window) const { |
| return drop_target_widget_ && |
| drop_target_widget_->GetNativeWindow() == window; |
| } |
| |
| OverviewItem* OverviewGrid::GetDropTarget() { |
| return drop_target_widget_ |
| ? GetOverviewItemContaining(drop_target_widget_->GetNativeWindow()) |
| : nullptr; |
| } |
| |
| void OverviewGrid::OnWindowDestroying(aura::Window* window) { |
| window_observer_.Remove(window); |
| window_state_observer_.Remove(wm::GetWindowState(window)); |
| auto iter = GetOverviewItemIterContainingWindow(window); |
| DCHECK(iter != window_list_.end()); |
| |
| // Windows that are animating to a close state already call PositionWindows, |
| // no need to call it twice. |
| const bool needs_repositioning = !((*iter)->animating_to_close()); |
| |
| size_t removed_index = iter - window_list_.begin(); |
| // Erase from the list first because deleting OverviewItem can lead to |
| // iterating through the |window_list_|. |
| std::unique_ptr<OverviewItem> tmp = std::move(*iter); |
| window_list_.erase(iter); |
| tmp.reset(); |
| |
| if (empty()) { |
| selection_widget_.reset(); |
| // If the grid is now empty, notify |overview_session_| so that it erases us |
| // from its grid list. |
| if (overview_session_) |
| overview_session_->OnGridEmpty(this); |
| return; |
| } |
| |
| // If selecting, update the selection index. |
| if (selection_widget_) { |
| bool send_focus_alert = selected_index_ == removed_index; |
| if (selected_index_ >= removed_index && selected_index_ != 0) |
| selected_index_--; |
| SelectedWindow()->set_selected(true); |
| if (send_focus_alert) |
| SelectedWindow()->SendAccessibleSelectionEvent(); |
| } |
| |
| if (needs_repositioning) |
| PositionWindows(true); |
| } |
| |
| void OverviewGrid::OnWindowBoundsChanged(aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) { |
| // During preparation, window bounds can change. Ignore bounds |
| // change notifications in this case; we'll reposition soon. |
| if (!prepared_for_overview_) |
| return; |
| |
| // |drop_target_widget_| will get its bounds set as opposed to its transform |
| // set in |OverviewItem::SetItemBounds| so do not position windows again when |
| // that particular window has its bounds changed. |
| if (IsDropTargetWindow(window)) |
| return; |
| |
| auto iter = GetOverviewItemIterContainingWindow(window); |
| DCHECK(iter != window_list_.end()); |
| |
| // Immediately finish any active bounds animation. |
| window->layer()->GetAnimator()->StopAnimatingProperty( |
| ui::LayerAnimationElement::BOUNDS); |
| (*iter)->UpdateWindowDimensionsType(); |
| PositionWindows(false); |
| } |
| |
| void OverviewGrid::OnWindowPropertyChanged(aura::Window* window, |
| const void* key, |
| intptr_t old) { |
| if (prepared_for_overview_ && key == aura::client::kTopViewInset && |
| window->GetProperty(aura::client::kTopViewInset) != |
| static_cast<int>(old)) { |
| PositionWindows(/*animate=*/false); |
| } |
| } |
| |
| void OverviewGrid::OnPostWindowStateTypeChange(wm::WindowState* window_state, |
| WindowStateType old_type) { |
| // During preparation, window state can change, e.g. updating shelf |
| // visibility may show the temporarily hidden (minimized) panels. |
| if (!prepared_for_overview_) |
| return; |
| |
| // When swiping away overview mode via shelf, windows will get minimized, but |
| // we do not want to create minimized widgets in their place. |
| if (overview_session_->enter_exit_overview_type() == |
| OverviewSession::EnterExitOverviewType::kSwipeFromShelf) { |
| return; |
| } |
| |
| WindowStateType new_type = window_state->GetStateType(); |
| if (IsMinimizedWindowStateType(old_type) == |
| IsMinimizedWindowStateType(new_type)) { |
| return; |
| } |
| |
| auto iter = std::find_if(window_list_.begin(), window_list_.end(), |
| [window_state](std::unique_ptr<OverviewItem>& item) { |
| return item->Contains(window_state->window()); |
| }); |
| if (iter != window_list_.end()) { |
| (*iter)->OnMinimizedStateChanged(); |
| PositionWindows(/*animate=*/false); |
| } |
| } |
| |
| void OverviewGrid::OnScreenCopiedBeforeRotation() { |
| for (auto& window : window_list()) { |
| window->set_disable_mask(true); |
| window->UpdateMaskAndShadow(); |
| } |
| } |
| |
| void OverviewGrid::OnScreenRotationAnimationFinished( |
| ScreenRotationAnimator* animator, |
| bool canceled) { |
| for (auto& window : window_list()) |
| window->set_disable_mask(false); |
| Shell::Get()->overview_controller()->DelayedUpdateMaskAndShadow(); |
| } |
| |
| void OverviewGrid::OnStartingAnimationComplete(bool canceled) { |
| fps_counter_.reset(); |
| if (canceled) |
| return; |
| |
| MaybeInitDesksWidget(); |
| |
| for (auto& window : window_list()) |
| window->OnStartingAnimationComplete(); |
| } |
| |
| bool OverviewGrid::ShouldAnimateWallpaper() const { |
| // Kiosk next shell mode will have an opaque background covering the wallpaper |
| // prior to entering overview, so there's no need to animate it. |
| if (Shell::Get()->kiosk_next_shell_controller()->IsEnabled()) |
| return false; |
| |
| // Never animate when doing app dragging or when immediately exiting. |
| const auto enter_exit_type = overview_session_->enter_exit_overview_type(); |
| if (enter_exit_type == |
| OverviewSession::EnterExitOverviewType::kWindowDragged || |
| enter_exit_type == |
| OverviewSession::EnterExitOverviewType::kImmediateExit) { |
| return false; |
| } |
| |
| // If one of the windows covers the workspace, we do not need to animate. |
| for (const auto& overview_item : window_list_) { |
| if (CanCoverAvailableWorkspace(overview_item->GetWindow())) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void OverviewGrid::CalculateWindowListAnimationStates( |
| OverviewItem* selected_item, |
| OverviewSession::OverviewTransition transition, |
| const std::vector<gfx::RectF>& target_bounds) { |
| using OverviewTransition = OverviewSession::OverviewTransition; |
| |
| // Sanity checks to enforce assumptions used in later codes. |
| switch (transition) { |
| case OverviewTransition::kEnter: |
| DCHECK_EQ(target_bounds.size(), window_list_.size()); |
| break; |
| case OverviewTransition::kExit: |
| DCHECK(target_bounds.empty()); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // Create a copy of |window_list_| which has always on top windows in the |
| // front. |
| std::vector<OverviewItem*> items; |
| std::transform( |
| window_list_.begin(), window_list_.end(), std::back_inserter(items), |
| [](const std::unique_ptr<OverviewItem>& item) -> OverviewItem* { |
| return item.get(); |
| }); |
| // Sort items by: |
| // 1) Selected items that are always on top windows. |
| // 2) Other always on top windows. |
| // 3) Selected items that are not always on top windows. |
| // 4) Other not always on top windows. |
| // Preserves ordering if the category is the same. |
| std::sort(items.begin(), items.end(), |
| [&selected_item](OverviewItem* a, OverviewItem* b) { |
| const bool a_on_top = |
| a->GetWindow()->GetProperty(aura::client::kAlwaysOnTopKey); |
| const bool b_on_top = |
| b->GetWindow()->GetProperty(aura::client::kAlwaysOnTopKey); |
| if (selected_item && a_on_top && b_on_top) |
| return a == selected_item; |
| if (a_on_top) |
| return true; |
| if (b_on_top) |
| return false; |
| if (selected_item) |
| return a == selected_item; |
| return false; |
| }); |
| |
| SkRegion occluded_region; |
| for (size_t i = 0; i < items.size(); ++i) { |
| const bool minimized = |
| wm::GetWindowState(items[i]->GetWindow())->IsMinimized(); |
| bool src_occluded = minimized; |
| bool dst_occluded = false; |
| gfx::Rect src_bounds_temp = |
| minimized ? gfx::Rect() |
| : items[i]->GetWindow()->GetBoundsInRootWindow(); |
| // On exiting overview, |GetBoundsInRootWindow| will have the overview |
| // translation applied to it, so undo it to get the true target bounds. |
| if (!src_bounds_temp.IsEmpty() && transition == OverviewTransition::kExit) { |
| const gfx::Vector2dF offset = |
| -items[i]->GetWindow()->transform().To2dTranslation(); |
| src_bounds_temp.Offset(gfx::ToCeiledVector2d(offset)); |
| } |
| SkIRect src_bounds = gfx::RectToSkIRect(src_bounds_temp); |
| SkIRect dst_bounds = gfx::RectToSkIRect(gfx::ToEnclosedRect( |
| transition == OverviewTransition::kEnter ? target_bounds[i] |
| : items[i]->target_bounds())); |
| |
| if (!occluded_region.isEmpty()) { |
| src_occluded |= |
| (!src_bounds.isEmpty() && occluded_region.contains(src_bounds)); |
| dst_occluded |= occluded_region.contains(dst_bounds); |
| } |
| |
| // Add |src_bounds| to our region if it is not empty (minimized window). |
| if (!src_bounds.isEmpty()) |
| occluded_region.op(src_bounds, SkRegion::kUnion_Op); |
| |
| const bool should_animate = !(src_occluded && dst_occluded); |
| if (transition == OverviewSession::OverviewTransition::kEnter) |
| items[i]->set_should_animate_when_entering(should_animate); |
| else if (transition == OverviewSession::OverviewTransition::kExit) |
| items[i]->set_should_animate_when_exiting(should_animate); |
| } |
| } |
| |
| void OverviewGrid::SetWindowListNotAnimatedWhenExiting() { |
| should_animate_when_exiting_ = false; |
| for (const auto& item : window_list_) |
| item->set_should_animate_when_exiting(false); |
| } |
| |
| void OverviewGrid::StartNudge(OverviewItem* item) { |
| // When there is one window left, there is no need to nudge. |
| if (window_list_.size() <= 1) { |
| nudge_data_.clear(); |
| return; |
| } |
| |
| // If any of the items are being animated to close, do not nudge any windows |
| // otherwise we have to deal with potential items getting removed from |
| // |window_list_| midway through a nudge. |
| for (const auto& window_item : window_list_) { |
| if (window_item->animating_to_close()) { |
| nudge_data_.clear(); |
| return; |
| } |
| } |
| |
| DCHECK(item); |
| |
| // Get the bounds of the windows currently, and the bounds if |item| were to |
| // be removed. |
| std::vector<gfx::RectF> src_rects; |
| for (const auto& window_item : window_list_) |
| src_rects.push_back(window_item->target_bounds()); |
| |
| std::vector<gfx::RectF> dst_rects = GetWindowRects({item}); |
| |
| const size_t index = GetOverviewItemIndex(item); |
| |
| // Returns a vector of integers indicating which row the item is in. |index| |
| // is the index of the element which is going to be deleted and should not |
| // factor into calculations. The call site should mark |index| as -1 if it |
| // should not be used. The item at |index| is marked with a 0. The heights of |
| // items are all set to the same value so a new row is determined if the y |
| // value has changed from the previous item. |
| auto get_rows = [](const std::vector<gfx::RectF>& bounds_list, size_t index) { |
| std::vector<int> row_numbers; |
| int current_row = 1; |
| float last_y = 0; |
| for (size_t i = 0; i < bounds_list.size(); ++i) { |
| if (i == index) { |
| row_numbers.push_back(0); |
| continue; |
| } |
| |
| // Update |current_row| if the y position has changed (heights are all |
| // equal in overview, so a new y position indicates a new row). |
| if (last_y != 0 && last_y != bounds_list[i].y()) |
| ++current_row; |
| |
| row_numbers.push_back(current_row); |
| last_y = bounds_list[i].y(); |
| } |
| |
| return row_numbers; |
| }; |
| |
| std::vector<int> src_rows = get_rows(src_rects, -1); |
| std::vector<int> dst_rows = get_rows(dst_rects, index); |
| |
| // Do nothing if the number of rows change. |
| if (dst_rows.back() != 0 && src_rows.back() != dst_rows.back()) |
| return; |
| size_t second_last_index = src_rows.size() - 2; |
| if (dst_rows.back() == 0 && |
| src_rows[second_last_index] != dst_rows[second_last_index]) { |
| return; |
| } |
| |
| // Do nothing if the last item from the previous row will drop onto the |
| // current row, this will cause the items in the current row to shift to the |
| // right while the previous item stays in the previous row, which looks weird. |
| if (src_rows[index] > 1) { |
| // Find the last item from the previous row. |
| size_t previous_row_last_index = index; |
| while (src_rows[previous_row_last_index] == src_rows[index]) { |
| --previous_row_last_index; |
| } |
| |
| // Early return if the last item in the previous row changes rows. |
| if (src_rows[previous_row_last_index] != dst_rows[previous_row_last_index]) |
| return; |
| } |
| |
| // Helper to check whether the item at |item_index| will be nudged. |
| auto should_nudge = [&src_rows, &dst_rows, &index](size_t item_index) { |
| // Out of bounds. |
| if (item_index >= src_rows.size()) |
| return false; |
| |
| // Nudging happens when the item stays on the same row and is also on the |
| // same row as the item to be deleted was. |
| if (dst_rows[item_index] == src_rows[index] && |
| dst_rows[item_index] == src_rows[item_index]) { |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| // Starting from |index| go up and down while the nudge condition returns |
| // true. |
| std::vector<int> affected_indexes; |
| size_t loop_index; |
| |
| if (index > 0) { |
| loop_index = index - 1; |
| while (should_nudge(loop_index)) { |
| affected_indexes.push_back(loop_index); |
| --loop_index; |
| } |
| } |
| |
| loop_index = index + 1; |
| while (should_nudge(loop_index)) { |
| affected_indexes.push_back(loop_index); |
| ++loop_index; |
| } |
| |
| // Populate |nudge_data_| with the indexes in |affected_indexes| and their |
| // respective source and destination bounds. |
| nudge_data_.resize(affected_indexes.size()); |
| for (size_t i = 0; i < affected_indexes.size(); ++i) { |
| NudgeData data; |
| data.index = affected_indexes[i]; |
| data.src = src_rects[data.index]; |
| data.dst = dst_rects[data.index]; |
| nudge_data_[i] = data; |
| } |
| } |
| |
| void OverviewGrid::UpdateNudge(OverviewItem* item, double value) { |
| for (const auto& data : nudge_data_) { |
| DCHECK_LT(data.index, window_list_.size()); |
| |
| OverviewItem* nudged_item = window_list_[data.index].get(); |
| double nudge_param = value * value / 30.0; |
| nudge_param = base::ClampToRange(nudge_param, 0.0, 1.0); |
| gfx::RectF bounds = |
| gfx::Tween::RectFValueBetween(nudge_param, data.src, data.dst); |
| nudged_item->SetBounds(bounds, OVERVIEW_ANIMATION_NONE); |
| } |
| } |
| |
| void OverviewGrid::EndNudge() { |
| nudge_data_.clear(); |
| } |
| |
| void OverviewGrid::SlideWindowsIn() { |
| for (const auto& window_item : window_list_) |
| window_item->SlideWindowIn(); |
| } |
| |
| std::unique_ptr<ui::ScopedLayerAnimationSettings> |
| OverviewGrid::UpdateYPositionAndOpacity( |
| int new_y, |
| float opacity, |
| OverviewSession::UpdateAnimationSettingsCallback callback) { |
| DCHECK(!window_list_.empty()); |
| // Translate the window items to |new_y| with the opacity. Observe the |
| // animation of the first window. |
| std::unique_ptr<ui::ScopedLayerAnimationSettings> settings_to_observe; |
| for (const auto& window_item : window_list_) { |
| auto new_settings = |
| window_item->UpdateYPositionAndOpacity(new_y, opacity, callback); |
| if (!settings_to_observe && new_settings) |
| settings_to_observe = std::move(new_settings); |
| } |
| return settings_to_observe; |
| } |
| |
| aura::Window* OverviewGrid::GetTargetWindowOnLocation( |
| const gfx::PointF& location_in_screen, |
| OverviewItem* ignored_item) { |
| for (std::unique_ptr<OverviewItem>& item : window_list_) { |
| if (item.get() == ignored_item) |
| continue; |
| if (item->target_bounds().Contains(location_in_screen)) |
| return item->GetWindow(); |
| } |
| return nullptr; |
| } |
| |
| bool OverviewGrid::IsDesksBarViewActive() const { |
| DCHECK(features::IsVirtualDesksEnabled()); |
| |
| // The desk bar view is not active if there is only a single desk when |
| // overview is started. Once there are more than one desk, it should stay |
| // active even if the 2nd to last desk is deleted. |
| return DesksController::Get()->desks().size() > 1 || |
| (desks_bar_view_ && !desks_bar_view_->mini_views().empty()); |
| } |
| |
| void OverviewGrid::MaybeInitDesksWidget() { |
| if (!features::IsVirtualDesksEnabled() || desks_widget_) |
| return; |
| |
| desks_widget_ = DesksBarView::CreateDesksWidget( |
| root_window_, GetDesksWidgetBounds(root_window_, bounds_)); |
| desks_bar_view_ = new DesksBarView; |
| |
| // The following order of function calls is significant: SetContentsView() |
| // must be called before DesksBarView:: Init(). This is needed because the |
| // desks mini views need to access the widget to get the root window in order |
| // to know how to layout themselves. |
| desks_widget_->SetContentsView(desks_bar_view_); |
| desks_bar_view_->Init(); |
| |
| desks_widget_->Show(); |
| } |
| |
| void OverviewGrid::InitSelectionWidget(OverviewSession::Direction direction) { |
| selection_widget_ = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params; |
| params.type = views::Widget::InitParams::TYPE_POPUP; |
| params.keep_on_top = false; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| params.layer_type = ui::LAYER_SOLID_COLOR; |
| params.accept_events = false; |
| selection_widget_->set_focus_on_creation(false); |
| params.parent = root_window_->GetChildById(kShellWindowId_WallpaperContainer); |
| selection_widget_->Init(params); |
| aura::Window* widget_window = selection_widget_->GetNativeWindow(); |
| widget_window->SetProperty(kHideInDeskMiniViewKey, true); |
| // Disable the "bounce in" animation when showing the window. |
| ::wm::SetWindowVisibilityAnimationTransition(widget_window, |
| ::wm::ANIMATE_NONE); |
| widget_window->layer()->SetColor(kWindowSelectionColor); |
| widget_window->layer()->SetRoundedCornerRadius( |
| {kWindowSelectionRadius, kWindowSelectionRadius, kWindowSelectionRadius, |
| kWindowSelectionRadius}); |
| // Set the opacity to 0 initial so we can fade it in. |
| widget_window->layer()->SetOpacity(0.f); |
| selection_widget_->Show(); |
| |
| gfx::Rect target_bounds = |
| gfx::ToEnclosedRect(SelectedWindow()->target_bounds()); |
| ::wm::ConvertRectFromScreen(root_window_, &target_bounds); |
| gfx::Vector2d fade_out_direction = |
| GetSlideVectorForFadeIn(direction, target_bounds); |
| widget_window->SetBounds(target_bounds - fade_out_direction); |
| widget_window->SetName("OverviewModeSelector"); |
| |
| selector_shadow_ = std::make_unique<ui::Shadow>(); |
| selector_shadow_->Init(kWindowSelectionShadowElevation); |
| selector_shadow_->layer()->SetVisible(true); |
| selection_widget_->GetLayer()->SetMasksToBounds(false); |
| selection_widget_->GetLayer()->Add(selector_shadow_->layer()); |
| selector_shadow_->SetContentBounds(gfx::Rect(target_bounds.size())); |
| } |
| |
| void OverviewGrid::MoveSelectionWidget(OverviewSession::Direction direction, |
| bool recreate_selection_widget, |
| bool out_of_bounds, |
| bool animate) { |
| // If the selection widget is already active, fade it out in the selection |
| // direction. |
| if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) { |
| if (overview_session_->enter_exit_overview_type() == |
| OverviewSession::EnterExitOverviewType::kImmediateExit) { |
| ImmediatelyCloseWidgetOnExit(std::move(selection_widget_)); |
| } else { |
| // Animate the old selection widget and then destroy it. |
| views::Widget* old_selection = selection_widget_.get(); |
| aura::Window* old_selection_window = old_selection->GetNativeWindow(); |
| gfx::Vector2d fade_out_direction = |
| GetSlideVectorForFadeIn(direction, old_selection_window->bounds()); |
| |
| ScopedOverviewAnimationSettings settings( |
| OVERVIEW_ANIMATION_SELECTION_WINDOW, old_selection_window); |
| // CleanupAnimationObserver will delete itself (and the widget) when the |
| // motion animation is complete. Ownership over the observer is passed to |
| // the overview_session_->delegate() which has longer lifetime so that |
| // animations can continue even after the overview session is shut down. |
| std::unique_ptr<CleanupAnimationObserver> observer( |
| new CleanupAnimationObserver(std::move(selection_widget_))); |
| settings.AddObserver(observer.get()); |
| overview_session_->delegate()->AddExitAnimationObserver( |
| std::move(observer)); |
| old_selection->SetOpacity(0.f); |
| old_selection_window->SetBounds(old_selection_window->bounds() + |
| fade_out_direction); |
| old_selection->Hide(); |
| } |
| } |
| if (out_of_bounds) |
| return; |
| |
| if (!selection_widget_) |
| InitSelectionWidget(direction); |
| // Send an a11y alert so that if ChromeVox is enabled, the item label is |
| // read. |
| SelectedWindow()->SendAccessibleSelectionEvent(); |
| // The selection widget is moved to the newly selected item in the same |
| // grid. |
| MoveSelectionWidgetToTarget(animate); |
| } |
| |
| void OverviewGrid::MoveSelectionWidgetToTarget(bool animate) { |
| gfx::Rect bounds = gfx::ToEnclosingRect(SelectedWindow()->target_bounds()); |
| ::wm::ConvertRectFromScreen(root_window_, &bounds); |
| if (animate) { |
| ScopedOverviewAnimationSettings settings( |
| OVERVIEW_ANIMATION_SELECTION_WINDOW, |
| selection_widget_->GetNativeWindow()); |
| selection_widget_->SetBounds(bounds); |
| selection_widget_->SetOpacity(1.f); |
| |
| if (selector_shadow_) { |
| ScopedOverviewAnimationSettings settings( |
| OVERVIEW_ANIMATION_SELECTION_WINDOW_SHADOW, |
| selector_shadow_->shadow_layer()->GetAnimator()); |
| bounds.Inset(1, 1); |
| selector_shadow_->SetContentBounds( |
| gfx::Rect(gfx::Point(1, 1), bounds.size())); |
| } |
| return; |
| } |
| selection_widget_->SetBounds(bounds); |
| selection_widget_->SetOpacity(1.f); |
| if (selector_shadow_) { |
| bounds.Inset(1, 1); |
| selector_shadow_->SetContentBounds( |
| gfx::Rect(gfx::Point(1, 1), bounds.size())); |
| } |
| } |
| |
| std::vector<gfx::RectF> OverviewGrid::GetWindowRects( |
| const base::flat_set<OverviewItem*>& ignored_items) { |
| gfx::Rect total_bounds = bounds_; |
| |
| if (features::IsVirtualDesksEnabled()) { |
| const int desks_bar_height = DesksBarView::GetBarHeight(); |
| |
| // Always reduce the grid's height, even if the desks bar is not active. We |
| // do this to avoid changing the overview windows sizes once the desks bar |
| // becomes active, and we shift the grid downwards. |
| total_bounds.set_height(total_bounds.height() - desks_bar_height); |
| |
| if (IsDesksBarViewActive()) |
| total_bounds.Offset(0, desks_bar_height); |
| } |
| |
| // Windows occupy vertically centered area with additional vertical insets. |
| int horizontal_inset = |
| gfx::ToFlooredInt(std::min(kOverviewInsetRatio * total_bounds.width(), |
| kOverviewInsetRatio * total_bounds.height())); |
| int vertical_inset = |
| horizontal_inset + |
| kOverviewVerticalInset * (total_bounds.height() - 2 * horizontal_inset); |
| total_bounds.Inset(std::max(0, horizontal_inset - kWindowMargin), |
| std::max(0, vertical_inset - kWindowMargin)); |
| std::vector<gfx::RectF> rects; |
| |
| // Keep track of the lowest coordinate. |
| int max_bottom = total_bounds.y(); |
| |
| // Right bound of the narrowest row. |
| int min_right = total_bounds.right(); |
| // Right bound of the widest row. |
| int max_right = total_bounds.x(); |
| |
| // Keep track of the difference between the narrowest and the widest row. |
| // Initially this is set to the worst it can ever be assuming the windows fit. |
| int width_diff = total_bounds.width(); |
| |
| // Initially allow the windows to occupy all available width. Shrink this |
| // available space horizontally to find the breakdown into rows that achieves |
| // the minimal |width_diff|. |
| int right_bound = total_bounds.right(); |
| |
| // Determine the optimal height bisecting between |low_height| and |
| // |high_height|. Once this optimal height is known, |height_fixed| is set to |
| // true and the rows are balanced by repeatedly squeezing the widest row to |
| // cause windows to overflow to the subsequent rows. |
| int low_height = 2 * kWindowMargin; |
| int high_height = std::max(low_height, total_bounds.height() + 1); |
| int height = 0.5 * (low_height + high_height); |
| bool height_fixed = false; |
| |
| // Repeatedly try to fit the windows |rects| within |right_bound|. |
| // If a maximum |height| is found such that all window |rects| fit, this |
| // fitting continues while shrinking the |right_bound| in order to balance the |
| // rows. If the windows fit the |right_bound| would have been decremented at |
| // least once so it needs to be incremented once before getting out of this |
| // loop and one additional pass made to actually fit the |rects|. |
| // If the |rects| cannot fit (e.g. there are too many windows) the bisection |
| // will still finish and we might increment the |right_bound| once pixel extra |
| // which is acceptable since there is an unused margin on the right. |
| bool make_last_adjustment = false; |
| while (true) { |
| gfx::Rect overview_mode_bounds(total_bounds); |
| overview_mode_bounds.set_width(right_bound - total_bounds.x()); |
| bool windows_fit = FitWindowRectsInBounds( |
| overview_mode_bounds, std::min(kMaxHeight + 2 * kWindowMargin, height), |
| ignored_items, &rects, &max_bottom, &min_right, &max_right); |
| |
| if (height_fixed) { |
| if (!windows_fit) { |
| // Revert the previous change to |right_bound| and do one last pass. |
| right_bound++; |
| make_last_adjustment = true; |
| break; |
| } |
| // Break if all the windows are zero-width at the current scale. |
| if (max_right <= total_bounds.x()) |
| break; |
| } else { |
| // Find the optimal row height bisecting between |low_height| and |
| // |high_height|. |
| if (windows_fit) |
| low_height = height; |
| else |
| high_height = height; |
| height = 0.5 * (low_height + high_height); |
| // When height can no longer be improved, start balancing the rows. |
| if (height == low_height) |
| height_fixed = true; |
| } |
| |
| if (windows_fit && height_fixed) { |
| if (max_right - min_right <= width_diff) { |
| // Row alignment is getting better. Try to shrink the |right_bound| in |
| // order to squeeze the widest row. |
| right_bound = max_right - 1; |
| width_diff = max_right - min_right; |
| } else { |
| // Row alignment is getting worse. |
| // Revert the previous change to |right_bound| and do one last pass. |
| right_bound++; |
| make_last_adjustment = true; |
| break; |
| } |
| } |
| } |
| // Once the windows in |window_list_| no longer fit, the change to |
| // |right_bound| was reverted. Perform one last pass to position the |rects|. |
| if (make_last_adjustment) { |
| gfx::Rect overview_mode_bounds(total_bounds); |
| overview_mode_bounds.set_width(right_bound - total_bounds.x()); |
| FitWindowRectsInBounds( |
| overview_mode_bounds, std::min(kMaxHeight + 2 * kWindowMargin, height), |
| ignored_items, &rects, &max_bottom, &min_right, &max_right); |
| } |
| |
| gfx::Vector2dF offset(0, (total_bounds.bottom() - max_bottom) / 2.f); |
| for (size_t i = 0; i < rects.size(); ++i) |
| rects[i] += offset; |
| return rects; |
| } |
| |
| bool OverviewGrid::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) { |
| const size_t window_count = window_list_.size(); |
| out_rects->resize(window_count); |
| |
| // Start in the top-left corner of |bounds|. |
| int left = bounds.x(); |
| int top = bounds.y(); |
| |
| // Keep track of the lowest coordinate. |
| *out_max_bottom = bounds.y(); |
| |
| // Right bound of the narrowest row. |
| *out_min_right = bounds.right(); |
| // Right bound of the widest row. |
| *out_max_right = bounds.x(); |
| |
| // All elements are of same height and only the height is necessary to |
| // determine each item's scale. |
| const gfx::Size item_size(0, height); |
| for (size_t i = 0u; i < window_count; ++i) { |
| if (window_list_[i]->animating_to_close() || |
| ignored_items.contains(window_list_[i].get())) { |
| continue; |
| } |
| |
| const gfx::RectF target_bounds = window_list_[i]->GetTargetBoundsInScreen(); |
| int width = std::max( |
| 1, gfx::ToFlooredInt(target_bounds.width() * |
| window_list_[i]->GetItemScale(item_size)) + |
| 2 * kWindowMargin); |
| switch (window_list_[i]->GetWindowDimensionsType()) { |
| case ScopedOverviewTransformWindow::GridWindowFillMode::kLetterBoxed: |
| width = ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold * |
| height; |
| break; |
| case ScopedOverviewTransformWindow::GridWindowFillMode::kPillarBoxed: |
| width = height / |
| ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold; |
| break; |
| default: |
| break; |
| } |
| |
| if (left + width > bounds.right()) { |
| // Move to the next row if possible. |
| if (*out_min_right > left) |
| *out_min_right = left; |
| if (*out_max_right < left) |
| *out_max_right = left; |
| top += height; |
| |
| // Check if the new row reaches the bottom or if the first item in the new |
| // row does not fit within the available width. |
| if (top + height > bounds.bottom() || |
| bounds.x() + width > bounds.right()) { |
| return false; |
| } |
| left = bounds.x(); |
| } |
| |
| // Position the current rect. |
| (*out_rects)[i] = gfx::RectF(gfx::Rect(left, top, width, height)); |
| |
| // Increment horizontal position using sanitized positive |width()|. |
| left += gfx::ToRoundedInt((*out_rects)[i].width()); |
| |
| *out_max_bottom = top + height; |
| } |
| |
| // Update the narrowest and widest row width for the last row. |
| if (*out_min_right > left) |
| *out_min_right = left; |
| if (*out_max_right < left) |
| *out_max_right = left; |
| |
| return true; |
| } |
| |
| std::vector<std::unique_ptr<OverviewItem>>::iterator |
| OverviewGrid::GetOverviewItemIterContainingWindow(aura::Window* window) { |
| return std::find_if(window_list_.begin(), window_list_.end(), |
| [window](std::unique_ptr<OverviewItem>& item) { |
| return item->GetWindow() == window; |
| }); |
| } |
| |
| size_t OverviewGrid::GetOverviewItemIndex(OverviewItem* item) const { |
| auto iter = std::find_if(window_list_.begin(), window_list_.end(), |
| base::MatchesUniquePtr(item)); |
| DCHECK(iter != window_list_.end()); |
| return iter - window_list_.begin(); |
| } |
| |
| void OverviewGrid::AddDraggedWindowIntoOverviewOnDragEnd( |
| aura::Window* dragged_window) { |
| DCHECK(overview_session_); |
| if (overview_session_->IsWindowInOverview(dragged_window)) |
| return; |
| |
| // Update the dragged window's bounds before adding it to overview. The |
| // dragged window might have resized to a smaller size if the drag |
| // happens on tab(s). |
| if (wm::IsDraggingTabs(dragged_window)) { |
| const gfx::Rect old_bounds = dragged_window->bounds(); |
| // We need to temporarily disable the dragged window's ability to merge |
| // into another window when changing the dragged window's bounds, so |
| // that the dragged window doesn't merge into another window because of |
| // its changed bounds. |
| dragged_window->SetProperty(ash::kCanAttachToAnotherWindowKey, false); |
| TabletModeWindowState::UpdateWindowPosition( |
| wm::GetWindowState(dragged_window), /*animate=*/false); |
| const gfx::Rect new_bounds = dragged_window->bounds(); |
| if (old_bounds != new_bounds) { |
| // It's for smoother animation. |
| gfx::Transform transform = |
| ScopedOverviewTransformWindow::GetTransformForRect( |
| gfx::RectF(new_bounds), gfx::RectF(old_bounds)); |
| dragged_window->SetTransform(transform); |
| } |
| dragged_window->ClearProperty(ash::kCanAttachToAnotherWindowKey); |
| } |
| |
| overview_session_->AddItem(dragged_window, /*reposition=*/false, |
| /*animate=*/false); |
| } |
| |
| } // namespace ash |