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 27 namespace message_center { 28 namespace { 29 30 // The width of a toast before animated reveal and after closing. 31 const int kClosedToastWidth = 5; 32 33 // FadeIn/Out look a bit better if they are slightly longer then default slide. 34 const int kFadeInOutDuration = 200; 35 36 } // namespace. 37 38 // static 39 gfx::Size ToastContentsView::GetToastSizeForView(views::View* view) { 40 int width = kNotificationWidth + view->GetInsets().width(); 41 return gfx::Size(width, view->GetHeightForWidth(width)); 42 } 43 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), 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. 67 ToastContentsView::~ToastContentsView() { 68 } 69 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; 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 86 void 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 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()); 106 107 gfx::Rect stable_bounds(origin_, preferred_size_); 108 109 SetBoundsInstantly(GetClosedToastBounds(stable_bounds)); 110 StartFadeIn(); 111 SetBoundsWithAnimation(stable_bounds); 112 } 113 114 void 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 125 void 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 135 void 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 160 void 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 172 void 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 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 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 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); 218 } 219 } 220 221 void ToastContentsView::AnimationEnded(const ui::Animation* animation) { 222 OnBoundsAnimationEndedOrCancelled(animation); 223 } 224 225 void ToastContentsView::AnimationCanceled( 226 const ui::Animation* animation) { 227 OnBoundsAnimationEndedOrCancelled(animation); 228 } 229 230 // views::WidgetDelegate 231 views::View* ToastContentsView::GetContentsView() { 232 return this; 233 } 234 235 void ToastContentsView::WindowClosing() { 236 if (!is_closing_ && collection_) 237 collection_->RemoveToast(this); 238 } 239 240 bool ToastContentsView::CanActivate() const { 241 #if defined(OS_WIN) && defined(USE_AURA) 242 return true; 243 #else 244 return false; 245 #endif 246 } 247 248 void 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 261 void 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 275 void ToastContentsView::OnMouseEntered(const ui::MouseEvent& event) { 276 if (collection_) 277 collection_->OnMouseEntered(this); 278 } 279 280 void ToastContentsView::OnMouseExited(const ui::MouseEvent& event) { 281 if (collection_) 282 collection_->OnMouseExited(this); 283 } 284 285 void ToastContentsView::Layout() { 286 if (child_count() > 0) { 287 child_at(0)->SetBounds( 288 0, 0, preferred_size_.width(), preferred_size_.height()); 289 } 290 } 291 292 gfx::Size ToastContentsView::GetPreferredSize() { 293 return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size(); 294 } 295 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; 300 } 301 302 gfx::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