dillon - branch off of chromium-67.0.3396.87 and apply 67.bad-dfly
[chromium-dfly.git] / ui / message_center / views / toast_contents_view.cc
1 // Copyright (c) 2013 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 "ui/message_center/views/toast_contents_view.h"
6
7 #include <memory>
8
9 #include "base/bind.h"
10 #include "base/compiler_specific.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/time/time.h"
13 #include "build/build_config.h"
14 #include "ui/accessibility/ax_enums.mojom.h"
15 #include "ui/accessibility/ax_node_data.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_targeter.h"
18 #include "ui/display/display.h"
19 #include "ui/display/screen.h"
20 #include "ui/gfx/animation/animation_delegate.h"
21 #include "ui/gfx/animation/slide_animation.h"
22 #include "ui/message_center/public/cpp/message_center_constants.h"
23 #include "ui/message_center/public/cpp/notification.h"
24 #include "ui/message_center/views/message_popup_collection.h"
25 #include "ui/message_center/views/message_view.h"
26 #include "ui/message_center/views/popup_alignment_delegate.h"
27 #include "ui/views/background.h"
28 #include "ui/views/view.h"
29 #include "ui/views/widget/widget.h"
30 #include "ui/views/widget/widget_delegate.h"
31
32 #if defined(OS_WIN)
33 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
34 #endif
35
36 using display::Screen;
37
38 namespace message_center {
39 namespace {
40
41 // The width of a toast before animated reveal and after closing.
42 const int kClosedToastWidth = 5;
43
44 // FadeIn/Out look a bit better if they are slightly longer then default slide.
45 const int kFadeInOutDuration = 200;
46
47 }  // namespace.
48
49 // static
50 const char ToastContentsView::kViewClassName[] = "ToastContentsView";
51
52 // static
53 gfx::Size ToastContentsView::GetToastSizeForView(const views::View* view) {
54   int width = kNotificationWidth + view->GetInsets().width();
55   return gfx::Size(width, view->GetHeightForWidth(width));
56 }
57
58 ToastContentsView::ToastContentsView(
59     const std::string& notification_id,
60     PopupAlignmentDelegate* alignment_delegate,
61     base::WeakPtr<MessagePopupCollection> collection)
62     : collection_(collection),
63       id_(notification_id),
64       is_closing_(false),
65       closing_animation_(NULL) {
66   DCHECK(alignment_delegate);
67   set_notify_enter_exit_on_child(true);
68   // Sets the transparent background. Then, when the message view is slid out,
69   // the whole toast seems to slide although the actual bound of the widget
70   // remains. This is hacky but easier to keep the consistency.
71   SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
72
73   fade_animation_.reset(new gfx::SlideAnimation(this));
74   fade_animation_->SetSlideDuration(kFadeInOutDuration);
75
76   CreateWidget(alignment_delegate);
77 }
78
79 // This is destroyed when the toast window closes.
80 ToastContentsView::~ToastContentsView() {
81   if (collection_)
82     collection_->ForgetToast(this);
83 }
84
85 void ToastContentsView::SetContents(MessageView* view,
86                                     bool a11y_feedback_for_updates) {
87   message_view_ = view;
88   RemoveAllChildViews(true);
89   AddChildView(view);
90   UpdatePreferredSize();
91   if (a11y_feedback_for_updates)
92     NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
93 }
94
95 void ToastContentsView::UpdateContents(const Notification& notification,
96                                        bool a11y_feedback_for_updates) {
97   DCHECK_GT(child_count(), 0);
98   MessageView* message_view = static_cast<MessageView*>(child_at(0));
99   message_view->UpdateWithNotification(notification);
100   UpdatePreferredSize();
101   if (a11y_feedback_for_updates)
102     NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
103 }
104
105 void ToastContentsView::RevealWithAnimation(gfx::Point origin) {
106   // Place/move the toast widgets. Currently it stacks the widgets from the
107   // right-bottom of the work area.
108   // TODO(mukai): allow to specify the placement policy from outside of this
109   // class. The policy should be specified from preference on Windows, or
110   // the launcher alignment on ChromeOS.
111   origin_ = gfx::Point(origin.x() - preferred_size_.width(),
112                        origin.y() - preferred_size_.height());
113
114   gfx::Rect stable_bounds(origin_, preferred_size_);
115
116   SetBoundsInstantly(GetClosedToastBounds(stable_bounds));
117   StartFadeIn();
118   SetBoundsWithAnimation(stable_bounds);
119 }
120
121 void ToastContentsView::CloseWithAnimation() {
122   if (is_closing_)
123     return;
124   is_closing_ = true;
125   StartFadeOut();
126 }
127
128 void ToastContentsView::SetBoundsInstantly(gfx::Rect new_bounds) {
129   DCHECK(new_bounds.size().width() <= preferred_size_.width() &&
130          new_bounds.size().height() <= preferred_size_.height())
131       << "we can not display widget bigger than notification";
132
133   if (!GetWidget())
134     return;
135
136   if (new_bounds == GetWidget()->GetWindowBoundsInScreen())
137     return;
138
139   origin_ = new_bounds.origin();
140   GetWidget()->SetBounds(new_bounds);
141 }
142
143 void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds) {
144   DCHECK(new_bounds.size().width() <= preferred_size_.width() &&
145          new_bounds.size().height() <= preferred_size_.height())
146       << "we can not display widget bigger than notification";
147
148   if (!GetWidget())
149     return;
150
151   if (new_bounds == animated_bounds_end_)
152     return;
153
154   origin_ = new_bounds.origin();
155   // This picks up the current bounds, so if there was a previous animation
156   // half-done, the next one will pick up from the current location.
157   // This is the only place that should query current location of the Widget
158   // on screen, the rest should refer to the bounds_.
159   animated_bounds_start_ = GetWidget()->GetWindowBoundsInScreen();
160   animated_bounds_end_ = new_bounds;
161
162   if (bounds_animation_.get())
163     bounds_animation_->Stop();
164
165   bounds_animation_.reset(new gfx::SlideAnimation(this));
166   bounds_animation_->Show();
167 }
168
169 void ToastContentsView::StartFadeIn() {
170   fade_animation_->Stop();
171
172   GetWidget()->SetOpacity(0);
173   GetWidget()->ShowInactive();
174   fade_animation_->Reset(0);
175   fade_animation_->Show();
176 }
177
178 void ToastContentsView::StartFadeOut() {
179   fade_animation_->Stop();
180
181   closing_animation_ = (is_closing_ ? fade_animation_.get() : nullptr);
182   if (GetWidget()->GetLayer()->opacity() > 0.0) {
183     fade_animation_->Reset(1);
184     fade_animation_->Hide();
185   } else {
186     // If the layer is already transparent, do not trigger animation again.
187     // It happens when the toast is removed by touch gesture.
188     OnBoundsAnimationEndedOrCancelled(fade_animation_.get());
189   }
190 }
191
192 void ToastContentsView::OnBoundsAnimationEndedOrCancelled(
193     const gfx::Animation* animation) {
194   if (is_closing_ && closing_animation_ == animation && GetWidget()) {
195     views::Widget* widget = GetWidget();
196
197     // TODO(dewittj): This is a workaround to prevent a nasty bug where
198     // closing a transparent widget doesn't actually remove the window,
199     // causing entire areas of the screen to become unresponsive to clicks.
200     // See crbug.com/243469
201     widget->Hide();
202 #if defined(OS_WIN)
203     widget->SetOpacity(1.f);
204 #endif
205
206     widget->Close();
207   }
208 }
209
210 // gfx::AnimationDelegate
211 void ToastContentsView::AnimationProgressed(const gfx::Animation* animation) {
212   if (animation == bounds_animation_.get()) {
213     gfx::Rect current(animation->CurrentValueBetween(
214         animated_bounds_start_, animated_bounds_end_));
215     GetWidget()->SetBounds(current);
216   } else if (animation == fade_animation_.get()) {
217     GetWidget()->SetOpacity(
218         static_cast<float>(fade_animation_->GetCurrentValue()));
219   }
220 }
221
222 void ToastContentsView::AnimationEnded(const gfx::Animation* animation) {
223   OnBoundsAnimationEndedOrCancelled(animation);
224 }
225
226 void ToastContentsView::AnimationCanceled(
227     const gfx::Animation* animation) {
228   OnBoundsAnimationEndedOrCancelled(animation);
229 }
230
231 // views::WidgetDelegate
232 void ToastContentsView::WindowClosing() {
233   if (!is_closing_ && collection_)
234     collection_->ForgetToast(this);
235 }
236
237 void ToastContentsView::OnDisplayChanged() {
238   views::Widget* widget = GetWidget();
239   if (!widget)
240     return;
241
242   gfx::NativeView native_view = widget->GetNativeView();
243   if (!native_view || !collection_.get())
244     return;
245
246   collection_->OnDisplayMetricsChanged(
247       Screen::GetScreen()->GetDisplayNearestView(native_view));
248 }
249
250 void ToastContentsView::OnWorkAreaChanged() {
251   views::Widget* widget = GetWidget();
252   if (!widget)
253     return;
254
255   gfx::NativeView native_view = widget->GetNativeView();
256   if (!native_view || !collection_.get())
257     return;
258
259   collection_->OnDisplayMetricsChanged(
260       Screen::GetScreen()->GetDisplayNearestView(native_view));
261 }
262
263 void ToastContentsView::OnWidgetActivationChanged(views::Widget* widget,
264                                                   bool active) {
265   if (active)
266     collection_->PausePopupTimers();
267   else
268     collection_->RestartPopupTimers();
269 }
270
271 // views::View
272 void ToastContentsView::OnMouseEntered(const ui::MouseEvent& event) {
273   if (collection_)
274     collection_->OnMouseEntered(this);
275 }
276
277 void ToastContentsView::OnMouseExited(const ui::MouseEvent& event) {
278   if (collection_)
279     collection_->OnMouseExited(this);
280 }
281
282 void ToastContentsView::Layout() {
283   if (child_count() > 0) {
284     child_at(0)->SetBounds(
285         0, 0, preferred_size_.width(), preferred_size_.height());
286   }
287 }
288
289 gfx::Size ToastContentsView::CalculatePreferredSize() const {
290   return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size();
291 }
292
293 void ToastContentsView::UpdatePreferredSize() {
294   DCHECK_GT(child_count(), 0);
295   gfx::Size new_size = GetToastSizeForView(child_at(0));
296   if (preferred_size_ == new_size)
297     return;
298   // Growing notifications instantly can cause notification's to overlap
299   // And shrinking with animation, leaves area, where nothing is drawn
300   const bool change_instantly = preferred_size_.width() > new_size.width() ||
301                                 preferred_size_.height() > new_size.height();
302   preferred_size_ = new_size;
303   Layout();
304   if (change_instantly) {
305     SetBoundsInstantly(bounds());
306     return;
307   }
308   SetBoundsWithAnimation(bounds());
309 }
310
311 void ToastContentsView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
312   if (child_count() > 0)
313     child_at(0)->GetAccessibleNodeData(node_data);
314   node_data->role = ax::mojom::Role::kAlertDialog;
315 }
316
317 const char* ToastContentsView::GetClassName() const {
318   return kViewClassName;
319 }
320
321 void ToastContentsView::CreateWidget(
322     PopupAlignmentDelegate* alignment_delegate) {
323   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
324   params.keep_on_top = true;
325 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD)
326   params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
327 #else
328   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
329 #endif
330   params.delegate = this;
331   views::Widget* widget = new views::Widget();
332   alignment_delegate->ConfigureWidgetInitParamsForContainer(widget, &params);
333   widget->set_focus_on_creation(false);
334   widget->AddObserver(this);
335
336 #if defined(OS_WIN)
337   // We want to ensure that this toast always goes to the native desktop,
338   // not the Ash desktop (since there is already another toast contents view
339   // there.
340   if (!params.parent)
341     params.native_widget = new views::DesktopNativeWidgetAura(widget);
342 #endif
343
344   widget->Init(params);
345
346 #if defined(OS_CHROMEOS)
347   // On Chrome OS, this widget is shown in the shelf container. It means this
348   // widget would inherit the parent's window targeter (ShelfWindowTarget) by
349   // default. But it is not good for popup. So we override it with the normal
350   // WindowTargeter.
351   gfx::NativeWindow native_window = widget->GetNativeWindow();
352   native_window->SetEventTargeter(std::make_unique<aura::WindowTargeter>());
353 #endif
354 }
355
356 gfx::Rect ToastContentsView::GetClosedToastBounds(gfx::Rect bounds) {
357   return gfx::Rect(bounds.x() + bounds.width() - kClosedToastWidth,
358                    bounds.y(),
359                    kClosedToastWidth,
360                    bounds.height());
361 }
362
363 }  // namespace message_center