Home | History | Annotate | Download | only in wm
      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 
     30 namespace ash {
     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 shelf.
     37 const int kAnimationOffset = 8;
     38 
     39 // The maximum shift in pixels when over-scroll happens.
     40 const 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.
     45 const int kMinimalAnchorPositionOffset = 57;
     46 
     47 // The minimal margin (in pixels) around the app list when in centered mode.
     48 const int kMinimalCenteredAppListMargin = 10;
     49 
     50 ui::Layer* GetLayer(views::Widget* widget) {
     51   return widget->GetNativeView()->layer();
     52 }
     53 
     54 // Gets arrow location based on shelf alignment.
     55 views::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.
     66 gfx::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).
     91 gfx::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).
    124 gfx::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.
    146 int 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 
    155 AppListController::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 
    164 AppListController::~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 
    176 void 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 
    247 bool AppListController::IsVisible() const {
    248   return view_ && view_->GetWidget()->IsVisible();
    249 }
    250 
    251 aura::Window* AppListController::GetWindow() {
    252   return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL;
    253 }
    254 
    255 ////////////////////////////////////////////////////////////////////////////////
    256 // AppListController, private:
    257 
    258 void AppListController::SetDragAndDropHostOfCurrentAppList(
    259     app_list::ApplicationDragAndDropHost* drag_and_drop_host) {
    260   if (view_ && is_visible_)
    261     view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
    262 }
    263 
    264 void 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 
    285 void 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 
    306 void 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 
    333 void 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 
    360 void 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 
    374 void AppListController::OnMouseEvent(ui::MouseEvent* event) {
    375   if (event->type() == ui::ET_MOUSE_PRESSED)
    376     ProcessLocatedEvent(event);
    377 }
    378 
    379 void 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 
    387 void 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:
    402 void 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 
    411 void 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 
    421 void 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 
    431 void AppListController::OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) {
    432   UpdateBounds();
    433 }
    434 
    435 ////////////////////////////////////////////////////////////////////////////////
    436 // AppListController, ShellObserver implementation:
    437 void AppListController::OnShelfAlignmentChanged(aura::Window* root_window) {
    438   if (view_)
    439     view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView()));
    440 }
    441 
    442 ////////////////////////////////////////////////////////////////////////////////
    443 // AppListController, ShelfIconObserver implementation:
    444 
    445 void AppListController::OnShelfIconPositionsChanged() {
    446   UpdateBounds();
    447 }
    448 
    449 ////////////////////////////////////////////////////////////////////////////////
    450 // AppListController, PaginationModelObserver implementation:
    451 
    452 void AppListController::TotalPagesChanged() {
    453 }
    454 
    455 void AppListController::SelectedPageChanged(int old_selected,
    456                                             int new_selected) {
    457   current_apps_page_ = new_selected;
    458 }
    459 
    460 void AppListController::TransitionStarted() {
    461 }
    462 
    463 void 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