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.
5 #include "ui/message_center/views/toast_contents_view.h"
8 #include "base/compiler_specific.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/memory/weak_ptr.h"
11 #include "base/time/time.h"
12 #include "base/timer/timer.h"
13 #include "ui/base/accessibility/accessible_view_state.h"
14 #include "ui/base/animation/animation_delegate.h"
15 #include "ui/base/animation/slide_animation.h"
16 #include "ui/gfx/display.h"
17 #include "ui/gfx/screen.h"
18 #include "ui/message_center/message_center.h"
19 #include "ui/message_center/message_center_style.h"
20 #include "ui/message_center/notification.h"
21 #include "ui/message_center/views/message_popup_collection.h"
22 #include "ui/message_center/views/message_view.h"
23 #include "ui/views/view.h"
24 #include "ui/views/widget/widget.h"
25 #include "ui/views/widget/widget_delegate.h"
27 namespace message_center
{
30 // The width of a toast before animated reveal and after closing.
31 const int kClosedToastWidth
= 5;
33 // FadeIn/Out look a bit better if they are slightly longer then default slide.
34 const int kFadeInOutDuration
= 200;
39 gfx::Size
ToastContentsView::GetToastSizeForView(views::View
* view
) {
40 int width
= kNotificationWidth
+ view
->GetInsets().width();
41 return gfx::Size(width
, view
->GetHeightForWidth(width
));
44 ToastContentsView::ToastContentsView(
45 const Notification
* notification
,
46 base::WeakPtr
<MessagePopupCollection
> collection
,
47 MessageCenter
* message_center
)
48 : collection_(collection
),
49 message_center_(message_center
),
50 id_(notification
->id()),
51 is_animating_bounds_(false),
53 closing_animation_(NULL
) {
56 set_notify_enter_exit_on_child(true);
57 // Sets the transparent background. Then, when the message view is slid out,
58 // the whole toast seems to slide although the actual bound of the widget
59 // remains. This is hacky but easier to keep the consistency.
60 set_background(views::Background::CreateSolidBackground(0, 0, 0, 0));
62 fade_animation_
.reset(new ui::SlideAnimation(this));
63 fade_animation_
->SetSlideDuration(kFadeInOutDuration
);
66 // This is destroyed when the toast window closes.
67 ToastContentsView::~ToastContentsView() {
70 views::Widget
* ToastContentsView::CreateWidget(gfx::NativeView parent
) {
71 views::Widget::InitParams
params(
72 views::Widget::InitParams::TYPE_POPUP
);
73 params
.keep_on_top
= true;
75 params
.parent
= parent
;
77 params
.top_level
= true;
78 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
79 params
.delegate
= this;
80 views::Widget
* widget
= new views::Widget();
81 widget
->set_focus_on_creation(false);
86 void ToastContentsView::SetContents(MessageView
* view
) {
87 bool already_has_contents
= child_count() > 0;
88 RemoveAllChildViews(true);
90 preferred_size_
= GetToastSizeForView(view
);
92 // If it has the contents already, this invocation means an update of the
93 // popup toast, and the new contents should be read through a11y feature.
94 if (already_has_contents
)
95 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_FOCUS
, false);
98 void ToastContentsView::RevealWithAnimation(gfx::Point origin
) {
99 // Place/move the toast widgets. Currently it stacks the widgets from the
100 // right-bottom of the work area.
101 // TODO(mukai): allow to specify the placement policy from outside of this
102 // class. The policy should be specified from preference on Windows, or
103 // the launcher alignment on ChromeOS.
104 origin_
= gfx::Point(origin
.x() - preferred_size_
.width(),
105 origin
.y() - preferred_size_
.height());
107 gfx::Rect
stable_bounds(origin_
, preferred_size_
);
109 SetBoundsInstantly(GetClosedToastBounds(stable_bounds
));
111 SetBoundsWithAnimation(stable_bounds
);
114 void ToastContentsView::CloseWithAnimation(bool mark_as_shown
) {
119 collection_
->RemoveToast(this);
121 message_center_
->MarkSinglePopupAsShown(id(), false);
125 void ToastContentsView::SetBoundsInstantly(gfx::Rect new_bounds
) {
126 if (new_bounds
== bounds())
129 origin_
= new_bounds
.origin();
132 GetWidget()->SetBounds(new_bounds
);
135 void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds
) {
136 if (new_bounds
== bounds())
139 origin_
= new_bounds
.origin();
143 // This picks up the current bounds, so if there was a previous animation
144 // half-done, the next one will pick up from the current location.
145 // This is the only place that should query current location of the Widget
146 // on screen, the rest should refer to the bounds_.
147 animated_bounds_start_
= GetWidget()->GetWindowBoundsInScreen();
148 animated_bounds_end_
= new_bounds
;
151 collection_
->IncrementDeferCounter();
153 if (bounds_animation_
.get())
154 bounds_animation_
->Stop();
156 bounds_animation_
.reset(new ui::SlideAnimation(this));
157 bounds_animation_
->Show();
160 void ToastContentsView::StartFadeIn() {
161 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
163 collection_
->IncrementDeferCounter();
164 fade_animation_
->Stop();
166 GetWidget()->SetOpacity(0);
168 fade_animation_
->Reset(0);
169 fade_animation_
->Show();
172 void ToastContentsView::StartFadeOut() {
173 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
175 collection_
->IncrementDeferCounter();
176 fade_animation_
->Stop();
178 closing_animation_
= (is_closing_
? fade_animation_
.get() : NULL
);
179 fade_animation_
->Reset(1);
180 fade_animation_
->Hide();
183 void ToastContentsView::OnBoundsAnimationEndedOrCancelled(
184 const ui::Animation
* animation
) {
185 if (is_closing_
&& closing_animation_
== animation
&& GetWidget()) {
186 views::Widget
* widget
= GetWidget();
187 #if defined(USE_AURA)
188 // TODO(dewittj): This is a workaround to prevent a nasty bug where
189 // closing a transparent widget doesn't actually remove the window,
190 // causing entire areas of the screen to become unresponsive to clicks.
191 // See crbug.com/243469
194 widget
->SetOpacity(0xFF);
200 // This cannot be called before GetWidget()->Close(). Decrementing defer count
201 // will invoke update, which may invoke another close animation with
202 // incrementing defer counter. Close() after such process will cause a
203 // mismatch between increment/decrement. See crbug.com/238477
205 collection_
->DecrementDeferCounter();
208 // ui::AnimationDelegate
209 void ToastContentsView::AnimationProgressed(const ui::Animation
* animation
) {
210 if (animation
== bounds_animation_
.get()) {
211 gfx::Rect
current(animation
->CurrentValueBetween(
212 animated_bounds_start_
, animated_bounds_end_
));
213 GetWidget()->SetBounds(current
);
214 } else if (animation
== fade_animation_
.get()) {
215 unsigned char opacity
=
216 static_cast<unsigned char>(fade_animation_
->GetCurrentValue() * 255);
217 GetWidget()->SetOpacity(opacity
);
221 void ToastContentsView::AnimationEnded(const ui::Animation
* animation
) {
222 OnBoundsAnimationEndedOrCancelled(animation
);
225 void ToastContentsView::AnimationCanceled(
226 const ui::Animation
* animation
) {
227 OnBoundsAnimationEndedOrCancelled(animation
);
230 // views::WidgetDelegate
231 views::View
* ToastContentsView::GetContentsView() {
235 void ToastContentsView::WindowClosing() {
236 if (!is_closing_
&& collection_
)
237 collection_
->RemoveToast(this);
240 bool ToastContentsView::CanActivate() const {
241 #if defined(OS_WIN) && defined(USE_AURA)
248 void ToastContentsView::OnDisplayChanged() {
249 views::Widget
* widget
= GetWidget();
253 gfx::NativeView native_view
= widget
->GetNativeView();
254 if (!native_view
|| !collection_
)
257 collection_
->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor(
258 native_view
)->GetDisplayNearestWindow(native_view
));
261 void ToastContentsView::OnWorkAreaChanged() {
262 views::Widget
* widget
= GetWidget();
266 gfx::NativeView native_view
= widget
->GetNativeView();
267 if (!native_view
|| !collection_
)
270 collection_
->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor(
271 native_view
)->GetDisplayNearestWindow(native_view
));
275 void ToastContentsView::OnMouseEntered(const ui::MouseEvent
& event
) {
277 collection_
->OnMouseEntered(this);
280 void ToastContentsView::OnMouseExited(const ui::MouseEvent
& event
) {
282 collection_
->OnMouseExited(this);
285 void ToastContentsView::Layout() {
286 if (child_count() > 0) {
287 child_at(0)->SetBounds(
288 0, 0, preferred_size_
.width(), preferred_size_
.height());
292 gfx::Size
ToastContentsView::GetPreferredSize() {
293 return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size();
296 void ToastContentsView::GetAccessibleState(ui::AccessibleViewState
* state
) {
297 if (child_count() > 0)
298 child_at(0)->GetAccessibleState(state
);
299 state
->role
= ui::AccessibilityTypes::ROLE_WINDOW
;
302 gfx::Rect
ToastContentsView::GetClosedToastBounds(gfx::Rect bounds
) {
303 return gfx::Rect(bounds
.x() + bounds
.width() - kClosedToastWidth
,
309 } // namespace message_center