Skip to content

Commit

Permalink
float: Dragging from shelf can pick non active window
Browse files Browse the repository at this point in the history
Prior to this patch, the window to drag would always be the active
window, even if the event was under a non-active window, which can
happen with floated windows. Also does not drag floated windows that
are magnetized to the top.

Test: ash_unittests *DragWindowFromShelfController*
Bug: b/252504142
Change-Id: Ia7d1f8f64a62a5b581df8850d766e40e88c4906e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4198959
Reviewed-by: Xiaoqian Dai <xdai@chromium.org>
Commit-Queue: Sammie Quon <sammiequon@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1098236}
  • Loading branch information
Sammie Quon authored and Chromium LUCI CQ committed Jan 28, 2023
1 parent 34bf312 commit 048cdb3
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 23 deletions.
152 changes: 152 additions & 0 deletions ash/shelf/drag_window_from_shelf_controller_unittest.cc
Expand Up @@ -9,6 +9,7 @@
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/public/cpp/overview_test_api.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/window_backdrop.h"
Expand Down Expand Up @@ -45,6 +46,7 @@
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/test_utils.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/transient_window_manager.h"
#include "ui/wm/core/window_modality_controller.h"
Expand All @@ -58,6 +60,13 @@ gfx::Rect GetShelfBounds() {
return Shelf::ForWindow(Shell::GetPrimaryRootWindow())->GetIdealBounds();
}

// Gets the drag controller owned by the shelf.
DragWindowFromShelfController* GetDragWindowFromShelfController() {
return AshTestBase::GetPrimaryShelf()
->shelf_layout_manager()
->window_drag_controller_for_testing();
}

} // namespace

// This definition is needed because this constant is odr-used.
Expand Down Expand Up @@ -1518,4 +1527,147 @@ TEST_F(FloatDragWindowFromShelfControllerTest, WindowStatePreserved) {
EXPECT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
}

// Tests that the correct window (if any) gets chosen by the shelf layout
// manager when there is a floated window.
TEST_F(FloatDragWindowFromShelfControllerTest, DraggingFloatedWindow) {
UpdateDisplay("800x600");

auto floated_window = CreateFloatedWindow();

// Start dragging on the shelf, but not under the floated window. Verify that
// nothing gets dragged.
const gfx::Rect shelf_bounds = GetShelfBounds();
const gfx::Point drag_point_not_under_float(100,
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_not_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
auto* drag_controller = GetDragWindowFromShelfController();
ASSERT_FALSE(drag_controller);
GetEventGenerator()->ReleaseTouch();

// Drag under the floated window. Verify that the float window gets dragged.
const gfx::Rect float_bounds = floated_window->GetBoundsInScreen();
const gfx::Point drag_point_under_float(
floated_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(floated_window.get(), drag_controller->dragged_window());

// Move back towards the shelf to ensure we do not enter overview.
GetEventGenerator()->MoveTouchBy(0, 200);
GetEventGenerator()->ReleaseTouch();
ASSERT_FALSE(GetDragWindowFromShelfController()->drag_started());

// We need to force a layout to start dragging. Drag the window so that it is
// magnetized to the top edge.
views::test::RunScheduledLayout(
NonClientFrameViewAsh::Get(floated_window.get()));
GetEventGenerator()->PressTouch(float_bounds.top_center() +
gfx::Vector2d(0, 10));
GetEventGenerator()->MoveTouchBy(0, -100);
GetEventGenerator()->ReleaseTouch();
EXPECT_NE(float_bounds, floated_window->GetBoundsInScreen());

// Since the floated window is magnetized to the top, dragging on the shelf
// does nothing.
GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
EXPECT_FALSE(GetDragWindowFromShelfController()->drag_started());
}

// Tests that the correct window gets chosen by the shelf layout manager when
// there is a floated and maximized window.
TEST_F(FloatDragWindowFromShelfControllerTest,
DraggingFloatedAndMaximizedWindow) {
UpdateDisplay("800x600");

// Create one maximized and one floated window.
auto maximized_window = CreateTestWindow();
auto floated_window = CreateFloatedWindow();
wm::ActivateWindow(maximized_window.get());

// Drag under the floated window. Even though the maximized window is active,
// the floated window is the one that is dragged.
const gfx::Rect shelf_bounds = GetShelfBounds();
const gfx::Point drag_point_under_float(
floated_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());

GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
auto* drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(floated_window.get(), drag_controller->dragged_window());

// Move back towards the shelf to ensure we do not enter overview.
GetEventGenerator()->MoveTouchBy(0, 200);
GetEventGenerator()->ReleaseTouch();

// Drag under the maximized window. Even though the floated window is active,
// the maximized window is the one that is dragged.
wm::ActivateWindow(floated_window.get());
const gfx::Point drag_point_under_maximize(100,
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_maximize);
GetEventGenerator()->MoveTouchBy(0, -200);
drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(maximized_window.get(), drag_controller->dragged_window());
}

// Tests that the correct window gets chosen by the shelf layout manager when
// there are floated and snapped windows.
TEST_F(FloatDragWindowFromShelfControllerTest,
DraggingFloatedAndSnappedWindow) {
UpdateDisplay("800x600");

// Create two snapped and one floated window.
auto left_window = CreateTestWindow();
auto right_window = CreateTestWindow();
auto floated_window = CreateFloatedWindow();
split_view_controller()->SnapWindow(
left_window.get(), SplitViewController::SnapPosition::kPrimary);
split_view_controller()->SnapWindow(
right_window.get(), SplitViewController::SnapPosition::kSecondary);

// Ensure we are in a both snapped state with a floated window.
ASSERT_TRUE(WindowState::Get(left_window.get())->IsSnapped());
ASSERT_TRUE(WindowState::Get(right_window.get())->IsSnapped());
ASSERT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
wm::ActivateWindow(floated_window.get());

// Verify that the floated window by default is magnetized to the bottom right
// corner.
ASSERT_TRUE(right_window->GetBoundsInScreen().Contains(
floated_window->GetBoundsInScreen()));

// Drag under the floated window. It should be the dragged window.
const gfx::Rect shelf_bounds = GetShelfBounds();
const gfx::Point drag_point_under_float(
floated_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
auto* drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(floated_window.get(), drag_controller->dragged_window());

// Move back towards the shelf to ensure we do not enter overview.
GetEventGenerator()->MoveTouchBy(0, 200);
GetEventGenerator()->ReleaseTouch();

// Drag under the right snapped window. It should be the dragged window.
const gfx::Point drag_point_under_right(
right_window->GetBoundsInScreen().x() + 10,
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_right);
GetEventGenerator()->MoveTouchBy(0, -200);
drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(right_window.get(), drag_controller->dragged_window());
}

} // namespace ash
70 changes: 47 additions & 23 deletions ash/shelf/shelf_layout_manager.cc
Expand Up @@ -44,6 +44,7 @@
#include "ash/system/locale/locale_update_controller_impl.h"
#include "ash/system/status_area_widget.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/fullscreen_window_finder.h"
#include "ash/wm/lock_state_controller.h"
#include "ash/wm/mru_window_tracker.h"
Expand Down Expand Up @@ -253,37 +254,60 @@ aura::Window* GetWindowForDragToHomeOrOverview(

auto mru_windows =
Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
if (mru_windows.empty())
if (mru_windows.empty()) {
return nullptr;
}

aura::Window* window = nullptr;
SplitViewController* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
const bool is_in_splitview = split_view_controller->InSplitViewMode();
const bool is_in_overview =
Shell::Get()->overview_controller()->InOverviewSession();
if (!is_in_splitview && !is_in_overview) {
// If split view mode is not active, use the first MRU window.
window = mru_windows[0];
} else if (is_in_splitview) {
// If split view mode is active, use the event location to decide which
// window should be the dragged window.
aura::Window* left_window = split_view_controller->primary_window();
aura::Window* right_window = split_view_controller->secondary_window();
const int divider_position = split_view_controller->divider_position();
const bool is_landscape = IsCurrentScreenOrientationLandscape();
const bool is_primary = IsCurrentScreenOrientationPrimary();
const gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
split_view_controller->GetDefaultSnappedWindow());
if (is_landscape) {
if (location_in_screen.x() < work_area.x() + divider_position)
window = is_primary ? left_window : right_window;
else
window = is_primary ? right_window : left_window;
// Cannot drag anything if in non splitview overview.
if (!is_in_splitview &&
Shell::Get()->overview_controller()->InOverviewSession()) {
return nullptr;
}

// Checks for a floated window. The floated window is the dragged window if it
// is not tucked, magnetized to the bottom, and above the event.
if (aura::Window* floated_window =
window_util::GetFloatedWindowForActiveDesk()) {
if (Shell::Get()->float_controller()->IsFloatedWindowAlignedWithShelf(
floated_window)) {
const gfx::Rect floated_window_bounds =
floated_window->GetBoundsInScreen();
if (location_in_screen.x() <= floated_window_bounds.right() &&
location_in_screen.x() >= floated_window_bounds.x()) {
DCHECK(floated_window->IsVisible());
return floated_window;
}
}
}

// If split view mode is not active, use the first MRU window.
if (!is_in_splitview) {
aura::Window* window = window_util::GetTopNonFloatedWindow();
return window && window->IsVisible() ? window : nullptr;
}

aura::Window* window = nullptr;
// If split view mode is active, use the event location to decide which
// window should be the dragged window.
aura::Window* left_window = split_view_controller->primary_window();
aura::Window* right_window = split_view_controller->secondary_window();
const int divider_position = split_view_controller->divider_position();
const bool is_landscape = IsCurrentScreenOrientationLandscape();
const bool is_primary = IsCurrentScreenOrientationPrimary();
const gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
split_view_controller->GetDefaultSnappedWindow());
if (is_landscape) {
if (location_in_screen.x() < work_area.x() + divider_position) {
window = is_primary ? left_window : right_window;
} else {
window = is_primary ? right_window : left_window;
}
} else {
window = is_primary ? right_window : left_window;
}
return window && window->IsVisible() ? window : nullptr;
}
Expand Down
13 changes: 13 additions & 0 deletions ash/wm/float/float_controller.cc
Expand Up @@ -394,6 +394,19 @@ bool FloatController::IsFloatedWindowTuckedForTablet(
return floated_window_info->is_tucked_for_tablet();
}

bool FloatController::IsFloatedWindowAlignedWithShelf(
aura::Window* floated_window) const {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
DCHECK(floated_window_info);
if (floated_window_info->is_tucked_for_tablet()) {
return false;
}

MagnetismCorner magnetism_corner = floated_window_info->magnetism_corner();
return magnetism_corner == MagnetismCorner::kBottomLeft ||
magnetism_corner == MagnetismCorner::kBottomRight;
}

views::Widget* FloatController::GetTuckHandleWidget(
const aura::Window* floated_window) const {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
Expand Down
5 changes: 5 additions & 0 deletions ash/wm/float/float_controller.h
Expand Up @@ -67,6 +67,11 @@ class ASH_EXPORT FloatController : public TabletModeObserver,
// Checks if `floated_window` is tucked.
bool IsFloatedWindowTuckedForTablet(const aura::Window* floated_window) const;

// Returns true if `floated_window` is not tucked and magnetized to the
// bottom. Used by the shelf layout manager to determine what window to use
// for the drag window from shelf feature.
bool IsFloatedWindowAlignedWithShelf(aura::Window* floated_window) const;

// Gets the tuck handle for a floated and tucked window.
views::Widget* GetTuckHandleWidget(const aura::Window* floated_window) const;

Expand Down

0 comments on commit 048cdb3

Please sign in to comment.