snap-group: Implement the entry point for experiment arm 2
Implement the user-initiated entry point for the Snap Group feature. A lock widget will appear below the resize widget when two windows are snapped. User will be able to toggle the lock widget to create or remove a snap group. To run this experiment, append this command: --enable-features=SnapGroup:"AutomaticLockGroup"/false Demo for this feature is available at: http://b/264604628#comment2 Bug: b:264604628 Test: Manually + added unit tests Change-Id: Id54e5400714da8b97aa9934fe204f2418365ba15 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4164006 Reviewed-by: Xiaoqian Dai <xdai@chromium.org> Commit-Queue: Michele Fan <michelefan@chromium.org> Cr-Commit-Position: refs/heads/main@{#1094870}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
6c0ddf95de
commit
cd90a569c9
@ -2268,6 +2268,8 @@ component("ash") {
|
||||
"wm/snap_group/snap_group.h",
|
||||
"wm/snap_group/snap_group_controller.cc",
|
||||
"wm/snap_group/snap_group_controller.h",
|
||||
"wm/snap_group/snap_group_lock_button.cc",
|
||||
"wm/snap_group/snap_group_lock_button.h",
|
||||
"wm/splitview/split_view_constants.h",
|
||||
"wm/splitview/split_view_controller.cc",
|
||||
"wm/splitview/split_view_controller.h",
|
||||
|
@ -5334,6 +5334,14 @@ Here are some things you can try to get started.
|
||||
Linux files
|
||||
</message>
|
||||
|
||||
<!-- Snap Group -->
|
||||
<message name="IDS_ASH_SNAP_GROUP_CLICK_TO_LOCK_WINDOWS" desc="Click to lock the windows.">
|
||||
Lock the windows
|
||||
</message>
|
||||
<message name="IDS_ASH_SNAP_GROUP_CLICK_TO_UNLOCK_WINDOWS" desc="Click to unlock the locked windows.">
|
||||
Unlock the windows
|
||||
</message>
|
||||
|
||||
<!-- Switch Between TABLET/LAPTOP MODE-->
|
||||
<message name="IDS_ASH_SWITCH_TO_TABLET_MODE" desc="Alert of switching to tablet mode.">
|
||||
Switched to tablet mode
|
||||
|
@ -0,0 +1 @@
|
||||
b199547c766db8578e9ca86f9df458e62a2ba4b8
|
@ -0,0 +1 @@
|
||||
f173c113518b4c3a846d7919b59dc8a484648cd6
|
@ -1955,6 +1955,13 @@ BASE_FEATURE(kSmartLockUIRevamp,
|
||||
// Controls whether the snap group feature is enabled or not.
|
||||
BASE_FEATURE(kSnapGroup, "SnapGroup", base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
// Controls whether to create the snap group automatically when two windows are
|
||||
// snapped if true. Otherwise, the user has to explicitly lock the two windows
|
||||
// when both are snapped via cliking on the lock button when hovering the mouse
|
||||
// over the shared edge of the two snapped windows.
|
||||
constexpr base::FeatureParam<bool> kAutomaticallyLockGroup{
|
||||
&kSnapGroup, "AutomaticLockGroup", true};
|
||||
|
||||
// Controls whether the speak-on-mute detection feature is enabled or not.
|
||||
BASE_FEATURE(kSpeakOnMuteEnabled,
|
||||
"SpeakOnMuteEnabled",
|
||||
|
@ -553,6 +553,8 @@ BASE_DECLARE_FEATURE(kSmartDimExperimentalComponent);
|
||||
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSmartLockSignInRemoved);
|
||||
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSmartLockUIRevamp);
|
||||
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSnapGroup);
|
||||
COMPONENT_EXPORT(ASH_CONSTANTS)
|
||||
extern const base::FeatureParam<bool> kAutomaticallyLockGroup;
|
||||
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSnoopingProtection);
|
||||
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSpeakOnMuteEnabled);
|
||||
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kStylusBatteryStatus);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ash/shell.h"
|
||||
#include "ash/wm/snap_group/snap_group.h"
|
||||
#include "base/check.h"
|
||||
#include "base/containers/cxx20_erase.h"
|
||||
@ -17,6 +18,12 @@ SnapGroupController::SnapGroupController() = default;
|
||||
|
||||
SnapGroupController::~SnapGroupController() = default;
|
||||
|
||||
bool SnapGroupController::AreWindowsInSnapGroup(aura::Window* window1,
|
||||
aura::Window* window2) const {
|
||||
return window1 == RetrieveTheOtherWindowInSnapGroup(window2) &&
|
||||
window2 == RetrieveTheOtherWindowInSnapGroup(window1);
|
||||
}
|
||||
|
||||
bool SnapGroupController::AddSnapGroup(aura::Window* window1,
|
||||
aura::Window* window2) {
|
||||
if (window_to_snap_group_map_.find(window1) !=
|
||||
@ -49,4 +56,27 @@ bool SnapGroupController::RemoveSnapGroup(SnapGroup* snap_group) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SnapGroupController::RemoveSnapGroupContainingWindow(
|
||||
aura::Window* window) {
|
||||
if (window_to_snap_group_map_.find(window) ==
|
||||
window_to_snap_group_map_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SnapGroup* snap_group = window_to_snap_group_map_.find(window)->second;
|
||||
return RemoveSnapGroup(snap_group);
|
||||
}
|
||||
|
||||
aura::Window* SnapGroupController::RetrieveTheOtherWindowInSnapGroup(
|
||||
aura::Window* window) const {
|
||||
if (window_to_snap_group_map_.find(window) ==
|
||||
window_to_snap_group_map_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SnapGroup* snap_group = window_to_snap_group_map_.find(window)->second;
|
||||
return window == snap_group->window1() ? snap_group->window2()
|
||||
: snap_group->window1();
|
||||
}
|
||||
|
||||
} // namespace ash
|
@ -20,7 +20,7 @@ namespace ash {
|
||||
class SnapGroup;
|
||||
|
||||
// Works as the centralized place to manage the `SnapGroup`. A single instance
|
||||
// of this class will be created and owned by `Shell`. it controls the creation
|
||||
// of this class will be created and owned by `Shell`. It controls the creation
|
||||
// and destruction of the `SnapGroup`. TODO: It also implements the
|
||||
// `OverviewObserver` and `TabletObserver`.
|
||||
class ASH_EXPORT SnapGroupController {
|
||||
@ -33,6 +33,10 @@ class ASH_EXPORT SnapGroupController {
|
||||
SnapGroupController& operator=(const SnapGroupController&) = delete;
|
||||
~SnapGroupController();
|
||||
|
||||
// Returns true if `window1` and `window2` are in the same snap group.
|
||||
bool AreWindowsInSnapGroup(aura::Window* window1,
|
||||
aura::Window* window2) const;
|
||||
|
||||
// Returns true if the corresponding SnapGroup for the given `window1` and
|
||||
// `window2` gets created, added to the `snap_groups_` and updated
|
||||
// `window_to_snap_group_map_` successfully. False otherwise.
|
||||
@ -43,12 +47,21 @@ class ASH_EXPORT SnapGroupController {
|
||||
// `window_to_snap_group_map_`. False otherwise.
|
||||
bool RemoveSnapGroup(SnapGroup* snap_group);
|
||||
|
||||
// Returns true if the corresponding snap group that contains the
|
||||
// given `window` has been removed successfully. Returns false otherwise.
|
||||
bool RemoveSnapGroupContainingWindow(aura::Window* window);
|
||||
|
||||
const SnapGroups& snap_groups_for_testing() const { return snap_groups_; }
|
||||
const WindowToSnapGroupMap& window_to_snap_group_map_for_testing() const {
|
||||
return window_to_snap_group_map_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Retrieves the other window that is in the same snap group if any. Returns
|
||||
// nullptr if such window can't be found i.e. the window is not in a snap
|
||||
// group.
|
||||
aura::Window* RetrieveTheOtherWindowInSnapGroup(aura::Window* window) const;
|
||||
|
||||
// Contains all the `SnapGroup`, we will have one `SnapGroup` globally for the
|
||||
// first iteration but will have multiple in the future iteration.
|
||||
SnapGroups snap_groups_;
|
||||
|
84
ash/wm/snap_group/snap_group_lock_button.cc
Normal file
84
ash/wm/snap_group/snap_group_lock_button.cc
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2023 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/snap_group/snap_group_lock_button.h"
|
||||
|
||||
#include "ash/resources/vector_icons/vector_icons.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/strings/grit/ash_strings.h"
|
||||
#include "ash/style/ash_color_id.h"
|
||||
#include "ash/wm/snap_group/snap_group_controller.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
#include "ui/base/models/image_model.h"
|
||||
#include "ui/gfx/paint_vector_icon.h"
|
||||
#include "ui/views/background.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kLockButtonCornerRadius = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
SnapGroupLockButton::SnapGroupLockButton(aura::Window* window1,
|
||||
aura::Window* window2)
|
||||
: ImageButton(base::BindRepeating(&SnapGroupLockButton::OnLockButtonPressed,
|
||||
base::Unretained(this),
|
||||
window1,
|
||||
window2)) {
|
||||
SetImageHorizontalAlignment(ALIGN_CENTER);
|
||||
SetImageVerticalAlignment(ALIGN_MIDDLE);
|
||||
|
||||
SnapGroupController* snap_group_controller =
|
||||
Shell::Get()->snap_group_controller();
|
||||
const bool locked =
|
||||
snap_group_controller->AreWindowsInSnapGroup(window1, window2);
|
||||
UpdateLockButtonIcon(locked);
|
||||
UpdateLockButtonTooltip(locked);
|
||||
|
||||
SetBackground(views::CreateThemedRoundedRectBackground(
|
||||
kColorAshShieldAndBase80, kLockButtonCornerRadius));
|
||||
}
|
||||
|
||||
SnapGroupLockButton::~SnapGroupLockButton() = default;
|
||||
|
||||
void SnapGroupLockButton::OnLockButtonPressed(aura::Window* window1,
|
||||
aura::Window* window2) {
|
||||
DCHECK(window1);
|
||||
DCHECK(window2);
|
||||
SnapGroupController* snap_group_controller =
|
||||
Shell::Get()->snap_group_controller();
|
||||
const bool locked =
|
||||
snap_group_controller->AreWindowsInSnapGroup(window1, window2);
|
||||
|
||||
if (locked) {
|
||||
snap_group_controller->RemoveSnapGroupContainingWindow(window1);
|
||||
} else {
|
||||
snap_group_controller->AddSnapGroup(window1, window2);
|
||||
}
|
||||
|
||||
UpdateLockButtonIcon(!locked);
|
||||
UpdateLockButtonTooltip(!locked);
|
||||
}
|
||||
|
||||
void SnapGroupLockButton::UpdateLockButtonIcon(bool locked) {
|
||||
SetImageModel(
|
||||
views::Button::STATE_NORMAL,
|
||||
ui::ImageModel::FromVectorIcon(locked ? kLockScreenEasyUnlockCloseIcon
|
||||
: kLockScreenEasyUnlockOpenIcon,
|
||||
kColorAshIconColorPrimary));
|
||||
}
|
||||
|
||||
void SnapGroupLockButton::UpdateLockButtonTooltip(bool locked) {
|
||||
SetTooltipText(l10n_util::GetStringUTF16(
|
||||
locked ? IDS_ASH_SNAP_GROUP_CLICK_TO_UNLOCK_WINDOWS
|
||||
: IDS_ASH_SNAP_GROUP_CLICK_TO_LOCK_WINDOWS));
|
||||
}
|
||||
|
||||
BEGIN_METADATA(SnapGroupLockButton, views::ImageButton)
|
||||
END_METADATA
|
||||
|
||||
} // namespace ash
|
40
ash/wm/snap_group/snap_group_lock_button.h
Normal file
40
ash/wm/snap_group/snap_group_lock_button.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ASH_WM_SNAP_GROUP_SNAP_GROUP_LOCK_BUTTON_H_
|
||||
#define ASH_WM_SNAP_GROUP_SNAP_GROUP_LOCK_BUTTON_H_
|
||||
|
||||
#include "ui/base/metadata/metadata_header_macros.h"
|
||||
#include "ui/views/controls/button/image_button.h"
|
||||
|
||||
namespace aura {
|
||||
class Window;
|
||||
} // namespace aura
|
||||
|
||||
namespace ash {
|
||||
|
||||
// Contents view of the lock widget that appears below the resize widget when
|
||||
// two windows are snapped. It acts as the entry point for the creating or
|
||||
// removing the `SnapGroup`. This entry point is guarded by the feature flag
|
||||
// `kSnapGroup` and will only be enabled when the feature param
|
||||
// `kAutomaticallyLockGroup` is false.
|
||||
class SnapGroupLockButton : public views::ImageButton {
|
||||
public:
|
||||
METADATA_HEADER(SnapGroupLockButton);
|
||||
SnapGroupLockButton(aura::Window* window1, aura::Window* window2);
|
||||
SnapGroupLockButton(const SnapGroupLockButton&) = delete;
|
||||
SnapGroupLockButton& operator=(const SnapGroupLockButton&) = delete;
|
||||
~SnapGroupLockButton() override;
|
||||
|
||||
// Decides to create or remove a snap group on button toggled.
|
||||
void OnLockButtonPressed(aura::Window* window1, aura::Window* window2);
|
||||
|
||||
private:
|
||||
void UpdateLockButtonIcon(bool locked);
|
||||
void UpdateLockButtonTooltip(bool locked);
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
||||
#endif // ASH_WM_SNAP_GROUP_SNAP_GROUP_LOCK_BUTTON_H_
|
@ -6,15 +6,29 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ash/constants/ash_features.h"
|
||||
#include "ash/public/cpp/test/shell_test_api.h"
|
||||
#include "ash/resources/vector_icons/vector_icons.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/strings/grit/ash_strings.h"
|
||||
#include "ash/style/ash_color_id.h"
|
||||
#include "ash/test/ash_test_base.h"
|
||||
#include "ash/test/ash_test_util.h"
|
||||
#include "ash/wm/mru_window_tracker.h"
|
||||
#include "ash/wm/snap_group/snap_group_controller.h"
|
||||
#include "ash/wm/snap_group/snap_group_lock_button.h"
|
||||
#include "ash/wm/window_state.h"
|
||||
#include "ash/wm/wm_event.h"
|
||||
#include "ash/wm/workspace/multi_window_resize_controller.h"
|
||||
#include "ash/wm/workspace/workspace_event_handler_test_helper.h"
|
||||
#include "ash/wm/workspace_controller_test_api.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
#include "ui/gfx/image/image_unittest_util.h"
|
||||
#include "ui/gfx/paint_vector_icon.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/wm/core/window_util.h"
|
||||
|
||||
namespace ash {
|
||||
@ -137,4 +151,217 @@ TEST_F(SnapGroupTest, WindowActivationTest) {
|
||||
EXPECT_TRUE(IsStackedBelow(w3.get(), w2.get()));
|
||||
}
|
||||
|
||||
// A test fixture that tests the user-initiated snap group entry point. This
|
||||
// entry point is guarded by the feature flag `kSnapGroup` and will only be
|
||||
// enabled when the feature param `kAutomaticallyLockGroup` is false.
|
||||
class SnapGroupEntryPointArm2Test : public SnapGroupTest {
|
||||
public:
|
||||
SnapGroupEntryPointArm2Test() = default;
|
||||
SnapGroupEntryPointArm2Test(const SnapGroupEntryPointArm2Test&) = delete;
|
||||
SnapGroupEntryPointArm2Test& operator=(const SnapGroupEntryPointArm2Test&) =
|
||||
delete;
|
||||
~SnapGroupEntryPointArm2Test() override = default;
|
||||
|
||||
// SnapGroupTest:
|
||||
void SetUp() override {
|
||||
scoped_feature_list_.InitAndEnableFeatureWithParameters(
|
||||
features::kSnapGroup, {{"AutomaticLockGroup", "false"}});
|
||||
AshTestBase::SetUp();
|
||||
WorkspaceEventHandler* event_handler =
|
||||
WorkspaceControllerTestApi(ShellTestApi().workspace_controller())
|
||||
.GetEventHandler();
|
||||
resize_controller_ =
|
||||
WorkspaceEventHandlerTestHelper(event_handler).resize_controller();
|
||||
}
|
||||
|
||||
views::Widget* GetLockWidget() const {
|
||||
return resize_controller_->lock_widget_.get();
|
||||
}
|
||||
|
||||
views::Widget* GetResizeWidget() const {
|
||||
return resize_controller_->resize_widget_.get();
|
||||
}
|
||||
|
||||
base::OneShotTimer* GetShowTimer() const {
|
||||
return &resize_controller_->show_timer_;
|
||||
}
|
||||
|
||||
bool IsShowing() const { return resize_controller_->IsShowing(); }
|
||||
|
||||
MultiWindowResizeController* resize_controller() const {
|
||||
return resize_controller_;
|
||||
}
|
||||
|
||||
// Verifies that the given two windows can be locked properly and the tooltip
|
||||
// is updated accordingly.
|
||||
void ToggleLockWidgetToLockTwoWindows(aura::Window* window1,
|
||||
aura::Window* window2) {
|
||||
auto* snap_group_controller = Shell::Get()->snap_group_controller();
|
||||
ASSERT_TRUE(snap_group_controller);
|
||||
EXPECT_TRUE(snap_group_controller->snap_groups_for_testing().empty());
|
||||
EXPECT_TRUE(
|
||||
snap_group_controller->window_to_snap_group_map_for_testing().empty());
|
||||
EXPECT_FALSE(
|
||||
snap_group_controller->AreWindowsInSnapGroup(window1, window2));
|
||||
|
||||
auto* event_generator = GetEventGenerator();
|
||||
auto hover_location = window1->bounds().right_center();
|
||||
event_generator->MoveMouseTo(hover_location);
|
||||
auto* timer = GetShowTimer();
|
||||
EXPECT_TRUE(timer->IsRunning());
|
||||
EXPECT_TRUE(IsShowing());
|
||||
timer->FireNow();
|
||||
EXPECT_TRUE(GetLockWidget());
|
||||
|
||||
gfx::Rect lock_widget_bounds(GetLockWidget()->GetWindowBoundsInScreen());
|
||||
hover_location = lock_widget_bounds.CenterPoint();
|
||||
event_generator->MoveMouseTo(hover_location);
|
||||
EXPECT_TRUE(GetLockWidget());
|
||||
event_generator->PressLeftButton();
|
||||
event_generator->ReleaseLeftButton();
|
||||
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(window1, window2));
|
||||
VerifyLockButton(/*locked=*/true,
|
||||
resize_controller_->lock_button_for_testing());
|
||||
}
|
||||
|
||||
// Verifies that the given two windows can be unlocked properly and the
|
||||
// tooltip is updated accordingly.
|
||||
void ToggleLockWidgetToUnlockTwoWindows(aura::Window* window1,
|
||||
aura::Window* window2) {
|
||||
auto* snap_group_controller = Shell::Get()->snap_group_controller();
|
||||
ASSERT_TRUE(snap_group_controller);
|
||||
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(window1, window2));
|
||||
|
||||
auto* event_generator = GetEventGenerator();
|
||||
const auto hover_location =
|
||||
GetLockWidget()->GetWindowBoundsInScreen().CenterPoint();
|
||||
event_generator->MoveMouseTo(hover_location);
|
||||
EXPECT_TRUE(GetLockWidget());
|
||||
event_generator->PressLeftButton();
|
||||
event_generator->ReleaseLeftButton();
|
||||
EXPECT_FALSE(
|
||||
snap_group_controller->AreWindowsInSnapGroup(window1, window2));
|
||||
VerifyLockButton(/*locked=*/false,
|
||||
resize_controller_->lock_button_for_testing());
|
||||
}
|
||||
|
||||
private:
|
||||
// Verifies that the icon image and the tooltip of the lock button gets
|
||||
// updated correctly based on the `locked` state.
|
||||
void VerifyLockButton(bool locked, SnapGroupLockButton* lock_button) {
|
||||
SkColor color =
|
||||
lock_button->GetColorProvider()->GetColor(kColorAshIconColorPrimary);
|
||||
const gfx::ImageSkia locked_icon_image =
|
||||
gfx::CreateVectorIcon(kLockScreenEasyUnlockCloseIcon, color);
|
||||
const gfx::ImageSkia unlocked_icon_image =
|
||||
gfx::CreateVectorIcon(kLockScreenEasyUnlockOpenIcon, color);
|
||||
const SkBitmap* expected_icon =
|
||||
locked ? locked_icon_image.bitmap() : unlocked_icon_image.bitmap();
|
||||
const SkBitmap* actual_icon =
|
||||
lock_button->GetImage(views::ImageButton::ButtonState::STATE_NORMAL)
|
||||
.bitmap();
|
||||
EXPECT_TRUE(gfx::test::AreBitmapsEqual(*actual_icon, *expected_icon));
|
||||
|
||||
const auto expected_tooltip_string = l10n_util::GetStringUTF16(
|
||||
locked ? IDS_ASH_SNAP_GROUP_CLICK_TO_UNLOCK_WINDOWS
|
||||
: IDS_ASH_SNAP_GROUP_CLICK_TO_LOCK_WINDOWS);
|
||||
EXPECT_EQ(lock_button->GetTooltipText(), expected_tooltip_string);
|
||||
}
|
||||
|
||||
base::test::ScopedFeatureList scoped_feature_list_;
|
||||
MultiWindowResizeController* resize_controller_;
|
||||
};
|
||||
|
||||
// Tests that the lock widget will show below the resize widget when two windows
|
||||
// are snapped. And the location of the lock widget will be updated on mouse
|
||||
// move.
|
||||
TEST_F(SnapGroupEntryPointArm2Test, LockWidgetShowAndMoveTest) {
|
||||
std::unique_ptr<aura::Window> w1(CreateTestWindow());
|
||||
std::unique_ptr<aura::Window> w2(CreateTestWindow());
|
||||
SnapTwoTestWindows(w1.get(), w2.get());
|
||||
EXPECT_FALSE(GetResizeWidget());
|
||||
EXPECT_FALSE(GetLockWidget());
|
||||
|
||||
auto* event_generator = GetEventGenerator();
|
||||
auto hover_location = w1->bounds().right_center();
|
||||
event_generator->MoveMouseTo(hover_location);
|
||||
auto* timer = GetShowTimer();
|
||||
EXPECT_TRUE(timer->IsRunning());
|
||||
EXPECT_TRUE(IsShowing());
|
||||
timer->FireNow();
|
||||
EXPECT_TRUE(GetResizeWidget());
|
||||
EXPECT_TRUE(GetLockWidget());
|
||||
|
||||
gfx::Rect ori_resize_widget_bounds(
|
||||
GetResizeWidget()->GetWindowBoundsInScreen());
|
||||
gfx::Rect ori_lock_widget_bounds(GetLockWidget()->GetWindowBoundsInScreen());
|
||||
|
||||
resize_controller()->MouseMovedOutOfHost();
|
||||
EXPECT_FALSE(timer->IsRunning());
|
||||
EXPECT_FALSE(IsShowing());
|
||||
|
||||
const int x_delta = 0;
|
||||
const int y_delta = 5;
|
||||
hover_location.Offset(x_delta, y_delta);
|
||||
event_generator->MoveMouseTo(hover_location);
|
||||
EXPECT_TRUE(timer->IsRunning());
|
||||
EXPECT_TRUE(IsShowing());
|
||||
timer->FireNow();
|
||||
EXPECT_TRUE(GetResizeWidget());
|
||||
EXPECT_TRUE(GetLockWidget());
|
||||
|
||||
gfx::Rect new_resize_widget_bounds(
|
||||
GetResizeWidget()->GetWindowBoundsInScreen());
|
||||
gfx::Rect new_lock_widget_bounds(GetLockWidget()->GetWindowBoundsInScreen());
|
||||
|
||||
gfx::Rect expected_resize_widget_bounds = ori_resize_widget_bounds;
|
||||
expected_resize_widget_bounds.Offset(x_delta, y_delta);
|
||||
gfx::Rect expected_lock_widget_bounds = ori_lock_widget_bounds;
|
||||
expected_lock_widget_bounds.Offset(x_delta, y_delta);
|
||||
EXPECT_EQ(expected_resize_widget_bounds, new_resize_widget_bounds);
|
||||
EXPECT_EQ(expected_lock_widget_bounds, new_lock_widget_bounds);
|
||||
}
|
||||
|
||||
// Tests that a snap group will be created and removed by toggling the lock
|
||||
// widget.
|
||||
TEST_F(SnapGroupEntryPointArm2Test,
|
||||
SnapGroupAddAndRemovalThroughLockButtonTest) {
|
||||
std::unique_ptr<aura::Window> w1(CreateTestWindow());
|
||||
std::unique_ptr<aura::Window> w2(CreateTestWindow());
|
||||
SnapTwoTestWindows(w1.get(), w2.get());
|
||||
EXPECT_FALSE(GetLockWidget());
|
||||
|
||||
auto* snap_group_controller = Shell::Get()->snap_group_controller();
|
||||
ToggleLockWidgetToLockTwoWindows(w1.get(), w2.get());
|
||||
EXPECT_EQ(
|
||||
snap_group_controller->window_to_snap_group_map_for_testing().size(), 2u);
|
||||
EXPECT_EQ(snap_group_controller->snap_groups_for_testing().size(), 1u);
|
||||
|
||||
ToggleLockWidgetToUnlockTwoWindows(w1.get(), w2.get());
|
||||
EXPECT_TRUE(
|
||||
snap_group_controller->window_to_snap_group_map_for_testing().empty());
|
||||
EXPECT_TRUE(snap_group_controller->snap_groups_for_testing().empty());
|
||||
}
|
||||
|
||||
// Tests the activation functionalities of the snap group.
|
||||
TEST_F(SnapGroupEntryPointArm2Test, SnapGroupActivationTest) {
|
||||
std::unique_ptr<aura::Window> w1(CreateTestWindow());
|
||||
std::unique_ptr<aura::Window> w2(CreateTestWindow());
|
||||
SnapTwoTestWindows(w1.get(), w2.get());
|
||||
EXPECT_FALSE(GetLockWidget());
|
||||
|
||||
ToggleLockWidgetToLockTwoWindows(w1.get(), w2.get());
|
||||
|
||||
std::unique_ptr<aura::Window> w3(CreateTestWindow());
|
||||
wm::ActivateWindow(w3.get());
|
||||
wm::ActivateWindow(w1.get());
|
||||
EXPECT_TRUE(IsStackedBelow(w3.get(), w2.get()));
|
||||
|
||||
ToggleLockWidgetToUnlockTwoWindows(w1.get(), w2.get());
|
||||
|
||||
wm::ActivateWindow(w3.get());
|
||||
wm::ActivateWindow(w1.get());
|
||||
EXPECT_FALSE(IsStackedBelow(w3.get(), w2.get()));
|
||||
}
|
||||
|
||||
} // namespace ash
|
@ -6,34 +6,33 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ash/constants/ash_features.h"
|
||||
#include "ash/public/cpp/shell_window_ids.h"
|
||||
#include "ash/root_window_controller.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/wm/overview/overview_controller.h"
|
||||
#include "ash/wm/resize_shadow_controller.h"
|
||||
#include "ash/wm/snap_group/snap_group_lock_button.h"
|
||||
#include "ash/wm/window_util.h"
|
||||
#include "ash/wm/wm_metrics.h"
|
||||
#include "ash/wm/workspace/workspace_window_resizer.h"
|
||||
#include "base/containers/adapters.h"
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/metrics/user_metrics.h"
|
||||
#include "base/time/time.h"
|
||||
#include "ui/aura/client/aura_constants.h"
|
||||
#include "ui/aura/window_delegate.h"
|
||||
#include "ui/base/cursor/cursor.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
#include "ui/display/screen.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/geometry/point_f.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/views/view.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
#include "ui/wm/core/compound_event_filter.h"
|
||||
#include "ui/wm/core/coordinate_conversion.h"
|
||||
#include "ui/wm/core/window_animations.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
namespace {
|
||||
|
||||
// Delay before hiding the `resize_widget_`.
|
||||
@ -42,6 +41,21 @@ constexpr base::TimeDelta kHideDelay = base::Milliseconds(500);
|
||||
// Padding from the bottom/right edge the resize widget is shown at.
|
||||
const int kResizeWidgetPadding = 15;
|
||||
|
||||
// Distance between the resize widget and lock widget.
|
||||
const int kResizeWidgetAndLockWidgetDistance = 75;
|
||||
|
||||
// Returns the widget init params needed to create the resize widget or snap
|
||||
// group lock widget.
|
||||
views::Widget::InitParams CreateWidgetParams(aura::Window* parent_window,
|
||||
const std::string& widget_name) {
|
||||
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
|
||||
params.parent = parent_window;
|
||||
params.name = widget_name;
|
||||
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
|
||||
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
|
||||
return params;
|
||||
}
|
||||
|
||||
gfx::PointF ConvertPointFromScreen(aura::Window* window,
|
||||
const gfx::PointF& point) {
|
||||
gfx::PointF result(point);
|
||||
@ -83,7 +97,7 @@ bool ContainsScreenY(aura::Window* window, int y_in_screen) {
|
||||
return ContainsY(window, window_loc.y());
|
||||
}
|
||||
|
||||
// Returns true if |p| is on the edge |edge_want| of |window|.
|
||||
// Returns true if `p` is on the edge `edge_want` of `window`.
|
||||
bool PointOnWindowEdge(aura::Window* window,
|
||||
int edge_want,
|
||||
const gfx::Point& p) {
|
||||
@ -106,8 +120,17 @@ bool Intersects(int x1, int max_1, int x2, int max_2) {
|
||||
return x2 <= max_1 && max_2 > x1;
|
||||
}
|
||||
|
||||
// Returns true if the entry point to create and remove the snap group through
|
||||
// the multi-window resizer is enabled.
|
||||
bool CanShowLockWidget() {
|
||||
return features::IsSnapGroupEnabled() &&
|
||||
!features::kAutomaticallyLockGroup.Get();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ResizeView:
|
||||
// View contained in the widget. Passes along mouse events to the
|
||||
// MultiWindowResizeController so that it can start/stop the resize loop.
|
||||
class MultiWindowResizeController::ResizeView : public views::View {
|
||||
@ -117,13 +140,15 @@ class MultiWindowResizeController::ResizeView : public views::View {
|
||||
|
||||
ResizeView(const ResizeView&) = delete;
|
||||
ResizeView& operator=(const ResizeView&) = delete;
|
||||
~ResizeView() override = default;
|
||||
|
||||
// views::View overrides:
|
||||
// views::View:
|
||||
gfx::Size CalculatePreferredSize() const override {
|
||||
const bool vert = direction_ == Direction::kLeftRight;
|
||||
return gfx::Size(vert ? kShortSide : kLongSide,
|
||||
vert ? kLongSide : kShortSide);
|
||||
}
|
||||
|
||||
void OnPaint(gfx::Canvas* canvas) override {
|
||||
cc::PaintFlags flags;
|
||||
flags.setColor(SkColorSetA(SK_ColorBLACK, 0x7F));
|
||||
@ -190,20 +215,26 @@ class MultiWindowResizeController::ResizeView : public views::View {
|
||||
const Direction direction_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ResizeMouseWatcherHost:
|
||||
// MouseWatcherHost implementation for MultiWindowResizeController. Forwards
|
||||
// Contains() to MultiWindowResizeController.
|
||||
class MultiWindowResizeController::ResizeMouseWatcherHost
|
||||
: public views::MouseWatcherHost {
|
||||
public:
|
||||
ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
|
||||
explicit ResizeMouseWatcherHost(MultiWindowResizeController* host)
|
||||
: host_(host) {}
|
||||
|
||||
ResizeMouseWatcherHost(const ResizeMouseWatcherHost&) = delete;
|
||||
ResizeMouseWatcherHost& operator=(const ResizeMouseWatcherHost&) = delete;
|
||||
~ResizeMouseWatcherHost() override = default;
|
||||
|
||||
// MouseWatcherHost overrides:
|
||||
// views::MouseWatcherHost:
|
||||
bool Contains(const gfx::Point& point_in_screen, EventType type) override {
|
||||
return (type == EventType::kPress)
|
||||
? host_->IsOverResizeWidget(point_in_screen)
|
||||
? host_->IsOverResizeWidget(point_in_screen) ||
|
||||
(CanShowLockWidget() &&
|
||||
host_->IsOverLockWidget(point_in_screen))
|
||||
: host_->IsOverWindows(point_in_screen);
|
||||
}
|
||||
|
||||
@ -225,6 +256,8 @@ bool MultiWindowResizeController::ResizeWindows::Equals(
|
||||
direction == other.direction;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MultiWindowResizeController:
|
||||
MultiWindowResizeController::MultiWindowResizeController() {
|
||||
Shell::Get()->overview_controller()->AddObserver(this);
|
||||
}
|
||||
@ -312,8 +345,10 @@ void MultiWindowResizeController::OnOverviewModeStarting() {
|
||||
|
||||
void MultiWindowResizeController::OnOverviewModeEndingAnimationComplete(
|
||||
bool canceled) {
|
||||
if (canceled)
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show resize-lock shadow UI after exiting overview.
|
||||
Shell::Get()->resize_shadow_controller()->TryShowAllShadows();
|
||||
}
|
||||
@ -445,7 +480,7 @@ aura::Window* MultiWindowResizeController::FindWindowTouching(
|
||||
NOTREACHED();
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MultiWindowResizeController::FindWindowsTouching(
|
||||
@ -488,26 +523,25 @@ void MultiWindowResizeController::ShowNow() {
|
||||
show_timer_.Stop();
|
||||
aura::Window* window1 = windows_.window1;
|
||||
aura::Window* window2 = windows_.window2;
|
||||
|
||||
resize_widget_ = std::make_unique<views::Widget>();
|
||||
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
|
||||
params.name = "MultiWindowResizeController";
|
||||
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
|
||||
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
|
||||
params.parent = window1->GetRootWindow()->GetChildById(
|
||||
kShellWindowId_AlwaysOnTopContainer);
|
||||
resize_widget_->set_focus_on_creation(false);
|
||||
resize_widget_->Init(std::move(params));
|
||||
aura::Window* parent_window = window1->GetRootWindow()->GetChildById(
|
||||
kShellWindowId_AlwaysOnTopContainer);
|
||||
resize_widget_->Init(CreateWidgetParams(
|
||||
parent_window, /*widget_name=*/"MultiWindowResizeController"));
|
||||
|
||||
::wm::SetWindowVisibilityAnimationType(
|
||||
resize_widget_->GetNativeWindow(),
|
||||
::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
|
||||
resize_widget_->SetContentsView(
|
||||
std::make_unique<ResizeView>(this, windows_.direction));
|
||||
show_bounds_in_screen_ = ConvertRectToScreen(
|
||||
window1->parent(),
|
||||
CalculateResizeWidgetBounds(gfx::PointF(show_location_in_parent_)));
|
||||
resize_widget_->SetBounds(show_bounds_in_screen_);
|
||||
gfx::Rect resize_widget_bounds =
|
||||
CalculateResizeWidgetBounds(gfx::PointF(show_location_in_parent_));
|
||||
resize_widget_show_bounds_in_screen_ =
|
||||
ConvertRectToScreen(window1->parent(), resize_widget_bounds);
|
||||
resize_widget_->SetBounds(resize_widget_show_bounds_in_screen_);
|
||||
resize_widget_->Show();
|
||||
CreateMouseWatcher();
|
||||
|
||||
base::RecordAction(base::UserMetricsAction(kMultiWindowResizerShow));
|
||||
base::UmaHistogramBoolean(kMultiWindowResizerShowHistogramName, true);
|
||||
@ -518,7 +552,24 @@ void MultiWindowResizeController::ShowNow() {
|
||||
base::UserMetricsAction(kMultiWindowResizerShowTwoWindowsSnapped));
|
||||
base::UmaHistogramBoolean(
|
||||
kMultiWindowResizerShowTwoWindowsSnappedHistogramName, true);
|
||||
|
||||
if (CanShowLockWidget()) {
|
||||
DCHECK(!lock_widget_.get());
|
||||
lock_widget_ = std::make_unique<views::Widget>();
|
||||
lock_widget_->Init(CreateWidgetParams(
|
||||
parent_window, /*widget_name=*/"SnapGroupLockWidget"));
|
||||
lock_button_ = lock_widget_->SetContentsView(
|
||||
std::make_unique<SnapGroupLockButton>(window1, window2));
|
||||
|
||||
gfx::Rect lock_widget_show_bounds_in_screen =
|
||||
ConvertRectToScreen(windows_.window1->parent(),
|
||||
CalculateLockWidgetBounds(resize_widget_bounds));
|
||||
lock_widget_->SetBounds(lock_widget_show_bounds_in_screen);
|
||||
lock_widget_->Show();
|
||||
}
|
||||
}
|
||||
|
||||
CreateMouseWatcher();
|
||||
}
|
||||
|
||||
bool MultiWindowResizeController::IsShowing() const {
|
||||
@ -526,8 +577,10 @@ bool MultiWindowResizeController::IsShowing() const {
|
||||
}
|
||||
|
||||
void MultiWindowResizeController::Hide() {
|
||||
if (window_resizer_)
|
||||
return; // Ignore hides while actively resizing.
|
||||
// Ignore `Hide` while actively resizing.
|
||||
if (window_resizer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (windows_.window1) {
|
||||
StopObserving(windows_.window1);
|
||||
@ -539,12 +592,16 @@ void MultiWindowResizeController::Hide() {
|
||||
}
|
||||
|
||||
show_timer_.Stop();
|
||||
lock_widget_.reset();
|
||||
|
||||
if (!resize_widget_)
|
||||
if (!resize_widget_) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto* window : windows_.other_windows)
|
||||
for (auto* window : windows_.other_windows) {
|
||||
StopObserving(window);
|
||||
}
|
||||
|
||||
mouse_watcher_.reset();
|
||||
resize_widget_.reset();
|
||||
windows_ = ResizeWindows();
|
||||
@ -567,10 +624,12 @@ void MultiWindowResizeController::StartResize(
|
||||
DCHECK(windows_.other_windows.empty());
|
||||
FindWindowsTouching(windows_.window2, windows_.direction,
|
||||
&windows_.other_windows);
|
||||
for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
|
||||
StartObserving(windows_.other_windows[i]);
|
||||
windows.push_back(windows_.other_windows[i]);
|
||||
|
||||
for (auto* other_window : windows_.other_windows) {
|
||||
StartObserving(other_window);
|
||||
windows.push_back(other_window);
|
||||
}
|
||||
|
||||
int component =
|
||||
windows_.direction == Direction::kLeftRight ? HTRIGHT : HTBOTTOM;
|
||||
WindowState* window_state = WindowState::Get(windows_.window1);
|
||||
@ -601,10 +660,12 @@ void MultiWindowResizeController::Resize(const gfx::PointF& location_in_screen,
|
||||
ConvertRectToScreen(windows_.window1->parent(),
|
||||
CalculateResizeWidgetBounds(location_in_parent));
|
||||
|
||||
if (windows_.direction == Direction::kLeftRight)
|
||||
bounds.set_y(show_bounds_in_screen_.y());
|
||||
else
|
||||
bounds.set_x(show_bounds_in_screen_.x());
|
||||
if (windows_.direction == Direction::kLeftRight) {
|
||||
bounds.set_y(resize_widget_show_bounds_in_screen_.y());
|
||||
} else {
|
||||
bounds.set_x(resize_widget_show_bounds_in_screen_.x());
|
||||
}
|
||||
|
||||
resize_widget_->SetBounds(bounds);
|
||||
}
|
||||
|
||||
@ -619,19 +680,23 @@ void MultiWindowResizeController::CompleteResize() {
|
||||
Hide();
|
||||
} else {
|
||||
// If the mouse is over the resizer we need to remove observers on any of
|
||||
// the |other_windows|. If we start another resize we'll recalculate the
|
||||
// |other_windows| and invoke AddObserver() as necessary.
|
||||
for (size_t i = 0; i < windows_.other_windows.size(); ++i)
|
||||
StopObserving(windows_.other_windows[i]);
|
||||
windows_.other_windows.clear();
|
||||
// the `other_windows`. If we start another resize we'll recalculate the
|
||||
// `other_windows` and invoke AddObserver() as necessary.
|
||||
for (auto* other_window : windows_.other_windows) {
|
||||
StopObserving(other_window);
|
||||
}
|
||||
|
||||
windows_.other_windows.clear();
|
||||
CreateMouseWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiWindowResizeController::CancelResize() {
|
||||
if (!window_resizer_)
|
||||
return; // Happens if window was destroyed and we nuked the WindowResizer.
|
||||
// Happens if window was destroyed and we nuked the WindowResizer.
|
||||
if (!window_resizer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
window_resizer_->RevertDrag();
|
||||
WindowState::Get(window_resizer_->GetTarget())->DeleteDragDetails();
|
||||
ResetResizer();
|
||||
@ -659,15 +724,40 @@ gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
|
||||
return gfx::Rect(x, y, pref.width(), pref.height());
|
||||
}
|
||||
|
||||
gfx::Rect MultiWindowResizeController::CalculateLockWidgetBounds(
|
||||
const gfx::Rect& resize_widget_bounds) const {
|
||||
if (windows_.direction == Direction::kLeftRight) {
|
||||
return gfx::Rect(
|
||||
resize_widget_bounds.x(),
|
||||
resize_widget_bounds.y() + kResizeWidgetAndLockWidgetDistance,
|
||||
resize_widget_bounds.width(), resize_widget_bounds.width());
|
||||
} else {
|
||||
return gfx::Rect(
|
||||
resize_widget_bounds.x() + kResizeWidgetAndLockWidgetDistance,
|
||||
resize_widget_bounds.y(), resize_widget_bounds.height(),
|
||||
resize_widget_bounds.height());
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiWindowResizeController::IsOverResizeWidget(
|
||||
const gfx::Point& location_in_screen) const {
|
||||
return resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen);
|
||||
}
|
||||
|
||||
bool MultiWindowResizeController::IsOverLockWidget(
|
||||
const gfx::Point& location_in_screen) const {
|
||||
return lock_widget_->GetWindowBoundsInScreen().Contains(location_in_screen);
|
||||
}
|
||||
|
||||
bool MultiWindowResizeController::IsOverWindows(
|
||||
const gfx::Point& location_in_screen) const {
|
||||
if (IsOverResizeWidget(location_in_screen))
|
||||
if (IsOverResizeWidget(location_in_screen)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CanShowLockWidget() && IsOverLockWidget(location_in_screen)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (windows_.direction == Direction::kTopBottom) {
|
||||
if (!ContainsScreenX(windows_.window1, location_in_screen.x()) ||
|
||||
@ -681,7 +771,7 @@ bool MultiWindowResizeController::IsOverWindows(
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether |location_in_screen| is in the event target's resize region.
|
||||
// Check whether `location_in_screen` is in the event target's resize region.
|
||||
// This is tricky because a window's resize region can extend outside a
|
||||
// window's bounds.
|
||||
aura::Window* target = RootWindowController::ForWindow(windows_.window1)
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "ash/ash_export.h"
|
||||
#include "ash/wm/overview/overview_observer.h"
|
||||
#include "ash/wm/snap_group/snap_group_lock_button.h"
|
||||
#include "ash/wm/window_state.h"
|
||||
#include "ash/wm/window_state_observer.h"
|
||||
#include "base/scoped_multi_source_observation.h"
|
||||
@ -34,7 +35,10 @@ class WorkspaceWindowResizer;
|
||||
|
||||
// MultiWindowResizeController is responsible for determining and showing a
|
||||
// widget that allows resizing multiple windows at the same time.
|
||||
// MultiWindowResizeController is driven by WorkspaceEventHandler.
|
||||
// MultiWindowResizeController is driven by WorkspaceEventHandler. It can also
|
||||
// be an entry point to create or remove a snap group which is guarded by the
|
||||
// feature flag `kSnapGroup` and will only be available when the feature param
|
||||
// `kAutomaticallyLockGroup` is false.
|
||||
class ASH_EXPORT MultiWindowResizeController
|
||||
: public views::MouseWatcherListener,
|
||||
public aura::WindowObserver,
|
||||
@ -74,8 +78,11 @@ class ASH_EXPORT MultiWindowResizeController
|
||||
void OnOverviewModeStarting() override;
|
||||
void OnOverviewModeEndingAnimationComplete(bool canceled) override;
|
||||
|
||||
SnapGroupLockButton* lock_button_for_testing() const { return lock_button_; }
|
||||
|
||||
private:
|
||||
friend class MultiWindowResizeControllerTest;
|
||||
friend class SnapGroupEntryPointArm2Test;
|
||||
class ResizeMouseWatcherHost;
|
||||
class ResizeView;
|
||||
|
||||
@ -156,7 +163,7 @@ class ASH_EXPORT MultiWindowResizeController
|
||||
// Returns true if the widget is showing.
|
||||
bool IsShowing() const;
|
||||
|
||||
// Hides the resize widget.
|
||||
// Hides the resize widget and lock widget if it gets created.
|
||||
void Hide();
|
||||
|
||||
// Resets the window resizer and hides the resize widget.
|
||||
@ -178,10 +185,20 @@ class ASH_EXPORT MultiWindowResizeController
|
||||
gfx::Rect CalculateResizeWidgetBounds(
|
||||
const gfx::PointF& location_in_parent) const;
|
||||
|
||||
// Returns true if |location_in_screen| is over the resize widget.
|
||||
// Returns the bounds for the `lock_widget_` based on the
|
||||
// `resize_widget_bounds`.
|
||||
gfx::Rect CalculateLockWidgetBounds(
|
||||
const gfx::Rect& resize_widget_bounds) const;
|
||||
|
||||
// Returns true if `location_in_screen` is over the resize widget.
|
||||
bool IsOverResizeWidget(const gfx::Point& location_in_screen) const;
|
||||
|
||||
// Returns true if |location_in_screen| is over the resize windows
|
||||
// Returns true if `location_in_screen` is over the resize widget.
|
||||
// TODO(michelefan): combine with `IsOverResizeWidget` to create a more
|
||||
// general function if arm2 under the `kSnapGroup` flag is enabled by default.
|
||||
bool IsOverLockWidget(const gfx::Point& location_in_screen) const;
|
||||
|
||||
// Returns true if `location_in_screen` is over the resize windows
|
||||
// (or the resize widget itself).
|
||||
bool IsOverWindows(const gfx::Point& location_in_screen) const;
|
||||
|
||||
@ -198,14 +215,24 @@ class ASH_EXPORT MultiWindowResizeController
|
||||
|
||||
std::unique_ptr<views::Widget> resize_widget_;
|
||||
|
||||
// The lock widget that is used to create or remove a snap group when
|
||||
// `kAutomaticallyLockGroup` of `kSnapGroup` is false.
|
||||
std::unique_ptr<views::Widget> lock_widget_;
|
||||
|
||||
// The contents view of the `lock_widget_`.
|
||||
SnapGroupLockButton* lock_button_;
|
||||
|
||||
// If non-null we're in a resize loop.
|
||||
std::unique_ptr<WorkspaceWindowResizer> window_resizer_;
|
||||
|
||||
// Mouse coordinate passed to Show() in container's coodinates.
|
||||
gfx::Point show_location_in_parent_;
|
||||
|
||||
// Bounds the widget was last shown at in screen coordinates.
|
||||
gfx::Rect show_bounds_in_screen_;
|
||||
// Bounds the resize widget was last shown at in screen coordinates.
|
||||
gfx::Rect resize_widget_show_bounds_in_screen_;
|
||||
|
||||
// Bounds the lock widget was last shown at in screen coordinates.
|
||||
gfx::Rect lock_widget_show_bounds_in_screen_;
|
||||
|
||||
// Used to detect whether the mouse is over the windows. While
|
||||
// |resize_widget_| is non-NULL (ie the widget is showing) we ignore calls
|
||||
|
@ -69,6 +69,7 @@ class MultiWindowResizeControllerTest : public AshTestBase {
|
||||
|
||||
~MultiWindowResizeControllerTest() override = default;
|
||||
|
||||
// AshTestBase:
|
||||
void SetUp() override {
|
||||
AshTestBase::SetUp();
|
||||
WorkspaceController* wc = ShellTestApi().workspace_controller();
|
||||
|
Reference in New Issue
Block a user