Commit 01bc58c8 authored by Bailey Berro's avatar Bailey Berro Committed by Commit Bot

Add DisplayAlignmentController

This change adds DisplayAlignmentController, a class that manages a set
of DisplayAlignmentIndicators to be shown when the mouse repeatedly hits the
edges of a display within a short period.

Note: This is a continuation of crrev.com/c/2146794

Bug: 1070122
Change-Id: I8ab7a84754c750f41824b535b2a30105e409f47b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2159620
Commit-Queue: Bailey Berro <baileyberro@chromium.org>
Reviewed-by: default avatarAhmed Fakhry <afakhry@chromium.org>
Cr-Commit-Position: refs/heads/master@{#780864}
parent a5cce3fa
......@@ -266,6 +266,8 @@ component("ash") {
"display/cros_display_config.h",
"display/cursor_window_controller.cc",
"display/cursor_window_controller.h",
"display/display_alignment_controller.cc",
"display/display_alignment_controller.h",
"display/display_alignment_indicator.cc",
"display/display_alignment_indicator.h",
"display/display_animator.cc",
......@@ -1855,6 +1857,7 @@ test("ash_unittests") {
"dip_unittest.cc",
"display/cros_display_config_unittest.cc",
"display/cursor_window_controller_unittest.cc",
"display/display_alignment_controller_unittest.cc",
"display/display_alignment_indicator_unittest.cc",
"display/display_color_manager_unittest.cc",
"display/display_configuration_controller_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/display/display_alignment_controller.h"
#include "ash/display/display_alignment_indicator.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/callback.h"
#include "base/timer/timer.h"
#include "ui/events/event.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/rect.h"
namespace ash {
namespace {
// Number of times the mouse has to hit the edge to show the indicators.
constexpr int kTriggerThresholdCount = 2;
// Time between last time the mouse leaves a screen edge and the counter
// resetting.
constexpr base::TimeDelta kCounterResetTime = base::TimeDelta::FromSeconds(1);
// How long the indicators are visible for.
constexpr base::TimeDelta kIndicatorVisibilityDuration =
base::TimeDelta::FromSeconds(2);
// Returns true if |screen_location| is on the edge of |display|. |display| must
// be valid.
bool IsOnBoundary(const gfx::Point& screen_location,
const display::Display& display) {
DCHECK(display.is_valid());
const gfx::Rect& bounds = display.bounds();
const int top = bounds.y();
const int bottom = bounds.bottom() - 1;
const int left = bounds.x();
const int right = bounds.right() - 1;
// See if current screen_location is within 1px of the display's
// borders. 1px leniency is necessary as some resolution/size factor
// combination results in the mouse not being able to reach the edges of the
// display by 1px.
if (std::abs(screen_location.x() - left) <= 1)
return true;
if (std::abs(screen_location.x() - right) <= 1)
return true;
if (std::abs(screen_location.y() - top) <= 1)
return true;
return std::abs(screen_location.y() - bottom) <= 1;
}
} // namespace
DisplayAlignmentController::DisplayAlignmentController()
: action_trigger_timer_(std::make_unique<base::OneShotTimer>()) {
Shell* shell = Shell::Get();
shell->AddPreTargetHandler(this);
shell->session_controller()->AddObserver(this);
shell->window_tree_host_manager()->AddObserver(this);
is_locked_ = shell->session_controller()->IsScreenLocked();
RefreshState();
}
DisplayAlignmentController::~DisplayAlignmentController() {
Shell* shell = Shell::Get();
shell->window_tree_host_manager()->RemoveObserver(this);
shell->session_controller()->RemoveObserver(this);
shell->RemovePreTargetHandler(this);
}
void DisplayAlignmentController::OnDisplayConfigurationChanged() {
RefreshState();
}
void DisplayAlignmentController::OnDisplaysInitialized() {
RefreshState();
}
void DisplayAlignmentController::OnMouseEvent(ui::MouseEvent* event) {
if (current_state_ == DisplayAlignmentState::kDisabled ||
event->type() != ui::ET_MOUSE_MOVED) {
return;
}
// If mouse enters the edge of the display.
const gfx::Point screen_location = event->target()->GetScreenLocation(*event);
const display::Display& src_display =
Shell::Get()->display_manager()->FindDisplayContainingPoint(
screen_location);
if (!src_display.is_valid())
return;
const bool is_on_edge = IsOnBoundary(screen_location, src_display);
// Restart the reset timer when the mouse moves off an edge.
if (!is_on_edge) {
if (current_state_ == DisplayAlignmentState::kOnEdge) {
current_state_ = DisplayAlignmentState::kIdle;
// The cursor was moved off the edge. Start the reset timer. If the cursor
// does not hit an edge on the same display within |kCounterResetTime|,
// state is reset by ResetState() and indicators will not be shown.
action_trigger_timer_->Start(
FROM_HERE, kCounterResetTime,
base::BindOnce(&DisplayAlignmentController::ResetState,
base::Unretained(this)));
}
return;
}
if (current_state_ != DisplayAlignmentState::kIdle)
return;
// |trigger_count_| should only increment when the mouse hits the edges of
// the same display.
if (triggered_display_id_ == src_display.id()) {
trigger_count_++;
} else {
triggered_display_id_ = src_display.id();
trigger_count_ = 1;
}
action_trigger_timer_->Stop();
current_state_ = DisplayAlignmentState::kOnEdge;
if (trigger_count_ == kTriggerThresholdCount)
ShowIndicators(src_display);
}
void DisplayAlignmentController::OnLockStateChanged(const bool locked) {
is_locked_ = locked;
RefreshState();
}
void DisplayAlignmentController::SetTimerForTesting(
std::unique_ptr<base::OneShotTimer> timer) {
action_trigger_timer_ = std::move(timer);
}
const std::vector<std::unique_ptr<DisplayAlignmentIndicator>>&
DisplayAlignmentController::GetActiveIndicatorsForTesting() {
return active_indicators_;
}
void DisplayAlignmentController::ShowIndicators(
const display::Display& src_display) {
DCHECK_EQ(src_display.id(), triggered_display_id_);
current_state_ = DisplayAlignmentState::kIndicatorsVisible;
// Iterate through all the active displays and see if they are neighbors to
// |src_display|.
display::DisplayManager* display_manager = Shell::Get()->display_manager();
const display::Displays& display_list =
display_manager->active_display_list();
for (const display::Display& peer : display_list) {
// Skip currently triggered display or it might be detected as a neighbor
if (peer.id() == triggered_display_id_)
continue;
// Check whether |src_display| and |peer| are neighbors.
gfx::Rect source_edge;
gfx::Rect peer_edge;
if (display::ComputeBoundary(src_display, peer, &source_edge, &peer_edge)) {
// TODO(1070697): Handle pills overlapping for certain display
// configuration.
// Pills are created for the indicators in the src display, but not in the
// peers.
const std::string& dst_name =
display_manager->GetDisplayInfo(peer.id()).name();
active_indicators_.push_back(std::make_unique<DisplayAlignmentIndicator>(
src_display, source_edge, dst_name));
active_indicators_.push_back(std::make_unique<DisplayAlignmentIndicator>(
peer, peer_edge, /*target_name=*/""));
}
}
action_trigger_timer_->Start(
FROM_HERE, kIndicatorVisibilityDuration,
base::BindOnce(&DisplayAlignmentController::ResetState,
base::Unretained(this)));
}
void DisplayAlignmentController::ResetState() {
action_trigger_timer_->Stop();
active_indicators_.clear();
trigger_count_ = 0;
// Do not re-enable if disabled.
if (current_state_ != DisplayAlignmentState::kDisabled)
current_state_ = DisplayAlignmentState::kIdle;
}
void DisplayAlignmentController::RefreshState() {
ResetState();
// This feature is only enabled when the screen is not locked and there is
// more than one display connected.
if (is_locked_) {
current_state_ = DisplayAlignmentState::kDisabled;
return;
}
const display::Displays& display_list =
Shell::Get()->display_manager()->active_display_list();
if (display_list.size() < 2) {
current_state_ = DisplayAlignmentState::kDisabled;
return;
}
if (current_state_ == DisplayAlignmentState::kDisabled)
current_state_ = DisplayAlignmentState::kIdle;
}
} // namespace ash
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_DISPLAY_DISPLAY_ALIGNMENT_CONTROLLER_H_
#define ASH_DISPLAY_DISPLAY_ALIGNMENT_CONTROLLER_H_
#include <memory>
#include "ash/ash_export.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/session/session_observer.h"
#include "base/containers/flat_map.h"
#include "ui/events/event_handler.h"
namespace base {
class OneShotTimer;
} // namespace base
namespace ash {
class DisplayAlignmentIndicator;
// DisplayAlignmentController is responsible for creating new
// DisplayAlignmentIndicators when the activation criteria is met.
// TODO(1091497): Consider combining DisplayHighlightController and
// DisplayAlignmentController.
class ASH_EXPORT DisplayAlignmentController
: public ui::EventHandler,
public WindowTreeHostManager::Observer,
public SessionObserver {
public:
enum class DisplayAlignmentState {
// No indicators shown and mouse is not on edge
kIdle,
// Mouse is currently on one of the edges.
kOnEdge,
// The indicators are visible.
kIndicatorsVisible,
// Screen is locked or there is only one display.
kDisabled,
};
DisplayAlignmentController();
DisplayAlignmentController(const DisplayAlignmentController&) = delete;
DisplayAlignmentController& operator=(const DisplayAlignmentController&) =
delete;
~DisplayAlignmentController() override;
// WindowTreeHostManager::Observer:
void OnDisplayConfigurationChanged() override;
void OnDisplaysInitialized() override;
// ui::EventHandler:
void OnMouseEvent(ui::MouseEvent* event) override;
// SessionObserver:
void OnLockStateChanged(bool locked) override;
// Overrides the default OneShotTimer for unit testing.
void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer);
const std::vector<std::unique_ptr<DisplayAlignmentIndicator>>&
GetActiveIndicatorsForTesting();
private:
// Show all indicators on |src_display| and other indicators that shares
// an edge with |src_display|. Indicators on other displays are shown without
// pills. All indicators are created in this method and stored in
// |active_indicators_| to be destroyed in ResetState().
void ShowIndicators(const display::Display& src_display);
// Clears all indicators, containers, timer, and resets the state back to
// kIdle.
void ResetState();
// Used to transition to kDisable if required. Called whenever display
// configuration or lock state updates.
void RefreshState();
// Stores all DisplayAlignmentIndicators currently being shown. All indicators
// should either belong to or be a shared edge of display with
// |triggered_display_id_|. Indicators are created upon activation in
// ShowIndicators() and cleared in ResetState().
std::vector<std::unique_ptr<DisplayAlignmentIndicator>> active_indicators_;
// Timer used for both edge trigger timeouts and hiding indicators.
std::unique_ptr<base::OneShotTimer> action_trigger_timer_;
// Tracks current state of the controller. Mostly used to determine if action
// is taken in OnMouseEvent();
DisplayAlignmentState current_state_ = DisplayAlignmentState::kIdle;
// Tracks if the screen is locked to disable highlights.
bool is_locked_ = false;
// Keeps track of the most recent display where the mouse hit the edge.
// Prevents activating indicators when user hits edges of different displays.
int64_t triggered_display_id_ = display::kInvalidDisplayId;
// Number of times the mouse was on an edge of some display specified by
// |triggered_display_id_| recently.
int trigger_count_ = 0;
};
} // namespace ash
#endif // ASH_DISPLAY_DISPLAY_ALIGNMENT_CONTROLLER_H_
This diff is collapsed.
......@@ -45,6 +45,7 @@ class ASH_EXPORT DisplayAlignmentIndicator {
private:
friend class DisplayAlignmentIndicatorTest;
friend class DisplayAlignmentControllerTest;
// View and Widget for showing the blue indicator highlights on the edge of
// the display.
......
......@@ -15,6 +15,8 @@ namespace ash {
// DisplayHighlightController manages which display should render display
// identification highlights. Highlights are translucent blue rectangles on the
// edges of a display.
// TODO(1091497): Consider combining DisplayHighlightController and
// DisplayAlignmentController.
class ASH_EXPORT DisplayHighlightController
: public WindowTreeHostManager::Observer,
public SessionObserver {
......
......@@ -28,6 +28,7 @@
#include "ash/detachable_base/detachable_base_notification_controller.h"
#include "ash/display/cros_display_config.h"
#include "ash/display/cursor_window_controller.h"
#include "ash/display/display_alignment_controller.h"
#include "ash/display/display_color_manager.h"
#include "ash/display/display_configuration_controller.h"
#include "ash/display/display_configuration_observer.h"
......@@ -598,6 +599,7 @@ Shell::~Shell() {
cros_display_config_.reset();
display_configuration_observer_.reset();
display_prefs_.reset();
display_alignment_controller_.reset();
// Remove the focus from any window. This will prevent overhead and side
// effects (e.g. crashes) from changing focus during shutdown.
......@@ -1185,11 +1187,18 @@ void Shell::Init(
std::make_unique<MediaNotificationControllerImpl>();
}
// TODO(1091497): Consider combining DisplayHighlightController and
// DisplayAlignmentController.
if (features::IsDisplayIdentificationEnabled()) {
display_highlight_controller_ =
std::make_unique<DisplayHighlightController>();
}
if (features::IsDisplayAlignmentAssistanceEnabled()) {
display_alignment_controller_ =
std::make_unique<DisplayAlignmentController>();
}
for (auto& observer : shell_observers_)
observer.OnShellInitialized();
......
......@@ -99,6 +99,7 @@ class CrosDisplayConfig;
class DesksController;
class DetachableBaseHandler;
class DetachableBaseNotificationController;
class DisplayAlignmentController;
class DisplayColorManager;
class DisplayConfigurationController;
class DisplayConfigurationObserver;
......@@ -342,6 +343,10 @@ class ASH_EXPORT Shell : public SessionObserver,
return display_configuration_controller_.get();
}
DisplayAlignmentController* display_alignment_controller() {
return display_alignment_controller_.get();
}
display::DisplayConfigurator* display_configurator();
DisplayColorManager* display_color_manager() {
......@@ -758,6 +763,7 @@ class ASH_EXPORT Shell : public SessionObserver,
std::unique_ptr<BluetoothPowerController> bluetooth_power_controller_;
std::unique_ptr<TrayBluetoothHelper> tray_bluetooth_helper_;
std::unique_ptr<KeyboardControllerImpl> keyboard_controller_;
std::unique_ptr<DisplayAlignmentController> display_alignment_controller_;
std::unique_ptr<DisplayColorManager> display_color_manager_;
std::unique_ptr<DisplayErrorObserver> display_error_observer_;
std::unique_ptr<ProjectingObserver> projecting_observer_;
......
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