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",
const base::Feature kHideArcMediaNotifications{
"HideArcMediaNotifications", base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kInteractiveWindowCycleList{"InteractiveWindowCycleList",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kManagedDeviceUIRedesign{"ManagedDeviceUIRedesign",
base::FEATURE_ENABLED_BY_DEFAULT};
......@@ -189,6 +192,10 @@ bool IsLockScreenHideSensitiveNotificationsSupported() {
kLockScreenHideSensitiveNotificationsSupport);
}
bool IsInteractiveWindowCycleListEnabled() {
return base::FeatureList::IsEnabled(kInteractiveWindowCycleList);
}
bool IsManagedDeviceUIRedesignEnabled() {
return base::FeatureList::IsEnabled(kManagedDeviceUIRedesign);
}
......
......@@ -69,6 +69,11 @@ ASH_PUBLIC_EXPORT extern const base::Feature kLockScreenMediaControls;
// TODO(beccahughes): Remove after launch. (https://crbug.com/897836)
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.
ASH_PUBLIC_EXPORT extern const base::Feature kManagedDeviceUIRedesign;
......@@ -182,6 +187,8 @@ ASH_PUBLIC_EXPORT bool IsKeyboardShortcutViewerAppEnabled();
ASH_PUBLIC_EXPORT bool IsLockScreenNotificationsEnabled();
ASH_PUBLIC_EXPORT bool IsInteractiveWindowCycleListEnabled();
ASH_PUBLIC_EXPORT bool IsManagedDeviceUIRedesignEnabled();
ASH_PUBLIC_EXPORT bool IsLockScreenInlineReplyEnabled();
......
......@@ -124,11 +124,20 @@ void WindowCycleController::CancelCycling() {
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:
void WindowCycleController::Step(Direction direction) {
DCHECK(window_cycle_list_.get());
DCHECK(window_cycle_list_);
window_cycle_list_->Step(direction);
}
......
......@@ -16,6 +16,10 @@ namespace aura {
class Window;
}
namespace ui {
class MouseEvent;
}
namespace ash {
class WindowCycleEventFilter;
......@@ -56,6 +60,12 @@ class ASH_EXPORT WindowCycleController {
void CompleteCycling();
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.
const WindowCycleList* window_cycle_list() const {
return window_cycle_list_.get();
......
......@@ -116,6 +116,13 @@ class WindowCycleControllerTest : public AshTestBase {
->widget();
}
const views::View::Views& GetWindowCycleItemViews() const {
return Shell::Get()
->window_cycle_controller()
->window_cycle_list()
->GetWindowCycleItemViewsForTesting();
}
private:
std::unique_ptr<ShelfViewTestAPI> shelf_view_test_;
......@@ -574,6 +581,11 @@ TEST_F(WindowCycleControllerTest, TabKeyNotLeaked) {
// While the UI is active, mouse events are captured.
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.
aura::test::TestWindowDelegate delegate;
std::unique_ptr<Window> w0(CreateTestWindowInShellWithDelegate(
......@@ -606,6 +618,11 @@ TEST_F(WindowCycleControllerTest, MouseEventsCaptured) {
controller->CompleteCycling();
generator->ClickLeftButton();
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.
......@@ -828,6 +845,62 @@ TEST_F(LimitedWindowCycleControllerTest, CycleShowsActiveDeskWindows) {
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
// starts and ends.
TEST_F(WindowCycleControllerTest, FrameThrottling) {
......
......@@ -5,6 +5,7 @@
#include "ash/wm/window_cycle_event_filter.h"
#include "ash/accelerators/debug_commands.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/shell.h"
#include "ash/wm/window_cycle_controller.h"
#include "ash/wm/window_cycle_list.h"
......@@ -56,6 +57,11 @@ void WindowCycleEventFilter::OnKeyEvent(ui::KeyEvent* 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
// <crbug.com/641171> but don't interfere with drag and drop operations
// <crbug.com/660945>.
......
......@@ -120,6 +120,7 @@ class WindowCycleItemView : public WindowMiniView {
public:
explicit WindowCycleItemView(aura::Window* window) : WindowMiniView(window) {
SetFocusBehavior(FocusBehavior::ALWAYS);
set_notify_enter_exit_on_child(true);
}
WindowCycleItemView(const WindowCycleItemView&) = delete;
WindowCycleItemView& operator=(const WindowCycleItemView&) = delete;
......@@ -136,6 +137,16 @@ class WindowCycleItemView : public WindowMiniView {
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:
// WindowMiniView:
gfx::Size GetPreviewViewSize() const override {
......@@ -413,6 +424,10 @@ class WindowCycleView : public views::WidgetDelegateView,
return window_view_map_[target_window_];
}
const views::View::Views& GetPreviewViewsForTesting() const {
return mirror_container_->children();
}
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override {
occlusion_tracker_pauser_.reset();
......@@ -481,7 +496,7 @@ WindowCycleList::~WindowCycleList() {
Shell::Get()->frame_throttling_controller()->EndThrottling();
}
void WindowCycleList::Step(WindowCycleController::Direction direction) {
void WindowCycleList::Step(int offset) {
if (windows_.empty())
return;
......@@ -499,13 +514,11 @@ void WindowCycleList::Step(WindowCycleController::Direction direction) {
// Special case the situation where we're cycling forward but the MRU
// window is not active. This occurs when all windows are minimized. The
// starting window should be the first one rather than the second.
if (direction == WindowCycleController::FORWARD &&
!wm::IsActiveWindow(windows_[0]))
if (offset == 1 && !wm::IsActiveWindow(windows_[0]))
current_index_ = -1;
}
// We're in a valid cycle, so step forward or backward.
current_index_ += direction == WindowCycleController::FORWARD ? 1 : -1;
current_index_ += offset;
// Wrap to window list size.
current_index_ = (current_index_ + windows_.size()) % windows_.size();
......@@ -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
void WindowCycleList::DisableInitialDelayForTesting() {
g_disable_initial_delay = true;
......@@ -644,4 +674,9 @@ void WindowCycleList::SelectWindow(aura::Window* window) {
window_selected_ = true;
}
const views::View::Views& WindowCycleList::GetWindowCycleItemViewsForTesting()
const {
return cycle_view_->GetPreviewViewsForTesting();
}
} // namespace ash
......@@ -15,6 +15,7 @@
#include "ui/aura/window_observer.h"
#include "ui/display/display_observer.h"
#include "ui/display/screen.h"
#include "ui/views/view.h"
namespace aura {
class ScopedWindowTargeter;
......@@ -44,6 +45,12 @@ class ASH_EXPORT WindowCycleList : public aura::WindowObserver,
// Cycles to the next or previous window based on |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) {
user_did_accept_ = user_did_accept;
}
......@@ -76,6 +83,12 @@ class ASH_EXPORT WindowCycleList : public aura::WindowObserver,
// PIP.
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 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
......
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