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.
5 #include "ash/system/tray/system_tray_bubble.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"
23 using views::TrayBubbleView
;
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
34 const int kDetailedBubbleMaxHeight
= kTrayPopupItemHeight
* 5;
36 // Duration of swipe animation used when transitioning from a default to
37 // detailed view or vice versa.
38 const int kSwipeDelayMS
= 150;
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
{
44 TrayPopupItemContainer(views::View
* view
,
45 bool change_background
,
48 change_background_(change_background
) {
49 set_notify_enter_exit_on_child(true);
52 views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor
));
54 views::BoxLayout
* layout
= new views::BoxLayout(
55 views::BoxLayout::kVertical
, 0, 0, 0);
56 layout
->SetDefaultFlex(1);
57 SetLayoutManager(layout
);
58 SetPaintToLayer(view
->layer() != NULL
);
60 SetFillsBoundsOpaquely(view
->layer()->fills_bounds_opaquely());
62 SetVisible(view
->visible());
65 virtual ~TrayPopupItemContainer() {}
68 // Overridden from views::View.
69 virtual void ChildVisibilityChanged(View
* child
) OVERRIDE
{
70 if (visible() == child
->visible())
72 SetVisible(child
->visible());
73 PreferredSizeChanged();
76 virtual void ChildPreferredSizeChanged(View
* child
) OVERRIDE
{
77 PreferredSizeChanged();
80 virtual void OnMouseEntered(const ui::MouseEvent
& event
) OVERRIDE
{
85 virtual void OnMouseExited(const ui::MouseEvent
& event
) OVERRIDE
{
90 virtual void OnPaintBackground(gfx::Canvas
* canvas
) OVERRIDE
{
91 if (child_count() == 0)
94 views::View
* view
= child_at(0);
95 if (!view
->background()) {
96 canvas
->FillRect(gfx::Rect(size()), (hover_
&& change_background_
) ?
97 kHoverBackgroundColor
: kBackgroundColor
);
102 bool change_background_
;
104 DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer
);
107 // Implicit animation observer that deletes itself and the layer at the end of
109 class AnimationObserverDeleteLayer
: public ui::ImplicitAnimationObserver
{
111 explicit AnimationObserverDeleteLayer(ui::Layer
* layer
)
115 virtual ~AnimationObserverDeleteLayer() {
118 virtual void OnImplicitAnimationsCompleted() OVERRIDE
{
119 base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE
, this);
123 scoped_ptr
<ui::Layer
> layer_
;
125 DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer
);
132 SystemTrayBubble::SystemTrayBubble(
133 ash::SystemTray
* tray
,
134 const std::vector
<ash::SystemTrayItem
*>& items
,
135 BubbleType bubble_type
)
139 bubble_type_(bubble_type
),
140 autoclose_delay_(0) {
143 SystemTrayBubble::~SystemTrayBubble() {
145 // Reset the host pointer in bubble_view_ in case its destruction is deferred.
147 bubble_view_
->reset_delegate();
150 void SystemTrayBubble::UpdateView(
151 const std::vector
<ash::SystemTrayItem
*>& items
,
152 BubbleType bubble_type
) {
153 DCHECK(bubble_type
!= BUBBLE_TYPE_NOTIFICATION
);
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();
163 layer
->SuppressPaint();
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
);
179 // Add a shadow layer to make the old layer darker as the animation
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());
186 layer
->StackAtTop(shadow
);
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
);
204 bubble_view_
->RemoveAllChildViews(true);
207 bubble_type_
= bubble_type
;
209 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
211 // Close bubble view if we failed to create the item view.
212 if (!bubble_view_
->has_children()) {
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.
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();
229 // Make sure the new layer is stacked above the old layer during the
231 new_layer
->parent()->StackAbove(new_layer
, scoped_layer
.get());
233 gfx::Rect bounds
= new_layer
->bounds();
234 gfx::Transform transform
;
235 transform
.Translate(bounds
.width(), 0.0);
236 new_layer
->SetTransform(transform
);
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());
250 void SystemTrayBubble::InitView(views::View
* anchor
,
251 user::LoginStatus login_status
,
252 TrayBubbleView::InitParams
* init_params
) {
253 DCHECK(bubble_view_
== NULL
);
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;
261 bubble_view_
= TrayBubbleView::Create(
262 tray_
->GetBubbleWindowContainer(), anchor
, tray_
, init_params
);
263 bubble_view_
->set_adjust_if_offscreen(false);
264 CreateItemViews(login_status
);
266 if (bubble_view_
->CanActivate()) {
267 bubble_view_
->NotifyAccessibilityEvent(
268 ui::AX_EVENT_ALERT
, true);
272 void SystemTrayBubble::FocusDefaultIfNeeded() {
273 views::FocusManager
* manager
= bubble_view_
->GetFocusManager();
274 if (!manager
|| manager
->GetFocusedView())
277 views::View
* view
= manager
->GetNextFocusableView(NULL
, NULL
, false, false);
279 view
->RequestFocus();
282 void SystemTrayBubble::DestroyItemViews() {
283 for (std::vector
<ash::SystemTrayItem
*>::iterator it
= items_
.begin();
286 switch (bubble_type_
) {
287 case BUBBLE_TYPE_DEFAULT
:
288 (*it
)->DestroyDefaultView();
290 case BUBBLE_TYPE_DETAILED
:
291 (*it
)->DestroyDetailedView();
293 case BUBBLE_TYPE_NOTIFICATION
:
294 (*it
)->DestroyNotificationView();
300 void SystemTrayBubble::BubbleViewDestroyed() {
304 void SystemTrayBubble::StartAutoCloseTimer(int seconds
) {
306 autoclose_delay_
= seconds
;
307 if (autoclose_delay_
) {
308 autoclose_
.Start(FROM_HERE
,
309 base::TimeDelta::FromSeconds(autoclose_delay_
),
310 this, &SystemTrayBubble::Close
);
314 void SystemTrayBubble::StopAutoCloseTimer() {
318 void SystemTrayBubble::RestartAutoCloseTimer() {
319 if (autoclose_delay_
)
320 StartAutoCloseTimer(autoclose_delay_
);
323 void SystemTrayBubble::Close() {
324 tray_
->HideBubbleWithView(bubble_view());
327 void SystemTrayBubble::SetVisible(bool is_visible
) {
330 views::Widget
* bubble_widget
= bubble_view_
->GetWidget();
332 bubble_widget
->Show();
334 bubble_widget
->Hide();
337 bool SystemTrayBubble::IsVisible() {
338 return bubble_view() && bubble_view()->GetWidget()->IsVisible();
341 bool SystemTrayBubble::ShouldShowShelf() const {
342 for (std::vector
<ash::SystemTrayItem
*>::const_iterator it
= items_
.begin();
345 if ((*it
)->ShouldShowShelf())
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
355 if (Shell::GetInstance()->IsSystemModalWindowOpen() &&
356 login_status
!= user::LOGGED_IN_NONE
) {
357 login_status
= user::LOGGED_IN_LOCKED
;
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())
369 case BUBBLE_TYPE_DETAILED
:
370 view
= items_
[i
]->CreateDetailedView(login_status
);
372 case BUBBLE_TYPE_NOTIFICATION
:
373 view
= items_
[i
]->CreateNotificationView(login_status
);
377 item_views
.push_back(view
);
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)));
389 focus_view
->RequestFocus();