1 // Copyright 2019 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 
5 #include "ash/wm/overview/overview_highlight_controller.h"
6 
7 #include "ash/magnifier/docked_magnifier_controller_impl.h"
8 #include "ash/magnifier/magnification_controller.h"
9 #include "ash/shell.h"
10 #include "ash/wm/desks/desk_mini_view.h"
11 #include "ash/wm/desks/desk_name_view.h"
12 #include "ash/wm/desks/desks_bar_view.h"
13 #include "ash/wm/desks/desks_util.h"
14 #include "ash/wm/desks/new_desk_button.h"
15 #include "ash/wm/overview/overview_grid.h"
16 #include "ash/wm/overview/overview_item.h"
17 #include "ash/wm/overview/overview_item_view.h"
18 #include "ash/wm/overview/overview_session.h"
19 #include "ash/wm/overview/overview_utils.h"
20 #include "ui/accessibility/ax_enums.mojom.h"
21 #include "ui/views/view.h"
22 
23 namespace ash {
24 
25 // -----------------------------------------------------------------------------
26 // OverviewHighlightController::OverviewHighlightableView
27 
28 void OverviewHighlightController::OverviewHighlightableView::
SetHighlightVisibility(bool visible)29     SetHighlightVisibility(bool visible) {
30   if (visible == is_highlighted_)
31     return;
32 
33   is_highlighted_ = visible;
34   if (is_highlighted_)
35     OnViewHighlighted();
36   else
37     OnViewUnhighlighted();
38 }
39 
40 gfx::Point OverviewHighlightController::OverviewHighlightableView::
GetMagnifierFocusPointInScreen()41     GetMagnifierFocusPointInScreen() {
42   return GetView()->GetBoundsInScreen().CenterPoint();
43 }
44 
45 // -----------------------------------------------------------------------------
46 // OverviewHighlightController::TestApi
47 
TestApi(OverviewHighlightController * highlight_controller)48 OverviewHighlightController::TestApi::TestApi(
49     OverviewHighlightController* highlight_controller)
50     : highlight_controller_(highlight_controller) {}
51 
52 OverviewHighlightController::TestApi::~TestApi() = default;
53 
54 OverviewHighlightController::OverviewHighlightableView*
GetHighlightView() const55 OverviewHighlightController::TestApi::GetHighlightView() const {
56   return highlight_controller_->highlighted_view_;
57 }
58 
59 // -----------------------------------------------------------------------------
60 // OverviewHighlightController
61 
OverviewHighlightController(OverviewSession * overview_session)62 OverviewHighlightController::OverviewHighlightController(
63     OverviewSession* overview_session)
64     : overview_session_(overview_session) {}
65 
66 OverviewHighlightController::~OverviewHighlightController() = default;
67 
MoveHighlight(bool reverse)68 void OverviewHighlightController::MoveHighlight(bool reverse) {
69   const std::vector<OverviewHighlightableView*> traversable_views =
70       GetTraversableViews();
71   const int count = int{traversable_views.size()};
72 
73   // |count| can be zero when there are no overview items and no desk views (eg.
74   // "No recent items" or PIP windows are shown but they aren't traversable).
75   if (count == 0)
76     return;
77 
78   int index = 0;
79   if (!highlighted_view_) {
80     // Pick up where we left off if |deleted_index_| has a value.
81     if (deleted_index_) {
82       index = *deleted_index_ >= count ? count - 1 : *deleted_index_;
83       deleted_index_.reset();
84     } else if (reverse) {
85       index = count - 1;
86     }
87   } else {
88     auto it = std::find(traversable_views.begin(), traversable_views.end(),
89                         highlighted_view_);
90     DCHECK(it != traversable_views.end());
91     const int current_index = std::distance(traversable_views.begin(), it);
92     DCHECK_GE(current_index, 0);
93     index = (((reverse ? -1 : 1) + current_index) + count) % count;
94   }
95 
96   UpdateHighlight(traversable_views[index], reverse);
97 }
98 
OnViewDestroyingOrDisabling(OverviewHighlightableView * view)99 void OverviewHighlightController::OnViewDestroyingOrDisabling(
100     OverviewHighlightableView* view) {
101   DCHECK(view);
102 
103   // TODO(afakhry): Refactor this code.
104   const std::vector<OverviewHighlightableView*> traversable_views =
105       GetTraversableViews();
106   const auto it =
107       std::find(traversable_views.begin(), traversable_views.end(), view);
108   if (it == traversable_views.end())
109     return;
110 
111   const int view_index = std::distance(traversable_views.begin(), it);
112   DCHECK_GE(view_index, 0);
113 
114   if (view != highlighted_view_) {
115     if (!deleted_index_)
116       return;
117 
118     // We need to update the |deleted_index_| in case the destroying view
119     // resides before a previously removed highlighted view in the highlight
120     // order.
121     if (view_index < *deleted_index_)
122       deleted_index_ = std::max(0, --(*deleted_index_));
123     return;
124   }
125 
126   deleted_index_ = view_index;
127   highlighted_view_->SetHighlightVisibility(false);
128   highlighted_view_ = nullptr;
129 }
130 
SetFocusHighlightVisibility(bool visible)131 void OverviewHighlightController::SetFocusHighlightVisibility(bool visible) {
132   if (highlighted_view_)
133     highlighted_view_->SetHighlightVisibility(visible);
134 }
135 
IsFocusHighlightVisible() const136 bool OverviewHighlightController::IsFocusHighlightVisible() const {
137   return highlighted_view_ && highlighted_view_->IsViewHighlighted();
138 }
139 
MaybeActivateHighlightedView()140 bool OverviewHighlightController::MaybeActivateHighlightedView() {
141   if (!highlighted_view_)
142     return false;
143 
144   highlighted_view_->MaybeActivateHighlightedView();
145   return true;
146 }
147 
MaybeCloseHighlightedView()148 bool OverviewHighlightController::MaybeCloseHighlightedView() {
149   if (!highlighted_view_)
150     return false;
151 
152   highlighted_view_->MaybeCloseHighlightedView();
153   return true;
154 }
155 
GetHighlightedItem() const156 OverviewItem* OverviewHighlightController::GetHighlightedItem() const {
157   if (!highlighted_view_)
158     return nullptr;
159 
160   for (auto& grid : overview_session_->grid_list()) {
161     for (auto& item : grid->window_list()) {
162       if (highlighted_view_->GetView() == item->overview_item_view())
163         return item.get();
164     }
165   }
166 
167   return nullptr;
168 }
169 
HideTabDragHighlight()170 void OverviewHighlightController::HideTabDragHighlight() {
171   if (tab_dragged_view_)
172     tab_dragged_view_->SetHighlightVisibility(false);
173   tab_dragged_view_ = nullptr;
174 }
175 
ShowTabDragHighlight(OverviewHighlightableView * view)176 void OverviewHighlightController::ShowTabDragHighlight(
177     OverviewHighlightableView* view) {
178   if (tab_dragged_view_)
179     tab_dragged_view_->SetHighlightVisibility(false);
180   tab_dragged_view_ = view;
181   tab_dragged_view_->SetHighlightVisibility(true);
182 }
183 
IsTabDragHighlightVisible() const184 bool OverviewHighlightController::IsTabDragHighlightVisible() const {
185   return !!tab_dragged_view_;
186 }
187 
188 std::vector<OverviewHighlightController::OverviewHighlightableView*>
GetTraversableViews() const189 OverviewHighlightController::GetTraversableViews() const {
190   std::vector<OverviewHighlightableView*> traversable_views;
191   traversable_views.reserve(overview_session_->num_items() +
192                             (desks_util::kMaxNumberOfDesks + 1) *
193                                 Shell::Get()->GetAllRootWindows().size());
194   for (auto& grid : overview_session_->grid_list()) {
195     auto* bar_view = grid->desks_bar_view();
196     if (bar_view) {
197       // The desk items are always traversable from left to right, even in RTL
198       // languages.
199       for (auto* mini_view : bar_view->mini_views()) {
200         traversable_views.push_back(mini_view);
201         traversable_views.push_back(mini_view->desk_name_view());
202       }
203 
204       if (bar_view->new_desk_button()->GetEnabled())
205         traversable_views.push_back(bar_view->new_desk_button());
206     }
207 
208     for (auto& item : grid->window_list())
209       traversable_views.push_back(item->overview_item_view());
210   }
211   return traversable_views;
212 }
213 
UpdateHighlight(OverviewHighlightableView * view_to_be_highlighted,bool reverse)214 void OverviewHighlightController::UpdateHighlight(
215     OverviewHighlightableView* view_to_be_highlighted,
216     bool reverse) {
217   if (highlighted_view_ == view_to_be_highlighted)
218     return;
219 
220   OverviewHighlightableView* previous_view = highlighted_view_;
221   highlighted_view_ = view_to_be_highlighted;
222 
223   // Perform accessibility related tasks.
224   highlighted_view_->GetView()->NotifyAccessibilityEvent(
225       ax::mojom::Event::kSelection, true);
226   // Note that both magnifiers are mutually exclusive. The overview "focus"
227   // works differently from regular focusing so we need to update the magnifier
228   // manually here.
229   DockedMagnifierControllerImpl* docked_magnifier =
230       Shell::Get()->docked_magnifier_controller();
231   MagnificationController* fullscreen_magnifier =
232       Shell::Get()->magnification_controller();
233   const gfx::Point point_of_interest =
234       highlighted_view_->GetMagnifierFocusPointInScreen();
235   if (docked_magnifier->GetEnabled())
236     docked_magnifier->CenterOnPoint(point_of_interest);
237   else if (fullscreen_magnifier->IsEnabled())
238     fullscreen_magnifier->CenterOnPoint(point_of_interest);
239 
240   if (previous_view)
241     previous_view->SetHighlightVisibility(false);
242   highlighted_view_->SetHighlightVisibility(true);
243 }
244 
245 }  // namespace ash
246