Home | History | Annotate | Download | only in tray
      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/system/tray/system_tray_bubble.h"
      6 
      7 #include "ash/shell.h"
      8 #include "ash/system/tray/system_tray.h"
      9 #include "ash/system/tray/system_tray_delegate.h"
     10 #include "ash/system/tray/system_tray_item.h"
     11 #include "ash/system/tray/tray_bubble_wrapper.h"
     12 #include "ash/system/tray/tray_constants.h"
     13 #include "base/message_loop/message_loop.h"
     14 #include "ui/aura/window.h"
     15 #include "ui/compositor/layer.h"
     16 #include "ui/compositor/layer_animation_observer.h"
     17 #include "ui/compositor/scoped_layer_animation_settings.h"
     18 #include "ui/gfx/canvas.h"
     19 #include "ui/views/layout/box_layout.h"
     20 #include "ui/views/view.h"
     21 #include "ui/views/widget/widget.h"
     22 
     23 using views::TrayBubbleView;
     24 
     25 namespace ash {
     26 
     27 namespace {
     28 
     29 // Normally a detailed view is the same size as the default view. However,
     30 // when showing a detailed view directly (e.g. clicking on a notification),
     31 // we may not know the height of the default view, or the default view may
     32 // be too short, so we use this as a default and minimum height for any
     33 // detailed view.
     34 const int kDetailedBubbleMaxHeight = kTrayPopupItemHeight * 5;
     35 
     36 // Duration of swipe animation used when transitioning from a default to
     37 // detailed view or vice versa.
     38 const int kSwipeDelayMS = 150;
     39 
     40 // A view with some special behaviour for tray items in the popup:
     41 // - optionally changes background color on hover.
     42 class TrayPopupItemContainer : public views::View {
     43  public:
     44   TrayPopupItemContainer(views::View* view,
     45                          bool change_background,
     46                          bool draw_border)
     47       : hover_(false),
     48         change_background_(change_background) {
     49     set_notify_enter_exit_on_child(true);
     50     if (draw_border) {
     51       set_border(
     52           views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor));
     53     }
     54     views::BoxLayout* layout = new views::BoxLayout(
     55         views::BoxLayout::kVertical, 0, 0, 0);
     56     layout->set_spread_blank_space(true);
     57     SetLayoutManager(layout);
     58     SetPaintToLayer(view->layer() != NULL);
     59     if (view->layer())
     60       SetFillsBoundsOpaquely(view->layer()->fills_bounds_opaquely());
     61     AddChildView(view);
     62     SetVisible(view->visible());
     63   }
     64 
     65   virtual ~TrayPopupItemContainer() {}
     66 
     67  private:
     68   // Overridden from views::View.
     69   virtual void ChildVisibilityChanged(View* child) OVERRIDE {
     70     if (visible() == child->visible())
     71       return;
     72     SetVisible(child->visible());
     73     PreferredSizeChanged();
     74   }
     75 
     76   virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
     77     PreferredSizeChanged();
     78   }
     79 
     80   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
     81     hover_ = true;
     82     SchedulePaint();
     83   }
     84 
     85   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
     86     hover_ = false;
     87     SchedulePaint();
     88   }
     89 
     90   virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
     91     if (child_count() == 0)
     92       return;
     93 
     94     views::View* view = child_at(0);
     95     if (!view->background()) {
     96       canvas->FillRect(gfx::Rect(size()), (hover_ && change_background_) ?
     97           kHoverBackgroundColor : kBackgroundColor);
     98     }
     99   }
    100 
    101   bool hover_;
    102   bool change_background_;
    103 
    104   DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer);
    105 };
    106 
    107 // Implicit animation observer that deletes itself and the layer at the end of
    108 // the animation.
    109 class AnimationObserverDeleteLayer : public ui::ImplicitAnimationObserver {
    110  public:
    111   explicit AnimationObserverDeleteLayer(ui::Layer* layer)
    112       : layer_(layer) {
    113   }
    114 
    115   virtual ~AnimationObserverDeleteLayer() {
    116   }
    117 
    118   virtual void OnImplicitAnimationsCompleted() OVERRIDE {
    119     base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE, this);
    120   }
    121 
    122  private:
    123   scoped_ptr<ui::Layer> layer_;
    124 
    125   DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer);
    126 };
    127 
    128 }  // namespace
    129 
    130 namespace internal {
    131 
    132 // SystemTrayBubble
    133 
    134 SystemTrayBubble::SystemTrayBubble(
    135     ash::SystemTray* tray,
    136     const std::vector<ash::SystemTrayItem*>& items,
    137     BubbleType bubble_type)
    138     : tray_(tray),
    139       bubble_view_(NULL),
    140       items_(items),
    141       bubble_type_(bubble_type),
    142       autoclose_delay_(0) {
    143 }
    144 
    145 SystemTrayBubble::~SystemTrayBubble() {
    146   DestroyItemViews();
    147   // Reset the host pointer in bubble_view_ in case its destruction is deferred.
    148   if (bubble_view_)
    149     bubble_view_->reset_delegate();
    150 }
    151 
    152 void SystemTrayBubble::UpdateView(
    153     const std::vector<ash::SystemTrayItem*>& items,
    154     BubbleType bubble_type) {
    155   DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION);
    156 
    157   scoped_ptr<ui::Layer> scoped_layer;
    158   if (bubble_type != bubble_type_) {
    159     base::TimeDelta swipe_duration =
    160         base::TimeDelta::FromMilliseconds(kSwipeDelayMS);
    161     scoped_layer.reset(bubble_view_->RecreateLayer());
    162     // Keep the reference to layer as we need it after releasing it.
    163     ui::Layer* layer = scoped_layer.get();
    164     DCHECK(layer);
    165     layer->SuppressPaint();
    166 
    167     // When transitioning from detailed view to default view, animate the
    168     // existing view (slide out towards the right).
    169     if (bubble_type == BUBBLE_TYPE_DEFAULT) {
    170       ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
    171       settings.AddObserver(
    172           new AnimationObserverDeleteLayer(scoped_layer.release()));
    173       settings.SetTransitionDuration(swipe_duration);
    174       settings.SetTweenType(ui::Tween::EASE_OUT);
    175       gfx::Transform transform;
    176       transform.Translate(layer->bounds().width(), 0.0);
    177       layer->SetTransform(transform);
    178     }
    179 
    180     {
    181       // Add a shadow layer to make the old layer darker as the animation
    182       // progresses.
    183       ui::Layer* shadow = new ui::Layer(ui::LAYER_SOLID_COLOR);
    184       shadow->SetColor(SK_ColorBLACK);
    185       shadow->SetOpacity(0.01f);
    186       shadow->SetBounds(layer->bounds());
    187       layer->Add(shadow);
    188       layer->StackAtTop(shadow);
    189       {
    190         // Animate the darkening effect a little longer than the swipe-in. This
    191         // is to make sure the darkening animation does not end up finishing
    192         // early, because the dark layer goes away at the end of the animation,
    193         // and there is a brief moment when the old view is still visible, but
    194         // it does not have the shadow layer on top.
    195         ui::ScopedLayerAnimationSettings settings(shadow->GetAnimator());
    196         settings.AddObserver(new AnimationObserverDeleteLayer(shadow));
    197         settings.SetTransitionDuration(swipe_duration +
    198                                        base::TimeDelta::FromMilliseconds(150));
    199         settings.SetTweenType(ui::Tween::LINEAR);
    200         shadow->SetOpacity(0.15f);
    201       }
    202     }
    203   }
    204 
    205   DestroyItemViews();
    206   bubble_view_->RemoveAllChildViews(true);
    207 
    208   items_ = items;
    209   bubble_type_ = bubble_type;
    210   CreateItemViews(
    211       Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
    212 
    213   // Close bubble view if we failed to create the item view.
    214   if (!bubble_view_->has_children()) {
    215     Close();
    216     return;
    217   }
    218 
    219   bubble_view_->GetWidget()->GetContentsView()->Layout();
    220   // Make sure that the bubble is large enough for the default view.
    221   if (bubble_type_ == BUBBLE_TYPE_DEFAULT) {
    222     bubble_view_->SetMaxHeight(0);  // Clear max height limit.
    223   }
    224 
    225   if (scoped_layer) {
    226     // When transitioning from default view to detailed view, animate the new
    227     // view (slide in from the right).
    228     if (bubble_type == BUBBLE_TYPE_DETAILED) {
    229       ui::Layer* new_layer = bubble_view_->layer();
    230 
    231       // Make sure the new layer is stacked above the old layer during the
    232       // animation.
    233       new_layer->parent()->StackAbove(new_layer, scoped_layer.get());
    234 
    235       gfx::Rect bounds = new_layer->bounds();
    236       gfx::Transform transform;
    237       transform.Translate(bounds.width(), 0.0);
    238       new_layer->SetTransform(transform);
    239       {
    240         ui::ScopedLayerAnimationSettings settings(new_layer->GetAnimator());
    241         settings.AddObserver(
    242             new AnimationObserverDeleteLayer(scoped_layer.release()));
    243         settings.SetTransitionDuration(
    244             base::TimeDelta::FromMilliseconds(kSwipeDelayMS));
    245         settings.SetTweenType(ui::Tween::EASE_OUT);
    246         new_layer->SetTransform(gfx::Transform());
    247       }
    248     }
    249   }
    250 }
    251 
    252 void SystemTrayBubble::InitView(views::View* anchor,
    253                                 user::LoginStatus login_status,
    254                                 TrayBubbleView::InitParams* init_params) {
    255   DCHECK(bubble_view_ == NULL);
    256 
    257   if (bubble_type_ == BUBBLE_TYPE_DETAILED &&
    258       init_params->max_height < kDetailedBubbleMaxHeight) {
    259     init_params->max_height = kDetailedBubbleMaxHeight;
    260   } else if (bubble_type_ == BUBBLE_TYPE_NOTIFICATION) {
    261     init_params->close_on_deactivate = false;
    262   }
    263   bubble_view_ = TrayBubbleView::Create(
    264       tray_->GetBubbleWindowContainer(), anchor, tray_, init_params);
    265   bubble_view_->set_adjust_if_offscreen(false);
    266   CreateItemViews(login_status);
    267 
    268   if (bubble_view_->CanActivate()) {
    269     bubble_view_->NotifyAccessibilityEvent(
    270         ui::AccessibilityTypes::EVENT_ALERT, true);
    271   }
    272 }
    273 
    274 void SystemTrayBubble::DestroyItemViews() {
    275   for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
    276        it != items_.end();
    277        ++it) {
    278     switch (bubble_type_) {
    279       case BUBBLE_TYPE_DEFAULT:
    280         (*it)->DestroyDefaultView();
    281         break;
    282       case BUBBLE_TYPE_DETAILED:
    283         (*it)->DestroyDetailedView();
    284         break;
    285       case BUBBLE_TYPE_NOTIFICATION:
    286         (*it)->DestroyNotificationView();
    287         break;
    288     }
    289   }
    290 }
    291 
    292 void SystemTrayBubble::BubbleViewDestroyed() {
    293   bubble_view_ = NULL;
    294 }
    295 
    296 void SystemTrayBubble::StartAutoCloseTimer(int seconds) {
    297   autoclose_.Stop();
    298   autoclose_delay_ = seconds;
    299   if (autoclose_delay_) {
    300     autoclose_.Start(FROM_HERE,
    301                      base::TimeDelta::FromSeconds(autoclose_delay_),
    302                      this, &SystemTrayBubble::Close);
    303   }
    304 }
    305 
    306 void SystemTrayBubble::StopAutoCloseTimer() {
    307   autoclose_.Stop();
    308 }
    309 
    310 void SystemTrayBubble::RestartAutoCloseTimer() {
    311   if (autoclose_delay_)
    312     StartAutoCloseTimer(autoclose_delay_);
    313 }
    314 
    315 void SystemTrayBubble::Close() {
    316   tray_->HideBubbleWithView(bubble_view());
    317 }
    318 
    319 void SystemTrayBubble::SetVisible(bool is_visible) {
    320   if (!bubble_view_)
    321     return;
    322   views::Widget* bubble_widget = bubble_view_->GetWidget();
    323   if (is_visible)
    324     bubble_widget->Show();
    325   else
    326     bubble_widget->Hide();
    327 }
    328 
    329 bool SystemTrayBubble::IsVisible() {
    330   return bubble_view() && bubble_view()->GetWidget()->IsVisible();
    331 }
    332 
    333 bool SystemTrayBubble::ShouldShowLauncher() const {
    334   for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin();
    335        it != items_.end();
    336        ++it) {
    337     if ((*it)->ShouldShowLauncher())
    338       return true;
    339   }
    340   return false;
    341 }
    342 
    343 void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) {
    344   std::vector<views::View*> item_views;
    345   for (size_t i = 0; i < items_.size(); ++i) {
    346     views::View* view = NULL;
    347     switch (bubble_type_) {
    348       case BUBBLE_TYPE_DEFAULT:
    349         view = items_[i]->CreateDefaultView(login_status);
    350         break;
    351       case BUBBLE_TYPE_DETAILED:
    352         view = items_[i]->CreateDetailedView(login_status);
    353         break;
    354       case BUBBLE_TYPE_NOTIFICATION:
    355         view = items_[i]->CreateNotificationView(login_status);
    356         break;
    357     }
    358     if (view)
    359       item_views.push_back(view);
    360   }
    361 
    362   bool is_default_bubble = bubble_type_ == BUBBLE_TYPE_DEFAULT;
    363   for (size_t i = 0; i < item_views.size(); ++i) {
    364     // For default view, draw bottom border for each item, except the last
    365     // 2 items, which are the bottom header row and the one just above it.
    366     bubble_view_->AddChildView(new TrayPopupItemContainer(
    367         item_views[i], is_default_bubble,
    368         is_default_bubble && (i < item_views.size() - 2)));
    369   }
    370 }
    371 
    372 }  // namespace internal
    373 }  // namespace ash
    374