Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(48)

Side by Side Diff: ash/wm/overview/window_grid.cc

Issue 251103005: Added arrow key navigation to Overview Mode (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Replaced unittest by a better one, fixed lints, minor tweaks. Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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/window_grid.h"
6
7 #include "ash/screen_util.h"
8 #include "ash/shell.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/overview/scoped_transform_overview_window.h"
11 #include "ash/wm/overview/window_selector.h"
12 #include "ash/wm/overview/window_selector_item.h"
13 #include "ash/wm/overview/window_selector_panels.h"
14 #include "ash/wm/overview/window_selector_window.h"
15 #include "ash/wm/window_state.h"
16 #include "base/memory/scoped_vector.h"
17 #include "third_party/skia/include/core/SkColor.h"
18 #include "ui/aura/window.h"
19 #include "ui/compositor/layer_animation_observer.h"
20 #include "ui/compositor/scoped_layer_animation_settings.h"
21 #include "ui/gfx/vector2d.h"
22 #include "ui/views/background.h"
23 #include "ui/views/view.h"
24 #include "ui/views/widget/widget.h"
25 #include "ui/wm/core/window_animations.h"
26
27 namespace ash {
28 namespace {
29
30 // An observer which holds onto the passed widget until the animation is
31 // complete.
32 class CleanupWidgetAfterAnimationObserver
33 : public ui::ImplicitAnimationObserver {
34 public:
35 explicit CleanupWidgetAfterAnimationObserver(
36 scoped_ptr<views::Widget> widget);
37 virtual ~CleanupWidgetAfterAnimationObserver();
38
39 // ui::ImplicitAnimationObserver:
40 virtual void OnImplicitAnimationsCompleted() OVERRIDE;
41
42 private:
43 scoped_ptr<views::Widget> widget_;
44
45 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
46 };
47
48 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
49 scoped_ptr<views::Widget> widget)
50 : widget_(widget.Pass()) {
51 }
52
53 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
54 }
55
56 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
57 delete this;
58 }
59
60 // A comparator for locating a given target window.
61 struct WindowSelectorItemComparator
62 : public std::unary_function<WindowSelectorItem*, bool> {
63 explicit WindowSelectorItemComparator(const aura::Window* target_window)
64 : target(target_window) {
65 }
66
67 bool operator()(WindowSelectorItem* window) const {
68 return window->HasSelectableWindow(target);
69 }
70
71 const aura::Window* target;
72 };
73
74 // A comparator for locating a WindowSelectorItem given a targeted window.
75 struct WindowSelectorItemTargetComparator
76 : public std::unary_function<WindowSelectorItem*, bool> {
77 explicit WindowSelectorItemTargetComparator(const aura::Window* target_window)
78 : target(target_window) {
79 }
80
81 bool operator()(WindowSelectorItem* window) const {
82 return window->Contains(target);
83 }
84
85 const aura::Window* target;
86 };
87
88 // Conceptually the window overview is a table or grid of cells having this
89 // fixed aspect ratio. The number of columns is determined by maximizing the
90 // area of them based on the number of window_list.
91 const float kCardAspectRatio = 4.0f / 3.0f;
92
93 // The minimum number of cards along the major axis (i.e. horizontally on a
94 // landscape orientation).
95 const int kMinCardsMajor = 3;
96
97 const int kOverviewSelectorTransitionMilliseconds = 100;
98
99 // The color and opacity of the overview selector.
100 const SkColor kWindowOverviewSelectionColor = SK_ColorBLACK;
101 const unsigned char kWindowOverviewSelectorOpacity = 128;
102
103 // Returns the vector for the fade in animation.
104 gfx::Vector2d GetSlideVectorForFadeIn(WindowSelector::Direction direction,
105 const gfx::Rect& bounds) {
106 gfx::Vector2d vector;
107 switch (direction) {
108 case WindowSelector::DOWN:
109 vector.set_y(bounds.width());
110 break;
111 case WindowSelector::RIGHT:
112 vector.set_x(bounds.height());
113 break;
114 case WindowSelector::UP:
115 vector.set_y(-bounds.width());
116 break;
117 case WindowSelector::LEFT:
118 vector.set_x(-bounds.height());
119 break;
120 }
121 return vector;
122 }
123
124 } // namespace
125
126 WindowGrid::WindowGrid(aura::Window* root_window,
127 const std::vector<aura::Window*>& windows,
128 WindowSelector* window_selector)
129 : root_window_(root_window),
130 window_selector_(window_selector) {
131 WindowSelectorPanels* panels_item = NULL;
132 for (aura::Window::Windows::const_iterator iter = windows.begin();
133 iter != windows.end(); ++iter) {
134 if ((*iter)->GetRootWindow() != root_window)
135 continue;
136 (*iter)->AddObserver(this);
137 observed_windows_.insert(*iter);
138 WindowSelectorItem* item = NULL;
139
140 if ((*iter)->type() == ui::wm::WINDOW_TYPE_PANEL &&
141 wm::GetWindowState(*iter)->panel_attached()) {
142 // Attached panel windows are grouped into a single overview item per
143 // grid.
144 if (!panels_item) {
145 panels_item = new WindowSelectorPanels();
146 window_list_.push_back(panels_item);
147 }
148 panels_item->AddWindow(*iter);
149 item = panels_item;
150 } else {
151 item = new WindowSelectorWindow(*iter);
152 item->PrepareForOverview();
153 window_list_.push_back(item);
154 }
155 }
156 if (window_list_.empty())
157 return;
158
159 if (panels_item)
160 panels_item->PrepareForOverview();
161
162 PositionWindows(true);
163 }
164
165 WindowGrid::~WindowGrid() {
166 for (std::set<aura::Window*>::iterator iter = observed_windows_.begin();
167 iter != observed_windows_.end(); iter++) {
168 (*iter)->RemoveObserver(this);
169 }
170 }
171
172 void WindowGrid::PositionWindows(bool animate) {
173 CHECK(!window_list_.empty());
174
175 gfx::Size window_size;
176 gfx::Rect total_bounds = ScreenUtil::ConvertRectToScreen(
177 root_window_,
178 ScreenUtil::GetDisplayWorkAreaBoundsInParent(
179 Shell::GetContainer(root_window_, kShellWindowId_DefaultContainer)));
180
181 // Find the minimum number of window_list per row that will fit all of the
182 // window_list on screen.
183 num_columns_ = std::max(
184 total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
185 static_cast<int>(ceil(sqrt(total_bounds.width() * window_list_.size() /
186 (kCardAspectRatio * total_bounds.height())))));
187 int num_rows = ((window_list_.size() + num_columns_ - 1) / num_columns_);
188 window_size.set_width(std::min(
189 static_cast<int>(total_bounds.width() / num_columns_),
190 static_cast<int>(total_bounds.height() * kCardAspectRatio / num_rows)));
191 window_size.set_height(window_size.width() / kCardAspectRatio);
192
193 // Calculate the X and Y offsets necessary to center the grid.
194 int x_offset = total_bounds.x() + ((window_list_.size() >= num_columns_ ? 0 :
195 (num_columns_ - window_list_.size()) * window_size.width()) +
196 (total_bounds.width() - num_columns_ * window_size.width())) / 2;
197 int y_offset = total_bounds.y() + (total_bounds.height() -
198 num_rows * window_size.height()) / 2;
199 for (size_t i = 0; i < window_list_.size(); ++i) {
200 gfx::Transform transform;
201 int column = i % num_columns_;
202 int row = i / num_columns_;
203 gfx::Rect target_bounds(window_size.width() * column + x_offset,
204 window_size.height() * row + y_offset,
205 window_size.width(),
206 window_size.height());
207 window_list_[i]->SetBounds(root_window_, target_bounds, animate);
208 }
209
210 // If we have less than |kMinCardsMajor| windows, adjust the column_ value to
211 // reflect how many "real" columns we have.
212 if (num_columns_ > window_list_.size())
213 num_columns_ = window_list_.size();
214
215 // If the selection widget is active, reposition it without any animation.
216 if (selection_widget_)
217 selection_widget_->SetBounds(SelectedWindow()->target_bounds());
218 }
219
220 bool WindowGrid::Move(WindowSelector::Direction direction) {
221 bool recreate_selection_widget = false;
222 bool out_of_bounds = false;
223 if (!selection_widget_) {
224 switch (direction) {
225 case WindowSelector::LEFT:
226 selected_index_ = window_list_.size() - 1;
227 break;
228 case WindowSelector::UP:
229 selected_index_ =
230 (window_list_.size() / num_columns_) * num_columns_ - 1;
231 break;
232 case WindowSelector::RIGHT:
233 case WindowSelector::DOWN:
234 selected_index_ = 0;
235 break;
236 }
237 } else {
238 switch (direction) {
239 case WindowSelector::RIGHT:
240 if (selected_index_ >= window_list_.size() - 1)
241 out_of_bounds = true;
242 selected_index_++;
243 if (selected_index_ % num_columns_ == 0)
244 recreate_selection_widget = true;
245 break;
246 case WindowSelector::LEFT:
247 if (selected_index_ == 0)
248 out_of_bounds = true;
249 selected_index_--;
250 if ((selected_index_ + 1) % num_columns_ == 0)
251 recreate_selection_widget = true;
252 break;
253 case WindowSelector::DOWN:
254 selected_index_ += num_columns_;
255 if (selected_index_ >= window_list_.size()) {
256 selected_index_ = (selected_index_ + 1) % num_columns_;
257 if (selected_index_ == 0)
258 out_of_bounds = true;
259 recreate_selection_widget = true;
260 }
261 break;
262 case WindowSelector::UP:
263 if (selected_index_ == 0)
264 out_of_bounds = true;
265 if (selected_index_ < num_columns_) {
266 selected_index_ += num_columns_ *
267 ((window_list_.size() - selected_index_) / num_columns_) - 1;
flackr 2014/06/05 20:16:32 Looks much cleaner now, thanks!
268 recreate_selection_widget = true;
269 } else {
270 selected_index_ -= num_columns_;
271 }
272 break;
273 }
274 }
275
276 MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds);
277 return out_of_bounds;
278 }
279
280 WindowSelectorItem* WindowGrid::SelectedWindow() const {
281 CHECK(selected_index_ < window_list_.size());
282 return window_list_[selected_index_];
283 }
284
285 bool WindowGrid::Contains(const aura::Window* window) const {
286 return std::find_if(window_list_.begin(), window_list_.end(),
287 WindowSelectorItemTargetComparator(window)) !=
288 window_list_.end();
289 }
290
291 void WindowGrid::OnWindowDestroying(aura::Window* window) {
292 window->RemoveObserver(this);
293 observed_windows_.erase(window);
294 ScopedVector<WindowSelectorItem>::iterator iter =
295 std::find_if(window_list_.begin(), window_list_.end(),
296 WindowSelectorItemComparator(window));
297
298 DCHECK(iter != window_list_.end());
299
300 (*iter)->RemoveWindow(window);
301
302 // If there are still windows in this selector entry then the overview is
303 // still active and the active selection remains the same.
304 if (!(*iter)->empty())
305 return;
306
307 size_t removed_index = iter - window_list_.begin();
308 window_list_.erase(iter);
309
310 if (empty()) {
311 // If the grid is now empty, notify the window selector so that it erases us
312 // from its grid list.
313 window_selector_->OnGridEmpty(this);
314 return;
315 }
316
317 PositionWindows(true);
318
319 // If existing, reposition the selection widget.
320 if (selection_widget_) {
321 if (selected_index_ >= removed_index && selected_index_ != 0) {
322 selected_index_--;
323 SelectedWindow()->SendFocusAlert();
flackr 2014/06/05 20:16:32 Unless selected_index_ == removed_index, I don't t
Nina 2014/06/05 22:06:23 Done.
324 }
325 MoveSelectionWidgetToTarget();
326 }
327 }
328
329 void WindowGrid::OnWindowBoundsChanged(aura::Window* window,
330 const gfx::Rect& old_bounds,
331 const gfx::Rect& new_bounds) {
332 ScopedVector<WindowSelectorItem>::const_iterator iter =
333 std::find_if(window_list_.begin(), window_list_.end(),
334 WindowSelectorItemTargetComparator(window));
335 DCHECK(iter != window_list_.end());
336
337 // Immediately finish any active bounds animation.
338 window->layer()->GetAnimator()->StopAnimatingProperty(
339 ui::LayerAnimationElement::BOUNDS);
340
341 // Recompute the transform for the window.
342 (*iter)->RecomputeWindowTransforms();
343 }
344
345 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) {
346 selection_widget_.reset(new views::Widget);
347 views::Widget::InitParams params;
348 params.type = views::Widget::InitParams::TYPE_POPUP;
349 params.keep_on_top = false;
350 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
351 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
352 params.parent = Shell::GetContainer(root_window_,
353 kShellWindowId_DefaultContainer);
354 params.accept_events = false;
355 selection_widget_->set_focus_on_creation(false);
356 selection_widget_->Init(params);
357 // Disable the "bounce in" animation when showing the window.
358 ::wm::SetWindowVisibilityAnimationTransition(
359 selection_widget_->GetNativeWindow(), ::wm::ANIMATE_NONE);
360 views::View* content_view = new views::View;
361 content_view->set_background(
362 views::Background::CreateSolidBackground(kWindowOverviewSelectionColor));
363 selection_widget_->SetContentsView(content_view);
364 selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
365 selection_widget_->GetNativeWindow());
366 selection_widget_->Show();
367 // New selection widget starts with 0 opacity and then fades in.
368 selection_widget_->GetNativeWindow()->layer()->SetOpacity(0);
369
370 const gfx::Rect target_bounds = SelectedWindow()->target_bounds();
371 gfx::Vector2d fade_out_direction =
372 GetSlideVectorForFadeIn(direction, target_bounds);
373 gfx::Display dst_display = gfx::Screen::GetScreenFor(root_window_)->
374 GetDisplayMatching(target_bounds);
375 selection_widget_->GetNativeWindow()->SetBoundsInScreen(
376 target_bounds - fade_out_direction, dst_display);
377 }
378
379 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction,
380 bool recreate_selection_widget,
381 bool out_of_bounds) {
382 // If the selection widget is already active, fade it out in the selection
383 // direction.
384 if (selection_widget_) {
385 if (recreate_selection_widget || out_of_bounds) {
flackr 2014/06/05 20:16:32 Merge with if condition above.
Nina 2014/06/05 22:06:22 Done.
Nina 2014/06/05 22:06:23 Done.
386 // Animate the old selection widget and then destroy it.
387 views::Widget* old_selection = selection_widget_.get();
388 gfx::Vector2d fade_out_direction =
389 GetSlideVectorForFadeIn(
390 direction, old_selection->GetNativeWindow()->bounds());
391
392 ui::ScopedLayerAnimationSettings animation_settings(
393 old_selection->GetNativeWindow()->layer()->GetAnimator());
394 animation_settings.SetTransitionDuration(
395 base::TimeDelta::FromMilliseconds(
396 kOverviewSelectorTransitionMilliseconds));
397 animation_settings.SetPreemptionStrategy(
398 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
399 // CleanupWidgetAfterAnimationObserver will delete itself (and the
400 // widget) when the movement animation is complete.
401 animation_settings.AddObserver(
402 new CleanupWidgetAfterAnimationObserver(selection_widget_.Pass()));
403 old_selection->SetOpacity(0);
404 old_selection->GetNativeWindow()->SetBounds(
405 old_selection->GetNativeWindow()->bounds() + fade_out_direction);
406 old_selection->Hide();
407 if (!out_of_bounds)
408 InitSelectionWidget(direction);
flackr 2014/06/05 20:16:32 Remove this, instead rely on it being recreated la
Nina 2014/06/05 22:06:23 Yeah, this is some leftover from the way it worked
409 }
410 } else {
411 InitSelectionWidget(direction);
flackr 2014/06/05 20:16:32 Move this to inside the if (!out_of_bounds) and do
Nina 2014/06/05 22:06:23 Done.
412 }
413 if (!out_of_bounds) {
414 // Send an a11y alert so that if ChromeVox is enabled, the item label is
415 // read.
416 SelectedWindow()->SendFocusAlert();
417 // The selection widget is moved to the newly selected item in the same
418 // grid.
419 MoveSelectionWidgetToTarget();
420 }
421 }
422
423 void WindowGrid::MoveSelectionWidgetToTarget() {
424 ui::ScopedLayerAnimationSettings animation_settings(
425 selection_widget_->GetNativeWindow()->layer()->GetAnimator());
426 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
427 kOverviewSelectorTransitionMilliseconds));
428 animation_settings.SetPreemptionStrategy(
429 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
430 selection_widget_->SetBounds(SelectedWindow()->target_bounds());
431 selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
432 }
433
434 } // namespace ash
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698