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 |