chromium/ash/app_menu/notification_menu_view.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/app_menu/notification_menu_view.h"

#include "ash/app_menu/notification_item_view.h"
#include "ash/app_menu/notification_menu_header_view.h"
#include "ash/app_menu/notification_overflow_view.h"
#include "ash/public/cpp/app_menu_constants.h"
#include "base/ranges/algorithm.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/menu_separator_types.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/views/proportional_image_view.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_separator.h"

namespace ash {

NotificationMenuView::NotificationMenuView(
    Delegate* notification_item_view_delegate,
    views::SlideOutControllerDelegate* 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),
      double_separator_(AddChildView(std::make_unique<views::MenuSeparator>(
          ui::MenuSeparatorType::DOUBLE_SEPARATOR))),
      header_view_(
          AddChildView(std::make_unique<NotificationMenuHeaderView>())) {
  DCHECK(notification_item_view_delegate_);
  DCHECK(slide_out_controller_delegate_);
  DCHECK(!app_id_.empty())
      << "Only context menus for applications can show notifications.";
}

NotificationMenuView::~NotificationMenuView() = default;

gfx::Size NotificationMenuView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  return gfx::Size(
      views::MenuConfig::instance().touchable_menu_min_width,
      double_separator_->GetPreferredSize().height() +
          header_view_->GetPreferredSize({}).height() +
          kNotificationItemViewHeight +
          (overflow_view_ ? overflow_view_->GetPreferredSize().height() : 0));
}

void NotificationMenuView::Layout(PassKey) {
  int y = 0;
  double_separator_->SetBoundsRect(gfx::Rect(
      gfx::Point(0, y),
      gfx::Size(views::MenuConfig::instance().touchable_menu_min_width,
                double_separator_->GetPreferredSize().height())));
  y += double_separator_->GetPreferredSize().height();

  header_view_->SetBoundsRect(
      gfx::Rect(gfx::Point(0, y), header_view_->GetPreferredSize({})));
  y += header_view_->height();

  auto* item = GetDisplayedNotificationItemView();
  if (item) {
    item->SetBoundsRect(gfx::Rect(gfx::Point(0, y), item->GetPreferredSize()));
    y = item->bounds().bottom();
  }

  if (overflow_view_) {
    overflow_view_->SetBoundsRect(
        gfx::Rect(gfx::Point(0, y), overflow_view_->GetPreferredSize()));
  }
}

bool NotificationMenuView::IsEmpty() const {
  return notification_item_views_.empty();
}

void NotificationMenuView::AddNotificationItemView(
    const message_center::Notification& notification) {
  auto* old_displayed_item = GetDisplayedNotificationItemView();

  auto notification_view = std::make_unique<NotificationItemView>(
      notification_item_view_delegate_, slide_out_controller_delegate_,
      notification.title(), notification.message(), notification.icon(),
      notification.id());
  notification_item_views_.push_front(
      AddChildView(std::move(notification_view)));

  header_view_->UpdateCounter(notification_item_views_.size());

  if (!old_displayed_item)
    return;

  // Push |old_displayed_notification_item_view| to |overflow_view_|.
  old_displayed_item->SetVisible(false);

  const bool overflow_view_created = !overflow_view_;
  if (!overflow_view_)
    overflow_view_ = AddChildView(std::make_unique<NotificationOverflowView>());

  overflow_view_->AddIcon(old_displayed_item->proportional_image_view(),
                          old_displayed_item->notification_id());

  if (overflow_view_created) {
    PreferredSizeChanged();
    // OnOverflowAddedOrRemoved must be called after PreferredSizeChange to
    // ensure that enough room is allocated for the overflow view.
    notification_item_view_delegate_->OnOverflowAddedOrRemoved();
  }
  DeprecatedLayoutImmediately();
}

void NotificationMenuView::UpdateNotificationItemView(
    const message_center::Notification& notification) {
  // Find the view which corresponds to |notification|.
  const auto i = NotificationIterForId(notification.id());
  if (i == notification_item_views_.end())
    return;

  (*i)->UpdateContents(notification.title(), notification.message(),
                       notification.icon());
}

void NotificationMenuView::OnNotificationRemoved(
    const std::string& notification_id) {
  // Find the view which corresponds to |notification_id|.
  const auto i = NotificationIterForId(notification_id);
  if (i == notification_item_views_.end())
    return;
  const bool removed_displayed_notification =
      *i == GetDisplayedNotificationItemView();

  RemoveChildViewT(*i);
  notification_item_views_.erase(i);
  header_view_->UpdateCounter(notification_item_views_.size());

  if (removed_displayed_notification) {
    // Display the next notification.
    auto* item = GetDisplayedNotificationItemView();
    if (item) {
      item->SetVisible(true);
      if (overflow_view_)
        overflow_view_->RemoveIcon(item->notification_id());
    }
  } else if (overflow_view_) {
    overflow_view_->RemoveIcon(notification_id);
  }

  if (overflow_view_ && overflow_view_->is_empty()) {
    // Remove and delete |overflow_view_|.
    RemoveChildViewT(overflow_view_.get());
    overflow_view_ = nullptr;
    PreferredSizeChanged();
    notification_item_view_delegate_->OnOverflowAddedOrRemoved();
  }
}

ui::Layer* NotificationMenuView::GetSlideOutLayer() {
  auto* item = GetDisplayedNotificationItemView();
  return item ? item->layer() : nullptr;
}

const NotificationItemView*
NotificationMenuView::GetDisplayedNotificationItemView() const {
  return notification_item_views_.empty() ? nullptr
                                          : notification_item_views_.front();
}

const std::string& NotificationMenuView::GetDisplayedNotificationID() const {
  DCHECK(!notification_item_views_.empty());
  return GetDisplayedNotificationItemView()->notification_id();
}

NotificationMenuView::NotificationItemViews::iterator
NotificationMenuView::NotificationIterForId(const std::string& id) {
  return base::ranges::find(notification_item_views_, id,
                            &NotificationItemView::notification_id);
}

BEGIN_METADATA(NotificationMenuView)
END_METADATA

}  // namespace ash