| OLD | NEW |
| (Empty) |
| 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/overview/window_overview.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "ash/accessibility_delegate.h" | |
| 10 #include "ash/metrics/user_metrics_recorder.h" | |
| 11 #include "ash/screen_util.h" | |
| 12 #include "ash/shell.h" | |
| 13 #include "ash/shell_window_ids.h" | |
| 14 #include "ash/switchable_windows.h" | |
| 15 #include "ash/wm/overview/scoped_transform_overview_window.h" | |
| 16 #include "ash/wm/overview/window_selector.h" | |
| 17 #include "ash/wm/overview/window_selector_item.h" | |
| 18 #include "base/metrics/histogram.h" | |
| 19 #include "third_party/skia/include/core/SkColor.h" | |
| 20 #include "ui/aura/client/cursor_client.h" | |
| 21 #include "ui/aura/window.h" | |
| 22 #include "ui/aura/window_event_dispatcher.h" | |
| 23 #include "ui/compositor/layer_animation_observer.h" | |
| 24 #include "ui/compositor/scoped_layer_animation_settings.h" | |
| 25 #include "ui/events/event.h" | |
| 26 #include "ui/gfx/screen.h" | |
| 27 #include "ui/views/background.h" | |
| 28 #include "ui/views/widget/widget.h" | |
| 29 | |
| 30 namespace ash { | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 // Conceptually the window overview is a table or grid of cells having this | |
| 35 // fixed aspect ratio. The number of columns is determined by maximizing the | |
| 36 // area of them based on the number of windows. | |
| 37 const float kCardAspectRatio = 4.0f / 3.0f; | |
| 38 | |
| 39 // In the conceptual overview table, the window margin is the space reserved | |
| 40 // around the window within the cell. This margin does not overlap so the | |
| 41 // closest distance between adjacent windows will be twice this amount. | |
| 42 const int kWindowMargin = 30; | |
| 43 | |
| 44 // The minimum number of cards along the major axis (i.e. horizontally on a | |
| 45 // landscape orientation). | |
| 46 const int kMinCardsMajor = 3; | |
| 47 | |
| 48 // The duration of transition animations on the overview selector. | |
| 49 const int kOverviewSelectorTransitionMilliseconds = 100; | |
| 50 | |
| 51 // The color and opacity of the overview selector. | |
| 52 const SkColor kWindowOverviewSelectionColor = SK_ColorBLACK; | |
| 53 const float kWindowOverviewSelectionOpacity = 0.5f; | |
| 54 | |
| 55 // The padding or amount of the window selector widget visible around the edges | |
| 56 // of the currently selected window. | |
| 57 const int kWindowOverviewSelectionPadding = 25; | |
| 58 | |
| 59 // A comparator for locating a given target window. | |
| 60 struct WindowSelectorItemComparator | |
| 61 : public std::unary_function<WindowSelectorItem*, bool> { | |
| 62 explicit WindowSelectorItemComparator(const aura::Window* target_window) | |
| 63 : target(target_window) { | |
| 64 } | |
| 65 | |
| 66 bool operator()(WindowSelectorItem* window) const { | |
| 67 return window->TargetedWindow(target) != NULL; | |
| 68 } | |
| 69 | |
| 70 const aura::Window* target; | |
| 71 }; | |
| 72 | |
| 73 // An observer which holds onto the passed widget until the animation is | |
| 74 // complete. | |
| 75 class CleanupWidgetAfterAnimationObserver : public ui::LayerAnimationObserver { | |
| 76 public: | |
| 77 explicit CleanupWidgetAfterAnimationObserver( | |
| 78 scoped_ptr<views::Widget> widget); | |
| 79 | |
| 80 // ui::LayerAnimationObserver: | |
| 81 virtual void OnLayerAnimationEnded( | |
| 82 ui::LayerAnimationSequence* sequence) OVERRIDE; | |
| 83 virtual void OnLayerAnimationAborted( | |
| 84 ui::LayerAnimationSequence* sequence) OVERRIDE; | |
| 85 virtual void OnLayerAnimationScheduled( | |
| 86 ui::LayerAnimationSequence* sequence) OVERRIDE; | |
| 87 | |
| 88 private: | |
| 89 virtual ~CleanupWidgetAfterAnimationObserver(); | |
| 90 | |
| 91 scoped_ptr<views::Widget> widget_; | |
| 92 | |
| 93 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver); | |
| 94 }; | |
| 95 | |
| 96 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver( | |
| 97 scoped_ptr<views::Widget> widget) | |
| 98 : widget_(widget.Pass()) { | |
| 99 widget_->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this); | |
| 100 } | |
| 101 | |
| 102 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() { | |
| 103 widget_->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this); | |
| 104 } | |
| 105 | |
| 106 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded( | |
| 107 ui::LayerAnimationSequence* sequence) { | |
| 108 delete this; | |
| 109 } | |
| 110 | |
| 111 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted( | |
| 112 ui::LayerAnimationSequence* sequence) { | |
| 113 delete this; | |
| 114 } | |
| 115 | |
| 116 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled( | |
| 117 ui::LayerAnimationSequence* sequence) { | |
| 118 } | |
| 119 | |
| 120 } // namespace | |
| 121 | |
| 122 WindowOverview::WindowOverview(WindowSelector* window_selector, | |
| 123 WindowSelectorItemList* windows) | |
| 124 : window_selector_(window_selector), | |
| 125 windows_(windows), | |
| 126 selection_index_(0), | |
| 127 overview_start_time_(base::Time::Now()), | |
| 128 cursor_client_(NULL) { | |
| 129 Shell* shell = Shell::GetInstance(); | |
| 130 shell->OnOverviewModeStarting(); | |
| 131 for (WindowSelectorItemList::iterator iter = windows_->begin(); | |
| 132 iter != windows_->end(); ++iter) { | |
| 133 (*iter)->PrepareForOverview(); | |
| 134 } | |
| 135 PositionWindows(/* animate */ true); | |
| 136 DCHECK(!windows_->empty()); | |
| 137 cursor_client_ = aura::client::GetCursorClient( | |
| 138 windows_->front()->GetRootWindow()); | |
| 139 if (cursor_client_) { | |
| 140 cursor_client_->SetCursor(ui::kCursorPointer); | |
| 141 cursor_client_->ShowCursor(); | |
| 142 // TODO(flackr): Only prevent cursor changes for windows in the overview. | |
| 143 // This will be easier to do without exposing the overview mode code if the | |
| 144 // cursor changes are moved to ToplevelWindowEventHandler::HandleMouseMoved | |
| 145 // as suggested there. | |
| 146 cursor_client_->LockCursor(); | |
| 147 } | |
| 148 shell->PrependPreTargetHandler(this); | |
| 149 shell->GetScreen()->AddObserver(this); | |
| 150 shell->metrics()->RecordUserMetricsAction(UMA_WINDOW_OVERVIEW); | |
| 151 HideAndTrackNonOverviewWindows(); | |
| 152 // Send an a11y alert. | |
| 153 shell->accessibility_delegate()->TriggerAccessibilityAlert( | |
| 154 A11Y_ALERT_WINDOW_OVERVIEW_MODE_ENTERED); | |
| 155 } | |
| 156 | |
| 157 WindowOverview::~WindowOverview() { | |
| 158 const aura::WindowTracker::Windows hidden_windows = hidden_windows_.windows(); | |
| 159 for (aura::WindowTracker::Windows::const_iterator iter = | |
| 160 hidden_windows.begin(); iter != hidden_windows.end(); ++iter) { | |
| 161 ui::ScopedLayerAnimationSettings settings( | |
| 162 (*iter)->layer()->GetAnimator()); | |
| 163 settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( | |
| 164 ScopedTransformOverviewWindow::kTransitionMilliseconds)); | |
| 165 settings.SetPreemptionStrategy( | |
| 166 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 167 (*iter)->layer()->SetOpacity(1); | |
| 168 (*iter)->Show(); | |
| 169 } | |
| 170 if (cursor_client_) | |
| 171 cursor_client_->UnlockCursor(); | |
| 172 ash::Shell* shell = ash::Shell::GetInstance(); | |
| 173 shell->RemovePreTargetHandler(this); | |
| 174 shell->GetScreen()->RemoveObserver(this); | |
| 175 UMA_HISTOGRAM_MEDIUM_TIMES( | |
| 176 "Ash.WindowSelector.TimeInOverview", | |
| 177 base::Time::Now() - overview_start_time_); | |
| 178 shell->OnOverviewModeEnding(); | |
| 179 } | |
| 180 | |
| 181 void WindowOverview::SetSelection(size_t index) { | |
| 182 gfx::Rect target_bounds(GetSelectionBounds(index)); | |
| 183 | |
| 184 if (selection_widget_) { | |
| 185 // If the selection widget is already active, determine the animation to | |
| 186 // use to animate the widget to the new bounds. | |
| 187 int change = static_cast<int>(index) - static_cast<int>(selection_index_); | |
| 188 int windows = static_cast<int>(windows_->size()); | |
| 189 // If moving from the first to the last or last to the first index, | |
| 190 // convert the delta to be +/- 1. | |
| 191 if (windows > 2 && abs(change) == windows - 1) { | |
| 192 if (change < 0) | |
| 193 change += windows; | |
| 194 else | |
| 195 change -= windows; | |
| 196 } | |
| 197 if (selection_index_ < windows_->size() && | |
| 198 (*windows_)[selection_index_]->target_bounds().y() != | |
| 199 (*windows_)[index]->target_bounds().y() && | |
| 200 abs(change) == 1) { | |
| 201 // The selection has changed forward or backwards by one with a change | |
| 202 // in the height of the target. In this case create a new selection widget | |
| 203 // to fade in on the new position and animate and fade out the old one. | |
| 204 gfx::Display dst_display = gfx::Screen::GetScreenFor( | |
| 205 selection_widget_->GetNativeWindow())->GetDisplayMatching( | |
| 206 target_bounds); | |
| 207 gfx::Vector2d fade_out_direction( | |
| 208 change * ((*windows_)[selection_index_]->target_bounds().width() + | |
| 209 2 * kWindowMargin), 0); | |
| 210 aura::Window* old_selection = selection_widget_->GetNativeWindow(); | |
| 211 | |
| 212 // CleanupWidgetAfterAnimationObserver will delete itself (and the | |
| 213 // widget) when the animation is complete. | |
| 214 new CleanupWidgetAfterAnimationObserver(selection_widget_.Pass()); | |
| 215 ui::ScopedLayerAnimationSettings animation_settings( | |
| 216 old_selection->layer()->GetAnimator()); | |
| 217 animation_settings.SetTransitionDuration( | |
| 218 base::TimeDelta::FromMilliseconds( | |
| 219 kOverviewSelectorTransitionMilliseconds)); | |
| 220 animation_settings.SetPreemptionStrategy( | |
| 221 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 222 old_selection->SetBoundsInScreen( | |
| 223 GetSelectionBounds(selection_index_) + fade_out_direction, | |
| 224 dst_display); | |
| 225 old_selection->Hide(); | |
| 226 old_selection->layer()->SetOpacity(0); | |
| 227 InitializeSelectionWidget(); | |
| 228 selection_widget_->GetNativeWindow()->SetBoundsInScreen( | |
| 229 target_bounds - fade_out_direction, dst_display); | |
| 230 // New selection widget starts with 0 opacity and fades in. | |
| 231 selection_widget_->GetNativeWindow()->layer()->SetOpacity(0); | |
| 232 } | |
| 233 ui::ScopedLayerAnimationSettings animation_settings( | |
| 234 selection_widget_->GetNativeWindow()->layer()->GetAnimator()); | |
| 235 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( | |
| 236 kOverviewSelectorTransitionMilliseconds)); | |
| 237 animation_settings.SetPreemptionStrategy( | |
| 238 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 239 selection_widget_->SetBounds(target_bounds); | |
| 240 selection_widget_->GetNativeWindow()->layer()->SetOpacity( | |
| 241 kWindowOverviewSelectionOpacity); | |
| 242 } else { | |
| 243 InitializeSelectionWidget(); | |
| 244 selection_widget_->SetBounds(target_bounds); | |
| 245 } | |
| 246 selection_index_ = index; | |
| 247 } | |
| 248 | |
| 249 void WindowOverview::OnWindowsChanged() { | |
| 250 PositionWindows(/* animate */ true); | |
| 251 } | |
| 252 | |
| 253 void WindowOverview::OnKeyEvent(ui::KeyEvent* event) { | |
| 254 if (GetTargetedWindow(static_cast<aura::Window*>(event->target()))) | |
| 255 event->StopPropagation(); | |
| 256 if (event->type() != ui::ET_KEY_PRESSED) | |
| 257 return; | |
| 258 | |
| 259 if (event->key_code() == ui::VKEY_ESCAPE) | |
| 260 window_selector_->CancelSelection(); | |
| 261 } | |
| 262 | |
| 263 void WindowOverview::OnMouseEvent(ui::MouseEvent* event) { | |
| 264 aura::Window* target = GetEventTarget(event); | |
| 265 if (!target) | |
| 266 return; | |
| 267 | |
| 268 event->SetHandled(); | |
| 269 if (event->type() != ui::ET_MOUSE_RELEASED) | |
| 270 return; | |
| 271 | |
| 272 window_selector_->SelectWindow(target); | |
| 273 } | |
| 274 | |
| 275 void WindowOverview::OnScrollEvent(ui::ScrollEvent* event) { | |
| 276 // Set the handled flag to prevent delivering scroll events to the window but | |
| 277 // still allowing other pretarget handlers to process the scroll event. | |
| 278 if (GetTargetedWindow(static_cast<aura::Window*>(event->target()))) | |
| 279 event->SetHandled(); | |
| 280 } | |
| 281 | |
| 282 void WindowOverview::OnTouchEvent(ui::TouchEvent* event) { | |
| 283 // Existing touches should be allowed to continue. This prevents getting | |
| 284 // stuck in a gesture or with pressed fingers being tracked elsewhere. | |
| 285 if (event->type() != ui::ET_TOUCH_PRESSED) | |
| 286 return; | |
| 287 | |
| 288 aura::Window* target = GetEventTarget(event); | |
| 289 if (!target) | |
| 290 return; | |
| 291 | |
| 292 // TODO(flackr): StopPropogation prevents generation of gesture events. | |
| 293 // We should find a better way to prevent events from being delivered to | |
| 294 // the window, perhaps a transparent window in front of the target window | |
| 295 // or using EventClientImpl::CanProcessEventsWithinSubtree and then a tap | |
| 296 // gesture could be used to activate the window. | |
| 297 event->SetHandled(); | |
| 298 window_selector_->SelectWindow(target); | |
| 299 } | |
| 300 | |
| 301 void WindowOverview::OnDisplayBoundsChanged(const gfx::Display& display) { | |
| 302 PositionWindows(/* animate */ false); | |
| 303 } | |
| 304 | |
| 305 void WindowOverview::OnDisplayAdded(const gfx::Display& display) { | |
| 306 } | |
| 307 | |
| 308 void WindowOverview::OnDisplayRemoved(const gfx::Display& display) { | |
| 309 } | |
| 310 | |
| 311 aura::Window* WindowOverview::GetEventTarget(ui::LocatedEvent* event) { | |
| 312 aura::Window* target = static_cast<aura::Window*>(event->target()); | |
| 313 // If the target window doesn't actually contain the event location (i.e. | |
| 314 // mouse down over the window and mouse up elsewhere) then do not select the | |
| 315 // window. | |
| 316 if (!target->ContainsPoint(event->location())) | |
| 317 return NULL; | |
| 318 | |
| 319 return GetTargetedWindow(target); | |
| 320 } | |
| 321 | |
| 322 aura::Window* WindowOverview::GetTargetedWindow(aura::Window* window) { | |
| 323 for (WindowSelectorItemList::iterator iter = windows_->begin(); | |
| 324 iter != windows_->end(); ++iter) { | |
| 325 aura::Window* selected = (*iter)->TargetedWindow(window); | |
| 326 if (selected) | |
| 327 return selected; | |
| 328 } | |
| 329 return NULL; | |
| 330 } | |
| 331 | |
| 332 void WindowOverview::HideAndTrackNonOverviewWindows() { | |
| 333 // Add the windows to hidden_windows first so that if any are destroyed | |
| 334 // while hiding them they are tracked. | |
| 335 aura::Window::Windows root_windows = Shell::GetAllRootWindows(); | |
| 336 for (aura::Window::Windows::const_iterator root_iter = root_windows.begin(); | |
| 337 root_iter != root_windows.end(); ++root_iter) { | |
| 338 for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) { | |
| 339 aura::Window* container = Shell::GetContainer(*root_iter, | |
| 340 kSwitchableWindowContainerIds[i]); | |
| 341 for (aura::Window::Windows::const_iterator iter = | |
| 342 container->children().begin(); iter != container->children().end(); | |
| 343 ++iter) { | |
| 344 if (GetTargetedWindow(*iter) || !(*iter)->IsVisible()) | |
| 345 continue; | |
| 346 hidden_windows_.Add(*iter); | |
| 347 } | |
| 348 } | |
| 349 } | |
| 350 | |
| 351 // Copy the window list as it can change during iteration. | |
| 352 const aura::WindowTracker::Windows hidden_windows(hidden_windows_.windows()); | |
| 353 for (aura::WindowTracker::Windows::const_iterator iter = | |
| 354 hidden_windows.begin(); iter != hidden_windows.end(); ++iter) { | |
| 355 if (!hidden_windows_.Contains(*iter)) | |
| 356 continue; | |
| 357 ui::ScopedLayerAnimationSettings settings( | |
| 358 (*iter)->layer()->GetAnimator()); | |
| 359 settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( | |
| 360 ScopedTransformOverviewWindow::kTransitionMilliseconds)); | |
| 361 settings.SetPreemptionStrategy( | |
| 362 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 363 (*iter)->Hide(); | |
| 364 // Hiding the window can result in it being destroyed. | |
| 365 if (!hidden_windows_.Contains(*iter)) | |
| 366 continue; | |
| 367 (*iter)->layer()->SetOpacity(0); | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 void WindowOverview::PositionWindows(bool animate) { | |
| 372 aura::Window::Windows root_window_list = Shell::GetAllRootWindows(); | |
| 373 for (size_t i = 0; i < root_window_list.size(); ++i) | |
| 374 PositionWindowsFromRoot(root_window_list[i], animate); | |
| 375 } | |
| 376 | |
| 377 void WindowOverview::PositionWindowsFromRoot(aura::Window* root_window, | |
| 378 bool animate) { | |
| 379 std::vector<WindowSelectorItem*> windows; | |
| 380 for (WindowSelectorItemList::iterator iter = windows_->begin(); | |
| 381 iter != windows_->end(); ++iter) { | |
| 382 if ((*iter)->GetRootWindow() == root_window) | |
| 383 windows.push_back(*iter); | |
| 384 } | |
| 385 | |
| 386 if (windows.empty()) | |
| 387 return; | |
| 388 | |
| 389 gfx::Size window_size; | |
| 390 gfx::Rect total_bounds = ScreenUtil::ConvertRectToScreen( | |
| 391 root_window, | |
| 392 ScreenUtil::GetDisplayWorkAreaBoundsInParent( | |
| 393 Shell::GetContainer(root_window, kShellWindowId_DefaultContainer))); | |
| 394 | |
| 395 // Find the minimum number of windows per row that will fit all of the | |
| 396 // windows on screen. | |
| 397 size_t columns = std::max( | |
| 398 total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1, | |
| 399 static_cast<int>(ceil(sqrt(total_bounds.width() * windows.size() / | |
| 400 (kCardAspectRatio * total_bounds.height()))))); | |
| 401 size_t rows = ((windows.size() + columns - 1) / columns); | |
| 402 window_size.set_width(std::min( | |
| 403 static_cast<int>(total_bounds.width() / columns), | |
| 404 static_cast<int>(total_bounds.height() * kCardAspectRatio / rows))); | |
| 405 window_size.set_height(window_size.width() / kCardAspectRatio); | |
| 406 | |
| 407 // Calculate the X and Y offsets necessary to center the grid. | |
| 408 int x_offset = total_bounds.x() + ((windows.size() >= columns ? 0 : | |
| 409 (columns - windows.size()) * window_size.width()) + | |
| 410 (total_bounds.width() - columns * window_size.width())) / 2; | |
| 411 int y_offset = total_bounds.y() + (total_bounds.height() - | |
| 412 rows * window_size.height()) / 2; | |
| 413 for (size_t i = 0; i < windows.size(); ++i) { | |
| 414 gfx::Transform transform; | |
| 415 int column = i % columns; | |
| 416 int row = i / columns; | |
| 417 gfx::Rect target_bounds(window_size.width() * column + x_offset, | |
| 418 window_size.height() * row + y_offset, | |
| 419 window_size.width(), | |
| 420 window_size.height()); | |
| 421 target_bounds.Inset(kWindowMargin, kWindowMargin); | |
| 422 windows[i]->SetBounds(root_window, target_bounds, animate); | |
| 423 } | |
| 424 } | |
| 425 | |
| 426 void WindowOverview::InitializeSelectionWidget() { | |
| 427 selection_widget_.reset(new views::Widget); | |
| 428 views::Widget::InitParams params; | |
| 429 params.type = views::Widget::InitParams::TYPE_POPUP; | |
| 430 params.can_activate = false; | |
| 431 params.keep_on_top = false; | |
| 432 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
| 433 params.opacity = views::Widget::InitParams::OPAQUE_WINDOW; | |
| 434 params.parent = Shell::GetContainer(windows_->front()->GetRootWindow(), | |
| 435 kShellWindowId_DefaultContainer); | |
| 436 params.accept_events = false; | |
| 437 selection_widget_->set_focus_on_creation(false); | |
| 438 selection_widget_->Init(params); | |
| 439 views::View* content_view = new views::View; | |
| 440 content_view->set_background( | |
| 441 views::Background::CreateSolidBackground(kWindowOverviewSelectionColor)); | |
| 442 selection_widget_->SetContentsView(content_view); | |
| 443 selection_widget_->Show(); | |
| 444 selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom( | |
| 445 selection_widget_->GetNativeWindow()); | |
| 446 selection_widget_->GetNativeWindow()->layer()->SetOpacity( | |
| 447 kWindowOverviewSelectionOpacity); | |
| 448 } | |
| 449 | |
| 450 gfx::Rect WindowOverview::GetSelectionBounds(size_t index) { | |
| 451 gfx::Rect bounds((*windows_)[index]->bounds()); | |
| 452 bounds.Inset(-kWindowOverviewSelectionPadding, | |
| 453 -kWindowOverviewSelectionPadding); | |
| 454 return bounds; | |
| 455 } | |
| 456 | |
| 457 } // namespace ash | |
| OLD | NEW |