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: 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 set_border( 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_spread_blank_space(true); 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 65 virtual ~TrayPopupItemContainer() {} 66 67 private: 68 // Overridden from views::View. 69 virtual void ChildVisibilityChanged(View* child) OVERRIDE { 70 if (visible() == child->visible()) 71 return; 72 SetVisible(child->visible()); 73 PreferredSizeChanged(); 74 } 75 76 virtual void ChildPreferredSizeChanged(View* child) OVERRIDE { 77 PreferredSizeChanged(); 78 } 79 80 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE { 81 hover_ = true; 82 SchedulePaint(); 83 } 84 85 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE { 86 hover_ = false; 87 SchedulePaint(); 88 } 89 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: 111 explicit AnimationObserverDeleteLayer(ui::Layer* layer) 112 : layer_(layer) { 113 } 114 115 virtual ~AnimationObserverDeleteLayer() { 116 } 117 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 namespace internal { 131 132 // SystemTrayBubble 133 134 SystemTrayBubble::SystemTrayBubble( 135 ash::SystemTray* tray, 136 const std::vector<ash::SystemTrayItem*>& items, 137 BubbleType bubble_type) 138 : tray_(tray), 139 bubble_view_(NULL), 140 items_(items), 141 bubble_type_(bubble_type), 142 autoclose_delay_(0) { 143 } 144 145 SystemTrayBubble::~SystemTrayBubble() { 146 DestroyItemViews(); 147 // Reset the host pointer in bubble_view_ in case its destruction is deferred. 148 if (bubble_view_) 149 bubble_view_->reset_delegate(); 150 } 151 152 void SystemTrayBubble::UpdateView( 153 const std::vector<ash::SystemTrayItem*>& items, 154 BubbleType bubble_type) { 155 DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION); 156 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(); 164 DCHECK(layer); 165 layer->SuppressPaint(); 166 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(gfx::Tween::EASE_OUT); 175 gfx::Transform transform; 176 transform.Translate(layer->bounds().width(), 0.0); 177 layer->SetTransform(transform); 178 } 179 180 { 181 // Add a shadow layer to make the old layer darker as the animation 182 // progresses. 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()); 187 layer->Add(shadow); 188 layer->StackAtTop(shadow); 189 { 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(gfx::Tween::LINEAR); 200 shadow->SetOpacity(0.15f); 201 } 202 } 203 } 204 205 DestroyItemViews(); 206 bubble_view_->RemoveAllChildViews(true); 207 208 items_ = items; 209 bubble_type_ = bubble_type; 210 CreateItemViews( 211 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()); 212 213 // Close bubble view if we failed to create the item view. 214 if (!bubble_view_->has_children()) { 215 Close(); 216 return; 217 } 218 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. 223 } 224 225 if (scoped_layer) { 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(); 230 231 // Make sure the new layer is stacked above the old layer during the 232 // animation. 233 new_layer->parent()->StackAbove(new_layer, scoped_layer.get()); 234 235 gfx::Rect bounds = new_layer->bounds(); 236 gfx::Transform transform; 237 transform.Translate(bounds.width(), 0.0); 238 new_layer->SetTransform(transform); 239 { 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(gfx::Tween::EASE_OUT); 246 new_layer->SetTransform(gfx::Transform()); 247 } 248 } 249 } 250 } 251 252 void SystemTrayBubble::InitView(views::View* anchor, 253 user::LoginStatus login_status, 254 TrayBubbleView::InitParams* init_params) { 255 DCHECK(bubble_view_ == NULL); 256 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; 262 } 263 bubble_view_ = TrayBubbleView::Create( 264 tray_->GetBubbleWindowContainer(), anchor, tray_, init_params); 265 bubble_view_->set_adjust_if_offscreen(false); 266 CreateItemViews(login_status); 267 268 if (bubble_view_->CanActivate()) { 269 bubble_view_->NotifyAccessibilityEvent( 270 ui::AccessibilityTypes::EVENT_ALERT, true); 271 } 272 } 273 274 void SystemTrayBubble::FocusDefaultIfNeeded() { 275 views::FocusManager* manager = bubble_view_->GetFocusManager(); 276 if (!manager || manager->GetFocusedView()) 277 return; 278 279 views::View* view = manager->GetNextFocusableView(NULL, NULL, false, false); 280 if (view) 281 view->RequestFocus(); 282 } 283 284 void SystemTrayBubble::DestroyItemViews() { 285 for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin(); 286 it != items_.end(); 287 ++it) { 288 switch (bubble_type_) { 289 case BUBBLE_TYPE_DEFAULT: 290 (*it)->DestroyDefaultView(); 291 break; 292 case BUBBLE_TYPE_DETAILED: 293 (*it)->DestroyDetailedView(); 294 break; 295 case BUBBLE_TYPE_NOTIFICATION: 296 (*it)->DestroyNotificationView(); 297 break; 298 } 299 } 300 } 301 302 void SystemTrayBubble::BubbleViewDestroyed() { 303 bubble_view_ = NULL; 304 } 305 306 void SystemTrayBubble::StartAutoCloseTimer(int seconds) { 307 autoclose_.Stop(); 308 autoclose_delay_ = seconds; 309 if (autoclose_delay_) { 310 autoclose_.Start(FROM_HERE, 311 base::TimeDelta::FromSeconds(autoclose_delay_), 312 this, &SystemTrayBubble::Close); 313 } 314 } 315 316 void SystemTrayBubble::StopAutoCloseTimer() { 317 autoclose_.Stop(); 318 } 319 320 void SystemTrayBubble::RestartAutoCloseTimer() { 321 if (autoclose_delay_) 322 StartAutoCloseTimer(autoclose_delay_); 323 } 324 325 void SystemTrayBubble::Close() { 326 tray_->HideBubbleWithView(bubble_view()); 327 } 328 329 void SystemTrayBubble::SetVisible(bool is_visible) { 330 if (!bubble_view_) 331 return; 332 views::Widget* bubble_widget = bubble_view_->GetWidget(); 333 if (is_visible) 334 bubble_widget->Show(); 335 else 336 bubble_widget->Hide(); 337 } 338 339 bool SystemTrayBubble::IsVisible() { 340 return bubble_view() && bubble_view()->GetWidget()->IsVisible(); 341 } 342 343 bool SystemTrayBubble::ShouldShowLauncher() const { 344 for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin(); 345 it != items_.end(); 346 ++it) { 347 if ((*it)->ShouldShowLauncher()) 348 return true; 349 } 350 return false; 351 } 352 353 void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) { 354 std::vector<views::View*> item_views; 355 views::View* focus_view = NULL; 356 for (size_t i = 0; i < items_.size(); ++i) { 357 views::View* view = NULL; 358 switch (bubble_type_) { 359 case BUBBLE_TYPE_DEFAULT: 360 view = items_[i]->CreateDefaultView(login_status); 361 if (items_[i]->restore_focus()) 362 focus_view = view; 363 break; 364 case BUBBLE_TYPE_DETAILED: 365 view = items_[i]->CreateDetailedView(login_status); 366 break; 367 case BUBBLE_TYPE_NOTIFICATION: 368 view = items_[i]->CreateNotificationView(login_status); 369 break; 370 } 371 if (view) 372 item_views.push_back(view); 373 } 374 375 bool is_default_bubble = bubble_type_ == BUBBLE_TYPE_DEFAULT; 376 for (size_t i = 0; i < item_views.size(); ++i) { 377 // For default view, draw bottom border for each item, except the last 378 // 2 items, which are the bottom header row and the one just above it. 379 bubble_view_->AddChildView(new TrayPopupItemContainer( 380 item_views[i], is_default_bubble, 381 is_default_bubble && (i < item_views.size() - 2))); 382 } 383 if (focus_view) 384 focus_view->RequestFocus(); 385 } 386 387 } // namespace internal 388 } // namespace ash 389