• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
TrayPopupItemContainer(views::View * view,bool change_background,bool draw_border)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       SetBorder(
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_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
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 
~TrayPopupItemContainer()65   virtual ~TrayPopupItemContainer() {}
66 
67  private:
68   // Overridden from views::View.
ChildVisibilityChanged(View * child)69   virtual void ChildVisibilityChanged(View* child) OVERRIDE {
70     if (visible() == child->visible())
71       return;
72     SetVisible(child->visible());
73     PreferredSizeChanged();
74   }
75 
ChildPreferredSizeChanged(View * child)76   virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
77     PreferredSizeChanged();
78   }
79 
OnMouseEntered(const ui::MouseEvent & event)80   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
81     hover_ = true;
82     SchedulePaint();
83   }
84 
OnMouseExited(const ui::MouseEvent & event)85   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
86     hover_ = false;
87     SchedulePaint();
88   }
89 
OnPaintBackground(gfx::Canvas * canvas)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:
AnimationObserverDeleteLayer(ui::Layer * layer)111   explicit AnimationObserverDeleteLayer(ui::Layer* layer)
112       : layer_(layer) {
113   }
114 
~AnimationObserverDeleteLayer()115   virtual ~AnimationObserverDeleteLayer() {
116   }
117 
OnImplicitAnimationsCompleted()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 // SystemTrayBubble
131 
SystemTrayBubble(ash::SystemTray * tray,const std::vector<ash::SystemTrayItem * > & items,BubbleType bubble_type)132 SystemTrayBubble::SystemTrayBubble(
133     ash::SystemTray* tray,
134     const std::vector<ash::SystemTrayItem*>& items,
135     BubbleType bubble_type)
136     : tray_(tray),
137       bubble_view_(NULL),
138       items_(items),
139       bubble_type_(bubble_type),
140       autoclose_delay_(0) {
141 }
142 
~SystemTrayBubble()143 SystemTrayBubble::~SystemTrayBubble() {
144   DestroyItemViews();
145   // Reset the host pointer in bubble_view_ in case its destruction is deferred.
146   if (bubble_view_)
147     bubble_view_->reset_delegate();
148 }
149 
UpdateView(const std::vector<ash::SystemTrayItem * > & items,BubbleType bubble_type)150 void SystemTrayBubble::UpdateView(
151     const std::vector<ash::SystemTrayItem*>& items,
152     BubbleType bubble_type) {
153   DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION);
154 
155   scoped_ptr<ui::Layer> scoped_layer;
156   if (bubble_type != bubble_type_) {
157     base::TimeDelta swipe_duration =
158         base::TimeDelta::FromMilliseconds(kSwipeDelayMS);
159     scoped_layer = bubble_view_->RecreateLayer();
160     // Keep the reference to layer as we need it after releasing it.
161     ui::Layer* layer = scoped_layer.get();
162     DCHECK(layer);
163     layer->SuppressPaint();
164 
165     // When transitioning from detailed view to default view, animate the
166     // existing view (slide out towards the right).
167     if (bubble_type == BUBBLE_TYPE_DEFAULT) {
168       ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
169       settings.AddObserver(
170           new AnimationObserverDeleteLayer(scoped_layer.release()));
171       settings.SetTransitionDuration(swipe_duration);
172       settings.SetTweenType(gfx::Tween::EASE_OUT);
173       gfx::Transform transform;
174       transform.Translate(layer->bounds().width(), 0.0);
175       layer->SetTransform(transform);
176     }
177 
178     {
179       // Add a shadow layer to make the old layer darker as the animation
180       // progresses.
181       ui::Layer* shadow = new ui::Layer(ui::LAYER_SOLID_COLOR);
182       shadow->SetColor(SK_ColorBLACK);
183       shadow->SetOpacity(0.01f);
184       shadow->SetBounds(layer->bounds());
185       layer->Add(shadow);
186       layer->StackAtTop(shadow);
187       {
188         // Animate the darkening effect a little longer than the swipe-in. This
189         // is to make sure the darkening animation does not end up finishing
190         // early, because the dark layer goes away at the end of the animation,
191         // and there is a brief moment when the old view is still visible, but
192         // it does not have the shadow layer on top.
193         ui::ScopedLayerAnimationSettings settings(shadow->GetAnimator());
194         settings.AddObserver(new AnimationObserverDeleteLayer(shadow));
195         settings.SetTransitionDuration(swipe_duration +
196                                        base::TimeDelta::FromMilliseconds(150));
197         settings.SetTweenType(gfx::Tween::LINEAR);
198         shadow->SetOpacity(0.15f);
199       }
200     }
201   }
202 
203   DestroyItemViews();
204   bubble_view_->RemoveAllChildViews(true);
205 
206   items_ = items;
207   bubble_type_ = bubble_type;
208   CreateItemViews(
209       Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
210 
211   // Close bubble view if we failed to create the item view.
212   if (!bubble_view_->has_children()) {
213     Close();
214     return;
215   }
216 
217   bubble_view_->GetWidget()->GetContentsView()->Layout();
218   // Make sure that the bubble is large enough for the default view.
219   if (bubble_type_ == BUBBLE_TYPE_DEFAULT) {
220     bubble_view_->SetMaxHeight(0);  // Clear max height limit.
221   }
222 
223   if (scoped_layer) {
224     // When transitioning from default view to detailed view, animate the new
225     // view (slide in from the right).
226     if (bubble_type == BUBBLE_TYPE_DETAILED) {
227       ui::Layer* new_layer = bubble_view_->layer();
228 
229       // Make sure the new layer is stacked above the old layer during the
230       // animation.
231       new_layer->parent()->StackAbove(new_layer, scoped_layer.get());
232 
233       gfx::Rect bounds = new_layer->bounds();
234       gfx::Transform transform;
235       transform.Translate(bounds.width(), 0.0);
236       new_layer->SetTransform(transform);
237       {
238         ui::ScopedLayerAnimationSettings settings(new_layer->GetAnimator());
239         settings.AddObserver(
240             new AnimationObserverDeleteLayer(scoped_layer.release()));
241         settings.SetTransitionDuration(
242             base::TimeDelta::FromMilliseconds(kSwipeDelayMS));
243         settings.SetTweenType(gfx::Tween::EASE_OUT);
244         new_layer->SetTransform(gfx::Transform());
245       }
246     }
247   }
248 }
249 
InitView(views::View * anchor,user::LoginStatus login_status,TrayBubbleView::InitParams * init_params)250 void SystemTrayBubble::InitView(views::View* anchor,
251                                 user::LoginStatus login_status,
252                                 TrayBubbleView::InitParams* init_params) {
253   DCHECK(bubble_view_ == NULL);
254 
255   if (bubble_type_ == BUBBLE_TYPE_DETAILED &&
256       init_params->max_height < kDetailedBubbleMaxHeight) {
257     init_params->max_height = kDetailedBubbleMaxHeight;
258   } else if (bubble_type_ == BUBBLE_TYPE_NOTIFICATION) {
259     init_params->close_on_deactivate = false;
260   }
261   bubble_view_ = TrayBubbleView::Create(
262       tray_->GetBubbleWindowContainer(), anchor, tray_, init_params);
263   bubble_view_->set_adjust_if_offscreen(false);
264   CreateItemViews(login_status);
265 
266   if (bubble_view_->CanActivate()) {
267     bubble_view_->NotifyAccessibilityEvent(
268         ui::AX_EVENT_ALERT, true);
269   }
270 }
271 
FocusDefaultIfNeeded()272 void SystemTrayBubble::FocusDefaultIfNeeded() {
273   views::FocusManager* manager = bubble_view_->GetFocusManager();
274   if (!manager || manager->GetFocusedView())
275     return;
276 
277   views::View* view = manager->GetNextFocusableView(NULL, NULL, false, false);
278   if (view)
279     view->RequestFocus();
280 }
281 
DestroyItemViews()282 void SystemTrayBubble::DestroyItemViews() {
283   for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
284        it != items_.end();
285        ++it) {
286     switch (bubble_type_) {
287       case BUBBLE_TYPE_DEFAULT:
288         (*it)->DestroyDefaultView();
289         break;
290       case BUBBLE_TYPE_DETAILED:
291         (*it)->DestroyDetailedView();
292         break;
293       case BUBBLE_TYPE_NOTIFICATION:
294         (*it)->DestroyNotificationView();
295         break;
296     }
297   }
298 }
299 
BubbleViewDestroyed()300 void SystemTrayBubble::BubbleViewDestroyed() {
301   bubble_view_ = NULL;
302 }
303 
StartAutoCloseTimer(int seconds)304 void SystemTrayBubble::StartAutoCloseTimer(int seconds) {
305   autoclose_.Stop();
306   autoclose_delay_ = seconds;
307   if (autoclose_delay_) {
308     autoclose_.Start(FROM_HERE,
309                      base::TimeDelta::FromSeconds(autoclose_delay_),
310                      this, &SystemTrayBubble::Close);
311   }
312 }
313 
StopAutoCloseTimer()314 void SystemTrayBubble::StopAutoCloseTimer() {
315   autoclose_.Stop();
316 }
317 
RestartAutoCloseTimer()318 void SystemTrayBubble::RestartAutoCloseTimer() {
319   if (autoclose_delay_)
320     StartAutoCloseTimer(autoclose_delay_);
321 }
322 
Close()323 void SystemTrayBubble::Close() {
324   tray_->HideBubbleWithView(bubble_view());
325 }
326 
SetVisible(bool is_visible)327 void SystemTrayBubble::SetVisible(bool is_visible) {
328   if (!bubble_view_)
329     return;
330   views::Widget* bubble_widget = bubble_view_->GetWidget();
331   if (is_visible)
332     bubble_widget->Show();
333   else
334     bubble_widget->Hide();
335 }
336 
IsVisible()337 bool SystemTrayBubble::IsVisible() {
338   return bubble_view() && bubble_view()->GetWidget()->IsVisible();
339 }
340 
ShouldShowShelf() const341 bool SystemTrayBubble::ShouldShowShelf() const {
342   for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin();
343        it != items_.end();
344        ++it) {
345     if ((*it)->ShouldShowShelf())
346       return true;
347   }
348   return false;
349 }
350 
CreateItemViews(user::LoginStatus login_status)351 void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) {
352   std::vector<views::View*> item_views;
353   // If a system modal dialog is present, create the same tray as
354   // in locked state.
355   if (Shell::GetInstance()->IsSystemModalWindowOpen() &&
356       login_status != user::LOGGED_IN_NONE) {
357     login_status = user::LOGGED_IN_LOCKED;
358   }
359 
360   views::View* focus_view = NULL;
361   for (size_t i = 0; i < items_.size(); ++i) {
362     views::View* view = NULL;
363     switch (bubble_type_) {
364       case BUBBLE_TYPE_DEFAULT:
365         view = items_[i]->CreateDefaultView(login_status);
366         if (items_[i]->restore_focus())
367           focus_view = view;
368         break;
369       case BUBBLE_TYPE_DETAILED:
370         view = items_[i]->CreateDetailedView(login_status);
371         break;
372       case BUBBLE_TYPE_NOTIFICATION:
373         view = items_[i]->CreateNotificationView(login_status);
374         break;
375     }
376     if (view)
377       item_views.push_back(view);
378   }
379 
380   bool is_default_bubble = bubble_type_ == BUBBLE_TYPE_DEFAULT;
381   for (size_t i = 0; i < item_views.size(); ++i) {
382     // For default view, draw bottom border for each item, except the last
383     // 2 items, which are the bottom header row and the one just above it.
384     bubble_view_->AddChildView(new TrayPopupItemContainer(
385         item_views[i], is_default_bubble,
386         is_default_bubble && (i < item_views.size() - 2)));
387   }
388   if (focus_view)
389     focus_view->RequestFocus();
390 }
391 
392 }  // namespace ash
393