// Copyright 2019 The Chromium Authors
// 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 "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/window_occlusion_calculator.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_metrics.h"
#include "ash/wm/overview/overview_test_base.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/workspace/backdrop_controller.h"
#include "ash/wm/workspace/workspace_layout_manager.h"
#include "ash/wm/workspace_controller.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/test_utils.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/wm/core/window_util.h"
namespace ash {
class OverviewGridTest : public AshTestBase {
public:
OverviewGridTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{features::kSnapGroup,
features::kOsSettingsRevampWayfinding},
/*disabled_features=*/{features::kForestFeature});
}
OverviewGridTest(const OverviewGridTest&) = delete;
OverviewGridTest& operator=(const OverviewGridTest&) = delete;
~OverviewGridTest() override = default;
void InitializeGrid(
const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows) {
aura::Window* root = Shell::GetPrimaryRootWindow();
grid_ = std::make_unique<OverviewGrid>(
root, windows, nullptr, window_occlusion_calculator_.AsWeakPtr());
}
void CheckAnimationStates(
const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows,
const std::vector<gfx::RectF>& target_bounds,
const std::vector<bool>& expected_start_animations,
const std::vector<bool>& expected_end_animations,
std::optional<size_t> selected_window_index = std::nullopt) {
ASSERT_EQ(windows.size(), target_bounds.size());
ASSERT_EQ(windows.size(), expected_start_animations.size());
ASSERT_EQ(windows.size(), expected_end_animations.size());
InitializeGrid(windows);
ASSERT_EQ(windows.size(), grid_->item_list().size());
// The default values are to animate.
for (const auto& item : grid_->item_list()) {
SCOPED_TRACE("Initial values");
EXPECT_TRUE(item->should_animate_when_entering());
EXPECT_TRUE(item->should_animate_when_exiting());
}
grid_->CalculateWindowListAnimationStates(
/*selected_item=*/nullptr, OverviewTransition::kEnter, target_bounds);
for (size_t i = 0; i < grid_->item_list().size(); ++i) {
SCOPED_TRACE("Enter animation, window " + base::NumberToString(i + 1));
EXPECT_EQ(expected_start_animations[i],
grid_->item_list()[i]->should_animate_when_entering());
}
for (size_t i = 0; i < grid_->item_list().size(); ++i) {
grid_->item_list()[i]->set_target_bounds_for_testing(target_bounds[i]);
}
auto* selected_item = selected_window_index
? grid_->item_list()[*selected_window_index].get()
: nullptr;
grid_->CalculateWindowListAnimationStates(selected_item,
OverviewTransition::kExit, {});
for (size_t i = 0; i < grid_->item_list().size(); ++i) {
SCOPED_TRACE("Exit animation, window " + base::NumberToString(i + 1));
EXPECT_EQ(expected_end_animations[i],
grid_->item_list()[i]->should_animate_when_exiting());
}
}
SplitViewController* split_view_controller() {
return SplitViewController::Get(Shell::GetPrimaryRootWindow());
}
OverviewGrid* grid() { return grid_.get(); }
private:
WindowOcclusionCalculator window_occlusion_calculator_;
std::unique_ptr<OverviewGrid> grid_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that with only one window, we always animate.
TEST_F(OverviewGridTest, AnimateWithSingleWindow) {
auto window = CreateTestWindow(gfx::Rect(100, 100));
CheckAnimationStates({window.get()}, {gfx::RectF(100.f, 100.f)}, {true},
{true});
}
// Tests that if both the source and destination is hidden, there are no
// animations on the second window.
TEST_F(OverviewGridTest, SourceDestinationBothHidden) {
auto window1 = CreateTestWindow(gfx::Rect(400, 400));
auto window2 = CreateTestWindow(gfx::Rect(100, 100));
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, false}, {true, false});
}
// Tests that are animations if the destination bounds are shown.
TEST_F(OverviewGridTest, SourceHiddenDestinationShown) {
auto window1 = CreateTestWindow(gfx::Rect(400, 400));
auto window2 = CreateTestWindow(gfx::Rect(100, 100));
std::vector<gfx::RectF> target_bounds = {
gfx::RectF(100.f, 100.f), gfx::RectF(400.f, 400.f, 100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, true}, {true, true});
}
// Tests that are animations if the source bounds are shown.
TEST_F(OverviewGridTest, SourceShownDestinationHidden) {
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(400, 400));
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, true}, {true, true});
}
// Tests that a window that is in the union of two other windows, but is still
// shown will be animated.
TEST_F(OverviewGridTest, SourceShownButInTheUnionOfTwoOtherWindows) {
// Create three windows, the union of the first two windows will be
// gfx::Rect(0,0,200,200). Window 3 will be in that union, but should still
// animate since its not fully occluded.
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(50, 50, 150, 150));
auto window3 = CreateTestWindow(gfx::Rect(50, 200));
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get(), window3.get()},
target_bounds, {true, true, true}, {true, true, true});
}
// Tests that an always on top window will take precedence over a normal
// window.
TEST_F(OverviewGridTest, AlwaysOnTopWindow) {
// Create two windows, the second is always on top and covers the first
// window. So the first window will not animate.
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(400, 400));
window2->SetProperty(aura::client::kZOrderingKey,
ui::ZOrderLevel::kFloatingWindow);
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{false, true}, {false, true});
}
// Tests that windows that are minimized are animated as expected.
TEST_F(OverviewGridTest, MinimizedWindows) {
// Create 3 windows with the second and third windows being minimized. Both
// the minimized window bounds are not occluded but only the third window is
// animated because the target bounds for the first window is blocked.
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(400, 400));
auto window3 = CreateTestWindow(gfx::Rect(400, 400));
WindowState::Get(window2.get())->Minimize();
WindowState::Get(window3.get())->Minimize();
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f),
gfx::RectF(200.f, 200.f)};
CheckAnimationStates({window1.get(), window2.get(), window3.get()},
target_bounds, {true, false, true}, {true, false, true});
}
TEST_F(OverviewGridTest, SelectedWindow) {
// Create 3 windows with the third window being maximized. All windows are
// visible on entering, so they should all be animated. On exit we select the
// third window which is maximized, so the other two windows should not
// animate.
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(400, 400));
auto window3 = CreateTestWindow(gfx::Rect(400, 400));
WindowState::Get(window3.get())->Maximize();
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get(), window3.get()},
target_bounds, {true, true, true}, {false, false, true},
std::make_optional(2u));
}
TEST_F(OverviewGridTest, WindowWithBackdrop) {
// Create one non resizable window and one normal window and verify that the
// backdrop shows over the non resizable window, and that normal window
// becomes maximized upon entering tablet mode.
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(400, 400));
window1->SetProperty(aura::client::kResizeBehaviorKey,
aura::client::kResizeBehaviorNone);
wm::ActivateWindow(window1.get());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
BackdropController* backdrop_controller =
GetWorkspaceControllerForContext(window1.get())
->layout_manager()
->backdrop_controller();
EXPECT_EQ(window1.get(), backdrop_controller->GetTopmostWindowWithBackdrop());
EXPECT_TRUE(backdrop_controller->backdrop_window());
EXPECT_TRUE(WindowState::Get(window2.get())->IsMaximized());
// Tests that the second window despite being larger than the first window
// does not animate as it is hidden behind the backdrop. On exit, it still
// animates as the backdrop is not visible yet.
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, false}, {true, true});
}
TEST_F(OverviewGridTest, DestinationPartiallyOffscreenWindow) {
UpdateDisplay("500x400");
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(100, 100));
// Position |window2|'s destination to be partially offscreen. Tests that it
// still animates because the onscreen portion is not occluded by |window1|.
std::vector<gfx::RectF> target_bounds = {
gfx::RectF(100.f, 100.f), gfx::RectF(350.f, 100.f, 100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, true}, {true, true});
// Maximize |window1|. |window2| should no longer animate since the parts of
// it that are onscreen are fully occluded.
WindowState::Get(window1.get())->Maximize();
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, false}, {true, false});
}
TEST_F(OverviewGridTest, SourcePartiallyOffscreenWindow) {
UpdateDisplay("500x400");
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
// Create |window2| to be partially offscreen.
auto window2 = CreateTestWindow(gfx::Rect(450, 100, 100, 100));
// Tests that it still animates because the onscreen portion is not occluded
// by |window1|.
std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
gfx::RectF(200.f, 200.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, true}, {true, true});
// Maximize |window1|. |window2| should no longer animate since the parts of
// it that are onscreen are fully occluded.
WindowState::Get(window1.get())->Maximize();
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, false}, {true, false});
}
// Tests that windows whose destination is fully offscreen never animate.
TEST_F(OverviewGridTest, FullyOffscreenWindow) {
UpdateDisplay("500x400");
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(100, 100));
std::vector<gfx::RectF> target_bounds = {
gfx::RectF(100.f, 100.f), gfx::RectF(450.f, 450.f, 100.f, 100.f)};
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, false}, {true, false});
WindowState::Get(window1.get())->Maximize();
CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
{true, false}, {true, false});
}
// Tests that only one window animates when entering overview from splitview
// double snapped.
TEST_F(OverviewGridTest, SnappedWindow) {
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(100, 100));
auto window3 = CreateTestWindow(gfx::Rect(100, 100));
wm::ActivateWindow(window1.get());
wm::ActivateWindow(window2.get());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
// Snap |window2| and check that |window3| is maximized.
split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
EXPECT_TRUE(WindowState::Get(window3.get())->IsMaximized());
// We cannot create a grid object like in the other tests because creating a
// grid calls |GetGridBoundsInScreen| with split view state both snapped which
// is an unnatural state.
EnterOverview();
// Tests that |window3| is not animated even though its bounds are larger than
// |window2| because it is fully occluded by |window1| + |window2| and the
// split view divider.
auto* item2 = GetOverviewItemForWindow(window2.get());
auto* item3 = GetOverviewItemForWindow(window3.get());
EXPECT_TRUE(item2->should_animate_when_entering());
EXPECT_FALSE(item3->should_animate_when_entering());
}
// TODO(b/350771229): Replace `OverviewGridTest` with `OverviewGridForestTest`
// once `kForestFeature` is launched.
TEST_F(OverviewGridTest, RecordsDelayedDeskBarPresentationMetric) {
ui::ScopedAnimationDurationScaleMode animation_scale(
ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
// Since the windows are not maximized, the desk bar should open after
// the overview animation is complete, causing
// `kOverviewDelayedDeskBarPresentationHistogram` to be recorded.
std::unique_ptr<aura::Window> window1(CreateTestWindow());
std::unique_ptr<aura::Window> window2(CreateTestWindow());
ui::Compositor* const compositor = window1->GetHost()->compositor();
base::HistogramTester histogram_tester;
ToggleOverview();
ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
histogram_tester.ExpectTotalCount(
kOverviewDelayedDeskBarPresentationHistogram, 0);
WaitForOverviewEnterAnimation();
ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
histogram_tester.ExpectTotalCount(
kOverviewDelayedDeskBarPresentationHistogram, 1);
}
// TODO(b/350771229): Replace `OverviewGridTest` with `OverviewGridForestTest`
// once `kForestFeature` is launched.
TEST_F(OverviewGridTest, DoesNotRecordDelayedDeskBarPresentationMetric) {
ui::ScopedAnimationDurationScaleMode animation_scale(
ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
// Since the windows are maximized, the desk bar should open immediately when
// we enter overview and `kOverviewDelayedDeskBarPresentationHistogram` should
// not be recorded.
std::unique_ptr<aura::Window> window1(CreateTestWindow());
std::unique_ptr<aura::Window> window2(CreateTestWindow());
WindowState::Get(window1.get())->Maximize();
WindowState::Get(window2.get())->Maximize();
ui::Compositor* const compositor = window1->GetHost()->compositor();
base::HistogramTester histogram_tester;
ToggleOverview();
ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
WaitForOverviewEnterAnimation();
ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
histogram_tester.ExpectTotalCount(
kOverviewDelayedDeskBarPresentationHistogram, 0);
}
class OverviewGridForestTest : public OverviewTestBase {
public:
OverviewGridForestTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{features::kOsSettingsRevampWayfinding,
features::kForestFeature,
features::kDeskBarWindowOcclusionOptimization},
/*disabled_features=*/{});
}
OverviewGridForestTest(const OverviewGridForestTest&) = delete;
OverviewGridForestTest& operator=(const OverviewGridForestTest&) = delete;
~OverviewGridForestTest() override = default;
// Calculates `OverviewItemBase::should_animate_when_exiting_`. The reason we
// do it like this is because this is normally called during shutdown, and
// then the grid and items objects are destroyed. Note that this function
// assumes one root window.
void CalculateShouldAnimateWhenExiting(
OverviewItemBase* selected_item = nullptr) {
ASSERT_EQ(1u, Shell::GetAllRootWindows().size());
OverviewGrid* grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(grid);
grid->CalculateWindowListAnimationStates(selected_item,
OverviewTransition::kExit,
/*target_bounds=*/{});
}
// Checks expected against actual enter and exit animation values. Note that
// this function assumes one root window.
void VerifyAnimationStates(
const std::vector<bool>& expected_enter_animations,
const std::vector<bool>& expected_exit_animations) {
ASSERT_EQ(1u, Shell::GetAllRootWindows().size());
OverviewGrid* grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(grid);
const std::vector<std::unique_ptr<OverviewItemBase>>& overview_items =
grid->item_list();
if (!expected_enter_animations.empty()) {
ASSERT_EQ(overview_items.size(), expected_enter_animations.size());
for (size_t i = 0; i < overview_items.size(); ++i) {
EXPECT_EQ(expected_enter_animations[i],
overview_items[i]->should_animate_when_entering());
}
}
if (!expected_exit_animations.empty()) {
ASSERT_EQ(overview_items.size(), expected_exit_animations.size());
for (size_t i = 0; i < overview_items.size(); ++i) {
EXPECT_EQ(expected_exit_animations[i],
overview_items[i]->should_animate_when_exiting());
}
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that with only one window, we always animate.
TEST_F(OverviewGridForestTest, AnimateWithSingleWindow) {
auto window = CreateAppWindow(gfx::Rect(100, 100));
ToggleOverview();
VerifyAnimationStates({true}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true});
}
// Tests that are animations if the destination bounds are shown.
TEST_F(OverviewGridForestTest, SourceHiddenDestinationShown) {
auto window1 = CreateAppWindow(gfx::Rect(100, 100));
auto window2 = CreateAppWindow(gfx::Rect(200, 200));
ToggleOverview();
VerifyAnimationStates({true, true}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true, true});
}
// Tests that are animations if the source bounds are shown.
TEST_F(OverviewGridForestTest, SourceShownDestinationHidden) {
auto window1 = CreateAppWindow(gfx::Rect(100, 100));
WindowState::Get(window1.get())->Maximize();
auto window2 = CreateAppWindow(gfx::Rect(400, 400));
ToggleOverview();
VerifyAnimationStates({true, true}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true, true});
}
// Tests that a window that is in the union of two other windows, but is still
// shown will be animated.
TEST_F(OverviewGridForestTest, SourceShownButInTheUnionOfTwoOtherWindows) {
// Create three windows, the union of the first two windows will be
// gfx::Rect(0, 0, 200, 200). Window 3 will be in that union, but should still
// animate since its not fully occluded.
auto window3 = CreateAppWindow(gfx::Rect(50, 200));
auto window2 = CreateAppWindow(gfx::Rect(50, 50, 150, 150));
auto window1 = CreateAppWindow(gfx::Rect(100, 100));
ToggleOverview();
VerifyAnimationStates({true, true, true}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true, true, true});
}
// Tests that an always on top window will still be animated even if its source
// and destination bounds are covered.
TEST_F(OverviewGridForestTest, AlwaysOnTopWindow) {
UpdateDisplay("800x600");
// Create two windows, even if `window1` is maximized, `window2` will still
// animate since it is always on top.
auto window2 = CreateAppWindow(gfx::Rect(100, 100));
window2->SetProperty(aura::client::kZOrderingKey,
ui::ZOrderLevel::kFloatingWindow);
auto window1 = CreateAppWindow(gfx::Rect(100, 100));
WindowState::Get(window1.get())->Maximize();
ToggleOverview();
VerifyAnimationStates({true, true}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true, true});
}
// Tests that windows that are minimized are animated as expected.
TEST_F(OverviewGridForestTest, MinimizedWindows) {
UpdateDisplay("800x600");
auto window3 = CreateAppWindow(gfx::Rect(800, 600));
WindowState::Get(window3.get())->Minimize();
auto window2 = CreateAppWindow(gfx::Rect(800, 600));
WindowState::Get(window2.get())->Minimize();
auto window1 = CreateAppWindow(gfx::Rect(10, 10, 780, 580));
// The minimized windows do not animate since their source is hidden, and
// their destination is blocked by the near maximized window.
ToggleOverview();
VerifyAnimationStates({true, false, false}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true, false, false});
}
TEST_F(OverviewGridForestTest, SelectedWindow) {
// Create 3 windows with the third window being maximized. All windows are
// visible on entering, so they should all be animated. On exit we select the
// third window which is maximized, so the other two windows should not
// animate.
auto window3 = CreateAppWindow(gfx::Rect(400, 400));
WindowState::Get(window3.get())->Maximize();
auto window2 = CreateAppWindow(gfx::Rect(400, 400));
auto window1 = CreateAppWindow(gfx::Rect(100, 100));
ToggleOverview();
VerifyAnimationStates({true, true, true}, {});
OverviewItemBase* selected_item = GetOverviewItemForWindow(window3.get());
CalculateShouldAnimateWhenExiting(selected_item);
VerifyAnimationStates({}, {false, false, true});
}
TEST_F(OverviewGridForestTest, WindowWithBackdrop) {
// Create one non resizable window and one normal window and verify that the
// backdrop shows over the non resizable window, and that normal window
// becomes maximized upon entering tablet mode.
auto window1 = CreateTestWindow(gfx::Rect(100, 100));
auto window2 = CreateTestWindow(gfx::Rect(400, 400));
window1->SetProperty(aura::client::kResizeBehaviorKey,
aura::client::kResizeBehaviorNone);
wm::ActivateWindow(window1.get());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
BackdropController* backdrop_controller =
GetWorkspaceControllerForContext(window1.get())
->layout_manager()
->backdrop_controller();
EXPECT_EQ(window1.get(), backdrop_controller->GetTopmostWindowWithBackdrop());
EXPECT_TRUE(backdrop_controller->backdrop_window());
EXPECT_TRUE(WindowState::Get(window2.get())->IsMaximized());
// Tests that the second window despite being larger than the first window
// does not animate as it is hidden behind the backdrop. On exit, it still
// animates as the backdrop is not visible yet.
ToggleOverview();
VerifyAnimationStates({true, false}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true, true});
}
TEST_F(OverviewGridForestTest, SourcePartiallyOffscreenWindow) {
UpdateDisplay("500x400");
// Create `window2` to be partially offscreen.
auto window2 = CreateAppWindow(gfx::Rect(450, 100, 100, 100));
auto window1 = CreateAppWindow(gfx::Rect(100, 100));
// Tests that it still animates because the onscreen portion is not occluded
// by `window1`.
ToggleOverview();
VerifyAnimationStates({true, true}, {});
CalculateShouldAnimateWhenExiting();
VerifyAnimationStates({}, {true, true});
ToggleOverview();
// Maximize `window1`. `window2` should no longer animate since the parts of
// it that are onscreen are fully occluded.
WindowState::Get(window1.get())->Maximize();
ToggleOverview();
VerifyAnimationStates({true, false}, {});
}
// Tests that windows whose destination is partially or fully offscreen never
// animate.
TEST_F(OverviewGridForestTest, PartialAndFullOffscreenWindow) {
UpdateDisplay("800x600");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
// With this display size, the 9th and 10th windows will be partially
// offscreen and the 11th and 12th windows will be fully offscreen once we
// enter overview. Since the earlier created windows are lower on the MRU
// order, this equals the 3rd and 4th windows and the 1st and 2nd window
// respectively.
std::vector<std::unique_ptr<aura::Window>> windows;
for (int i = 0; i < 12; ++i) {
windows.push_back(CreateAppWindow(gfx::Rect(100, 100)));
}
// Enter overview and assert that we have one partially offscreen overview
// item and one fully offscreen overview item.
ToggleOverview();
OverviewItemBase* partially_offscreen_item =
GetOverviewItemForWindow(windows[2].get());
OverviewItemBase* fully_offscreen_item =
GetOverviewItemForWindow(windows[0].get());
ASSERT_FALSE(gfx::RectF(800.f, 600.f)
.Contains(partially_offscreen_item->target_bounds()));
ASSERT_TRUE(gfx::RectF(800.f, 600.f)
.Intersects(partially_offscreen_item->target_bounds()));
ASSERT_FALSE(
gfx::RectF(800.f, 600.f).Contains(fully_offscreen_item->target_bounds()));
ASSERT_FALSE(gfx::RectF(800.f, 600.f)
.Intersects(fully_offscreen_item->target_bounds()));
EXPECT_FALSE(partially_offscreen_item->should_animate_when_entering());
EXPECT_FALSE(fully_offscreen_item->should_animate_when_entering());
CalculateShouldAnimateWhenExiting();
EXPECT_FALSE(partially_offscreen_item->should_animate_when_exiting());
EXPECT_FALSE(fully_offscreen_item->should_animate_when_exiting());
}
// Tests that only one window animates when entering overview from splitview
// double snapped.
TEST_F(OverviewGridForestTest, SnappedWindow) {
auto window3 = CreateAppWindow(gfx::Rect(100, 100));
auto window2 = CreateAppWindow(gfx::Rect(100, 100));
auto window1 = CreateAppWindow(gfx::Rect(100, 100));
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
auto* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
// Snap `window2` and check that `window3` is maximized.
split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);
EXPECT_TRUE(WindowState::Get(window3.get())->IsMaximized());
// Tests that `window3` is not animated even though its bounds are larger than
// `window2` because it is fully occluded by `window1` + `window2` and the
// split view divider.
ToggleOverview();
VerifyAnimationStates({true, false}, {});
}
} // namespace ash