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::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 
    240 void 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 
    269 bool AppListController::IsVisible() const {
    270   return view_ && view_->GetWidget()->IsVisible();
    271 }
    272 
    273 aura::Window* AppListController::GetWindow() {
    274   return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL;
    275 }
    276 
    277 ////////////////////////////////////////////////////////////////////////////////
    278 // AppListController, private:
    279 
    280 void AppListController::SetDragAndDropHostOfCurrentAppList(
    281     app_list::ApplicationDragAndDropHost* drag_and_drop_host) {
    282   if (view_ && is_visible_)
    283     view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
    284 }
    285 
    286 void 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 
    307 void 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 
    328 void 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 
    355 void 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 
    382 void 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 
    396 void AppListController::OnMouseEvent(ui::MouseEvent* event) {
    397   if (event->type() == ui::ET_MOUSE_PRESSED)
    398     ProcessLocatedEvent(event);
    399 }
    400 
    401 void 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 
    409 void 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:
    424 void 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 
    433 void 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 
    443 void 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 
    453 void AppListController::OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) {
    454   UpdateBounds();
    455 }
    456 
    457 ////////////////////////////////////////////////////////////////////////////////
    458 // AppListController, ShellObserver implementation:
    459 void AppListController::OnShelfAlignmentChanged(aura::Window* root_window) {
    460   if (view_)
    461     view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView()));
    462 }
    463 
    464 ////////////////////////////////////////////////////////////////////////////////
    465 // AppListController, ShelfIconObserver implementation:
    466 
    467 void AppListController::OnShelfIconPositionsChanged() {
    468   UpdateBounds();
    469 }
    470 
    471 ////////////////////////////////////////////////////////////////////////////////
    472 // AppListController, PaginationModelObserver implementation:
    473 
    474 void AppListController::TotalPagesChanged() {
    475 }
    476 
    477 void AppListController::SelectedPageChanged(int old_selected,
    478                                             int new_selected) {
    479   current_apps_page_ = new_selected;
    480 }
    481 
    482 void AppListController::TransitionStarted() {
    483 }
    484 
    485 void 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