diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-09-18 14:34:04 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-10-04 11:15:27 +0000 |
commit | e6430e577f105ad8813c92e75c54660c4985026e (patch) | |
tree | 88115e5d1fb471fea807111924dcccbeadbf9e4f /chromium/ui/message_center/views | |
parent | 53d399fe6415a96ea6986ec0d402a9c07da72453 (diff) | |
download | qtwebengine-chromium-e6430e577f105ad8813c92e75c54660c4985026e.tar.gz |
BASELINE: Update Chromium to 61.0.3163.99
Change-Id: I8452f34574d88ca2b27af9bd56fc9ff3f16b1367
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/ui/message_center/views')
30 files changed, 2513 insertions, 262 deletions
diff --git a/chromium/ui/message_center/views/bounded_label.cc b/chromium/ui/message_center/views/bounded_label.cc index 84ce848eb56..64d88e4bf4f 100644 --- a/chromium/ui/message_center/views/bounded_label.cc +++ b/chromium/ui/message_center/views/bounded_label.cc @@ -263,14 +263,14 @@ void InnerBoundedLabel::SetCachedSize(std::pair<int, int> width_and_lines, BoundedLabel::BoundedLabel(const base::string16& text, const gfx::FontList& font_list) - : line_limit_(-1) { + : line_limit_(-1), fixed_width_(0) { label_.reset(new InnerBoundedLabel(*this)); label_->SetFontList(font_list); label_->SetText(text); } BoundedLabel::BoundedLabel(const base::string16& text) - : line_limit_(-1) { + : line_limit_(-1), fixed_width_(0) { label_.reset(new InnerBoundedLabel(*this)); label_->SetText(text); } @@ -312,12 +312,21 @@ gfx::Size BoundedLabel::GetSizeForWidthAndLines(int width, int lines) { label_->GetSizeForWidthAndLines(width, lines) : gfx::Size(); } +void BoundedLabel::SizeToFit(int fixed_width) { + fixed_width_ = fixed_width; + label_->SizeToFit(fixed_width); +} + int BoundedLabel::GetBaseline() const { return label_->GetBaseline(); } gfx::Size BoundedLabel::CalculatePreferredSize() const { - return visible() ? label_->GetSizeForWidthAndLines(-1, -1) : gfx::Size(); + if (!visible()) + return gfx::Size(); + return fixed_width_ != 0 + ? label_->GetSizeForWidthAndLines(fixed_width_, line_limit_) + : label_->GetSizeForWidthAndLines(-1, -1); } int BoundedLabel::GetHeightForWidth(int width) const { diff --git a/chromium/ui/message_center/views/bounded_label.h b/chromium/ui/message_center/views/bounded_label.h index e241b5aaf5b..8d204cdac19 100644 --- a/chromium/ui/message_center/views/bounded_label.h +++ b/chromium/ui/message_center/views/bounded_label.h @@ -42,6 +42,7 @@ class MESSAGE_CENTER_EXPORT BoundedLabel : public views::View { void SetLineHeight(int height); // Pass in 0 for default height. void SetLineLimit(int lines); // Pass in -1 for no limit. void SetText(const base::string16& text); // Additionally clears caches. + void SizeToFit(int fixed_width); // Same interface as Label::SizeToFit. int GetLineHeight() const; int GetLineLimit() const; @@ -70,6 +71,7 @@ class MESSAGE_CENTER_EXPORT BoundedLabel : public views::View { std::unique_ptr<InnerBoundedLabel> label_; int line_limit_; + int fixed_width_; DISALLOW_COPY_AND_ASSIGN(BoundedLabel); }; diff --git a/chromium/ui/message_center/views/constants.h b/chromium/ui/message_center/views/constants.h index 9942ed8a1e1..a99a199ccd0 100644 --- a/chromium/ui/message_center/views/constants.h +++ b/chromium/ui/message_center/views/constants.h @@ -22,7 +22,7 @@ const SkColor kContextTextBackgroundColor = SK_ColorWHITE; const int kTextBottomPadding = 12; const int kItemTitleToMessagePadding = 3; -const int kButtonVecticalPadding = 0; +const int kButtonVerticalPadding = 0; const int kButtonTitleTopPadding = 0; const int kNotificationSettingsPadding = 5; diff --git a/chromium/ui/message_center/views/message_bubble_base.cc b/chromium/ui/message_center/views/message_bubble_base.cc index 4089234047b..25f0120c3b3 100644 --- a/chromium/ui/message_center/views/message_bubble_base.cc +++ b/chromium/ui/message_center/views/message_bubble_base.cc @@ -8,7 +8,6 @@ #include "base/location.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" -#include "ui/message_center/message_center_style.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" @@ -32,7 +31,7 @@ MessageBubbleBase::MessageBubbleBase(MessageCenter* message_center, MessageBubbleBase::~MessageBubbleBase() { if (bubble_view_) - bubble_view_->reset_delegate(); + bubble_view_->ResetDelegate(); } void MessageBubbleBase::BubbleViewDestroyed() { @@ -64,14 +63,4 @@ void MessageBubbleBase::SetMaxHeight(int height) { bubble_view_->SetMaxHeight(max_height_); } -views::TrayBubbleView::InitParams MessageBubbleBase::GetDefaultInitParams( - views::TrayBubbleView::AnchorAlignment anchor_alignment) { - views::TrayBubbleView::InitParams init_params( - anchor_alignment, - kNotificationWidth, - kNotificationWidth); - init_params.bg_color = kBackgroundDarkColor; - return init_params; -} - } // namespace message_center diff --git a/chromium/ui/message_center/views/message_bubble_base.h b/chromium/ui/message_center/views/message_bubble_base.h index 0c9718a85f6..779b9b3fa03 100644 --- a/chromium/ui/message_center/views/message_bubble_base.h +++ b/chromium/ui/message_center/views/message_bubble_base.h @@ -8,6 +8,7 @@ #include <memory> #include "base/macros.h" +#include "ui/gfx/native_widget_types.h" #include "ui/message_center/message_center.h" #include "ui/message_center/message_center_export.h" #include "ui/views/bubble/tray_bubble_view.h" @@ -31,10 +32,6 @@ class MESSAGE_CENTER_EXPORT MessageBubbleBase { void SetMaxHeight(int height); int max_height() const { return max_height_; } - // Gets the init params for the implementation. - virtual views::TrayBubbleView::InitParams GetInitParams( - views::TrayBubbleView::AnchorAlignment anchor_alignment) = 0; - // Called after the bubble view has been constructed. Creates and initializes // the bubble contents. virtual void InitializeContents(views::TrayBubbleView* bubble_view) = 0; @@ -58,8 +55,6 @@ class MESSAGE_CENTER_EXPORT MessageBubbleBase { views::TrayBubbleView* bubble_view() const { return bubble_view_; } protected: - views::TrayBubbleView::InitParams GetDefaultInitParams( - views::TrayBubbleView::AnchorAlignment anchor_alignment); MessageCenter* message_center() { return message_center_; } MessageCenterTray* tray() { return tray_; } void set_bubble_view(views::TrayBubbleView* bubble_view) { diff --git a/chromium/ui/message_center/views/message_center_bubble.cc b/chromium/ui/message_center/views/message_center_bubble.cc index 3045bfd29e4..ad76cef5ace 100644 --- a/chromium/ui/message_center/views/message_center_bubble.cc +++ b/chromium/ui/message_center/views/message_center_bubble.cc @@ -5,10 +5,8 @@ #include "ui/message_center/views/message_center_bubble.h" #include "base/macros.h" -#include "ui/base/l10n/l10n_util.h" #include "ui/message_center/message_center_style.h" #include "ui/message_center/views/message_center_view.h" -#include "ui/strings/grit/ui_strings.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/widget/widget.h" @@ -79,17 +77,6 @@ void MessageCenterBubble::SetSettingsVisible() { initially_settings_visible_ = true; } -views::TrayBubbleView::InitParams MessageCenterBubble::GetInitParams( - views::TrayBubbleView::AnchorAlignment anchor_alignment) { - views::TrayBubbleView::InitParams init_params = - GetDefaultInitParams(anchor_alignment); - init_params.min_width += kMarginBetweenItems * 2; - init_params.max_width += kMarginBetweenItems * 2; - init_params.max_height = max_height(); - init_params.can_activate = true; - return init_params; -} - void MessageCenterBubble::InitializeContents( views::TrayBubbleView* new_bubble_view) { set_bubble_view(new_bubble_view); diff --git a/chromium/ui/message_center/views/message_center_bubble.h b/chromium/ui/message_center/views/message_center_bubble.h index 4364a124d76..138c562ea03 100644 --- a/chromium/ui/message_center/views/message_center_bubble.h +++ b/chromium/ui/message_center/views/message_center_bubble.h @@ -33,8 +33,6 @@ class MESSAGE_CENTER_EXPORT MessageCenterBubble void OnWidgetClosing(views::Widget* widget) override; // Overridden from MessageBubbleBase: - views::TrayBubbleView::InitParams GetInitParams( - views::TrayBubbleView::AnchorAlignment anchor_alignment) override; void InitializeContents(views::TrayBubbleView* bubble_view) override; void OnBubbleViewDestroyed() override; void UpdateBubbleView() override; diff --git a/chromium/ui/message_center/views/message_center_button_bar.cc b/chromium/ui/message_center/views/message_center_button_bar.cc index 5b8ef31ce98..f08fc1655ca 100644 --- a/chromium/ui/message_center/views/message_center_button_bar.cc +++ b/chromium/ui/message_center/views/message_center_button_bar.cc @@ -9,6 +9,7 @@ #include "ui/base/models/simple_menu_model.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" #include "ui/gfx/text_constants.h" #include "ui/message_center/message_center.h" #include "ui/message_center/message_center_style.h" @@ -83,8 +84,7 @@ MessageCenterButtonBar::MessageCenterButtonBar( settings_button_(NULL), quiet_mode_button_(NULL) { SetPaintToLayer(); - set_background( - views::Background::CreateSolidBackground(kMessageCenterBackgroundColor)); + SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor)); ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance(); @@ -104,7 +104,7 @@ MessageCenterButtonBar::MessageCenterButtonBar( button_container_ = new views::View; button_container_->SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); + new views::BoxLayout(views::BoxLayout::kHorizontal)); quiet_mode_button_ = CreateNotificationCenterButton( this, IDR_NOTIFICATION_DO_NOT_DISTURB, IDR_NOTIFICATION_DO_NOT_DISTURB_HOVER, diff --git a/chromium/ui/message_center/views/message_center_view.cc b/chromium/ui/message_center/views/message_center_view.cc index 36ed38c7f28..ae14fb94d9c 100644 --- a/chromium/ui/message_center/views/message_center_view.cc +++ b/chromium/ui/message_center/views/message_center_view.cc @@ -27,8 +27,8 @@ #include "ui/message_center/views/message_view.h" #include "ui/message_center/views/message_view_context_menu_controller.h" #include "ui/message_center/views/message_view_factory.h" +#include "ui/message_center/views/notification_control_buttons_view.h" #include "ui/message_center/views/notifier_settings_view.h" -#include "ui/resources/grit/ui_resources.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/background.h" #include "ui/views/border.h" @@ -81,8 +81,7 @@ MessageCenterView::MessageCenterView(MessageCenter* message_center, focus_manager_(nullptr) { message_center_->AddObserver(this); set_notify_enter_exit_on_child(true); - set_background(views::Background::CreateSolidBackground( - kMessageCenterBackgroundColor)); + SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor)); NotifierSettingsProvider* notifier_settings_provider = message_center_->GetNotifierSettingsProvider(); @@ -212,12 +211,15 @@ void MessageCenterView::SetIsClosing(bool is_closing) { void MessageCenterView::OnDidChangeFocus(views::View* before, views::View* now) { - if (message_list_view_ && (message_list_view_->Contains(before) || - message_list_view_->Contains(now))) { - // Focus state of a children of message view center is changed. - for (auto pair : notification_views_) { - if (pair.second->Contains(before) || pair.second->Contains(now)) - pair.second->UpdateControlButtonsVisibility(); + // Update the button visibility when the focus state is changed. + for (auto pair : notification_views_) { + // ControlButtonsView is not in the same view hierarchy on ARC++ + // notifications, so check it separately. + if (pair.second->Contains(before) || pair.second->Contains(now) || + (pair.second->GetControlButtonsView() && + (pair.second->GetControlButtonsView()->Contains(before) || + pair.second->GetControlButtonsView()->Contains(now)))) { + pair.second->UpdateControlButtonsVisibility(); } } } @@ -513,7 +515,12 @@ void MessageCenterView::AddNotificationAt(const Notification& notification, int index) { MessageView* view = MessageViewFactory::Create(this, notification, false); // Not top-level. - view->set_context_menu_controller(context_menu_controller_.get()); + + // TODO(yoshiki): Temporary disable context menu on custom notifications. + // See crbug.com/750307 for detail. + if (notification.type() != NOTIFICATION_TYPE_CUSTOM) + view->set_context_menu_controller(context_menu_controller_.get()); + notification_views_[notification.id()] = view; view->set_scroller(scroller_); message_list_view_->AddNotificationAt(view, index); diff --git a/chromium/ui/message_center/views/message_list_view.cc b/chromium/ui/message_center/views/message_list_view.cc index 127f3fdc8c4..6a1b78426e6 100644 --- a/chromium/ui/message_center/views/message_list_view.cc +++ b/chromium/ui/message_center/views/message_list_view.cc @@ -2,18 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <algorithm> +#include "ui/message_center/views/message_list_view.h" #include "base/command_line.h" #include "base/location.h" #include "base/message_loop/message_loop.h" #include "base/single_thread_task_runner.h" +#include "base/stl_util.h" #include "base/threading/thread_task_runner_handle.h" #include "ui/gfx/animation/slide_animation.h" +#include "ui/gfx/geometry/insets.h" #include "ui/message_center/message_center_style.h" #include "ui/message_center/message_center_switches.h" #include "ui/message_center/views/message_center_view.h" -#include "ui/message_center/views/message_list_view.h" #include "ui/message_center/views/message_view.h" #include "ui/views/background.h" #include "ui/views/border.h" @@ -35,7 +36,7 @@ MessageListView::MessageListView() quit_message_loop_after_animation_for_test_(false), weak_ptr_factory_(this) { views::BoxLayout* layout = - new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1); + new views::BoxLayout(views::BoxLayout::kVertical, gfx::Insets(), 1); layout->SetDefaultFlex(1); SetLayoutManager(layout); @@ -44,8 +45,7 @@ MessageListView::MessageListView() // because of the shadow of message view. Use an empty border instead // to provide this margin. gfx::Insets shadow_insets = MessageView::GetShadowInsets(); - set_background( - views::Background::CreateSolidBackground(kMessageCenterBackgroundColor)); + SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor)); SetBorder(views::CreateEmptyBorder( kMarginBetweenItems - shadow_insets.top(), /* top */ kMarginBetweenItems - shadow_insets.left(), /* left */ @@ -105,8 +105,7 @@ void MessageListView::RemoveNotification(MessageView* view) { // TODO(yhananda): We should consider consolidating clearing_all_views_, // deleting_views_ and deleted_when_done_. - if (std::find(clearing_all_views_.begin(), clearing_all_views_.end(), view) != - clearing_all_views_.end() || + if (base::ContainsValue(clearing_all_views_, view) || deleting_views_.find(view) != deleting_views_.end() || deleted_when_done_.find(view) != deleted_when_done_.end()) { // Let's skip deleting the view if it's already scheduled for deleting. @@ -138,8 +137,7 @@ void MessageListView::RemoveNotification(MessageView* view) { void MessageListView::UpdateNotification(MessageView* view, const Notification& notification) { // Skip updating the notification being cleared - if (std::find(clearing_all_views_.begin(), clearing_all_views_.end(), view) != - clearing_all_views_.end()) + if (base::ContainsValue(clearing_all_views_, view)) return; int index = GetIndexOf(view); @@ -345,8 +343,7 @@ bool MessageListView::IsValidChild(const views::View* child) const { deleting_views_.end() && deleted_when_done_.find(const_cast<views::View*>(child)) == deleted_when_done_.end() && - std::find(clearing_all_views_.begin(), clearing_all_views_.end(), - child) == clearing_all_views_.end(); + !base::ContainsValue(clearing_all_views_, child); } void MessageListView::DoUpdateIfPossible() { diff --git a/chromium/ui/message_center/views/message_popup_collection.cc b/chromium/ui/message_center/views/message_popup_collection.cc index 396721d56fd..be17705b3d7 100644 --- a/chromium/ui/message_center/views/message_popup_collection.cc +++ b/chromium/ui/message_center/views/message_popup_collection.cc @@ -210,7 +210,11 @@ void MessagePopupCollection::UpdateWidgets() { view = MessageViewFactory::Create(NULL, *(*iter), true); } - view->set_context_menu_controller(context_menu_controller_.get()); + // TODO(yoshiki): Temporary disable context menu on custom notifications. + // See crbug.com/750307 for detail. + if ((*iter)->type() != NOTIFICATION_TYPE_CUSTOM) + view->set_context_menu_controller(context_menu_controller_.get()); + int view_height = ToastContentsView::GetToastSizeForView(view).height(); int height_available = top_down ? alignment_delegate_->GetWorkArea().bottom() - base diff --git a/chromium/ui/message_center/views/message_view.cc b/chromium/ui/message_center/views/message_view.cc index f7a5d4bfb2d..340d9bcce8d 100644 --- a/chromium/ui/message_center/views/message_view.cc +++ b/chromium/ui/message_center/views/message_view.cc @@ -16,7 +16,6 @@ #include "ui/message_center/message_center.h" #include "ui/message_center/message_center_style.h" #include "ui/message_center/views/message_center_controller.h" -#include "ui/resources/grit/ui_resources.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/background.h" #include "ui/views/border.h" @@ -75,8 +74,8 @@ MessageView::MessageView(MessageCenterController* controller, // Create the opaque background that's above the view's shadow. background_view_ = new views::View(); - background_view_->set_background( - views::Background::CreateSolidBackground(kNotificationBackgroundColor)); + background_view_->SetBackground( + views::CreateSolidBackground(kNotificationBackgroundColor)); AddChildView(background_view_); focus_painter_ = views::Painter::CreateSolidFocusPainter( @@ -116,6 +115,10 @@ void MessageView::SetIsNested() { void MessageView::GetAccessibleNodeData(ui::AXNodeData* node_data) { node_data->role = ui::AX_ROLE_BUTTON; + node_data->AddStringAttribute( + ui::AX_ATTR_ROLE_DESCRIPTION, + l10n_util::GetStringUTF8( + IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); node_data->SetName(accessible_name_); } @@ -171,6 +174,8 @@ void MessageView::OnBlur() { } void MessageView::Layout() { + views::View::Layout(); + gfx::Rect content_bounds = GetContentsBounds(); // Background. @@ -230,6 +235,10 @@ void MessageView::OnCloseButtonPressed() { controller_->RemoveNotification(notification_id_, true); // By user. } +void MessageView::OnSettingsButtonPressed() { + controller_->ClickOnSettingsButton(notification_id_); +} + void MessageView::SetDrawBackgroundAsActive(bool active) { background_view_->background()-> SetNativeControlColor(active ? kHoveredButtonBackgroundColor : diff --git a/chromium/ui/message_center/views/message_view.h b/chromium/ui/message_center/views/message_view.h index b55a934b464..d07d76a6760 100644 --- a/chromium/ui/message_center/views/message_view.h +++ b/chromium/ui/message_center/views/message_view.h @@ -28,14 +28,9 @@ class ScrollView; namespace message_center { class Notification; +class NotificationControlButtonsView; class MessageCenterController; -// Individual notifications constants. -const int kPaddingBetweenItems = 10; -const int kPaddingHorizontal = 18; -const int kWebNotificationButtonWidth = 32; -const int kWebNotificationIconSize = 40; - // An base class for a notification entry. Contains background and other // elements shared by derived notification views. class MESSAGE_CENTER_EXPORT MessageView @@ -55,11 +50,13 @@ class MESSAGE_CENTER_EXPORT MessageView // Creates a shadow around the notification and changes slide-out behavior. void SetIsNested(); + virtual NotificationControlButtonsView* GetControlButtonsView() const = 0; virtual bool IsCloseButtonFocused() const = 0; virtual void RequestFocusOnCloseButton() = 0; virtual void UpdateControlButtonsVisibility() = 0; void OnCloseButtonPressed(); + void OnSettingsButtonPressed(); // views::View void GetAccessibleNodeData(ui::AXNodeData* node_data) override; diff --git a/chromium/ui/message_center/views/message_view_factory.cc b/chromium/ui/message_center/views/message_view_factory.cc index f1c982f4b99..60567af4533 100644 --- a/chromium/ui/message_center/views/message_view_factory.cc +++ b/chromium/ui/message_center/views/message_view_factory.cc @@ -4,8 +4,11 @@ #include "ui/message_center/views/message_view_factory.h" +#include "base/command_line.h" +#include "ui/message_center/message_center_switches.h" #include "ui/message_center/notification_types.h" #include "ui/message_center/views/notification_view.h" +#include "ui/message_center/views/notification_view_md.h" #if defined(OS_WIN) #include "ui/base/win/shell.h" @@ -23,10 +26,23 @@ MessageView* MessageViewFactory::Create(MessageCenterController* controller, case NOTIFICATION_TYPE_IMAGE: case NOTIFICATION_TYPE_MULTIPLE: case NOTIFICATION_TYPE_SIMPLE: - case NOTIFICATION_TYPE_PROGRESS: + case NOTIFICATION_TYPE_PROGRESS: { + bool new_style_notification_enabled = false; // default value + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableMessageCenterNewStyleNotification)) { + new_style_notification_enabled = true; + } else if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableMessageCenterNewStyleNotification)) { + new_style_notification_enabled = false; + } + // All above roads lead to the generic NotificationView. - notification_view = new NotificationView(controller, notification); + if (new_style_notification_enabled) + notification_view = new NotificationViewMD(controller, notification); + else + notification_view = new NotificationView(controller, notification); break; + } #if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX) case NOTIFICATION_TYPE_CUSTOM: notification_view = diff --git a/chromium/ui/message_center/views/notification_button.cc b/chromium/ui/message_center/views/notification_button.cc index 72b2be7cdc6..fe0877a8041 100644 --- a/chromium/ui/message_center/views/notification_button.cc +++ b/chromium/ui/message_center/views/notification_button.cc @@ -5,6 +5,7 @@ #include "ui/message_center/views/notification_button.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" #include "ui/message_center/message_center_style.h" #include "ui/message_center/views/constants.h" #include "ui/views/background.h" @@ -17,23 +18,19 @@ namespace message_center { NotificationButton::NotificationButton(views::ButtonListener* listener) - : views::CustomButton(listener), - icon_(NULL), - title_(NULL), - focus_painter_(views::Painter::CreateSolidFocusPainter( - message_center::kFocusBorderColor, - gfx::Insets(1, 2, 2, 2))) { + : views::CustomButton(listener), icon_(NULL), title_(NULL) { SetFocusForPlatform(); // Create a background so that it does not change when the MessageView // background changes to show touch feedback - set_background(views::Background::CreateSolidBackground( - kNotificationBackgroundColor)); + SetBackground(views::CreateSolidBackground(kNotificationBackgroundColor)); set_notify_enter_exit_on_child(true); - SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kHorizontal, - message_center::kButtonHorizontalPadding, - kButtonVecticalPadding, - message_center::kButtonIconToTitlePadding)); + SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kHorizontal, + gfx::Insets(kButtonVerticalPadding, + message_center::kButtonHorizontalPadding), + message_center::kButtonIconToTitlePadding)); + SetFocusPainter(views::Painter::CreateSolidFocusPainter( + message_center::kFocusBorderColor, gfx::Insets(1, 2, 2, 2))); } NotificationButton::~NotificationButton() { @@ -83,22 +80,9 @@ int NotificationButton::GetHeightForWidth(int width) const { return message_center::kButtonHeight; } -void NotificationButton::OnPaint(gfx::Canvas* canvas) { - CustomButton::OnPaint(canvas); - views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get()); -} - void NotificationButton::OnFocus() { views::CustomButton::OnFocus(); ScrollRectToVisible(GetLocalBounds()); - // We render differently when focused. - SchedulePaint(); - } - -void NotificationButton::OnBlur() { - views::CustomButton::OnBlur(); - // We render differently when focused. - SchedulePaint(); } void NotificationButton::ViewHierarchyChanged( @@ -111,11 +95,10 @@ void NotificationButton::ViewHierarchyChanged( void NotificationButton::StateChanged(ButtonState old_state) { if (state() == STATE_HOVERED || state() == STATE_PRESSED) { - set_background(views::Background::CreateSolidBackground( + SetBackground(views::CreateSolidBackground( message_center::kHoveredButtonBackgroundColor)); } else { - set_background(views::Background::CreateSolidBackground( - kNotificationBackgroundColor)); + SetBackground(views::CreateSolidBackground(kNotificationBackgroundColor)); } } diff --git a/chromium/ui/message_center/views/notification_button.h b/chromium/ui/message_center/views/notification_button.h index 69f80bae190..3d3d2186b5b 100644 --- a/chromium/ui/message_center/views/notification_button.h +++ b/chromium/ui/message_center/views/notification_button.h @@ -30,9 +30,7 @@ class NotificationButton : public views::CustomButton { // Overridden from views::View: gfx::Size CalculatePreferredSize() const override; int GetHeightForWidth(int width) const override; - void OnPaint(gfx::Canvas* canvas) override; void OnFocus() override; - void OnBlur() override; void ViewHierarchyChanged( const ViewHierarchyChangedDetails& details) override; @@ -42,7 +40,6 @@ class NotificationButton : public views::CustomButton { private: views::ImageView* icon_; views::Label* title_; - std::unique_ptr<views::Painter> focus_painter_; DISALLOW_COPY_AND_ASSIGN(NotificationButton); }; diff --git a/chromium/ui/message_center/views/notification_control_buttons_view.cc b/chromium/ui/message_center/views/notification_control_buttons_view.cc new file mode 100644 index 00000000000..bb10972f525 --- /dev/null +++ b/chromium/ui/message_center/views/notification_control_buttons_view.cc @@ -0,0 +1,184 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/message_center/views/notification_control_buttons_view.h" + +#include "base/memory/ptr_util.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/compositor/layer.h" +#include "ui/events/event.h" +#include "ui/gfx/animation/linear_animation.h" +#include "ui/message_center/message_center_style.h" +#include "ui/message_center/views/message_view.h" +#include "ui/message_center/views/padded_button.h" +#include "ui/strings/grit/ui_strings.h" +#include "ui/views/background.h" +#include "ui/views/layout/box_layout.h" + +namespace { + +// This value should be the same as the duration of reveal animation of +// the settings view of an Android notification. +constexpr auto kBackgroundColorChangeDuration = + base::TimeDelta::FromMilliseconds(360); + +// The initial background color of the view. +constexpr SkColor kInitialBackgroundColor = + message_center::kControlButtonBackgroundColor; + +} // anonymous namespace + +namespace message_center { + +const char NotificationControlButtonsView::kViewClassName[] = + "NotificationControlButtonsView"; + +NotificationControlButtonsView::NotificationControlButtonsView( + MessageView* message_view) + : message_view_(message_view), + bgcolor_origin_(kInitialBackgroundColor), + bgcolor_target_(kInitialBackgroundColor) { + DCHECK(message_view); + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal)); + + // Use layer to change the opacity. + SetPaintToLayer(); + layer()->SetFillsBoundsOpaquely(false); + + SetBackground(views::CreateSolidBackground(kInitialBackgroundColor)); +} + +NotificationControlButtonsView::~NotificationControlButtonsView() = default; + +void NotificationControlButtonsView::ShowCloseButton(bool show) { + if (show && !close_button_) { + close_button_ = base::MakeUnique<message_center::PaddedButton>(this); + close_button_->set_owned_by_client(); + close_button_->SetImage(views::CustomButton::STATE_NORMAL, + message_center::GetCloseIcon()); + close_button_->SetAccessibleName(l10n_util::GetStringUTF16( + IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME)); + close_button_->SetTooltipText(l10n_util::GetStringUTF16( + IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_TOOLTIP)); + close_button_->SetBackground( + views::CreateSolidBackground(SK_ColorTRANSPARENT)); + + // Add the button at the last. + DCHECK_LE(child_count(), 1); + AddChildView(close_button_.get()); + } else if (!show && close_button_) { + DCHECK(Contains(close_button_.get())); + close_button_.reset(); + } +} + +void NotificationControlButtonsView::ShowSettingsButton(bool show) { + if (show && !settings_button_) { + settings_button_ = base::MakeUnique<message_center::PaddedButton>(this); + settings_button_->set_owned_by_client(); + settings_button_->SetImage(views::CustomButton::STATE_NORMAL, + message_center::GetSettingsIcon()); + settings_button_->SetAccessibleName(l10n_util::GetStringUTF16( + IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); + settings_button_->SetTooltipText(l10n_util::GetStringUTF16( + IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); + settings_button_->SetBackground( + views::CreateSolidBackground(SK_ColorTRANSPARENT)); + + // Add the button at the first. + DCHECK_LE(child_count(), 1); + AddChildViewAt(settings_button_.get(), 0); + } else if (!show && settings_button_) { + DCHECK(Contains(settings_button_.get())); + settings_button_.reset(); + } +} + +void NotificationControlButtonsView::SetBackgroundColor( + const SkColor& target_bgcolor) { + DCHECK(background()); + if (background()->get_color() != target_bgcolor) { + bgcolor_origin_ = background()->get_color(); + bgcolor_target_ = target_bgcolor; + + if (bgcolor_animation_) + bgcolor_animation_->End(); + bgcolor_animation_.reset(new gfx::LinearAnimation(this)); + bgcolor_animation_->SetDuration(kBackgroundColorChangeDuration); + bgcolor_animation_->Start(); + } +} + +void NotificationControlButtonsView::SetVisible(bool visible) { + DCHECK(layer()); + // Manipulate the opacity instead of changing the visibility to keep the tab + // order even when the view is invisible. + layer()->SetOpacity(visible ? 1. : 0.); + set_can_process_events_within_subtree(visible); +} + +void NotificationControlButtonsView::RequestFocusOnCloseButton() { + if (close_button_) + close_button_->RequestFocus(); +} + +bool NotificationControlButtonsView::IsCloseButtonFocused() const { + return close_button_ && close_button_->HasFocus(); +} + +bool NotificationControlButtonsView::IsSettingsButtonFocused() const { + return settings_button_ && settings_button_->HasFocus(); +} + +message_center::PaddedButton* NotificationControlButtonsView::close_button() + const { + return close_button_.get(); +} + +message_center::PaddedButton* NotificationControlButtonsView::settings_button() + const { + return settings_button_.get(); +} + +const char* NotificationControlButtonsView::GetClassName() const { + return kViewClassName; +} + +void NotificationControlButtonsView::ButtonPressed(views::Button* sender, + const ui::Event& event) { + if (close_button_ && sender == close_button_.get()) { + message_view_->OnCloseButtonPressed(); + } else if (settings_button_ && sender == settings_button_.get()) { + message_view_->OnSettingsButtonPressed(); + } +} + +void NotificationControlButtonsView::AnimationProgressed( + const gfx::Animation* animation) { + DCHECK_EQ(animation, bgcolor_animation_.get()); + + const SkColor color = gfx::Tween::ColorValueBetween( + animation->GetCurrentValue(), bgcolor_origin_, bgcolor_target_); + SetBackground(views::CreateSolidBackground(color)); + SchedulePaint(); +} + +void NotificationControlButtonsView::AnimationEnded( + const gfx::Animation* animation) { + DCHECK_EQ(animation, bgcolor_animation_.get()); + bgcolor_animation_.reset(); + bgcolor_origin_ = bgcolor_target_; +} + +void NotificationControlButtonsView::AnimationCanceled( + const gfx::Animation* animation) { + // The animation is never cancelled explicitly. + NOTREACHED(); + + bgcolor_origin_ = bgcolor_target_; + SetBackground(views::CreateSolidBackground(bgcolor_target_)); + SchedulePaint(); +} + +} // namespace message_center diff --git a/chromium/ui/message_center/views/notification_control_buttons_view.h b/chromium/ui/message_center/views/notification_control_buttons_view.h new file mode 100644 index 00000000000..627f0bc9c66 --- /dev/null +++ b/chromium/ui/message_center/views/notification_control_buttons_view.h @@ -0,0 +1,88 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_CONTROL_BUTTONS_VIEW_H_ +#define UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_CONTROL_BUTTONS_VIEW_H_ + +#include "base/macros.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/animation/animation_delegate.h" +#include "ui/message_center/message_center_export.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/view.h" + +namespace ui { +class Event; +} + +namespace gfx { +class LinearAnimation; +} + +namespace message_center { + +class MessageView; +class PaddedButton; + +class MESSAGE_CENTER_EXPORT NotificationControlButtonsView + : public views::View, + public views::ButtonListener, + public gfx::AnimationDelegate { + public: + // String to be returned by GetClassName() method. + static const char kViewClassName[]; + + explicit NotificationControlButtonsView(MessageView* message_view); + ~NotificationControlButtonsView() override; + + // Change the visibility of the close button. True to show, false to hide. + void ShowCloseButton(bool show); + // Change the visibility of the settings button. True to show, false to hide. + void ShowSettingsButton(bool show); + + // Set the background color of the view. + void SetBackgroundColor(const SkColor& target_bgcolor); + + // Request the focus on the close button. + void RequestFocusOnCloseButton(); + + // Return the focus status of the close button. True if the focus is on the + // close button, false otherwise. + bool IsCloseButtonFocused() const; + // Return the focus status of the settings button. True if the focus is on the + // close button, false otherwise. + bool IsSettingsButtonFocused() const; + + // Methods for retrieving the control buttons directly. + message_center::PaddedButton* close_button() const; + message_center::PaddedButton* settings_button() const; + + // views::View + const char* GetClassName() const override; + void SetVisible(bool visible) override; + + // views::ButtonListener + void ButtonPressed(views::Button* sender, const ui::Event& event) override; + + // gfx::AnimationDelegate + void AnimationEnded(const gfx::Animation* animation) override; + void AnimationProgressed(const gfx::Animation* animation) override; + void AnimationCanceled(const gfx::Animation* animation) override; + + private: + MessageView* message_view_; + + std::unique_ptr<message_center::PaddedButton> close_button_; + std::unique_ptr<message_center::PaddedButton> settings_button_; + + std::unique_ptr<gfx::LinearAnimation> bgcolor_animation_; + SkColor bgcolor_origin_; + SkColor bgcolor_target_; + + DISALLOW_COPY_AND_ASSIGN(NotificationControlButtonsView); +}; + +} // namespace message_center + +#endif // UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_CONTROL_BUTTONS_VIEW_H_ diff --git a/chromium/ui/message_center/views/notification_header_view.cc b/chromium/ui/message_center/views/notification_header_view.cc new file mode 100644 index 00000000000..a43e6eaaf37 --- /dev/null +++ b/chromium/ui/message_center/views/notification_header_view.cc @@ -0,0 +1,360 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/message_center/views/notification_header_view.h" + +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/paint_vector_icon.h" +#include "ui/message_center/message_center_style.h" +#include "ui/message_center/vector_icons.h" +#include "ui/message_center/views/padded_button.h" +#include "ui/strings/grit/ui_strings.h" +#include "ui/views/animation/flood_fill_ink_drop_ripple.h" +#include "ui/views/animation/ink_drop_highlight.h" +#include "ui/views/animation/ink_drop_impl.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/painter.h" + +namespace message_center { + +namespace { + +constexpr int kHeaderHeight = 28; +constexpr int kAppIconSize = 12; +constexpr int kExpandIconSize = 12; +constexpr gfx::Insets kHeaderPadding(0, 12, 0, 2); +constexpr int kHeaderHorizontalSpacing = 2; +constexpr int kAppInfoConatainerTopPadding = 12; +// Bullet character. The divider symbol between different parts of the header. +constexpr wchar_t kNotificationHeaderDivider[] = L" \u2022 "; + +// Base ink drop color of action buttons. +const SkColor kInkDropBaseColor = SkColorSetRGB(0x0, 0x0, 0x0); +// Ripple ink drop opacity of action buttons. +constexpr float kInkDropRippleVisibleOpacity = 0.08f; +// Highlight (hover) ink drop opacity of action buttons. +constexpr float kInkDropHighlightVisibleOpacity = 0.08f; + +// base::TimeBase has similar constants, but some of them are missing. +constexpr int64_t kMinuteInMillis = 60LL * 1000LL; +constexpr int64_t kHourInMillis = 60LL * kMinuteInMillis; +constexpr int64_t kDayInMillis = 24LL * kHourInMillis; +// In Android, DateUtils.YEAR_IN_MILLIS is 364 days. +constexpr int64_t kYearInMillis = 364LL * kDayInMillis; + +// ExpandButtton forwards all mouse and key events to NotificationHeaderView, +// but takes tab focus for accessibility purpose. +class ExpandButton : public views::ImageView { + public: + ExpandButton(); + ~ExpandButton() override; + + void OnPaint(gfx::Canvas* canvas) override; + void OnFocus() override; + void OnBlur() override; + + private: + std::unique_ptr<views::Painter> focus_painter_; +}; + +ExpandButton::ExpandButton() { + SetImage(gfx::CreateVectorIcon(kNotificationExpandMoreIcon, kExpandIconSize, + gfx::kChromeIconGrey)); + focus_painter_ = views::Painter::CreateSolidFocusPainter( + kFocusBorderColor, gfx::Insets(1, 2, 2, 2)); + SetFocusBehavior(FocusBehavior::ALWAYS); +} + +ExpandButton::~ExpandButton() = default; + +void ExpandButton::OnPaint(gfx::Canvas* canvas) { + views::ImageView::OnPaint(canvas); + views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get()); +} + +void ExpandButton::OnFocus() { + views::ImageView::OnFocus(); + SchedulePaint(); +} + +void ExpandButton::OnBlur() { + views::ImageView::OnBlur(); + SchedulePaint(); +} + +// Do relative time string formatting that is similar to +// com.java.android.widget.DateTimeView.updateRelativeTime. +// Chromium has its own base::TimeFormat::Simple(), but none of the formats +// supported by the function is similar to Android's one. +base::string16 FormatToRelativeTime(base::Time past) { + base::Time now = base::Time::Now(); + int64_t duration = (now - past).InMilliseconds(); + if (duration < kMinuteInMillis) { + return l10n_util::GetStringUTF16( + IDS_MESSAGE_NOTIFICATION_NOW_STRING_SHORTEST); + } else if (duration < kHourInMillis) { + int count = static_cast<int>(duration / kMinuteInMillis); + return l10n_util::GetPluralStringFUTF16( + IDS_MESSAGE_NOTIFICATION_DURATION_MINUTES_SHORTEST, count); + } else if (duration < kDayInMillis) { + int count = static_cast<int>(duration / kHourInMillis); + return l10n_util::GetPluralStringFUTF16( + IDS_MESSAGE_NOTIFICATION_DURATION_HOURS_SHORTEST, count); + } else if (duration < kYearInMillis) { + int count = static_cast<int>(duration / kDayInMillis); + return l10n_util::GetPluralStringFUTF16( + IDS_MESSAGE_NOTIFICATION_DURATION_DAYS_SHORTEST, count); + } else { + int count = static_cast<int>(duration / kYearInMillis); + return l10n_util::GetPluralStringFUTF16( + IDS_MESSAGE_NOTIFICATION_DURATION_YEARS_SHORTEST, count); + } +} + +} // namespace + +NotificationHeaderView::NotificationHeaderView(views::ButtonListener* listener) + : views::CustomButton(listener) { + SetInkDropMode(InkDropMode::ON); + set_has_ink_drop_action_on_click(true); + set_animate_on_state_change(true); + set_notify_enter_exit_on_child(true); + set_ink_drop_base_color(kInkDropBaseColor); + set_ink_drop_visible_opacity(kInkDropRippleVisibleOpacity); + + views::BoxLayout* layout = new views::BoxLayout( + views::BoxLayout::kHorizontal, kHeaderPadding, kHeaderHorizontalSpacing); + layout->set_cross_axis_alignment( + views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); + SetLayoutManager(layout); + + views::View* app_info_container = new views::View(); + views::BoxLayout* app_info_layout = + new views::BoxLayout(views::BoxLayout::kHorizontal, + gfx::Insets(kAppInfoConatainerTopPadding, 0, 0, 0), + kHeaderHorizontalSpacing); + app_info_layout->set_cross_axis_alignment( + views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); + app_info_container->SetLayoutManager(app_info_layout); + AddChildView(app_info_container); + + // App icon view + app_icon_view_ = new views::ImageView(); + app_icon_view_->SetImageSize(gfx::Size(kAppIconSize, kAppIconSize)); + app_info_container->AddChildView(app_icon_view_); + + // App name view + const gfx::FontList& font_list = views::Label().font_list().Derive( + -2, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); + app_name_view_ = new views::Label(base::string16()); + app_name_view_->SetFontList(font_list); + app_name_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + app_info_container->AddChildView(app_name_view_); + + // Summary text divider + summary_text_divider_ = + new views::Label(base::WideToUTF16(kNotificationHeaderDivider)); + summary_text_divider_->SetFontList(font_list); + summary_text_divider_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + summary_text_divider_->SetVisible(false); + app_info_container->AddChildView(summary_text_divider_); + + // Summary text view + summary_text_view_ = new views::Label(base::string16()); + summary_text_view_->SetFontList(font_list); + summary_text_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + summary_text_view_->SetVisible(false); + app_info_container->AddChildView(summary_text_view_); + + // Timestamp divider + timestamp_divider_ = + new views::Label(base::WideToUTF16(kNotificationHeaderDivider)); + timestamp_divider_->SetFontList(font_list); + timestamp_divider_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + timestamp_divider_->SetVisible(false); + app_info_container->AddChildView(timestamp_divider_); + + // Timestamp view + timestamp_view_ = new views::Label(base::string16()); + timestamp_view_->SetFontList(font_list); + timestamp_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + timestamp_view_->SetVisible(false); + app_info_container->AddChildView(timestamp_view_); + + // Expand button view + expand_button_ = new ExpandButton(); + app_info_container->AddChildView(expand_button_); + + // Spacer between left-aligned views and right-aligned views + views::View* spacer = new views::View; + spacer->SetPreferredSize(gfx::Size(1, kHeaderHeight)); + AddChildView(spacer); + layout->SetFlexForView(spacer, 1); + + // Settings button view + settings_button_ = new PaddedButton(listener); + settings_button_->SetImage(views::Button::STATE_NORMAL, GetSettingsIcon()); + settings_button_->SetAccessibleName(l10n_util::GetStringUTF16( + IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); + settings_button_->SetTooltipText(l10n_util::GetStringUTF16( + IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); + AddChildView(settings_button_); + + // Close button view + close_button_ = new PaddedButton(listener); + close_button_->SetImage(views::Button::STATE_NORMAL, GetCloseIcon()); + close_button_->SetAccessibleName(l10n_util::GetStringUTF16( + IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME)); + close_button_->SetTooltipText(l10n_util::GetStringUTF16( + IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_TOOLTIP)); + AddChildView(close_button_); +} + +void NotificationHeaderView::SetAppIcon(const gfx::ImageSkia& img) { + app_icon_view_->SetImage(img); +} + +void NotificationHeaderView::SetAppName(const base::string16& name) { + app_name_view_->SetText(name); +} + +void NotificationHeaderView::SetProgress(int progress) { + summary_text_view_->SetText(l10n_util::GetStringFUTF16Int( + IDS_MESSAGE_CENTER_NOTIFICATION_PROGRESS_PERCENTAGE, progress)); + has_progress_ = true; + UpdateSummaryTextVisibility(); +} + +void NotificationHeaderView::ClearProgress() { + has_progress_ = false; + UpdateSummaryTextVisibility(); +} + +void NotificationHeaderView::SetOverflowIndicator(int count) { + if (count > 0) { + summary_text_view_->SetText(l10n_util::GetStringFUTF16Int( + IDS_MESSAGE_CENTER_LIST_NOTIFICATION_HEADER_OVERFLOW_INDICATOR, count)); + has_overflow_indicator_ = true; + } else { + has_overflow_indicator_ = false; + } + UpdateSummaryTextVisibility(); +} + +void NotificationHeaderView::ClearOverflowIndicator() { + has_overflow_indicator_ = false; + UpdateSummaryTextVisibility(); +} + +void NotificationHeaderView::SetTimestamp(base::Time past) { + timestamp_view_->SetText(FormatToRelativeTime(past)); + has_timestamp_ = true; + UpdateSummaryTextVisibility(); +} + +void NotificationHeaderView::ClearTimestamp() { + has_timestamp_ = false; + UpdateSummaryTextVisibility(); +} + +void NotificationHeaderView::SetExpandButtonEnabled(bool enabled) { + // SetInkDropMode iff. the visibility changed. + // Otherwise, the ink drop animation cannot finish. + if (expand_button_->visible() != enabled) + SetInkDropMode(enabled ? InkDropMode::ON : InkDropMode::OFF); + + expand_button_->SetVisible(enabled); +} + +void NotificationHeaderView::SetExpanded(bool expanded) { + expand_button_->SetImage( + gfx::CreateVectorIcon( + expanded ? kNotificationExpandLessIcon : kNotificationExpandMoreIcon, + kExpandIconSize, gfx::kChromeIconGrey)); +} + +void NotificationHeaderView::SetSettingsButtonEnabled(bool enabled) { + if (settings_button_enabled_ != enabled) { + settings_button_enabled_ = enabled; + UpdateControlButtonsVisibility(); + } +} + +void NotificationHeaderView::SetCloseButtonEnabled(bool enabled) { + if (close_button_enabled_ != enabled) { + close_button_enabled_ = enabled; + UpdateControlButtonsVisibility(); + } +} + +void NotificationHeaderView::SetControlButtonsVisible(bool visible) { + if (is_control_buttons_visible_ != visible) { + is_control_buttons_visible_ = visible; + UpdateControlButtonsVisibility(); + } +} + +bool NotificationHeaderView::IsExpandButtonEnabled() { + return expand_button_->visible(); +} + +bool NotificationHeaderView::IsSettingsButtonEnabled() { + return settings_button_enabled_; +} + +bool NotificationHeaderView::IsCloseButtonEnabled() { + return close_button_enabled_; +} + +std::unique_ptr<views::InkDrop> NotificationHeaderView::CreateInkDrop() { + auto ink_drop = base::MakeUnique<views::InkDropImpl>(this, size()); + ink_drop->SetAutoHighlightMode( + views::InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE); + ink_drop->SetShowHighlightOnHover(false); + return ink_drop; +} + +std::unique_ptr<views::InkDropRipple> +NotificationHeaderView::CreateInkDropRipple() const { + return base::MakeUnique<views::FloodFillInkDropRipple>( + size(), GetInkDropCenterBasedOnLastEvent(), GetInkDropBaseColor(), + ink_drop_visible_opacity()); +} + +std::unique_ptr<views::InkDropHighlight> +NotificationHeaderView::CreateInkDropHighlight() const { + auto highlight = base::MakeUnique<views::InkDropHighlight>( + size(), kInkDropSmallCornerRadius, + gfx::RectF(GetLocalBounds()).CenterPoint(), GetInkDropBaseColor()); + highlight->set_visible_opacity(kInkDropHighlightVisibleOpacity); + return highlight; +} + +void NotificationHeaderView::UpdateControlButtonsVisibility() { + settings_button_->SetVisible(settings_button_enabled_ && + is_control_buttons_visible_); + close_button_->SetVisible(close_button_enabled_ && + is_control_buttons_visible_); + Layout(); +} + +void NotificationHeaderView::UpdateSummaryTextVisibility() { + const bool visible = has_progress_ || has_overflow_indicator_; + summary_text_divider_->SetVisible(visible); + summary_text_view_->SetVisible(visible); + timestamp_divider_->SetVisible(!has_progress_ && has_timestamp_); + timestamp_view_->SetVisible(!has_progress_ && has_timestamp_); + Layout(); +} + +} // namespace message_center diff --git a/chromium/ui/message_center/views/notification_header_view.h b/chromium/ui/message_center/views/notification_header_view.h new file mode 100644 index 00000000000..2471c67a405 --- /dev/null +++ b/chromium/ui/message_center/views/notification_header_view.h @@ -0,0 +1,78 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_HEADER_VIEW_H_ +#define UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_HEADER_VIEW_H_ + +#include "base/macros.h" +#include "ui/message_center/views/padded_button.h" +#include "ui/views/controls/button/custom_button.h" + +namespace views { +class ImageButton; +class ImageView; +class Label; +} + +namespace message_center { + +class NotificationHeaderView : public views::CustomButton { + public: + NotificationHeaderView(views::ButtonListener* listener); + void SetAppIcon(const gfx::ImageSkia& img); + void SetAppName(const base::string16& name); + void SetProgress(int progress); + void SetOverflowIndicator(int count); + void SetTimestamp(base::Time past); + void SetExpandButtonEnabled(bool enabled); + void SetExpanded(bool expanded); + void SetSettingsButtonEnabled(bool enabled); + void SetCloseButtonEnabled(bool enabled); + void SetControlButtonsVisible(bool visible); + void ClearProgress(); + void ClearOverflowIndicator(); + void ClearTimestamp(); + bool IsExpandButtonEnabled(); + bool IsSettingsButtonEnabled(); + bool IsCloseButtonEnabled(); + bool IsCloseButtonFocused(); + + // CustomButton override: + std::unique_ptr<views::InkDrop> CreateInkDrop() override; + std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override; + std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight() + const override; + + views::ImageView* expand_button() { return expand_button_; } + views::ImageButton* settings_button() { return settings_button_; } + views::ImageButton* close_button() { return close_button_; } + + private: + void UpdateControlButtonsVisibility(); + // Update visibility for both |summary_text_view_| and |timestamp_view_|. + void UpdateSummaryTextVisibility(); + + views::Label* app_name_view_ = nullptr; + views::Label* summary_text_divider_ = nullptr; + views::Label* summary_text_view_ = nullptr; + views::Label* timestamp_divider_ = nullptr; + views::Label* timestamp_view_ = nullptr; + views::ImageView* app_icon_view_ = nullptr; + views::ImageView* expand_button_ = nullptr; + PaddedButton* settings_button_ = nullptr; + PaddedButton* close_button_ = nullptr; + + bool settings_button_enabled_ = false; + bool close_button_enabled_ = false; + bool is_control_buttons_visible_ = false; + bool has_progress_ = false; + bool has_overflow_indicator_ = false; + bool has_timestamp_ = false; + + DISALLOW_COPY_AND_ASSIGN(NotificationHeaderView); +}; + +} // namespace message_center + +#endif // UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_HEADER_VIEW_H_ diff --git a/chromium/ui/message_center/views/notification_view.cc b/chromium/ui/message_center/views/notification_view.cc index d4c55effee6..0a1ad48595f 100644 --- a/chromium/ui/message_center/views/notification_view.cc +++ b/chromium/ui/message_center/views/notification_view.cc @@ -17,6 +17,7 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/base/layout.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/skia_util.h" #include "ui/gfx/text_elider.h" @@ -28,10 +29,10 @@ #include "ui/message_center/views/constants.h" #include "ui/message_center/views/message_center_controller.h" #include "ui/message_center/views/notification_button.h" +#include "ui/message_center/views/notification_control_buttons_view.h" #include "ui/message_center/views/padded_button.h" #include "ui/message_center/views/proportional_image_view.h" #include "ui/native_theme/native_theme.h" -#include "ui/resources/grit/ui_resources.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/background.h" #include "ui/views/border.h" @@ -103,8 +104,9 @@ class ItemView : public views::View { }; ItemView::ItemView(const message_center::NotificationItem& item) { - SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, - 0, 0, message_center::kItemTitleToMessagePadding)); + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, gfx::Insets(), + message_center::kItemTitleToMessagePadding)); views::Label* title = new views::Label(item.title); title->set_collapse_when_hidden(true); @@ -152,10 +154,10 @@ views::View* NotificationView::TargetForRect(views::View* root, // called. But buttons are exceptions, they'll have their own event handlings. std::vector<views::View*> buttons(action_buttons_.begin(), action_buttons_.end()); - if (settings_button_view_) - buttons.push_back(settings_button_view_); - if (close_button()) - buttons.push_back(close_button()); + if (control_buttons_view_->settings_button()) + buttons.push_back(control_buttons_view_->settings_button()); + if (control_buttons_view_->close_button()) + buttons.push_back(control_buttons_view_->close_button()); for (size_t i = 0; i < buttons.size(); ++i) { gfx::Point point_in_child = point; @@ -176,7 +178,6 @@ void NotificationView::CreateOrUpdateViews(const Notification& notification) { CreateOrUpdateSmallIconView(notification); CreateOrUpdateImageView(notification); CreateOrUpdateContextMessageView(notification); - CreateOrUpdateSettingsButtonView(notification); CreateOrUpdateActionButtonViews(notification); } @@ -189,7 +190,7 @@ NotificationView::NotificationView(MessageCenterController* controller, // close button. top_view_ = new views::View(); top_view_->SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); + new views::BoxLayout(views::BoxLayout::kVertical)); top_view_->SetBorder( MakeEmptyBorder(kTextTopPadding - 8, 0, kTextBottomPadding - 5, 0)); AddChildView(top_view_); @@ -197,9 +198,12 @@ NotificationView::NotificationView(MessageCenterController* controller, // below the notification icon. bottom_view_ = new views::View(); bottom_view_->SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); + new views::BoxLayout(views::BoxLayout::kVertical)); AddChildView(bottom_view_); + control_buttons_view_ = new NotificationControlButtonsView(this); + AddChildView(control_buttons_view_); + views::ImageView* small_image_view = new views::ImageView(); small_image_view->SetImageSize(gfx::Size(kSmallImageSize, kSmallImageSize)); small_image_view->set_owned_by_client(); @@ -208,11 +212,11 @@ NotificationView::NotificationView(MessageCenterController* controller, CreateOrUpdateViews(notification); // Put together the different content and control views. Layering those allows - // for proper layout logic and it also allows the close button and small + // for proper layout logic and it also allows the control buttons and small // image to overlap the content as needed to provide large enough click and // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>). AddChildView(small_image_view_.get()); - CreateOrUpdateCloseButtonView(notification); + UpdateControlButtonsVisibilityWithNotification(notification); SetEventTargeter( std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); @@ -290,31 +294,17 @@ void NotificationView::Layout() { icon_view_->SetBounds(insets.left(), insets.top(), kNotificationIconSize, kNotificationIconSize); - // Settings & Bottom views. - int bottom_y = insets.top() + std::max(top_height, kNotificationIconSize); - int bottom_height = bottom_view_->GetHeightForWidth(content_width); - - if (settings_button_view_) { - const gfx::Size settings_size(settings_button_view_->GetPreferredSize()); - int marginFromRight = settings_size.width() + kControlButtonPadding; - if (close_button_) - marginFromRight += close_button_->GetPreferredSize().width(); - gfx::Rect settings_rect(insets.left() + content_width - marginFromRight, - GetContentsBounds().y() + kControlButtonPadding, - settings_size.width(), settings_size.height()); - settings_button_view_->SetBoundsRect(settings_rect); - } - - // Close button. - if (close_button_) { - gfx::Rect content_bounds = GetContentsBounds(); - gfx::Size close_size(close_button_->GetPreferredSize()); - gfx::Rect close_rect( - content_bounds.right() - close_size.width() - kControlButtonPadding, - content_bounds.y() + kControlButtonPadding, close_size.width(), - close_size.height()); - close_button_->SetBoundsRect(close_rect); - } + // Control buttons (close and settings buttons). + gfx::Rect control_buttons_bounds(content_bounds); + int buttons_width = control_buttons_view_->GetPreferredSize().width(); + int buttons_height = control_buttons_view_->GetPreferredSize().height(); + control_buttons_bounds.set_x(control_buttons_bounds.right() - buttons_width - + message_center::kControlButtonPadding); + control_buttons_bounds.set_y(control_buttons_bounds.y() + + message_center::kControlButtonPadding); + control_buttons_bounds.set_width(buttons_width); + control_buttons_bounds.set_height(buttons_height); + control_buttons_view_->SetBoundsRect(control_buttons_bounds); // Small icon. gfx::Size small_image_size(small_image_view_->GetPreferredSize()); @@ -325,6 +315,9 @@ void NotificationView::Layout() { kSmallImagePadding)); small_image_view_->SetBoundsRect(small_image_rect); + // Bottom views. + int bottom_y = insets.top() + std::max(top_height, kNotificationIconSize); + int bottom_height = bottom_view_->GetHeightForWidth(content_width); bottom_view_->SetBounds(insets.left(), bottom_y, content_width, bottom_height); } @@ -367,7 +360,7 @@ void NotificationView::UpdateWithNotification( MessageView::UpdateWithNotification(notification); CreateOrUpdateViews(notification); - CreateOrUpdateCloseButtonView(notification); + UpdateControlButtonsVisibilityWithNotification(notification); Layout(); SchedulePaint(); } @@ -379,18 +372,6 @@ void NotificationView::ButtonPressed(views::Button* sender, // TODO(dewittj): Remove this hack. std::string id(notification_id()); - if (close_button_ && sender == close_button_.get()) { - // Warning: This causes the NotificationView itself to be deleted, so don't - // do anything afterwards. - OnCloseButtonPressed(); - return; - } - - if (sender == settings_button_view_) { - controller()->ClickOnSettingsButton(id); - return; - } - // See if the button pressed was an action button. for (size_t i = 0; i < action_buttons_.size(); ++i) { if (sender == action_buttons_[i]) { @@ -398,20 +379,16 @@ void NotificationView::ButtonPressed(views::Button* sender, return; } } + + NOTREACHED(); } bool NotificationView::IsCloseButtonFocused() const { - if (!close_button_) - return false; - - const views::FocusManager* focus_manager = GetFocusManager(); - return focus_manager && - focus_manager->GetFocusedView() == close_button_.get(); + return control_buttons_view_->IsCloseButtonFocused(); } void NotificationView::RequestFocusOnCloseButton() { - if (close_button_) - close_button_->RequestFocus(); + control_buttons_view_->RequestFocusOnCloseButton(); } void NotificationView::CreateOrUpdateTitleView( @@ -522,25 +499,6 @@ void NotificationView::CreateOrUpdateContextMessageView( } } -void NotificationView::CreateOrUpdateSettingsButtonView( - const Notification& notification) { - delete settings_button_view_; - settings_button_view_ = nullptr; - - if (!settings_button_view_ && notification.delegate() && - notification.delegate()->ShouldDisplaySettingsButton()) { - PaddedButton* settings = new PaddedButton(this); - settings->SetImage(views::Button::STATE_NORMAL, GetSettingsIcon()); - settings->SetAccessibleName(l10n_util::GetStringUTF16( - IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); - settings->SetTooltipText(l10n_util::GetStringUTF16( - IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME)); - settings_button_view_ = settings; - AddChildView(settings_button_view_); - } - UpdateControlButtonsVisibility(); -} - void NotificationView::CreateOrUpdateProgressBarView( const Notification& notification) { if (notification.type() != NOTIFICATION_TYPE_PROGRESS) { @@ -625,8 +583,8 @@ void NotificationView::CreateOrUpdateImageView( image_container_ = new views::View(); image_container_->SetLayoutManager(new views::FillLayout()); - image_container_->set_background(views::Background::CreateSolidBackground( - message_center::kImageBackgroundColor)); + image_container_->SetBackground( + views::CreateSolidBackground(message_center::kImageBackgroundColor)); image_view_ = new message_center::ProportionalImageView(ideal_size); image_container_->AddChildView(image_view_); @@ -692,48 +650,35 @@ void NotificationView::CreateOrUpdateActionButtonViews( } } -void NotificationView::CreateOrUpdateCloseButtonView( +void NotificationView::UpdateControlButtonsVisibilityWithNotification( const Notification& notification) { - if (!notification.pinned() && !close_button_) { - close_button_ = base::MakeUnique<PaddedButton>(this); - close_button_->SetImage(views::Button::STATE_NORMAL, GetCloseIcon()); - close_button_->SetAccessibleName(l10n_util::GetStringUTF16( - IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME)); - close_button_->SetTooltipText(l10n_util::GetStringUTF16( - IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_TOOLTIP)); - close_button_->set_owned_by_client(); - AddChildView(close_button_.get()); - UpdateControlButtonsVisibility(); - } else if (notification.pinned() && close_button_) { - close_button_.reset(); - } + control_buttons_view_->ShowSettingsButton( + notification.delegate() && + notification.delegate()->ShouldDisplaySettingsButton()); + control_buttons_view_->ShowCloseButton(!notification.pinned()); + UpdateControlButtonsVisibility(); } void NotificationView::UpdateControlButtonsVisibility() { #if defined(OS_CHROMEOS) - // On Chrome OS, the settings button and the close button are shown only when - // the mouse is hovering on the notification. + // On Chrome OS, the settings button and the close button are shown when: + // (1) the mouse is hovering on the notification. + // (2) the focus is on the control buttons. const bool target_visibility = - IsMouseHovered() || HasFocus() || - (close_button_ && - (close_button_->HasFocus() || close_button_->IsMouseHovered())) || - (settings_button_view_ && (settings_button_view_->HasFocus() || - settings_button_view_->IsMouseHovered())); + IsMouseHovered() || control_buttons_view_->IsCloseButtonFocused() || + control_buttons_view_->IsSettingsButtonFocused(); #else // On non Chrome OS, the settings button and the close button are always // shown. const bool target_visibility = true; #endif - if (close_button_) { - if (target_visibility != close_button_->visible()) - close_button_->SetVisible(target_visibility); - } + control_buttons_view_->SetVisible(target_visibility); +} - if (settings_button_view_) { - if (target_visibility != settings_button_view_->visible()) - settings_button_view_->SetVisible(target_visibility); - } +NotificationControlButtonsView* NotificationView::GetControlButtonsView() + const { + return control_buttons_view_; } int NotificationView::GetMessageLineLimit(int title_lines, int width) const { diff --git a/chromium/ui/message_center/views/notification_view.h b/chromium/ui/message_center/views/notification_view.h index 7fa691aff25..23e18509b0e 100644 --- a/chromium/ui/message_center/views/notification_view.h +++ b/chromium/ui/message_center/views/notification_view.h @@ -24,6 +24,7 @@ namespace message_center { class BoundedLabel; class NotificationButton; +class NotificationControlButtonsView; class ProportionalImageView; // View that displays all current types of notification (web, basic, image, and @@ -56,9 +57,7 @@ class MESSAGE_CENTER_EXPORT NotificationView bool IsCloseButtonFocused() const override; void RequestFocusOnCloseButton() override; void UpdateControlButtonsVisibility() override; - - protected: - views::ImageButton* close_button() { return close_button_.get(); } + NotificationControlButtonsView* GetControlButtonsView() const override; private: FRIEND_TEST_ALL_PREFIXES(NotificationViewTest, CreateOrUpdateTest); @@ -82,14 +81,14 @@ class MESSAGE_CENTER_EXPORT NotificationView void CreateOrUpdateTitleView(const Notification& notification); void CreateOrUpdateMessageView(const Notification& notification); void CreateOrUpdateContextMessageView(const Notification& notification); - void CreateOrUpdateSettingsButtonView(const Notification& notification); void CreateOrUpdateProgressBarView(const Notification& notification); void CreateOrUpdateListItemViews(const Notification& notification); void CreateOrUpdateIconView(const Notification& notification); void CreateOrUpdateSmallIconView(const Notification& notification); void CreateOrUpdateImageView(const Notification& notification); void CreateOrUpdateActionButtonViews(const Notification& notification); - void CreateOrUpdateCloseButtonView(const Notification& notification); + void UpdateControlButtonsVisibilityWithNotification( + const Notification& notification); int GetMessageLineLimit(int title_lines, int width) const; int GetMessageHeight(int width, int limit) const; @@ -108,7 +107,6 @@ class MESSAGE_CENTER_EXPORT NotificationView BoundedLabel* title_view_ = nullptr; BoundedLabel* message_view_ = nullptr; BoundedLabel* context_message_view_ = nullptr; - views::ImageButton* settings_button_view_ = nullptr; std::vector<views::View*> item_views_; ProportionalImageView* icon_view_ = nullptr; views::View* bottom_view_ = nullptr; @@ -117,8 +115,8 @@ class MESSAGE_CENTER_EXPORT NotificationView views::ProgressBar* progress_bar_view_ = nullptr; std::vector<NotificationButton*> action_buttons_; std::vector<views::View*> separators_; - std::unique_ptr<views::ImageButton> close_button_ = nullptr; std::unique_ptr<views::ImageView> small_image_view_; + NotificationControlButtonsView* control_buttons_view_; DISALLOW_COPY_AND_ASSIGN(NotificationView); }; diff --git a/chromium/ui/message_center/views/notification_view_md.cc b/chromium/ui/message_center/views/notification_view_md.cc new file mode 100644 index 00000000000..39555c6e1a5 --- /dev/null +++ b/chromium/ui/message_center/views/notification_view_md.cc @@ -0,0 +1,932 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/message_center/views/notification_view_md.h" + +#include <stddef.h> + +#include "base/i18n/case_conversion.h" +#include "base/strings/string_util.h" +#include "ui/base/cursor/cursor.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/paint_vector_icon.h" +#include "ui/gfx/skia_util.h" +#include "ui/gfx/text_elider.h" +#include "ui/message_center/message_center.h" +#include "ui/message_center/message_center_style.h" +#include "ui/message_center/notification.h" +#include "ui/message_center/notification_types.h" +#include "ui/message_center/vector_icons.h" +#include "ui/message_center/views/bounded_label.h" +#include "ui/message_center/views/constants.h" +#include "ui/message_center/views/message_center_controller.h" +#include "ui/message_center/views/notification_header_view.h" +#include "ui/message_center/views/padded_button.h" +#include "ui/message_center/views/proportional_image_view.h" +#include "ui/strings/grit/ui_strings.h" +#include "ui/views/animation/ink_drop_highlight.h" +#include "ui/views/background.h" +#include "ui/views/border.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/progress_bar.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/native_cursor.h" +#include "ui/views/view_targeter.h" +#include "ui/views/widget/widget.h" + +namespace message_center { + +namespace { + +// Dimensions. +constexpr gfx::Insets kContentRowPadding(4, 12, 12, 12); +constexpr gfx::Insets kActionsRowPadding(8, 8, 8, 8); +constexpr int kActionsRowHorizontalSpacing = 8; +constexpr gfx::Insets kActionButtonPadding(0, 12, 0, 12); +constexpr gfx::Size kActionButtonMinSize(88, 32); +constexpr gfx::Size kIconViewSize(30, 30); +constexpr gfx::Insets kLargeImageContainerPadding(0, 12, 12, 12); +constexpr gfx::Size kLargeImageMinSize(328, 0); +constexpr gfx::Size kLargeImageMaxSize(328, 218); + +// Foreground of small icon image. +constexpr SkColor kSmallImageBackgroundColor = SK_ColorWHITE; +// Background of small icon image. +const SkColor kSmallImageColor = SkColorSetRGB(0x43, 0x43, 0x43); +// Background of inline actions area. +const SkColor kActionsRowBackgroundColor = SkColorSetRGB(0xee, 0xee, 0xee); +// Base ink drop color of action buttons. +const SkColor kActionButtonInkDropBaseColor = SkColorSetRGB(0x0, 0x0, 0x0); +// Ripple ink drop opacity of action buttons. +const float kActionButtonInkDropRippleVisibleOpacity = 0.08f; +// Highlight (hover) ink drop opacity of action buttons. +const float kActionButtonInkDropHighlightVisibleOpacity = 0.08f; +// Text color of action button. +const SkColor kActionButtonTextColor = SkColorSetRGB(0x33, 0x67, 0xD6); +// Background color of the large image. +const SkColor kLargeImageBackgroundColor = SkColorSetRGB(0xf5, 0xf5, 0xf5); + +// Max number of lines for message_view_. +constexpr int kMaxLinesForMessageView = 1; +constexpr int kMaxLinesForExpandedMessageView = 4; + +constexpr int kCompactTitleMessageViewSpacing = 12; + +constexpr int kProgressBarHeight = 4; + +constexpr int kMessageViewWidth = + message_center::kNotificationWidth - kIconViewSize.width() - + kContentRowPadding.left() - kContentRowPadding.right(); + +const gfx::ImageSkia CreateSolidColorImage(int width, + int height, + SkColor color) { + SkBitmap bitmap; + bitmap.allocN32Pixels(width, height); + bitmap.eraseColor(color); + return gfx::ImageSkia::CreateFrom1xBitmap(bitmap); +} + +// Take the alpha channel of icon, mask it with the foreground, +// then add the masked foreground on top of the background +const gfx::ImageSkia GetMaskedIcon(const gfx::ImageSkia& icon) { + int width = icon.width(); + int height = icon.height(); + + // Background color grey + const gfx::ImageSkia background = CreateSolidColorImage( + width, height, message_center::kSmallImageBackgroundColor); + // Foreground color white + const gfx::ImageSkia foreground = + CreateSolidColorImage(width, height, message_center::kSmallImageColor); + const gfx::ImageSkia masked_small_image = + gfx::ImageSkiaOperations::CreateMaskedImage(foreground, icon); + return gfx::ImageSkiaOperations::CreateSuperimposedImage(background, + masked_small_image); +} + +const gfx::ImageSkia GetProductIcon() { + return gfx::CreateVectorIcon(kProductIcon, kSmallImageColor); +} + +// ItemView //////////////////////////////////////////////////////////////////// + +// ItemViews are responsible for drawing each list notification item's title and +// message next to each other within a single column. +class ItemView : public views::View { + public: + explicit ItemView(const message_center::NotificationItem& item); + ~ItemView() override; + + const char* GetClassName() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(ItemView); +}; + +ItemView::ItemView(const message_center::NotificationItem& item) { + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, gfx::Insets(), 0)); + + views::Label* title = new views::Label(item.title); + title->set_collapse_when_hidden(true); + title->SetHorizontalAlignment(gfx::ALIGN_LEFT); + title->SetEnabledColor(message_center::kRegularTextColor); + title->SetBackgroundColor(message_center::kDimTextBackgroundColor); + AddChildView(title); + + views::Label* message = new views::Label(l10n_util::GetStringFUTF16( + IDS_MESSAGE_CENTER_LIST_NOTIFICATION_MESSAGE_WITH_DIVIDER, item.message)); + message->set_collapse_when_hidden(true); + message->SetHorizontalAlignment(gfx::ALIGN_LEFT); + message->SetEnabledColor(message_center::kDimTextColor); + message->SetBackgroundColor(message_center::kDimTextBackgroundColor); + AddChildView(message); +} + +ItemView::~ItemView() = default; + +const char* ItemView::GetClassName() const { + return "ItemView"; +} + +// CompactTitleMessageView ///////////////////////////////////////////////////// + +// CompactTitleMessageView shows notification title and message in a single +// line. This view is used for NOTIFICATION_TYPE_PROGRESS. +class CompactTitleMessageView : public views::View { + public: + explicit CompactTitleMessageView(); + ~CompactTitleMessageView() override; + + const char* GetClassName() const override; + + void OnPaint(gfx::Canvas* canvas) override; + + void set_title(const base::string16& title) { title_ = title; } + void set_message(const base::string16& message) { message_ = message; } + + private: + DISALLOW_COPY_AND_ASSIGN(CompactTitleMessageView); + + base::string16 title_; + base::string16 message_; + + views::Label* title_view_ = nullptr; + views::Label* message_view_ = nullptr; +}; + +CompactTitleMessageView::~CompactTitleMessageView() = default; + +const char* CompactTitleMessageView::GetClassName() const { + return "CompactTitleMessageView"; +} + +CompactTitleMessageView::CompactTitleMessageView() { + SetLayoutManager(new views::FillLayout()); + + const gfx::FontList& font_list = views::Label().font_list().Derive( + 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); + + title_view_ = new views::Label(); + title_view_->SetFontList(font_list); + title_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + title_view_->SetEnabledColor(message_center::kRegularTextColor); + AddChildView(title_view_); + + message_view_ = new views::Label(); + message_view_->SetFontList(font_list); + message_view_->SetHorizontalAlignment(gfx::ALIGN_RIGHT); + message_view_->SetEnabledColor(message_center::kDimTextColor); + AddChildView(message_view_); +} + +void CompactTitleMessageView::OnPaint(gfx::Canvas* canvas) { + base::string16 title = title_; + base::string16 message = message_; + + const gfx::FontList& font_list = views::Label().font_list().Derive( + 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); + + // Elides title and message. The behavior is based on Android's one. + // * If the title is too long, only the title is shown. + // * If the message is too long, the full content of the title is shown, + // kCompactTitleMessageViewSpacing is added between them, and the elided + // message is shown. + // * If they are short enough, the title is left-aligned and the message is + // right-aligned. + const int original_title_width = + gfx::Canvas::GetStringWidthF(title, font_list); + if (original_title_width >= width()) + message.clear(); + title = gfx::ElideText(title, font_list, width(), gfx::ELIDE_TAIL); + const int title_width = gfx::Canvas::GetStringWidthF(title, font_list); + const int message_width = + std::max(0, width() - title_width - kCompactTitleMessageViewSpacing); + message = gfx::ElideText(message, font_list, message_width, gfx::ELIDE_TAIL); + + title_view_->SetText(title); + message_view_->SetText(message); + + views::View::OnPaint(canvas); +} + +// NotificationButtonMD //////////////////////////////////////////////////////// + +// This class is needed in addition to LabelButton mainly becuase we want to set +// visible_opacity of InkDropHighlight. +// This button capitalizes the given label string. +class NotificationButtonMD : public views::LabelButton { + public: + NotificationButtonMD(views::ButtonListener* listener, + const base::string16& text); + ~NotificationButtonMD() override; + + void SetText(const base::string16& text) override; + const char* GetClassName() const override; + + std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight() + const override; + + private: + DISALLOW_COPY_AND_ASSIGN(NotificationButtonMD); +}; + +NotificationButtonMD::NotificationButtonMD(views::ButtonListener* listener, + const base::string16& text) + : views::LabelButton(listener, + base::i18n::ToUpper(text), + views::style::CONTEXT_BUTTON_MD) { + SetHorizontalAlignment(gfx::ALIGN_CENTER); + SetInkDropMode(views::LabelButton::InkDropMode::ON); + set_has_ink_drop_action_on_click(true); + set_ink_drop_base_color(kActionButtonInkDropBaseColor); + set_ink_drop_visible_opacity(kActionButtonInkDropRippleVisibleOpacity); + SetEnabledTextColors(kActionButtonTextColor); + SetBorder(views::CreateEmptyBorder(kActionButtonPadding)); + SetMinSize(kActionButtonMinSize); + SetFocusForPlatform(); +} + +NotificationButtonMD::~NotificationButtonMD() = default; + +void NotificationButtonMD::SetText(const base::string16& text) { + views::LabelButton::SetText(base::i18n::ToUpper(text)); +} + +const char* NotificationButtonMD::GetClassName() const { + return "NotificationButtonMD"; +} + +std::unique_ptr<views::InkDropHighlight> +NotificationButtonMD::CreateInkDropHighlight() const { + std::unique_ptr<views::InkDropHighlight> highlight = + views::LabelButton::CreateInkDropHighlight(); + highlight->set_visible_opacity(kActionButtonInkDropHighlightVisibleOpacity); + return highlight; +} + +// LargeImageView ////////////////////////////////////////////////////////////// + +class LargeImageView : public views::View { + public: + LargeImageView(); + ~LargeImageView() override; + + void SetImage(const gfx::ImageSkia& image); + + void OnPaint(gfx::Canvas* canvas) override; + const char* GetClassName() const override; + + private: + gfx::Size GetResizedImageSize(); + + gfx::ImageSkia image_; + + DISALLOW_COPY_AND_ASSIGN(LargeImageView); +}; + +LargeImageView::LargeImageView() { + SetBackground(views::CreateSolidBackground(kLargeImageBackgroundColor)); +} + +LargeImageView::~LargeImageView() = default; + +void LargeImageView::SetImage(const gfx::ImageSkia& image) { + image_ = image; + gfx::Size preferred_size = GetResizedImageSize(); + preferred_size.SetToMax(kLargeImageMinSize); + preferred_size.SetToMin(kLargeImageMaxSize); + SetPreferredSize(preferred_size); + SchedulePaint(); + Layout(); +} + +void LargeImageView::OnPaint(gfx::Canvas* canvas) { + views::View::OnPaint(canvas); + + gfx::Size resized_size = GetResizedImageSize(); + gfx::Size drawn_size = resized_size; + drawn_size.SetToMin(kLargeImageMaxSize); + gfx::Rect drawn_bounds = GetContentsBounds(); + drawn_bounds.ClampToCenteredSize(drawn_size); + + gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage( + image_, skia::ImageOperations::RESIZE_BEST, resized_size); + + // Cut off the overflown part. + gfx::ImageSkia drawn_image = gfx::ImageSkiaOperations::ExtractSubset( + resized_image, gfx::Rect(drawn_size)); + + canvas->DrawImageInt(drawn_image, drawn_bounds.x(), drawn_bounds.y()); +} + +const char* LargeImageView::GetClassName() const { + return "LargeImageView"; +} + +// Returns expected size of the image right after resizing. +// The GetResizedImageSize().width() <= kLargeImageMaxSize.width() holds, but +// GetResizedImageSize().height() may be larger than kLargeImageMaxSize.height() +// In this case, the overflown part will be just cutted off from the view. +gfx::Size LargeImageView::GetResizedImageSize() { + gfx::Size original_size = image_.size(); + if (original_size.width() <= kLargeImageMaxSize.width()) + return image_.size(); + + const double proportion = + original_size.height() / static_cast<double>(original_size.width()); + gfx::Size resized_size; + resized_size.SetSize(kLargeImageMaxSize.width(), + kLargeImageMaxSize.width() * proportion); + return resized_size; +} + +// LargeImageContainerView ///////////////////////////////////////////////////// + +// We have a container view outside LargeImageView, because we want to fill +// area that is not coverted by the image by background color. +class LargeImageContainerView : public views::View { + public: + LargeImageContainerView(); + ~LargeImageContainerView() override; + + void SetImage(const gfx::ImageSkia& image); + const char* GetClassName() const override; + + private: + LargeImageView* const image_view_; + + DISALLOW_COPY_AND_ASSIGN(LargeImageContainerView); +}; + +LargeImageContainerView::LargeImageContainerView() + : image_view_(new LargeImageView()) { + SetLayoutManager(new views::FillLayout()); + SetBorder(views::CreateEmptyBorder(kLargeImageContainerPadding)); + SetBackground( + views::CreateSolidBackground(message_center::kImageBackgroundColor)); + AddChildView(image_view_); +} + +LargeImageContainerView::~LargeImageContainerView() = default; + +void LargeImageContainerView::SetImage(const gfx::ImageSkia& image) { + image_view_->SetImage(image); +} + +const char* LargeImageContainerView::GetClassName() const { + return "LargeImageContainerView"; +} + +} // anonymous namespace + +// //////////////////////////////////////////////////////////// +// NotificationViewMD +// //////////////////////////////////////////////////////////// + +views::View* NotificationViewMD::TargetForRect(views::View* root, + const gfx::Rect& rect) { + CHECK_EQ(root, this); + + // TODO(tdanderson): Modify this function to support rect-based event + // targeting. Using the center point of |rect| preserves this function's + // expected behavior for the time being. + gfx::Point point = rect.CenterPoint(); + + // Want to return this for underlying views, otherwise GetCursor is not + // called. But buttons are exceptions, they'll have their own event handlings. + std::vector<views::View*> buttons(action_buttons_.begin(), + action_buttons_.end()); + if (header_row_->settings_button()) + buttons.push_back(header_row_->settings_button()); + if (header_row_->close_button()) + buttons.push_back(header_row_->close_button()); + if (header_row_->expand_button()) + buttons.push_back(header_row_->expand_button()); + buttons.push_back(header_row_); + + for (size_t i = 0; i < buttons.size(); ++i) { + gfx::Point point_in_child = point; + ConvertPointToTarget(this, buttons[i], &point_in_child); + if (buttons[i]->HitTestPoint(point_in_child)) + return buttons[i]->GetEventHandlerForPoint(point_in_child); + } + + return root; +} + +void NotificationViewMD::CreateOrUpdateViews(const Notification& notification) { + CreateOrUpdateContextTitleView(notification); + CreateOrUpdateTitleView(notification); + CreateOrUpdateMessageView(notification); + CreateOrUpdateCompactTitleMessageView(notification); + CreateOrUpdateProgressBarView(notification); + CreateOrUpdateListItemViews(notification); + CreateOrUpdateIconView(notification); + CreateOrUpdateSmallIconView(notification); + CreateOrUpdateImageView(notification); + CreateOrUpdateCloseButtonView(notification); + CreateOrUpdateSettingsButtonView(notification); + UpdateViewForExpandedState(expanded_); + // Should be called at the last because SynthesizeMouseMoveEvent() requires + // everything is in the right location when called. + CreateOrUpdateActionButtonViews(notification); +} + +NotificationViewMD::NotificationViewMD(MessageCenterController* controller, + const Notification& notification) + : MessageView(controller, notification), + clickable_(notification.clickable()) { + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, gfx::Insets(), 0)); + + // |header_row_| contains app_icon, app_name, control buttons, etc... + header_row_ = new NotificationHeaderView(this); + AddChildView(header_row_); + + // |content_row_| contains title, message, image, progressbar, etc... + content_row_ = new views::View(); + views::BoxLayout* content_row_layout = new views::BoxLayout( + views::BoxLayout::kHorizontal, kContentRowPadding, 0); + content_row_layout->set_cross_axis_alignment( + views::BoxLayout::CROSS_AXIS_ALIGNMENT_START); + content_row_->SetLayoutManager(content_row_layout); + AddChildView(content_row_); + + // |left_content_| contains most contents like title, message, etc... + left_content_ = new views::View(); + left_content_->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, gfx::Insets(), 0)); + content_row_->AddChildView(left_content_); + content_row_layout->SetFlexForView(left_content_, 1); + + // |right_content_| contains notification icon and small image. + right_content_ = new views::View(); + right_content_->SetLayoutManager(new views::FillLayout()); + content_row_->AddChildView(right_content_); + + // |action_row_| contains inline action button. + actions_row_ = new views::View(); + actions_row_->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, kActionsRowPadding, + kActionsRowHorizontalSpacing)); + actions_row_->SetBackground( + views::CreateSolidBackground(kActionsRowBackgroundColor)); + actions_row_->SetVisible(false); + AddChildView(actions_row_); + + CreateOrUpdateViews(notification); + + SetEventTargeter( + std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); +} + +NotificationViewMD::~NotificationViewMD() {} + +void NotificationViewMD::Layout() { + MessageView::Layout(); + + // We need to call IsExpandable() at the end of Layout() call, since whether + // we should show expand button or not depends on the current view layout. + // (e.g. Show expand button when |message_view_| exceeds one line.) + header_row_->SetExpandButtonEnabled(IsExpandable()); +} + +void NotificationViewMD::OnFocus() { + MessageView::OnFocus(); + ScrollRectToVisible(GetLocalBounds()); +} + +void NotificationViewMD::ScrollRectToVisible(const gfx::Rect& rect) { + // Notification want to show the whole notification when a part of it (like + // a button) gets focused. + views::View::ScrollRectToVisible(GetLocalBounds()); +} + +gfx::NativeCursor NotificationViewMD::GetCursor(const ui::MouseEvent& event) { + if (!clickable_ || !controller()->HasClickedListener(notification_id())) + return views::View::GetCursor(event); + + return views::GetNativeHandCursor(); +} + +void NotificationViewMD::OnMouseMoved(const ui::MouseEvent& event) { + MessageView::OnMouseMoved(event); + UpdateControlButtonsVisibility(); +} + +void NotificationViewMD::OnMouseEntered(const ui::MouseEvent& event) { + MessageView::OnMouseEntered(event); + UpdateControlButtonsVisibility(); +} + +void NotificationViewMD::OnMouseExited(const ui::MouseEvent& event) { + MessageView::OnMouseExited(event); + UpdateControlButtonsVisibility(); +} + +void NotificationViewMD::UpdateWithNotification( + const Notification& notification) { + MessageView::UpdateWithNotification(notification); + + CreateOrUpdateViews(notification); + Layout(); + SchedulePaint(); +} + +void NotificationViewMD::ButtonPressed(views::Button* sender, + const ui::Event& event) { + // Certain operations can cause |this| to be destructed, so copy the members + // we send to other parts of the code. + // TODO(dewittj): Remove this hack. + std::string id(notification_id()); + + if (header_row_->IsCloseButtonEnabled() && + sender == header_row_->close_button()) { + // Warning: This causes the NotificationViewMD itself to be deleted, so + // don't do anything afterwards. + OnCloseButtonPressed(); + return; + } + + if (header_row_->IsSettingsButtonEnabled() && + sender == header_row_->settings_button()) { + controller()->ClickOnSettingsButton(id); + return; + } + + // Tapping anywhere on |header_row_| can expand the notification, though only + // |expand_button| can be focused by TAB. + if (IsExpandable() && sender == header_row_) { + ToggleExpanded(); + Layout(); + SchedulePaint(); + return; + } + + // See if the button pressed was an action button. + for (size_t i = 0; i < action_buttons_.size(); ++i) { + if (sender == action_buttons_[i]) { + controller()->ClickOnNotificationButton(id, i); + return; + } + } +} + +bool NotificationViewMD::IsCloseButtonFocused() const { + if (!header_row_->IsCloseButtonEnabled()) + return false; + + const views::FocusManager* focus_manager = GetFocusManager(); + return focus_manager && + focus_manager->GetFocusedView() == header_row_->close_button(); +} + +void NotificationViewMD::RequestFocusOnCloseButton() { + if (header_row_->IsCloseButtonEnabled()) + header_row_->close_button()->RequestFocus(); +} + +void NotificationViewMD::CreateOrUpdateContextTitleView( + const Notification& notification) { + header_row_->SetAppName(notification.display_source()); + header_row_->SetTimestamp(notification.timestamp()); +} + +void NotificationViewMD::CreateOrUpdateTitleView( + const Notification& notification) { + if (notification.title().empty() || + notification.type() == NOTIFICATION_TYPE_PROGRESS) { + DCHECK(!title_view_ || left_content_->Contains(title_view_)); + delete title_view_; + title_view_ = nullptr; + return; + } + const gfx::FontList& font_list = views::Label().font_list().Derive( + 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); + + int title_character_limit = + kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter; + + base::string16 title = gfx::TruncateString( + notification.title(), title_character_limit, gfx::WORD_BREAK); + if (!title_view_) { + title_view_ = new views::Label(title); + title_view_->SetFontList(font_list); + title_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + title_view_->SetEnabledColor(message_center::kRegularTextColor); + left_content_->AddChildView(title_view_); + } else { + title_view_->SetText(title); + } +} + +void NotificationViewMD::CreateOrUpdateMessageView( + const Notification& notification) { + if (notification.type() == NOTIFICATION_TYPE_PROGRESS || + notification.message().empty()) { + // Deletion will also remove |message_view_| from its parent. + delete message_view_; + message_view_ = nullptr; + return; + } + + base::string16 text = gfx::TruncateString( + notification.message(), kMessageCharacterLimit, gfx::WORD_BREAK); + + const gfx::FontList& font_list = views::Label().font_list().Derive( + 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL); + + if (!message_view_) { + message_view_ = new BoundedLabel(text, font_list); + message_view_->SetLineLimit(kMaxLinesForMessageView); + message_view_->SetColors(message_center::kDimTextColor, + kContextTextBackgroundColor); + + // TODO(tetsui): Workaround https://crbug.com/682266 by explicitly setting + // the width. + // Ideally, we should fix the original bug, but it seems there's no obvious + // solution for the bug according to https://crbug.com/678337#c7, we should + // ensure that the change won't break any of the users of BoxLayout class. + DCHECK(right_content_); + message_view_->SizeToFit(kMessageViewWidth); + + left_content_->AddChildView(message_view_); + } else { + message_view_->SetText(text); + } + + message_view_->SetVisible(notification.items().empty()); +} + +void NotificationViewMD::CreateOrUpdateCompactTitleMessageView( + const Notification& notification) { + if (notification.type() != NOTIFICATION_TYPE_PROGRESS) { + DCHECK(!compact_title_message_view_ || + left_content_->Contains(compact_title_message_view_)); + delete compact_title_message_view_; + compact_title_message_view_ = nullptr; + return; + } + if (!compact_title_message_view_) { + compact_title_message_view_ = new CompactTitleMessageView(); + left_content_->AddChildView(compact_title_message_view_); + } + + compact_title_message_view_->set_title(notification.title()); + compact_title_message_view_->set_message(notification.message()); + left_content_->InvalidateLayout(); +} + +void NotificationViewMD::CreateOrUpdateProgressBarView( + const Notification& notification) { + if (notification.type() != NOTIFICATION_TYPE_PROGRESS) { + DCHECK(!progress_bar_view_ || left_content_->Contains(progress_bar_view_)); + delete progress_bar_view_; + progress_bar_view_ = nullptr; + header_row_->ClearProgress(); + return; + } + + DCHECK(left_content_); + + if (!progress_bar_view_) { + progress_bar_view_ = new views::ProgressBar(kProgressBarHeight, + /* allow_round_corner */ false); + progress_bar_view_->SetBorder(views::CreateEmptyBorder( + message_center::kProgressBarTopPadding, 0, 0, 0)); + left_content_->AddChildView(progress_bar_view_); + } + + progress_bar_view_->SetValue(notification.progress() / 100.0); + progress_bar_view_->SetVisible(notification.items().empty()); + + header_row_->SetProgress(notification.progress()); +} + +void NotificationViewMD::CreateOrUpdateListItemViews( + const Notification& notification) { + for (auto* item_view : item_views_) + delete item_view; + item_views_.clear(); + + const std::vector<NotificationItem>& items = notification.items(); + + for (size_t i = 0; i < items.size() && i < kMaxLinesForExpandedMessageView; + ++i) { + ItemView* item_view = new ItemView(items[i]); + item_views_.push_back(item_view); + left_content_->AddChildView(item_view); + } + + list_items_count_ = items.size(); + + // Needed when CreateOrUpdateViews is called for update. + if (!item_views_.empty()) + left_content_->InvalidateLayout(); +} + +void NotificationViewMD::CreateOrUpdateIconView( + const Notification& notification) { + if (notification.type() == NOTIFICATION_TYPE_PROGRESS || + notification.type() == NOTIFICATION_TYPE_MULTIPLE || + notification.type() == NOTIFICATION_TYPE_IMAGE) { + DCHECK(!icon_view_ || right_content_->Contains(icon_view_)); + delete icon_view_; + icon_view_ = nullptr; + return; + } + + if (!icon_view_) { + icon_view_ = new ProportionalImageView(kIconViewSize); + right_content_->AddChildView(icon_view_); + } + + gfx::ImageSkia icon = notification.icon().AsImageSkia(); + icon_view_->SetImage(icon, icon.size()); +} + +void NotificationViewMD::CreateOrUpdateSmallIconView( + const Notification& notification) { + gfx::ImageSkia icon = + notification.small_image().IsEmpty() + ? GetProductIcon() + : GetMaskedIcon(notification.small_image().AsImageSkia()); + header_row_->SetAppIcon(icon); +} + +void NotificationViewMD::CreateOrUpdateImageView( + const Notification& notification) { + if (notification.image().IsEmpty()) { + if (image_container_view_) { + DCHECK(Contains(image_container_view_)); + delete image_container_view_; + image_container_view_ = nullptr; + } + return; + } + + if (!image_container_view_) { + image_container_view_ = new LargeImageContainerView(); + // Insert the created image container just after the |content_row_|. + AddChildViewAt(image_container_view_, GetIndexOf(content_row_) + 1); + } + + image_container_view_->SetImage(notification.image().AsImageSkia()); +} + +void NotificationViewMD::CreateOrUpdateActionButtonViews( + const Notification& notification) { + std::vector<ButtonInfo> buttons = notification.buttons(); + bool new_buttons = action_buttons_.size() != buttons.size(); + + if (new_buttons || buttons.size() == 0) { + for (auto* item : action_buttons_) + delete item; + action_buttons_.clear(); + } + + DCHECK_EQ(this, actions_row_->parent()); + + for (size_t i = 0; i < buttons.size(); ++i) { + ButtonInfo button_info = buttons[i]; + if (new_buttons) { + NotificationButtonMD* button = + new NotificationButtonMD(this, button_info.title); + action_buttons_.push_back(button); + actions_row_->AddChildView(button); + } else { + action_buttons_[i]->SetText(button_info.title); + action_buttons_[i]->SchedulePaint(); + action_buttons_[i]->Layout(); + } + } + + // Inherit mouse hover state when action button views reset. + // If the view is not expanded, there should be no hover state. + if (new_buttons && expanded_) { + views::Widget* widget = GetWidget(); + if (widget) { + // This Layout() is needed because button should be in the right location + // in the view hierarchy when SynthesizeMouseMoveEvent() is called. + Layout(); + widget->SetSize(widget->GetContentsView()->GetPreferredSize()); + GetWidget()->SynthesizeMouseMoveEvent(); + } + } +} + +void NotificationViewMD::CreateOrUpdateCloseButtonView( + const Notification& notification) { + if (!notification.pinned()) { + header_row_->SetCloseButtonEnabled(true); + } else { + header_row_->SetCloseButtonEnabled(false); + } +} + +void NotificationViewMD::CreateOrUpdateSettingsButtonView( + const Notification& notification) { + if (notification.delegate() && + notification.delegate()->ShouldDisplaySettingsButton()) + header_row_->SetSettingsButtonEnabled(true); + else + header_row_->SetSettingsButtonEnabled(false); +} + +bool NotificationViewMD::IsExpandable() { + // Expandable if the message exceeds one line. + if (message_view_ && message_view_->visible() && + message_view_->GetLinesForWidthAndLimit(message_view_->width(), -1) > 1) { + return true; + } + // Expandable if there is at least one inline action. + if (actions_row_->has_children()) + return true; + + // Expandable if the notification has image. + if (image_container_view_) + return true; + + // Expandable if there are multiple list items. + if (item_views_.size() > 1) + return true; + + // TODO(fukino): Expandable if both progress bar and message exist. + + return false; +} + +void NotificationViewMD::ToggleExpanded() { + expanded_ = !expanded_; + UpdateViewForExpandedState(expanded_); + content_row_->InvalidateLayout(); + if (controller()) + controller()->UpdateNotificationSize(notification_id()); +} + +void NotificationViewMD::UpdateViewForExpandedState(bool expanded) { + header_row_->SetExpanded(expanded); + if (message_view_) { + message_view_->SetLineLimit(expanded ? kMaxLinesForExpandedMessageView + : kMaxLinesForMessageView); + } + if (image_container_view_) + image_container_view_->SetVisible(expanded); + actions_row_->SetVisible(expanded && actions_row_->has_children()); + for (size_t i = kMaxLinesForMessageView; i < item_views_.size(); ++i) { + item_views_[i]->SetVisible(expanded); + } + header_row_->SetOverflowIndicator( + list_items_count_ - + (expanded ? item_views_.size() : kMaxLinesForMessageView)); +} + +void NotificationViewMD::UpdateControlButtonsVisibility() { + const bool target_visibility = IsMouseHovered() || HasFocus() || + (header_row_->IsExpandButtonEnabled() && + header_row_->expand_button()->HasFocus()) || + (header_row_->IsCloseButtonEnabled() && + header_row_->close_button()->HasFocus()) || + (header_row_->IsSettingsButtonEnabled() && + header_row_->settings_button()->HasFocus()); + + header_row_->SetControlButtonsVisible(target_visibility); +} + +NotificationControlButtonsView* NotificationViewMD::GetControlButtonsView() + const { + // TODO(yoshiki): have this view use NotificationControlButtonsView. + return nullptr; +} + +} // namespace message_center diff --git a/chromium/ui/message_center/views/notification_view_md.h b/chromium/ui/message_center/views/notification_view_md.h new file mode 100644 index 00000000000..8d595d92f1f --- /dev/null +++ b/chromium/ui/message_center/views/notification_view_md.h @@ -0,0 +1,129 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_VIEW_MD_H_ +#define UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_VIEW_MD_H_ + +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "ui/message_center/message_center_export.h" +#include "ui/message_center/views/message_view.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/view_targeter_delegate.h" + +namespace views { +class Label; +class LabelButton; +class ProgressBar; +} + +namespace message_center { + +class BoundedLabel; +class NotificationHeaderView; +class ProportionalImageView; + +namespace { +class CompactTitleMessageView; +class ItemView; +class LargeImageContainerView; +} + +// View that displays all current types of notification (web, basic, image, and +// list) except the custom notification. Future notification types may be +// handled by other classes, in which case instances of those classes would be +// returned by the Create() factory method below. +class MESSAGE_CENTER_EXPORT NotificationViewMD + : public MessageView, + public views::ButtonListener, + public views::ViewTargeterDelegate { + public: + NotificationViewMD(MessageCenterController* controller, + const Notification& notification); + ~NotificationViewMD() override; + + // Overridden from views::View: + void Layout() override; + void OnFocus() override; + void ScrollRectToVisible(const gfx::Rect& rect) override; + gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override; + void OnMouseMoved(const ui::MouseEvent& event) override; + void OnMouseEntered(const ui::MouseEvent& event) override; + void OnMouseExited(const ui::MouseEvent& event) override; + + // Overridden from MessageView: + void UpdateWithNotification(const Notification& notification) override; + void ButtonPressed(views::Button* sender, const ui::Event& event) override; + bool IsCloseButtonFocused() const override; + void RequestFocusOnCloseButton() override; + void UpdateControlButtonsVisibility() override; + NotificationControlButtonsView* GetControlButtonsView() const override; + + // views::ViewTargeterDelegate: + views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override; + + private: + FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, CreateOrUpdateTest); + FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, TestIconSizing); + FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, UpdateButtonsStateTest); + FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, UpdateButtonCountTest); + FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, ExpandLongMessage); + + friend class NotificationViewMDTest; + + void CreateOrUpdateViews(const Notification& notification); + + void CreateOrUpdateContextTitleView(const Notification& notification); + void CreateOrUpdateTitleView(const Notification& notification); + void CreateOrUpdateMessageView(const Notification& notification); + void CreateOrUpdateCompactTitleMessageView(const Notification& notification); + void CreateOrUpdateProgressBarView(const Notification& notification); + void CreateOrUpdateListItemViews(const Notification& notification); + void CreateOrUpdateIconView(const Notification& notification); + void CreateOrUpdateSmallIconView(const Notification& notification); + void CreateOrUpdateImageView(const Notification& notification); + void CreateOrUpdateActionButtonViews(const Notification& notification); + void CreateOrUpdateCloseButtonView(const Notification& notification); + void CreateOrUpdateSettingsButtonView(const Notification& notification); + + bool IsExpandable(); + void ToggleExpanded(); + void UpdateViewForExpandedState(bool expanded); + + // Whether this notification is expanded or not. + bool expanded_ = false; + + // Number of total list items in the given Notification class. + int list_items_count_ = 0; + + // Describes whether the view should display a hand pointer or not. + bool clickable_; + + // Container views directly attached to this view. + NotificationHeaderView* header_row_ = nullptr; + views::View* content_row_ = nullptr; + views::View* actions_row_ = nullptr; + + // Containers for left and right side on |content_row_| + views::View* left_content_ = nullptr; + views::View* right_content_ = nullptr; + + // Views which are dinamicallly created inside view hierarchy. + views::Label* title_view_ = nullptr; + BoundedLabel* message_view_ = nullptr; + ProportionalImageView* icon_view_ = nullptr; + LargeImageContainerView* image_container_view_ = nullptr; + std::vector<views::LabelButton*> action_buttons_; + std::vector<ItemView*> item_views_; + views::ProgressBar* progress_bar_view_ = nullptr; + CompactTitleMessageView* compact_title_message_view_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(NotificationViewMD); +}; + +} // namespace message_center + +#endif // UI_MESSAGE_CENTER_VIEWS_NOTIFICATION_VIEW_MD_H_ diff --git a/chromium/ui/message_center/views/notification_view_md_unittest.cc b/chromium/ui/message_center/views/notification_view_md_unittest.cc new file mode 100644 index 00000000000..f39b2d565fb --- /dev/null +++ b/chromium/ui/message_center/views/notification_view_md_unittest.cc @@ -0,0 +1,535 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/message_center/views/notification_view_md.h" + +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/compositor/scoped_animation_duration_scale_mode.h" +#include "ui/events/event_processor.h" +#include "ui/events/event_utils.h" +#include "ui/events/test/event_generator.h" +#include "ui/gfx/canvas.h" +#include "ui/message_center/message_center_style.h" +#include "ui/message_center/views/bounded_label.h" +#include "ui/message_center/views/message_center_controller.h" +#include "ui/message_center/views/notification_header_view.h" +#include "ui/message_center/views/proportional_image_view.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/test/widget_test.h" + +namespace message_center { + +/* Test fixture ***************************************************************/ + +// Used to fill bitmaps returned by CreateBitmap(). +static const SkColor kBitmapColor = SK_ColorGREEN; + +class NotificationViewMDTest : public views::ViewsTestBase, + public MessageCenterController { + public: + NotificationViewMDTest(); + ~NotificationViewMDTest() override; + + // Overridden from ViewsTestBase: + void SetUp() override; + void TearDown() override; + + // Overridden from MessageCenterController: + void ClickOnNotification(const std::string& notification_id) override; + void RemoveNotification(const std::string& notification_id, + bool by_user) override; + std::unique_ptr<ui::MenuModel> CreateMenuModel( + const NotifierId& notifier_id, + const base::string16& display_source) override; + bool HasClickedListener(const std::string& notification_id) override; + void ClickOnNotificationButton(const std::string& notification_id, + int button_index) override; + void ClickOnSettingsButton(const std::string& notification_id) override; + void UpdateNotificationSize(const std::string& notification_id) override; + + NotificationViewMD* notification_view() const { + return notification_view_.get(); + } + Notification* notification() const { return notification_.get(); } + views::Widget* widget() const { + DCHECK_EQ(widget_, notification_view()->GetWidget()); + return widget_; + } + + protected: + const gfx::Image CreateTestImage(int width, int height); + const SkBitmap CreateBitmap(int width, int height); + std::vector<ButtonInfo> CreateButtons(int number); + + // Paints |view| and returns the size that the original image (which must have + // been created by CreateBitmap()) was scaled to. + gfx::Size GetImagePaintSize(ProportionalImageView* view); + + void UpdateNotificationViews(); + float GetNotificationSlideAmount() const; + bool IsRemoved(const std::string& notification_id) const; + void DispatchGesture(const ui::GestureEventDetails& details); + void BeginScroll(); + void EndScroll(); + void ScrollBy(int dx); + views::ImageButton* GetCloseButton(); + + private: + std::set<std::string> removed_ids_; + + std::unique_ptr<RichNotificationData> data_; + std::unique_ptr<Notification> notification_; + std::unique_ptr<NotificationViewMD> notification_view_; + views::Widget* widget_; + + DISALLOW_COPY_AND_ASSIGN(NotificationViewMDTest); +}; + +NotificationViewMDTest::NotificationViewMDTest() = default; +NotificationViewMDTest::~NotificationViewMDTest() = default; + +void NotificationViewMDTest::SetUp() { + views::ViewsTestBase::SetUp(); + // Create a dummy notification. + data_.reset(new RichNotificationData()); + notification_.reset(new Notification( + NOTIFICATION_TYPE_BASE_FORMAT, std::string("notification id"), + base::UTF8ToUTF16("title"), base::UTF8ToUTF16("message"), + CreateTestImage(80, 80), base::UTF8ToUTF16("display source"), GURL(), + NotifierId(NotifierId::APPLICATION, "extension_id"), *data_, nullptr)); + notification_->set_small_image(CreateTestImage(16, 16)); + notification_->set_image(CreateTestImage(320, 240)); + + // Then create a new NotificationView with that single notification. + // In the actual code path, this is instantiated by + // MessageViewFactory::Create. + // TODO(tetsui): Confirm that NotificationViewMD options are same as one + // created by the method. + notification_view_.reset(new NotificationViewMD(this, *notification_)); + notification_view_->SetIsNested(); + notification_view_->set_owned_by_client(); + + views::Widget::InitParams init_params( + CreateParams(views::Widget::InitParams::TYPE_POPUP)); + widget_ = new views::Widget(); + widget_->Init(init_params); + widget_->SetContentsView(notification_view_.get()); + widget_->SetSize(notification_view_->GetPreferredSize()); + widget_->Show(); +} + +void NotificationViewMDTest::TearDown() { + widget()->Close(); + notification_view_.reset(); + views::ViewsTestBase::TearDown(); +} + +void NotificationViewMDTest::ClickOnNotification( + const std::string& notification_id) { + // For this test, this method should not be invoked. + NOTREACHED(); +} + +void NotificationViewMDTest::RemoveNotification( + const std::string& notification_id, + bool by_user) { + removed_ids_.insert(notification_id); +} + +std::unique_ptr<ui::MenuModel> NotificationViewMDTest::CreateMenuModel( + const NotifierId& notifier_id, + const base::string16& display_source) { + // For this test, this method should not be invoked. + NOTREACHED(); + return nullptr; +} + +bool NotificationViewMDTest::HasClickedListener( + const std::string& notification_id) { + return true; +} + +void NotificationViewMDTest::ClickOnNotificationButton( + const std::string& notification_id, + int button_index) { + // For this test, this method should not be invoked. + NOTREACHED(); +} + +void NotificationViewMDTest::ClickOnSettingsButton( + const std::string& notification_id) { + // For this test, this method should not be invoked. + NOTREACHED(); +} + +void NotificationViewMDTest::UpdateNotificationSize( + const std::string& notification_id) { + widget()->SetSize(notification_view()->GetPreferredSize()); +} + +const gfx::Image NotificationViewMDTest::CreateTestImage(int width, + int height) { + return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height)); +} + +const SkBitmap NotificationViewMDTest::CreateBitmap(int width, int height) { + SkBitmap bitmap; + bitmap.allocN32Pixels(width, height); + bitmap.eraseColor(kBitmapColor); + return bitmap; +} + +std::vector<ButtonInfo> NotificationViewMDTest::CreateButtons(int number) { + ButtonInfo info(base::ASCIIToUTF16("Test button.")); + info.icon = CreateTestImage(4, 4); + return std::vector<ButtonInfo>(number, info); +} + +gfx::Size NotificationViewMDTest::GetImagePaintSize( + ProportionalImageView* view) { + CHECK(view); + if (view->bounds().IsEmpty()) + return gfx::Size(); + + gfx::Size canvas_size = view->bounds().size(); + gfx::Canvas canvas(canvas_size, 1.0 /* image_scale */, true /* is_opaque */); + static_assert(kBitmapColor != SK_ColorBLACK, + "The bitmap color must match the background color"); + canvas.DrawColor(SK_ColorBLACK); + view->OnPaint(&canvas); + + SkBitmap bitmap = canvas.GetBitmap(); + // Incrementally inset each edge at its midpoint to find the bounds of the + // rect containing the image's color. This assumes that the image is + // centered in the canvas. + const int kHalfWidth = canvas_size.width() / 2; + const int kHalfHeight = canvas_size.height() / 2; + gfx::Rect rect(canvas_size); + while (rect.width() > 0 && + bitmap.getColor(rect.x(), kHalfHeight) != kBitmapColor) + rect.Inset(1, 0, 0, 0); + while (rect.height() > 0 && + bitmap.getColor(kHalfWidth, rect.y()) != kBitmapColor) + rect.Inset(0, 1, 0, 0); + while (rect.width() > 0 && + bitmap.getColor(rect.right() - 1, kHalfHeight) != kBitmapColor) + rect.Inset(0, 0, 1, 0); + while (rect.height() > 0 && + bitmap.getColor(kHalfWidth, rect.bottom() - 1) != kBitmapColor) + rect.Inset(0, 0, 0, 1); + + return rect.size(); +} + +void NotificationViewMDTest::UpdateNotificationViews() { + notification_view()->UpdateWithNotification(*notification()); +} + +float NotificationViewMDTest::GetNotificationSlideAmount() const { + return notification_view_->GetSlideOutLayer() + ->transform() + .To2dTranslation() + .x(); +} + +bool NotificationViewMDTest::IsRemoved( + const std::string& notification_id) const { + return (removed_ids_.find(notification_id) != removed_ids_.end()); +} + +void NotificationViewMDTest::DispatchGesture( + const ui::GestureEventDetails& details) { + ui::test::EventGenerator generator( + notification_view()->GetWidget()->GetNativeWindow()); + ui::GestureEvent event(0, 0, 0, ui::EventTimeForNow(), details); + generator.Dispatch(&event); +} + +void NotificationViewMDTest::BeginScroll() { + DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN)); +} + +void NotificationViewMDTest::EndScroll() { + DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END)); +} + +void NotificationViewMDTest::ScrollBy(int dx) { + DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, dx, 0)); +} + +views::ImageButton* NotificationViewMDTest::GetCloseButton() { + return notification_view()->header_row_->close_button(); +} + +/* Unit tests *****************************************************************/ + +// TODO(tetsui): Following tests are not yet ported from NotificationViewTest. +// * CreateOrUpdateTestSettingsButton +// * TestLineLimits +// * TestImageSizing +// * SettingsButtonTest +// * ViewOrderingTest +// * FormatContextMessageTest + +TEST_F(NotificationViewMDTest, CreateOrUpdateTest) { + EXPECT_NE(nullptr, notification_view()->title_view_); + EXPECT_NE(nullptr, notification_view()->message_view_); + EXPECT_NE(nullptr, notification_view()->icon_view_); + EXPECT_NE(nullptr, notification_view()->image_container_view_); + + notification()->set_image(gfx::Image()); + notification()->set_title(base::string16()); + notification()->set_message(base::string16()); + notification()->set_icon(gfx::Image()); + + notification_view()->CreateOrUpdateViews(*notification()); + + EXPECT_EQ(nullptr, notification_view()->title_view_); + EXPECT_EQ(nullptr, notification_view()->message_view_); + EXPECT_EQ(nullptr, notification_view()->image_container_view_); + // We still expect an icon view for all layouts. + EXPECT_NE(nullptr, notification_view()->icon_view_); +} + +TEST_F(NotificationViewMDTest, TestIconSizing) { + // TODO(tetsui): Remove duplicated integer literal in CreateOrUpdateIconView. + const int kNotificationIconSize = 30; + + notification()->set_type(NOTIFICATION_TYPE_SIMPLE); + ProportionalImageView* view = notification_view()->icon_view_; + + // Icons smaller than the maximum size should remain unscaled. + notification()->set_icon( + CreateTestImage(kNotificationIconSize / 2, kNotificationIconSize / 4)); + UpdateNotificationViews(); + EXPECT_EQ(gfx::Size(kNotificationIconSize / 2, kNotificationIconSize / 4) + .ToString(), + GetImagePaintSize(view).ToString()); + + // Icons of exactly the intended icon size should remain unscaled. + notification()->set_icon( + CreateTestImage(kNotificationIconSize, kNotificationIconSize)); + UpdateNotificationViews(); + EXPECT_EQ(gfx::Size(kNotificationIconSize, kNotificationIconSize).ToString(), + GetImagePaintSize(view).ToString()); + + // Icons over the maximum size should be scaled down, maintaining proportions. + notification()->set_icon( + CreateTestImage(2 * kNotificationIconSize, 2 * kNotificationIconSize)); + UpdateNotificationViews(); + EXPECT_EQ(gfx::Size(kNotificationIconSize, kNotificationIconSize).ToString(), + GetImagePaintSize(view).ToString()); + + notification()->set_icon( + CreateTestImage(4 * kNotificationIconSize, 2 * kNotificationIconSize)); + UpdateNotificationViews(); + EXPECT_EQ( + gfx::Size(kNotificationIconSize, kNotificationIconSize / 2).ToString(), + GetImagePaintSize(view).ToString()); +} + +TEST_F(NotificationViewMDTest, UpdateButtonsStateTest) { + notification()->set_buttons(CreateButtons(2)); + notification_view()->CreateOrUpdateViews(*notification()); + widget()->Show(); + + // Action buttons are hidden by collapsed state. + if (!notification_view()->expanded_) + notification_view()->ToggleExpanded(); + EXPECT_TRUE(notification_view()->actions_row_->visible()); + + EXPECT_EQ(views::CustomButton::STATE_NORMAL, + notification_view()->action_buttons_[0]->state()); + + // Now construct a mouse move event 1 pixel inside the boundary of the action + // button. + gfx::Point cursor_location(1, 1); + views::View::ConvertPointToWidget(notification_view()->action_buttons_[0], + &cursor_location); + ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); + widget()->OnMouseEvent(&move); + + EXPECT_EQ(views::CustomButton::STATE_HOVERED, + notification_view()->action_buttons_[0]->state()); + + notification_view()->CreateOrUpdateViews(*notification()); + + EXPECT_EQ(views::CustomButton::STATE_HOVERED, + notification_view()->action_buttons_[0]->state()); + + // Now construct a mouse move event 1 pixel outside the boundary of the + // widget. + cursor_location = gfx::Point(-1, -1); + move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); + widget()->OnMouseEvent(&move); + + EXPECT_EQ(views::CustomButton::STATE_NORMAL, + notification_view()->action_buttons_[0]->state()); +} + +TEST_F(NotificationViewMDTest, UpdateButtonCountTest) { + notification()->set_buttons(CreateButtons(2)); + notification_view()->UpdateWithNotification(*notification()); + widget()->Show(); + + // Action buttons are hidden by collapsed state. + if (!notification_view()->expanded_) + notification_view()->ToggleExpanded(); + EXPECT_TRUE(notification_view()->actions_row_->visible()); + + EXPECT_EQ(views::CustomButton::STATE_NORMAL, + notification_view()->action_buttons_[0]->state()); + EXPECT_EQ(views::CustomButton::STATE_NORMAL, + notification_view()->action_buttons_[1]->state()); + + // Now construct a mouse move event 1 pixel inside the boundary of the action + // button. + gfx::Point cursor_location(1, 1); + views::View::ConvertPointToScreen(notification_view()->action_buttons_[0], + &cursor_location); + ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); + ui::EventDispatchDetails details = + views::test::WidgetTest::GetEventSink(widget())->OnEventFromSource(&move); + EXPECT_FALSE(details.dispatcher_destroyed); + + EXPECT_EQ(views::CustomButton::STATE_HOVERED, + notification_view()->action_buttons_[0]->state()); + EXPECT_EQ(views::CustomButton::STATE_NORMAL, + notification_view()->action_buttons_[1]->state()); + + notification()->set_buttons(CreateButtons(1)); + notification_view()->UpdateWithNotification(*notification()); + + EXPECT_EQ(views::CustomButton::STATE_HOVERED, + notification_view()->action_buttons_[0]->state()); + EXPECT_EQ(1u, notification_view()->action_buttons_.size()); + + // Now construct a mouse move event 1 pixel outside the boundary of the + // widget. + cursor_location = gfx::Point(-1, -1); + move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); + widget()->OnMouseEvent(&move); + + EXPECT_EQ(views::CustomButton::STATE_NORMAL, + notification_view()->action_buttons_[0]->state()); +} + +TEST_F(NotificationViewMDTest, SlideOut) { + ui::ScopedAnimationDurationScaleMode zero_duration_scope( + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); + + UpdateNotificationViews(); + std::string notification_id = notification()->id(); + + BeginScroll(); + ScrollBy(-10); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(-10.f, GetNotificationSlideAmount()); + EndScroll(); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(0.f, GetNotificationSlideAmount()); + + BeginScroll(); + ScrollBy(-200); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(-200.f, GetNotificationSlideAmount()); + EndScroll(); + EXPECT_TRUE(IsRemoved(notification_id)); +} + +TEST_F(NotificationViewMDTest, SlideOutNested) { + ui::ScopedAnimationDurationScaleMode zero_duration_scope( + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); + + UpdateNotificationViews(); + notification_view()->SetIsNested(); + std::string notification_id = notification()->id(); + + BeginScroll(); + ScrollBy(-10); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(-10.f, GetNotificationSlideAmount()); + EndScroll(); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(0.f, GetNotificationSlideAmount()); + + BeginScroll(); + ScrollBy(-200); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(-200.f, GetNotificationSlideAmount()); + EndScroll(); + EXPECT_TRUE(IsRemoved(notification_id)); +} + +// Pinning notification is ChromeOS only feature. +#if defined(OS_CHROMEOS) + +TEST_F(NotificationViewMDTest, SlideOutPinned) { + ui::ScopedAnimationDurationScaleMode zero_duration_scope( + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); + + notification()->set_pinned(true); + UpdateNotificationViews(); + std::string notification_id = notification()->id(); + + BeginScroll(); + ScrollBy(-200); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_LT(-200.f, GetNotificationSlideAmount()); + EndScroll(); + EXPECT_FALSE(IsRemoved(notification_id)); +} + +TEST_F(NotificationViewMDTest, Pinned) { + notification()->set_pinned(true); + + UpdateNotificationViews(); + EXPECT_FALSE(GetCloseButton()->visible()); +} + +#endif // defined(OS_CHROMEOS) + +TEST_F(NotificationViewMDTest, ExpandLongMessage) { + notification()->set_type(NotificationType::NOTIFICATION_TYPE_SIMPLE); + // Test in a case where left_content_ does not have views other than + // message_view_. + // Without doing this, inappropriate fix such as + // message_view_->GetPreferredSize() returning gfx::Size() can pass. + notification()->set_title(base::string16()); + notification()->set_message(base::ASCIIToUTF16( + "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore " + "et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " + "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")); + + UpdateNotificationViews(); + EXPECT_FALSE(notification_view()->expanded_); + const int collapsed_height = notification_view()->message_view_->height(); + const int collapsed_preferred_height = + notification_view()->GetPreferredSize().height(); + EXPECT_LT(0, collapsed_height); + EXPECT_LT(0, collapsed_preferred_height); + + notification_view()->ToggleExpanded(); + EXPECT_TRUE(notification_view()->expanded_); + EXPECT_LT(collapsed_height, notification_view()->message_view_->height()); + EXPECT_LT(collapsed_preferred_height, + notification_view()->GetPreferredSize().height()); + + notification_view()->ToggleExpanded(); + EXPECT_FALSE(notification_view()->expanded_); + EXPECT_EQ(collapsed_height, notification_view()->message_view_->height()); + EXPECT_EQ(collapsed_preferred_height, + notification_view()->GetPreferredSize().height()); +} + +} // namespace message_center diff --git a/chromium/ui/message_center/views/notification_view_unittest.cc b/chromium/ui/message_center/views/notification_view_unittest.cc index 299a1068279..4e5ac153ed8 100644 --- a/chromium/ui/message_center/views/notification_view_unittest.cc +++ b/chromium/ui/message_center/views/notification_view_unittest.cc @@ -29,6 +29,8 @@ #include "ui/message_center/views/message_center_controller.h" #include "ui/message_center/views/message_view_factory.h" #include "ui/message_center/views/notification_button.h" +#include "ui/message_center/views/notification_control_buttons_view.h" +#include "ui/message_center/views/padded_button.h" #include "ui/message_center/views/proportional_image_view.h" #include "ui/views/controls/button/image_button.h" #include "ui/views/layout/fill_layout.h" @@ -160,8 +162,12 @@ class NotificationViewTest : public views::ViewsTestBase, } } - views::ImageButton* GetCloseButton() { - return notification_view()->close_button(); + PaddedButton* GetCloseButton() { + return notification_view()->control_buttons_view_->close_button(); + } + + PaddedButton* GetSettingsButton() { + return notification_view()->control_buttons_view_->settings_button(); } void UpdateNotificationViews() { @@ -308,11 +314,16 @@ TEST_F(NotificationViewTest, CreateOrUpdateTest) { notification()->set_message(base::ASCIIToUTF16("")); notification()->set_icon(gfx::Image()); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); EXPECT_TRUE(NULL == notification_view()->title_view_); EXPECT_TRUE(NULL == notification_view()->message_view_); EXPECT_TRUE(NULL == notification_view()->image_view_); - EXPECT_TRUE(NULL == notification_view()->settings_button_view_); + // Notification must have a control buttons view. + EXPECT_TRUE(NULL != notification_view()->control_buttons_view_); + // Notification is not pinned and have a close button by default. + EXPECT_TRUE(NULL != GetCloseButton()); + // Notification doesn't have a settings button by default. + EXPECT_TRUE(NULL == GetSettingsButton()); // We still expect an icon view for all layouts. EXPECT_TRUE(NULL != notification_view()->icon_view_); } @@ -328,11 +339,12 @@ TEST_F(NotificationViewTest, CreateOrUpdateTestSettingsButton) { NotifierId(NotifierId::APPLICATION, "extension_id"), *data(), delegate.get()); - notification_view()->CreateOrUpdateViews(notf); + notification_view()->UpdateWithNotification(notf); EXPECT_TRUE(NULL != notification_view()->title_view_); EXPECT_TRUE(NULL != notification_view()->message_view_); EXPECT_TRUE(NULL != notification_view()->context_message_view_); - EXPECT_TRUE(NULL != notification_view()->settings_button_view_); + EXPECT_TRUE(NULL != GetCloseButton()); + EXPECT_TRUE(NULL != GetSettingsButton()); EXPECT_TRUE(NULL != notification_view()->icon_view_); EXPECT_TRUE(NULL == notification_view()->image_view_); @@ -341,21 +353,21 @@ TEST_F(NotificationViewTest, CreateOrUpdateTestSettingsButton) { TEST_F(NotificationViewTest, TestLineLimits) { notification()->set_image(CreateTestImage(0, 0)); notification()->set_context_message(base::ASCIIToUTF16("")); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); EXPECT_EQ(5, notification_view()->GetMessageLineLimit(0, 360)); EXPECT_EQ(5, notification_view()->GetMessageLineLimit(1, 360)); EXPECT_EQ(3, notification_view()->GetMessageLineLimit(2, 360)); notification()->set_image(CreateTestImage(2, 2)); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); EXPECT_EQ(2, notification_view()->GetMessageLineLimit(0, 360)); EXPECT_EQ(2, notification_view()->GetMessageLineLimit(1, 360)); EXPECT_EQ(1, notification_view()->GetMessageLineLimit(2, 360)); notification()->set_context_message(base::ASCIIToUTF16("foo")); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); EXPECT_TRUE(notification_view()->context_message_view_ != NULL); @@ -448,7 +460,7 @@ TEST_F(NotificationViewTest, TestImageSizing) { TEST_F(NotificationViewTest, UpdateButtonsStateTest) { notification()->set_buttons(CreateButtons(2)); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); widget()->Show(); EXPECT_EQ(views::CustomButton::STATE_NORMAL, @@ -466,7 +478,7 @@ TEST_F(NotificationViewTest, UpdateButtonsStateTest) { EXPECT_EQ(views::CustomButton::STATE_HOVERED, notification_view()->action_buttons_[0]->state()); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); EXPECT_EQ(views::CustomButton::STATE_HOVERED, notification_view()->action_buttons_[0]->state()); @@ -484,7 +496,7 @@ TEST_F(NotificationViewTest, UpdateButtonsStateTest) { TEST_F(NotificationViewTest, UpdateButtonCountTest) { notification()->set_buttons(CreateButtons(2)); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); widget()->Show(); EXPECT_EQ(views::CustomButton::STATE_NORMAL, @@ -509,7 +521,7 @@ TEST_F(NotificationViewTest, UpdateButtonCountTest) { notification_view()->action_buttons_[1]->state()); notification()->set_buttons(CreateButtons(1)); - notification_view()->CreateOrUpdateViews(*notification()); + notification_view()->UpdateWithNotification(*notification()); EXPECT_EQ(views::CustomButton::STATE_HOVERED, notification_view()->action_buttons_[0]->state()); @@ -536,19 +548,16 @@ TEST_F(NotificationViewTest, SettingsButtonTest) { GURL("https://hello.com"), NotifierId(NotifierId::APPLICATION, "extension_id"), *data(), delegate.get()); - notification_view()->CreateOrUpdateViews(notf); + notification_view()->UpdateWithNotification(notf); widget()->Show(); - notification_view()->Layout(); - EXPECT_TRUE(NULL != notification_view()->settings_button_view_); - EXPECT_EQ(views::CustomButton::STATE_NORMAL, - notification_view()->settings_button_view_->state()); + EXPECT_TRUE(NULL != GetSettingsButton()); + EXPECT_EQ(views::CustomButton::STATE_NORMAL, GetSettingsButton()->state()); // Now construct a mouse move event 1 pixel inside the boundary of the action // button. gfx::Point cursor_location(1, 1); - views::View::ConvertPointToScreen(notification_view()->settings_button_view_, - &cursor_location); + views::View::ConvertPointToScreen(GetSettingsButton(), &cursor_location); ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); widget()->OnMouseEvent(&move); @@ -556,8 +565,7 @@ TEST_F(NotificationViewTest, SettingsButtonTest) { views::test::WidgetTest::GetEventSink(widget())->OnEventFromSource(&move); EXPECT_FALSE(details.dispatcher_destroyed); - EXPECT_EQ(views::CustomButton::STATE_HOVERED, - notification_view()->settings_button_view_->state()); + EXPECT_EQ(views::CustomButton::STATE_HOVERED, GetSettingsButton()->state()); // Now construct a mouse move event 1 pixel outside the boundary of the // widget. @@ -566,8 +574,7 @@ TEST_F(NotificationViewTest, SettingsButtonTest) { ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); widget()->OnMouseEvent(&move); - EXPECT_EQ(views::CustomButton::STATE_NORMAL, - notification_view()->settings_button_view_->state()); + EXPECT_EQ(views::CustomButton::STATE_NORMAL, GetSettingsButton()->state()); } TEST_F(NotificationViewTest, ViewOrderingTest) { diff --git a/chromium/ui/message_center/views/notifier_settings_view.cc b/chromium/ui/message_center/views/notifier_settings_view.cc index 3e541c754a2..7958f037b9f 100644 --- a/chromium/ui/message_center/views/notifier_settings_view.cc +++ b/chromium/ui/message_center/views/notifier_settings_view.cc @@ -21,6 +21,7 @@ #include "ui/events/event_utils.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/image/image.h" #include "ui/message_center/message_center_style.h" @@ -424,8 +425,7 @@ NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider* provider) provider_->AddObserver(this); SetFocusBehavior(FocusBehavior::ALWAYS); - set_background( - views::Background::CreateSolidBackground(kMessageCenterBackgroundColor)); + SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor)); SetPaintToLayer(); title_label_ = new views::Label( @@ -440,6 +440,7 @@ NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider* provider) AddChildView(title_label_); scroller_ = new views::ScrollView(); + scroller_->SetBackgroundColor(kMessageCenterBackgroundColor); scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false)); scroller_->SetHorizontalScrollBar(new views::OverlayScrollBar(true)); AddChildView(scroller_); @@ -489,12 +490,14 @@ void NotifierSettingsView::UpdateContentsView( buttons_.clear(); views::View* contents_view = new views::View(); - contents_view->SetLayoutManager(new views::BoxLayout( - views::BoxLayout::kVertical, settings::kHorizontalMargin, 0, 0)); + contents_view->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, + gfx::Insets(0, settings::kHorizontalMargin))); views::View* contents_title_view = new views::View(); - contents_title_view->SetLayoutManager(new views::BoxLayout( - views::BoxLayout::kVertical, 0, 0, kComputedTitleElementSpacing)); + contents_title_view->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, gfx::Insets(), + kComputedTitleElementSpacing)); bool need_account_switcher = provider_ && provider_->GetNotifierGroupCount() > 1; diff --git a/chromium/ui/message_center/views/padded_button.cc b/chromium/ui/message_center/views/padded_button.cc index 08e3e6f1620..b85350446e1 100644 --- a/chromium/ui/message_center/views/padded_button.cc +++ b/chromium/ui/message_center/views/padded_button.cc @@ -4,6 +4,7 @@ #include "ui/message_center/views/padded_button.h" +#include "base/memory/ptr_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas.h" #include "ui/message_center/message_center_style.h" @@ -22,8 +23,7 @@ PaddedButton::PaddedButton(views::ButtonListener* listener) SetFocusPainter(views::Painter::CreateSolidFocusPainter( kFocusBorderColor, gfx::Insets(1, 2, 2, 2))); - set_background( - views::Background::CreateSolidBackground(kControlButtonBackgroundColor)); + SetBackground(views::CreateSolidBackground(kControlButtonBackgroundColor)); SetBorder(views::CreateEmptyBorder(gfx::Insets(kControlButtonBorderSize))); set_animate_on_state_change(false); diff --git a/chromium/ui/message_center/views/toast_contents_view.cc b/chromium/ui/message_center/views/toast_contents_view.cc index 785ffa6c05e..20c1ec930e8 100644 --- a/chromium/ui/message_center/views/toast_contents_view.cc +++ b/chromium/ui/message_center/views/toast_contents_view.cc @@ -12,6 +12,8 @@ #include "base/time/time.h" #include "build/build_config.h" #include "ui/accessibility/ax_node_data.h" +#include "ui/aura/window.h" +#include "ui/aura/window_targeter.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/animation/animation_delegate.h" @@ -65,7 +67,7 @@ ToastContentsView::ToastContentsView( // Sets the transparent background. Then, when the message view is slid out, // the whole toast seems to slide although the actual bound of the widget // remains. This is hacky but easier to keep the consistency. - set_background(views::Background::CreateSolidBackground(0, 0, 0, 0)); + SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT)); fade_animation_.reset(new gfx::SlideAnimation(this)); fade_animation_->SetSlideDuration(kFadeInOutDuration); @@ -171,12 +173,6 @@ void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds) { bounds_animation_->Show(); } -void ToastContentsView::ActivateToast() { - set_can_activate(true); - if (GetWidget()) - GetWidget()->Activate(); -} - void ToastContentsView::StartFadeIn() { // The decrement is done in OnBoundsAnimationEndedOrCancelled callback. if (collection_) @@ -408,6 +404,15 @@ void ToastContentsView::CreateWidget( #endif widget->Init(params); + +#if defined(OS_CHROMEOS) + // On Chrome OS, this widget is shown in the shelf container. It means this + // widget would inherit the parent's window targeter (ShelfWindowTarget) by + // default. But it is not good for popup. So we override it with the normal + // WindowTargeter. + gfx::NativeWindow native_window = widget->GetNativeWindow(); + native_window->SetEventTargeter(base::MakeUnique<aura::WindowTargeter>()); +#endif } gfx::Rect ToastContentsView::GetClosedToastBounds(gfx::Rect bounds) { diff --git a/chromium/ui/message_center/views/toast_contents_view.h b/chromium/ui/message_center/views/toast_contents_view.h index 7c762d145b2..ee1895584c9 100644 --- a/chromium/ui/message_center/views/toast_contents_view.h +++ b/chromium/ui/message_center/views/toast_contents_view.h @@ -75,9 +75,6 @@ class MESSAGE_CENTER_EXPORT ToastContentsView void SetBoundsWithAnimation(gfx::Rect new_bounds); - // Makes the toast activatable, then activate. - void ActivateToast(); - // Origin and bounds are not 'instant', but rather 'current stable values', // there could be animation in progress that targets these values. gfx::Point origin() { return origin_; } |