system_tray_bubble.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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.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
23using views::TrayBubbleView;
24
25namespace ash {
26
27namespace {
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.
34const int kDetailedBubbleMaxHeight = kTrayPopupItemHeight * 5;
35
36// A view with some special behaviour for tray items in the popup:
37// - optionally changes background color on hover.
38class TrayPopupItemContainer : public views::View {
39 public:
40  TrayPopupItemContainer(views::View* view,
41                         ShelfAlignment alignment,
42                         bool change_background)
43      : hover_(false),
44        change_background_(change_background) {
45    set_notify_enter_exit_on_child(true);
46    views::BoxLayout* layout = new views::BoxLayout(
47        views::BoxLayout::kVertical, 0, 0, 0);
48    layout->set_spread_blank_space(true);
49    SetLayoutManager(layout);
50    SetPaintToLayer(view->layer() != NULL);
51    if (view->layer())
52      SetFillsBoundsOpaquely(view->layer()->fills_bounds_opaquely());
53    AddChildView(view);
54    SetVisible(view->visible());
55  }
56
57  virtual ~TrayPopupItemContainer() {}
58
59 private:
60  // Overridden from views::View.
61  virtual void ChildVisibilityChanged(View* child) OVERRIDE {
62    if (visible() == child->visible())
63      return;
64    SetVisible(child->visible());
65    PreferredSizeChanged();
66  }
67
68  virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
69    PreferredSizeChanged();
70  }
71
72  virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
73    hover_ = true;
74    SchedulePaint();
75  }
76
77  virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
78    hover_ = false;
79    SchedulePaint();
80  }
81
82  virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
83    if (child_count() == 0)
84      return;
85
86    views::View* view = child_at(0);
87    if (!view->background()) {
88      canvas->FillRect(gfx::Rect(size()), (hover_ && change_background_) ?
89          kHoverBackgroundColor : kBackgroundColor);
90    }
91  }
92
93  bool hover_;
94  bool change_background_;
95
96  DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer);
97};
98
99// Implicit animation observer that deletes itself and the layer at the end of
100// the animation.
101class AnimationObserverDeleteLayer : public ui::ImplicitAnimationObserver {
102 public:
103  explicit AnimationObserverDeleteLayer(ui::Layer* layer)
104      : layer_(layer) {
105  }
106
107  virtual ~AnimationObserverDeleteLayer() {
108  }
109
110  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
111    MessageLoopForUI::current()->DeleteSoon(FROM_HERE, this);
112  }
113
114 private:
115  scoped_ptr<ui::Layer> layer_;
116
117  DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer);
118};
119
120}  // namespace
121
122namespace internal {
123
124// SystemTrayBubble
125
126SystemTrayBubble::SystemTrayBubble(
127    ash::SystemTray* tray,
128    const std::vector<ash::SystemTrayItem*>& items,
129    BubbleType bubble_type)
130    : tray_(tray),
131      bubble_view_(NULL),
132      items_(items),
133      bubble_type_(bubble_type),
134      autoclose_delay_(0) {
135}
136
137SystemTrayBubble::~SystemTrayBubble() {
138  DestroyItemViews();
139  // Reset the host pointer in bubble_view_ in case its destruction is deferred.
140  if (bubble_view_)
141    bubble_view_->reset_delegate();
142}
143
144void SystemTrayBubble::UpdateView(
145    const std::vector<ash::SystemTrayItem*>& items,
146    BubbleType bubble_type) {
147  DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION);
148  DCHECK(bubble_type != bubble_type_);
149
150  const int kSwipeDelayMS = 150;
151  base::TimeDelta swipe_duration =
152      base::TimeDelta::FromMilliseconds(kSwipeDelayMS);
153  ui::Layer* layer = bubble_view_->RecreateLayer();
154  DCHECK(layer);
155  layer->SuppressPaint();
156
157  // When transitioning from detailed view to default view, animate the existing
158  // view (slide out towards the right).
159  if (bubble_type == BUBBLE_TYPE_DEFAULT) {
160    // Make sure the old view is visibile over the new view during the
161    // animation.
162    layer->parent()->StackAbove(layer, bubble_view_->layer());
163    ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
164    settings.AddObserver(new AnimationObserverDeleteLayer(layer));
165    settings.SetTransitionDuration(swipe_duration);
166    settings.SetTweenType(ui::Tween::EASE_OUT);
167    gfx::Transform transform;
168    transform.SetTranslateX(layer->bounds().width());
169    layer->SetTransform(transform);
170  }
171
172  {
173    // Add a shadow layer to make the old layer darker as the animation
174    // progresses.
175    ui::Layer* shadow = new ui::Layer(ui::LAYER_SOLID_COLOR);
176    shadow->SetColor(SK_ColorBLACK);
177    shadow->SetOpacity(0.01f);
178    shadow->SetBounds(layer->bounds());
179    layer->Add(shadow);
180    layer->StackAtTop(shadow);
181    {
182      // Animate the darkening effect a little longer than the swipe-in. This is
183      // to make sure the darkening animation does not end up finishing early,
184      // because the dark layer goes away at the end of the animation, and there
185      // is a brief moment when the old view is still visible, but it does not
186      // have the shadow layer on top.
187      ui::ScopedLayerAnimationSettings settings(shadow->GetAnimator());
188      settings.AddObserver(new AnimationObserverDeleteLayer(shadow));
189      settings.SetTransitionDuration(swipe_duration +
190                                     base::TimeDelta::FromMilliseconds(150));
191      settings.SetTweenType(ui::Tween::LINEAR);
192      shadow->SetOpacity(0.15f);
193    }
194  }
195
196  DestroyItemViews();
197  bubble_view_->RemoveAllChildViews(true);
198
199  items_ = items;
200  bubble_type_ = bubble_type;
201  CreateItemViews(Shell::GetInstance()->tray_delegate()->GetUserLoginStatus());
202
203  // Close bubble view if we failed to create the item view.
204  if (!bubble_view_->has_children()) {
205    Close();
206    return;
207  }
208
209  bubble_view_->GetWidget()->GetContentsView()->Layout();
210  // Make sure that the bubble is large enough for the default view.
211  if (bubble_type_ == BUBBLE_TYPE_DEFAULT) {
212    bubble_view_->SetMaxHeight(0);  // Clear max height limit.
213  }
214
215  // When transitioning from default view to detailed view, animate the new
216  // view (slide in from the right).
217  if (bubble_type == BUBBLE_TYPE_DETAILED) {
218    ui::Layer* new_layer = bubble_view_->layer();
219    gfx::Rect bounds = new_layer->bounds();
220    gfx::Transform transform;
221    transform.SetTranslateX(bounds.width());
222    new_layer->SetTransform(transform);
223    {
224      ui::ScopedLayerAnimationSettings settings(new_layer->GetAnimator());
225      settings.AddObserver(new AnimationObserverDeleteLayer(layer));
226      settings.SetTransitionDuration(swipe_duration);
227      settings.SetTweenType(ui::Tween::EASE_OUT);
228      new_layer->SetTransform(gfx::Transform());
229    }
230  }
231}
232
233void SystemTrayBubble::InitView(views::View* anchor,
234                                user::LoginStatus login_status,
235                                TrayBubbleView::InitParams* init_params) {
236  DCHECK(bubble_view_ == NULL);
237
238  if (bubble_type_ == BUBBLE_TYPE_DETAILED &&
239      init_params->max_height < kDetailedBubbleMaxHeight) {
240    init_params->max_height = kDetailedBubbleMaxHeight;
241  } else if (bubble_type_ == BUBBLE_TYPE_NOTIFICATION) {
242    init_params->close_on_deactivate = false;
243  }
244  bubble_view_ = TrayBubbleView::Create(
245      tray_->GetBubbleWindowContainer(), anchor, tray_, init_params);
246
247  CreateItemViews(login_status);
248}
249
250void SystemTrayBubble::DestroyItemViews() {
251  for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
252       it != items_.end();
253       ++it) {
254    switch (bubble_type_) {
255      case BUBBLE_TYPE_DEFAULT:
256        (*it)->DestroyDefaultView();
257        break;
258      case BUBBLE_TYPE_DETAILED:
259        (*it)->DestroyDetailedView();
260        break;
261      case BUBBLE_TYPE_NOTIFICATION:
262        (*it)->DestroyNotificationView();
263        break;
264    }
265  }
266}
267
268void SystemTrayBubble::BubbleViewDestroyed() {
269  bubble_view_ = NULL;
270}
271
272void SystemTrayBubble::StartAutoCloseTimer(int seconds) {
273  autoclose_.Stop();
274  autoclose_delay_ = seconds;
275  if (autoclose_delay_) {
276    autoclose_.Start(FROM_HERE,
277                     base::TimeDelta::FromSeconds(autoclose_delay_),
278                     this, &SystemTrayBubble::Close);
279  }
280}
281
282void SystemTrayBubble::StopAutoCloseTimer() {
283  autoclose_.Stop();
284}
285
286void SystemTrayBubble::RestartAutoCloseTimer() {
287  if (autoclose_delay_)
288    StartAutoCloseTimer(autoclose_delay_);
289}
290
291void SystemTrayBubble::Close() {
292  tray_->HideBubbleWithView(bubble_view());
293}
294
295void SystemTrayBubble::SetVisible(bool is_visible) {
296  if (!bubble_view_)
297    return;
298  views::Widget* bubble_widget = bubble_view_->GetWidget();
299  if (is_visible)
300    bubble_widget->Show();
301  else
302    bubble_widget->Hide();
303}
304
305bool SystemTrayBubble::IsVisible() {
306  return bubble_view() && bubble_view()->GetWidget()->IsVisible();
307}
308
309bool SystemTrayBubble::ShouldShowLauncher() const {
310  for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin();
311       it != items_.end();
312       ++it) {
313    if ((*it)->ShouldShowLauncher())
314      return true;
315  }
316  return false;
317}
318
319void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) {
320  for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
321       it != items_.end();
322       ++it) {
323    views::View* view = NULL;
324    switch (bubble_type_) {
325      case BUBBLE_TYPE_DEFAULT:
326        view = (*it)->CreateDefaultView(login_status);
327        break;
328      case BUBBLE_TYPE_DETAILED:
329        view = (*it)->CreateDetailedView(login_status);
330        break;
331      case BUBBLE_TYPE_NOTIFICATION:
332        view = (*it)->CreateNotificationView(login_status);
333        break;
334    }
335    if (view) {
336      bubble_view_->AddChildView(new TrayPopupItemContainer(
337          view, tray_->shelf_alignment(), bubble_type_ == BUBBLE_TYPE_DEFAULT));
338    }
339  }
340}
341
342}  // namespace internal
343}  // namespace ash
344