blob: f452966021e006505372332b56cdc45c6f377fb4 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/ui/views/profiles/profile_menu_view_base.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/metrics/user_metrics.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/hover_button.h"
#include "chrome/browser/ui/views/profiles/incognito_menu_view.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view_class_properties.h"
#if !defined(OS_CHROMEOS)
#include "chrome/browser/ui/views/profiles/profile_menu_view.h"
#include "chrome/browser/ui/views/sync/dice_signin_button_view.h"
#endif
namespace {
ProfileMenuViewBase* g_profile_bubble_ = nullptr;
// Helpers --------------------------------------------------------------------
constexpr int kMenuWidth = 288;
constexpr int kIconSize = 16;
constexpr int kIdentityImageSize = 64;
constexpr int kMaxImageSize = kIdentityImageSize;
// If the bubble is too large to fit on the screen, it still needs to be at
// least this tall to show one row.
constexpr int kMinimumScrollableContentHeight = 40;
// Spacing between the edge of the user menu and the top/bottom or left/right of
// the menu items.
constexpr int kMenuEdgeMargin = 16;
SkColor GetDefaultIconColor() {
return ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
ui::NativeTheme::kColorId_DefaultIconColor);
}
SkColor GetDefaultSeparatorColor() {
return ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
ui::NativeTheme::kColorId_MenuSeparatorColor);
}
gfx::ImageSkia SizeImage(const gfx::ImageSkia& image, int size) {
return gfx::ImageSkiaOperations::CreateResizedImage(
image, skia::ImageOperations::RESIZE_BEST, gfx::Size(size, size));
}
gfx::ImageSkia ColorImage(const gfx::ImageSkia& image, SkColor color) {
return gfx::ImageSkiaOperations::CreateColorMask(image, color);
}
class CircleImageSource : public gfx::CanvasImageSource {
public:
CircleImageSource(int size, SkColor color)
: gfx::CanvasImageSource(gfx::Size(size, size)), color_(color) {}
~CircleImageSource() override = default;
void Draw(gfx::Canvas* canvas) override;
private:
SkColor color_;
DISALLOW_COPY_AND_ASSIGN(CircleImageSource);
};
void CircleImageSource::Draw(gfx::Canvas* canvas) {
float radius = size().width() / 2.0f;
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setAntiAlias(true);
flags.setColor(color_);
canvas->DrawCircle(gfx::PointF(radius, radius), radius, flags);
}
gfx::ImageSkia CreateCircle(int size, SkColor color = SK_ColorWHITE) {
return gfx::CanvasImageSource::MakeImageSkia<CircleImageSource>(size, color);
}
gfx::ImageSkia CropCircle(const gfx::ImageSkia& image) {
DCHECK_EQ(image.width(), image.height());
return gfx::ImageSkiaOperations::CreateMaskedImage(
image, CreateCircle(image.width()));
}
gfx::ImageSkia AddCircularBackground(const gfx::ImageSkia& image,
SkColor bg_color,
int size) {
if (image.isNull())
return gfx::ImageSkia();
return gfx::ImageSkiaOperations::CreateSuperimposedImage(
CreateCircle(size, bg_color), image);
}
std::unique_ptr<views::BoxLayout> CreateBoxLayout(
views::BoxLayout::Orientation orientation,
views::BoxLayout::CrossAxisAlignment cross_axis_alignment,
gfx::Insets insets = gfx::Insets()) {
auto layout = std::make_unique<views::BoxLayout>(orientation, insets);
layout->set_cross_axis_alignment(cross_axis_alignment);
return layout;
}
std::unique_ptr<views::View> CreateBorderedBoxView() {
constexpr int kBorderThickness = 1;
auto bordered_box = std::make_unique<views::View>();
bordered_box->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
bordered_box->SetBorder(views::CreateRoundedRectBorder(
kBorderThickness,
views::LayoutProvider::Get()->GetCornerRadiusMetric(views::EMPHASIS_HIGH),
GetDefaultSeparatorColor()));
bordered_box->SetProperty(views::kMarginsKey, gfx::Insets(kMenuEdgeMargin));
return bordered_box;
}
std::unique_ptr<views::Button> CreateCircularImageButton(
views::ButtonListener* listener,
const gfx::ImageSkia& image,
const base::string16& text,
bool show_border = false) {
constexpr int kImageSize = 28;
const int kBorderThickness = show_border ? 1 : 0;
const SkScalar kButtonRadius = (kImageSize + 2 * kBorderThickness) / 2.0;
auto button = std::make_unique<views::ImageButton>(listener);
button->SetImage(views::Button::STATE_NORMAL, SizeImage(image, kImageSize));
button->SetTooltipText(text);
button->SetInkDropMode(views::Button::InkDropMode::ON);
button->SetFocusForPlatform();
button->set_ink_drop_base_color(GetDefaultIconColor());
if (show_border) {
button->SetBorder(views::CreateRoundedRectBorder(
kBorderThickness, kButtonRadius, GetDefaultSeparatorColor()));
}
InstallCircleHighlightPathGenerator(button.get());
return button;
}
} // namespace
// MenuItems--------------------------------------------------------------------
ProfileMenuViewBase::MenuItems::MenuItems()
: first_item_type(ProfileMenuViewBase::MenuItems::kNone),
last_item_type(ProfileMenuViewBase::MenuItems::kNone),
different_item_types(false) {}
ProfileMenuViewBase::MenuItems::MenuItems(MenuItems&&) = default;
ProfileMenuViewBase::MenuItems::~MenuItems() = default;
// ProfileMenuViewBase ---------------------------------------------------------
// static
void ProfileMenuViewBase::ShowBubble(
profiles::BubbleViewMode view_mode,
signin_metrics::AccessPoint access_point,
views::Button* anchor_button,
Browser* browser,
bool is_source_keyboard) {
if (IsShowing())
return;
base::RecordAction(base::UserMetricsAction("ProfileMenu_Opened"));
ProfileMenuViewBase* bubble;
if (base::FeatureList::IsEnabled(features::kProfileMenuRevamp)) {
#if !defined(OS_CHROMEOS)
// On Desktop, all modes(regular, guest, incognito) are handled within
// ProfileMenuView.
bubble = new ProfileMenuView(anchor_button, browser, access_point);
#else
// On ChromeOS, only the incognito menu is implemented.
DCHECK(browser->profile()->IsIncognitoProfile());
bubble = new IncognitoMenuView(anchor_button, browser);
#endif
} else {
if (view_mode == profiles::BUBBLE_VIEW_MODE_INCOGNITO) {
DCHECK(browser->profile()->IsIncognitoProfile());
bubble = new IncognitoMenuView(anchor_button, browser);
} else {
DCHECK_EQ(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, view_mode);
#if !defined(OS_CHROMEOS)
bubble = new ProfileMenuView(anchor_button, browser, access_point);
#else
NOTREACHED();
return;
#endif
}
}
views::BubbleDialogDelegateView::CreateBubble(bubble)->Show();
if (is_source_keyboard)
bubble->FocusButtonOnKeyboardOpen();
}
// static
bool ProfileMenuViewBase::IsShowing() {
return g_profile_bubble_ != nullptr;
}
// static
void ProfileMenuViewBase::Hide() {
if (g_profile_bubble_)
g_profile_bubble_->GetWidget()->Close();
}
// static
ProfileMenuViewBase* ProfileMenuViewBase::GetBubbleForTesting() {
return g_profile_bubble_;
}
ProfileMenuViewBase::ProfileMenuViewBase(views::Button* anchor_button,
Browser* browser)
: BubbleDialogDelegateView(anchor_button, views::BubbleBorder::TOP_RIGHT),
browser_(browser),
anchor_button_(anchor_button),
close_bubble_helper_(this, browser) {
DCHECK(!g_profile_bubble_);
g_profile_bubble_ = this;
// TODO(sajadm): Remove when fixing https://crbug.com/822075
// The sign in webview will be clipped on the bottom corners without these
// margins, see related bug <http://crbug.com/593203>.
set_margins(gfx::Insets(2, 0));
DCHECK(anchor_button);
anchor_button->AnimateInkDrop(views::InkDropState::ACTIVATED, nullptr);
EnableUpDownKeyboardAccelerators();
GetViewAccessibility().OverrideRole(ax::mojom::Role::kMenu);
}
ProfileMenuViewBase::~ProfileMenuViewBase() {
// Items stored for menu generation are removed after menu is finalized, hence
// it's not expected to have while destroying the object.
DCHECK(g_profile_bubble_ != this);
DCHECK(menu_item_groups_.empty());
}
void ProfileMenuViewBase::SetIdentityInfo(const gfx::ImageSkia& image,
const gfx::ImageSkia& badge,
const base::string16& title,
const base::string16& subtitle) {
constexpr int kTopMargin = 16;
constexpr int kBottomMargin = 8;
constexpr int kImageToLabelSpacing = 4;
constexpr int kBadgeSize = 16;
constexpr int kBadgePadding = 1;
identity_info_container_->RemoveAllChildViews(/*delete_children=*/true);
identity_info_container_->SetLayoutManager(
CreateBoxLayout(views::BoxLayout::Orientation::kVertical,
views::BoxLayout::CrossAxisAlignment::kCenter,
gfx::Insets(kTopMargin, 0, kBottomMargin, 0)));
views::ImageView* image_view = identity_info_container_->AddChildView(
std::make_unique<views::ImageView>());
// Fall back on |kUserAccountAvatarIcon| if |image| is empty. This can happen
// in tests and when the account image hasn't been fetched yet.
gfx::ImageSkia sized_image =
image.isNull()
? gfx::CreateVectorIcon(kUserAccountAvatarIcon, kIdentityImageSize,
GetDefaultIconColor())
: CropCircle(SizeImage(image, kIdentityImageSize));
gfx::ImageSkia sized_badge =
AddCircularBackground(SizeImage(badge, kBadgeSize), SK_ColorWHITE,
kBadgeSize + 2 * kBadgePadding);
gfx::ImageSkia badged_image =
gfx::ImageSkiaOperations::CreateIconWithBadge(sized_image, sized_badge);
image_view->SetImage(badged_image);
views::View* title_label =
identity_info_container_->AddChildView(std::make_unique<views::Label>(
title, views::style::CONTEXT_DIALOG_TITLE));
title_label->SetBorder(
views::CreateEmptyBorder(kImageToLabelSpacing, 0, 0, 0));
if (!subtitle.empty()) {
identity_info_container_->AddChildView(std::make_unique<views::Label>(
subtitle, views::style::CONTEXT_LABEL, views::style::STYLE_SECONDARY));
}
}
void ProfileMenuViewBase::SetSyncInfo(const gfx::ImageSkia& icon,
const base::string16& description,
const base::string16& clickable_text,
base::RepeatingClosure action) {
constexpr int kVerticalSpacing = 8;
constexpr int kIconSize = 16;
sync_info_container_->RemoveAllChildViews(/*delete_children=*/true);
sync_info_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
views::LabelButton* button = nullptr;
if (description.empty()) {
button = sync_info_container_->AddChildView(std::make_unique<HoverButton>(
this, SizeImage(icon, kIconSize), clickable_text));
} else {
// Add icon + description on top.
views::View* description_container =
sync_info_container_->AddChildView(std::make_unique<views::View>());
views::BoxLayout* description_layout =
description_container->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(kVerticalSpacing, kMenuEdgeMargin),
/*between_child_spacing=*/
ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_LABEL_HORIZONTAL)));
if (icon.isNull()) {
// If there is no image, the description text should be centered.
description_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
} else {
views::ImageView* icon_view = description_container->AddChildView(
std::make_unique<views::ImageView>());
icon_view->SetImage(SizeImage(icon, kIconSize));
}
views::Label* label = description_container->AddChildView(
std::make_unique<views::Label>(description));
label->SetMultiLine(true);
label->SetHandlesTooltips(false);
// Add blue button at the bottom.
button = sync_info_container_->AddChildView(
views::MdTextButton::CreateSecondaryUiBlueButton(this, clickable_text));
button->SetProperty(
views::kMarginsKey,
gfx::Insets(/*top=*/0, /*left=*/kMenuEdgeMargin,
/*bottom=*/kVerticalSpacing, /*right=*/kMenuEdgeMargin));
}
RegisterClickAction(button, std::move(action));
}
void ProfileMenuViewBase::AddShortcutFeatureButton(
const gfx::ImageSkia& icon,
const base::string16& text,
base::RepeatingClosure action) {
constexpr int kBottomMargin = 8;
constexpr int kButtonSpacing = 6;
// Initialize layout if this is the first time a button is added.
if (!shortcut_features_container_->GetLayoutManager()) {
views::BoxLayout* layout = shortcut_features_container_->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(0, 0, kBottomMargin, 0), kButtonSpacing));
layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
}
views::Button* button = shortcut_features_container_->AddChildView(
CreateCircularImageButton(this, icon, text, /*show_border=*/true));
RegisterClickAction(button, std::move(action));
}
void ProfileMenuViewBase::AddAccountFeatureButton(
const gfx::ImageSkia& icon,
const base::string16& text,
base::RepeatingClosure action) {
constexpr int kIconSize = 16;
// Initialize layout if this is the first time a button is added.
if (!account_features_container_->GetLayoutManager()) {
account_features_container_->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
}
views::Button* button =
account_features_container_->AddChildView(std::make_unique<HoverButton>(
this, SizeImage(ColorImage(icon, GetDefaultIconColor()), kIconSize),
text));
RegisterClickAction(button, std::move(action));
}
void ProfileMenuViewBase::SetProfileHeading(const base::string16& heading) {
profile_heading_container_->RemoveAllChildViews(/*delete_children=*/true);
profile_heading_container_->SetLayoutManager(
std::make_unique<views::FillLayout>());
views::Label* label =
profile_heading_container_->AddChildView(std::make_unique<views::Label>(
heading, views::style::CONTEXT_LABEL, STYLE_HINT));
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetHandlesTooltips(false);
label->SetBorder(views::CreateEmptyBorder(gfx::Insets(0, kMenuEdgeMargin)));
}
void ProfileMenuViewBase::AddSelectableProfile(const gfx::ImageSkia& image,
const base::string16& name,
base::RepeatingClosure action) {
constexpr int kTopMargin = 8;
constexpr int kImageSize = 22;
// Initialize layout if this is the first time a button is added.
if (!selectable_profiles_container_->GetLayoutManager()) {
selectable_profiles_container_->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(kTopMargin, 0, 0, 0)));
}
gfx::ImageSkia sized_image = CropCircle(SizeImage(image, kImageSize));
views::Button* button = selectable_profiles_container_->AddChildView(
std::make_unique<HoverButton>(this, sized_image, name));
RegisterClickAction(button, std::move(action));
}
void ProfileMenuViewBase::AddProfileShortcutFeatureButton(
const gfx::ImageSkia& icon,
const base::string16& text,
base::RepeatingClosure action) {
// Initialize layout if this is the first time a button is added.
if (!profile_shortcut_features_container_->GetLayoutManager()) {
profile_shortcut_features_container_->SetLayoutManager(
CreateBoxLayout(views::BoxLayout::Orientation::kHorizontal,
views::BoxLayout::CrossAxisAlignment::kCenter,
gfx::Insets(0, 0, 0, /*right=*/kMenuEdgeMargin)));
}
views::Button* button = profile_shortcut_features_container_->AddChildView(
CreateCircularImageButton(this, icon, text));
RegisterClickAction(button, std::move(action));
}
void ProfileMenuViewBase::AddProfileFeatureButton(
const gfx::ImageSkia& icon,
const base::string16& text,
base::RepeatingClosure action) {
constexpr int kIconSize = 22;
// Initialize layout if this is the first time a button is added.
if (!profile_features_container_->GetLayoutManager()) {
profile_features_container_->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
}
views::Button* button = profile_features_container_->AddChildView(
std::make_unique<HoverButton>(this, SizeImage(icon, kIconSize), text));
RegisterClickAction(button, std::move(action));
}
gfx::ImageSkia ProfileMenuViewBase::ImageForMenu(const gfx::VectorIcon& icon,
float icon_to_image_ratio) {
const int padding =
static_cast<int>(kMaxImageSize * (1.0 - icon_to_image_ratio) / 2.0);
auto sized_icon = gfx::CreateVectorIcon(icon, kMaxImageSize - 2 * padding,
GetDefaultIconColor());
return gfx::CanvasImageSource::CreatePadded(sized_icon, gfx::Insets(padding));
}
gfx::ImageSkia ProfileMenuViewBase::ColoredImageForMenu(
const gfx::VectorIcon& icon,
SkColor color) {
return gfx::CreateVectorIcon(icon, kMaxImageSize, color);
}
ax::mojom::Role ProfileMenuViewBase::GetAccessibleWindowRole() {
// Return |ax::mojom::Role::kDialog| which will make screen readers announce
// the following in the listed order:
// the title of the dialog, labels (if any), the focused View within the
// dialog (if any)
return ax::mojom::Role::kDialog;
}
void ProfileMenuViewBase::OnThemeChanged() {
views::BubbleDialogDelegateView::OnThemeChanged();
SetBackground(views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DialogBackground)));
}
int ProfileMenuViewBase::GetDialogButtons() const {
return ui::DIALOG_BUTTON_NONE;
}
bool ProfileMenuViewBase::HandleContextMenu(
content::RenderFrameHost* render_frame_host,
const content::ContextMenuParams& params) {
// Suppresses the context menu because some features, such as inspecting
// elements, are not appropriate in a bubble.
return true;
}
void ProfileMenuViewBase::Init() {
Reset();
BuildMenu();
if (!base::FeatureList::IsEnabled(features::kProfileMenuRevamp))
RepopulateViewFromMenuItems();
}
void ProfileMenuViewBase::WindowClosing() {
DCHECK_EQ(g_profile_bubble_, this);
if (anchor_button())
anchor_button()->AnimateInkDrop(views::InkDropState::DEACTIVATED, nullptr);
g_profile_bubble_ = nullptr;
}
void ProfileMenuViewBase::ButtonPressed(views::Button* button,
const ui::Event& event) {
OnClick(button);
}
void ProfileMenuViewBase::LinkClicked(views::Link* link, int event_flags) {
OnClick(link);
}
void ProfileMenuViewBase::StyledLabelLinkClicked(views::StyledLabel* link,
const gfx::Range& range,
int event_flags) {
OnClick(link);
}
void ProfileMenuViewBase::OnClick(views::View* clickable_view) {
DCHECK(!click_actions_[clickable_view].is_null());
base::RecordAction(
base::UserMetricsAction("ProfileMenu_ActionableItemClicked"));
click_actions_[clickable_view].Run();
}
int ProfileMenuViewBase::GetMaxHeight() const {
gfx::Rect anchor_rect = GetAnchorRect();
gfx::Rect screen_space =
display::Screen::GetScreen()
->GetDisplayNearestPoint(anchor_rect.CenterPoint())
.work_area();
int available_space = screen_space.bottom() - anchor_rect.bottom();
#if defined(OS_WIN)
// On Windows the bubble can also be show to the top of the anchor.
available_space =
std::max(available_space, anchor_rect.y() - screen_space.y());
#endif
return std::max(kMinimumScrollableContentHeight, available_space);
}
void ProfileMenuViewBase::Reset() {
if (!base::FeatureList::IsEnabled(features::kProfileMenuRevamp)) {
menu_item_groups_.clear();
return;
}
click_actions_.clear();
RemoveAllChildViews(/*delete_childen=*/true);
auto components = std::make_unique<views::View>();
components->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
// Create and add new component containers in the correct order.
// First, add the bordered box with the identity and feature buttons.
views::View* bordered_box = components->AddChildView(CreateBorderedBoxView());
identity_info_container_ =
bordered_box->AddChildView(std::make_unique<views::View>());
shortcut_features_container_ =
bordered_box->AddChildView(std::make_unique<views::View>());
sync_info_container_ =
bordered_box->AddChildView(std::make_unique<views::View>());
account_features_container_ =
bordered_box->AddChildView(std::make_unique<views::View>());
// Second, add the profile header.
auto profile_header = std::make_unique<views::View>();
views::BoxLayout* profile_header_layout =
profile_header->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
profile_heading_container_ =
profile_header->AddChildView(std::make_unique<views::View>());
profile_header_layout->SetFlexForView(profile_heading_container_, 1);
profile_shortcut_features_container_ =
profile_header->AddChildView(std::make_unique<views::View>());
profile_header_layout->SetFlexForView(profile_shortcut_features_container_,
0);
components->AddChildView(std::move(profile_header));
// Third, add the profile buttons at the bottom.
selectable_profiles_container_ =
components->AddChildView(std::make_unique<views::View>());
profile_features_container_ =
components->AddChildView(std::make_unique<views::View>());
// Create a scroll view to hold the components.
auto scroll_view = std::make_unique<views::ScrollView>();
scroll_view->SetHideHorizontalScrollBar(true);
// TODO(https://crbug.com/871762): it's a workaround for the crash.
scroll_view->SetDrawOverflowIndicator(false);
scroll_view->ClipHeightTo(0, GetMaxHeight());
scroll_view->SetContents(std::move(components));
// Create a grid layout to set the menu width.
views::GridLayout* layout =
SetLayoutManager(std::make_unique<views::GridLayout>());
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
views::GridLayout::kFixedSize, views::GridLayout::FIXED,
kMenuWidth, kMenuWidth);
layout->StartRow(1.0, 0);
layout->AddView(std::move(scroll_view));
}
int ProfileMenuViewBase::GetMarginSize(GroupMarginSize margin_size) const {
switch (margin_size) {
case kNone:
return 0;
case kTiny:
return ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_CONTENT_LIST_VERTICAL_SINGLE);
case kSmall:
return ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_CONTROL_VERTICAL_SMALL);
case kLarge:
return ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_UNRELATED_CONTROL_VERTICAL_LARGE);
}
}
void ProfileMenuViewBase::AddMenuGroup(bool add_separator) {
if (add_separator && !menu_item_groups_.empty()) {
DCHECK(!menu_item_groups_.back().items.empty());
menu_item_groups_.emplace_back();
}
menu_item_groups_.emplace_back();
}
void ProfileMenuViewBase::AddMenuItemInternal(std::unique_ptr<views::View> view,
MenuItems::ItemType item_type) {
DCHECK(!menu_item_groups_.empty());
auto& current_group = menu_item_groups_.back();
current_group.items.push_back(std::move(view));
if (current_group.items.size() == 1) {
current_group.first_item_type = item_type;
current_group.last_item_type = item_type;
} else {
current_group.different_item_types |=
current_group.last_item_type != item_type;
current_group.last_item_type = item_type;
}
}
views::Button* ProfileMenuViewBase::CreateAndAddTitleCard(
std::unique_ptr<views::View> icon_view,
const base::string16& title,
const base::string16& subtitle,
base::RepeatingClosure action) {
std::unique_ptr<HoverButton> title_card = std::make_unique<HoverButton>(
this, std::move(icon_view), title, subtitle);
if (action.is_null())
title_card->SetEnabled(false);
views::Button* button_ptr = title_card.get();
RegisterClickAction(button_ptr, std::move(action));
AddMenuItemInternal(std::move(title_card), MenuItems::kTitleCard);
return button_ptr;
}
views::Button* ProfileMenuViewBase::CreateAndAddButton(
const gfx::ImageSkia& icon,
const base::string16& title,
base::RepeatingClosure action) {
std::unique_ptr<HoverButton> button =
std::make_unique<HoverButton>(this, icon, title);
views::Button* pointer = button.get();
RegisterClickAction(pointer, std::move(action));
AddMenuItemInternal(std::move(button), MenuItems::kButton);
return pointer;
}
views::Button* ProfileMenuViewBase::CreateAndAddBlueButton(
const base::string16& text,
bool md_style,
base::RepeatingClosure action) {
std::unique_ptr<views::LabelButton> button =
md_style ? views::MdTextButton::CreateSecondaryUiBlueButton(this, text)
: views::MdTextButton::Create(this, text);
views::Button* pointer = button.get();
RegisterClickAction(pointer, std::move(action));
// Add margins.
std::unique_ptr<views::View> margined_view = std::make_unique<views::View>();
margined_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(0, kMenuEdgeMargin)));
margined_view->AddChildView(std::move(button));
AddMenuItemInternal(std::move(margined_view), MenuItems::kStyledButton);
return pointer;
}
#if !defined(OS_CHROMEOS)
DiceSigninButtonView* ProfileMenuViewBase::CreateAndAddDiceSigninButton(
AccountInfo* account_info,
gfx::Image* account_icon,
base::RepeatingClosure action) {
std::unique_ptr<DiceSigninButtonView> button =
account_info ? std::make_unique<DiceSigninButtonView>(*account_info,
*account_icon, this)
: std::make_unique<DiceSigninButtonView>(this);
DiceSigninButtonView* pointer = button.get();
RegisterClickAction(pointer->signin_button(), std::move(action));
// Add margins.
std::unique_ptr<views::View> margined_view = std::make_unique<views::View>();
margined_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(GetMarginSize(kSmall), kMenuEdgeMargin)));
margined_view->AddChildView(std::move(button));
AddMenuItemInternal(std::move(margined_view), MenuItems::kStyledButton);
return pointer;
}
#endif
views::Label* ProfileMenuViewBase::CreateAndAddLabel(const base::string16& text,
int text_context) {
std::unique_ptr<views::Label> label =
std::make_unique<views::Label>(text, text_context);
label->SetMultiLine(true);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetMaximumWidth(kMenuWidth - 2 * kMenuEdgeMargin);
views::Label* pointer = label.get();
// Add margins.
std::unique_ptr<views::View> margined_view = std::make_unique<views::View>();
margined_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(0, kMenuEdgeMargin)));
margined_view->AddChildView(std::move(label));
AddMenuItemInternal(std::move(margined_view), MenuItems::kLabel);
return pointer;
}
views::StyledLabel* ProfileMenuViewBase::CreateAndAddLabelWithLink(
const base::string16& text,
gfx::Range link_range,
base::RepeatingClosure action) {
auto label_with_link = std::make_unique<views::StyledLabel>(text, this);
label_with_link->SetDefaultTextStyle(views::style::STYLE_SECONDARY);
label_with_link->AddStyleRange(
link_range, views::StyledLabel::RangeStyleInfo::CreateForLink());
views::StyledLabel* pointer = label_with_link.get();
RegisterClickAction(pointer, std::move(action));
AddViewItem(std::move(label_with_link));
return pointer;
}
void ProfileMenuViewBase::AddViewItem(std::unique_ptr<views::View> view) {
// Add margins.
std::unique_ptr<views::View> margined_view = std::make_unique<views::View>();
margined_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(0, kMenuEdgeMargin)));
margined_view->AddChildView(std::move(view));
AddMenuItemInternal(std::move(margined_view), MenuItems::kGeneral);
}
void ProfileMenuViewBase::RegisterClickAction(views::View* clickable_view,
base::RepeatingClosure action) {
DCHECK(click_actions_.count(clickable_view) == 0);
click_actions_[clickable_view] = std::move(action);
}
void ProfileMenuViewBase::RepopulateViewFromMenuItems() {
RemoveAllChildViews(true);
// Create a view to keep menu contents.
auto contents_view = std::make_unique<views::View>();
contents_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets()));
for (unsigned group_index = 0; group_index < menu_item_groups_.size();
group_index++) {
MenuItems& group = menu_item_groups_[group_index];
if (group.items.empty()) {
// An empty group represents a separator.
contents_view->AddChildView(new views::Separator());
} else {
views::View* sub_view = new views::View();
GroupMarginSize top_margin;
GroupMarginSize bottom_margin;
GroupMarginSize child_spacing;
if (group.first_item_type == MenuItems::kTitleCard ||
group.first_item_type == MenuItems::kLabel) {
top_margin = kTiny;
} else {
top_margin = kSmall;
}
if (group.last_item_type == MenuItems::kTitleCard) {
bottom_margin = kTiny;
} else if (group.last_item_type == MenuItems::kButton) {
bottom_margin = kSmall;
} else {
bottom_margin = kLarge;
}
if (!group.different_item_types) {
child_spacing = kNone;
} else if (group.items.size() == 2 &&
group.first_item_type == MenuItems::kTitleCard &&
group.last_item_type == MenuItems::kButton) {
child_spacing = kNone;
} else {
child_spacing = kLarge;
}
// Reduce margins if previous/next group is not a separator.
if (group_index + 1 < menu_item_groups_.size() &&
!menu_item_groups_[group_index + 1].items.empty()) {
bottom_margin = kTiny;
}
if (group_index > 0 &&
!menu_item_groups_[group_index - 1].items.empty()) {
top_margin = kTiny;
}
sub_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(GetMarginSize(top_margin), 0,
GetMarginSize(bottom_margin), 0),
GetMarginSize(child_spacing)));
for (std::unique_ptr<views::View>& item : group.items)
sub_view->AddChildView(std::move(item));
contents_view->AddChildView(sub_view);
}
}
menu_item_groups_.clear();
// Create a scroll view to hold contents view.
auto scroll_view = std::make_unique<views::ScrollView>();
scroll_view->SetHideHorizontalScrollBar(true);
// TODO(https://crbug.com/871762): it's a workaround for the crash.
scroll_view->SetDrawOverflowIndicator(false);
scroll_view->ClipHeightTo(0, GetMaxHeight());
scroll_view->SetContents(std::move(contents_view));
// Create a grid layout to set the menu width.
views::GridLayout* layout =
SetLayoutManager(std::make_unique<views::GridLayout>());
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
views::GridLayout::kFixedSize, views::GridLayout::FIXED,
kMenuWidth, kMenuWidth);
layout->StartRow(1.0, 0);
layout->AddView(std::move(scroll_view));
if (GetBubbleFrameView()) {
SizeToContents();
// SizeToContents() will perform a layout, but only if the size changed.
Layout();
}
}
gfx::ImageSkia ProfileMenuViewBase::CreateVectorIcon(
const gfx::VectorIcon& icon) {
return gfx::CreateVectorIcon(icon, kIconSize, GetDefaultIconColor());
}
int ProfileMenuViewBase::GetDefaultIconSize() {
return kIconSize;
}