blob: 4581dfa20a7c15600aca613c3ba5df2dab75a98e [file] [log] [blame]
// 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 "chrome/browser/ui/views/profiles/dice_accounts_menu.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/view.h"
namespace {
constexpr int kProfilesAvatarIconSize = 16;
// Used to identify the "Use another account" button.
constexpr int kUseAnotherAccountCmdId = std::numeric_limits<int>::max();
// Used to identify the "Sign out" button.
constexpr int kSignOutCmdId = std::numeric_limits<int>::max() - 1;
// Anchor inset used to position the accounts menu.
constexpr int kAnchorInset = 8;
constexpr int kVerticalItemMargins = 12;
constexpr int kHorizontalIconSpacing = 16;
gfx::ImageSkia SizeAndCircleIcon(const gfx::Image& icon) {
gfx::Image circled_icon = profiles::GetSizedAvatarIcon(
icon, true, kProfilesAvatarIconSize, kProfilesAvatarIconSize,
profiles::SHAPE_CIRCLE);
return *circled_icon.ToImageSkia();
}
} // namespace
DiceAccountsMenu::DiceAccountsMenu(const std::vector<AccountInfo>& accounts,
Callback account_selected_callback)
: accounts_(accounts),
account_selected_callback_(std::move(account_selected_callback)) {
}
void DiceAccountsMenu::Show(views::View* anchor_view,
views::MenuButton* menu_button) {
DCHECK(!runner_);
runner_ = std::make_unique<views::MenuRunner>(BuildMenu(),
views::MenuRunner::COMBOBOX);
// Calculate custom anchor bounds to position the menu.
// The menu is aligned along the right edge (left edge in RTL mode) of the
// anchor, slightly shifted inside by |kAnchorInset| and overlapping
// |anchor_view| on the bottom by |kAnchorInset|. |anchor_bounds| is collapsed
// so the menu only takes the width it needs.
gfx::Rect anchor_bounds = anchor_view->GetBoundsInScreen();
anchor_bounds.Inset(kAnchorInset, kAnchorInset);
if (base::i18n::IsRTL())
anchor_bounds.Inset(0, 0, anchor_bounds.width(), 0);
else
anchor_bounds.Inset(anchor_bounds.width(), 0, 0, 0);
runner_->RunMenuAt(anchor_view->GetWidget(), menu_button->button_controller(),
anchor_bounds, views::MenuAnchorPosition::kTopRight,
ui::MENU_SOURCE_MOUSE);
}
DiceAccountsMenu::~DiceAccountsMenu() {}
void DiceAccountsMenu::SetSignOutButtonCallback(
base::OnceClosure signout_callback) {
signout_callback_ = std::move(signout_callback);
}
views::MenuItemView* DiceAccountsMenu::BuildMenu() {
views::MenuItemView* menu = new views::MenuItemView(this);
gfx::Image default_icon =
ui::ResourceBundle::GetSharedInstance().GetImageNamed(
profiles::GetPlaceholderAvatarIconResourceID());
#if !defined(OS_MACOSX)
// Add spacing at top.
menu->AppendMenuItemImpl(
0, base::string16(), base::string16(), base::string16(), nullptr,
gfx::ImageSkia(), views::MenuItemView::SEPARATOR, ui::SPACING_SEPARATOR);
#endif
// Add a menu item for each account.
for (size_t idx = 0; idx < accounts_.size(); idx++) {
views::MenuItemView* item = menu->AppendMenuItemWithIcon(
idx, base::UTF8ToUTF16(accounts_[idx].email),
SizeAndCircleIcon(accounts_[idx].account_image.IsEmpty()
? default_icon
: accounts_[idx].account_image));
item->SetMargins(kVerticalItemMargins, kVerticalItemMargins);
}
// Add the "Use another account" button.
views::MenuItemView* item = menu->AppendMenuItemWithIcon(
kUseAnotherAccountCmdId,
l10n_util::GetStringUTF16(IDS_PROFILES_DICE_USE_ANOTHER_ACCOUNT_BUTTON),
SizeAndCircleIcon(default_icon));
item->SetMargins(kVerticalItemMargins, kVerticalItemMargins);
if (!signout_callback_.is_null()) {
// Add the "Sign out" button.
item = menu->AppendMenuItemWithIcon(
kSignOutCmdId, l10n_util::GetStringUTF16(IDS_SCREEN_LOCK_SIGN_OUT),
gfx::CreateVectorIcon(kSignOutIcon, kProfilesAvatarIconSize,
gfx::kGoogleGrey600));
item->SetMargins(kVerticalItemMargins, kVerticalItemMargins);
}
#if !defined(OS_MACOSX)
// Add spacing at bottom.
menu->AppendMenuItemImpl(
0, base::string16(), base::string16(), base::string16(), nullptr,
gfx::ImageSkia(), views::MenuItemView::SEPARATOR, ui::SPACING_SEPARATOR);
#endif
menu->set_has_icons(true);
return menu;
}
void DiceAccountsMenu::ExecuteCommand(int id) {
DCHECK((0 <= id && static_cast<size_t>(id) < accounts_.size()) ||
id == kUseAnotherAccountCmdId || id == kSignOutCmdId);
if (id == kSignOutCmdId) {
std::move(signout_callback_).Run();
return;
}
base::Optional<AccountInfo> account;
if (id != kUseAnotherAccountCmdId)
account = accounts_[id];
std::move(account_selected_callback_).Run(account);
}
void DiceAccountsMenu::GetHorizontalIconMargins(int command_id,
int icon_size,
int* left_margin,
int* right_margin) const {
const views::MenuConfig& config = views::MenuConfig::instance();
*left_margin = kHorizontalIconSpacing - config.item_horizontal_padding;
*right_margin = kHorizontalIconSpacing - config.item_horizontal_padding;
}
void DiceAccountsMenu::GetLabelStyle(int id, LabelStyle* style) const {
const views::LayoutProvider* provider = views::LayoutProvider::Get();
// Match the font of the HoverButtons in the avatar bubble.
style->font_list = provider->GetTypographyProvider().GetFont(
views::style::CONTEXT_BUTTON, STYLE_SECONDARY);
}