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/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 28 namespace ash { 29 namespace { 30 31 // An observer which holds onto the passed widget until the animation is 32 // complete. 33 class 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 49 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver( 50 scoped_ptr<views::Widget> widget) 51 : widget_(widget.Pass()) { 52 } 53 54 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() { 55 } 56 57 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() { 58 delete this; 59 } 60 61 // A comparator for locating a given target window. 62 struct 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. 76 struct 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. 92 const 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). 96 const int kMinCardsMajor = 3; 97 98 const int kOverviewSelectorTransitionMilliseconds = 100; 99 100 // The color and opacity of the overview selector. 101 const SkColor kWindowOverviewSelectionColor = SK_ColorBLACK; 102 const unsigned char kWindowOverviewSelectorOpacity = 128; 103 104 // Returns the vector for the fade in animation. 105 gfx::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 127 WindowGrid::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 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(root_window_); 146 window_list_.push_back(panels_item); 147 } 148 panels_item->AddWindow(*iter); 149 } else { 150 window_list_.push_back(new WindowSelectorWindow(*iter)); 151 } 152 } 153 if (window_list_.empty()) 154 return; 155 } 156 157 WindowGrid::~WindowGrid() { 158 for (std::set<aura::Window*>::iterator iter = observed_windows_.begin(); 159 iter != observed_windows_.end(); iter++) { 160 (*iter)->RemoveObserver(this); 161 } 162 } 163 164 void WindowGrid::PrepareForOverview() { 165 for (ScopedVector<WindowSelectorItem>::iterator iter = window_list_.begin(); 166 iter != window_list_.end(); ++iter) { 167 (*iter)->PrepareForOverview(); 168 } 169 } 170 171 void WindowGrid::PositionWindows(bool animate) { 172 CHECK(!window_list_.empty()); 173 174 gfx::Size window_size; 175 gfx::Rect total_bounds = ScreenUtil::ConvertRectToScreen( 176 root_window_, 177 ScreenUtil::GetDisplayWorkAreaBoundsInParent( 178 Shell::GetContainer(root_window_, kShellWindowId_DefaultContainer))); 179 180 // Find the minimum number of windows per row that will fit all of the 181 // windows on screen. 182 num_columns_ = std::max( 183 total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1, 184 static_cast<int>(ceil(sqrt(total_bounds.width() * window_list_.size() / 185 (kCardAspectRatio * total_bounds.height()))))); 186 int num_rows = ((window_list_.size() + num_columns_ - 1) / num_columns_); 187 window_size.set_width(std::min( 188 static_cast<int>(total_bounds.width() / num_columns_), 189 static_cast<int>(total_bounds.height() * kCardAspectRatio / num_rows))); 190 window_size.set_height(window_size.width() / kCardAspectRatio); 191 192 // Calculate the X and Y offsets necessary to center the grid. 193 int x_offset = total_bounds.x() + ((window_list_.size() >= num_columns_ ? 0 : 194 (num_columns_ - window_list_.size()) * window_size.width()) + 195 (total_bounds.width() - num_columns_ * window_size.width())) / 2; 196 int y_offset = total_bounds.y() + (total_bounds.height() - 197 num_rows * window_size.height()) / 2; 198 for (size_t i = 0; i < window_list_.size(); ++i) { 199 gfx::Transform transform; 200 int column = i % num_columns_; 201 int row = i / num_columns_; 202 gfx::Rect target_bounds(window_size.width() * column + x_offset, 203 window_size.height() * row + y_offset, 204 window_size.width(), 205 window_size.height()); 206 window_list_[i]->SetBounds(root_window_, target_bounds, animate); 207 } 208 209 // If we have less than |kMinCardsMajor| windows, adjust the column_ value to 210 // reflect how many "real" columns we have. 211 if (num_columns_ > window_list_.size()) 212 num_columns_ = window_list_.size(); 213 214 // If the selection widget is active, reposition it without any animation. 215 if (selection_widget_) 216 MoveSelectionWidgetToTarget(animate); 217 } 218 219 bool WindowGrid::Move(WindowSelector::Direction direction) { 220 bool recreate_selection_widget = false; 221 bool out_of_bounds = false; 222 if (!selection_widget_) { 223 switch (direction) { 224 case WindowSelector::LEFT: 225 selected_index_ = window_list_.size() - 1; 226 break; 227 case WindowSelector::UP: 228 selected_index_ = 229 (window_list_.size() / num_columns_) * num_columns_ - 1; 230 break; 231 case WindowSelector::RIGHT: 232 case WindowSelector::DOWN: 233 selected_index_ = 0; 234 break; 235 } 236 } else { 237 switch (direction) { 238 case WindowSelector::RIGHT: 239 if (selected_index_ >= window_list_.size() - 1) 240 out_of_bounds = true; 241 selected_index_++; 242 if (selected_index_ % num_columns_ == 0) 243 recreate_selection_widget = true; 244 break; 245 case WindowSelector::LEFT: 246 if (selected_index_ == 0) 247 out_of_bounds = true; 248 selected_index_--; 249 if ((selected_index_ + 1) % num_columns_ == 0) 250 recreate_selection_widget = true; 251 break; 252 case WindowSelector::DOWN: 253 selected_index_ += num_columns_; 254 if (selected_index_ >= window_list_.size()) { 255 selected_index_ = (selected_index_ + 1) % num_columns_; 256 if (selected_index_ == 0) 257 out_of_bounds = true; 258 recreate_selection_widget = true; 259 } 260 break; 261 case WindowSelector::UP: 262 if (selected_index_ == 0) 263 out_of_bounds = true; 264 if (selected_index_ < num_columns_) { 265 selected_index_ += num_columns_ * 266 ((window_list_.size() - selected_index_) / num_columns_) - 1; 267 recreate_selection_widget = true; 268 } else { 269 selected_index_ -= num_columns_; 270 } 271 break; 272 } 273 } 274 275 MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds); 276 return out_of_bounds; 277 } 278 279 WindowSelectorItem* WindowGrid::SelectedWindow() const { 280 CHECK(selected_index_ < window_list_.size()); 281 return window_list_[selected_index_]; 282 } 283 284 bool WindowGrid::Contains(const aura::Window* window) const { 285 return std::find_if(window_list_.begin(), window_list_.end(), 286 WindowSelectorItemTargetComparator(window)) != 287 window_list_.end(); 288 } 289 290 void WindowGrid::OnWindowDestroying(aura::Window* window) { 291 window->RemoveObserver(this); 292 observed_windows_.erase(window); 293 ScopedVector<WindowSelectorItem>::iterator iter = 294 std::find_if(window_list_.begin(), window_list_.end(), 295 WindowSelectorItemComparator(window)); 296 297 DCHECK(iter != window_list_.end()); 298 299 (*iter)->RemoveWindow(window); 300 301 // If there are still windows in this selector entry then the overview is 302 // still active and the active selection remains the same. 303 if (!(*iter)->empty()) 304 return; 305 306 size_t removed_index = iter - window_list_.begin(); 307 window_list_.erase(iter); 308 309 if (empty()) { 310 // If the grid is now empty, notify the window selector so that it erases us 311 // from its grid list. 312 window_selector_->OnGridEmpty(this); 313 return; 314 } 315 316 // If selecting, update the selection index. 317 if (selection_widget_) { 318 bool send_focus_alert = selected_index_ == removed_index; 319 if (selected_index_ >= removed_index && selected_index_ != 0) 320 selected_index_--; 321 if (send_focus_alert) 322 SelectedWindow()->SendFocusAlert(); 323 } 324 325 PositionWindows(true); 326 } 327 328 void WindowGrid::OnWindowBoundsChanged(aura::Window* window, 329 const gfx::Rect& old_bounds, 330 const gfx::Rect& new_bounds) { 331 ScopedVector<WindowSelectorItem>::const_iterator iter = 332 std::find_if(window_list_.begin(), window_list_.end(), 333 WindowSelectorItemTargetComparator(window)); 334 DCHECK(iter != window_list_.end()); 335 336 // Immediately finish any active bounds animation. 337 window->layer()->GetAnimator()->StopAnimatingProperty( 338 ui::LayerAnimationElement::BOUNDS); 339 340 // Recompute the transform for the window. 341 (*iter)->RecomputeWindowTransforms(); 342 } 343 344 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) { 345 selection_widget_.reset(new views::Widget); 346 views::Widget::InitParams params; 347 params.type = views::Widget::InitParams::TYPE_POPUP; 348 params.keep_on_top = false; 349 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 350 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 351 params.parent = Shell::GetContainer(root_window_, 352 kShellWindowId_DefaultContainer); 353 params.accept_events = false; 354 selection_widget_->set_focus_on_creation(false); 355 selection_widget_->Init(params); 356 // Disable the "bounce in" animation when showing the window. 357 ::wm::SetWindowVisibilityAnimationTransition( 358 selection_widget_->GetNativeWindow(), ::wm::ANIMATE_NONE); 359 // The selection widget should not activate the shelf when passing under it. 360 ash::wm::GetWindowState(selection_widget_->GetNativeWindow())-> 361 set_ignored_by_shelf(true); 362 363 views::View* content_view = new views::View; 364 content_view->set_background( 365 views::Background::CreateSolidBackground(kWindowOverviewSelectionColor)); 366 selection_widget_->SetContentsView(content_view); 367 selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom( 368 selection_widget_->GetNativeWindow()); 369 selection_widget_->Show(); 370 // New selection widget starts with 0 opacity and then fades in. 371 selection_widget_->GetNativeWindow()->layer()->SetOpacity(0); 372 373 const gfx::Rect target_bounds = SelectedWindow()->target_bounds(); 374 gfx::Vector2d fade_out_direction = 375 GetSlideVectorForFadeIn(direction, target_bounds); 376 gfx::Display dst_display = gfx::Screen::GetScreenFor(root_window_)-> 377 GetDisplayMatching(target_bounds); 378 selection_widget_->GetNativeWindow()->SetBoundsInScreen( 379 target_bounds - fade_out_direction, dst_display); 380 } 381 382 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction, 383 bool recreate_selection_widget, 384 bool out_of_bounds) { 385 // If the selection widget is already active, fade it out in the selection 386 // direction. 387 if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) { 388 // Animate the old selection widget and then destroy it. 389 views::Widget* old_selection = selection_widget_.get(); 390 gfx::Vector2d fade_out_direction = 391 GetSlideVectorForFadeIn( 392 direction, old_selection->GetNativeWindow()->bounds()); 393 394 ui::ScopedLayerAnimationSettings animation_settings( 395 old_selection->GetNativeWindow()->layer()->GetAnimator()); 396 animation_settings.SetTransitionDuration( 397 base::TimeDelta::FromMilliseconds( 398 kOverviewSelectorTransitionMilliseconds)); 399 animation_settings.SetPreemptionStrategy( 400 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 401 animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN); 402 // CleanupWidgetAfterAnimationObserver will delete itself (and the 403 // widget) when the movement animation is complete. 404 animation_settings.AddObserver( 405 new CleanupWidgetAfterAnimationObserver(selection_widget_.Pass())); 406 old_selection->SetOpacity(0); 407 old_selection->GetNativeWindow()->SetBounds( 408 old_selection->GetNativeWindow()->bounds() + fade_out_direction); 409 old_selection->Hide(); 410 } 411 if (out_of_bounds) 412 return; 413 414 if (!selection_widget_) 415 InitSelectionWidget(direction); 416 // Send an a11y alert so that if ChromeVox is enabled, the item label is 417 // read. 418 SelectedWindow()->SendFocusAlert(); 419 // The selection widget is moved to the newly selected item in the same 420 // grid. 421 MoveSelectionWidgetToTarget(true); 422 } 423 424 void WindowGrid::MoveSelectionWidgetToTarget(bool animate) { 425 if (animate) { 426 ui::ScopedLayerAnimationSettings animation_settings( 427 selection_widget_->GetNativeWindow()->layer()->GetAnimator()); 428 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 429 kOverviewSelectorTransitionMilliseconds)); 430 animation_settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN); 431 animation_settings.SetPreemptionStrategy( 432 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 433 selection_widget_->SetBounds(SelectedWindow()->target_bounds()); 434 selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity); 435 return; 436 } 437 selection_widget_->SetBounds(SelectedWindow()->target_bounds()); 438 selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity); 439 } 440 441 } // namespace ash 442