blob: c6520fb32c3c0dc656704dd668b2584c37524198 [file] [log] [blame]
// Copyright 2024 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/lens/lens_preselection_bubble.h"
#include <memory>
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/lens/lens_help_menu_utils.h"
#include "chrome/browser/ui/lens/lens_overlay_controller.h"
#include "chrome/browser/ui/lens/lens_search_feature_flag_utils.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/lens/lens_features.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/mojom/menu_source_type.mojom.h"
#include "ui/color/color_provider.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/menu_button_controller.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/window/dialog_delegate.h"
namespace lens {
namespace {
// The minimum y value in screen coordinates for the preselection bubble.
const int kPreselectionBubbleMinY = 8;
} // namespace
LensPreselectionBubble::LensPreselectionBubble(
base::WeakPtr<LensOverlayController> lens_overlay_controller,
views::View* anchor_view,
bool offline,
ExitClickedCallback exit_clicked_callback,
base::OnceClosure on_cancel_callback)
: BubbleDialogDelegateView(anchor_view,
views::BubbleBorder::NONE,
views::BubbleBorder::NO_SHADOW),
lens_overlay_controller_(lens_overlay_controller),
offline_(offline),
exit_clicked_callback_(std::move(exit_clicked_callback)) {
SetShowCloseButton(false);
set_close_on_deactivate(false);
DialogDelegate::SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
set_corner_radius(48);
SetBackgroundColor(kColorLensOverlayToastBackground);
SetProperty(views::kElementIdentifierKey, kLensPreselectionBubbleElementId);
SetAccessibleWindowRole(ax::mojom::Role::kAlertDialog);
SetCancelCallback(std::move(on_cancel_callback));
}
LensPreselectionBubble::~LensPreselectionBubble() = default;
void LensPreselectionBubble::Init() {
views::BoxLayout* layout =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets()));
offline_ ? set_margins(gfx::Insets::TLBR(6, 16, 6, 6))
: set_margins(gfx::Insets::TLBR(12, 16, 12, 16));
// Set bubble icon and text
const std::u16string online_toast_text = l10n_util::GetStringUTF16(
IDS_LENS_OVERLAY_INITIAL_TOAST_MESSAGE_SIMPLIFIED);
const std::u16string toast_text =
offline_ ? l10n_util::GetStringUTF16(
IDS_LENS_OVERLAY_INITIAL_TOAST_ERROR_MESSAGE)
: online_toast_text;
SetAccessibleTitle(toast_text);
icon_view_ = AddChildView(std::make_unique<views::ImageView>());
label_ = AddChildView(std::make_unique<views::Label>(toast_text));
label_->SetMultiLine(false);
label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label_->SetAllowCharacterBreak(false);
if (lens::IsLensOverlayContextualSearchboxEnabled()) {
auto button = views::CreateVectorImageButtonWithNativeTheme(
base::RepeatingClosure(), kHelpMenuIcon, 20,
kColorLensOverlayToastForeground, kColorLensOverlayToastForeground);
views::HighlightPathGenerator::Install(
button.get(),
std::make_unique<views::CircleHighlightPathGenerator>(gfx::Insets()));
button->SetTooltipText(
l10n_util::GetStringUTF16(IDS_LENS_OVERLAY_MORE_OPTIONS_BUTTON_LABEL));
more_info_button_ = AddChildView(std::move(button));
more_info_button_->SetButtonController(
std::make_unique<views::MenuButtonController>(
more_info_button_,
base::BindRepeating(&LensPreselectionBubble::OpenMoreInfoMenu,
base::Unretained(this)),
std::make_unique<views::Button::DefaultButtonControllerDelegate>(
more_info_button_)));
}
layout->set_between_child_spacing(8);
// Need to set this false so label color token doesn't get changed by
// changed by SetEnabledColor() color mapper. Color tokens provided
// have enough contrast.
label_->SetAutoColorReadabilityEnabled(false);
if (offline_) {
exit_button_ = AddChildView(std::make_unique<views::MdTextButton>(
std::move(exit_clicked_callback_),
l10n_util::GetStringUTF16(
IDS_LENS_OVERLAY_INITIAL_TOAST_ERROR_EXIT_BUTTON_TEXT)));
exit_button_->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(0, 8, 0, 0));
exit_button_->SetPreferredSize(gfx::Size(55, 36));
exit_button_->SetStyle(ui::ButtonStyle::kProminent);
exit_button_->SetProperty(views::kElementIdentifierKey,
kLensPreselectionBubbleExitButtonElementId);
}
NotifyAccessibilityEventDeprecated(ax::mojom::Event::kAlert, true);
}
void LensPreselectionBubble::SetLabelText(int string_id) {
// If the bubble had offline state, we don't want to reset the text.
if (offline_) {
return;
}
const std::u16string new_toast_text = l10n_util::GetStringUTF16(string_id);
SetAccessibleTitle(new_toast_text);
label_->SetText(new_toast_text);
SizeToContents();
}
gfx::Rect LensPreselectionBubble::GetBubbleBounds() {
views::View* anchor_view = GetAnchorView();
if (!anchor_view) {
return gfx::Rect();
}
const bool is_tab_strip_visible = lens_overlay_controller_->GetTabInterface()
->GetBrowserWindowInterface()
->IsTabStripVisible();
const gfx::Size bubble_size =
GetWidget()->GetContentsView()->GetPreferredSize();
const gfx::Rect anchor_bounds = anchor_view->GetBoundsInScreen();
const int x =
anchor_bounds.x() + (anchor_bounds.width() - bubble_size.width()) / 2;
// Take bubble out of its original bounds to cross "line of death". Since, the
// preselection bubble is anchored to the overlay, the line of death is above
// the top of the anchor bounds. However, if not tab strip is visible, and
// therefore there is no line of death to cross, we instead want to set the
// preselection bubble to be kPreselectionBubbleMinY from the top of the
// overlay.
const int y = is_tab_strip_visible
? anchor_bounds.y() - bubble_size.height() / 2
: anchor_bounds.y() + kPreselectionBubbleMinY;
return gfx::Rect(x, y, bubble_size.width(), bubble_size.height());
}
void LensPreselectionBubble::OnThemeChanged() {
BubbleDialogDelegateView::OnThemeChanged();
const auto* color_provider = GetColorProvider();
icon_view_->SetImage(ui::ImageModel::FromVectorIcon(
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
offline_ ? vector_icons::kErrorOutlineIcon
: vector_icons::kGoogleLensMonochromeLogoIcon,
#else
offline_ ? vector_icons::kErrorOutlineIcon
: vector_icons::kSearchChromeRefreshIcon,
#endif
color_provider->GetColor(kColorLensOverlayToastForeground),
/*icon_size=*/24));
label_->SetEnabledColor(
color_provider->GetColor(kColorLensOverlayToastForeground));
if (offline_) {
CHECK(exit_button_);
exit_button_->SetEnabledTextColors(
color_provider->GetColor(kColorLensOverlayToastForeground));
exit_button_->SetBorder(views::CreateRoundedRectBorder(
1, 48, color_provider->GetColor(kColorLensOverlayToastButtonBorder)));
exit_button_->SetBgColorIdOverride(kColorLensOverlayToastBackground);
}
}
void LensPreselectionBubble::OpenMoreInfoMenu() {
auto menu_model = std::make_unique<ui::SimpleMenuModel>(this);
menu_model->AddItem(COMMAND_MY_ACTIVITY,
l10n_util::GetStringUTF16(IDS_LENS_OVERLAY_MY_ACTIVITY));
menu_model->AddItem(COMMAND_LEARN_MORE,
l10n_util::GetStringUTF16(IDS_LENS_OVERLAY_LEARN_MORE));
menu_model->AddItem(COMMAND_SEND_FEEDBACK,
l10n_util::GetStringUTF16(IDS_LENS_SEND_FEEDBACK));
more_info_menu_model_ = std::move(menu_model);
menu_runner_ = std::make_unique<views::MenuRunner>(
more_info_menu_model_.get(), views::MenuRunner::HAS_MNEMONICS);
menu_runner_->RunMenuAt(more_info_button_->GetWidget(),
static_cast<views::MenuButtonController*>(
more_info_button_->button_controller()),
more_info_button_->GetAnchorBoundsInScreen(),
views::MenuAnchorPosition::kTopRight,
ui::mojom::MenuSourceType::kNone);
}
void LensPreselectionBubble::ExecuteCommand(int command_id, int event_flags) {
CHECK(lens_overlay_controller_);
switch (command_id) {
case COMMAND_MY_ACTIVITY: {
ActivityRequestedByEvent(lens_overlay_controller_->GetTabInterface(),
event_flags);
break;
}
case COMMAND_LEARN_MORE: {
InfoRequestedByEvent(lens_overlay_controller_->GetTabInterface(),
event_flags);
break;
}
case COMMAND_SEND_FEEDBACK: {
FeedbackRequestedByEvent(lens_overlay_controller_->GetTabInterface(),
event_flags);
break;
}
default: {
NOTREACHED() << "Unknown option";
}
}
}
BEGIN_METADATA(LensPreselectionBubble)
END_METADATA
} // namespace lens