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