Commit feab4f74 authored by chinsenj's avatar chinsenj Committed by Commit Bot

cros: Add alternate keys for interacting with window cycler (alt+tab).

Currently a user can only cycle the window cycle list using alt+tab or
alt+shift+tab. Also, the user can only confirm their selection by
releasing the alt key.

This CL improves the interactivity of window cycler by introducing the
following changes. Pressing the left and right arrows now move the
selection while the window cycler is open. Pressing enter or space
now confirms the selection.

      WindowCycleControllerTest.KeysConfirmSelection

Test: WindowCycleControllerTest.LeftRightCycle,
Bug: 1067327
Change-Id: I03682c5545bf82415b9e71a4814bf5c1b0c196bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2358586
Commit-Queue: Jeremy Chinsen <chinsenj@chromium.org>
Reviewed-by: default avatarSammie Quon <sammiequon@chromium.org>
Reviewed-by: default avatarXiaoqian Dai <xdai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800423}
parent adec36d5
...@@ -901,6 +901,78 @@ TEST_F(InteractiveWindowCycleControllerTest, MouseHoverAndSelect) { ...@@ -901,6 +901,78 @@ TEST_F(InteractiveWindowCycleControllerTest, MouseHoverAndSelect) {
EXPECT_TRUE(wm::IsActiveWindow(w1.get())); EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
} }
// Tests that the left and right keys cycle after the cycle list has been
// initialized.
TEST_F(InteractiveWindowCycleControllerTest, LeftRightCycle) {
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();
// Start cycle, simulating alt button being held down. Cycle right to the
// third item.
// Starting order of windows in cycle list is [2,1,0].
controller->StartCycling();
generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
controller->CompleteCycling();
EXPECT_TRUE(wm::IsActiveWindow(w0.get()));
// Start cycle. Cycle right once, then left two times.
// Starting order of windows in cycle list is [0,2,1].
controller->StartCycling();
generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
controller->CompleteCycling();
EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
// Start cycle. Cycle right once, then left once, then right once.
// Starting order of windows in cycle list is [0,2,1].
controller->StartCycling();
generator->PressKey(ui::VKEY_LEFT, ui::EF_ALT_DOWN);
generator->PressKey(ui::VKEY_RIGHT, ui::EF_ALT_DOWN);
generator->PressKey(ui::VKEY_LEFT, ui::EF_ALT_DOWN);
controller->CompleteCycling();
EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
}
// Tests that pressing the space key, pressing the enter key, or releasing the
// alt key during window cycle confirms a selection.
TEST_F(InteractiveWindowCycleControllerTest, KeysConfirmSelection) {
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();
// Start cycle, simulating alt button being held down. Cycle right once and
// complete cycle using space.
// Starting order of windows in cycle list is [2,1,0].
controller->StartCycling();
controller->HandleCycleWindow(WindowCycleController::FORWARD);
generator->PressKey(ui::VKEY_SPACE, ui::EF_NONE);
EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
// Start cycle, simulating alt button being held down. Cycle right once and
// complete cycle using enter.
// Starting order of windows in cycle list is [1,2,0].
controller->StartCycling();
controller->HandleCycleWindow(WindowCycleController::FORWARD);
generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
// Start cycle, simulating alt button being held down. Cycle right once and
// complete cycle by releasing alt key (Views uses VKEY_MENU for both left and
// right alt keys).
// Starting order of windows in cycle list is [2,1,0].
controller->StartCycling();
controller->HandleCycleWindow(WindowCycleController::FORWARD);
generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
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) {
......
...@@ -32,30 +32,76 @@ WindowCycleEventFilter::~WindowCycleEventFilter() { ...@@ -32,30 +32,76 @@ WindowCycleEventFilter::~WindowCycleEventFilter() {
void WindowCycleEventFilter::OnKeyEvent(ui::KeyEvent* event) { void WindowCycleEventFilter::OnKeyEvent(ui::KeyEvent* event) {
// Until the alt key is released, all key events except the trigger key press // Until the alt key is released, all key events except the trigger key press
// (which is handled by the accelerator controller to call Step) are handled // (which is handled by the accelerator controller to call Step) are handled
// by this window cycle controller: https://crbug.com/340339. // by this window cycle controller: https://crbug.com/340339. When the window
bool is_trigger_key = event->key_code() == ui::VKEY_TAB || // cycle list exists, right + left arrow keys are considered trigger keys and
(debug::DeveloperAcceleratorsEnabled() && // those two are handled by this.
event->key_code() == ui::VKEY_W); const bool is_trigger_key = IsTriggerKey(event);
const bool is_exit_key = IsExitKey(event);
if (!is_trigger_key || event->type() != ui::ET_KEY_PRESSED) if (!is_trigger_key || event->type() != ui::ET_KEY_PRESSED)
event->StopPropagation(); event->StopPropagation();
if (is_trigger_key) {
if (event->type() == ui::ET_KEY_RELEASED) { if (is_trigger_key)
repeat_timer_.Stop(); HandleTriggerKey(event);
} else if (event->type() == ui::ET_KEY_PRESSED && event->is_repeat() && else if (is_exit_key)
!repeat_timer_.IsRunning()) { Shell::Get()->window_cycle_controller()->CompleteCycling();
repeat_timer_.Start( else if (event->key_code() == ui::VKEY_ESCAPE)
FROM_HERE, base::TimeDelta::FromMilliseconds(180),
base::BindRepeating(
&WindowCycleController::HandleCycleWindow,
base::Unretained(Shell::Get()->window_cycle_controller()),
event->IsShiftDown() ? WindowCycleController::BACKWARD
: WindowCycleController::FORWARD));
}
} else if (event->key_code() == ui::VKEY_ESCAPE) {
Shell::Get()->window_cycle_controller()->CancelCycling(); Shell::Get()->window_cycle_controller()->CancelCycling();
}
void WindowCycleEventFilter::HandleTriggerKey(ui::KeyEvent* event) {
if (event->type() == ui::ET_KEY_RELEASED) {
repeat_timer_.Stop();
} else if (ShouldRepeatKey(event)) {
repeat_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(180),
base::BindRepeating(
&WindowCycleController::HandleCycleWindow,
base::Unretained(Shell::Get()->window_cycle_controller()),
GetDirection(event)));
} else if (event->key_code() == ui::VKEY_LEFT ||
event->key_code() == ui::VKEY_RIGHT) {
Shell::Get()->window_cycle_controller()->HandleCycleWindow(
GetDirection(event));
} }
} }
bool WindowCycleEventFilter::IsTriggerKey(ui::KeyEvent* event) const {
const bool interactive_trigger_key =
features::IsInteractiveWindowCycleListEnabled() &&
(event->key_code() == ui::VKEY_LEFT ||
event->key_code() == ui::VKEY_RIGHT);
return event->key_code() == ui::VKEY_TAB ||
(debug::DeveloperAcceleratorsEnabled() &&
event->key_code() == ui::VKEY_W) ||
interactive_trigger_key;
}
bool WindowCycleEventFilter::IsExitKey(ui::KeyEvent* event) const {
return features::IsInteractiveWindowCycleListEnabled() &&
(event->key_code() == ui::VKEY_RETURN ||
event->key_code() == ui::VKEY_SPACE);
}
bool WindowCycleEventFilter::ShouldRepeatKey(ui::KeyEvent* event) const {
return event->type() == ui::ET_KEY_PRESSED && event->is_repeat() &&
!repeat_timer_.IsRunning();
}
WindowCycleController::Direction WindowCycleEventFilter::GetDirection(
ui::KeyEvent* event) const {
DCHECK(IsTriggerKey(event));
// Move backward if left arrow, forward if right arrow, tab, or W. Shift flips
// the direction.
const bool left = event->key_code() == ui::VKEY_LEFT;
const bool shift = event->IsShiftDown();
return (left ^ shift) ? WindowCycleController::BACKWARD
: WindowCycleController::FORWARD;
}
void WindowCycleEventFilter::OnMouseEvent(ui::MouseEvent* event) { void WindowCycleEventFilter::OnMouseEvent(ui::MouseEvent* event) {
if (features::IsInteractiveWindowCycleListEnabled() && if (features::IsInteractiveWindowCycleListEnabled() &&
Shell::Get()->window_cycle_controller()->IsEventInCycleView(event)) { Shell::Get()->window_cycle_controller()->IsEventInCycleView(event)) {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define ASH_WM_WINDOW_CYCLE_EVENT_FILTER_H_ #define ASH_WM_WINDOW_CYCLE_EVENT_FILTER_H_
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "ash/wm/window_cycle_controller.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "ui/events/event_handler.h" #include "ui/events/event_handler.h"
...@@ -14,6 +15,7 @@ namespace ash { ...@@ -14,6 +15,7 @@ namespace ash {
// Created by WindowCycleController when cycling through windows. Eats all key // Created by WindowCycleController when cycling through windows. Eats all key
// events and stops cycling when the necessary key sequence is encountered. // events and stops cycling when the necessary key sequence is encountered.
// Also allows users to cycle using right/left keys.
class ASH_EXPORT WindowCycleEventFilter : public ui::EventHandler { class ASH_EXPORT WindowCycleEventFilter : public ui::EventHandler {
public: public:
WindowCycleEventFilter(); WindowCycleEventFilter();
...@@ -29,12 +31,32 @@ class ASH_EXPORT WindowCycleEventFilter : public ui::EventHandler { ...@@ -29,12 +31,32 @@ class ASH_EXPORT WindowCycleEventFilter : public ui::EventHandler {
AltReleaseHandler(); AltReleaseHandler();
~AltReleaseHandler() override; ~AltReleaseHandler() override;
// ui::EventHandler:
void OnKeyEvent(ui::KeyEvent* event) override; void OnKeyEvent(ui::KeyEvent* event) override;
private: private:
DISALLOW_COPY_AND_ASSIGN(AltReleaseHandler); DISALLOW_COPY_AND_ASSIGN(AltReleaseHandler);
}; };
// Depending on the values of |event| either repeatedly cycle through windows,
// stop repeatedly cycling through windows, or cycle once.
void HandleTriggerKey(ui::KeyEvent* event);
// Returns whether |event| is a trigger key (tab, left, right, w (when
// debugging)).
bool IsTriggerKey(ui::KeyEvent* event) const;
// Returns whether |event| is an exit key (return, space).
bool IsExitKey(ui::KeyEvent* event) const;
// Returns whether the window cycle should repeatedly cycle in the
// direction given by |event|.
bool ShouldRepeatKey(ui::KeyEvent* event) const;
// Returns the direction the window cycle should cycle depending on the
// combination of keys being pressed.
WindowCycleController::Direction GetDirection(ui::KeyEvent* event) const;
// When the user holds Alt+Tab, this timer is used to send repeated // When the user holds Alt+Tab, this timer is used to send repeated
// cycle commands to WindowCycleController. Note this is not accomplished // cycle commands to WindowCycleController. Note this is not accomplished
// by marking the Alt+Tab accelerator as "repeatable" in the accelerator // by marking the Alt+Tab accelerator as "repeatable" in the accelerator
......
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