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::SetVisible(bool visible, aura::Window* window) { 177 if (visible == is_visible_) 178 return; 179 180 is_visible_ = visible; 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 // Our widget is currently active. When the animation completes we'll hide 189 // the widget, changing activation. If a menu is shown before the animation 190 // completes then the activation change triggers the menu to close. By 191 // deactivating now we ensure there is no activation change when the 192 // animation completes and any menus stay open. 193 if (!visible) 194 view_->GetWidget()->Deactivate(); 195 ScheduleAnimation(); 196 } else if (is_visible_) { 197 // AppListModel and AppListViewDelegate are owned by AppListView. They 198 // will be released with AppListView on close. 199 app_list::AppListView* view = new app_list::AppListView( 200 Shell::GetInstance()->delegate()->CreateAppListViewDelegate()); 201 aura::Window* root_window = window->GetRootWindow(); 202 aura::Window* container = GetRootWindowController(root_window)-> 203 GetContainer(kShellWindowId_AppListContainer); 204 views::View* applist_button = 205 Shelf::ForWindow(container)->GetAppListButtonView(); 206 is_centered_ = view->ShouldCenterWindow(); 207 if (is_centered_) { 208 // Note: We can't center the app list until we have its dimensions, so we 209 // init at (0, 0) and then reset its anchor point. 210 view->InitAsBubbleAtFixedLocation(container, 211 current_apps_page_, 212 gfx::Point(), 213 views::BubbleBorder::FLOAT, 214 true /* border_accepts_events */); 215 // The experimental app list is centered over the display of the app list 216 // button that was pressed (if triggered via keyboard, this is the display 217 // with the currently focused window). 218 view->SetAnchorPoint(GetCenterOfDisplayForView( 219 applist_button, GetMinimumBoundsHeightForAppList(view))); 220 } else { 221 gfx::Rect applist_button_bounds = applist_button->GetBoundsInScreen(); 222 // We need the location of the button within the local screen. 223 applist_button_bounds = ScreenUtil::ConvertRectFromScreen( 224 root_window, 225 applist_button_bounds); 226 view->InitAsBubbleAttachedToAnchor( 227 container, 228 current_apps_page_, 229 Shelf::ForWindow(container)->GetAppListButtonView(), 230 GetAnchorPositionOffsetToShelf( 231 applist_button_bounds, 232 Shelf::ForWindow(container)->GetAppListButtonView()->GetWidget()), 233 GetBubbleArrow(container), 234 true /* border_accepts_events */); 235 view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE); 236 } 237 SetView(view); 238 // By setting us as DnD recipient, the app list knows that we can 239 // handle items. 240 SetDragAndDropHostOfCurrentAppList( 241 Shelf::ForWindow(window)->GetDragAndDropHostForAppList()); 242 } 243 // Update applist button status when app list visibility is changed. 244 Shelf::ForWindow(window)->GetAppListButtonView()->SchedulePaint(); 245} 246 247bool AppListController::IsVisible() const { 248 return view_ && view_->GetWidget()->IsVisible(); 249} 250 251aura::Window* AppListController::GetWindow() { 252 return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL; 253} 254 255//////////////////////////////////////////////////////////////////////////////// 256// AppListController, private: 257 258void AppListController::SetDragAndDropHostOfCurrentAppList( 259 app_list::ApplicationDragAndDropHost* drag_and_drop_host) { 260 if (view_ && is_visible_) 261 view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 262} 263 264void AppListController::SetView(app_list::AppListView* view) { 265 DCHECK(view_ == NULL); 266 DCHECK(is_visible_); 267 268 view_ = view; 269 views::Widget* widget = view_->GetWidget(); 270 widget->AddObserver(this); 271 keyboard::KeyboardController* keyboard_controller = 272 keyboard::KeyboardController::GetInstance(); 273 if (keyboard_controller) 274 keyboard_controller->AddObserver(this); 275 Shell::GetInstance()->AddPreTargetHandler(this); 276 Shelf::ForWindow(widget->GetNativeWindow())->AddIconObserver(this); 277 widget->GetNativeView()->GetRootWindow()->AddObserver(this); 278 aura::client::GetFocusClient(widget->GetNativeView())->AddObserver(this); 279 280 view_->GetAppsPaginationModel()->AddObserver(this); 281 282 view_->ShowWhenReady(); 283} 284 285void AppListController::ResetView() { 286 if (!view_) 287 return; 288 289 views::Widget* widget = view_->GetWidget(); 290 widget->RemoveObserver(this); 291 GetLayer(widget)->GetAnimator()->RemoveObserver(this); 292 keyboard::KeyboardController* keyboard_controller = 293 keyboard::KeyboardController::GetInstance(); 294 if (keyboard_controller) 295 keyboard_controller->RemoveObserver(this); 296 Shell::GetInstance()->RemovePreTargetHandler(this); 297 Shelf::ForWindow(widget->GetNativeWindow())->RemoveIconObserver(this); 298 widget->GetNativeView()->GetRootWindow()->RemoveObserver(this); 299 aura::client::GetFocusClient(widget->GetNativeView())->RemoveObserver(this); 300 301 view_->GetAppsPaginationModel()->RemoveObserver(this); 302 303 view_ = NULL; 304} 305 306void AppListController::ScheduleAnimation() { 307 // Stop observing previous animation. 308 StopObservingImplicitAnimations(); 309 310 views::Widget* widget = view_->GetWidget(); 311 ui::Layer* layer = GetLayer(widget); 312 layer->GetAnimator()->StopAnimating(); 313 314 gfx::Rect target_bounds; 315 if (is_visible_) { 316 target_bounds = widget->GetWindowBoundsInScreen(); 317 widget->SetBounds(OffsetTowardsShelf(target_bounds, widget)); 318 } else { 319 target_bounds = OffsetTowardsShelf(widget->GetWindowBoundsInScreen(), 320 widget); 321 } 322 323 ui::ScopedLayerAnimationSettings animation(layer->GetAnimator()); 324 animation.SetTransitionDuration( 325 base::TimeDelta::FromMilliseconds( 326 is_visible_ ? 0 : kAnimationDurationMs)); 327 animation.AddObserver(this); 328 329 layer->SetOpacity(is_visible_ ? 1.0 : 0.0); 330 widget->SetBounds(target_bounds); 331} 332 333void AppListController::ProcessLocatedEvent(ui::LocatedEvent* event) { 334 if (!view_ || !is_visible_) 335 return; 336 337 // If the event happened on a menu, then the event should not close the app 338 // list. 339 aura::Window* target = static_cast<aura::Window*>(event->target()); 340 if (target) { 341 RootWindowController* root_controller = 342 GetRootWindowController(target->GetRootWindow()); 343 if (root_controller) { 344 aura::Window* menu_container = 345 root_controller->GetContainer(kShellWindowId_MenuContainer); 346 if (menu_container->Contains(target)) 347 return; 348 aura::Window* keyboard_container = root_controller->GetContainer( 349 kShellWindowId_VirtualKeyboardContainer); 350 if (keyboard_container->Contains(target)) 351 return; 352 } 353 } 354 355 aura::Window* window = view_->GetWidget()->GetNativeView()->parent(); 356 if (!window->Contains(target)) 357 SetVisible(false, window); 358} 359 360void AppListController::UpdateBounds() { 361 if (!view_ || !is_visible_) 362 return; 363 364 view_->UpdateBounds(); 365 366 if (is_centered_) 367 view_->SetAnchorPoint(GetCenterOfDisplayForView( 368 view_, GetMinimumBoundsHeightForAppList(view_))); 369} 370 371//////////////////////////////////////////////////////////////////////////////// 372// AppListController, aura::EventFilter implementation: 373 374void AppListController::OnMouseEvent(ui::MouseEvent* event) { 375 if (event->type() == ui::ET_MOUSE_PRESSED) 376 ProcessLocatedEvent(event); 377} 378 379void AppListController::OnGestureEvent(ui::GestureEvent* event) { 380 if (event->type() == ui::ET_GESTURE_TAP_DOWN) 381 ProcessLocatedEvent(event); 382} 383 384//////////////////////////////////////////////////////////////////////////////// 385// AppListController, aura::FocusObserver implementation: 386 387void AppListController::OnWindowFocused(aura::Window* gained_focus, 388 aura::Window* lost_focus) { 389 if (view_ && is_visible_) { 390 aura::Window* applist_window = view_->GetWidget()->GetNativeView(); 391 aura::Window* applist_container = applist_window->parent(); 392 393 if (applist_container->Contains(lost_focus) && 394 (!gained_focus || !applist_container->Contains(gained_focus))) { 395 SetVisible(false, applist_window); 396 } 397 } 398} 399 400//////////////////////////////////////////////////////////////////////////////// 401// AppListController, aura::WindowObserver implementation: 402void AppListController::OnWindowBoundsChanged(aura::Window* root, 403 const gfx::Rect& old_bounds, 404 const gfx::Rect& new_bounds) { 405 UpdateBounds(); 406} 407 408//////////////////////////////////////////////////////////////////////////////// 409// AppListController, ui::ImplicitAnimationObserver implementation: 410 411void AppListController::OnImplicitAnimationsCompleted() { 412 if (is_visible_ ) 413 view_->GetWidget()->Activate(); 414 else 415 view_->GetWidget()->Close(); 416} 417 418//////////////////////////////////////////////////////////////////////////////// 419// AppListController, views::WidgetObserver implementation: 420 421void AppListController::OnWidgetDestroying(views::Widget* widget) { 422 DCHECK(view_->GetWidget() == widget); 423 if (is_visible_) 424 SetVisible(false, widget->GetNativeView()); 425 ResetView(); 426} 427 428//////////////////////////////////////////////////////////////////////////////// 429// AppListController, keyboard::KeyboardControllerObserver implementation: 430 431void AppListController::OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) { 432 UpdateBounds(); 433} 434 435//////////////////////////////////////////////////////////////////////////////// 436// AppListController, ShellObserver implementation: 437void AppListController::OnShelfAlignmentChanged(aura::Window* root_window) { 438 if (view_) 439 view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView())); 440} 441 442//////////////////////////////////////////////////////////////////////////////// 443// AppListController, ShelfIconObserver implementation: 444 445void AppListController::OnShelfIconPositionsChanged() { 446 UpdateBounds(); 447} 448 449//////////////////////////////////////////////////////////////////////////////// 450// AppListController, PaginationModelObserver implementation: 451 452void AppListController::TotalPagesChanged() { 453} 454 455void AppListController::SelectedPageChanged(int old_selected, 456 int new_selected) { 457 current_apps_page_ = new_selected; 458} 459 460void AppListController::TransitionStarted() { 461} 462 463void AppListController::TransitionChanged() { 464 // |view_| could be NULL when app list is closed with a running transition. 465 if (!view_) 466 return; 467 468 app_list::PaginationModel* pagination_model = view_->GetAppsPaginationModel(); 469 470 const app_list::PaginationModel::Transition& transition = 471 pagination_model->transition(); 472 if (pagination_model->is_valid_page(transition.target_page)) 473 return; 474 475 views::Widget* widget = view_->GetWidget(); 476 ui::LayerAnimator* widget_animator = GetLayer(widget)->GetAnimator(); 477 if (!pagination_model->IsRevertingCurrentTransition()) { 478 // Update cached |view_bounds_| if it is the first over-scroll move and 479 // widget does not have running animations. 480 if (!should_snap_back_ && !widget_animator->is_animating()) 481 view_bounds_ = widget->GetWindowBoundsInScreen(); 482 483 const int current_page = pagination_model->selected_page(); 484 const int dir = transition.target_page > current_page ? -1 : 1; 485 486 const double progress = 1.0 - pow(1.0 - transition.progress, 4); 487 const int shift = kMaxOverScrollShift * progress * dir; 488 489 gfx::Rect shifted(view_bounds_); 490 shifted.set_x(shifted.x() + shift); 491 widget->SetBounds(shifted); 492 should_snap_back_ = true; 493 } else if (should_snap_back_) { 494 should_snap_back_ = false; 495 ui::ScopedLayerAnimationSettings animation(widget_animator); 496 animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 497 app_list::kOverscrollPageTransitionDurationMs)); 498 widget->SetBounds(view_bounds_); 499 } 500} 501 502} // namespace ash 503