| OLD | NEW |
| (Empty) |
| 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/common/system/tray/system_tray_bubble.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 #include <vector> | |
| 9 | |
| 10 #include "ash/common/material_design/material_design_controller.h" | |
| 11 #include "ash/common/system/tray/system_tray.h" | |
| 12 #include "ash/common/system/tray/system_tray_delegate.h" | |
| 13 #include "ash/common/system/tray/system_tray_item.h" | |
| 14 #include "ash/common/system/tray/tray_bubble_wrapper.h" | |
| 15 #include "ash/common/system/tray/tray_constants.h" | |
| 16 #include "ash/common/system/tray/tray_popup_item_container.h" | |
| 17 #include "ash/common/wm_shell.h" | |
| 18 #include "base/metrics/histogram_macros.h" | |
| 19 #include "base/threading/thread_task_runner_handle.h" | |
| 20 #include "ui/compositor/layer.h" | |
| 21 #include "ui/compositor/layer_animation_observer.h" | |
| 22 #include "ui/compositor/scoped_layer_animation_settings.h" | |
| 23 #include "ui/gfx/canvas.h" | |
| 24 #include "ui/views/border.h" | |
| 25 #include "ui/views/layout/box_layout.h" | |
| 26 #include "ui/views/view.h" | |
| 27 #include "ui/views/widget/widget.h" | |
| 28 | |
| 29 using views::TrayBubbleView; | |
| 30 | |
| 31 namespace ash { | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 // Normally a detailed view is the same size as the default view. However, | |
| 36 // when showing a detailed view directly (e.g. clicking on a notification), | |
| 37 // we may not know the height of the default view, or the default view may | |
| 38 // be too short, so we use this as a default and minimum height for any | |
| 39 // detailed view. | |
| 40 int GetDetailedBubbleMaxHeight() { | |
| 41 return kTrayPopupItemMinHeight * 5; | |
| 42 } | |
| 43 | |
| 44 // Duration of swipe animation used when transitioning from a default to | |
| 45 // detailed view or vice versa. | |
| 46 const int kSwipeDelayMS = 150; | |
| 47 | |
| 48 // Extra bottom padding when showing the BUBBLE_TYPE_DEFAULT view. | |
| 49 const int kDefaultViewBottomPadding = 4; | |
| 50 | |
| 51 // Implicit animation observer that deletes itself and the layer at the end of | |
| 52 // the animation. | |
| 53 class AnimationObserverDeleteLayer : public ui::ImplicitAnimationObserver { | |
| 54 public: | |
| 55 explicit AnimationObserverDeleteLayer(ui::Layer* layer) : layer_(layer) {} | |
| 56 | |
| 57 ~AnimationObserverDeleteLayer() override {} | |
| 58 | |
| 59 void OnImplicitAnimationsCompleted() override { | |
| 60 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); | |
| 61 } | |
| 62 | |
| 63 private: | |
| 64 std::unique_ptr<ui::Layer> layer_; | |
| 65 | |
| 66 DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer); | |
| 67 }; | |
| 68 | |
| 69 } // namespace | |
| 70 | |
| 71 // SystemTrayBubble | |
| 72 | |
| 73 SystemTrayBubble::SystemTrayBubble( | |
| 74 ash::SystemTray* tray, | |
| 75 const std::vector<ash::SystemTrayItem*>& items, | |
| 76 BubbleType bubble_type) | |
| 77 : tray_(tray), | |
| 78 bubble_view_(nullptr), | |
| 79 items_(items), | |
| 80 bubble_type_(bubble_type), | |
| 81 autoclose_delay_(0) {} | |
| 82 | |
| 83 SystemTrayBubble::~SystemTrayBubble() { | |
| 84 DestroyItemViews(); | |
| 85 // Reset the host pointer in bubble_view_ in case its destruction is deferred. | |
| 86 if (bubble_view_) | |
| 87 bubble_view_->reset_delegate(); | |
| 88 } | |
| 89 | |
| 90 void SystemTrayBubble::UpdateView( | |
| 91 const std::vector<ash::SystemTrayItem*>& items, | |
| 92 BubbleType bubble_type) { | |
| 93 std::unique_ptr<ui::Layer> scoped_layer; | |
| 94 if (bubble_type != bubble_type_) { | |
| 95 base::TimeDelta swipe_duration = | |
| 96 base::TimeDelta::FromMilliseconds(kSwipeDelayMS); | |
| 97 scoped_layer = bubble_view_->RecreateLayer(); | |
| 98 // Keep the reference to layer as we need it after releasing it. | |
| 99 ui::Layer* layer = scoped_layer.get(); | |
| 100 DCHECK(layer); | |
| 101 layer->SuppressPaint(); | |
| 102 | |
| 103 // When transitioning from detailed view to default view, animate the | |
| 104 // existing view (slide out towards the right). | |
| 105 if (bubble_type == BUBBLE_TYPE_DEFAULT) { | |
| 106 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); | |
| 107 settings.AddObserver( | |
| 108 new AnimationObserverDeleteLayer(scoped_layer.release())); | |
| 109 settings.SetTransitionDuration(swipe_duration); | |
| 110 settings.SetTweenType(gfx::Tween::EASE_OUT); | |
| 111 gfx::Transform transform; | |
| 112 transform.Translate(layer->bounds().width(), 0.0); | |
| 113 layer->SetTransform(transform); | |
| 114 } | |
| 115 | |
| 116 { | |
| 117 // Add a shadow layer to make the old layer darker as the animation | |
| 118 // progresses. | |
| 119 ui::Layer* shadow = new ui::Layer(ui::LAYER_SOLID_COLOR); | |
| 120 shadow->SetColor(SK_ColorBLACK); | |
| 121 shadow->SetOpacity(0.01f); | |
| 122 shadow->SetBounds(layer->bounds()); | |
| 123 layer->Add(shadow); | |
| 124 layer->StackAtTop(shadow); | |
| 125 { | |
| 126 // Animate the darkening effect a little longer than the swipe-in. This | |
| 127 // is to make sure the darkening animation does not end up finishing | |
| 128 // early, because the dark layer goes away at the end of the animation, | |
| 129 // and there is a brief moment when the old view is still visible, but | |
| 130 // it does not have the shadow layer on top. | |
| 131 ui::ScopedLayerAnimationSettings settings(shadow->GetAnimator()); | |
| 132 settings.AddObserver(new AnimationObserverDeleteLayer(shadow)); | |
| 133 settings.SetTransitionDuration(swipe_duration + | |
| 134 base::TimeDelta::FromMilliseconds(150)); | |
| 135 settings.SetTweenType(gfx::Tween::LINEAR); | |
| 136 shadow->SetOpacity(0.15f); | |
| 137 } | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 DestroyItemViews(); | |
| 142 bubble_view_->RemoveAllChildViews(true); | |
| 143 | |
| 144 items_ = items; | |
| 145 bubble_type_ = bubble_type; | |
| 146 CreateItemViews(WmShell::Get()->system_tray_delegate()->GetUserLoginStatus()); | |
| 147 | |
| 148 // Close bubble view if we failed to create the item view. | |
| 149 if (!bubble_view_->has_children()) { | |
| 150 Close(); | |
| 151 return; | |
| 152 } | |
| 153 | |
| 154 UpdateBottomPadding(); | |
| 155 bubble_view_->GetWidget()->GetContentsView()->Layout(); | |
| 156 // Make sure that the bubble is large enough for the default view. | |
| 157 if (bubble_type_ == BUBBLE_TYPE_DEFAULT) { | |
| 158 bubble_view_->SetMaxHeight(0); // Clear max height limit. | |
| 159 } | |
| 160 | |
| 161 if (scoped_layer) { | |
| 162 // When transitioning from default view to detailed view, animate the new | |
| 163 // view (slide in from the right). | |
| 164 if (bubble_type == BUBBLE_TYPE_DETAILED) { | |
| 165 ui::Layer* new_layer = bubble_view_->layer(); | |
| 166 | |
| 167 // Make sure the new layer is stacked above the old layer during the | |
| 168 // animation. | |
| 169 new_layer->parent()->StackAbove(new_layer, scoped_layer.get()); | |
| 170 | |
| 171 gfx::Rect bounds = new_layer->bounds(); | |
| 172 gfx::Transform transform; | |
| 173 transform.Translate(bounds.width(), 0.0); | |
| 174 new_layer->SetTransform(transform); | |
| 175 { | |
| 176 ui::ScopedLayerAnimationSettings settings(new_layer->GetAnimator()); | |
| 177 settings.AddObserver( | |
| 178 new AnimationObserverDeleteLayer(scoped_layer.release())); | |
| 179 settings.SetTransitionDuration( | |
| 180 base::TimeDelta::FromMilliseconds(kSwipeDelayMS)); | |
| 181 settings.SetTweenType(gfx::Tween::EASE_OUT); | |
| 182 new_layer->SetTransform(gfx::Transform()); | |
| 183 } | |
| 184 } | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 void SystemTrayBubble::InitView(views::View* anchor, | |
| 189 LoginStatus login_status, | |
| 190 TrayBubbleView::InitParams* init_params) { | |
| 191 DCHECK(anchor); | |
| 192 DCHECK(!bubble_view_); | |
| 193 | |
| 194 if (bubble_type_ == BUBBLE_TYPE_DETAILED && | |
| 195 init_params->max_height < GetDetailedBubbleMaxHeight()) { | |
| 196 init_params->max_height = GetDetailedBubbleMaxHeight(); | |
| 197 } | |
| 198 | |
| 199 bubble_view_ = TrayBubbleView::Create(anchor, tray_, init_params); | |
| 200 UpdateBottomPadding(); | |
| 201 bubble_view_->set_adjust_if_offscreen(false); | |
| 202 CreateItemViews(login_status); | |
| 203 | |
| 204 if (bubble_view_->CanActivate()) { | |
| 205 bubble_view_->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 void SystemTrayBubble::FocusDefaultIfNeeded() { | |
| 210 views::FocusManager* manager = bubble_view_->GetFocusManager(); | |
| 211 if (!manager || manager->GetFocusedView()) | |
| 212 return; | |
| 213 | |
| 214 views::View* view = | |
| 215 manager->GetNextFocusableView(nullptr, nullptr, false, false); | |
| 216 // TODO(oshima): RequestFocus calls View::OnFocus even if the widget | |
| 217 // is not active (crbug.com/621791). Remove this check once the bug | |
| 218 // is fixed. | |
| 219 if (bubble_view_->GetWidget()->IsActive()) { | |
| 220 view->RequestFocus(); | |
| 221 } else { | |
| 222 manager->SetStoredFocusView(view); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 void SystemTrayBubble::DestroyItemViews() { | |
| 227 for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin(); | |
| 228 it != items_.end(); ++it) { | |
| 229 switch (bubble_type_) { | |
| 230 case BUBBLE_TYPE_DEFAULT: | |
| 231 (*it)->DestroyDefaultView(); | |
| 232 break; | |
| 233 case BUBBLE_TYPE_DETAILED: | |
| 234 (*it)->DestroyDetailedView(); | |
| 235 break; | |
| 236 } | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 void SystemTrayBubble::BubbleViewDestroyed() { | |
| 241 bubble_view_ = nullptr; | |
| 242 } | |
| 243 | |
| 244 void SystemTrayBubble::StartAutoCloseTimer(int seconds) { | |
| 245 autoclose_.Stop(); | |
| 246 autoclose_delay_ = seconds; | |
| 247 if (autoclose_delay_) { | |
| 248 autoclose_.Start(FROM_HERE, base::TimeDelta::FromSeconds(autoclose_delay_), | |
| 249 this, &SystemTrayBubble::Close); | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 void SystemTrayBubble::StopAutoCloseTimer() { | |
| 254 autoclose_.Stop(); | |
| 255 } | |
| 256 | |
| 257 void SystemTrayBubble::RestartAutoCloseTimer() { | |
| 258 if (autoclose_delay_) | |
| 259 StartAutoCloseTimer(autoclose_delay_); | |
| 260 } | |
| 261 | |
| 262 void SystemTrayBubble::Close() { | |
| 263 tray_->HideBubbleWithView(bubble_view()); | |
| 264 } | |
| 265 | |
| 266 void SystemTrayBubble::SetVisible(bool is_visible) { | |
| 267 if (!bubble_view_) | |
| 268 return; | |
| 269 views::Widget* bubble_widget = bubble_view_->GetWidget(); | |
| 270 if (is_visible) | |
| 271 bubble_widget->Show(); | |
| 272 else | |
| 273 bubble_widget->Hide(); | |
| 274 } | |
| 275 | |
| 276 bool SystemTrayBubble::IsVisible() { | |
| 277 return bubble_view() && bubble_view()->GetWidget()->IsVisible(); | |
| 278 } | |
| 279 | |
| 280 bool SystemTrayBubble::ShouldShowShelf() const { | |
| 281 for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin(); | |
| 282 it != items_.end(); ++it) { | |
| 283 if ((*it)->ShouldShowShelf()) | |
| 284 return true; | |
| 285 } | |
| 286 return false; | |
| 287 } | |
| 288 | |
| 289 void SystemTrayBubble::RecordVisibleRowMetrics() { | |
| 290 if (bubble_type_ != BUBBLE_TYPE_DEFAULT) | |
| 291 return; | |
| 292 | |
| 293 for (const std::pair<SystemTrayItem::UmaType, views::View*>& pair : | |
| 294 tray_item_view_map_) { | |
| 295 if (pair.second->visible() && | |
| 296 pair.first != SystemTrayItem::UMA_NOT_RECORDED) { | |
| 297 UMA_HISTOGRAM_ENUMERATION("Ash.SystemMenu.DefaultView.VisibleRows", | |
| 298 pair.first, SystemTrayItem::UMA_COUNT); | |
| 299 } | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 void SystemTrayBubble::UpdateBottomPadding() { | |
| 304 if (bubble_type_ == BUBBLE_TYPE_DEFAULT && | |
| 305 MaterialDesignController::IsSystemTrayMenuMaterial()) { | |
| 306 bubble_view_->SetBottomPadding(kDefaultViewBottomPadding); | |
| 307 } else { | |
| 308 bubble_view_->SetBottomPadding(0); | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 void SystemTrayBubble::CreateItemViews(LoginStatus login_status) { | |
| 313 tray_item_view_map_.clear(); | |
| 314 | |
| 315 // If a system modal dialog is present, create the same tray as | |
| 316 // in locked state. | |
| 317 if (WmShell::Get()->IsSystemModalWindowOpen() && | |
| 318 login_status != LoginStatus::NOT_LOGGED_IN) { | |
| 319 login_status = LoginStatus::LOCKED; | |
| 320 } | |
| 321 | |
| 322 std::vector<TrayPopupItemContainer*> item_containers; | |
| 323 views::View* focus_view = nullptr; | |
| 324 const bool is_default_bubble = bubble_type_ == BUBBLE_TYPE_DEFAULT; | |
| 325 for (size_t i = 0; i < items_.size(); ++i) { | |
| 326 views::View* item_view = nullptr; | |
| 327 switch (bubble_type_) { | |
| 328 case BUBBLE_TYPE_DEFAULT: | |
| 329 item_view = items_[i]->CreateDefaultView(login_status); | |
| 330 if (items_[i]->restore_focus()) | |
| 331 focus_view = item_view; | |
| 332 break; | |
| 333 case BUBBLE_TYPE_DETAILED: | |
| 334 item_view = items_[i]->CreateDetailedView(login_status); | |
| 335 break; | |
| 336 } | |
| 337 if (item_view) { | |
| 338 TrayPopupItemContainer* tray_popup_item_container = | |
| 339 new TrayPopupItemContainer( | |
| 340 item_view, | |
| 341 is_default_bubble && | |
| 342 !MaterialDesignController::IsSystemTrayMenuMaterial()); | |
| 343 bubble_view_->AddChildView(tray_popup_item_container); | |
| 344 item_containers.push_back(tray_popup_item_container); | |
| 345 tray_item_view_map_[items_[i]->uma_type()] = tray_popup_item_container; | |
| 346 } | |
| 347 } | |
| 348 | |
| 349 if (!MaterialDesignController::IsSystemTrayMenuMaterial()) { | |
| 350 // For default view, draw bottom border for each item, except the last | |
| 351 // 2 items, which are the bottom header row and the one just above it. | |
| 352 if (is_default_bubble) { | |
| 353 const int last_item_with_border = | |
| 354 static_cast<int>(item_containers.size()) - 2; | |
| 355 for (int i = 0; i < last_item_with_border; ++i) { | |
| 356 item_containers.at(i)->SetBorder( | |
| 357 views::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor)); | |
| 358 } | |
| 359 } | |
| 360 } | |
| 361 | |
| 362 if (focus_view) { | |
| 363 tray_->ActivateBubble(); | |
| 364 focus_view->RequestFocus(); | |
| 365 } | |
| 366 } | |
| 367 | |
| 368 } // namespace ash | |
| OLD | NEW |