1 // Copyright 2013 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/window_selector.h" 6 7 #include <algorithm> 8 9 #include "ash/screen_ash.h" 10 #include "ash/shell.h" 11 #include "ash/shell_window_ids.h" 12 #include "ash/wm/window_selector_delegate.h" 13 #include "ash/wm/window_util.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "third_party/skia/include/core/SkColor.h" 16 #include "ui/aura/client/aura_constants.h" 17 #include "ui/aura/client/screen_position_client.h" 18 #include "ui/aura/root_window.h" 19 #include "ui/aura/window.h" 20 #include "ui/base/events/event.h" 21 #include "ui/compositor/layer_animation_observer.h" 22 #include "ui/compositor/scoped_layer_animation_settings.h" 23 #include "ui/gfx/display.h" 24 #include "ui/gfx/interpolated_transform.h" 25 #include "ui/gfx/transform_util.h" 26 #include "ui/views/corewm/shadow_types.h" 27 #include "ui/views/corewm/window_animations.h" 28 #include "ui/views/corewm/window_util.h" 29 #include "ui/views/widget/widget.h" 30 31 namespace ash { 32 33 namespace { 34 35 const float kCardAspectRatio = 4.0f / 3.0f; 36 const int kWindowMargin = 30; 37 const int kMinCardsMajor = 3; 38 const int kOverviewTransitionMilliseconds = 100; 39 const SkColor kWindowSelectorSelectionColor = SK_ColorBLACK; 40 const float kWindowSelectorSelectionOpacity = 0.5f; 41 const int kWindowSelectorSelectionPadding = 15; 42 43 // Creates a copy of |window| with |recreated_layer| in the |target_root|. 44 views::Widget* CreateCopyOfWindow(aura::RootWindow* target_root, 45 aura::Window* src_window, 46 ui::Layer* recreated_layer) { 47 views::Widget* widget = new views::Widget; 48 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); 49 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 50 params.parent = src_window->parent(); 51 params.can_activate = false; 52 params.keep_on_top = true; 53 widget->set_focus_on_creation(false); 54 widget->Init(params); 55 widget->SetVisibilityChangedAnimationsEnabled(false); 56 std::string name = src_window->name() + " (Copy)"; 57 widget->GetNativeWindow()->SetName(name); 58 views::corewm::SetShadowType(widget->GetNativeWindow(), 59 views::corewm::SHADOW_TYPE_RECTANGULAR); 60 61 // Set the bounds in the target root window. 62 gfx::Display target_display = 63 Shell::GetScreen()->GetDisplayNearestWindow(target_root); 64 aura::client::ScreenPositionClient* screen_position_client = 65 aura::client::GetScreenPositionClient(src_window->GetRootWindow()); 66 if (screen_position_client && target_display.is_valid()) { 67 screen_position_client->SetBounds(widget->GetNativeWindow(), 68 src_window->GetBoundsInScreen(), target_display); 69 } else { 70 widget->SetBounds(src_window->GetBoundsInScreen()); 71 } 72 widget->StackAbove(src_window); 73 74 // Move the |recreated_layer| to the newly created window. 75 recreated_layer->set_delegate(src_window->layer()->delegate()); 76 gfx::Rect layer_bounds = recreated_layer->bounds(); 77 layer_bounds.set_origin(gfx::Point(0, 0)); 78 recreated_layer->SetBounds(layer_bounds); 79 recreated_layer->SetVisible(false); 80 recreated_layer->parent()->Remove(recreated_layer); 81 82 aura::Window* window = widget->GetNativeWindow(); 83 recreated_layer->SetVisible(true); 84 window->layer()->Add(recreated_layer); 85 window->layer()->StackAtTop(recreated_layer); 86 window->layer()->SetOpacity(1); 87 window->Show(); 88 return widget; 89 } 90 91 // An observer which closes the widget and deletes the layer after an 92 // animation finishes. 93 class CleanupWidgetAfterAnimationObserver : public ui::LayerAnimationObserver { 94 public: 95 CleanupWidgetAfterAnimationObserver(views::Widget* widget, ui::Layer* layer); 96 97 virtual void OnLayerAnimationEnded( 98 ui::LayerAnimationSequence* sequence) OVERRIDE; 99 virtual void OnLayerAnimationAborted( 100 ui::LayerAnimationSequence* sequence) OVERRIDE; 101 virtual void OnLayerAnimationScheduled( 102 ui::LayerAnimationSequence* sequence) OVERRIDE; 103 104 protected: 105 virtual ~CleanupWidgetAfterAnimationObserver(); 106 107 private: 108 views::Widget* widget_; 109 ui::Layer* layer_; 110 111 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver); 112 }; 113 114 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver( 115 views::Widget* widget, 116 ui::Layer* layer) 117 : widget_(widget), 118 layer_(layer) { 119 widget_->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this); 120 } 121 122 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded( 123 ui::LayerAnimationSequence* sequence) { 124 delete this; 125 } 126 127 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted( 128 ui::LayerAnimationSequence* sequence) { 129 delete this; 130 } 131 132 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled( 133 ui::LayerAnimationSequence* sequence) { 134 } 135 136 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() { 137 widget_->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this); 138 widget_->Close(); 139 widget_ = NULL; 140 if (layer_) { 141 views::corewm::DeepDeleteLayers(layer_); 142 layer_ = NULL; 143 } 144 } 145 146 // The animation settings used for window selector animations. 147 class WindowSelectorAnimationSettings 148 : public ui::ScopedLayerAnimationSettings { 149 public: 150 WindowSelectorAnimationSettings(aura::Window* window) : 151 ui::ScopedLayerAnimationSettings(window->layer()->GetAnimator()) { 152 SetPreemptionStrategy( 153 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 154 SetTransitionDuration( 155 base::TimeDelta::FromMilliseconds(kOverviewTransitionMilliseconds)); 156 } 157 158 virtual ~WindowSelectorAnimationSettings() { 159 } 160 }; 161 162 } // namespace 163 164 // TODO(flackr): Split up into separate file under subdirectory in ash/wm. 165 class WindowSelectorWindow { 166 public: 167 explicit WindowSelectorWindow(aura::Window* window); 168 virtual ~WindowSelectorWindow(); 169 170 aura::Window* window() { return window_; } 171 const aura::Window* window() const { return window_; } 172 173 // Returns true if this window selector window contains the |target|. This is 174 // used to determine if an event targetted this window. 175 bool Contains(const aura::Window* target) const; 176 177 // Restores this window on exit rather than returning it to a minimized state 178 // if it was minimized on entering overview mode. 179 void RestoreWindowOnExit(); 180 181 // Informs the WindowSelectorWindow that the window being watched was 182 // destroyed. This resets the internal window pointer to avoid calling 183 // anything on the window at destruction time. 184 void OnWindowDestroyed(); 185 186 // Applies a transform to the window to fit within |target_bounds| while 187 // maintaining its aspect ratio. 188 void TransformToFitBounds(aura::RootWindow* root_window, 189 const gfx::Rect& target_bounds); 190 191 gfx::Rect bounds() { return fit_bounds_; } 192 193 private: 194 // A weak pointer to the real window in the overview. 195 aura::Window* window_; 196 197 // A copy of the window used to transition the window to another root. 198 views::Widget* window_copy_; 199 200 // A weak pointer to a deep copy of the window's layers. 201 ui::Layer* layer_; 202 203 // If true, the window was minimized and should be restored if the window 204 // was not selected. 205 bool minimized_; 206 207 // The original transform of the window before entering overview mode. 208 gfx::Transform original_transform_; 209 210 // The bounds this window is fit to. 211 gfx::Rect fit_bounds_; 212 213 DISALLOW_COPY_AND_ASSIGN(WindowSelectorWindow); 214 }; 215 216 WindowSelectorWindow::WindowSelectorWindow(aura::Window* window) 217 : window_(window), 218 window_copy_(NULL), 219 layer_(NULL), 220 minimized_(window->GetProperty(aura::client::kShowStateKey) == 221 ui::SHOW_STATE_MINIMIZED), 222 original_transform_(window->layer()->transform()) { 223 if (minimized_) 224 window_->Show(); 225 } 226 227 WindowSelectorWindow::~WindowSelectorWindow() { 228 if (window_) { 229 WindowSelectorAnimationSettings animation_settings(window_); 230 gfx::Transform transform; 231 window_->SetTransform(original_transform_); 232 if (minimized_) { 233 // Setting opacity 0 and visible false ensures that the property change 234 // to SHOW_STATE_MINIMIZED will not animate the window from its original 235 // bounds to the minimized position. 236 window_->layer()->SetOpacity(0); 237 window_->layer()->SetVisible(false); 238 window_->SetProperty(aura::client::kShowStateKey, 239 ui::SHOW_STATE_MINIMIZED); 240 } 241 } 242 // If a copy of the window was created, clean it up. 243 if (window_copy_) { 244 if (window_) { 245 // If the initial window wasn't destroyed, the copy needs to be animated 246 // out. CleanupWidgetAfterAnimationObserver will destroy the widget and 247 // layer after the animation is complete. 248 new CleanupWidgetAfterAnimationObserver(window_copy_, layer_); 249 WindowSelectorAnimationSettings animation_settings( 250 window_copy_->GetNativeWindow()); 251 window_copy_->GetNativeWindow()->SetTransform(original_transform_); 252 } else { 253 window_copy_->Close(); 254 if (layer_) 255 views::corewm::DeepDeleteLayers(layer_); 256 } 257 window_copy_ = NULL; 258 layer_ = NULL; 259 } 260 } 261 262 bool WindowSelectorWindow::Contains(const aura::Window* window) const { 263 if (window_copy_ && window_copy_->GetNativeWindow()->Contains(window)) 264 return true; 265 return window_->Contains(window); 266 } 267 268 void WindowSelectorWindow::RestoreWindowOnExit() { 269 minimized_ = false; 270 original_transform_ = gfx::Transform(); 271 } 272 273 void WindowSelectorWindow::OnWindowDestroyed() { 274 window_ = NULL; 275 } 276 277 void WindowSelectorWindow::TransformToFitBounds( 278 aura::RootWindow* root_window, 279 const gfx::Rect& target_bounds) { 280 fit_bounds_ = target_bounds; 281 const gfx::Rect bounds = window_->GetBoundsInScreen(); 282 float scale = std::min(1.0f, 283 std::min(static_cast<float>(target_bounds.width()) / bounds.width(), 284 static_cast<float>(target_bounds.height()) / bounds.height())); 285 gfx::Transform transform; 286 gfx::Vector2d offset( 287 0.5 * (target_bounds.width() - scale * bounds.width()), 288 0.5 * (target_bounds.height() - scale * bounds.height())); 289 transform.Translate(target_bounds.x() - bounds.x() + offset.x(), 290 target_bounds.y() - bounds.y() + offset.y()); 291 transform.Scale(scale, scale); 292 if (root_window != window_->GetRootWindow()) { 293 if (!window_copy_) { 294 DCHECK(!layer_); 295 layer_ = views::corewm::RecreateWindowLayers(window_, true); 296 window_copy_ = CreateCopyOfWindow(root_window, window_, layer_); 297 } 298 WindowSelectorAnimationSettings animation_settings( 299 window_copy_->GetNativeWindow()); 300 window_copy_->GetNativeWindow()->SetTransform(transform); 301 } 302 WindowSelectorAnimationSettings animation_settings(window_); 303 window_->SetTransform(transform); 304 } 305 306 // A comparator for locating a given target window. 307 struct WindowSelectorWindowComparator 308 : public std::unary_function<WindowSelectorWindow*, bool> { 309 explicit WindowSelectorWindowComparator(const aura::Window* target_window) 310 : target(target_window) { 311 } 312 313 bool operator()(const WindowSelectorWindow* window) const { 314 return target == window->window(); 315 } 316 317 const aura::Window* target; 318 }; 319 320 WindowSelector::WindowSelector(const WindowList& windows, 321 WindowSelector::Mode mode, 322 WindowSelectorDelegate* delegate) 323 : mode_(mode), 324 delegate_(delegate), 325 selected_window_(0), 326 selection_root_(NULL) { 327 DCHECK(delegate_); 328 for (size_t i = 0; i < windows.size(); ++i) { 329 windows[i]->AddObserver(this); 330 windows_.push_back(new WindowSelectorWindow(windows[i])); 331 } 332 if (mode == WindowSelector::CYCLE) 333 selection_root_ = ash::Shell::GetActiveRootWindow(); 334 PositionWindows(); 335 ash::Shell::GetInstance()->AddPreTargetHandler(this); 336 } 337 338 WindowSelector::~WindowSelector() { 339 for (size_t i = 0; i < windows_.size(); i++) { 340 windows_[i]->window()->RemoveObserver(this); 341 } 342 ash::Shell::GetInstance()->RemovePreTargetHandler(this); 343 } 344 345 void WindowSelector::Step(WindowSelector::Direction direction) { 346 DCHECK(windows_.size() > 0); 347 if (!selection_widget_) 348 InitializeSelectionWidget(); 349 selected_window_ = (selected_window_ + windows_.size() + 350 (direction == WindowSelector::FORWARD ? 1 : -1)) % windows_.size(); 351 UpdateSelectionLocation(true); 352 } 353 354 void WindowSelector::SelectWindow() { 355 delegate_->OnWindowSelected(windows_[selected_window_]->window()); 356 } 357 358 void WindowSelector::OnEvent(ui::Event* event) { 359 // If the event is targetted at any of the windows in the overview, then 360 // prevent it from propagating. 361 aura::Window* target = static_cast<aura::Window*>(event->target()); 362 for (size_t i = 0; i < windows_.size(); ++i) { 363 if (windows_[i]->Contains(target)) { 364 event->StopPropagation(); 365 break; 366 } 367 } 368 369 // This object may not be valid after this call as a selection event can 370 // trigger deletion of the window selector. 371 ui::EventHandler::OnEvent(event); 372 } 373 374 void WindowSelector::OnMouseEvent(ui::MouseEvent* event) { 375 if (event->type() != ui::ET_MOUSE_RELEASED) 376 return; 377 WindowSelectorWindow* target = GetEventTarget(event); 378 if (!target) 379 return; 380 381 HandleSelectionEvent(target); 382 } 383 384 void WindowSelector::OnGestureEvent(ui::GestureEvent* event) { 385 if (event->type() != ui::ET_GESTURE_TAP) 386 return; 387 WindowSelectorWindow* target = GetEventTarget(event); 388 if (!target) 389 return; 390 391 HandleSelectionEvent(target); 392 } 393 394 void WindowSelector::OnWindowDestroyed(aura::Window* window) { 395 ScopedVector<WindowSelectorWindow>::iterator iter = 396 std::find_if(windows_.begin(), windows_.end(), 397 WindowSelectorWindowComparator(window)); 398 DCHECK(iter != windows_.end()); 399 size_t deleted_index = iter - windows_.begin(); 400 (*iter)->OnWindowDestroyed(); 401 windows_.erase(iter); 402 if (windows_.empty()) { 403 delegate_->OnSelectionCanceled(); 404 return; 405 } 406 if (selected_window_ >= deleted_index) { 407 if (selected_window_ > deleted_index) 408 selected_window_--; 409 selected_window_ = selected_window_ % windows_.size(); 410 UpdateSelectionLocation(true); 411 } 412 413 PositionWindows(); 414 } 415 416 WindowSelectorWindow* WindowSelector::GetEventTarget(ui::LocatedEvent* event) { 417 aura::Window* target = static_cast<aura::Window*>(event->target()); 418 // If the target window doesn't actually contain the event location (i.e. 419 // mouse down over the window and mouse up elsewhere) then do not select the 420 // window. 421 if (!target->HitTest(event->location())) 422 return NULL; 423 424 for (size_t i = 0; i < windows_.size(); i++) { 425 if (windows_[i]->Contains(target)) 426 return windows_[i]; 427 } 428 return NULL; 429 } 430 431 void WindowSelector::HandleSelectionEvent(WindowSelectorWindow* target) { 432 // The selected window should not be minimized when window selection is 433 // ended. 434 target->RestoreWindowOnExit(); 435 delegate_->OnWindowSelected(target->window()); 436 } 437 438 void WindowSelector::PositionWindows() { 439 if (selection_root_) { 440 DCHECK_EQ(mode_, CYCLE); 441 std::vector<WindowSelectorWindow*> windows; 442 for (size_t i = 0; i < windows_.size(); ++i) 443 windows.push_back(windows_[i]); 444 PositionWindowsOnRoot(selection_root_, windows); 445 } else { 446 DCHECK_EQ(mode_, OVERVIEW); 447 Shell::RootWindowList root_window_list = Shell::GetAllRootWindows(); 448 for (size_t i = 0; i < root_window_list.size(); ++i) 449 PositionWindowsFromRoot(root_window_list[i]); 450 } 451 } 452 453 void WindowSelector::PositionWindowsFromRoot(aura::RootWindow* root_window) { 454 std::vector<WindowSelectorWindow*> windows; 455 for (size_t i = 0; i < windows_.size(); ++i) { 456 if (windows_[i]->window()->GetRootWindow() == root_window) 457 windows.push_back(windows_[i]); 458 } 459 PositionWindowsOnRoot(root_window, windows); 460 } 461 462 void WindowSelector::PositionWindowsOnRoot( 463 aura::RootWindow* root_window, 464 const std::vector<WindowSelectorWindow*>& windows) { 465 if (windows.empty()) 466 return; 467 468 gfx::Size window_size; 469 gfx::Rect total_bounds = ScreenAsh::ConvertRectToScreen(root_window, 470 ScreenAsh::GetDisplayWorkAreaBoundsInParent( 471 Shell::GetContainer(root_window, 472 internal::kShellWindowId_DefaultContainer))); 473 474 // Find the minimum number of windows per row that will fit all of the 475 // windows on screen. 476 size_t columns = std::max( 477 total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1, 478 static_cast<int>(ceil(sqrt(total_bounds.width() * windows.size() / 479 (kCardAspectRatio * total_bounds.height()))))); 480 size_t rows = ((windows.size() + columns - 1) / columns); 481 window_size.set_width(std::min( 482 static_cast<int>(total_bounds.width() / columns), 483 static_cast<int>(total_bounds.height() * kCardAspectRatio / rows))); 484 window_size.set_height(window_size.width() / kCardAspectRatio); 485 486 // Calculate the X and Y offsets necessary to center the grid. 487 int x_offset = total_bounds.x() + ((windows.size() >= columns ? 0 : 488 (columns - windows.size()) * window_size.width()) + 489 (total_bounds.width() - columns * window_size.width())) / 2; 490 int y_offset = total_bounds.y() + (total_bounds.height() - 491 rows * window_size.height()) / 2; 492 for (size_t i = 0; i < windows.size(); ++i) { 493 gfx::Transform transform; 494 int column = i % columns; 495 int row = i / columns; 496 gfx::Rect target_bounds(window_size.width() * column + x_offset, 497 window_size.height() * row + y_offset, 498 window_size.width(), 499 window_size.height()); 500 target_bounds.Inset(kWindowMargin, kWindowMargin); 501 windows[i]->TransformToFitBounds(root_window, target_bounds); 502 } 503 } 504 505 void WindowSelector::InitializeSelectionWidget() { 506 selection_widget_.reset(new views::Widget); 507 views::Widget::InitParams params; 508 params.type = views::Widget::InitParams::TYPE_POPUP; 509 params.can_activate = false; 510 params.keep_on_top = false; 511 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 512 params.opacity = views::Widget::InitParams::OPAQUE_WINDOW; 513 params.parent = Shell::GetContainer( 514 selection_root_, 515 internal::kShellWindowId_DefaultContainer); 516 params.accept_events = false; 517 selection_widget_->set_focus_on_creation(false); 518 selection_widget_->Init(params); 519 views::View* content_view = new views::View; 520 content_view->set_background( 521 views::Background::CreateSolidBackground(kWindowSelectorSelectionColor)); 522 selection_widget_->SetContentsView(content_view); 523 UpdateSelectionLocation(false); 524 selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom( 525 selection_widget_->GetNativeWindow()); 526 selection_widget_->Show(); 527 selection_widget_->GetNativeWindow()->layer()->SetOpacity( 528 kWindowSelectorSelectionOpacity); 529 } 530 531 void WindowSelector::UpdateSelectionLocation(bool animate) { 532 if (!selection_widget_) 533 return; 534 gfx::Rect target_bounds = windows_[selected_window_]->bounds(); 535 target_bounds.Inset(-kWindowSelectorSelectionPadding, 536 -kWindowSelectorSelectionPadding); 537 if (animate) { 538 WindowSelectorAnimationSettings animation_settings( 539 selection_widget_->GetNativeWindow()); 540 selection_widget_->SetBounds(target_bounds); 541 } else { 542 selection_widget_->SetBounds(target_bounds); 543 } 544 } 545 546 } // namespace ash 547