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 "ash/system/tray/tray_popup_item_container.h"
14 #include "base/message_loop/message_loop.h"
15 #include "ui/aura/window.h"
16 #include "ui/compositor/layer.h"
17 #include "ui/compositor/layer_animation_observer.h"
18 #include "ui/compositor/scoped_layer_animation_settings.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/views/layout/box_layout.h"
21 #include "ui/views/view.h"
22 #include "ui/views/widget/widget.h"
24 using views::TrayBubbleView
;
30 // Normally a detailed view is the same size as the default view. However,
31 // when showing a detailed view directly (e.g. clicking on a notification),
32 // we may not know the height of the default view, or the default view may
33 // be too short, so we use this as a default and minimum height for any
35 const int kDetailedBubbleMaxHeight
= kTrayPopupItemHeight
* 5;
37 // Duration of swipe animation used when transitioning from a default to
38 // detailed view or vice versa.
39 const int kSwipeDelayMS
= 150;
41 // Implicit animation observer that deletes itself and the layer at the end of
43 class AnimationObserverDeleteLayer
: public ui::ImplicitAnimationObserver
{
45 explicit AnimationObserverDeleteLayer(ui::Layer
* layer
)
49 ~AnimationObserverDeleteLayer() override
{}
51 void OnImplicitAnimationsCompleted() override
{
52 base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE
, this);
56 scoped_ptr
<ui::Layer
> layer_
;
58 DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer
);
65 SystemTrayBubble::SystemTrayBubble(
66 ash::SystemTray
* tray
,
67 const std::vector
<ash::SystemTrayItem
*>& items
,
68 BubbleType bubble_type
)
72 bubble_type_(bubble_type
),
76 SystemTrayBubble::~SystemTrayBubble() {
78 // Reset the host pointer in bubble_view_ in case its destruction is deferred.
80 bubble_view_
->reset_delegate();
83 void SystemTrayBubble::UpdateView(
84 const std::vector
<ash::SystemTrayItem
*>& items
,
85 BubbleType bubble_type
) {
86 DCHECK(bubble_type
!= BUBBLE_TYPE_NOTIFICATION
);
88 scoped_ptr
<ui::Layer
> scoped_layer
;
89 if (bubble_type
!= bubble_type_
) {
90 base::TimeDelta swipe_duration
=
91 base::TimeDelta::FromMilliseconds(kSwipeDelayMS
);
92 scoped_layer
= bubble_view_
->RecreateLayer();
93 // Keep the reference to layer as we need it after releasing it.
94 ui::Layer
* layer
= scoped_layer
.get();
96 layer
->SuppressPaint();
98 // When transitioning from detailed view to default view, animate the
99 // existing view (slide out towards the right).
100 if (bubble_type
== BUBBLE_TYPE_DEFAULT
) {
101 ui::ScopedLayerAnimationSettings
settings(layer
->GetAnimator());
102 settings
.AddObserver(
103 new AnimationObserverDeleteLayer(scoped_layer
.release()));
104 settings
.SetTransitionDuration(swipe_duration
);
105 settings
.SetTweenType(gfx::Tween::EASE_OUT
);
106 gfx::Transform transform
;
107 transform
.Translate(layer
->bounds().width(), 0.0);
108 layer
->SetTransform(transform
);
112 // Add a shadow layer to make the old layer darker as the animation
114 ui::Layer
* shadow
= new ui::Layer(ui::LAYER_SOLID_COLOR
);
115 shadow
->SetColor(SK_ColorBLACK
);
116 shadow
->SetOpacity(0.01f
);
117 shadow
->SetBounds(layer
->bounds());
119 layer
->StackAtTop(shadow
);
121 // Animate the darkening effect a little longer than the swipe-in. This
122 // is to make sure the darkening animation does not end up finishing
123 // early, because the dark layer goes away at the end of the animation,
124 // and there is a brief moment when the old view is still visible, but
125 // it does not have the shadow layer on top.
126 ui::ScopedLayerAnimationSettings
settings(shadow
->GetAnimator());
127 settings
.AddObserver(new AnimationObserverDeleteLayer(shadow
));
128 settings
.SetTransitionDuration(swipe_duration
+
129 base::TimeDelta::FromMilliseconds(150));
130 settings
.SetTweenType(gfx::Tween::LINEAR
);
131 shadow
->SetOpacity(0.15f
);
137 bubble_view_
->RemoveAllChildViews(true);
140 bubble_type_
= bubble_type
;
142 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
144 // Close bubble view if we failed to create the item view.
145 if (!bubble_view_
->has_children()) {
150 bubble_view_
->GetWidget()->GetContentsView()->Layout();
151 // Make sure that the bubble is large enough for the default view.
152 if (bubble_type_
== BUBBLE_TYPE_DEFAULT
) {
153 bubble_view_
->SetMaxHeight(0); // Clear max height limit.
157 // When transitioning from default view to detailed view, animate the new
158 // view (slide in from the right).
159 if (bubble_type
== BUBBLE_TYPE_DETAILED
) {
160 ui::Layer
* new_layer
= bubble_view_
->layer();
162 // Make sure the new layer is stacked above the old layer during the
164 new_layer
->parent()->StackAbove(new_layer
, scoped_layer
.get());
166 gfx::Rect bounds
= new_layer
->bounds();
167 gfx::Transform transform
;
168 transform
.Translate(bounds
.width(), 0.0);
169 new_layer
->SetTransform(transform
);
171 ui::ScopedLayerAnimationSettings
settings(new_layer
->GetAnimator());
172 settings
.AddObserver(
173 new AnimationObserverDeleteLayer(scoped_layer
.release()));
174 settings
.SetTransitionDuration(
175 base::TimeDelta::FromMilliseconds(kSwipeDelayMS
));
176 settings
.SetTweenType(gfx::Tween::EASE_OUT
);
177 new_layer
->SetTransform(gfx::Transform());
183 void SystemTrayBubble::InitView(views::View
* anchor
,
184 user::LoginStatus login_status
,
185 TrayBubbleView::InitParams
* init_params
) {
186 DCHECK(bubble_view_
== NULL
);
188 if (bubble_type_
== BUBBLE_TYPE_DETAILED
&&
189 init_params
->max_height
< kDetailedBubbleMaxHeight
) {
190 init_params
->max_height
= kDetailedBubbleMaxHeight
;
191 } else if (bubble_type_
== BUBBLE_TYPE_NOTIFICATION
) {
192 init_params
->close_on_deactivate
= false;
194 bubble_view_
= TrayBubbleView::Create(
195 tray_
->GetBubbleWindowContainer(), anchor
, tray_
, init_params
);
196 bubble_view_
->set_adjust_if_offscreen(false);
197 CreateItemViews(login_status
);
199 if (bubble_view_
->CanActivate()) {
200 bubble_view_
->NotifyAccessibilityEvent(
201 ui::AX_EVENT_ALERT
, true);
205 void SystemTrayBubble::FocusDefaultIfNeeded() {
206 views::FocusManager
* manager
= bubble_view_
->GetFocusManager();
207 if (!manager
|| manager
->GetFocusedView())
210 views::View
* view
= manager
->GetNextFocusableView(NULL
, NULL
, false, false);
212 view
->RequestFocus();
215 void SystemTrayBubble::DestroyItemViews() {
216 for (std::vector
<ash::SystemTrayItem
*>::iterator it
= items_
.begin();
219 switch (bubble_type_
) {
220 case BUBBLE_TYPE_DEFAULT
:
221 (*it
)->DestroyDefaultView();
223 case BUBBLE_TYPE_DETAILED
:
224 (*it
)->DestroyDetailedView();
226 case BUBBLE_TYPE_NOTIFICATION
:
227 (*it
)->DestroyNotificationView();
233 void SystemTrayBubble::BubbleViewDestroyed() {
237 void SystemTrayBubble::StartAutoCloseTimer(int seconds
) {
239 autoclose_delay_
= seconds
;
240 if (autoclose_delay_
) {
241 autoclose_
.Start(FROM_HERE
,
242 base::TimeDelta::FromSeconds(autoclose_delay_
),
243 this, &SystemTrayBubble::Close
);
247 void SystemTrayBubble::StopAutoCloseTimer() {
251 void SystemTrayBubble::RestartAutoCloseTimer() {
252 if (autoclose_delay_
)
253 StartAutoCloseTimer(autoclose_delay_
);
256 void SystemTrayBubble::Close() {
257 tray_
->HideBubbleWithView(bubble_view());
260 void SystemTrayBubble::SetVisible(bool is_visible
) {
263 views::Widget
* bubble_widget
= bubble_view_
->GetWidget();
265 bubble_widget
->Show();
267 bubble_widget
->Hide();
270 bool SystemTrayBubble::IsVisible() {
271 return bubble_view() && bubble_view()->GetWidget()->IsVisible();
274 bool SystemTrayBubble::ShouldShowShelf() const {
275 for (std::vector
<ash::SystemTrayItem
*>::const_iterator it
= items_
.begin();
278 if ((*it
)->ShouldShowShelf())
284 void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status
) {
285 std::vector
<views::View
*> item_views
;
286 // If a system modal dialog is present, create the same tray as
288 if (Shell::GetInstance()->IsSystemModalWindowOpen() &&
289 login_status
!= user::LOGGED_IN_NONE
) {
290 login_status
= user::LOGGED_IN_LOCKED
;
293 views::View
* focus_view
= NULL
;
294 for (size_t i
= 0; i
< items_
.size(); ++i
) {
295 views::View
* view
= NULL
;
296 switch (bubble_type_
) {
297 case BUBBLE_TYPE_DEFAULT
:
298 view
= items_
[i
]->CreateDefaultView(login_status
);
299 if (items_
[i
]->restore_focus())
302 case BUBBLE_TYPE_DETAILED
:
303 view
= items_
[i
]->CreateDetailedView(login_status
);
305 case BUBBLE_TYPE_NOTIFICATION
:
306 view
= items_
[i
]->CreateNotificationView(login_status
);
310 item_views
.push_back(view
);
313 bool is_default_bubble
= bubble_type_
== BUBBLE_TYPE_DEFAULT
;
314 for (size_t i
= 0; i
< item_views
.size(); ++i
) {
315 // For default view, draw bottom border for each item, except the last
316 // 2 items, which are the bottom header row and the one just above it.
317 bubble_view_
->AddChildView(new TrayPopupItemContainer(
318 item_views
[i
], is_default_bubble
,
319 is_default_bubble
&& (i
< item_views
.size() - 2)));
322 focus_view
->RequestFocus();