display_alignment_controller.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/display/display_alignment_controller.h"
  5. #include "ash/display/display_alignment_indicator.h"
  6. #include "ash/session/session_controller_impl.h"
  7. #include "ash/shell.h"
  8. #include "base/bind.h"
  9. #include "base/callback.h"
  10. #include "base/timer/timer.h"
  11. #include "ui/events/event.h"
  12. #include "ui/gfx/color_palette.h"
  13. #include "ui/gfx/geometry/rect.h"
  14. namespace ash {
  15. namespace {
  16. // Number of times the mouse has to hit the edge to show the indicators.
  17. constexpr int kTriggerThresholdCount = 2;
  18. // Time between last time the mouse leaves a screen edge and the counter
  19. // resetting.
  20. constexpr base::TimeDelta kCounterResetTime = base::Seconds(1);
  21. // How long the indicators are visible for.
  22. constexpr base::TimeDelta kIndicatorVisibilityDuration = base::Seconds(2);
  23. // Returns true if |screen_location| is on the edge of |display|. |display| must
  24. // be valid.
  25. bool IsOnBoundary(const gfx::Point& screen_location,
  26. const display::Display& display) {
  27. DCHECK(display.is_valid());
  28. const gfx::Rect& bounds = display.bounds();
  29. const int top = bounds.y();
  30. const int bottom = bounds.bottom() - 1;
  31. const int left = bounds.x();
  32. const int right = bounds.right() - 1;
  33. // See if current screen_location is within 1px of the display's
  34. // borders. 1px leniency is necessary as some resolution/size factor
  35. // combination results in the mouse not being able to reach the edges of the
  36. // display by 1px.
  37. if (std::abs(screen_location.x() - left) <= 1)
  38. return true;
  39. if (std::abs(screen_location.x() - right) <= 1)
  40. return true;
  41. if (std::abs(screen_location.y() - top) <= 1)
  42. return true;
  43. return std::abs(screen_location.y() - bottom) <= 1;
  44. }
  45. } // namespace
  46. DisplayAlignmentController::DisplayAlignmentController()
  47. : action_trigger_timer_(std::make_unique<base::OneShotTimer>()) {
  48. Shell* shell = Shell::Get();
  49. shell->AddPreTargetHandler(this);
  50. shell->session_controller()->AddObserver(this);
  51. shell->window_tree_host_manager()->AddObserver(this);
  52. is_locked_ = shell->session_controller()->IsScreenLocked();
  53. RefreshState();
  54. }
  55. DisplayAlignmentController::~DisplayAlignmentController() {
  56. Shell* shell = Shell::Get();
  57. shell->window_tree_host_manager()->RemoveObserver(this);
  58. shell->session_controller()->RemoveObserver(this);
  59. shell->RemovePreTargetHandler(this);
  60. }
  61. void DisplayAlignmentController::OnDisplayConfigurationChanged() {
  62. RefreshState();
  63. }
  64. void DisplayAlignmentController::OnDisplaysInitialized() {
  65. RefreshState();
  66. }
  67. void DisplayAlignmentController::OnMouseEvent(ui::MouseEvent* event) {
  68. if (current_state_ == DisplayAlignmentState::kDisabled ||
  69. event->type() != ui::ET_MOUSE_MOVED) {
  70. return;
  71. }
  72. // If mouse enters the edge of the display.
  73. const gfx::Point screen_location = event->target()->GetScreenLocation(*event);
  74. const display::Display& src_display =
  75. Shell::Get()->display_manager()->FindDisplayContainingPoint(
  76. screen_location);
  77. if (!src_display.is_valid())
  78. return;
  79. const bool is_on_edge = IsOnBoundary(screen_location, src_display);
  80. // Restart the reset timer when the mouse moves off an edge.
  81. if (!is_on_edge) {
  82. if (current_state_ == DisplayAlignmentState::kOnEdge) {
  83. current_state_ = DisplayAlignmentState::kIdle;
  84. // The cursor was moved off the edge. Start the reset timer. If the cursor
  85. // does not hit an edge on the same display within |kCounterResetTime|,
  86. // state is reset by ResetState() and indicators will not be shown.
  87. action_trigger_timer_->Start(
  88. FROM_HERE, kCounterResetTime,
  89. base::BindOnce(&DisplayAlignmentController::ResetState,
  90. base::Unretained(this)));
  91. }
  92. return;
  93. }
  94. if (current_state_ != DisplayAlignmentState::kIdle)
  95. return;
  96. // |trigger_count_| should only increment when the mouse hits the edges of
  97. // the same display.
  98. if (triggered_display_id_ == src_display.id()) {
  99. trigger_count_++;
  100. } else {
  101. triggered_display_id_ = src_display.id();
  102. trigger_count_ = 1;
  103. }
  104. action_trigger_timer_->Stop();
  105. current_state_ = DisplayAlignmentState::kOnEdge;
  106. if (trigger_count_ == kTriggerThresholdCount)
  107. ShowIndicators(src_display);
  108. }
  109. void DisplayAlignmentController::OnLockStateChanged(const bool locked) {
  110. is_locked_ = locked;
  111. RefreshState();
  112. }
  113. void DisplayAlignmentController::DisplayDragged(int64_t display_id,
  114. int32_t delta_x,
  115. int32_t delta_y) {
  116. if (current_state_ != DisplayAlignmentState::kLayoutPreview) {
  117. // Clear existing indicators. They are all regenerated via
  118. // OnDisplayConfigurationChanged() after dragging ends.
  119. ResetState();
  120. dragged_display_id_ = display_id;
  121. current_state_ = DisplayAlignmentState::kLayoutPreview;
  122. }
  123. // It is not possible to change display being dragged without dropping it
  124. // first (and causing update on display configuration).
  125. DCHECK_EQ(dragged_display_id_, display_id);
  126. DCHECK_NE(dragged_display_id_, display::kInvalidDisplayId);
  127. dragged_offset_ += gfx::Vector2d(delta_x, delta_y);
  128. ComputePreviewIndicators();
  129. }
  130. void DisplayAlignmentController::SetTimerForTesting(
  131. std::unique_ptr<base::OneShotTimer> timer) {
  132. action_trigger_timer_ = std::move(timer);
  133. }
  134. const std::vector<std::unique_ptr<DisplayAlignmentIndicator>>&
  135. DisplayAlignmentController::GetActiveIndicatorsForTesting() {
  136. return active_indicators_;
  137. }
  138. int64_t DisplayAlignmentController::GetDraggedDisplayIdForTesting() const {
  139. return dragged_display_id_;
  140. }
  141. void DisplayAlignmentController::ShowIndicators(
  142. const display::Display& src_display) {
  143. DCHECK_EQ(src_display.id(), triggered_display_id_);
  144. current_state_ = DisplayAlignmentState::kIndicatorsVisible;
  145. // Iterate through all the active displays and see if they are neighbors to
  146. // |src_display|.
  147. display::DisplayManager* display_manager = Shell::Get()->display_manager();
  148. const display::Displays& display_list =
  149. display_manager->active_display_list();
  150. for (const display::Display& peer : display_list) {
  151. // Skip currently triggered display or it might be detected as a neighbor
  152. if (peer.id() == triggered_display_id_)
  153. continue;
  154. // Check whether |src_display| and |peer| are neighbors.
  155. gfx::Rect source_edge;
  156. gfx::Rect peer_edge;
  157. if (display::ComputeBoundary(src_display, peer, &source_edge, &peer_edge)) {
  158. // TODO(1070697): Handle pills overlapping for certain display
  159. // configuration.
  160. // Pills are created for the indicators in the src display, but not in the
  161. // peers.
  162. const std::string& dst_name =
  163. display_manager->GetDisplayInfo(peer.id()).name();
  164. active_indicators_.push_back(DisplayAlignmentIndicator::CreateWithPill(
  165. src_display, source_edge, dst_name));
  166. active_indicators_.push_back(
  167. DisplayAlignmentIndicator::Create(peer, peer_edge));
  168. }
  169. }
  170. action_trigger_timer_->Start(
  171. FROM_HERE, kIndicatorVisibilityDuration,
  172. base::BindOnce(&DisplayAlignmentController::ResetState,
  173. base::Unretained(this)));
  174. }
  175. void DisplayAlignmentController::ResetState() {
  176. action_trigger_timer_->Stop();
  177. active_indicators_.clear();
  178. trigger_count_ = 0;
  179. dragged_display_id_ = display::kInvalidDisplayId;
  180. dragged_offset_ = gfx::Vector2d(0, 0);
  181. // Do not re-enable if disabled.
  182. if (current_state_ != DisplayAlignmentState::kDisabled)
  183. current_state_ = DisplayAlignmentState::kIdle;
  184. }
  185. void DisplayAlignmentController::RefreshState() {
  186. ResetState();
  187. // This feature is only enabled when the screen is not locked and there is
  188. // more than one display connected.
  189. if (is_locked_) {
  190. current_state_ = DisplayAlignmentState::kDisabled;
  191. return;
  192. }
  193. const display::Displays& display_list =
  194. Shell::Get()->display_manager()->active_display_list();
  195. if (display_list.size() < 2) {
  196. current_state_ = DisplayAlignmentState::kDisabled;
  197. return;
  198. }
  199. if (current_state_ == DisplayAlignmentState::kDisabled)
  200. current_state_ = DisplayAlignmentState::kIdle;
  201. }
  202. void DisplayAlignmentController::ComputePreviewIndicators() {
  203. DCHECK_EQ(current_state_, DisplayAlignmentState::kLayoutPreview);
  204. DCHECK_NE(dragged_display_id_, display::kInvalidDisplayId);
  205. const display::Display& dragged_display =
  206. Shell::Get()->display_manager()->GetDisplayForId(dragged_display_id_);
  207. DCHECK(dragged_display.is_valid());
  208. gfx::Rect bounds = dragged_display.bounds();
  209. bounds += dragged_offset_;
  210. const display::Displays& display_list =
  211. Shell::Get()->display_manager()->active_display_list();
  212. // Iterate through all the active displays and see if they are neighbors to
  213. // |dragged_display|.
  214. for (const display::Display& peer : display_list) {
  215. // Skip currently dragged display or it might be detected as a neighbor
  216. if (peer.id() == dragged_display_id_)
  217. continue;
  218. // True if |source| and |peer| are neighbors. Returns |source_edge| and
  219. // |peer_edge| that denotes shared edges between |source| and |peer|
  220. // displays.
  221. gfx::Rect source_edge;
  222. gfx::Rect peer_edge;
  223. const bool are_neighbors = display::ComputeBoundary(
  224. bounds, peer.bounds(), &source_edge, &peer_edge);
  225. const auto& existing_indicator_it =
  226. std::find_if(active_indicators_.begin(), active_indicators_.end(),
  227. [id = peer.id()](const auto& indicator) {
  228. return id == indicator->display_id();
  229. });
  230. const bool indicator_exists =
  231. existing_indicator_it != active_indicators_.end();
  232. if (indicator_exists) {
  233. if (are_neighbors) {
  234. // Displays are already neighbors.
  235. (*existing_indicator_it)->Update(peer, peer_edge);
  236. (*existing_indicator_it)->Show();
  237. } else {
  238. // Displays are no longer neighbors but previously were neighbors.
  239. (*existing_indicator_it)->Hide();
  240. }
  241. } else if (are_neighbors) {
  242. // Displays are newly-neighbored.
  243. active_indicators_.push_back(
  244. DisplayAlignmentIndicator::Create(peer, peer_edge));
  245. }
  246. }
  247. }
  248. } // namespace ash