Commit d540c125 authored by Tetsui Ohkubo's avatar Tetsui Ohkubo Committed by Commit Bot

New implementation of MessagePopupCollection

This CL completely rewrites MessagePopupCollection which manages popup
notifications. The requirement for the class was changed over time, and
that made the class fragile. Also, there were too many internal states.

For simplicity and full test coverage, MessagePopupCollection manages
all animation states, and only single animation runs at one time.
(This is different from ToastContentsView. The class corresponds to
MessagePopupView in this CL, and it has fewer responsibilities.)

Also this CL has new Chrome OS popup animation in mind. go/qs-proto

As a further refactoring, merging of PopupAlignmentDelegate to
MessagePopupCollection is planned.

Design doc: go/chrome-popup-refactoring

BUG=863366
TEST=MessagePopupCollectionTest

Change-Id: I77c908423c03869d4ac3fa04133f41b73877bd3b
Reviewed-on: https://chromium-review.googlesource.com/1132720Reviewed-by: default avatarYoshiki Iguchi <yoshiki@chromium.org>
Reviewed-by: default avatarEvan Stade <estade@chromium.org>
Commit-Queue: Tetsui Ohkubo <tetsui@chromium.org>
Cr-Commit-Position: refs/heads/master@{#579349}
parent 2a3e6c9d
......@@ -55,6 +55,8 @@ void AshPopupAlignmentDelegate::StartObserving(
}
void AshPopupAlignmentDelegate::SetTrayBubbleHeight(int height) {
const int old_tray_bubble_height = tray_bubble_height_;
tray_bubble_height_ = height;
// If the shelf is shown during auto-hide state, the distance from the edge
......@@ -69,7 +71,8 @@ void AshPopupAlignmentDelegate::SetTrayBubbleHeight(int height) {
else
tray_bubble_height_ = 0;
DoUpdateIfPossible();
if (old_tray_bubble_height != tray_bubble_height_)
ResetBounds();
}
int AshPopupAlignmentDelegate::GetToastOriginX(
......@@ -104,9 +107,10 @@ bool AshPopupAlignmentDelegate::IsFromLeft() const {
return GetAlignment() == SHELF_ALIGNMENT_LEFT;
}
void AshPopupAlignmentDelegate::RecomputeAlignment(
bool AshPopupAlignmentDelegate::RecomputeAlignment(
const display::Display& display) {
// Nothing needs to be done.
return false;
}
void AshPopupAlignmentDelegate::ConfigureWidgetInitParamsForContainer(
......@@ -134,8 +138,12 @@ display::Display AshPopupAlignmentDelegate::GetCurrentDisplay() const {
}
void AshPopupAlignmentDelegate::UpdateWorkArea() {
work_area_ = shelf_->GetUserWorkAreaBounds();
DoUpdateIfPossible();
gfx::Rect new_work_area = shelf_->GetUserWorkAreaBounds();
if (work_area_ == new_work_area)
return;
work_area_ = new_work_area;
ResetBounds();
}
///////////////////////////////////////////////////////////////////////////////
......
......@@ -53,7 +53,7 @@ class ASH_EXPORT AshPopupAlignmentDelegate
gfx::Rect GetWorkArea() const override;
bool IsTopDown() const override;
bool IsFromLeft() const override;
void RecomputeAlignment(const display::Display& display) override;
bool RecomputeAlignment(const display::Display& display) override;
void ConfigureWidgetInitParamsForContainer(
views::Widget* widget,
views::Widget::InitParams* init_params) override;
......
......@@ -321,7 +321,7 @@ NotificationTray::NotificationTray(Shelf* shelf,
popup_alignment_delegate_ =
std::make_unique<AshPopupAlignmentDelegate>(shelf);
popup_collection_ = std::make_unique<message_center::MessagePopupCollection>(
message_center(), popup_alignment_delegate_.get());
popup_alignment_delegate_.get());
display::Screen* screen = display::Screen::GetScreen();
popup_alignment_delegate_->StartObserving(
screen, screen->GetDisplayNearestWindow(status_area_window_));
......@@ -425,7 +425,7 @@ bool NotificationTray::ShowPopups() {
if (IsMessageCenterVisible())
return false;
popup_collection_->DoUpdate();
popup_collection_->Update();
return true;
}
......
......@@ -70,7 +70,6 @@ UnifiedSystemTray::UiDelegate::UiDelegate(UnifiedSystemTray* owner)
std::make_unique<AshPopupAlignmentDelegate>(owner->shelf());
message_popup_collection_ =
std::make_unique<message_center::MessagePopupCollection>(
message_center::MessageCenter::Get(),
popup_alignment_delegate_.get());
display::Screen* screen = display::Screen::GetScreen();
popup_alignment_delegate_->StartObserving(
......@@ -87,7 +86,7 @@ void UnifiedSystemTray::UiDelegate::OnMessageCenterContentsChanged() {
bool UnifiedSystemTray::UiDelegate::ShowPopups() {
if (owner_->IsBubbleShown())
return false;
message_popup_collection_->DoUpdate();
message_popup_collection_->Update();
return true;
}
......
......@@ -17,8 +17,8 @@ message_center::UiDelegate* CreateUiDelegate() {
PopupsOnlyUiDelegate::PopupsOnlyUiDelegate() {
ui_controller_.reset(new message_center::UiController(this));
alignment_delegate_.reset(new message_center::DesktopPopupAlignmentDelegate);
popup_collection_.reset(new message_center::MessagePopupCollection(
message_center(), alignment_delegate_.get()));
popup_collection_.reset(
new message_center::MessagePopupCollection(alignment_delegate_.get()));
message_center()->SetHasMessageCenterView(false);
}
......@@ -35,7 +35,7 @@ message_center::MessageCenter* PopupsOnlyUiDelegate::message_center() {
bool PopupsOnlyUiDelegate::ShowPopups() {
alignment_delegate_->StartObserving(display::Screen::GetScreen());
popup_collection_->DoUpdate();
popup_collection_->Update();
return true;
}
......
......@@ -120,6 +120,8 @@ jumbo_component("message_center") {
"views/desktop_popup_alignment_delegate.h",
"views/message_popup_collection.cc",
"views/message_popup_collection.h",
"views/message_popup_view.cc",
"views/message_popup_view.h",
"views/message_view.cc",
"views/message_view.h",
"views/message_view_factory.cc",
......@@ -144,8 +146,6 @@ jumbo_component("message_center") {
"views/proportional_image_view.h",
"views/slide_out_controller.cc",
"views/slide_out_controller.h",
"views/toast_contents_view.cc",
"views/toast_contents_view.h",
]
if (!is_chromeos) {
sources += [
......
......@@ -211,7 +211,7 @@ class MESSAGE_CENTER_EXPORT MessageCenter {
friend class MessageCenterImplTestWithoutChangeQueue;
friend class UiControllerTest;
friend class TrayViewControllerTest;
friend class test::MessagePopupCollectionTest;
friend class MessagePopupCollectionTest;
virtual void DisableTimersForTest() = 0;
MessageCenter();
......
......@@ -56,10 +56,10 @@ bool DesktopPopupAlignmentDelegate::IsFromLeft() const {
return (alignment_ & POPUP_ALIGNMENT_LEFT) != 0;
}
void DesktopPopupAlignmentDelegate::RecomputeAlignment(
bool DesktopPopupAlignmentDelegate::RecomputeAlignment(
const display::Display& display) {
if (work_area_ == display.work_area())
return;
return false;
work_area_ = display.work_area();
......@@ -79,6 +79,8 @@ void DesktopPopupAlignmentDelegate::RecomputeAlignment(
work_area_.y() == display.bounds().y())
? POPUP_ALIGNMENT_LEFT
: POPUP_ALIGNMENT_RIGHT;
return true;
}
void DesktopPopupAlignmentDelegate::ConfigureWidgetInitParamsForContainer(
......@@ -101,8 +103,8 @@ void DesktopPopupAlignmentDelegate::UpdatePrimaryDisplay() {
display::Display primary_display = screen_->GetPrimaryDisplay();
if (primary_display.id() != primary_display_id_) {
primary_display_id_ = primary_display.id();
RecomputeAlignment(primary_display);
DoUpdateIfPossible();
if (RecomputeAlignment(primary_display))
ResetBounds();
}
}
......
......@@ -37,7 +37,7 @@ class MESSAGE_CENTER_EXPORT DesktopPopupAlignmentDelegate
gfx::Rect GetWorkArea() const override;
bool IsTopDown() const override;
bool IsFromLeft() const override;
void RecomputeAlignment(const display::Display& display) override;
bool RecomputeAlignment(const display::Display& display) override;
void ConfigureWidgetInitParamsForContainer(
views::Widget* widget,
views::Widget::InitParams* init_params) override;
......
// Copyright 2018 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/message_popup_view.h"
#include "base/feature_list.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.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/message_center/public/cpp/features.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/views/message_popup_collection.h"
#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/popup_alignment_delegate.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
#if defined(OS_WIN)
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif
namespace message_center {
MessagePopupView::MessagePopupView(const Notification& notification,
PopupAlignmentDelegate* alignment_delegate,
MessagePopupCollection* popup_collection)
: message_view_(MessageViewFactory::Create(notification, true)),
alignment_delegate_(alignment_delegate),
popup_collection_(popup_collection),
a11y_feedback_on_init_(
notification.rich_notification_data()
.should_make_spoken_feedback_for_popup_updates) {
#if !defined(OS_CHROMEOS)
if (!base::FeatureList::IsEnabled(message_center::kNewStyleNotifications)) {
context_menu_controller_ =
std::make_unique<MessageViewContextMenuController>();
message_view_->set_context_menu_controller(context_menu_controller_.get());
}
#endif
SetLayoutManager(std::make_unique<views::FillLayout>());
if (!message_view_->IsManuallyExpandedOrCollapsed())
message_view_->SetExpanded(message_view_->IsAutoExpandingAllowed());
AddChildView(message_view_);
set_notify_enter_exit_on_child(true);
}
MessagePopupView::MessagePopupView(PopupAlignmentDelegate* alignment_delegate,
MessagePopupCollection* popup_collection)
: message_view_(nullptr),
alignment_delegate_(alignment_delegate),
popup_collection_(popup_collection),
a11y_feedback_on_init_(false) {
SetLayoutManager(std::make_unique<views::FillLayout>());
}
MessagePopupView::~MessagePopupView() = default;
void MessagePopupView::UpdateContents(const Notification& notification) {
message_view_->UpdateWithNotification(notification);
popup_collection_->NotifyPopupResized();
if (notification.rich_notification_data()
.should_make_spoken_feedback_for_popup_updates) {
NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
}
}
float MessagePopupView::GetOpacity() const {
return GetWidget()->GetLayer()->opacity();
}
void MessagePopupView::SetPopupBounds(const gfx::Rect& bounds) {
GetWidget()->SetBounds(bounds);
}
void MessagePopupView::SetOpacity(float opacity) {
GetWidget()->SetOpacity(opacity);
}
void MessagePopupView::AutoCollapse() {
if (is_hovered_ || message_view_->IsManuallyExpandedOrCollapsed())
return;
message_view_->SetExpanded(false);
}
void MessagePopupView::Show() {
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.keep_on_top = true;
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
#else
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
#endif
params.delegate = this;
views::Widget* widget = new views::Widget();
alignment_delegate_->ConfigureWidgetInitParamsForContainer(widget, &params);
widget->set_focus_on_creation(false);
widget->AddObserver(this);
#if defined(OS_WIN)
// We want to ensure that this toast always goes to the native desktop,
// not the Ash desktop (since there is already another toast contents view
// there.
if (!params.parent)
params.native_widget = new views::DesktopNativeWidgetAura(widget);
#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(std::make_unique<aura::WindowTargeter>());
#endif
widget->SetOpacity(0.0);
widget->ShowInactive();
if (a11y_feedback_on_init_)
NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
}
void MessagePopupView::Close() {
if (!GetWidget()) {
DeleteDelegate();
return;
}
if (!GetWidget()->IsClosed())
GetWidget()->CloseNow();
}
void MessagePopupView::OnMouseEntered(const ui::MouseEvent& event) {
is_hovered_ = true;
popup_collection_->Update();
}
void MessagePopupView::OnMouseExited(const ui::MouseEvent& event) {
is_hovered_ = false;
popup_collection_->Update();
}
void MessagePopupView::ChildPreferredSizeChanged(views::View* child) {
popup_collection_->NotifyPopupResized();
}
void MessagePopupView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
message_view_->GetAccessibleNodeData(node_data);
node_data->role = ax::mojom::Role::kAlertDialog;
}
const char* MessagePopupView::GetClassName() const {
return "MessagePopupView";
}
void MessagePopupView::OnDisplayChanged() {
OnWorkAreaChanged();
}
void MessagePopupView::OnWorkAreaChanged() {
views::Widget* widget = GetWidget();
if (!widget)
return;
gfx::NativeView native_view = widget->GetNativeView();
if (!native_view)
return;
if (alignment_delegate_->RecomputeAlignment(
display::Screen::GetScreen()->GetDisplayNearestView(native_view))) {
popup_collection_->ResetBounds();
}
}
void MessagePopupView::OnWidgetActivationChanged(views::Widget* widget,
bool active) {
is_active_ = active;
popup_collection_->Update();
}
} // namespace message_center
// Copyright 2018 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_MESSAGE_POPUP_VIEW_H_
#define UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_VIEW_H_
#include "ui/message_center/message_center_export.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_observer.h"
namespace message_center {
class MessagePopupCollection;
class MessageView;
class MessageViewContextMenuController;
class Notification;
class PopupAlignmentDelegate;
// The widget delegate of a notification popup. The view is owned by the widget.
class MESSAGE_CENTER_EXPORT MessagePopupView : public views::WidgetDelegateView,
public views::WidgetObserver {
public:
MessagePopupView(const Notification& notification,
PopupAlignmentDelegate* alignment_delegate,
MessagePopupCollection* popup_collection);
~MessagePopupView() override;
// Update notification contents to |notification|. Virtual for unit testing.
virtual void UpdateContents(const Notification& notification);
// Return opacity of the widget.
float GetOpacity() const;
// Sets widget bounds.
void SetPopupBounds(const gfx::Rect& bounds);
// Set widget opacity.
void SetOpacity(float opacity);
// Collapses the notification unless the user is interacting with it. The
// request can be ignored. Virtual for unit testing.
virtual void AutoCollapse();
// Shows popup. After this call, MessagePopupView should be owned by the
// widget.
void Show();
// Closes popup. It should be callable even if Show() is not called, and
// in such case MessagePopupView should be deleted. Virtual for unit testing.
virtual void Close();
// views::WidgetDelegateView:
void OnMouseEntered(const ui::MouseEvent& event) override;
void OnMouseExited(const ui::MouseEvent& event) override;
void ChildPreferredSizeChanged(views::View* child) override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
const char* GetClassName() const override;
void OnDisplayChanged() override;
void OnWorkAreaChanged() override;
// views::WidgetObserver:
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
bool is_hovered() const { return is_hovered_; }
bool is_active() const { return is_active_; }
protected:
// For unit testing.
MessagePopupView(PopupAlignmentDelegate* alignment_delegate,
MessagePopupCollection* popup_collection);
private:
// Owned by views hierarchy.
MessageView* message_view_;
// Unowned.
PopupAlignmentDelegate* const alignment_delegate_;
MessagePopupCollection* const popup_collection_;
std::unique_ptr<MessageViewContextMenuController> context_menu_controller_;
const bool a11y_feedback_on_init_;
bool is_hovered_ = false;
bool is_active_ = false;
DISALLOW_COPY_AND_ASSIGN(MessagePopupView);
};
} // namespace message_center
#endif // UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_VIEW_H_
......@@ -12,9 +12,9 @@ PopupAlignmentDelegate::PopupAlignmentDelegate() : collection_(NULL) {}
PopupAlignmentDelegate::~PopupAlignmentDelegate() {}
void PopupAlignmentDelegate::DoUpdateIfPossible() {
void PopupAlignmentDelegate::ResetBounds() {
if (collection_)
collection_->DoUpdate();
collection_->ResetBounds();
}
} // namespace message_center
......@@ -47,8 +47,9 @@ class MESSAGE_CENTER_EXPORT PopupAlignmentDelegate {
// Called when a new toast appears or toasts are rearranged in the |display|.
// The subclass may override this method to check the current desktop status
// so that the toasts are arranged at the correct place.
virtual void RecomputeAlignment(const display::Display& display) = 0;
// so that the toasts are arranged at the correct place. Return true if
// alignment is actually changed.
virtual bool RecomputeAlignment(const display::Display& display) = 0;
// Sets the parent container for popups. If it does not set a parent a
// default parent will be used (e.g. the native desktop on Windows).
......@@ -63,7 +64,7 @@ class MESSAGE_CENTER_EXPORT PopupAlignmentDelegate {
protected:
virtual ~PopupAlignmentDelegate();
void DoUpdateIfPossible();
void ResetBounds();
private:
MessagePopupCollection* collection_;
......
This diff is collapsed.
// Copyright (c) 2013 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_TOAST_CONTENTS_VIEW_H_
#define UI_MESSAGE_CENTER_VIEWS_TOAST_CONTENTS_VIEW_H_
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/message_center/message_center_export.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_observer.h"
namespace gfx {
class Animation;
class SlideAnimation;
}
namespace views {
class View;
}
namespace message_center {
namespace test {
class MessagePopupCollectionTest;
}
class MessagePopupCollection;
class MessageView;
class Notification;
class PopupAlignmentDelegate;
// The widget host for a popup. Also implements MessageViewDelegate
// which delegates over to MessagePopupCollection, but takes care about
// checking the weakref since MessagePopupCollection may disappear before
// widget/views are closed/destructed.
class MESSAGE_CENTER_EXPORT ToastContentsView
: public views::WidgetDelegateView,
public views::WidgetObserver,
public gfx::AnimationDelegate {
public:
static const char kViewClassName[];
// Computes the size of a toast assuming it will host the given view.
static gfx::Size GetToastSizeForView(const views::View* view);
ToastContentsView(const std::string& notification_id,
PopupAlignmentDelegate* alignment_delegate,
base::WeakPtr<MessagePopupCollection> collection);
~ToastContentsView() override;
// Sets the inner view of the toast. If it has contents already,
// |a11y_feedback_for_updates| causes the view to notify that the
// accessibility message should be read after this update.
void SetContents(MessageView* view, bool a11y_feedback_for_updates);
void UpdateContents(const Notification& notification,
bool a11y_feedback_for_updates);
// Shows the new toast for the first time, animated.
// |origin| is the right-bottom corner of the toast.
void RevealWithAnimation(gfx::Point origin);
// Disconnectes the toast from the rest of the system immediately and start
// an animation. Once animation finishes, closes the widget.
void CloseWithAnimation();
void SetBoundsWithAnimation(gfx::Rect new_bounds);
// 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_; }
gfx::Rect bounds() { return gfx::Rect(origin_, preferred_size_); }
const std::string& id() const { return id_; }
MessageView* message_view() { return message_view_; }
// Overridden from views::View:
void OnMouseEntered(const ui::MouseEvent& event) override;
void OnMouseExited(const ui::MouseEvent& event) override;
void Layout() override;
gfx::Size CalculatePreferredSize() const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
const char* GetClassName() const override;
private:
friend class test::MessagePopupCollectionTest;
// Overridden from gfx::AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationEnded(const gfx::Animation* animation) override;
void AnimationCanceled(const gfx::Animation* animation) override;
// Overridden from views::WidgetDelegate:
void WindowClosing() override;
void OnDisplayChanged() override;
void OnWorkAreaChanged() override;
// Overridden from views::WidgetObserver:
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
// Recalculates preferred size from underlying view and notifies about it.
void UpdatePreferredSize();
// Initialization and update.
void CreateWidget(PopupAlignmentDelegate* alignment_delegate);
// Immediately moves the toast without any sort of delay or animation.
void SetBoundsInstantly(gfx::Rect new_bounds);
// Given the bounds of a toast on the screen, compute the bounds for that
// toast in 'closed' node_data. The 'closed' node_data is used as
// origin/destination in reveal/closing animations.
gfx::Rect GetClosedToastBounds(gfx::Rect bounds);
void StartFadeIn();
void StartFadeOut(); // Will call Widget::Close() when animation ends.
void OnBoundsAnimationEndedOrCancelled(const gfx::Animation* animation);
base::WeakPtr<MessagePopupCollection> collection_;
// Id if the corresponding Notification.
std::string id_;
std::unique_ptr<gfx::SlideAnimation> bounds_animation_;
std::unique_ptr<gfx::SlideAnimation> fade_animation_;
gfx::Rect animated_bounds_start_;
gfx::Rect animated_bounds_end_;
// Started closing animation, will close at the end.
bool is_closing_;
// Closing animation - when it ends, close the widget. Weak, only used
// for referential equality.
gfx::Animation* closing_animation_;
gfx::Point origin_;
gfx::Size preferred_size_;
// Weak reference to the MessageView.
MessageView* message_view_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(ToastContentsView);
};
} // namespace message_center
#endif // UI_MESSAGE_CENTER_VIEWS_TOAST_CONTENTS_VIEW_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment