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 28namespace ash { 29namespace internal { 30 31namespace { 32 33// Duration for show/hide animation in milliseconds. 34const int kAnimationDurationMs = 200; 35 36// Offset in pixels to animation away/towards the launcher. 37const int kAnimationOffset = 8; 38 39// The maximum shift in pixels when over-scroll happens. 40const 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. 45const 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. 50const int kMinimalAnchorPositionOffset = 57; 51 52ui::Layer* GetLayer(views::Widget* widget) { 53 return widget->GetNativeView()->layer(); 54} 55 56// Gets arrow location based on shelf alignment. 57views::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. 68gfx::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). 93gfx::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 134AppListController::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 143AppListController::~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 153void 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 206bool AppListController::IsVisible() const { 207 return view_ && view_->GetWidget()->IsVisible(); 208} 209 210aura::Window* AppListController::GetWindow() { 211 return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL; 212} 213 214//////////////////////////////////////////////////////////////////////////////// 215// AppListController, private: 216 217void AppListController::SetDragAndDropHostOfCurrentAppList( 218 app_list::ApplicationDragAndDropHost* drag_and_drop_host) { 219 if (view_ && is_visible_) 220 view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 221} 222 223void 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 238void 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 252void 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 279void 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 306void AppListController::UpdateBounds() { 307 if (view_ && is_visible_) 308 view_->UpdateBounds(); 309} 310 311//////////////////////////////////////////////////////////////////////////////// 312// AppListController, aura::EventFilter implementation: 313 314void AppListController::OnMouseEvent(ui::MouseEvent* event) { 315 if (event->type() == ui::ET_MOUSE_PRESSED) 316 ProcessLocatedEvent(event); 317} 318 319void 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 327void 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: 340void 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 349void 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 359void 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: 368void AppListController::OnShelfAlignmentChanged(aura::RootWindow* root_window) { 369 if (view_) 370 view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView())); 371} 372 373//////////////////////////////////////////////////////////////////////////////// 374// AppListController, LauncherIconObserver implementation: 375 376void AppListController::OnLauncherIconPositionsChanged() { 377 UpdateBounds(); 378} 379 380//////////////////////////////////////////////////////////////////////////////// 381// AppListController, PaginationModelObserver implementation: 382 383void AppListController::TotalPagesChanged() { 384} 385 386void AppListController::SelectedPageChanged(int old_selected, 387 int new_selected) { 388} 389 390void AppListController::TransitionStarted() { 391} 392 393void 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