Commit 527876b8 authored by chinsenj's avatar chinsenj Committed by Commit Bot

cros: Add mouse behaviour to window cycle list (alt-tab).

When a user alt-tabs they can only navigate the window cycle list using
the keyboard.

To make this feature more interactive, this CL adds mouse behaviour to
the window cycle list. This CL makes it so when a user hovers over an
item, the focus ring follows the mouse. Also when a user presses on an
item, it closes the window cycle list and activates the corresponding
window.

Test: WindowCycleControllerTest.MouseHoverAndSelect
Bug: 1067327
Change-Id: I293d0416fbf764ddfb0116f65a33c17f288f646a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2357329Reviewed-by: default avatarAhmed Fakhry <afakhry@chromium.org>
Reviewed-by: default avatarXiaoqian Dai <xdai@chromium.org>
Reviewed-by: default avatarSammie Quon <sammiequon@chromium.org>
Commit-Queue: Jeremy Chinsen <chinsenj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800347}
parent 911b7b5c
...@@ -75,6 +75,9 @@ const base::Feature kLockScreenMediaControls{"LockScreenMediaControls", ...@@ -75,6 +75,9 @@ const base::Feature kLockScreenMediaControls{"LockScreenMediaControls",
const base::Feature kHideArcMediaNotifications{ const base::Feature kHideArcMediaNotifications{
"HideArcMediaNotifications", base::FEATURE_ENABLED_BY_DEFAULT}; "HideArcMediaNotifications", base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kInteractiveWindowCycleList{"InteractiveWindowCycleList",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kManagedDeviceUIRedesign{"ManagedDeviceUIRedesign", const base::Feature kManagedDeviceUIRedesign{"ManagedDeviceUIRedesign",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
...@@ -189,6 +192,10 @@ bool IsLockScreenHideSensitiveNotificationsSupported() { ...@@ -189,6 +192,10 @@ bool IsLockScreenHideSensitiveNotificationsSupported() {
kLockScreenHideSensitiveNotificationsSupport); kLockScreenHideSensitiveNotificationsSupport);
} }
bool IsInteractiveWindowCycleListEnabled() {
return base::FeatureList::IsEnabled(kInteractiveWindowCycleList);
}
bool IsManagedDeviceUIRedesignEnabled() { bool IsManagedDeviceUIRedesignEnabled() {
return base::FeatureList::IsEnabled(kManagedDeviceUIRedesign); return base::FeatureList::IsEnabled(kManagedDeviceUIRedesign);
} }
......
...@@ -69,6 +69,11 @@ ASH_PUBLIC_EXPORT extern const base::Feature kLockScreenMediaControls; ...@@ -69,6 +69,11 @@ ASH_PUBLIC_EXPORT extern const base::Feature kLockScreenMediaControls;
// TODO(beccahughes): Remove after launch. (https://crbug.com/897836) // TODO(beccahughes): Remove after launch. (https://crbug.com/897836)
ASH_PUBLIC_EXPORT extern const base::Feature kHideArcMediaNotifications; ASH_PUBLIC_EXPORT extern const base::Feature kHideArcMediaNotifications;
// Enables more ways to interact with the window cycle list, i.e. mouse, touch,
// gestures, and alternate keys.
// TODO(chinsenj): Remove this when the feature is fully launched.
ASH_PUBLIC_EXPORT extern const base::Feature kInteractiveWindowCycleList;
// Enables the redesigned managed device info UI in the system tray. // Enables the redesigned managed device info UI in the system tray.
ASH_PUBLIC_EXPORT extern const base::Feature kManagedDeviceUIRedesign; ASH_PUBLIC_EXPORT extern const base::Feature kManagedDeviceUIRedesign;
...@@ -182,6 +187,8 @@ ASH_PUBLIC_EXPORT bool IsKeyboardShortcutViewerAppEnabled(); ...@@ -182,6 +187,8 @@ ASH_PUBLIC_EXPORT bool IsKeyboardShortcutViewerAppEnabled();
ASH_PUBLIC_EXPORT bool IsLockScreenNotificationsEnabled(); ASH_PUBLIC_EXPORT bool IsLockScreenNotificationsEnabled();
ASH_PUBLIC_EXPORT bool IsInteractiveWindowCycleListEnabled();
ASH_PUBLIC_EXPORT bool IsManagedDeviceUIRedesignEnabled(); ASH_PUBLIC_EXPORT bool IsManagedDeviceUIRedesignEnabled();
ASH_PUBLIC_EXPORT bool IsLockScreenInlineReplyEnabled(); ASH_PUBLIC_EXPORT bool IsLockScreenInlineReplyEnabled();
......
...@@ -124,11 +124,20 @@ void WindowCycleController::CancelCycling() { ...@@ -124,11 +124,20 @@ void WindowCycleController::CancelCycling() {
StopCycling(); StopCycling();
} }
void WindowCycleController::StepToWindow(aura::Window* window) {
DCHECK(window_cycle_list_);
window_cycle_list_->StepToWindow(window);
}
bool WindowCycleController::IsEventInCycleView(ui::MouseEvent* event) {
return window_cycle_list_ && window_cycle_list_->IsEventInCycleView(event);
}
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// WindowCycleController, private: // WindowCycleController, private:
void WindowCycleController::Step(Direction direction) { void WindowCycleController::Step(Direction direction) {
DCHECK(window_cycle_list_.get()); DCHECK(window_cycle_list_);
window_cycle_list_->Step(direction); window_cycle_list_->Step(direction);
} }
......
...@@ -16,6 +16,10 @@ namespace aura { ...@@ -16,6 +16,10 @@ namespace aura {
class Window; class Window;
} }
namespace ui {
class MouseEvent;
}
namespace ash { namespace ash {
class WindowCycleEventFilter; class WindowCycleEventFilter;
...@@ -56,6 +60,12 @@ class ASH_EXPORT WindowCycleController { ...@@ -56,6 +60,12 @@ class ASH_EXPORT WindowCycleController {
void CompleteCycling(); void CompleteCycling();
void CancelCycling(); void CancelCycling();
// Skip window cycle list directly to |window|.
void StepToWindow(aura::Window* window);
// Checks whether |event| occurs within the cycle view.
bool IsEventInCycleView(ui::MouseEvent* event);
// Returns the WindowCycleList. // Returns the WindowCycleList.
const WindowCycleList* window_cycle_list() const { const WindowCycleList* window_cycle_list() const {
return window_cycle_list_.get(); return window_cycle_list_.get();
......
...@@ -116,6 +116,13 @@ class WindowCycleControllerTest : public AshTestBase { ...@@ -116,6 +116,13 @@ class WindowCycleControllerTest : public AshTestBase {
->widget(); ->widget();
} }
const views::View::Views& GetWindowCycleItemViews() const {
return Shell::Get()
->window_cycle_controller()
->window_cycle_list()
->GetWindowCycleItemViewsForTesting();
}
private: private:
std::unique_ptr<ShelfViewTestAPI> shelf_view_test_; std::unique_ptr<ShelfViewTestAPI> shelf_view_test_;
...@@ -574,6 +581,11 @@ TEST_F(WindowCycleControllerTest, TabKeyNotLeaked) { ...@@ -574,6 +581,11 @@ TEST_F(WindowCycleControllerTest, TabKeyNotLeaked) {
// While the UI is active, mouse events are captured. // While the UI is active, mouse events are captured.
TEST_F(WindowCycleControllerTest, MouseEventsCaptured) { TEST_F(WindowCycleControllerTest, MouseEventsCaptured) {
// Set up a second root window
UpdateDisplay("1000x600,600x400");
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
ASSERT_EQ(2U, root_windows.size());
// This delegate allows the window to receive mouse events. // This delegate allows the window to receive mouse events.
aura::test::TestWindowDelegate delegate; aura::test::TestWindowDelegate delegate;
std::unique_ptr<Window> w0(CreateTestWindowInShellWithDelegate( std::unique_ptr<Window> w0(CreateTestWindowInShellWithDelegate(
...@@ -606,6 +618,11 @@ TEST_F(WindowCycleControllerTest, MouseEventsCaptured) { ...@@ -606,6 +618,11 @@ TEST_F(WindowCycleControllerTest, MouseEventsCaptured) {
controller->CompleteCycling(); controller->CompleteCycling();
generator->ClickLeftButton(); generator->ClickLeftButton();
EXPECT_LT(0, event_count.GetMouseEventCountAndReset()); EXPECT_LT(0, event_count.GetMouseEventCountAndReset());
// Click somewhere on the second root window.
generator->MoveMouseToCenterOf(root_windows[1]);
generator->ClickLeftButton();
EXPECT_EQ(0, event_count.GetMouseEventCountAndReset());
} }
// Tests that we can cycle past fullscreen windows: https://crbug.com/622396. // Tests that we can cycle past fullscreen windows: https://crbug.com/622396.
...@@ -828,6 +845,62 @@ TEST_F(LimitedWindowCycleControllerTest, CycleShowsActiveDeskWindows) { ...@@ -828,6 +845,62 @@ TEST_F(LimitedWindowCycleControllerTest, CycleShowsActiveDeskWindows) {
EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); EXPECT_EQ(win0.get(), window_util::GetActiveWindow());
} }
class InteractiveWindowCycleControllerTest : public WindowCycleControllerTest {
public:
InteractiveWindowCycleControllerTest() = default;
InteractiveWindowCycleControllerTest(const InteractiveWindowCycleControllerTest&) =
delete;
InteractiveWindowCycleControllerTest& operator=(
const InteractiveWindowCycleControllerTest&) = delete;
~InteractiveWindowCycleControllerTest() override = default;
// WindowCycleControllerTest:
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
features::kInteractiveWindowCycleList);
WindowCycleControllerTest::SetUp();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// When a user hovers their mouse over an item, it should cycle to it.
// If a user clicks on an item, it should complete cycling and activate
// the hovered item.
TEST_F(InteractiveWindowCycleControllerTest, MouseHoverAndSelect) {
std::unique_ptr<Window> w0 = CreateTestWindow();
std::unique_ptr<Window> w1 = CreateTestWindow();
std::unique_ptr<Window> w2 = CreateTestWindow();
ui::test::EventGenerator* generator = GetEventGenerator();
WindowCycleController* controller = Shell::Get()->window_cycle_controller();
// Cycle to the third item, mouse over second item, and release alt-tab.
// Starting order of windows in cycle list is [2,1,0].
controller->HandleCycleWindow(WindowCycleController::FORWARD);
controller->HandleCycleWindow(WindowCycleController::FORWARD);
generator->MoveMouseTo(
GetWindowCycleItemViews()[1]->GetBoundsInScreen().CenterPoint());
controller->CompleteCycling();
EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
// Start cycle, mouse over third item, and release alt-tab.
// Starting order of windows in cycle list is [1,2,0].
controller->StartCycling();
generator->MoveMouseTo(
GetWindowCycleItemViews()[2]->GetBoundsInScreen().CenterPoint());
controller->CompleteCycling();
EXPECT_TRUE(wm::IsActiveWindow(w0.get()));
// Start cycle, mouse over second item, and click.
// Starting order of windows in cycle list is [0,1,2].
controller->StartCycling();
generator->MoveMouseTo(
GetWindowCycleItemViews()[1]->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
}
// Tests that frame throttling starts and ends accordingly when window cycling // Tests that frame throttling starts and ends accordingly when window cycling
// starts and ends. // starts and ends.
TEST_F(WindowCycleControllerTest, FrameThrottling) { TEST_F(WindowCycleControllerTest, FrameThrottling) {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "ash/wm/window_cycle_event_filter.h" #include "ash/wm/window_cycle_event_filter.h"
#include "ash/accelerators/debug_commands.h" #include "ash/accelerators/debug_commands.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/wm/window_cycle_controller.h" #include "ash/wm/window_cycle_controller.h"
#include "ash/wm/window_cycle_list.h" #include "ash/wm/window_cycle_list.h"
...@@ -56,6 +57,11 @@ void WindowCycleEventFilter::OnKeyEvent(ui::KeyEvent* event) { ...@@ -56,6 +57,11 @@ void WindowCycleEventFilter::OnKeyEvent(ui::KeyEvent* event) {
} }
void WindowCycleEventFilter::OnMouseEvent(ui::MouseEvent* event) { void WindowCycleEventFilter::OnMouseEvent(ui::MouseEvent* event) {
if (features::IsInteractiveWindowCycleListEnabled() &&
Shell::Get()->window_cycle_controller()->IsEventInCycleView(event)) {
return;
}
// Prevent mouse clicks from doing anything while the Alt+Tab UI is active // Prevent mouse clicks from doing anything while the Alt+Tab UI is active
// <crbug.com/641171> but don't interfere with drag and drop operations // <crbug.com/641171> but don't interfere with drag and drop operations
// <crbug.com/660945>. // <crbug.com/660945>.
......
...@@ -120,6 +120,7 @@ class WindowCycleItemView : public WindowMiniView { ...@@ -120,6 +120,7 @@ class WindowCycleItemView : public WindowMiniView {
public: public:
explicit WindowCycleItemView(aura::Window* window) : WindowMiniView(window) { explicit WindowCycleItemView(aura::Window* window) : WindowMiniView(window) {
SetFocusBehavior(FocusBehavior::ALWAYS); SetFocusBehavior(FocusBehavior::ALWAYS);
set_notify_enter_exit_on_child(true);
} }
WindowCycleItemView(const WindowCycleItemView&) = delete; WindowCycleItemView(const WindowCycleItemView&) = delete;
WindowCycleItemView& operator=(const WindowCycleItemView&) = delete; WindowCycleItemView& operator=(const WindowCycleItemView&) = delete;
...@@ -136,6 +137,16 @@ class WindowCycleItemView : public WindowMiniView { ...@@ -136,6 +137,16 @@ class WindowCycleItemView : public WindowMiniView {
UpdatePreviewRoundedCorners(/*show=*/true); UpdatePreviewRoundedCorners(/*show=*/true);
} }
// views::View:
void OnMouseEntered(const ui::MouseEvent& event) override {
Shell::Get()->window_cycle_controller()->StepToWindow(source_window());
}
bool OnMousePressed(const ui::MouseEvent& event) override {
Shell::Get()->window_cycle_controller()->CompleteCycling();
return true;
}
private: private:
// WindowMiniView: // WindowMiniView:
gfx::Size GetPreviewViewSize() const override { gfx::Size GetPreviewViewSize() const override {
...@@ -413,6 +424,10 @@ class WindowCycleView : public views::WidgetDelegateView, ...@@ -413,6 +424,10 @@ class WindowCycleView : public views::WidgetDelegateView,
return window_view_map_[target_window_]; return window_view_map_[target_window_];
} }
const views::View::Views& GetPreviewViewsForTesting() const {
return mirror_container_->children();
}
// ui::ImplicitAnimationObserver: // ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override { void OnImplicitAnimationsCompleted() override {
occlusion_tracker_pauser_.reset(); occlusion_tracker_pauser_.reset();
...@@ -481,7 +496,7 @@ WindowCycleList::~WindowCycleList() { ...@@ -481,7 +496,7 @@ WindowCycleList::~WindowCycleList() {
Shell::Get()->frame_throttling_controller()->EndThrottling(); Shell::Get()->frame_throttling_controller()->EndThrottling();
} }
void WindowCycleList::Step(WindowCycleController::Direction direction) { void WindowCycleList::Step(int offset) {
if (windows_.empty()) if (windows_.empty())
return; return;
...@@ -499,13 +514,11 @@ void WindowCycleList::Step(WindowCycleController::Direction direction) { ...@@ -499,13 +514,11 @@ void WindowCycleList::Step(WindowCycleController::Direction direction) {
// Special case the situation where we're cycling forward but the MRU // Special case the situation where we're cycling forward but the MRU
// window is not active. This occurs when all windows are minimized. The // window is not active. This occurs when all windows are minimized. The
// starting window should be the first one rather than the second. // starting window should be the first one rather than the second.
if (direction == WindowCycleController::FORWARD && if (offset == 1 && !wm::IsActiveWindow(windows_[0]))
!wm::IsActiveWindow(windows_[0]))
current_index_ = -1; current_index_ = -1;
} }
// We're in a valid cycle, so step forward or backward. current_index_ += offset;
current_index_ += direction == WindowCycleController::FORWARD ? 1 : -1;
// Wrap to window list size. // Wrap to window list size.
current_index_ = (current_index_ + windows_.size()) % windows_.size(); current_index_ = (current_index_ + windows_.size()) % windows_.size();
...@@ -520,6 +533,23 @@ void WindowCycleList::Step(WindowCycleController::Direction direction) { ...@@ -520,6 +533,23 @@ void WindowCycleList::Step(WindowCycleController::Direction direction) {
} }
} }
void WindowCycleList::Step(WindowCycleController::Direction direction) {
Step(direction == WindowCycleController::FORWARD ? 1 : -1);
}
void WindowCycleList::StepToWindow(aura::Window* window) {
auto target_window = std::find(windows_.begin(), windows_.end(), window);
DCHECK(target_window != windows_.end());
int target_index = std::distance(windows_.begin(), target_window);
Step(target_index - current_index_);
}
bool WindowCycleList::IsEventInCycleView(ui::MouseEvent* event) {
return cycle_view_->GetBoundsInScreen().Contains(
display::Screen::GetScreen()->GetCursorScreenPoint());
}
// static // static
void WindowCycleList::DisableInitialDelayForTesting() { void WindowCycleList::DisableInitialDelayForTesting() {
g_disable_initial_delay = true; g_disable_initial_delay = true;
...@@ -644,4 +674,9 @@ void WindowCycleList::SelectWindow(aura::Window* window) { ...@@ -644,4 +674,9 @@ void WindowCycleList::SelectWindow(aura::Window* window) {
window_selected_ = true; window_selected_ = true;
} }
const views::View::Views& WindowCycleList::GetWindowCycleItemViewsForTesting()
const {
return cycle_view_->GetPreviewViewsForTesting();
}
} // namespace ash } // namespace ash
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "ui/aura/window_observer.h" #include "ui/aura/window_observer.h"
#include "ui/display/display_observer.h" #include "ui/display/display_observer.h"
#include "ui/display/screen.h" #include "ui/display/screen.h"
#include "ui/views/view.h"
namespace aura { namespace aura {
class ScopedWindowTargeter; class ScopedWindowTargeter;
...@@ -44,6 +45,12 @@ class ASH_EXPORT WindowCycleList : public aura::WindowObserver, ...@@ -44,6 +45,12 @@ class ASH_EXPORT WindowCycleList : public aura::WindowObserver,
// Cycles to the next or previous window based on |direction|. // Cycles to the next or previous window based on |direction|.
void Step(WindowCycleController::Direction direction); void Step(WindowCycleController::Direction direction);
// Skip window cycle list directly to |window|.
void StepToWindow(aura::Window* window);
// Checks whether |event| occurs within the cycle view.
bool IsEventInCycleView(ui::MouseEvent* event);
void set_user_did_accept(bool user_did_accept) { void set_user_did_accept(bool user_did_accept) {
user_did_accept_ = user_did_accept; user_did_accept_ = user_did_accept;
} }
...@@ -76,6 +83,12 @@ class ASH_EXPORT WindowCycleList : public aura::WindowObserver, ...@@ -76,6 +83,12 @@ class ASH_EXPORT WindowCycleList : public aura::WindowObserver,
// PIP. // PIP.
void SelectWindow(aura::Window* window); void SelectWindow(aura::Window* window);
// Cycles windows by |offset|.
void Step(int offset);
// Returns the views for the window cycle list.
const views::View::Views& GetWindowCycleItemViewsForTesting() const;
// List of weak pointers to windows to use while cycling with the keyboard. // List of weak pointers to windows to use while cycling with the keyboard.
// List is built when the user initiates the gesture (i.e. hits alt-tab the // List is built when the user initiates the gesture (i.e. hits alt-tab the
// first time) and is emptied when the gesture is complete (i.e. releases the // first time) and is emptied when the gesture is complete (i.e. releases the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment