Port Android relocation packer to chromium build
[chromium-blink-merge.git] / ash / wm / overview / window_grid.cc
bloba03966ab735fe606208b52407aef1fe205552813
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.
5 #include "ash/wm/overview/window_grid.h"
7 #include <algorithm>
8 #include <functional>
9 #include <set>
10 #include <vector>
12 #include "ash/ash_switches.h"
13 #include "ash/screen_util.h"
14 #include "ash/shell.h"
15 #include "ash/shell_window_ids.h"
16 #include "ash/wm/overview/scoped_transform_overview_window.h"
17 #include "ash/wm/overview/window_selector.h"
18 #include "ash/wm/overview/window_selector_item.h"
19 #include "ash/wm/window_state.h"
20 #include "base/command_line.h"
21 #include "base/i18n/string_search.h"
22 #include "base/memory/scoped_vector.h"
23 #include "third_party/skia/include/core/SkColor.h"
24 #include "ui/aura/window.h"
25 #include "ui/compositor/layer_animation_observer.h"
26 #include "ui/compositor/scoped_layer_animation_settings.h"
27 #include "ui/gfx/animation/tween.h"
28 #include "ui/gfx/geometry/vector2d.h"
29 #include "ui/views/background.h"
30 #include "ui/views/view.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/wm/core/window_animations.h"
34 namespace ash {
35 namespace {
37 typedef std::vector<aura::Window*> Windows;
39 // An observer which holds onto the passed widget until the animation is
40 // complete.
41 class CleanupWidgetAfterAnimationObserver
42 : public ui::ImplicitAnimationObserver {
43 public:
44 explicit CleanupWidgetAfterAnimationObserver(
45 scoped_ptr<views::Widget> widget);
46 ~CleanupWidgetAfterAnimationObserver() override;
48 // ui::ImplicitAnimationObserver:
49 void OnImplicitAnimationsCompleted() override;
51 private:
52 scoped_ptr<views::Widget> widget_;
54 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
57 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
58 scoped_ptr<views::Widget> widget)
59 : widget_(widget.Pass()) {
62 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
65 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
66 delete this;
69 // A comparator for locating a given target window.
70 struct WindowSelectorItemComparator
71 : public std::unary_function<WindowSelectorItem*, bool> {
72 explicit WindowSelectorItemComparator(const aura::Window* target_window)
73 : target(target_window) {
76 bool operator()(WindowSelectorItem* window) const {
77 return window->GetWindow() == target;
80 const aura::Window* target;
83 // Conceptually the window overview is a table or grid of cells having this
84 // fixed aspect ratio. The number of columns is determined by maximizing the
85 // area of them based on the number of window_list.
86 const float kCardAspectRatio = 4.0f / 3.0f;
88 // The minimum number of cards along the major axis (i.e. horizontally on a
89 // landscape orientation).
90 const int kMinCardsMajor = 3;
92 const int kOverviewSelectorTransitionMilliseconds = 100;
94 // The color and opacity of the overview selector.
95 const SkColor kWindowOverviewSelectionColor = SK_ColorBLACK;
96 const unsigned char kWindowOverviewSelectorOpacity = 128;
98 // The minimum amount of spacing between the bottom of the text filtering
99 // text field and the top of the selection widget on the first row of items.
100 const int kTextFilterBottomMargin = 5;
102 // Returns the vector for the fade in animation.
103 gfx::Vector2d GetSlideVectorForFadeIn(WindowSelector::Direction direction,
104 const gfx::Rect& bounds) {
105 gfx::Vector2d vector;
106 switch (direction) {
107 case WindowSelector::DOWN:
108 vector.set_y(bounds.width());
109 break;
110 case WindowSelector::RIGHT:
111 vector.set_x(bounds.height());
112 break;
113 case WindowSelector::UP:
114 vector.set_y(-bounds.width());
115 break;
116 case WindowSelector::LEFT:
117 vector.set_x(-bounds.height());
118 break;
120 return vector;
123 } // namespace
125 WindowGrid::WindowGrid(aura::Window* root_window,
126 const std::vector<aura::Window*>& windows,
127 WindowSelector* window_selector)
128 : root_window_(root_window),
129 window_selector_(window_selector) {
131 for (aura::Window::Windows::const_iterator iter = windows.begin();
132 iter != windows.end(); ++iter) {
133 if ((*iter)->GetRootWindow() != root_window)
134 continue;
135 (*iter)->AddObserver(this);
136 observed_windows_.insert(*iter);
138 window_list_.push_back(new WindowSelectorItem(*iter));
142 WindowGrid::~WindowGrid() {
143 for (std::set<aura::Window*>::iterator iter = observed_windows_.begin();
144 iter != observed_windows_.end(); iter++) {
145 (*iter)->RemoveObserver(this);
149 void WindowGrid::PrepareForOverview() {
150 for (ScopedVector<WindowSelectorItem>::iterator iter = window_list_.begin();
151 iter != window_list_.end(); ++iter) {
152 (*iter)->PrepareForOverview();
156 void WindowGrid::PositionWindows(bool animate) {
157 CHECK(!window_list_.empty());
159 gfx::Size window_size;
160 gfx::Rect total_bounds = ScreenUtil::ConvertRectToScreen(
161 root_window_,
162 ScreenUtil::GetDisplayWorkAreaBoundsInParent(
163 Shell::GetContainer(root_window_, kShellWindowId_DefaultContainer)));
165 // Reserve space at the top for the text filtering textbox to appear.
166 total_bounds.Inset(
167 0, WindowSelector::kTextFilterBottomEdge + kTextFilterBottomMargin, 0, 0);
169 // Find the minimum number of windows per row that will fit all of the
170 // windows on screen.
171 num_columns_ = std::max(
172 total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
173 static_cast<int>(ceil(sqrt(total_bounds.width() * window_list_.size() /
174 (kCardAspectRatio * total_bounds.height())))));
175 int num_rows = ((window_list_.size() + num_columns_ - 1) / num_columns_);
176 window_size.set_width(std::min(
177 static_cast<int>(total_bounds.width() / num_columns_),
178 static_cast<int>(total_bounds.height() * kCardAspectRatio / num_rows)));
179 window_size.set_height(window_size.width() / kCardAspectRatio);
181 // Calculate the X and Y offsets necessary to center the grid.
182 int x_offset = total_bounds.x() + ((window_list_.size() >= num_columns_ ? 0 :
183 (num_columns_ - window_list_.size()) * window_size.width()) +
184 (total_bounds.width() - num_columns_ * window_size.width())) / 2;
185 int y_offset = total_bounds.y() + (total_bounds.height() -
186 num_rows * window_size.height()) / 2;
188 for (size_t i = 0; i < window_list_.size(); ++i) {
189 gfx::Transform transform;
190 int column = i % num_columns_;
191 int row = i / num_columns_;
192 gfx::Rect target_bounds(window_size.width() * column + x_offset,
193 window_size.height() * row + y_offset,
194 window_size.width(),
195 window_size.height());
196 window_list_[i]->SetBounds(target_bounds, animate ?
197 OverviewAnimationType::OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS :
198 OverviewAnimationType::OVERVIEW_ANIMATION_NONE);
201 // If we have less than |kMinCardsMajor| windows, adjust the column_ value to
202 // reflect how many "real" columns we have.
203 if (num_columns_ > window_list_.size())
204 num_columns_ = window_list_.size();
206 // If the selection widget is active, reposition it without any animation.
207 if (selection_widget_)
208 MoveSelectionWidgetToTarget(animate);
211 bool WindowGrid::Move(WindowSelector::Direction direction, bool animate) {
212 bool recreate_selection_widget = false;
213 bool out_of_bounds = false;
214 if (!selection_widget_) {
215 switch (direction) {
216 case WindowSelector::LEFT:
217 selected_index_ = window_list_.size() - 1;
218 break;
219 case WindowSelector::UP:
220 selected_index_ =
221 (window_list_.size() / num_columns_) * num_columns_ - 1;
222 break;
223 case WindowSelector::RIGHT:
224 case WindowSelector::DOWN:
225 selected_index_ = 0;
226 break;
229 while (SelectedWindow()->dimmed() || selection_widget_) {
230 switch (direction) {
231 case WindowSelector::RIGHT:
232 if (selected_index_ >= window_list_.size() - 1)
233 out_of_bounds = true;
234 selected_index_++;
235 if (selected_index_ % num_columns_ == 0)
236 recreate_selection_widget = true;
237 break;
238 case WindowSelector::LEFT:
239 if (selected_index_ == 0)
240 out_of_bounds = true;
241 selected_index_--;
242 if ((selected_index_ + 1) % num_columns_ == 0)
243 recreate_selection_widget = true;
244 break;
245 case WindowSelector::DOWN:
246 selected_index_ += num_columns_;
247 if (selected_index_ >= window_list_.size()) {
248 selected_index_ = (selected_index_ + 1) % num_columns_;
249 if (selected_index_ == 0)
250 out_of_bounds = true;
251 recreate_selection_widget = true;
253 break;
254 case WindowSelector::UP:
255 if (selected_index_ == 0)
256 out_of_bounds = true;
257 if (selected_index_ < num_columns_) {
258 selected_index_ += num_columns_ *
259 ((window_list_.size() - selected_index_) / num_columns_) - 1;
260 recreate_selection_widget = true;
261 } else {
262 selected_index_ -= num_columns_;
264 break;
266 // Exit the loop if we broke free from the grid or found an active item.
267 if (out_of_bounds || !SelectedWindow()->dimmed())
268 break;
271 MoveSelectionWidget(direction, recreate_selection_widget,
272 out_of_bounds, animate);
273 return out_of_bounds;
276 WindowSelectorItem* WindowGrid::SelectedWindow() const {
277 CHECK(selected_index_ < window_list_.size());
278 return window_list_[selected_index_];
281 bool WindowGrid::Contains(const aura::Window* window) const {
282 for (const WindowSelectorItem* window_item : window_list_) {
283 if (window_item->Contains(window))
284 return true;
286 return false;
289 void WindowGrid::FilterItems(const base::string16& pattern) {
290 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents finder(pattern);
291 for (ScopedVector<WindowSelectorItem>::iterator iter = window_list_.begin();
292 iter != window_list_.end(); iter++) {
293 if (finder.Search((*iter)->GetWindow()->title(), nullptr, nullptr)) {
294 (*iter)->SetDimmed(false);
295 } else {
296 (*iter)->SetDimmed(true);
297 if (selection_widget_ && SelectedWindow() == *iter)
298 selection_widget_.reset();
303 void WindowGrid::OnWindowDestroying(aura::Window* window) {
304 window->RemoveObserver(this);
305 observed_windows_.erase(window);
306 ScopedVector<WindowSelectorItem>::iterator iter =
307 std::find_if(window_list_.begin(), window_list_.end(),
308 WindowSelectorItemComparator(window));
310 DCHECK(iter != window_list_.end());
312 size_t removed_index = iter - window_list_.begin();
313 window_list_.erase(iter);
315 if (empty()) {
316 // If the grid is now empty, notify the window selector so that it erases us
317 // from its grid list.
318 window_selector_->OnGridEmpty(this);
319 return;
322 // If selecting, update the selection index.
323 if (selection_widget_) {
324 bool send_focus_alert = selected_index_ == removed_index;
325 if (selected_index_ >= removed_index && selected_index_ != 0)
326 selected_index_--;
327 if (send_focus_alert)
328 SelectedWindow()->SendFocusAlert();
331 PositionWindows(true);
334 void WindowGrid::OnWindowBoundsChanged(aura::Window* window,
335 const gfx::Rect& old_bounds,
336 const gfx::Rect& new_bounds) {
337 ScopedVector<WindowSelectorItem>::const_iterator iter =
338 std::find_if(window_list_.begin(), window_list_.end(),
339 WindowSelectorItemComparator(window));
340 DCHECK(iter != window_list_.end());
342 // Immediately finish any active bounds animation.
343 window->layer()->GetAnimator()->StopAnimatingProperty(
344 ui::LayerAnimationElement::BOUNDS);
346 // Recompute the transform for the window.
347 (*iter)->RecomputeWindowTransforms();
350 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) {
351 selection_widget_.reset(new views::Widget);
352 views::Widget::InitParams params;
353 params.type = views::Widget::InitParams::TYPE_POPUP;
354 params.keep_on_top = false;
355 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
356 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
357 params.parent = Shell::GetContainer(root_window_,
358 kShellWindowId_DefaultContainer);
359 params.accept_events = false;
360 selection_widget_->set_focus_on_creation(false);
361 selection_widget_->Init(params);
362 // Disable the "bounce in" animation when showing the window.
363 ::wm::SetWindowVisibilityAnimationTransition(
364 selection_widget_->GetNativeWindow(), ::wm::ANIMATE_NONE);
365 // The selection widget should not activate the shelf when passing under it.
366 wm::GetWindowState(selection_widget_->GetNativeWindow())->
367 set_ignored_by_shelf(true);
369 views::View* content_view = new views::View;
370 content_view->set_background(
371 views::Background::CreateSolidBackground(kWindowOverviewSelectionColor));
372 selection_widget_->SetContentsView(content_view);
373 selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
374 selection_widget_->GetNativeWindow());
375 selection_widget_->Show();
376 // New selection widget starts with 0 opacity and then fades in.
377 selection_widget_->GetNativeWindow()->layer()->SetOpacity(0);
379 const gfx::Rect target_bounds = SelectedWindow()->target_bounds();
380 gfx::Vector2d fade_out_direction =
381 GetSlideVectorForFadeIn(direction, target_bounds);
382 gfx::Display dst_display = gfx::Screen::GetScreenFor(root_window_)->
383 GetDisplayMatching(target_bounds);
384 selection_widget_->GetNativeWindow()->SetBoundsInScreen(
385 target_bounds - fade_out_direction, dst_display);
388 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction,
389 bool recreate_selection_widget,
390 bool out_of_bounds,
391 bool animate) {
392 // If the selection widget is already active, fade it out in the selection
393 // direction.
394 if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) {
395 // Animate the old selection widget and then destroy it.
396 views::Widget* old_selection = selection_widget_.get();
397 gfx::Vector2d fade_out_direction =
398 GetSlideVectorForFadeIn(
399 direction, old_selection->GetNativeWindow()->bounds());
401 ui::ScopedLayerAnimationSettings animation_settings(
402 old_selection->GetNativeWindow()->layer()->GetAnimator());
403 animation_settings.SetTransitionDuration(
404 base::TimeDelta::FromMilliseconds(
405 kOverviewSelectorTransitionMilliseconds));
406 animation_settings.SetPreemptionStrategy(
407 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
408 animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN);
409 // CleanupWidgetAfterAnimationObserver will delete itself (and the
410 // widget) when the movement animation is complete.
411 animation_settings.AddObserver(
412 new CleanupWidgetAfterAnimationObserver(selection_widget_.Pass()));
413 old_selection->SetOpacity(0);
414 old_selection->GetNativeWindow()->SetBounds(
415 old_selection->GetNativeWindow()->bounds() + fade_out_direction);
416 old_selection->Hide();
418 if (out_of_bounds)
419 return;
421 if (!selection_widget_)
422 InitSelectionWidget(direction);
423 // Send an a11y alert so that if ChromeVox is enabled, the item label is
424 // read.
425 SelectedWindow()->SendFocusAlert();
426 // The selection widget is moved to the newly selected item in the same
427 // grid.
428 MoveSelectionWidgetToTarget(animate);
431 void WindowGrid::MoveSelectionWidgetToTarget(bool animate) {
432 if (animate) {
433 ui::ScopedLayerAnimationSettings animation_settings(
434 selection_widget_->GetNativeWindow()->layer()->GetAnimator());
435 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
436 kOverviewSelectorTransitionMilliseconds));
437 animation_settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
438 animation_settings.SetPreemptionStrategy(
439 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
440 selection_widget_->SetBounds(SelectedWindow()->target_bounds());
441 selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
442 return;
444 selection_widget_->SetBounds(SelectedWindow()->target_bounds());
445 selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
448 } // namespace ash