1// Copyright (c) 2012 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/app_list_controller.h" 6 7#include "ash/ash_switches.h" 8#include "ash/root_window_controller.h" 9#include "ash/screen_util.h" 10#include "ash/shelf/shelf.h" 11#include "ash/shelf/shelf_layout_manager.h" 12#include "ash/shell.h" 13#include "ash/shell_delegate.h" 14#include "ash/shell_window_ids.h" 15#include "base/command_line.h" 16#include "ui/app_list/app_list_constants.h" 17#include "ui/app_list/app_list_switches.h" 18#include "ui/app_list/pagination_model.h" 19#include "ui/app_list/views/app_list_view.h" 20#include "ui/aura/client/focus_client.h" 21#include "ui/aura/window.h" 22#include "ui/aura/window_event_dispatcher.h" 23#include "ui/compositor/layer.h" 24#include "ui/compositor/scoped_layer_animation_settings.h" 25#include "ui/events/event.h" 26#include "ui/gfx/transform_util.h" 27#include "ui/keyboard/keyboard_controller.h" 28#include "ui/views/widget/widget.h" 29 30namespace ash { 31namespace { 32 33// Duration for show/hide animation in milliseconds. 34const int kAnimationDurationMs = 200; 35 36// Offset in pixels to animation away/towards the shelf. 37const int kAnimationOffset = 8; 38 39// The maximum shift in pixels when over-scroll happens. 40const int kMaxOverScrollShift = 48; 41 42// The minimal anchor position offset to make sure that the bubble is still on 43// the screen with 8 pixels spacing on the left / right. This constant is a 44// result of minimal bubble arrow sizes and offsets. 45const int kMinimalAnchorPositionOffset = 57; 46 47// The minimal margin (in pixels) around the app list when in centered mode. 48const int kMinimalCenteredAppListMargin = 10; 49 50ui::Layer* GetLayer(views::Widget* widget) { 51 return widget->GetNativeView()->layer(); 52} 53 54// Gets arrow location based on shelf alignment. 55views::BubbleBorder::Arrow GetBubbleArrow(aura::Window* window) { 56 DCHECK(Shell::HasInstance()); 57 return ShelfLayoutManager::ForShelf(window)-> 58 SelectValueForShelfAlignment( 59 views::BubbleBorder::BOTTOM_CENTER, 60 views::BubbleBorder::LEFT_CENTER, 61 views::BubbleBorder::RIGHT_CENTER, 62 views::BubbleBorder::TOP_CENTER); 63} 64 65// Offset given |rect| towards shelf. 66gfx::Rect OffsetTowardsShelf(const gfx::Rect& rect, views::Widget* widget) { 67 DCHECK(Shell::HasInstance()); 68 ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment( 69 widget->GetNativeView()->GetRootWindow()); 70 gfx::Rect offseted(rect); 71 switch (shelf_alignment) { 72 case SHELF_ALIGNMENT_BOTTOM: 73 offseted.Offset(0, kAnimationOffset); 74 break; 75 case SHELF_ALIGNMENT_LEFT: 76 offseted.Offset(-kAnimationOffset, 0); 77 break; 78 case SHELF_ALIGNMENT_RIGHT: 79 offseted.Offset(kAnimationOffset, 0); 80 break; 81 case SHELF_ALIGNMENT_TOP: 82 offseted.Offset(0, -kAnimationOffset); 83 break; 84 } 85 86 return offseted; 87} 88 89// Using |button_bounds|, determine the anchor offset so that the bubble gets 90// shown above the shelf (used for the alternate shelf theme). 91gfx::Vector2d GetAnchorPositionOffsetToShelf( 92 const gfx::Rect& button_bounds, views::Widget* widget) { 93 DCHECK(Shell::HasInstance()); 94 ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment( 95 widget->GetNativeView()->GetRootWindow()); 96 gfx::Point anchor(button_bounds.CenterPoint()); 97 switch (shelf_alignment) { 98 case SHELF_ALIGNMENT_TOP: 99 case SHELF_ALIGNMENT_BOTTOM: 100 if (base::i18n::IsRTL()) { 101 int screen_width = widget->GetWorkAreaBoundsInScreen().width(); 102 return gfx::Vector2d( 103 std::min(screen_width - kMinimalAnchorPositionOffset - anchor.x(), 104 0), 0); 105 } 106 return gfx::Vector2d( 107 std::max(kMinimalAnchorPositionOffset - anchor.x(), 0), 0); 108 case SHELF_ALIGNMENT_LEFT: 109 return gfx::Vector2d( 110 0, std::max(kMinimalAnchorPositionOffset - anchor.y(), 0)); 111 case SHELF_ALIGNMENT_RIGHT: 112 return gfx::Vector2d( 113 0, std::max(kMinimalAnchorPositionOffset - anchor.y(), 0)); 114 default: 115 NOTREACHED(); 116 return gfx::Vector2d(); 117 } 118} 119 120// Gets the point at the center of the display that a particular view is on. 121// This calculation excludes the virtual keyboard area. If the height of the 122// display area is less than |minimum_height|, its bottom will be extended to 123// that height (so that the app list never starts above the top of the screen). 124gfx::Point GetCenterOfDisplayForView(const views::View* view, 125 int minimum_height) { 126 gfx::Rect bounds = Shell::GetScreen()->GetDisplayNearestWindow( 127 view->GetWidget()->GetNativeView()).bounds(); 128 129 // If the virtual keyboard is active, subtract it from the display bounds, so 130 // that the app list is centered in the non-keyboard area of the display. 131 // (Note that work_area excludes the keyboard, but it doesn't get updated 132 // until after this function is called.) 133 keyboard::KeyboardController* keyboard_controller = 134 keyboard::KeyboardController::GetInstance(); 135 if (keyboard_controller && keyboard_controller->keyboard_visible()) 136 bounds.Subtract(keyboard_controller->current_keyboard_bounds()); 137 138 // Apply the |minimum_height|. 139 if (bounds.height() < minimum_height) 140 bounds.set_height(minimum_height); 141 142 return bounds.CenterPoint(); 143} 144 145// Gets the minimum height of the rectangle to center the app list in. 146int GetMinimumBoundsHeightForAppList(const app_list::AppListView* app_list) { 147 return app_list->bounds().height() + 2 * kMinimalCenteredAppListMargin; 148} 149 150} // namespace 151 152//////////////////////////////////////////////////////////////////////////////// 153// AppListController, public: 154 155AppListController::AppListController() 156 : is_visible_(false), 157 is_centered_(false), 158 view_(NULL), 159 current_apps_page_(-1), 160 should_snap_back_(false) { 161 Shell::GetInstance()->AddShellObserver(this); 162} 163 164AppListController::~AppListController() { 165 // Ensures app list view goes before the controller since pagination model 166 // lives in the controller and app list view would access it on destruction. 167 if (view_) { 168 view_->GetAppsPaginationModel()->RemoveObserver(this); 169 if (view_->GetWidget()) 170 view_->GetWidget()->CloseNow(); 171 } 172 173 Shell::GetInstance()->RemoveShellObserver(this); 174} 175 176void AppListController::Show(aura::Window* window) { 177 if (is_visible_) 178 return; 179 180 is_visible_ = true; 181 182 // App list needs to know the new shelf layout in order to calculate its 183 // UI layout when AppListView visibility changes. 184 Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()-> 185 UpdateAutoHideState(); 186 187 if (view_) { 188 ScheduleAnimation(); 189 } else { 190 // Note the AppListViewDelegate outlives the AppListView. For Ash, the view 191 // is destroyed when dismissed. 192 app_list::AppListView* view = new app_list::AppListView( 193 Shell::GetInstance()->delegate()->GetAppListViewDelegate()); 194 aura::Window* root_window = window->GetRootWindow(); 195 aura::Window* container = GetRootWindowController(root_window)-> 196 GetContainer(kShellWindowId_AppListContainer); 197 views::View* applist_button = 198 Shelf::ForWindow(container)->GetAppListButtonView(); 199 is_centered_ = view->ShouldCenterWindow(); 200 if (is_centered_) { 201 // Note: We can't center the app list until we have its dimensions, so we 202 // init at (0, 0) and then reset its anchor point. 203 view->InitAsBubbleAtFixedLocation(container, 204 current_apps_page_, 205 gfx::Point(), 206 views::BubbleBorder::FLOAT, 207 true /* border_accepts_events */); 208 // The experimental app list is centered over the display of the app list 209 // button that was pressed (if triggered via keyboard, this is the display 210 // with the currently focused window). 211 view->SetAnchorPoint(GetCenterOfDisplayForView( 212 applist_button, GetMinimumBoundsHeightForAppList(view))); 213 } else { 214 gfx::Rect applist_button_bounds = applist_button->GetBoundsInScreen(); 215 // We need the location of the button within the local screen. 216 applist_button_bounds = ScreenUtil::ConvertRectFromScreen( 217 root_window, 218 applist_button_bounds); 219 view->InitAsBubbleAttachedToAnchor( 220 container, 221 current_apps_page_, 222 Shelf::ForWindow(container)->GetAppListButtonView(), 223 GetAnchorPositionOffsetToShelf( 224 applist_button_bounds, 225 Shelf::ForWindow(container)->GetAppListButtonView()->GetWidget()), 226 GetBubbleArrow(container), 227 true /* border_accepts_events */); 228 view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE); 229 } 230 SetView(view); 231 // By setting us as DnD recipient, the app list knows that we can 232 // handle items. 233 SetDragAndDropHostOfCurrentAppList( 234 Shelf::ForWindow(window)->GetDragAndDropHostForAppList()); 235 } 236 // Update applist button status when app list visibility is changed. 237 Shelf::ForWindow(window)->GetAppListButtonView()->SchedulePaint(); 238} 239 240void AppListController::Dismiss() { 241 if (!is_visible_) 242 return; 243 244 // If the app list is currently visible, there should be an existing view. 245 DCHECK(view_); 246 247 is_visible_ = false; 248 249 // App list needs to know the new shelf layout in order to calculate its 250 // UI layout when AppListView visibility changes. 251 Shell::GetPrimaryRootWindowController() 252 ->GetShelfLayoutManager() 253 ->UpdateAutoHideState(); 254 255 // Our widget is currently active. When the animation completes we'll hide 256 // the widget, changing activation. If a menu is shown before the animation 257 // completes then the activation change triggers the menu to close. By 258 // deactivating now we ensure there is no activation change when the 259 // animation completes and any menus stay open. 260 view_->GetWidget()->Deactivate(); 261 ScheduleAnimation(); 262 263 // Update applist button status when app list visibility is changed. 264 Shelf::ForWindow(view_->GetWidget()->GetNativeView()) 265 ->GetAppListButtonView() 266 ->SchedulePaint(); 267} 268 269bool AppListController::IsVisible() const { 270 return view_ && view_->GetWidget()->IsVisible(); 271} 272 273aura::Window* AppListController::GetWindow() { 274 return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL; 275} 276 277//////////////////////////////////////////////////////////////////////////////// 278// AppListController, private: 279 280void AppListController::SetDragAndDropHostOfCurrentAppList( 281 app_list::ApplicationDragAndDropHost* drag_and_drop_host) { 282 if (view_ && is_visible_) 283 view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 284} 285 286void AppListController::SetView(app_list::AppListView* view) { 287 DCHECK(view_ == NULL); 288 DCHECK(is_visible_); 289 290 view_ = view; 291 views::Widget* widget = view_->GetWidget(); 292 widget->AddObserver(this); 293 keyboard::KeyboardController* keyboard_controller = 294 keyboard::KeyboardController::GetInstance(); 295 if (keyboard_controller) 296 keyboard_controller->AddObserver(this); 297 Shell::GetInstance()->AddPreTargetHandler(this); 298 Shelf::ForWindow(widget->GetNativeWindow())->AddIconObserver(this); 299 widget->GetNativeView()->GetRootWindow()->AddObserver(this); 300 aura::client::GetFocusClient(widget->GetNativeView())->AddObserver(this); 301 302 view_->GetAppsPaginationModel()->AddObserver(this); 303 304 view_->ShowWhenReady(); 305} 306 307void AppListController::ResetView() { 308 if (!view_) 309 return; 310 311 views::Widget* widget = view_->GetWidget(); 312 widget->RemoveObserver(this); 313 GetLayer(widget)->GetAnimator()->RemoveObserver(this); 314 keyboard::KeyboardController* keyboard_controller = 315 keyboard::KeyboardController::GetInstance(); 316 if (keyboard_controller) 317 keyboard_controller->RemoveObserver(this); 318 Shell::GetInstance()->RemovePreTargetHandler(this); 319 Shelf::ForWindow(widget->GetNativeWindow())->RemoveIconObserver(this); 320 widget->GetNativeView()->GetRootWindow()->RemoveObserver(this); 321 aura::client::GetFocusClient(widget->GetNativeView())->RemoveObserver(this); 322 323 view_->GetAppsPaginationModel()->RemoveObserver(this); 324 325 view_ = NULL; 326} 327 328void AppListController::ScheduleAnimation() { 329 // Stop observing previous animation. 330 StopObservingImplicitAnimations(); 331 332 views::Widget* widget = view_->GetWidget(); 333 ui::Layer* layer = GetLayer(widget); 334 layer->GetAnimator()->StopAnimating(); 335 336 gfx::Rect target_bounds; 337 if (is_visible_) { 338 target_bounds = widget->GetWindowBoundsInScreen(); 339 widget->SetBounds(OffsetTowardsShelf(target_bounds, widget)); 340 } else { 341 target_bounds = OffsetTowardsShelf(widget->GetWindowBoundsInScreen(), 342 widget); 343 } 344 345 ui::ScopedLayerAnimationSettings animation(layer->GetAnimator()); 346 animation.SetTransitionDuration( 347 base::TimeDelta::FromMilliseconds( 348 is_visible_ ? 0 : kAnimationDurationMs)); 349 animation.AddObserver(this); 350 351 layer->SetOpacity(is_visible_ ? 1.0 : 0.0); 352 widget->SetBounds(target_bounds); 353} 354 355void AppListController::ProcessLocatedEvent(ui::LocatedEvent* event) { 356 if (!view_ || !is_visible_) 357 return; 358 359 // If the event happened on a menu, then the event should not close the app 360 // list. 361 aura::Window* target = static_cast<aura::Window*>(event->target()); 362 if (target) { 363 RootWindowController* root_controller = 364 GetRootWindowController(target->GetRootWindow()); 365 if (root_controller) { 366 aura::Window* menu_container = 367 root_controller->GetContainer(kShellWindowId_MenuContainer); 368 if (menu_container->Contains(target)) 369 return; 370 aura::Window* keyboard_container = root_controller->GetContainer( 371 kShellWindowId_VirtualKeyboardContainer); 372 if (keyboard_container->Contains(target)) 373 return; 374 } 375 } 376 377 aura::Window* window = view_->GetWidget()->GetNativeView()->parent(); 378 if (!window->Contains(target)) 379 Dismiss(); 380} 381 382void AppListController::UpdateBounds() { 383 if (!view_ || !is_visible_) 384 return; 385 386 view_->UpdateBounds(); 387 388 if (is_centered_) 389 view_->SetAnchorPoint(GetCenterOfDisplayForView( 390 view_, GetMinimumBoundsHeightForAppList(view_))); 391} 392 393//////////////////////////////////////////////////////////////////////////////// 394// AppListController, aura::EventFilter implementation: 395 396void AppListController::OnMouseEvent(ui::MouseEvent* event) { 397 if (event->type() == ui::ET_MOUSE_PRESSED) 398 ProcessLocatedEvent(event); 399} 400 401void AppListController::OnGestureEvent(ui::GestureEvent* event) { 402 if (event->type() == ui::ET_GESTURE_TAP_DOWN) 403 ProcessLocatedEvent(event); 404} 405 406//////////////////////////////////////////////////////////////////////////////// 407// AppListController, aura::FocusObserver implementation: 408 409void AppListController::OnWindowFocused(aura::Window* gained_focus, 410 aura::Window* lost_focus) { 411 if (view_ && is_visible_) { 412 aura::Window* applist_window = view_->GetWidget()->GetNativeView(); 413 aura::Window* applist_container = applist_window->parent(); 414 415 if (applist_container->Contains(lost_focus) && 416 (!gained_focus || !applist_container->Contains(gained_focus))) { 417 Dismiss(); 418 } 419 } 420} 421 422//////////////////////////////////////////////////////////////////////////////// 423// AppListController, aura::WindowObserver implementation: 424void AppListController::OnWindowBoundsChanged(aura::Window* root, 425 const gfx::Rect& old_bounds, 426 const gfx::Rect& new_bounds) { 427 UpdateBounds(); 428} 429 430//////////////////////////////////////////////////////////////////////////////// 431// AppListController, ui::ImplicitAnimationObserver implementation: 432 433void AppListController::OnImplicitAnimationsCompleted() { 434 if (is_visible_ ) 435 view_->GetWidget()->Activate(); 436 else 437 view_->GetWidget()->Close(); 438} 439 440//////////////////////////////////////////////////////////////////////////////// 441// AppListController, views::WidgetObserver implementation: 442 443void AppListController::OnWidgetDestroying(views::Widget* widget) { 444 DCHECK(view_->GetWidget() == widget); 445 if (is_visible_) 446 Dismiss(); 447 ResetView(); 448} 449 450//////////////////////////////////////////////////////////////////////////////// 451// AppListController, keyboard::KeyboardControllerObserver implementation: 452 453void AppListController::OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) { 454 UpdateBounds(); 455} 456 457//////////////////////////////////////////////////////////////////////////////// 458// AppListController, ShellObserver implementation: 459void AppListController::OnShelfAlignmentChanged(aura::Window* root_window) { 460 if (view_) 461 view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView())); 462} 463 464//////////////////////////////////////////////////////////////////////////////// 465// AppListController, ShelfIconObserver implementation: 466 467void AppListController::OnShelfIconPositionsChanged() { 468 UpdateBounds(); 469} 470 471//////////////////////////////////////////////////////////////////////////////// 472// AppListController, PaginationModelObserver implementation: 473 474void AppListController::TotalPagesChanged() { 475} 476 477void AppListController::SelectedPageChanged(int old_selected, 478 int new_selected) { 479 current_apps_page_ = new_selected; 480} 481 482void AppListController::TransitionStarted() { 483} 484 485void AppListController::TransitionChanged() { 486 // |view_| could be NULL when app list is closed with a running transition. 487 if (!view_) 488 return; 489 490 app_list::PaginationModel* pagination_model = view_->GetAppsPaginationModel(); 491 492 const app_list::PaginationModel::Transition& transition = 493 pagination_model->transition(); 494 if (pagination_model->is_valid_page(transition.target_page)) 495 return; 496 497 views::Widget* widget = view_->GetWidget(); 498 ui::LayerAnimator* widget_animator = GetLayer(widget)->GetAnimator(); 499 if (!pagination_model->IsRevertingCurrentTransition()) { 500 // Update cached |view_bounds_| if it is the first over-scroll move and 501 // widget does not have running animations. 502 if (!should_snap_back_ && !widget_animator->is_animating()) 503 view_bounds_ = widget->GetWindowBoundsInScreen(); 504 505 const int current_page = pagination_model->selected_page(); 506 const int dir = transition.target_page > current_page ? -1 : 1; 507 508 const double progress = 1.0 - pow(1.0 - transition.progress, 4); 509 const int shift = kMaxOverScrollShift * progress * dir; 510 511 gfx::Rect shifted(view_bounds_); 512 // Experimental app list scrolls vertically, so make the overscroll 513 // vertical. 514 if (app_list::switches::IsExperimentalAppListEnabled()) 515 shifted.set_y(shifted.y() + shift); 516 else 517 shifted.set_x(shifted.x() + shift); 518 widget->SetBounds(shifted); 519 should_snap_back_ = true; 520 } else if (should_snap_back_) { 521 should_snap_back_ = false; 522 ui::ScopedLayerAnimationSettings animation(widget_animator); 523 animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 524 app_list::kOverscrollPageTransitionDurationMs)); 525 widget->SetBounds(view_bounds_); 526 } 527} 528 529} // namespace ash 530