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