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