toast_contents_view.cc revision ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16
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 "base/bind.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" 26 27namespace message_center { 28namespace { 29 30// The width of a toast before animated reveal and after closing. 31const int kClosedToastWidth = 5; 32 33// FadeIn/Out look a bit better if they are slightly longer then default slide. 34const int kFadeInOutDuration = 200; 35 36} // namespace. 37 38// static 39gfx::Size ToastContentsView::GetToastSizeForView(views::View* view) { 40 int width = kNotificationWidth + view->GetInsets().width(); 41 return gfx::Size(width, view->GetHeightForWidth(width)); 42} 43 44ToastContentsView::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), 52 is_closing_(false), 53 closing_animation_(NULL) { 54 DCHECK(collection_); 55 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)); 61 62 fade_animation_.reset(new ui::SlideAnimation(this)); 63 fade_animation_->SetSlideDuration(kFadeInOutDuration); 64} 65 66// This is destroyed when the toast window closes. 67ToastContentsView::~ToastContentsView() { 68} 69 70views::Widget* ToastContentsView::CreateWidget(gfx::NativeView parent) { 71 views::Widget::InitParams params( 72 views::Widget::InitParams::TYPE_POPUP); 73 params.keep_on_top = true; 74 if (parent) 75 params.parent = parent; 76 else 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); 82 widget->Init(params); 83 return widget; 84} 85 86void ToastContentsView::SetContents(MessageView* view) { 87 bool already_has_contents = child_count() > 0; 88 RemoveAllChildViews(true); 89 AddChildView(view); 90 preferred_size_ = GetToastSizeForView(view); 91 Layout(); 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); 96} 97 98void 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()); 106 107 gfx::Rect stable_bounds(origin_, preferred_size_); 108 109 SetBoundsInstantly(GetClosedToastBounds(stable_bounds)); 110 StartFadeIn(); 111 SetBoundsWithAnimation(stable_bounds); 112} 113 114void ToastContentsView::CloseWithAnimation(bool mark_as_shown) { 115 if (is_closing_) 116 return; 117 is_closing_ = true; 118 if (collection_) 119 collection_->RemoveToast(this); 120 if (mark_as_shown) 121 message_center_->MarkSinglePopupAsShown(id(), false); 122 StartFadeOut(); 123} 124 125void ToastContentsView::SetBoundsInstantly(gfx::Rect new_bounds) { 126 if (new_bounds == bounds()) 127 return; 128 129 origin_ = new_bounds.origin(); 130 if (!GetWidget()) 131 return; 132 GetWidget()->SetBounds(new_bounds); 133} 134 135void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds) { 136 if (new_bounds == bounds()) 137 return; 138 139 origin_ = new_bounds.origin(); 140 if (!GetWidget()) 141 return; 142 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; 149 150 if (collection_) 151 collection_->IncrementDeferCounter(); 152 153 if (bounds_animation_.get()) 154 bounds_animation_->Stop(); 155 156 bounds_animation_.reset(new ui::SlideAnimation(this)); 157 bounds_animation_->Show(); 158} 159 160void ToastContentsView::StartFadeIn() { 161 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback. 162 if (collection_) 163 collection_->IncrementDeferCounter(); 164 fade_animation_->Stop(); 165 166 GetWidget()->SetOpacity(0); 167 GetWidget()->Show(); 168 fade_animation_->Reset(0); 169 fade_animation_->Show(); 170} 171 172void ToastContentsView::StartFadeOut() { 173 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback. 174 if (collection_) 175 collection_->IncrementDeferCounter(); 176 fade_animation_->Stop(); 177 178 closing_animation_ = (is_closing_ ? fade_animation_.get() : NULL); 179 fade_animation_->Reset(1); 180 fade_animation_->Hide(); 181} 182 183void 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 192 widget->Hide(); 193# if defined(OS_WIN) 194 widget->SetOpacity(0xFF); 195# endif 196#endif 197 widget->Close(); 198 } 199 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 204 if (collection_) 205 collection_->DecrementDeferCounter(); 206} 207 208// ui::AnimationDelegate 209void 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); 218 } 219} 220 221void ToastContentsView::AnimationEnded(const ui::Animation* animation) { 222 OnBoundsAnimationEndedOrCancelled(animation); 223} 224 225void ToastContentsView::AnimationCanceled( 226 const ui::Animation* animation) { 227 OnBoundsAnimationEndedOrCancelled(animation); 228} 229 230// views::WidgetDelegate 231views::View* ToastContentsView::GetContentsView() { 232 return this; 233} 234 235void ToastContentsView::WindowClosing() { 236 if (!is_closing_ && collection_) 237 collection_->RemoveToast(this); 238} 239 240bool ToastContentsView::CanActivate() const { 241#if defined(OS_WIN) && defined(USE_AURA) 242 return true; 243#else 244 return false; 245#endif 246} 247 248void ToastContentsView::OnDisplayChanged() { 249 views::Widget* widget = GetWidget(); 250 if (!widget) 251 return; 252 253 gfx::NativeView native_view = widget->GetNativeView(); 254 if (!native_view || !collection_) 255 return; 256 257 collection_->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor( 258 native_view)->GetDisplayNearestWindow(native_view)); 259} 260 261void ToastContentsView::OnWorkAreaChanged() { 262 views::Widget* widget = GetWidget(); 263 if (!widget) 264 return; 265 266 gfx::NativeView native_view = widget->GetNativeView(); 267 if (!native_view || !collection_) 268 return; 269 270 collection_->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor( 271 native_view)->GetDisplayNearestWindow(native_view)); 272} 273 274// views::View 275void ToastContentsView::OnMouseEntered(const ui::MouseEvent& event) { 276 if (collection_) 277 collection_->OnMouseEntered(this); 278} 279 280void ToastContentsView::OnMouseExited(const ui::MouseEvent& event) { 281 if (collection_) 282 collection_->OnMouseExited(this); 283} 284 285void ToastContentsView::Layout() { 286 if (child_count() > 0) { 287 child_at(0)->SetBounds( 288 0, 0, preferred_size_.width(), preferred_size_.height()); 289 } 290} 291 292gfx::Size ToastContentsView::GetPreferredSize() { 293 return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size(); 294} 295 296void ToastContentsView::GetAccessibleState(ui::AccessibleViewState* state) { 297 if (child_count() > 0) 298 child_at(0)->GetAccessibleState(state); 299 state->role = ui::AccessibilityTypes::ROLE_WINDOW; 300} 301 302gfx::Rect ToastContentsView::GetClosedToastBounds(gfx::Rect bounds) { 303 return gfx::Rect(bounds.x() + bounds.width() - kClosedToastWidth, 304 bounds.y(), 305 kClosedToastWidth, 306 bounds.height()); 307} 308 309} // namespace message_center 310