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