Commit c650008c authored by Alex Newcomer's avatar Alex Newcomer Committed by Commit Bot

Cros: Make notifications in context menus swipe/click-able

Add a NotificationItemView::NotificationItemViewDelegate, which:
 - Activates notifications.
 - Acts as the SlideOutController::Delegate.
 - Allows for easier testing of NotificationMenuView.

Make NotificationMenuController a NotificationItemViewDelegate.

Pass NotificationMenuController AppMenuModelAdapter:
 - Allows NotificationMenuController to close the menu.

Also added tests.

Bug: 843761
Change-Id: I20474838a0fb5360196a3d0585b7e84620b86e9f
Reviewed-on: https://chromium-review.googlesource.com/1120842
Commit-Queue: Alex Newcomer <newcomer@chromium.org>
Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/master@{#571993}
parent 4ddbb634
...@@ -54,8 +54,7 @@ void AppMenuModelAdapter::Run(const gfx::Rect& menu_anchor_rect, ...@@ -54,8 +54,7 @@ void AppMenuModelAdapter::Run(const gfx::Rect& menu_anchor_rect,
root_ = CreateMenu(); root_ = CreateMenu();
if (features::IsNotificationIndicatorEnabled()) { if (features::IsNotificationIndicatorEnabled()) {
notification_menu_controller_ = notification_menu_controller_ =
std::make_unique<NotificationMenuController>(app_id_, root_, std::make_unique<NotificationMenuController>(app_id_, root_, this);
model_.get());
} }
menu_runner_ = std::make_unique<views::MenuRunner>(root_, run_types); menu_runner_ = std::make_unique<views::MenuRunner>(root_, run_types);
menu_runner_->RunMenuAt(menu_owner_->GetWidget(), nullptr /* MenuButton */, menu_runner_->RunMenuAt(menu_owner_->GetWidget(), nullptr /* MenuButton */,
......
...@@ -54,14 +54,13 @@ class APP_MENU_EXPORT AppMenuModelAdapter : public views::MenuModelAdapter { ...@@ -54,14 +54,13 @@ class APP_MENU_EXPORT AppMenuModelAdapter : public views::MenuModelAdapter {
void ExecuteCommand(int id, int mouse_event_flags) override; void ExecuteCommand(int id, int mouse_event_flags) override;
void OnMenuClosed(views::MenuItemView* menu) override; void OnMenuClosed(views::MenuItemView* menu) override;
ui::SimpleMenuModel* model() { return model_.get(); }
protected: protected:
const std::string& app_id() const { return app_id_; } const std::string& app_id() const { return app_id_; }
base::TimeTicks menu_open_time() const { return menu_open_time_; } base::TimeTicks menu_open_time() const { return menu_open_time_; }
ui::MenuSourceType source_type() const { return source_type_; } ui::MenuSourceType source_type() const { return source_type_; }
ui::SimpleMenuModel* model() { return model_.get(); }
const ui::SimpleMenuModel* model() const { return model_.get(); }
// Helper method to record ExecuteCommand() histograms. // Helper method to record ExecuteCommand() histograms.
void RecordExecuteCommandHistogram(int command_id); void RecordExecuteCommandHistogram(int command_id);
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
#include "ui/gfx/text_elider.h" #include "ui/gfx/text_elider.h"
#include "ui/message_center/views/proportional_image_view.h" #include "ui/message_center/views/proportional_image_view.h"
#include "ui/views/background.h"
#include "ui/views/border.h" #include "ui/views/border.h"
#include "ui/views/controls/label.h" #include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_config.h" #include "ui/views/controls/menu/menu_config.h"
...@@ -38,14 +39,31 @@ constexpr SkColor kNotificationTitleTextColor = ...@@ -38,14 +39,31 @@ constexpr SkColor kNotificationTitleTextColor =
} // namespace } // namespace
NotificationItemView::NotificationItemView(const base::string16& title, NotificationItemView::NotificationItemView(
NotificationItemView::Delegate* delegate,
message_center::SlideOutController::Delegate* slide_out_controller_delegate,
const base::string16& title,
const base::string16& message, const base::string16& message,
const gfx::Image& icon, const gfx::Image& icon,
const std::string notification_id) const std::string& notification_id)
: title_(title), message_(message), notification_id_(notification_id) { : delegate_(delegate),
slide_out_controller_(
std::make_unique<message_center::SlideOutController>(
this,
slide_out_controller_delegate)),
title_(title),
message_(message),
notification_id_(notification_id) {
DCHECK(delegate_);
DCHECK(slide_out_controller_delegate);
// Paint to a new layer so |slide_out_controller_| can control the opacity.
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(true);
SetBorder(views::CreateEmptyBorder( SetBorder(views::CreateEmptyBorder(
gfx::Insets(kNotificationVerticalPadding, kNotificationHorizontalPadding, gfx::Insets(kNotificationVerticalPadding, kNotificationHorizontalPadding,
kNotificationVerticalPadding, kIconHorizontalPadding))); kNotificationVerticalPadding, kIconHorizontalPadding)));
SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
text_container_ = new views::View(); text_container_ = new views::View();
text_container_->SetLayoutManager(std::make_unique<views::BoxLayout>( text_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
...@@ -100,4 +118,35 @@ void NotificationItemView::Layout() { ...@@ -100,4 +118,35 @@ void NotificationItemView::Layout() {
kProportionalIconViewSize.height()); kProportionalIconViewSize.height());
} }
bool NotificationItemView::OnMousePressed(const ui::MouseEvent& event) {
return true;
}
bool NotificationItemView::OnMouseDragged(const ui::MouseEvent& event) {
return true;
}
void NotificationItemView::OnMouseReleased(const ui::MouseEvent& event) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
if (!event.IsOnlyLeftMouseButton() ||
!GetBoundsInScreen().Contains(location)) {
return;
}
delegate_->ActivateNotificationAndClose(notification_id_);
}
void NotificationItemView::OnGestureEvent(ui::GestureEvent* event) {
// Drag gestures are handled by |slide_out_controller_|.
switch (event->type()) {
case ui::ET_GESTURE_TAP:
event->SetHandled();
delegate_->ActivateNotificationAndClose(notification_id_);
return;
default:
return;
}
}
} // namespace ash } // namespace ash
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
#ifndef ASH_APP_MENU_NOTIFICATION_ITEM_VIEW_H_ #ifndef ASH_APP_MENU_NOTIFICATION_ITEM_VIEW_H_
#define ASH_APP_MENU_NOTIFICATION_ITEM_VIEW_H_ #define ASH_APP_MENU_NOTIFICATION_ITEM_VIEW_H_
#include <memory>
#include <string> #include <string>
#include "ash/app_menu/app_menu_export.h" #include "ash/app_menu/app_menu_export.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "ui/message_center/views/slide_out_controller.h"
#include "ui/views/view.h" #include "ui/views/view.h"
namespace gfx { namespace gfx {
...@@ -25,16 +27,32 @@ namespace ash { ...@@ -25,16 +27,32 @@ namespace ash {
// The view which contains the details of a notification. // The view which contains the details of a notification.
class APP_MENU_EXPORT NotificationItemView : public views::View { class APP_MENU_EXPORT NotificationItemView : public views::View {
public: public:
NotificationItemView(const base::string16& title, // Used to activate NotificationItemView.
class Delegate {
public:
// Activates the notification corresponding with |notification_id| and
// closes the menu.
virtual void ActivateNotificationAndClose(
const std::string& notification_id) = 0;
};
NotificationItemView(NotificationItemView::Delegate* delegate,
message_center::SlideOutController::Delegate*
slide_out_controller_delegate,
const base::string16& title,
const base::string16& message, const base::string16& message,
const gfx::Image& icon, const gfx::Image& icon,
const std::string notification_id); const std::string& notification_id);
~NotificationItemView() override; ~NotificationItemView() override;
// views::View overrides: // views::View overrides:
gfx::Size CalculatePreferredSize() const override; gfx::Size CalculatePreferredSize() const override;
void Layout() override; void Layout() override;
bool OnMousePressed(const ui::MouseEvent& event) override;
bool OnMouseDragged(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
const std::string& notification_id() const { return notification_id_; } const std::string& notification_id() const { return notification_id_; }
const base::string16& title() const { return title_; } const base::string16& title() const { return title_; }
...@@ -47,6 +65,12 @@ class APP_MENU_EXPORT NotificationItemView : public views::View { ...@@ -47,6 +65,12 @@ class APP_MENU_EXPORT NotificationItemView : public views::View {
// Holds the notification's icon. Owned by the views hierarchy. // Holds the notification's icon. Owned by the views hierarchy.
message_center::ProportionalImageView* proportional_icon_view_ = nullptr; message_center::ProportionalImageView* proportional_icon_view_ = nullptr;
// Owned by AppMenuModelAdapter. Used to activate notifications.
Delegate* const delegate_;
// Controls the sideways gesture drag behavior.
std::unique_ptr<message_center::SlideOutController> slide_out_controller_;
// Notification properties. // Notification properties.
const base::string16 title_; const base::string16 title_;
const base::string16 message_; const base::string16 message_;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "ash/app_menu/notification_menu_controller.h" #include "ash/app_menu/notification_menu_controller.h"
#include "ash/app_menu/app_menu_model_adapter.h"
#include "ash/app_menu/notification_menu_view.h" #include "ash/app_menu/notification_menu_view.h"
#include "ash/public/cpp/app_menu_constants.h" #include "ash/public/cpp/app_menu_constants.h"
#include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_item_view.h"
...@@ -14,11 +15,12 @@ namespace ash { ...@@ -14,11 +15,12 @@ namespace ash {
NotificationMenuController::NotificationMenuController( NotificationMenuController::NotificationMenuController(
const std::string& app_id, const std::string& app_id,
views::MenuItemView* root_menu, views::MenuItemView* root_menu,
ui::SimpleMenuModel* model) AppMenuModelAdapter* app_menu_model_adapter)
: app_id_(app_id), : app_id_(app_id),
root_menu_(root_menu), root_menu_(root_menu),
model_(model), app_menu_model_adapter_(app_menu_model_adapter),
message_center_observer_(this) { message_center_observer_(this) {
DCHECK(app_menu_model_adapter_);
message_center_observer_.Add(message_center::MessageCenter::Get()); message_center_observer_.Add(message_center::MessageCenter::Get());
InitializeNotificationMenuView(); InitializeNotificationMenuView();
} }
...@@ -60,9 +62,9 @@ void NotificationMenuController::OnNotificationRemoved( ...@@ -60,9 +62,9 @@ void NotificationMenuController::OnNotificationRemoved(
// |root_menu_|, and remove the entry from the model. // |root_menu_|, and remove the entry from the model.
const views::View* container = notification_menu_view_->parent(); const views::View* container = notification_menu_view_->parent();
root_menu_->RemoveMenuItemAt(root_menu_->GetSubmenu()->GetIndexOf(container)); root_menu_->RemoveMenuItemAt(root_menu_->GetSubmenu()->GetIndexOf(container));
// TODO(newcomer): move NOTIFICATION_CONTAINER and other CommandId enums to a app_menu_model_adapter_->model()->RemoveItemAt(
// shared constant file in ash/public/cpp. app_menu_model_adapter_->model()->GetIndexOfCommandId(
model_->RemoveItemAt(model_->GetIndexOfCommandId(NOTIFICATION_CONTAINER)); NOTIFICATION_CONTAINER));
notification_menu_view_ = nullptr; notification_menu_view_ = nullptr;
// Notify the root MenuItemView so it knows to resize and re-calculate the // Notify the root MenuItemView so it knows to resize and re-calculate the
...@@ -70,6 +72,28 @@ void NotificationMenuController::OnNotificationRemoved( ...@@ -70,6 +72,28 @@ void NotificationMenuController::OnNotificationRemoved(
root_menu_->ChildrenChanged(); root_menu_->ChildrenChanged();
} }
ui::Layer* NotificationMenuController::GetSlideOutLayer() {
return notification_menu_view_ ? notification_menu_view_->GetSlideOutLayer()
: nullptr;
}
void NotificationMenuController::OnSlideChanged() {}
void NotificationMenuController::OnSlideOut() {
// Results in |this| being deleted if there are no more notifications to show.
// Only the displayed NotificationItemView can call OnSlideOut.
message_center::MessageCenter::Get()->RemoveNotification(
notification_menu_view_->GetDisplayedNotificationID(), true);
}
void NotificationMenuController::ActivateNotificationAndClose(
const std::string& notification_id) {
message_center::MessageCenter::Get()->ClickOnNotification(notification_id);
// Results in |this| being deleted.
app_menu_model_adapter_->Cancel();
}
void NotificationMenuController::InitializeNotificationMenuView() { void NotificationMenuController::InitializeNotificationMenuView() {
DCHECK(!notification_menu_view_); DCHECK(!notification_menu_view_);
...@@ -80,11 +104,12 @@ void NotificationMenuController::InitializeNotificationMenuView() { ...@@ -80,11 +104,12 @@ void NotificationMenuController::InitializeNotificationMenuView() {
return; return;
} }
model_->AddItem(NOTIFICATION_CONTAINER, base::string16()); app_menu_model_adapter_->model()->AddItem(NOTIFICATION_CONTAINER,
base::string16());
// Add the container MenuItemView to |root_menu_|. // Add the container MenuItemView to |root_menu_|.
views::MenuItemView* container = root_menu_->AppendMenuItem( views::MenuItemView* container = root_menu_->AppendMenuItem(
NOTIFICATION_CONTAINER, base::string16(), views::MenuItemView::NORMAL); NOTIFICATION_CONTAINER, base::string16(), views::MenuItemView::NORMAL);
notification_menu_view_ = new NotificationMenuView(app_id_); notification_menu_view_ = new NotificationMenuView(this, this, app_id_);
container->AddChildView(notification_menu_view_); container->AddChildView(notification_menu_view_);
for (auto* notification : for (auto* notification :
......
...@@ -6,13 +6,11 @@ ...@@ -6,13 +6,11 @@
#define ASH_APP_MENU_NOTIFICATION_MENU_CONTROLLER_H_ #define ASH_APP_MENU_NOTIFICATION_MENU_CONTROLLER_H_
#include "ash/app_menu/app_menu_export.h" #include "ash/app_menu/app_menu_export.h"
#include "ash/app_menu/notification_item_view.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
#include "ui/message_center/message_center.h" #include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_observer.h" #include "ui/message_center/message_center_observer.h"
#include "ui/message_center/views/slide_out_controller.h"
namespace ui {
class SimpleMenuModel;
}
namespace views { namespace views {
class MenuItemView; class MenuItemView;
...@@ -20,17 +18,20 @@ class MenuItemView; ...@@ -20,17 +18,20 @@ class MenuItemView;
namespace ash { namespace ash {
class AppMenuModelAdapter;
class NotificationMenuView; class NotificationMenuView;
// Handles adding/removing NotificationMenuView from the root MenuItemView, // Handles adding/removing NotificationMenuView from the root MenuItemView,
// adding the container model entry, and updating the NotificationMenuView // adding the container model entry, and updating the NotificationMenuView
// as notifications come and go. // as notifications come and go.
class APP_MENU_EXPORT NotificationMenuController class APP_MENU_EXPORT NotificationMenuController
: public message_center::MessageCenterObserver { : public message_center::MessageCenterObserver,
public message_center::SlideOutController::Delegate,
public NotificationItemView::Delegate {
public: public:
NotificationMenuController(const std::string& app_id, NotificationMenuController(const std::string& app_id,
views::MenuItemView* root_menu, views::MenuItemView* root_menu,
ui::SimpleMenuModel* model); AppMenuModelAdapter* app_menu_model_adapter);
~NotificationMenuController() override; ~NotificationMenuController() override;
...@@ -39,6 +40,15 @@ class APP_MENU_EXPORT NotificationMenuController ...@@ -39,6 +40,15 @@ class APP_MENU_EXPORT NotificationMenuController
void OnNotificationRemoved(const std::string& notification_id, void OnNotificationRemoved(const std::string& notification_id,
bool by_user) override; bool by_user) override;
// message_center::SlideOutController::Delegate overrides:
ui::Layer* GetSlideOutLayer() override;
void OnSlideChanged() override;
void OnSlideOut() override;
// NotificationItemView::Delegate overrides:
void ActivateNotificationAndClose(
const std::string& notification_id) override;
private: private:
// Adds a container MenuItemView to |root_menu_|, adds NOTIFICATION_CONTAINER // Adds a container MenuItemView to |root_menu_|, adds NOTIFICATION_CONTAINER
// to |model_|, creates and initializes NotificationMenuView, and adds // to |model_|, creates and initializes NotificationMenuView, and adds
...@@ -51,8 +61,8 @@ class APP_MENU_EXPORT NotificationMenuController ...@@ -51,8 +61,8 @@ class APP_MENU_EXPORT NotificationMenuController
// The top level MenuItemView. Owned by |AppMenuModelAdapter::menu_runner_|. // The top level MenuItemView. Owned by |AppMenuModelAdapter::menu_runner_|.
views::MenuItemView* const root_menu_; views::MenuItemView* const root_menu_;
// Owned by AppMenuModelAdapter. // Manages showing the menu. Owned by the view requesting a menu.
ui::SimpleMenuModel* const model_; AppMenuModelAdapter* const app_menu_model_adapter_;
// The view which shows all active notifications for |app_id_|. Owned by the // The view which shows all active notifications for |app_id_|. Owned by the
// views hierarchy. // views hierarchy.
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
#include "ui/views/controls/menu/submenu_view.h" #include "ui/views/controls/menu/submenu_view.h"
namespace ash { namespace ash {
namespace test {
namespace { namespace {
constexpr char kTestAppId[] = "test-app-id"; constexpr char kTestAppId[] = "test-app-id";
...@@ -33,6 +32,23 @@ void BuildAndSendNotification(const std::string& app_id, ...@@ -33,6 +32,23 @@ void BuildAndSendNotification(const std::string& app_id,
std::move(notification)); std::move(notification));
} }
class TestAppMenuModelAdapter : public AppMenuModelAdapter {
public:
TestAppMenuModelAdapter(const std::string& app_id,
std::unique_ptr<ui::SimpleMenuModel> model)
: AppMenuModelAdapter(app_id,
std::move(model),
nullptr,
ui::MENU_SOURCE_TYPE_LAST,
base::OnceClosure()) {}
private:
// AppMenuModelAdapter overrides:
void RecordHistogramOnMenuClosed() override {}
DISALLOW_COPY_AND_ASSIGN(TestAppMenuModelAdapter);
};
} // namespace } // namespace
class NotificationMenuControllerTest : public AshTestBase { class NotificationMenuControllerTest : public AshTestBase {
...@@ -50,20 +66,25 @@ class NotificationMenuControllerTest : public AshTestBase { ...@@ -50,20 +66,25 @@ class NotificationMenuControllerTest : public AshTestBase {
} }
void BuildMenu() { void BuildMenu() {
model_ = std::make_unique<ui::SimpleMenuModel>( test_app_menu_model_adapter_ = std::make_unique<TestAppMenuModelAdapter>(
nullptr /*ui::SimpleMenuModel::Delegate not required*/); kTestAppId,
model_->AddItem(0, base::ASCIIToUTF16("item 1")); std::make_unique<ui::SimpleMenuModel>(
model_->AddItem(1, base::ASCIIToUTF16("item 2")); nullptr /*ui::SimpleMenuModel::Delegate not required*/));
test_app_menu_model_adapter_->model()->AddItem(
delegate_ = std::make_unique<views::MenuModelAdapter>(model_.get()); 0, base::ASCIIToUTF16("item 0"));
root_menu_item_view_ = new views::MenuItemView(delegate_.get()); test_app_menu_model_adapter_->model()->AddItem(
1, base::ASCIIToUTF16("item 1"));
root_menu_item_view_ =
new views::MenuItemView(test_app_menu_model_adapter_.get());
host_view_ = std::make_unique<views::View>(); host_view_ = std::make_unique<views::View>();
host_view_->AddChildView(root_menu_item_view_); host_view_->AddChildView(root_menu_item_view_);
delegate_->BuildMenu(root_menu_item_view_); test_app_menu_model_adapter_->BuildMenu(root_menu_item_view_);
notification_menu_controller_ = notification_menu_controller_ =
std::make_unique<NotificationMenuController>( std::make_unique<NotificationMenuController>(
kTestAppId, root_menu_item_view_, model_.get()); kTestAppId, root_menu_item_view_,
test_app_menu_model_adapter_.get());
} }
views::MenuItemView* root_menu_item_view() { return root_menu_item_view_; } views::MenuItemView* root_menu_item_view() { return root_menu_item_view_; }
...@@ -74,8 +95,7 @@ class NotificationMenuControllerTest : public AshTestBase { ...@@ -74,8 +95,7 @@ class NotificationMenuControllerTest : public AshTestBase {
// Allows the dtor to access the restricted views::MenuItemView dtor. // Allows the dtor to access the restricted views::MenuItemView dtor.
std::unique_ptr<views::View> host_view_; std::unique_ptr<views::View> host_view_;
std::unique_ptr<NotificationMenuController> notification_menu_controller_; std::unique_ptr<NotificationMenuController> notification_menu_controller_;
std::unique_ptr<ui::SimpleMenuModel> model_; std::unique_ptr<TestAppMenuModelAdapter> test_app_menu_model_adapter_;
std::unique_ptr<views::MenuModelAdapter> delegate_;
DISALLOW_COPY_AND_ASSIGN(NotificationMenuControllerTest); DISALLOW_COPY_AND_ASSIGN(NotificationMenuControllerTest);
}; };
...@@ -152,5 +172,4 @@ TEST_F(NotificationMenuControllerTest, MultipleNotifications) { ...@@ -152,5 +172,4 @@ TEST_F(NotificationMenuControllerTest, MultipleNotifications) {
EXPECT_EQ(2, root_menu_item_view()->GetSubmenu()->child_count()); EXPECT_EQ(2, root_menu_item_view()->GetSubmenu()->child_count());
} }
} // namespace test
} // namespace ash } // namespace ash
...@@ -15,10 +15,19 @@ ...@@ -15,10 +15,19 @@
namespace ash { namespace ash {
NotificationMenuView::NotificationMenuView(const std::string& app_id) NotificationMenuView::NotificationMenuView(
: app_id_(app_id) {
NotificationItemView::Delegate* notification_item_view_delegate,
message_center::SlideOutController::Delegate* slide_out_controller_delegate,
const std::string& app_id)
: app_id_(app_id),
notification_item_view_delegate_(notification_item_view_delegate),
slide_out_controller_delegate_(slide_out_controller_delegate) {
DCHECK(notification_item_view_delegate_);
DCHECK(slide_out_controller_delegate_);
DCHECK(!app_id_.empty()) DCHECK(!app_id_.empty())
<< "Only context menus for applications can show notifications."; << "Only context menus for applications can show notifications.";
SetLayoutManager(std::make_unique<views::BoxLayout>( SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical)); views::BoxLayout::Orientation::kVertical));
...@@ -46,6 +55,7 @@ void NotificationMenuView::AddNotificationItemView( ...@@ -46,6 +55,7 @@ void NotificationMenuView::AddNotificationItemView(
RemoveChildView(notification_item_views_.front().get()); RemoveChildView(notification_item_views_.front().get());
notification_item_views_.push_front(std::make_unique<NotificationItemView>( notification_item_views_.push_front(std::make_unique<NotificationItemView>(
notification_item_view_delegate_, slide_out_controller_delegate_,
notification.title(), notification.message(), notification.icon(), notification.title(), notification.message(), notification.icon(),
notification.id())); notification.id()));
notification_item_views_.front()->set_owned_by_client(); notification_item_views_.front()->set_owned_by_client();
...@@ -74,4 +84,15 @@ void NotificationMenuView::RemoveNotificationItemView( ...@@ -74,4 +84,15 @@ void NotificationMenuView::RemoveNotificationItemView(
AddChildView(notification_item_views_.front().get()); AddChildView(notification_item_views_.front().get());
} }
ui::Layer* NotificationMenuView::GetSlideOutLayer() {
if (notification_item_views_.empty())
return nullptr;
return notification_item_views_.front()->layer();
}
const std::string& NotificationMenuView::GetDisplayedNotificationID() {
DCHECK(!notification_item_views_.empty());
return notification_item_views_.front()->notification_id();
}
} // namespace ash } // namespace ash
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include <string> #include <string>
#include "ash/app_menu/app_menu_export.h" #include "ash/app_menu/app_menu_export.h"
#include "ash/app_menu/notification_item_view.h"
#include "ui/message_center/views/slide_out_controller.h"
#include "ui/views/view.h" #include "ui/views/view.h"
namespace message_center { namespace message_center {
...@@ -18,13 +20,16 @@ class Notification; ...@@ -18,13 +20,16 @@ class Notification;
namespace ash { namespace ash {
class NotificationMenuHeaderView; class NotificationMenuHeaderView;
class NotificationItemView;
// A view inserted into a container MenuItemView which shows a // A view inserted into a container MenuItemView which shows a
// NotificationItemView and a NotificationMenuHeaderView. // NotificationItemView and a NotificationMenuHeaderView.
class APP_MENU_EXPORT NotificationMenuView : public views::View { class APP_MENU_EXPORT NotificationMenuView : public views::View {
public: public:
explicit NotificationMenuView(const std::string& app_id); explicit NotificationMenuView(
NotificationItemView::Delegate* notification_item_view_delegate,
message_center::SlideOutController::Delegate*
slide_out_controller_delegate,
const std::string& app_id);
~NotificationMenuView() override; ~NotificationMenuView() override;
// Whether |notifications_for_this_app_| is empty. // Whether |notifications_for_this_app_| is empty.
...@@ -40,6 +45,12 @@ class APP_MENU_EXPORT NotificationMenuView : public views::View { ...@@ -40,6 +45,12 @@ class APP_MENU_EXPORT NotificationMenuView : public views::View {
// next one if available. // next one if available.
void RemoveNotificationItemView(const std::string& notification_id); void RemoveNotificationItemView(const std::string& notification_id);
// Gets the slide out layer, used to move the displayed NotificationItemView.
ui::Layer* GetSlideOutLayer();
// Gets the notification id of the displayed NotificationItemView.
const std::string& GetDisplayedNotificationID();
// views::View overrides: // views::View overrides:
gfx::Size CalculatePreferredSize() const override; gfx::Size CalculatePreferredSize() const override;
...@@ -49,6 +60,13 @@ class APP_MENU_EXPORT NotificationMenuView : public views::View { ...@@ -49,6 +60,13 @@ class APP_MENU_EXPORT NotificationMenuView : public views::View {
// Identifies the app for this menu. // Identifies the app for this menu.
const std::string app_id_; const std::string app_id_;
// Owned by AppMenuModelAdapter.
NotificationItemView::Delegate* const notification_item_view_delegate_;
// Owned by AppMenuModelAdapter.
message_center::SlideOutController::Delegate* const
slide_out_controller_delegate_;
// The deque of NotificationItemViews. The front item in the deque is the view // The deque of NotificationItemViews. The front item in the deque is the view
// which is shown. // which is shown.
std::deque<std::unique_ptr<NotificationItemView>> notification_item_views_; std::deque<std::unique_ptr<NotificationItemView>> notification_item_views_;
......
...@@ -9,19 +9,57 @@ ...@@ -9,19 +9,57 @@
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/transform.h"
#include "ui/message_center/public/cpp/notification.h" #include "ui/message_center/public/cpp/notification.h"
#include "ui/views/controls/label.h" #include "ui/views/controls/label.h"
#include "ui/views/test/views_test_base.h" #include "ui/views/test/views_test_base.h"
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/widget/widget_delegate.h"
namespace ash { namespace ash {
namespace test {
namespace { namespace {
// The app id used in tests. // The app id used in tests.
constexpr char kTestAppId[] = "test-app-id"; constexpr char kTestAppId[] = "test-app-id";
class MockNotificationMenuController
: public message_center::SlideOutController::Delegate,
public NotificationItemView::Delegate {
public:
MockNotificationMenuController() = default;
virtual ~MockNotificationMenuController() = default;
void ActivateNotificationAndClose(
const std::string& notification_id) override {
activation_count_++;
}
ui::Layer* GetSlideOutLayer() override {
return notification_menu_view_->GetSlideOutLayer();
}
void OnSlideChanged() override {}
void OnSlideOut() override { slide_out_count_++; }
void set_notification_menu_view(
NotificationMenuView* notification_menu_view) {
notification_menu_view_ = notification_menu_view;
}
int slide_out_count_ = 0;
int activation_count_ = 0;
// Owned by NotificationMenuViewTest.
NotificationMenuView* notification_menu_view_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(MockNotificationMenuController);
};
} // namespace } // namespace
class NotificationMenuViewTest : public views::ViewsTestBase { class NotificationMenuViewTest : public views::ViewsTestBase {
...@@ -32,10 +70,45 @@ class NotificationMenuViewTest : public views::ViewsTestBase { ...@@ -32,10 +70,45 @@ class NotificationMenuViewTest : public views::ViewsTestBase {
// views::ViewsTestBase: // views::ViewsTestBase:
void SetUp() override { void SetUp() override {
views::ViewsTestBase::SetUp(); views::ViewsTestBase::SetUp();
notification_menu_view_ =
std::make_unique<NotificationMenuView>(kTestAppId); zero_duration_scope_ =
std::make_unique<ui::ScopedAnimationDurationScaleMode>(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
mock_notification_menu_controller_ =
std::make_unique<MockNotificationMenuController>();
notification_menu_view_ = std::make_unique<NotificationMenuView>(
mock_notification_menu_controller_.get(),
mock_notification_menu_controller_.get(), kTestAppId);
notification_menu_view_->set_owned_by_client();
// Set the NotificationMenuView so |mock_notification_menu_controller_|
// can get the slide out layer. In production NotificationMenuController is
// the NotificationItemViewDelegate, and it gets a reference to
// NotificationMenuView when it is created.
mock_notification_menu_controller_->set_notification_menu_view(
notification_menu_view());
test_api_ = std::make_unique<NotificationMenuViewTestAPI>( test_api_ = std::make_unique<NotificationMenuViewTestAPI>(
notification_menu_view_.get()); notification_menu_view_.get());
widget_ = std::make_unique<views::Widget>();
views::Widget::InitParams init_params(
CreateParams(views::Widget::InitParams::TYPE_POPUP));
init_params.ownership =
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
widget_->Init(init_params);
widget_->SetContentsView(notification_menu_view_.get());
widget_->SetSize(notification_menu_view_->GetPreferredSize());
widget_->Show();
widget_->Activate();
}
void TearDown() override {
widget_->Close();
views::ViewsTestBase::TearDown();
} }
message_center::Notification AddNotification( message_center::Notification AddNotification(
...@@ -50,6 +123,7 @@ class NotificationMenuViewTest : public views::ViewsTestBase { ...@@ -50,6 +123,7 @@ class NotificationMenuViewTest : public views::ViewsTestBase {
notifier_id, message_center::RichNotificationData(), notifier_id, message_center::RichNotificationData(),
nullptr /* delegate */); nullptr /* delegate */);
notification_menu_view_->AddNotificationItemView(notification); notification_menu_view_->AddNotificationItemView(notification);
notification_menu_view_->Layout();
return notification; return notification;
} }
...@@ -64,15 +138,54 @@ class NotificationMenuViewTest : public views::ViewsTestBase { ...@@ -64,15 +138,54 @@ class NotificationMenuViewTest : public views::ViewsTestBase {
EXPECT_EQ(item_view->message(), notification.message()); EXPECT_EQ(item_view->message(), notification.message());
} }
void BeginScroll() {
DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
}
void EndScroll() {
DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
}
void ScrollBy(int dx) {
DispatchGesture(
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, dx, 0));
}
void DispatchGesture(const ui::GestureEventDetails& details) {
ui::test::EventGenerator generator(
notification_menu_view_->GetWidget()->GetNativeWindow());
ui::GestureEvent event(
0,
test_api()->GetDisplayedNotificationItemView()->GetBoundsInScreen().y(),
0, ui::EventTimeForNow(), details);
generator.Dispatch(&event);
}
float GetSlideAmount() const {
return notification_menu_view_->GetSlideOutLayer()
->transform()
.To2dTranslation()
.x();
}
NotificationMenuView* notification_menu_view() { NotificationMenuView* notification_menu_view() {
return notification_menu_view_.get(); return notification_menu_view_.get();
} }
NotificationMenuViewTestAPI* test_api() { return test_api_.get(); } NotificationMenuViewTestAPI* test_api() { return test_api_.get(); }
MockNotificationMenuController* mock_notification_menu_controller() {
return mock_notification_menu_controller_.get();
}
private: private:
std::unique_ptr<MockNotificationMenuController>
mock_notification_menu_controller_;
std::unique_ptr<NotificationMenuView> notification_menu_view_; std::unique_ptr<NotificationMenuView> notification_menu_view_;
std::unique_ptr<NotificationMenuViewTestAPI> test_api_; std::unique_ptr<NotificationMenuViewTestAPI> test_api_;
std::unique_ptr<views::Widget> widget_;
std::unique_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_scope_;
DISALLOW_COPY_AND_ASSIGN(NotificationMenuViewTest); DISALLOW_COPY_AND_ASSIGN(NotificationMenuViewTest);
}; };
...@@ -132,5 +245,92 @@ TEST_F(NotificationMenuViewTest, RemoveOlderNotification) { ...@@ -132,5 +245,92 @@ TEST_F(NotificationMenuViewTest, RemoveOlderNotification) {
CheckDisplayedNotification(notification_1); CheckDisplayedNotification(notification_1);
} }
} // namespace test // Tests that the displayed NotificationItemView is only dismissed when dragged
// beyond the threshold.
TEST_F(NotificationMenuViewTest, SlideOut) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->slide_out_count_);
BeginScroll();
// Scroll by a small amount, the notification should move but not slide out.
ScrollBy(-10);
EXPECT_EQ(0, mock_notification_menu_controller()->slide_out_count_);
EXPECT_EQ(-10.f, GetSlideAmount());
// End the scroll gesture, the notifications should return to its resting
// place.
EndScroll();
EXPECT_EQ(0, mock_notification_menu_controller()->slide_out_count_);
EXPECT_EQ(0.f, GetSlideAmount());
BeginScroll();
// Scroll beyond the threshold but do not release the gesture scroll.
ScrollBy(-200);
EXPECT_EQ(-200.f, GetSlideAmount());
// Release the gesture, the notification should slide out.
EndScroll();
EXPECT_EQ(1, mock_notification_menu_controller()->slide_out_count_);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
}
// Tests that tapping a notification activates it.
TEST_F(NotificationMenuViewTest, TapNotification) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, mock_notification_menu_controller()->activation_count_);
}
// Tests that an in bounds mouse release activates a notification.
TEST_F(NotificationMenuViewTest, ClickNotification) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
const gfx::Point cursor_location = test_api()
->GetDisplayedNotificationItemView()
->GetBoundsInScreen()
.origin();
ui::MouseEvent press(ui::ET_MOUSE_PRESSED, cursor_location, cursor_location,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&press);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
ui::MouseEvent release(ui::ET_MOUSE_RELEASED, cursor_location,
cursor_location, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&release);
EXPECT_EQ(1, mock_notification_menu_controller()->activation_count_);
}
// Tests that an out of bounds mouse release does not activate a notification.
TEST_F(NotificationMenuViewTest, OutOfBoundsClick) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
const gfx::Point cursor_location = test_api()
->GetDisplayedNotificationItemView()
->GetBoundsInScreen()
.origin();
ui::MouseEvent press(ui::ET_MOUSE_PRESSED, cursor_location, cursor_location,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&press);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
const gfx::Point out_of_bounds;
ui::MouseEvent out_of_bounds_release(ui::ET_MOUSE_RELEASED, out_of_bounds,
out_of_bounds, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&out_of_bounds_release);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
}
} // namespace ash } // namespace ash
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