blob: ab1e15ee789545e5026e82a5738aca1b48529b1d [file] [log] [blame]
// Copyright 2019 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/splitview/split_view_drag_indicators.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/overview/window_grid.h"
#include "ash/wm/overview/window_selector.h"
#include "ash/wm/overview/window_selector_controller.h"
#include "ash/wm/overview/window_selector_item.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "services/ws/public/mojom/window_tree_constants.mojom.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/widget/widget.h"
namespace ash {
class SplitViewDragIndicatorsTest : public AshTestBase {
public:
SplitViewDragIndicatorsTest() = default;
~SplitViewDragIndicatorsTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
// Ensure calls to EnableTabletModeWindowManager complete.
base::RunLoop().RunUntilIdle();
Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
base::RunLoop().RunUntilIdle();
}
void ToggleOverview() {
auto* window_selector_controller =
Shell::Get()->window_selector_controller();
window_selector_controller->ToggleOverview();
if (!window_selector_controller->IsSelecting()) {
window_selector_ = nullptr;
split_view_drag_indicators_ = nullptr;
return;
}
window_selector_ =
Shell::Get()->window_selector_controller()->window_selector();
ASSERT_TRUE(window_selector_);
split_view_drag_indicators_ =
window_selector_->split_view_drag_indicators();
}
SplitViewController* split_view_controller() {
return Shell::Get()->split_view_controller();
}
IndicatorState indicator_state() {
DCHECK(split_view_drag_indicators_);
return split_view_drag_indicators_->current_indicator_state();
}
bool IsPreviewAreaShowing() {
return indicator_state() == IndicatorState::kPreviewAreaLeft ||
indicator_state() == IndicatorState::kPreviewAreaRight;
}
WindowSelectorItem* GetOverviewItemForWindow(aura::Window* window,
int grid_index = 0) {
auto& windows =
window_selector_->grid_list_for_testing()[grid_index]->window_list();
auto iter =
std::find_if(windows.cbegin(), windows.cend(),
[window](const std::unique_ptr<WindowSelectorItem>& item) {
return item->Contains(window);
});
if (iter == windows.end())
return nullptr;
return iter->get();
}
int GetEdgeInset(int screen_width) const {
return screen_width * kHighlightScreenPrimaryAxisRatio +
kHighlightScreenEdgePaddingDp;
}
// Creates a window which cannot be snapped by splitview.
std::unique_ptr<aura::Window> CreateUnsnappableWindow() {
std::unique_ptr<aura::Window> window(CreateTestWindow());
window->SetProperty(aura::client::kResizeBehaviorKey,
ws::mojom::kResizeBehaviorNone);
return window;
}
protected:
SplitViewDragIndicators* split_view_drag_indicators_ = nullptr;
WindowSelector* window_selector_ = nullptr;
private:
DISALLOW_COPY_AND_ASSIGN(SplitViewDragIndicatorsTest);
};
TEST_F(SplitViewDragIndicatorsTest, Dragging) {
Shell::Get()->aura_env()->set_throttle_input_on_resize_for_testing(false);
UpdateDisplay("800x600");
const int screen_width = 800;
const int edge_inset = GetEdgeInset(screen_width);
std::unique_ptr<aura::Window> right_window(CreateTestWindow());
std::unique_ptr<aura::Window> left_window(CreateTestWindow());
ui::test::EventGenerator* generator = GetEventGenerator();
ToggleOverview();
WindowSelectorItem* left_selector_item =
GetOverviewItemForWindow(left_window.get());
WindowSelectorItem* right_selector_item =
GetOverviewItemForWindow(right_window.get());
// The inset on each side of the screen which is a snap region. Items dragged
// to and released under this region will get snapped.
const int drag_offset = 5;
const int drag_offset_snap_region = 48;
const int minimum_drag_offset = 96;
// The selector item has a margin which does not accept events. Inset any
// event aimed at the selector items edge so events will reach it.
const int selector_item_inset = 20;
// Check the two windows set up have a region which is under no snap region, a
// region that is under the left snap region and a region that is under the
// right snap region.
ASSERT_GT(left_selector_item->target_bounds().CenterPoint().x(), edge_inset);
ASSERT_LT(
left_selector_item->target_bounds().origin().x() + selector_item_inset,
edge_inset);
ASSERT_GT(right_selector_item->target_bounds().right() - selector_item_inset,
screen_width - edge_inset);
// Verify if the drag is not started in either snap region, the drag still
// must move by |drag_offset| before split view acknowledges the drag (ie.
// starts moving the selector item).
generator->set_current_screen_location(
left_selector_item->target_bounds().CenterPoint());
generator->PressLeftButton();
const gfx::Rect left_original_bounds = left_selector_item->target_bounds();
generator->MoveMouseBy(drag_offset - 1, 0);
EXPECT_EQ(left_original_bounds, left_selector_item->target_bounds());
generator->MoveMouseBy(1, 0);
EXPECT_NE(left_original_bounds, left_selector_item->target_bounds());
generator->ReleaseLeftButton();
// Verify if the drag is started in the left snap region, the drag needs to
// move by |drag_offset_snap_region| towards the right side of the screen
// before split view acknowledges the drag (shows the preview area).
ASSERT_TRUE(Shell::Get()->window_selector_controller()->IsSelecting());
generator->set_current_screen_location(gfx::Point(
left_selector_item->target_bounds().origin().x() + selector_item_inset,
left_selector_item->target_bounds().CenterPoint().y()));
generator->PressLeftButton();
generator->MoveMouseBy(-drag_offset, 0);
EXPECT_FALSE(IsPreviewAreaShowing());
generator->MoveMouseBy(drag_offset_snap_region, 0);
generator->MoveMouseBy(-minimum_drag_offset, 0);
EXPECT_TRUE(IsPreviewAreaShowing());
// Drag back to the middle before releasing so that we stay in overview mode
// on release.
generator->MoveMouseTo(left_original_bounds.CenterPoint());
generator->ReleaseLeftButton();
// Verify if the drag is started in the right snap region, the drag needs to
// move by |drag_offset_snap_region| towards the left side of the screen
// before split view acknowledges the drag.
ASSERT_TRUE(Shell::Get()->window_selector_controller()->IsSelecting());
generator->set_current_screen_location(gfx::Point(
right_selector_item->target_bounds().right() - selector_item_inset,
right_selector_item->target_bounds().CenterPoint().y()));
generator->PressLeftButton();
generator->MoveMouseBy(drag_offset, 0);
EXPECT_FALSE(IsPreviewAreaShowing());
generator->MoveMouseBy(-drag_offset_snap_region, 0);
generator->MoveMouseBy(minimum_drag_offset, 0);
EXPECT_TRUE(IsPreviewAreaShowing());
}
// Verify the split view preview area becomes visible when expected.
TEST_F(SplitViewDragIndicatorsTest, PreviewAreaVisibility) {
UpdateDisplay("800x600");
const int screen_width = 800;
const int edge_inset = GetEdgeInset(screen_width);
std::unique_ptr<aura::Window> window(CreateTestWindow());
ToggleOverview();
// Verify the preview area is visible when |selector_item|'s x is in the
// range [0, edge_inset] or [screen_width - edge_inset - 1, screen_width].
WindowSelectorItem* selector_item = GetOverviewItemForWindow(window.get());
ASSERT_TRUE(selector_item);
const gfx::Point start_location(selector_item->target_bounds().CenterPoint());
// Drag horizontally to avoid activating drag to close.
const int y = start_location.y();
window_selector_->InitiateDrag(selector_item, start_location);
EXPECT_FALSE(IsPreviewAreaShowing());
window_selector_->Drag(selector_item, gfx::Point(edge_inset + 1, y));
EXPECT_FALSE(IsPreviewAreaShowing());
window_selector_->Drag(selector_item, gfx::Point(edge_inset, y));
EXPECT_TRUE(IsPreviewAreaShowing());
window_selector_->Drag(selector_item,
gfx::Point(screen_width - edge_inset - 2, y));
EXPECT_FALSE(IsPreviewAreaShowing());
window_selector_->Drag(selector_item,
gfx::Point(screen_width - edge_inset - 1, y));
EXPECT_TRUE(IsPreviewAreaShowing());
// Drag back to |start_location| before compeleting the drag, otherwise
// |selector_time| will snap to the right and the system will enter splitview,
// making |window_drag_controller()| nullptr.
window_selector_->Drag(selector_item, start_location);
window_selector_->CompleteDrag(selector_item, start_location);
EXPECT_FALSE(IsPreviewAreaShowing());
}
// Verify that the preview area never shows up when dragging a unsnappable
// window.
TEST_F(SplitViewDragIndicatorsTest, PreviewAreaVisibilityUnsnappableWindow) {
UpdateDisplay("800x600");
const int screen_width = 800;
std::unique_ptr<aura::Window> window(CreateUnsnappableWindow());
ToggleOverview();
WindowSelectorItem* selector_item = GetOverviewItemForWindow(window.get());
const gfx::Point start_location(selector_item->target_bounds().CenterPoint());
window_selector_->InitiateDrag(selector_item, start_location);
EXPECT_FALSE(IsPreviewAreaShowing());
window_selector_->Drag(selector_item, gfx::Point(0, 1));
EXPECT_FALSE(IsPreviewAreaShowing());
window_selector_->Drag(selector_item, gfx::Point(screen_width, 1));
EXPECT_FALSE(IsPreviewAreaShowing());
window_selector_->CompleteDrag(selector_item, start_location);
EXPECT_FALSE(IsPreviewAreaShowing());
}
// Verify that the split view overview overlay has the expected state.
TEST_F(SplitViewDragIndicatorsTest, SplitViewDragIndicatorsState) {
UpdateDisplay("800x600");
const int screen_width = 800;
const int edge_inset = GetEdgeInset(screen_width);
std::unique_ptr<aura::Window> window1(CreateTestWindow());
std::unique_ptr<aura::Window> window2(CreateTestWindow());
ToggleOverview();
// Verify that when are no snapped windows, the indicator is visible once
// there is a long press or after the drag has started.
WindowSelectorItem* selector_item = GetOverviewItemForWindow(window1.get());
gfx::Point start_location(selector_item->target_bounds().CenterPoint());
window_selector_->InitiateDrag(selector_item, start_location);
EXPECT_EQ(IndicatorState::kNone, indicator_state());
window_selector_->StartSplitViewDragMode(start_location);
EXPECT_EQ(IndicatorState::kDragArea, indicator_state());
// Reset the gesture so we stay in overview mode.
window_selector_->ResetDraggedWindowGesture();
// Verify the indicator is visible once the item starts moving, and becomes a
// preview area once we reach the left edge of the screen. Drag horizontal to
// avoid activating drag to close.
const int y_position = start_location.y();
window_selector_->InitiateDrag(selector_item, start_location);
EXPECT_EQ(IndicatorState::kNone, indicator_state());
window_selector_->Drag(selector_item, gfx::Point(edge_inset + 1, y_position));
EXPECT_EQ(IndicatorState::kDragArea, indicator_state());
window_selector_->Drag(selector_item, gfx::Point(edge_inset, y_position));
EXPECT_EQ(IndicatorState::kPreviewAreaLeft, indicator_state());
// Snap window to the left.
window_selector_->CompleteDrag(selector_item,
gfx::Point(edge_inset, y_position));
ASSERT_TRUE(split_view_controller()->IsSplitViewModeActive());
ASSERT_EQ(SplitViewController::LEFT_SNAPPED,
split_view_controller()->state());
// Verify that when there is a left snapped window, dragging an item to the
// right will show the right preview area.
selector_item = GetOverviewItemForWindow(window2.get());
start_location = selector_item->target_bounds().CenterPoint();
window_selector_->InitiateDrag(selector_item, start_location);
EXPECT_EQ(IndicatorState::kNone, indicator_state());
window_selector_->Drag(selector_item,
gfx::Point(screen_width - 1, y_position));
EXPECT_EQ(IndicatorState::kPreviewAreaRight, indicator_state());
window_selector_->CompleteDrag(selector_item, start_location);
}
// Verify that the split view drag indicator is shown when expected when
// attempting to drag a unsnappable window.
TEST_F(SplitViewDragIndicatorsTest,
SplitViewDragIndicatorVisibilityUnsnappableWindow) {
std::unique_ptr<aura::Window> unsnappable_window(CreateUnsnappableWindow());
ToggleOverview();
WindowSelectorItem* selector_item =
GetOverviewItemForWindow(unsnappable_window.get());
gfx::Point start_location(selector_item->target_bounds().CenterPoint());
window_selector_->InitiateDrag(selector_item, start_location);
window_selector_->StartSplitViewDragMode(start_location);
EXPECT_EQ(IndicatorState::kCannotSnap, indicator_state());
const gfx::Point end_location1(0, 0);
window_selector_->Drag(selector_item, end_location1);
EXPECT_EQ(IndicatorState::kCannotSnap, indicator_state());
window_selector_->CompleteDrag(selector_item, end_location1);
EXPECT_EQ(IndicatorState::kNone, indicator_state());
}
// Verify when the split view drag indicators state changes, the expected
// indicators will become visible or invisible.
TEST_F(SplitViewDragIndicatorsTest, SplitViewDragIndicatorsVisibility) {
auto indicator = std::make_unique<SplitViewDragIndicators>();
auto to_int = [](IndicatorType type) { return static_cast<int>(type); };
// Helper function to which checks that all indicator types passed in |mask|
// are visible, and those that are not are not visible.
auto check_helper = [](SplitViewDragIndicators* svdi, int mask) {
const std::vector<IndicatorType> types = {
IndicatorType::kLeftHighlight, IndicatorType::kLeftText,
IndicatorType::kRightHighlight, IndicatorType::kRightText};
for (auto type : types) {
if ((static_cast<int>(type) & mask) > 0)
EXPECT_TRUE(svdi->GetIndicatorTypeVisibilityForTesting(type));
else
EXPECT_FALSE(svdi->GetIndicatorTypeVisibilityForTesting(type));
}
};
// Check each state has the correct views displayed. Pass and empty point as
// the location since there is no need to reparent the widget. Verify that
// nothing is shown in the none state.
indicator->SetIndicatorState(IndicatorState::kNone, gfx::Point());
check_helper(indicator.get(), 0);
const int all = to_int(IndicatorType::kLeftHighlight) |
to_int(IndicatorType::kLeftText) |
to_int(IndicatorType::kRightHighlight) |
to_int(IndicatorType::kRightText);
// Verify that everything is visible in the dragging and cannot snap states.
indicator->SetIndicatorState(IndicatorState::kDragArea, gfx::Point());
check_helper(indicator.get(), all);
indicator->SetIndicatorState(IndicatorState::kCannotSnap, gfx::Point());
check_helper(indicator.get(), all);
// Verify that only one highlight shows up for the preview area states.
indicator->SetIndicatorState(IndicatorState::kPreviewAreaLeft, gfx::Point());
check_helper(indicator.get(), to_int(IndicatorType::kLeftHighlight));
indicator->SetIndicatorState(IndicatorState::kPreviewAreaRight, gfx::Point());
check_helper(indicator.get(), to_int(IndicatorType::kRightHighlight));
}
// Verify that the split view drag indicators widget reparents when starting a
// drag on a different display.
TEST_F(SplitViewDragIndicatorsTest, SplitViewDragIndicatorsWidgetReparenting) {
// Add two displays and one window on each display.
UpdateDisplay("600x600,600x600");
// DisplayConfigurationObserver enables mirror mode when tablet mode is
// enabled. Disable mirror mode to test multiple displays.
display_manager()->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
base::RunLoop().RunUntilIdle();
auto root_windows = Shell::Get()->GetAllRootWindows();
ASSERT_EQ(2u, root_windows.size());
const gfx::Rect primary_screen_bounds(0, 0, 600, 600);
const gfx::Rect secondary_screen_bounds(600, 0, 600, 600);
auto primary_screen_window(CreateTestWindow(primary_screen_bounds));
auto secondary_screen_window(CreateTestWindow(secondary_screen_bounds));
ToggleOverview();
// Select an item on the primary display and verify the drag indicators
// widget's parent is the primary root window.
WindowSelectorItem* selector_item =
GetOverviewItemForWindow(primary_screen_window.get());
gfx::Point start_location(selector_item->target_bounds().CenterPoint());
window_selector_->InitiateDrag(selector_item, start_location);
window_selector_->Drag(selector_item, gfx::Point(100, start_location.y()));
EXPECT_EQ(IndicatorState::kDragArea, indicator_state());
EXPECT_EQ(root_windows[0], window_selector_->split_view_drag_indicators()
->widget_->GetNativeView()
->GetRootWindow());
// Drag the item in a way that neither opens the window nor activates
// splitview mode.
window_selector_->Drag(selector_item, primary_screen_bounds.CenterPoint());
window_selector_->CompleteDrag(selector_item,
primary_screen_bounds.CenterPoint());
ASSERT_TRUE(Shell::Get()->window_selector_controller()->IsSelecting());
ASSERT_FALSE(split_view_controller()->IsSplitViewModeActive());
// Select an item on the secondary display and verify the indicators widget
// has reparented to the secondary root window.
selector_item = GetOverviewItemForWindow(secondary_screen_window.get(), 1);
start_location = gfx::Point(selector_item->target_bounds().CenterPoint());
window_selector_->InitiateDrag(selector_item, start_location);
window_selector_->Drag(selector_item, gfx::Point(800, start_location.y()));
EXPECT_EQ(IndicatorState::kDragArea, indicator_state());
EXPECT_EQ(root_windows[1], window_selector_->split_view_drag_indicators()
->widget_->GetNativeView()
->GetRootWindow());
window_selector_->CompleteDrag(selector_item, start_location);
}
} // namespace ash