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
->set_spread_blank_space(true);
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
);
134 SystemTrayBubble::SystemTrayBubble(
135 ash::SystemTray
* tray
,
136 const std::vector
<ash::SystemTrayItem
*>& items
,
137 BubbleType bubble_type
)
141 bubble_type_(bubble_type
),
142 autoclose_delay_(0) {
145 SystemTrayBubble::~SystemTrayBubble() {
147 // Reset the host pointer in bubble_view_ in case its destruction is deferred.
149 bubble_view_
->reset_delegate();
152 void SystemTrayBubble::UpdateView(
153 const std::vector
<ash::SystemTrayItem
*>& items
,
154 BubbleType bubble_type
) {
155 DCHECK(bubble_type
!= BUBBLE_TYPE_NOTIFICATION
);
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();
165 layer
->SuppressPaint();
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
);
181 // Add a shadow layer to make the old layer darker as the animation
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());
188 layer
->StackAtTop(shadow
);
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
);
206 bubble_view_
->RemoveAllChildViews(true);
209 bubble_type_
= bubble_type
;
211 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
213 // Close bubble view if we failed to create the item view.
214 if (!bubble_view_
->has_children()) {
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.
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();
231 // Make sure the new layer is stacked above the old layer during the
233 new_layer
->parent()->StackAbove(new_layer
, scoped_layer
.get());
235 gfx::Rect bounds
= new_layer
->bounds();
236 gfx::Transform transform
;
237 transform
.Translate(bounds
.width(), 0.0);
238 new_layer
->SetTransform(transform
);
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());
252 void SystemTrayBubble::InitView(views::View
* anchor
,
253 user::LoginStatus login_status
,
254 TrayBubbleView::InitParams
* init_params
) {
255 DCHECK(bubble_view_
== NULL
);
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;
263 bubble_view_
= TrayBubbleView::Create(
264 tray_
->GetBubbleWindowContainer(), anchor
, tray_
, init_params
);
265 bubble_view_
->set_adjust_if_offscreen(false);
266 CreateItemViews(login_status
);
268 if (bubble_view_
->CanActivate()) {
269 bubble_view_
->NotifyAccessibilityEvent(
270 ui::AccessibilityTypes::EVENT_ALERT
, true);
274 void SystemTrayBubble::DestroyItemViews() {
275 for (std::vector
<ash::SystemTrayItem
*>::iterator it
= items_
.begin();
278 switch (bubble_type_
) {
279 case BUBBLE_TYPE_DEFAULT
:
280 (*it
)->DestroyDefaultView();
282 case BUBBLE_TYPE_DETAILED
:
283 (*it
)->DestroyDetailedView();
285 case BUBBLE_TYPE_NOTIFICATION
:
286 (*it
)->DestroyNotificationView();
292 void SystemTrayBubble::BubbleViewDestroyed() {
296 void SystemTrayBubble::StartAutoCloseTimer(int seconds
) {
298 autoclose_delay_
= seconds
;
299 if (autoclose_delay_
) {
300 autoclose_
.Start(FROM_HERE
,
301 base::TimeDelta::FromSeconds(autoclose_delay_
),
302 this, &SystemTrayBubble::Close
);
306 void SystemTrayBubble::StopAutoCloseTimer() {
310 void SystemTrayBubble::RestartAutoCloseTimer() {
311 if (autoclose_delay_
)
312 StartAutoCloseTimer(autoclose_delay_
);
315 void SystemTrayBubble::Close() {
316 tray_
->HideBubbleWithView(bubble_view());
319 void SystemTrayBubble::SetVisible(bool is_visible
) {
322 views::Widget
* bubble_widget
= bubble_view_
->GetWidget();
324 bubble_widget
->Show();
326 bubble_widget
->Hide();
329 bool SystemTrayBubble::IsVisible() {
330 return bubble_view() && bubble_view()->GetWidget()->IsVisible();
333 bool SystemTrayBubble::ShouldShowLauncher() const {
334 for (std::vector
<ash::SystemTrayItem
*>::const_iterator it
= items_
.begin();
337 if ((*it
)->ShouldShowLauncher())
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
);
351 case BUBBLE_TYPE_DETAILED
:
352 view
= items_
[i
]->CreateDetailedView(login_status
);
354 case BUBBLE_TYPE_NOTIFICATION
:
355 view
= items_
[i
]->CreateNotificationView(login_status
);
359 item_views
.push_back(view
);
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)));
372 } // namespace internal