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/launcher/launcher.h" 9 #include "ash/root_window_controller.h" 10 #include "ash/shelf/shelf_layout_manager.h" 11 #include "ash/shell.h" 12 #include "ash/shell_delegate.h" 13 #include "ash/shell_window_ids.h" 14 #include "ash/wm/property_util.h" 15 #include "base/command_line.h" 16 #include "ui/app_list/app_list_constants.h" 17 #include "ui/app_list/pagination_model.h" 18 #include "ui/app_list/views/app_list_view.h" 19 #include "ui/aura/client/focus_client.h" 20 #include "ui/aura/root_window.h" 21 #include "ui/aura/window.h" 22 #include "ui/base/events/event.h" 23 #include "ui/compositor/layer.h" 24 #include "ui/compositor/scoped_layer_animation_settings.h" 25 #include "ui/gfx/transform_util.h" 26 #include "ui/views/widget/widget.h" 27 28 namespace ash { 29 namespace internal { 30 31 namespace { 32 33 // Duration for show/hide animation in milliseconds. 34 const int kAnimationDurationMs = 200; 35 36 // Offset in pixels to animation away/towards the launcher. 37 const int kAnimationOffset = 8; 38 39 // The maximum shift in pixels when over-scroll happens. 40 const int kMaxOverScrollShift = 48; 41 42 // The alternate shelf style adjusts the bubble to be flush with the shelf 43 // when there is no bubble-tip. This is the tip height which needs to be 44 // offsetted. 45 const int kArrowTipHeight = 10; 46 47 // The minimal anchor position offset to make sure that the bubble is still on 48 // the screen with 8 pixels spacing on the left / right. This constant is a 49 // result of minimal bubble arrow sizes and offsets. 50 const int kMinimalAnchorPositionOffset = 57; 51 52 ui::Layer* GetLayer(views::Widget* widget) { 53 return widget->GetNativeView()->layer(); 54 } 55 56 // Gets arrow location based on shelf alignment. 57 views::BubbleBorder::Arrow GetBubbleArrow(aura::Window* window) { 58 DCHECK(Shell::HasInstance()); 59 return ShelfLayoutManager::ForLauncher(window)-> 60 SelectValueForShelfAlignment( 61 views::BubbleBorder::BOTTOM_CENTER, 62 views::BubbleBorder::LEFT_CENTER, 63 views::BubbleBorder::RIGHT_CENTER, 64 views::BubbleBorder::TOP_CENTER); 65 } 66 67 // Offset given |rect| towards shelf. 68 gfx::Rect OffsetTowardsShelf(const gfx::Rect& rect, views::Widget* widget) { 69 DCHECK(Shell::HasInstance()); 70 ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment( 71 widget->GetNativeView()->GetRootWindow()); 72 gfx::Rect offseted(rect); 73 switch (shelf_alignment) { 74 case SHELF_ALIGNMENT_BOTTOM: 75 offseted.Offset(0, kAnimationOffset); 76 break; 77 case SHELF_ALIGNMENT_LEFT: 78 offseted.Offset(-kAnimationOffset, 0); 79 break; 80 case SHELF_ALIGNMENT_RIGHT: 81 offseted.Offset(kAnimationOffset, 0); 82 break; 83 case SHELF_ALIGNMENT_TOP: 84 offseted.Offset(0, -kAnimationOffset); 85 break; 86 } 87 88 return offseted; 89 } 90 91 // Using |button_bounds|, determine the anchor so that the bubble gets shown 92 // above the shelf (used for the alternate shelf theme). 93 gfx::Point GetAdjustAnchorPositionToShelf( 94 const gfx::Rect& button_bounds, views::Widget* widget) { 95 DCHECK(Shell::HasInstance()); 96 ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment( 97 widget->GetNativeView()->GetRootWindow()); 98 gfx::Point anchor(button_bounds.CenterPoint()); 99 switch (shelf_alignment) { 100 case SHELF_ALIGNMENT_TOP: 101 case SHELF_ALIGNMENT_BOTTOM: 102 { 103 if (base::i18n::IsRTL()) { 104 int screen_width = widget->GetWorkAreaBoundsInScreen().width(); 105 anchor.set_x(std::min(screen_width - kMinimalAnchorPositionOffset, 106 anchor.x())); 107 } else { 108 anchor.set_x(std::max(kMinimalAnchorPositionOffset, anchor.x())); 109 } 110 int offset = button_bounds.height() / 2 - kArrowTipHeight; 111 if (shelf_alignment == SHELF_ALIGNMENT_TOP) 112 offset = -offset; 113 anchor.set_y(anchor.y() - offset); 114 } 115 break; 116 case SHELF_ALIGNMENT_LEFT: 117 anchor.set_x(button_bounds.right() - kArrowTipHeight); 118 anchor.set_y(std::max(kMinimalAnchorPositionOffset, anchor.y())); 119 break; 120 case SHELF_ALIGNMENT_RIGHT: 121 anchor.set_x(button_bounds.x() + kArrowTipHeight); 122 anchor.set_y(std::max(kMinimalAnchorPositionOffset, anchor.y())); 123 break; 124 } 125 126 return anchor; 127 } 128 129 } // namespace 130 131 //////////////////////////////////////////////////////////////////////////////// 132 // AppListController, public: 133 134 AppListController::AppListController() 135 : pagination_model_(new app_list::PaginationModel), 136 is_visible_(false), 137 view_(NULL), 138 should_snap_back_(false) { 139 Shell::GetInstance()->AddShellObserver(this); 140 pagination_model_->AddObserver(this); 141 } 142 143 AppListController::~AppListController() { 144 // Ensures app list view goes before the controller since pagination model 145 // lives in the controller and app list view would access it on destruction. 146 if (view_ && view_->GetWidget()) 147 view_->GetWidget()->CloseNow(); 148 149 Shell::GetInstance()->RemoveShellObserver(this); 150 pagination_model_->RemoveObserver(this); 151 } 152 153 void AppListController::SetVisible(bool visible, aura::Window* window) { 154 if (visible == is_visible_) 155 return; 156 157 is_visible_ = visible; 158 159 // App list needs to know the new shelf layout in order to calculate its 160 // UI layout when AppListView visibility changes. 161 Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()-> 162 UpdateAutoHideState(); 163 164 if (view_) { 165 ScheduleAnimation(); 166 } else if (is_visible_) { 167 // AppListModel and AppListViewDelegate are owned by AppListView. They 168 // will be released with AppListView on close. 169 app_list::AppListView* view = new app_list::AppListView( 170 Shell::GetInstance()->delegate()->CreateAppListViewDelegate()); 171 aura::Window* container = GetRootWindowController(window->GetRootWindow())-> 172 GetContainer(kShellWindowId_AppListContainer); 173 if (ash::switches::UseAlternateShelfLayout()) { 174 gfx::Rect applist_button_bounds = Launcher::ForWindow(container)-> 175 GetAppListButtonView()->GetBoundsInScreen(); 176 view->InitAsBubble( 177 container, 178 pagination_model_.get(), 179 NULL, 180 GetAdjustAnchorPositionToShelf(applist_button_bounds, 181 Launcher::ForWindow(container)->GetAppListButtonView()-> 182 GetWidget()), 183 GetBubbleArrow(container), 184 true /* border_accepts_events */); 185 view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE); 186 } else { 187 view->InitAsBubble( 188 container, 189 pagination_model_.get(), 190 Launcher::ForWindow(container)->GetAppListButtonView(), 191 gfx::Point(), 192 GetBubbleArrow(container), 193 true /* border_accepts_events */); 194 } 195 SetView(view); 196 // By setting us as DnD recipient, the app list knows that we can 197 // handle items. 198 if (!CommandLine::ForCurrentProcess()->HasSwitch( 199 ash::switches::kAshDisableDragAndDropAppListToLauncher)) { 200 SetDragAndDropHostOfCurrentAppList( 201 Launcher::ForWindow(window)->GetDragAndDropHostForAppList()); 202 } 203 } 204 } 205 206 bool AppListController::IsVisible() const { 207 return view_ && view_->GetWidget()->IsVisible(); 208 } 209 210 aura::Window* AppListController::GetWindow() { 211 return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL; 212 } 213 214 //////////////////////////////////////////////////////////////////////////////// 215 // AppListController, private: 216 217 void AppListController::SetDragAndDropHostOfCurrentAppList( 218 app_list::ApplicationDragAndDropHost* drag_and_drop_host) { 219 if (view_ && is_visible_) 220 view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 221 } 222 223 void AppListController::SetView(app_list::AppListView* view) { 224 DCHECK(view_ == NULL); 225 DCHECK(is_visible_); 226 227 view_ = view; 228 views::Widget* widget = view_->GetWidget(); 229 widget->AddObserver(this); 230 Shell::GetInstance()->AddPreTargetHandler(this); 231 Launcher::ForWindow(widget->GetNativeWindow())->AddIconObserver(this); 232 widget->GetNativeView()->GetRootWindow()->AddObserver(this); 233 aura::client::GetFocusClient(widget->GetNativeView())->AddObserver(this); 234 235 view_->ShowWhenReady(); 236 } 237 238 void AppListController::ResetView() { 239 if (!view_) 240 return; 241 242 views::Widget* widget = view_->GetWidget(); 243 widget->RemoveObserver(this); 244 GetLayer(widget)->GetAnimator()->RemoveObserver(this); 245 Shell::GetInstance()->RemovePreTargetHandler(this); 246 Launcher::ForWindow(widget->GetNativeWindow())->RemoveIconObserver(this); 247 widget->GetNativeView()->GetRootWindow()->RemoveObserver(this); 248 aura::client::GetFocusClient(widget->GetNativeView())->RemoveObserver(this); 249 view_ = NULL; 250 } 251 252 void AppListController::ScheduleAnimation() { 253 // Stop observing previous animation. 254 StopObservingImplicitAnimations(); 255 256 views::Widget* widget = view_->GetWidget(); 257 ui::Layer* layer = GetLayer(widget); 258 layer->GetAnimator()->StopAnimating(); 259 260 gfx::Rect target_bounds; 261 if (is_visible_) { 262 target_bounds = widget->GetWindowBoundsInScreen(); 263 widget->SetBounds(OffsetTowardsShelf(target_bounds, widget)); 264 } else { 265 target_bounds = OffsetTowardsShelf(widget->GetWindowBoundsInScreen(), 266 widget); 267 } 268 269 ui::ScopedLayerAnimationSettings animation(layer->GetAnimator()); 270 animation.SetTransitionDuration( 271 base::TimeDelta::FromMilliseconds( 272 is_visible_ ? 0 : kAnimationDurationMs)); 273 animation.AddObserver(this); 274 275 layer->SetOpacity(is_visible_ ? 1.0 : 0.0); 276 widget->SetBounds(target_bounds); 277 } 278 279 void AppListController::ProcessLocatedEvent(ui::LocatedEvent* event) { 280 // If the event happened on a menu, then the event should not close the app 281 // list. 282 aura::Window* target = static_cast<aura::Window*>(event->target()); 283 if (target) { 284 RootWindowController* root_controller = 285 GetRootWindowController(target->GetRootWindow()); 286 if (root_controller) { 287 aura::Window* menu_container = root_controller->GetContainer( 288 ash::internal::kShellWindowId_MenuContainer); 289 if (menu_container->Contains(target)) 290 return; 291 } 292 } 293 294 if (view_ && is_visible_) { 295 aura::Window* window = view_->GetWidget()->GetNativeView(); 296 gfx::Point window_local_point(event->root_location()); 297 aura::Window::ConvertPointToTarget(window->GetRootWindow(), 298 window, 299 &window_local_point); 300 // Use HitTest to respect the hit test mask of the bubble. 301 if (!window->HitTest(window_local_point)) 302 SetVisible(false, window); 303 } 304 } 305 306 void AppListController::UpdateBounds() { 307 if (view_ && is_visible_) 308 view_->UpdateBounds(); 309 } 310 311 //////////////////////////////////////////////////////////////////////////////// 312 // AppListController, aura::EventFilter implementation: 313 314 void AppListController::OnMouseEvent(ui::MouseEvent* event) { 315 if (event->type() == ui::ET_MOUSE_PRESSED) 316 ProcessLocatedEvent(event); 317 } 318 319 void AppListController::OnGestureEvent(ui::GestureEvent* event) { 320 if (event->type() == ui::ET_GESTURE_TAP_DOWN) 321 ProcessLocatedEvent(event); 322 } 323 324 //////////////////////////////////////////////////////////////////////////////// 325 // AppListController, aura::FocusObserver implementation: 326 327 void AppListController::OnWindowFocused(aura::Window* gained_focus, 328 aura::Window* lost_focus) { 329 if (gained_focus && view_ && is_visible_) { 330 aura::Window* applist_container = 331 GetRootWindowController(gained_focus->GetRootWindow())->GetContainer( 332 kShellWindowId_AppListContainer); 333 if (gained_focus->parent() != applist_container) 334 SetVisible(false, gained_focus); 335 } 336 } 337 338 //////////////////////////////////////////////////////////////////////////////// 339 // AppListController, aura::WindowObserver implementation: 340 void AppListController::OnWindowBoundsChanged(aura::Window* root, 341 const gfx::Rect& old_bounds, 342 const gfx::Rect& new_bounds) { 343 UpdateBounds(); 344 } 345 346 //////////////////////////////////////////////////////////////////////////////// 347 // AppListController, ui::ImplicitAnimationObserver implementation: 348 349 void AppListController::OnImplicitAnimationsCompleted() { 350 if (is_visible_ ) 351 view_->GetWidget()->Activate(); 352 else 353 view_->GetWidget()->Close(); 354 } 355 356 //////////////////////////////////////////////////////////////////////////////// 357 // AppListController, views::WidgetObserver implementation: 358 359 void AppListController::OnWidgetDestroying(views::Widget* widget) { 360 DCHECK(view_->GetWidget() == widget); 361 if (is_visible_) 362 SetVisible(false, widget->GetNativeView()); 363 ResetView(); 364 } 365 366 //////////////////////////////////////////////////////////////////////////////// 367 // AppListController, ShellObserver implementation: 368 void AppListController::OnShelfAlignmentChanged(aura::RootWindow* root_window) { 369 if (view_) 370 view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView())); 371 } 372 373 //////////////////////////////////////////////////////////////////////////////// 374 // AppListController, LauncherIconObserver implementation: 375 376 void AppListController::OnLauncherIconPositionsChanged() { 377 UpdateBounds(); 378 } 379 380 //////////////////////////////////////////////////////////////////////////////// 381 // AppListController, PaginationModelObserver implementation: 382 383 void AppListController::TotalPagesChanged() { 384 } 385 386 void AppListController::SelectedPageChanged(int old_selected, 387 int new_selected) { 388 } 389 390 void AppListController::TransitionStarted() { 391 } 392 393 void AppListController::TransitionChanged() { 394 // |view_| could be NULL when app list is closed with a running transition. 395 if (!view_) 396 return; 397 398 const app_list::PaginationModel::Transition& transition = 399 pagination_model_->transition(); 400 if (pagination_model_->is_valid_page(transition.target_page)) 401 return; 402 403 views::Widget* widget = view_->GetWidget(); 404 ui::LayerAnimator* widget_animator = GetLayer(widget)->GetAnimator(); 405 if (!pagination_model_->IsRevertingCurrentTransition()) { 406 // Update cached |view_bounds_| if it is the first over-scroll move and 407 // widget does not have running animations. 408 if (!should_snap_back_ && !widget_animator->is_animating()) 409 view_bounds_ = widget->GetWindowBoundsInScreen(); 410 411 const int current_page = pagination_model_->selected_page(); 412 const int dir = transition.target_page > current_page ? -1 : 1; 413 414 const double progress = 1.0 - pow(1.0 - transition.progress, 4); 415 const int shift = kMaxOverScrollShift * progress * dir; 416 417 gfx::Rect shifted(view_bounds_); 418 shifted.set_x(shifted.x() + shift); 419 widget->SetBounds(shifted); 420 should_snap_back_ = true; 421 } else if (should_snap_back_) { 422 should_snap_back_ = false; 423 ui::ScopedLayerAnimationSettings animation(widget_animator); 424 animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 425 app_list::kOverscrollPageTransitionDurationMs)); 426 widget->SetBounds(view_bounds_); 427 } 428 } 429 430 } // namespace internal 431 } // namespace ash 432