Chromium Code Reviews| 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 const SkColor kSmallImageBackgroundColor = SK_ColorWHITE; | |
|
fukino
2017/05/25 07:35:10
nit: constexpr for SkColor.
yoshiki
2017/05/29 03:52:38
Done.
| |
| 55 // Background of small icon image. | |
| 56 const SkColor kSmallImageColor = SkColorSetRGB(0x43, 0x43, 0x43); | |
|
fukino
2017/05/25 07:35:10
ditto
yoshiki
2017/05/29 03:52:39
We can't make it constexpr since SkColorSetRGB is
fukino
2017/05/30 07:32:33
I see. Acknowledged.
| |
| 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 } | |
| 134 | |
| 135 NotificationViewMD::NotificationViewMD(MessageCenterController* controller, | |
| 136 const Notification& notification) | |
| 137 : MessageView(controller, notification), | |
| 138 clickable_(notification.clickable()) { | |
| 139 layout_ = new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 2); | |
| 140 layout_->set_inside_border_insets(kNotificationPadding); | |
| 141 SetLayoutManager(layout_); | |
| 142 | |
| 143 // Create the top_view_, which collects into a vertical box all content | |
| 144 // at the top of the notification (to the right of the icon) except for the | |
| 145 // close button. | |
| 146 top_view_ = new views::View(); | |
| 147 views::BoxLayout* top_box_layout = | |
| 148 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 1, 5); | |
| 149 top_box_layout->set_cross_axis_alignment( | |
| 150 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); | |
| 151 top_view_->SetLayoutManager(top_box_layout); | |
| 152 AddChildView(top_view_); | |
| 153 | |
| 154 main_view_ = new views::View(); | |
| 155 views::BoxLayout* main_box_layout = | |
| 156 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); | |
| 157 main_view_->SetLayoutManager(main_box_layout); | |
|
fukino
2017/05/25 07:35:10
nit: Instead of having |main_box_layout|, calling
yoshiki
2017/05/29 03:52:38
Done.
| |
| 158 AddChildView(main_view_); | |
| 159 | |
| 160 // Create the bottom_view_, which collects notification icon. | |
| 161 bottom_view_ = new views::View(); | |
| 162 bottom_view_->SetLayoutManager( | |
| 163 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); | |
| 164 AddChildView(bottom_view_); | |
| 165 | |
| 166 views::ImageView* small_image_view = new views::ImageView(); | |
| 167 small_image_view->SetImageSize(gfx::Size(kSmallImageSize, kSmallImageSize)); | |
| 168 small_image_view->set_owned_by_client(); | |
| 169 small_image_view_.reset(small_image_view); | |
| 170 top_view_->AddChildView(small_image_view_.get()); | |
| 171 | |
| 172 CreateOrUpdateViews(notification); | |
| 173 CreateOrUpdateCloseButtonView(notification); | |
|
fukino
2017/05/25 07:35:10
Shouldn't we do CreateOrUpdateCloseButtonView() an
yoshiki
2017/05/29 03:52:39
Done.
| |
| 174 CreateOrUpdateSettingsButtonView(notification); | |
| 175 | |
| 176 SetEventTargeter( | |
| 177 std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); | |
| 178 } | |
| 179 | |
| 180 NotificationViewMD::~NotificationViewMD() {} | |
| 181 | |
| 182 void NotificationViewMD::Layout() { | |
| 183 // View::Layout() is not called in MessageView::Layout() for some reason. | |
|
fukino
2017/05/25 07:35:11
I'm a bit concerned about calling both views::View
yoshiki
2017/05/29 03:52:38
That's ideal but breaks NotificationView actually.
fukino
2017/05/30 07:32:32
Oh, it sounds scary that calling View::Layout() in
yoshiki
2017/05/31 03:53:01
As I checked, all wrong values which were set by V
| |
| 184 views::View::Layout(); | |
| 185 | |
| 186 MessageView::Layout(); | |
| 187 | |
| 188 // Before any resizing, set or adjust the number of message lines. | |
| 189 int title_lines = 0; | |
| 190 if (title_view_) { | |
| 191 title_lines = title_view_->GetLinesForWidthAndLimit(title_view_->width(), | |
| 192 kMaxTitleLines); | |
| 193 } | |
| 194 if (message_view_) { | |
| 195 message_view_->SetLineLimit( | |
| 196 std::max(0, message_center::kMessageExpandedLineLimit - title_lines)); | |
| 197 } | |
| 198 | |
| 199 // Settings & Bottom views. | |
| 200 if (settings_button_) { | |
| 201 gfx::Rect content_bounds = GetContentsBounds(); | |
| 202 const gfx::Size settings_size(settings_button_->GetPreferredSize()); | |
| 203 int marginFromRight = settings_size.width() + kControlButtonPadding; | |
| 204 if (close_button_) | |
| 205 marginFromRight += close_button_->GetPreferredSize().width(); | |
| 206 gfx::Rect settings_rect(content_bounds.right() - marginFromRight, | |
| 207 GetContentsBounds().y() + kControlButtonPadding, | |
| 208 settings_size.width(), settings_size.height()); | |
| 209 settings_button_->SetBoundsRect(settings_rect); | |
| 210 } | |
| 211 | |
| 212 // Close button. | |
| 213 if (close_button_) { | |
| 214 gfx::Rect content_bounds = GetContentsBounds(); | |
| 215 gfx::Size close_size(close_button_->GetPreferredSize()); | |
| 216 gfx::Rect close_rect( | |
| 217 content_bounds.right() - close_size.width() - kControlButtonPadding, | |
| 218 content_bounds.y() + kControlButtonPadding, close_size.width(), | |
| 219 close_size.height()); | |
| 220 close_button_->SetBoundsRect(close_rect); | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 void NotificationViewMD::OnFocus() { | |
| 225 MessageView::OnFocus(); | |
| 226 ScrollRectToVisible(GetLocalBounds()); | |
| 227 } | |
| 228 | |
| 229 void NotificationViewMD::ScrollRectToVisible(const gfx::Rect& rect) { | |
| 230 // Notification want to show the whole notification when a part of it (like | |
| 231 // a button) gets focused. | |
| 232 views::View::ScrollRectToVisible(GetLocalBounds()); | |
| 233 } | |
| 234 | |
| 235 gfx::NativeCursor NotificationViewMD::GetCursor(const ui::MouseEvent& event) { | |
| 236 if (!clickable_ || !controller()->HasClickedListener(notification_id())) | |
| 237 return views::View::GetCursor(event); | |
| 238 | |
| 239 return views::GetNativeHandCursor(); | |
| 240 } | |
| 241 | |
| 242 void NotificationViewMD::OnMouseEntered(const ui::MouseEvent& event) { | |
| 243 MessageView::OnMouseEntered(event); | |
| 244 UpdateControlButtonsVisibility(); | |
| 245 } | |
| 246 | |
| 247 void NotificationViewMD::OnMouseExited(const ui::MouseEvent& event) { | |
| 248 MessageView::OnMouseExited(event); | |
| 249 UpdateControlButtonsVisibility(); | |
| 250 } | |
| 251 | |
| 252 void NotificationViewMD::UpdateWithNotification( | |
| 253 const Notification& notification) { | |
| 254 MessageView::UpdateWithNotification(notification); | |
| 255 | |
| 256 CreateOrUpdateViews(notification); | |
| 257 CreateOrUpdateCloseButtonView(notification); | |
| 258 CreateOrUpdateSettingsButtonView(notification); | |
| 259 Layout(); | |
| 260 SchedulePaint(); | |
| 261 } | |
| 262 | |
| 263 void NotificationViewMD::ButtonPressed(views::Button* sender, | |
| 264 const ui::Event& event) { | |
| 265 // Certain operations can cause |this| to be destructed, so copy the members | |
| 266 // we send to other parts of the code. | |
| 267 // TODO(dewittj): Remove this hack. | |
| 268 std::string id(notification_id()); | |
| 269 | |
| 270 if (close_button_ && sender == close_button_.get()) { | |
| 271 // Warning: This causes the NotificationViewMD itself to be deleted, so | |
| 272 // don't do anything afterwards. | |
| 273 OnCloseButtonPressed(); | |
| 274 return; | |
| 275 } | |
| 276 | |
| 277 if (sender == settings_button_.get()) { | |
| 278 controller()->ClickOnSettingsButton(id); | |
| 279 return; | |
| 280 } | |
| 281 | |
| 282 // See if the button pressed was an action button. | |
| 283 for (size_t i = 0; i < action_buttons_.size(); ++i) { | |
| 284 if (sender == action_buttons_[i]) { | |
| 285 controller()->ClickOnNotificationButton(id, i); | |
| 286 return; | |
| 287 } | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 bool NotificationViewMD::IsCloseButtonFocused() const { | |
| 292 if (!close_button_) | |
| 293 return false; | |
| 294 | |
| 295 const views::FocusManager* focus_manager = GetFocusManager(); | |
| 296 return focus_manager && | |
| 297 focus_manager->GetFocusedView() == close_button_.get(); | |
| 298 } | |
| 299 | |
| 300 void NotificationViewMD::RequestFocusOnCloseButton() { | |
| 301 if (close_button_) | |
| 302 close_button_->RequestFocus(); | |
| 303 } | |
| 304 | |
| 305 void NotificationViewMD::CreateOrUpdateContextTitleView( | |
| 306 const Notification& notification) { | |
| 307 DCHECK(top_view_); | |
| 308 | |
| 309 const gfx::FontList& font_list = views::Label().font_list().Derive( | |
| 310 -2, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); | |
| 311 | |
| 312 base::string16 sub_title = notification.display_source(); | |
| 313 if (!context_title_view_) { | |
| 314 context_title_view_ = new BoundedLabel(sub_title, font_list); | |
| 315 context_title_view_->SetLineHeight(kTitleLineHeight); | |
| 316 context_title_view_->SetLineLimit(kMaxTitleLines); | |
|
fukino
2017/05/25 07:35:11
This line should be removed? (as kMaxContextTitleL
yoshiki
2017/05/29 03:52:38
Done.
| |
| 317 context_title_view_->SetLineLimit(kMaxContextTitleLines); | |
| 318 top_view_->AddChildView(context_title_view_); | |
| 319 } else { | |
| 320 context_title_view_->SetText(sub_title); | |
| 321 } | |
| 322 } | |
| 323 | |
| 324 void NotificationViewMD::CreateOrUpdateTitleView( | |
| 325 const Notification& notification) { | |
| 326 DCHECK(top_view_ != NULL); | |
| 327 | |
| 328 const gfx::FontList& font_list = views::Label().font_list().Derive( | |
| 329 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); | |
| 330 | |
| 331 int title_character_limit = | |
| 332 kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter; | |
| 333 | |
| 334 base::string16 title = gfx::TruncateString( | |
| 335 notification.title(), title_character_limit, gfx::WORD_BREAK); | |
| 336 if (!title_view_) { | |
| 337 title_view_ = new BoundedLabel(title, font_list); | |
| 338 title_view_->SetLineHeight(kMessageLineHeight); | |
| 339 title_view_->SetColors(message_center::kRegularTextColor, | |
| 340 kDimTextBackgroundColor); | |
| 341 main_view_->AddChildView(title_view_); | |
| 342 } else { | |
| 343 title_view_->SetText(title); | |
| 344 } | |
| 345 } | |
| 346 | |
| 347 void NotificationViewMD::CreateOrUpdateMessageView( | |
| 348 const Notification& notification) { | |
| 349 if (notification.message().empty()) { | |
| 350 // Deletion will also remove |context_message_view_| from its parent. | |
| 351 delete message_view_; | |
| 352 message_view_ = nullptr; | |
| 353 return; | |
| 354 } | |
| 355 | |
| 356 DCHECK(top_view_ != NULL); | |
| 357 | |
| 358 base::string16 text = gfx::TruncateString( | |
| 359 notification.message(), kMessageCharacterLimit, gfx::WORD_BREAK); | |
| 360 | |
| 361 const gfx::FontList& font_list = views::Label().font_list().Derive( | |
| 362 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); | |
| 363 | |
| 364 if (!message_view_) { | |
| 365 message_view_ = new BoundedLabel(text, font_list); | |
| 366 message_view_->SetLineLimit(message_center::kMessageExpandedLineLimit); | |
| 367 message_view_->SetColors(message_center::kDimTextColor, | |
| 368 kContextTextBackgroundColor); | |
| 369 main_view_->AddChildView(message_view_); | |
| 370 } else { | |
| 371 message_view_->SetText(text); | |
| 372 } | |
| 373 } | |
| 374 | |
| 375 void NotificationViewMD::CreateOrUpdateProgressBarView( | |
| 376 const Notification& notification) { | |
| 377 // TODO(yoshiki): Implement this. | |
| 378 } | |
| 379 | |
| 380 void NotificationViewMD::CreateOrUpdateListItemViews( | |
| 381 const Notification& notification) { | |
| 382 // TODO(yoshiki): Implement this. | |
| 383 } | |
| 384 | |
| 385 void NotificationViewMD::CreateOrUpdateIconView( | |
| 386 const Notification& notification) { | |
| 387 // TODO(yoshiki): Implement this. | |
| 388 } | |
| 389 | |
| 390 void NotificationViewMD::CreateOrUpdateSmallIconView( | |
| 391 const Notification& notification) { | |
| 392 gfx::ImageSkia icon = | |
| 393 notification.small_image().IsEmpty() | |
| 394 ? GetProductIcon() | |
| 395 : GetMaskedIcon(notification.small_image().AsImageSkia()); | |
| 396 | |
| 397 small_image_view_->SetImage(icon); | |
| 398 } | |
| 399 | |
| 400 void NotificationViewMD::CreateOrUpdateImageView( | |
| 401 const Notification& notification) { | |
| 402 // TODO(yoshiki): Implement this. | |
| 403 } | |
| 404 | |
| 405 void NotificationViewMD::CreateOrUpdateActionButtonViews( | |
| 406 const Notification& notification) { | |
| 407 // TODO(yoshiki): Implement this. | |
| 408 } | |
| 409 | |
| 410 void NotificationViewMD::CreateOrUpdateCloseButtonView( | |
| 411 const Notification& notification) { | |
| 412 if (!notification.pinned() && !close_button_) { | |
| 413 close_button_ = base::MakeUnique<PaddedButton>(this); | |
| 414 close_button_->SetImage(views::Button::STATE_NORMAL, GetCloseIcon()); | |
| 415 close_button_->SetAccessibleName(l10n_util::GetStringUTF16( | |
| 416 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME)); | |
| 417 close_button_->SetTooltipText(l10n_util::GetStringUTF16( | |
| 418 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_TOOLTIP)); | |
| 419 close_button_->set_owned_by_client(); | |
| 420 AddChildView(close_button_.get()); | |
| 421 UpdateControlButtonsVisibility(); | |
| 422 } else if (notification.pinned() && close_button_) { | |
| 423 close_button_.reset(); | |
| 424 } | |
| 425 } | |
| 426 | |
| 427 void NotificationViewMD::CreateOrUpdateSettingsButtonView( | |
| 428 const Notification& notification) { | |
| 429 if (!settings_button_ && notification.delegate() && | |
| 430 notification.delegate()->ShouldDisplaySettingsButton()) { | |
| 431 settings_button_ = base::MakeUnique<PaddedButton>(this); | |
| 432 settings_button_->SetImage(views::Button::STATE_NORMAL, GetSettingsIcon()); | |
| 433 settings_button_->SetAccessibleName(l10n_util::GetStringUTF16( | |
| 434 IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); | |
| 435 settings_button_->SetTooltipText(l10n_util::GetStringUTF16( | |
| 436 IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); | |
| 437 settings_button_->set_owned_by_client(); | |
| 438 AddChildView(settings_button_.get()); | |
| 439 } else { | |
| 440 settings_button_.reset(); | |
| 441 } | |
| 442 UpdateControlButtonsVisibility(); | |
| 443 } | |
| 444 | |
| 445 void NotificationViewMD::UpdateControlButtonsVisibility() { | |
| 446 const bool target_visibility = | |
| 447 IsMouseHovered() || HasFocus() || | |
| 448 (close_button_ && close_button_->HasFocus()) || | |
| 449 (settings_button_ && settings_button_->HasFocus()); | |
| 450 | |
| 451 if (close_button_) { | |
| 452 if (target_visibility != close_button_->visible()) | |
| 453 close_button_->SetVisible(target_visibility); | |
| 454 } | |
| 455 | |
| 456 if (settings_button_) { | |
| 457 if (target_visibility != settings_button_->visible()) | |
| 458 settings_button_->SetVisible(target_visibility); | |
| 459 } | |
| 460 } | |
| 461 | |
| 462 } // namespace message_center | |
| OLD | NEW |