OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 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/notification_view_md.h" |
| 6 |
| 7 #include <stddef.h> |
| 8 |
| 9 #include "base/strings/string_util.h" |
| 10 #include "ui/base/cursor/cursor.h" |
| 11 #include "ui/base/l10n/l10n_util.h" |
| 12 #include "ui/gfx/geometry/size.h" |
| 13 #include "ui/gfx/image/image_skia_operations.h" |
| 14 #include "ui/gfx/paint_vector_icon.h" |
| 15 #include "ui/gfx/skia_util.h" |
| 16 #include "ui/gfx/text_elider.h" |
| 17 #include "ui/message_center/message_center.h" |
| 18 #include "ui/message_center/message_center_style.h" |
| 19 #include "ui/message_center/notification.h" |
| 20 #include "ui/message_center/notification_types.h" |
| 21 #include "ui/message_center/vector_icons.h" |
| 22 #include "ui/message_center/views/bounded_label.h" |
| 23 #include "ui/message_center/views/constants.h" |
| 24 #include "ui/message_center/views/message_center_controller.h" |
| 25 #include "ui/message_center/views/notification_button.h" |
| 26 #include "ui/message_center/views/padded_button.h" |
| 27 #include "ui/strings/grit/ui_strings.h" |
| 28 #include "ui/views/background.h" |
| 29 #include "ui/views/border.h" |
| 30 #include "ui/views/controls/image_view.h" |
| 31 #include "ui/views/controls/label.h" |
| 32 #include "ui/views/focus/focus_manager.h" |
| 33 #include "ui/views/layout/box_layout.h" |
| 34 #include "ui/views/native_cursor.h" |
| 35 #include "ui/views/view_targeter.h" |
| 36 |
| 37 namespace message_center { |
| 38 |
| 39 namespace { |
| 40 |
| 41 // Dimensions. |
| 42 constexpr int kNotificationRightPadding = 13; |
| 43 constexpr int kNotificationLeftPadding = 13; |
| 44 constexpr int kNotificationTopPadding = 6; |
| 45 constexpr int kNotificationBottomPadding = 10; |
| 46 constexpr gfx::Insets kNotificationPadding(kNotificationTopPadding, |
| 47 kNotificationLeftPadding, |
| 48 kNotificationBottomPadding, |
| 49 kNotificationRightPadding); |
| 50 |
| 51 constexpr int kMaxContextTitleLines = 1; |
| 52 |
| 53 // Foreground of small icon image. |
| 54 constexpr SkColor kSmallImageBackgroundColor = SK_ColorWHITE; |
| 55 // Background of small icon image. |
| 56 const SkColor kSmallImageColor = SkColorSetRGB(0x43, 0x43, 0x43); |
| 57 |
| 58 const gfx::ImageSkia CreateSolidColorImage(int width, |
| 59 int height, |
| 60 SkColor color) { |
| 61 SkBitmap bitmap; |
| 62 bitmap.allocN32Pixels(width, height); |
| 63 bitmap.eraseColor(color); |
| 64 return gfx::ImageSkia::CreateFrom1xBitmap(bitmap); |
| 65 } |
| 66 |
| 67 // Take the alpha channel of icon, mask it with the foreground, |
| 68 // then add the masked foreground on top of the background |
| 69 const gfx::ImageSkia GetMaskedIcon(const gfx::ImageSkia& icon) { |
| 70 int width = icon.width(); |
| 71 int height = icon.height(); |
| 72 |
| 73 // Background color grey |
| 74 const gfx::ImageSkia background = CreateSolidColorImage( |
| 75 width, height, message_center::kSmallImageBackgroundColor); |
| 76 // Foreground color white |
| 77 const gfx::ImageSkia foreground = |
| 78 CreateSolidColorImage(width, height, message_center::kSmallImageColor); |
| 79 const gfx::ImageSkia masked_small_image = |
| 80 gfx::ImageSkiaOperations::CreateMaskedImage(foreground, icon); |
| 81 return gfx::ImageSkiaOperations::CreateSuperimposedImage(background, |
| 82 masked_small_image); |
| 83 } |
| 84 |
| 85 const gfx::ImageSkia GetProductIcon() { |
| 86 return gfx::CreateVectorIcon(kProductIcon, kSmallImageColor); |
| 87 } |
| 88 |
| 89 } // anonymous namespace |
| 90 |
| 91 // //////////////////////////////////////////////////////////// |
| 92 // NotificationViewMD |
| 93 // //////////////////////////////////////////////////////////// |
| 94 |
| 95 views::View* NotificationViewMD::TargetForRect(views::View* root, |
| 96 const gfx::Rect& rect) { |
| 97 CHECK_EQ(root, this); |
| 98 |
| 99 // TODO(tdanderson): Modify this function to support rect-based event |
| 100 // targeting. Using the center point of |rect| preserves this function's |
| 101 // expected behavior for the time being. |
| 102 gfx::Point point = rect.CenterPoint(); |
| 103 |
| 104 // Want to return this for underlying views, otherwise GetCursor is not |
| 105 // called. But buttons are exceptions, they'll have their own event handlings. |
| 106 std::vector<views::View*> buttons(action_buttons_.begin(), |
| 107 action_buttons_.end()); |
| 108 if (settings_button_) |
| 109 buttons.push_back(settings_button_.get()); |
| 110 if (close_button_) |
| 111 buttons.push_back(close_button_.get()); |
| 112 |
| 113 for (size_t i = 0; i < buttons.size(); ++i) { |
| 114 gfx::Point point_in_child = point; |
| 115 ConvertPointToTarget(this, buttons[i], &point_in_child); |
| 116 if (buttons[i]->HitTestPoint(point_in_child)) |
| 117 return buttons[i]->GetEventHandlerForPoint(point_in_child); |
| 118 } |
| 119 |
| 120 return root; |
| 121 } |
| 122 |
| 123 void NotificationViewMD::CreateOrUpdateViews(const Notification& notification) { |
| 124 CreateOrUpdateContextTitleView(notification); |
| 125 CreateOrUpdateTitleView(notification); |
| 126 CreateOrUpdateMessageView(notification); |
| 127 CreateOrUpdateProgressBarView(notification); |
| 128 CreateOrUpdateListItemViews(notification); |
| 129 CreateOrUpdateIconView(notification); |
| 130 CreateOrUpdateSmallIconView(notification); |
| 131 CreateOrUpdateImageView(notification); |
| 132 CreateOrUpdateActionButtonViews(notification); |
| 133 CreateOrUpdateCloseButtonView(notification); |
| 134 CreateOrUpdateSettingsButtonView(notification); |
| 135 } |
| 136 |
| 137 NotificationViewMD::NotificationViewMD(MessageCenterController* controller, |
| 138 const Notification& notification) |
| 139 : MessageView(controller, notification), |
| 140 clickable_(notification.clickable()) { |
| 141 layout_ = new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 2); |
| 142 layout_->set_inside_border_insets(kNotificationPadding); |
| 143 SetLayoutManager(layout_); |
| 144 |
| 145 // Create the top_view_, which collects into a vertical box all content |
| 146 // at the top of the notification (to the right of the icon) except for the |
| 147 // close button. |
| 148 top_view_ = new views::View(); |
| 149 views::BoxLayout* top_box_layout = |
| 150 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 1, 5); |
| 151 top_box_layout->set_cross_axis_alignment( |
| 152 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); |
| 153 top_view_->SetLayoutManager(top_box_layout); |
| 154 AddChildView(top_view_); |
| 155 |
| 156 main_view_ = new views::View(); |
| 157 main_view_->SetLayoutManager( |
| 158 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); |
| 159 AddChildView(main_view_); |
| 160 |
| 161 // Create the bottom_view_, which collects notification icon. |
| 162 bottom_view_ = new views::View(); |
| 163 bottom_view_->SetLayoutManager( |
| 164 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); |
| 165 AddChildView(bottom_view_); |
| 166 |
| 167 views::ImageView* small_image_view = new views::ImageView(); |
| 168 small_image_view->SetImageSize(gfx::Size(kSmallImageSize, kSmallImageSize)); |
| 169 small_image_view->set_owned_by_client(); |
| 170 small_image_view_.reset(small_image_view); |
| 171 top_view_->AddChildView(small_image_view_.get()); |
| 172 |
| 173 CreateOrUpdateViews(notification); |
| 174 |
| 175 SetEventTargeter( |
| 176 std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); |
| 177 } |
| 178 |
| 179 NotificationViewMD::~NotificationViewMD() {} |
| 180 |
| 181 void NotificationViewMD::Layout() { |
| 182 MessageView::Layout(); |
| 183 |
| 184 // Before any resizing, set or adjust the number of message lines. |
| 185 int title_lines = 0; |
| 186 if (title_view_) { |
| 187 title_lines = title_view_->GetLinesForWidthAndLimit(title_view_->width(), |
| 188 kMaxTitleLines); |
| 189 } |
| 190 if (message_view_) { |
| 191 message_view_->SetLineLimit( |
| 192 std::max(0, message_center::kMessageExpandedLineLimit - title_lines)); |
| 193 } |
| 194 |
| 195 // Settings & Bottom views. |
| 196 if (settings_button_) { |
| 197 gfx::Rect content_bounds = GetContentsBounds(); |
| 198 const gfx::Size settings_size(settings_button_->GetPreferredSize()); |
| 199 int marginFromRight = settings_size.width() + kControlButtonPadding; |
| 200 if (close_button_) |
| 201 marginFromRight += close_button_->GetPreferredSize().width(); |
| 202 gfx::Rect settings_rect(content_bounds.right() - marginFromRight, |
| 203 GetContentsBounds().y() + kControlButtonPadding, |
| 204 settings_size.width(), settings_size.height()); |
| 205 settings_button_->SetBoundsRect(settings_rect); |
| 206 } |
| 207 |
| 208 // Close button. |
| 209 if (close_button_) { |
| 210 gfx::Rect content_bounds = GetContentsBounds(); |
| 211 gfx::Size close_size(close_button_->GetPreferredSize()); |
| 212 gfx::Rect close_rect( |
| 213 content_bounds.right() - close_size.width() - kControlButtonPadding, |
| 214 content_bounds.y() + kControlButtonPadding, close_size.width(), |
| 215 close_size.height()); |
| 216 close_button_->SetBoundsRect(close_rect); |
| 217 } |
| 218 } |
| 219 |
| 220 void NotificationViewMD::OnFocus() { |
| 221 MessageView::OnFocus(); |
| 222 ScrollRectToVisible(GetLocalBounds()); |
| 223 } |
| 224 |
| 225 void NotificationViewMD::ScrollRectToVisible(const gfx::Rect& rect) { |
| 226 // Notification want to show the whole notification when a part of it (like |
| 227 // a button) gets focused. |
| 228 views::View::ScrollRectToVisible(GetLocalBounds()); |
| 229 } |
| 230 |
| 231 gfx::NativeCursor NotificationViewMD::GetCursor(const ui::MouseEvent& event) { |
| 232 if (!clickable_ || !controller()->HasClickedListener(notification_id())) |
| 233 return views::View::GetCursor(event); |
| 234 |
| 235 return views::GetNativeHandCursor(); |
| 236 } |
| 237 |
| 238 void NotificationViewMD::OnMouseEntered(const ui::MouseEvent& event) { |
| 239 MessageView::OnMouseEntered(event); |
| 240 UpdateControlButtonsVisibility(); |
| 241 } |
| 242 |
| 243 void NotificationViewMD::OnMouseExited(const ui::MouseEvent& event) { |
| 244 MessageView::OnMouseExited(event); |
| 245 UpdateControlButtonsVisibility(); |
| 246 } |
| 247 |
| 248 void NotificationViewMD::UpdateWithNotification( |
| 249 const Notification& notification) { |
| 250 MessageView::UpdateWithNotification(notification); |
| 251 |
| 252 CreateOrUpdateViews(notification); |
| 253 Layout(); |
| 254 SchedulePaint(); |
| 255 } |
| 256 |
| 257 void NotificationViewMD::ButtonPressed(views::Button* sender, |
| 258 const ui::Event& event) { |
| 259 // Certain operations can cause |this| to be destructed, so copy the members |
| 260 // we send to other parts of the code. |
| 261 // TODO(dewittj): Remove this hack. |
| 262 std::string id(notification_id()); |
| 263 |
| 264 if (close_button_ && sender == close_button_.get()) { |
| 265 // Warning: This causes the NotificationViewMD itself to be deleted, so |
| 266 // don't do anything afterwards. |
| 267 OnCloseButtonPressed(); |
| 268 return; |
| 269 } |
| 270 |
| 271 if (sender == settings_button_.get()) { |
| 272 controller()->ClickOnSettingsButton(id); |
| 273 return; |
| 274 } |
| 275 |
| 276 // See if the button pressed was an action button. |
| 277 for (size_t i = 0; i < action_buttons_.size(); ++i) { |
| 278 if (sender == action_buttons_[i]) { |
| 279 controller()->ClickOnNotificationButton(id, i); |
| 280 return; |
| 281 } |
| 282 } |
| 283 } |
| 284 |
| 285 bool NotificationViewMD::IsCloseButtonFocused() const { |
| 286 if (!close_button_) |
| 287 return false; |
| 288 |
| 289 const views::FocusManager* focus_manager = GetFocusManager(); |
| 290 return focus_manager && |
| 291 focus_manager->GetFocusedView() == close_button_.get(); |
| 292 } |
| 293 |
| 294 void NotificationViewMD::RequestFocusOnCloseButton() { |
| 295 if (close_button_) |
| 296 close_button_->RequestFocus(); |
| 297 } |
| 298 |
| 299 void NotificationViewMD::CreateOrUpdateContextTitleView( |
| 300 const Notification& notification) { |
| 301 DCHECK(top_view_); |
| 302 |
| 303 const gfx::FontList& font_list = views::Label().font_list().Derive( |
| 304 -2, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); |
| 305 |
| 306 base::string16 sub_title = notification.display_source(); |
| 307 if (!context_title_view_) { |
| 308 context_title_view_ = new BoundedLabel(sub_title, font_list); |
| 309 context_title_view_->SetLineHeight(kTitleLineHeight); |
| 310 context_title_view_->SetLineLimit(kMaxContextTitleLines); |
| 311 top_view_->AddChildView(context_title_view_); |
| 312 } else { |
| 313 context_title_view_->SetText(sub_title); |
| 314 } |
| 315 } |
| 316 |
| 317 void NotificationViewMD::CreateOrUpdateTitleView( |
| 318 const Notification& notification) { |
| 319 DCHECK(top_view_ != NULL); |
| 320 |
| 321 const gfx::FontList& font_list = views::Label().font_list().Derive( |
| 322 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); |
| 323 |
| 324 int title_character_limit = |
| 325 kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter; |
| 326 |
| 327 base::string16 title = gfx::TruncateString( |
| 328 notification.title(), title_character_limit, gfx::WORD_BREAK); |
| 329 if (!title_view_) { |
| 330 title_view_ = new BoundedLabel(title, font_list); |
| 331 title_view_->SetLineHeight(kMessageLineHeight); |
| 332 title_view_->SetColors(message_center::kRegularTextColor, |
| 333 kDimTextBackgroundColor); |
| 334 main_view_->AddChildView(title_view_); |
| 335 } else { |
| 336 title_view_->SetText(title); |
| 337 } |
| 338 } |
| 339 |
| 340 void NotificationViewMD::CreateOrUpdateMessageView( |
| 341 const Notification& notification) { |
| 342 if (notification.message().empty()) { |
| 343 // Deletion will also remove |context_message_view_| from its parent. |
| 344 delete message_view_; |
| 345 message_view_ = nullptr; |
| 346 return; |
| 347 } |
| 348 |
| 349 DCHECK(top_view_ != NULL); |
| 350 |
| 351 base::string16 text = gfx::TruncateString( |
| 352 notification.message(), kMessageCharacterLimit, gfx::WORD_BREAK); |
| 353 |
| 354 const gfx::FontList& font_list = views::Label().font_list().Derive( |
| 355 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); |
| 356 |
| 357 if (!message_view_) { |
| 358 message_view_ = new BoundedLabel(text, font_list); |
| 359 message_view_->SetLineLimit(message_center::kMessageExpandedLineLimit); |
| 360 message_view_->SetColors(message_center::kDimTextColor, |
| 361 kContextTextBackgroundColor); |
| 362 main_view_->AddChildView(message_view_); |
| 363 } else { |
| 364 message_view_->SetText(text); |
| 365 } |
| 366 } |
| 367 |
| 368 void NotificationViewMD::CreateOrUpdateProgressBarView( |
| 369 const Notification& notification) { |
| 370 // TODO(yoshiki): Implement this. |
| 371 } |
| 372 |
| 373 void NotificationViewMD::CreateOrUpdateListItemViews( |
| 374 const Notification& notification) { |
| 375 // TODO(yoshiki): Implement this. |
| 376 } |
| 377 |
| 378 void NotificationViewMD::CreateOrUpdateIconView( |
| 379 const Notification& notification) { |
| 380 // TODO(yoshiki): Implement this. |
| 381 } |
| 382 |
| 383 void NotificationViewMD::CreateOrUpdateSmallIconView( |
| 384 const Notification& notification) { |
| 385 gfx::ImageSkia icon = |
| 386 notification.small_image().IsEmpty() |
| 387 ? GetProductIcon() |
| 388 : GetMaskedIcon(notification.small_image().AsImageSkia()); |
| 389 |
| 390 small_image_view_->SetImage(icon); |
| 391 } |
| 392 |
| 393 void NotificationViewMD::CreateOrUpdateImageView( |
| 394 const Notification& notification) { |
| 395 // TODO(yoshiki): Implement this. |
| 396 } |
| 397 |
| 398 void NotificationViewMD::CreateOrUpdateActionButtonViews( |
| 399 const Notification& notification) { |
| 400 // TODO(yoshiki): Implement this. |
| 401 } |
| 402 |
| 403 void NotificationViewMD::CreateOrUpdateCloseButtonView( |
| 404 const Notification& notification) { |
| 405 if (!notification.pinned() && !close_button_) { |
| 406 close_button_ = base::MakeUnique<PaddedButton>(this); |
| 407 close_button_->SetImage(views::Button::STATE_NORMAL, GetCloseIcon()); |
| 408 close_button_->SetAccessibleName(l10n_util::GetStringUTF16( |
| 409 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME)); |
| 410 close_button_->SetTooltipText(l10n_util::GetStringUTF16( |
| 411 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_TOOLTIP)); |
| 412 close_button_->set_owned_by_client(); |
| 413 AddChildView(close_button_.get()); |
| 414 UpdateControlButtonsVisibility(); |
| 415 } else if (notification.pinned() && close_button_) { |
| 416 close_button_.reset(); |
| 417 } |
| 418 } |
| 419 |
| 420 void NotificationViewMD::CreateOrUpdateSettingsButtonView( |
| 421 const Notification& notification) { |
| 422 if (!settings_button_ && notification.delegate() && |
| 423 notification.delegate()->ShouldDisplaySettingsButton()) { |
| 424 settings_button_ = base::MakeUnique<PaddedButton>(this); |
| 425 settings_button_->SetImage(views::Button::STATE_NORMAL, GetSettingsIcon()); |
| 426 settings_button_->SetAccessibleName(l10n_util::GetStringUTF16( |
| 427 IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); |
| 428 settings_button_->SetTooltipText(l10n_util::GetStringUTF16( |
| 429 IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); |
| 430 settings_button_->set_owned_by_client(); |
| 431 AddChildView(settings_button_.get()); |
| 432 } else { |
| 433 settings_button_.reset(); |
| 434 } |
| 435 UpdateControlButtonsVisibility(); |
| 436 } |
| 437 |
| 438 void NotificationViewMD::UpdateControlButtonsVisibility() { |
| 439 const bool target_visibility = |
| 440 IsMouseHovered() || HasFocus() || |
| 441 (close_button_ && close_button_->HasFocus()) || |
| 442 (settings_button_ && settings_button_->HasFocus()); |
| 443 |
| 444 if (close_button_) { |
| 445 if (target_visibility != close_button_->visible()) |
| 446 close_button_->SetVisible(target_visibility); |
| 447 } |
| 448 |
| 449 if (settings_button_) { |
| 450 if (target_visibility != settings_button_->visible()) |
| 451 settings_button_->SetVisible(target_visibility); |
| 452 } |
| 453 } |
| 454 |
| 455 } // namespace message_center |
OLD | NEW |