123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- // 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/bind.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::Seconds(1);
- // How long the indicators are visible for.
- constexpr base::TimeDelta kIndicatorVisibilityDuration = base::Seconds(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::DisplayDragged(int64_t display_id,
- int32_t delta_x,
- int32_t delta_y) {
- if (current_state_ != DisplayAlignmentState::kLayoutPreview) {
- // Clear existing indicators. They are all regenerated via
- // OnDisplayConfigurationChanged() after dragging ends.
- ResetState();
- dragged_display_id_ = display_id;
- current_state_ = DisplayAlignmentState::kLayoutPreview;
- }
- // It is not possible to change display being dragged without dropping it
- // first (and causing update on display configuration).
- DCHECK_EQ(dragged_display_id_, display_id);
- DCHECK_NE(dragged_display_id_, display::kInvalidDisplayId);
- dragged_offset_ += gfx::Vector2d(delta_x, delta_y);
- ComputePreviewIndicators();
- }
- 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_;
- }
- int64_t DisplayAlignmentController::GetDraggedDisplayIdForTesting() const {
- return dragged_display_id_;
- }
- 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(DisplayAlignmentIndicator::CreateWithPill(
- src_display, source_edge, dst_name));
- active_indicators_.push_back(
- DisplayAlignmentIndicator::Create(peer, peer_edge));
- }
- }
- 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;
- dragged_display_id_ = display::kInvalidDisplayId;
- dragged_offset_ = gfx::Vector2d(0, 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;
- }
- void DisplayAlignmentController::ComputePreviewIndicators() {
- DCHECK_EQ(current_state_, DisplayAlignmentState::kLayoutPreview);
- DCHECK_NE(dragged_display_id_, display::kInvalidDisplayId);
- const display::Display& dragged_display =
- Shell::Get()->display_manager()->GetDisplayForId(dragged_display_id_);
- DCHECK(dragged_display.is_valid());
- gfx::Rect bounds = dragged_display.bounds();
- bounds += dragged_offset_;
- const display::Displays& display_list =
- Shell::Get()->display_manager()->active_display_list();
- // Iterate through all the active displays and see if they are neighbors to
- // |dragged_display|.
- for (const display::Display& peer : display_list) {
- // Skip currently dragged display or it might be detected as a neighbor
- if (peer.id() == dragged_display_id_)
- continue;
- // True if |source| and |peer| are neighbors. Returns |source_edge| and
- // |peer_edge| that denotes shared edges between |source| and |peer|
- // displays.
- gfx::Rect source_edge;
- gfx::Rect peer_edge;
- const bool are_neighbors = display::ComputeBoundary(
- bounds, peer.bounds(), &source_edge, &peer_edge);
- const auto& existing_indicator_it =
- std::find_if(active_indicators_.begin(), active_indicators_.end(),
- [id = peer.id()](const auto& indicator) {
- return id == indicator->display_id();
- });
- const bool indicator_exists =
- existing_indicator_it != active_indicators_.end();
- if (indicator_exists) {
- if (are_neighbors) {
- // Displays are already neighbors.
- (*existing_indicator_it)->Update(peer, peer_edge);
- (*existing_indicator_it)->Show();
- } else {
- // Displays are no longer neighbors but previously were neighbors.
- (*existing_indicator_it)->Hide();
- }
- } else if (are_neighbors) {
- // Displays are newly-neighbored.
- active_indicators_.push_back(
- DisplayAlignmentIndicator::Create(peer, peer_edge));
- }
- }
- }
- } // namespace ash
|