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 SetBorder( 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_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL); 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 // SystemTrayBubble 131 132 SystemTrayBubble::SystemTrayBubble( 133 ash::SystemTray* tray, 134 const std::vector<ash::SystemTrayItem*>& items, 135 BubbleType bubble_type) 136 : tray_(tray), 137 bubble_view_(NULL), 138 items_(items), 139 bubble_type_(bubble_type), 140 autoclose_delay_(0) { 141 } 142 143 SystemTrayBubble::~SystemTrayBubble() { 144 DestroyItemViews(); 145 // Reset the host pointer in bubble_view_ in case its destruction is deferred. 146 if (bubble_view_) 147 bubble_view_->reset_delegate(); 148 } 149 150 void SystemTrayBubble::UpdateView( 151 const std::vector<ash::SystemTrayItem*>& items, 152 BubbleType bubble_type) { 153 DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION); 154 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(); 162 DCHECK(layer); 163 layer->SuppressPaint(); 164 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); 176 } 177 178 { 179 // Add a shadow layer to make the old layer darker as the animation 180 // progresses. 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()); 185 layer->Add(shadow); 186 layer->StackAtTop(shadow); 187 { 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); 199 } 200 } 201 } 202 203 DestroyItemViews(); 204 bubble_view_->RemoveAllChildViews(true); 205 206 items_ = items; 207 bubble_type_ = bubble_type; 208 CreateItemViews( 209 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()); 210 211 // Close bubble view if we failed to create the item view. 212 if (!bubble_view_->has_children()) { 213 Close(); 214 return; 215 } 216 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. 221 } 222 223 if (scoped_layer) { 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(); 228 229 // Make sure the new layer is stacked above the old layer during the 230 // animation. 231 new_layer->parent()->StackAbove(new_layer, scoped_layer.get()); 232 233 gfx::Rect bounds = new_layer->bounds(); 234 gfx::Transform transform; 235 transform.Translate(bounds.width(), 0.0); 236 new_layer->SetTransform(transform); 237 { 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()); 245 } 246 } 247 } 248 } 249 250 void SystemTrayBubble::InitView(views::View* anchor, 251 user::LoginStatus login_status, 252 TrayBubbleView::InitParams* init_params) { 253 DCHECK(bubble_view_ == NULL); 254 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; 260 } 261 bubble_view_ = TrayBubbleView::Create( 262 tray_->GetBubbleWindowContainer(), anchor, tray_, init_params); 263 bubble_view_->set_adjust_if_offscreen(false); 264 CreateItemViews(login_status); 265 266 if (bubble_view_->CanActivate()) { 267 bubble_view_->NotifyAccessibilityEvent( 268 ui::AX_EVENT_ALERT, true); 269 } 270 } 271 272 void SystemTrayBubble::FocusDefaultIfNeeded() { 273 views::FocusManager* manager = bubble_view_->GetFocusManager(); 274 if (!manager || manager->GetFocusedView()) 275 return; 276 277 views::View* view = manager->GetNextFocusableView(NULL, NULL, false, false); 278 if (view) 279 view->RequestFocus(); 280 } 281 282 void SystemTrayBubble::DestroyItemViews() { 283 for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin(); 284 it != items_.end(); 285 ++it) { 286 switch (bubble_type_) { 287 case BUBBLE_TYPE_DEFAULT: 288 (*it)->DestroyDefaultView(); 289 break; 290 case BUBBLE_TYPE_DETAILED: 291 (*it)->DestroyDetailedView(); 292 break; 293 case BUBBLE_TYPE_NOTIFICATION: 294 (*it)->DestroyNotificationView(); 295 break; 296 } 297 } 298 } 299 300 void SystemTrayBubble::BubbleViewDestroyed() { 301 bubble_view_ = NULL; 302 } 303 304 void SystemTrayBubble::StartAutoCloseTimer(int seconds) { 305 autoclose_.Stop(); 306 autoclose_delay_ = seconds; 307 if (autoclose_delay_) { 308 autoclose_.Start(FROM_HERE, 309 base::TimeDelta::FromSeconds(autoclose_delay_), 310 this, &SystemTrayBubble::Close); 311 } 312 } 313 314 void SystemTrayBubble::StopAutoCloseTimer() { 315 autoclose_.Stop(); 316 } 317 318 void SystemTrayBubble::RestartAutoCloseTimer() { 319 if (autoclose_delay_) 320 StartAutoCloseTimer(autoclose_delay_); 321 } 322 323 void SystemTrayBubble::Close() { 324 tray_->HideBubbleWithView(bubble_view()); 325 } 326 327 void SystemTrayBubble::SetVisible(bool is_visible) { 328 if (!bubble_view_) 329 return; 330 views::Widget* bubble_widget = bubble_view_->GetWidget(); 331 if (is_visible) 332 bubble_widget->Show(); 333 else 334 bubble_widget->Hide(); 335 } 336 337 bool SystemTrayBubble::IsVisible() { 338 return bubble_view() && bubble_view()->GetWidget()->IsVisible(); 339 } 340 341 bool SystemTrayBubble::ShouldShowShelf() const { 342 for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin(); 343 it != items_.end(); 344 ++it) { 345 if ((*it)->ShouldShowShelf()) 346 return true; 347 } 348 return false; 349 } 350 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 354 // in locked state. 355 if (Shell::GetInstance()->IsSystemModalWindowOpen() && 356 login_status != user::LOGGED_IN_NONE) { 357 login_status = user::LOGGED_IN_LOCKED; 358 } 359 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()) 367 focus_view = view; 368 break; 369 case BUBBLE_TYPE_DETAILED: 370 view = items_[i]->CreateDetailedView(login_status); 371 break; 372 case BUBBLE_TYPE_NOTIFICATION: 373 view = items_[i]->CreateNotificationView(login_status); 374 break; 375 } 376 if (view) 377 item_views.push_back(view); 378 } 379 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))); 387 } 388 if (focus_view) 389 focus_view->RequestFocus(); 390 } 391 392 } // namespace ash 393