chromium/ash/app_menu/notification_overflow_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_overflow_view.h"

#include "ash/public/cpp/app_menu_constants.h"
#include "base/ranges/algorithm.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/menu_separator_types.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/message_center/views/proportional_image_view.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_separator.h"
#include "ui/views/vector_icons.h"

namespace {

// Padding in dips between the overflow separator and overflow icons.
constexpr int kOverflowSeparatorToIconPadding = 8;

// Padding in dips below the overflow icons.
constexpr int kOverflowAreaBottomPadding = 12;

// Size of overflow icons in dips.
constexpr int kIconSize = 16;

// Size used for laying out overflow icons in dips to prevent clipping.
constexpr int kIconLayoutSize = kIconSize + 1;

// Padding between overflow icons in dips.
constexpr int kInterIconPadding = 8;

}  // namespace

namespace ash {

// The icon which represents a notification.
class NotificationOverflowImageView
    : public message_center::ProportionalImageView {
  METADATA_HEADER(NotificationOverflowImageView,
                  message_center::ProportionalImageView)

 public:
  NotificationOverflowImageView(const ui::ImageModel& image,
                                const std::string& notification_id)
      : message_center::ProportionalImageView(gfx::Size(kIconSize, kIconSize)),
        notification_id_(notification_id) {
    SetID(kNotificationOverflowIconId);
    SetImage(image, gfx::Size(kIconSize, kIconSize));
  }

  NotificationOverflowImageView(const NotificationOverflowImageView&) = delete;
  NotificationOverflowImageView& operator=(
      const NotificationOverflowImageView&) = delete;

  ~NotificationOverflowImageView() override = default;

  const std::string& notification_id() const { return notification_id_; }

 private:
  std::string const notification_id_;
};

BEGIN_METADATA(NotificationOverflowImageView)
END_METADATA

NotificationOverflowView::NotificationOverflowView()
    : separator_(AddChildView(std::make_unique<views::MenuSeparator>(
          ui::MenuSeparatorType::NORMAL_SEPARATOR))) {
  SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
      0, kNotificationHorizontalPadding, kOverflowAreaBottomPadding,
      kNotificationHorizontalPadding)));
  SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
}

NotificationOverflowView::~NotificationOverflowView() = default;

void NotificationOverflowView::AddIcon(
    const message_center::ProportionalImageView& image_view,
    const std::string& notification_id) {
  // Insert the image at the front of the list, so that it appears on the right
  // side.
  image_views_.insert(
      image_views_.begin(),
      AddChildView(std::make_unique<NotificationOverflowImageView>(
          image_view.image(), notification_id)));

  if (image_views_.size() > kMaxOverflowIcons) {
    if (!overflow_icon_) {
      auto icon = ui::ImageModel::FromVectorIcon(views::kOptionsIcon,
                                                 ui::kColorIcon, kIconSize);
      auto overflow_icon =
          std::make_unique<message_center::ProportionalImageView>(
              gfx::Size(kIconSize, kIconSize));
      overflow_icon->SetID(kOverflowIconId);
      overflow_icon->SetImage(icon, gfx::Size(kIconSize, kIconSize));
      overflow_icon_ = AddChildView(std::move(overflow_icon));
    }
    overflow_icon_->SetVisible(true);
    image_views_.at(kMaxOverflowIcons)->SetVisible(false);
  }
  DeprecatedLayoutImmediately();
}

void NotificationOverflowView::RemoveIcon(const std::string& notification_id) {
  auto it = base::ranges::find(image_views_, notification_id,
                               &NotificationOverflowImageView::notification_id);
  if (it != image_views_.end()) {
    RemoveChildViewT(*it);
    image_views_.erase(it);
    MaybeRemoveOverflowIcon();
    DeprecatedLayoutImmediately();
  }
}

void NotificationOverflowView::Layout(PassKey) {
  separator_->SetBoundsRect(
      gfx::Rect(width(), separator_->GetPreferredSize().height()));

  int x = width() - GetInsets().right();
  const int y =
      separator_->GetPreferredSize().height() + kOverflowSeparatorToIconPadding;

  for (size_t i = 0; i < image_views_.size() && i <= kMaxOverflowIcons; ++i) {
    views::View* icon = image_views_.at(i);
    if (i == kMaxOverflowIcons)
      icon = overflow_icon_;

    x -= kIconLayoutSize;
    icon->SetBounds(x, y, kIconLayoutSize, kIconLayoutSize);
    x -= kInterIconPadding;
  }
}

gfx::Size NotificationOverflowView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  // This view is the last element in a MenuItemView, which means it has extra
  // padding on the bottom due to the corner radius of the root MenuItemView. If
  // the corner radius changes, |kOverflowSeparatorToIconPadding| must be
  // modified to vertically center the overflow icons.
  return gfx::Size(views::MenuConfig::instance().touchable_menu_min_width,
                   separator_->GetPreferredSize().height() +
                       kOverflowSeparatorToIconPadding + kIconLayoutSize);
}

void NotificationOverflowView::MaybeRemoveOverflowIcon() {
  if (!overflow_icon_ || image_views_.size() > kMaxOverflowIcons)
    return;

  overflow_icon_->SetVisible(false);
}

BEGIN_METADATA(NotificationOverflowView)
END_METADATA

}  // namespace ash