blob: 29cfdfc7b5bc3a6b1946c70f967b96c1a9a6636f [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// 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/toolbar/browser_app_menu_button.h"
#include <set>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/rand_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "cc/paint/paint_flags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_otr_state.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/toolbar/app_menu_icon_controller.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/toolbar/app_menu.h"
#include "chrome/browser/ui/views/toolbar/toolbar_button.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/browser/user_education/tutorial_identifiers.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "chrome/browser/user_education/user_education_service_factory.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/user_education/common/feature_promo/feature_promo_controller.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/metrics.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/virtual_keyboard_controller.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace {
constexpr int kChromeRefreshImageLabelPadding = 2;
}
// static
bool BrowserAppMenuButton::g_open_app_immediately_for_testing = false;
BrowserAppMenuButton::BrowserAppMenuButton(ToolbarView* toolbar_view)
: AppMenuButton(base::BindRepeating(&BrowserAppMenuButton::ButtonPressed,
base::Unretained(this))),
toolbar_view_(toolbar_view) {
SetHorizontalAlignment(gfx::ALIGN_RIGHT);
SetImageLabelSpacing(kChromeRefreshImageLabelPadding);
label()->SetPaintToLayer();
label()->SetSkipSubpixelRenderingOpacityCheck(true);
label()->layer()->SetFillsBoundsOpaquely(false);
label()->SetSubpixelRenderingEnabled(false);
}
BrowserAppMenuButton::~BrowserAppMenuButton() = default;
void BrowserAppMenuButton::SetTypeAndSeverity(
AppMenuIconController::TypeAndSeverity type_and_severity) {
type_and_severity_ = type_and_severity;
UpdateThemeBasedState();
}
void BrowserAppMenuButton::ShowMenu(int run_types) {
if (IsMenuShowing()) {
return;
}
#if BUILDFLAG(IS_CHROMEOS)
if (auto* input_method = GetInputMethod()) {
if (auto* controller = input_method->GetVirtualKeyboardController();
controller && controller->IsKeyboardVisible()) {
input_method->SetVirtualKeyboardVisibilityIfEnabled(false);
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
Browser* browser = toolbar_view_->browser();
// Allow highlighting menu items when the menu was opened while
// certain tutorials are running.
AlertMenuItem alert_item = GetAlertItemForRunningTutorial();
RunMenu(std::make_unique<AppMenuModel>(
toolbar_view_, browser, toolbar_view_->app_menu_icon_controller(),
alert_item),
browser, run_types);
}
AlertMenuItem BrowserAppMenuButton::GetAlertItemForRunningTutorial() {
Browser* browser = toolbar_view_->browser();
BrowserWindow* browser_window = browser->window();
if (browser_window == nullptr) {
return AlertMenuItem::kNone;
}
auto* const service =
UserEducationServiceFactory::GetForBrowserContext(browser->profile());
if (service && service->tutorial_service().IsRunningTutorial(
kPasswordManagerTutorialId)) {
return AlertMenuItem::kPasswordManager;
}
return AlertMenuItem::kNone;
}
void BrowserAppMenuButton::OnThemeChanged() {
UpdateThemeBasedState();
AppMenuButton::OnThemeChanged();
}
void BrowserAppMenuButton::UpdateThemeBasedState() {
UpdateLayoutInsets();
UpdateTextAndHighlightColor();
// Call `UpdateIcon()` after `UpdateTextAndHighlightColor()` as the icon color
// depends on if the container is in an expanded state.
UpdateIcon();
UpdateInkdrop();
// Outset focus ring should be present for the chip but not when only
// the icon is visible.
views::FocusRing::Get(this)->SetOutsetFocusRingDisabled(
!IsLabelPresentAndVisible());
}
void BrowserAppMenuButton::UpdateIcon() {
const gfx::VectorIcon& icon = ui::TouchUiController::Get()->touch_ui()
? kBrowserToolsTouchIcon
: kBrowserToolsChromeRefreshIcon;
for (auto state : kButtonStates) {
SkColor icon_color = GetForegroundColor(state);
SetImageModel(state, ui::ImageModel::FromVectorIcon(icon, icon_color));
}
}
void BrowserAppMenuButton::UpdateInkdrop() {
if (IsLabelPresentAndVisible()) {
ConfigureToolbarInkdropForRefresh2023(this, kColorAppMenuChipInkDropHover,
kColorAppMenuChipInkDropRipple);
} else {
ConfigureToolbarInkdropForRefresh2023(this, kColorToolbarInkDropHover,
kColorToolbarInkDropRipple);
}
}
bool BrowserAppMenuButton::IsLabelPresentAndVisible() const {
if (!label()) {
return false;
}
return label()->GetVisible() && !label()->GetText().empty();
}
SkColor BrowserAppMenuButton::GetForegroundColor(ButtonState state) const {
if (IsLabelPresentAndVisible()) {
const auto* const color_provider = GetColorProvider();
if (type_and_severity_.use_primary_colors) {
return color_provider->GetColor(kColorAppMenuExpandedForegroundPrimary);
}
return color_provider->GetColor(kColorAppMenuExpandedForegroundDefault);
}
return ToolbarButton::GetForegroundColor(state);
}
void BrowserAppMenuButton::UpdateTextAndHighlightColor() {
int tooltip_message_id;
std::u16string text;
if (type_and_severity_.severity == AppMenuIconController::Severity::NONE) {
tooltip_message_id = IDS_APPMENU_TOOLTIP;
} else if (type_and_severity_.type ==
AppMenuIconController::IconType::UPGRADE_NOTIFICATION) {
tooltip_message_id = IDS_APPMENU_TOOLTIP_UPDATE_AVAILABLE;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX))
int message_id = IDS_APP_MENU_BUTTON_UPDATE;
// Select an update text option randomly. Show this text in all browser
// windows.
static const int update_text_option = base::RandInt(1, 3);
if (update_text_option == 1) {
message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT1;
} else if (update_text_option == 2) {
message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT2;
} else {
message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT3;
}
text = l10n_util::GetStringUTF16(message_id);
#else
text = l10n_util::GetStringUTF16(IDS_APP_MENU_BUTTON_UPDATE);
#endif
} else if (type_and_severity_.type ==
AppMenuIconController::IconType::DEFAULT_BROWSER_PROMPT) {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
tooltip_message_id = IDS_APP_MENU_TOOLTIP_DEFAULT_PROMPT;
text = l10n_util::GetStringUTF16(IDS_APP_MENU_BUTTON_DEFAULT_PROMPT);
#else
tooltip_message_id = IDS_APPMENU_TOOLTIP;
#endif
} else {
tooltip_message_id = IDS_APPMENU_TOOLTIP_ALERT;
const int text_id =
type_and_severity_.severity == AppMenuIconController::Severity::LOW
? IDS_APP_MENU_BUTTON_ACTION_REQUIRED
: IDS_APP_MENU_BUTTON_ERROR;
text = l10n_util::GetStringUTF16(text_id);
}
SetTooltipText(l10n_util::GetStringUTF16(tooltip_message_id));
SetHighlight(text, GetHighlightColor());
}
bool BrowserAppMenuButton::ShouldPaintBorder() const {
return false;
}
void BrowserAppMenuButton::UpdateLayoutInsets() {
if (IsLabelPresentAndVisible()) {
SetLayoutInsets(::GetLayoutInsets(BROWSER_APP_MENU_CHIP_PADDING));
} else {
SetLayoutInsets(::GetLayoutInsets(TOOLBAR_BUTTON));
}
}
std::optional<SkColor> BrowserAppMenuButton::GetHighlightTextColor() const {
if (IsLabelPresentAndVisible()) {
const auto* const color_provider = GetColorProvider();
if (type_and_severity_.use_primary_colors) {
return color_provider->GetColor(kColorAppMenuExpandedForegroundPrimary);
}
return color_provider->GetColor(kColorAppMenuExpandedForegroundDefault);
}
return std::nullopt;
}
std::optional<SkColor> BrowserAppMenuButton::GetHighlightColor() const {
const auto* const color_provider = GetColorProvider();
if (type_and_severity_.severity == AppMenuIconController::Severity::NONE) {
return std::nullopt;
} else {
return color_provider->GetColor(type_and_severity_.use_primary_colors
? kColorAppMenuHighlightPrimary
: kColorAppMenuHighlightDefault);
}
}
void BrowserAppMenuButton::OnTouchUiChanged() {
UpdateColorsAndInsets();
PreferredSizeChanged();
}
void BrowserAppMenuButton::ButtonPressed(const ui::Event& event) {
#if BUILDFLAG(IS_CHROMEOS)
auto* const user_education =
BrowserUserEducationInterface::From(toolbar_view_->browser());
if (user_education->IsFeaturePromoActive(
feature_engagement::kIPHPasswordsSavePrimingPromoFeature)) {
user_education->NotifyFeaturePromoFeatureUsed(
feature_engagement::kIPHPasswordsSavePrimingPromoFeature,
FeaturePromoFeatureUsedAction::kClosePromoIfPresent);
}
#endif // BUILDFLAG(IS_CHROMEOS)
ShowMenu(event.IsKeyEvent() ? (views::MenuRunner::SHOULD_SHOW_MNEMONICS |
views::MenuRunner::INVOKED_FROM_KEYBOARD)
: views::MenuRunner::NO_FLAGS);
}
bool BrowserAppMenuButton::HandleAccessibleAction(
const ui::AXActionData& action_data) {
if (action_data.action == ax::mojom::Action::kExpand) {
ShowMenu(views::MenuRunner::NO_FLAGS);
return true;
}
if (action_data.action == ax::mojom::Action::kCollapse) {
if (AppMenuButton::IsMenuShowing()) {
CloseMenu();
}
return true;
}
return AppMenuButton::HandleAccessibleAction(action_data);
}
BEGIN_METADATA(BrowserAppMenuButton)
END_METADATA