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/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