diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index 3d5ae0891386..65c151ff55d9 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -578,6 +578,13 @@ Or change later at $2brave://settings/ext Dismiss Brave search conversion, press Enter to remove this suggestion + + Switch to Brave Search + + + Switch to Brave Search + + Help improve $1Brave Search by sending anonymous usage data. $2More info diff --git a/app/theme/brave_theme_resources.grd b/app/theme/brave_theme_resources.grd index 11ac016ddfe5..c2ab1c19414e 100644 --- a/app/theme/brave_theme_resources.grd +++ b/app/theme/brave_theme_resources.grd @@ -1,4 +1,4 @@ - + @@ -105,6 +105,7 @@ + diff --git a/app/theme/default_100_percent/brave/brave_search_conversion_button_brave_search_icon.png b/app/theme/default_100_percent/brave/brave_search_conversion_button_brave_search_icon.png new file mode 100644 index 000000000000..63b0667d2ea2 Binary files /dev/null and b/app/theme/default_100_percent/brave/brave_search_conversion_button_brave_search_icon.png differ diff --git a/app/theme/default_200_percent/brave/brave_search_conversion_button_brave_search_icon.png b/app/theme/default_200_percent/brave/brave_search_conversion_button_brave_search_icon.png new file mode 100644 index 000000000000..0ada49e32326 Binary files /dev/null and b/app/theme/default_200_percent/brave/brave_search_conversion_button_brave_search_icon.png differ diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 887ba35e6c43..51b2d78501c0 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -1084,6 +1084,10 @@ source_set("ui") { "views/frame/vertical_tab_strip_widget_delegate_view.h", "views/location_bar/brave_location_bar_view.cc", "views/location_bar/brave_location_bar_view.h", + "views/location_bar/brave_search_conversion/promotion_button_controller.cc", + "views/location_bar/brave_search_conversion/promotion_button_controller.h", + "views/location_bar/brave_search_conversion/promotion_button_view.cc", + "views/location_bar/brave_search_conversion/promotion_button_view.h", "views/location_bar/brave_star_view.cc", "views/location_bar/brave_star_view.h", "views/profiles/brave_avatar_toolbar_button.cc", diff --git a/browser/ui/color/brave_color_id.h b/browser/ui/color/brave_color_id.h index 97329bca5ef1..c7be6b88b797 100644 --- a/browser/ui/color/brave_color_id.h +++ b/browser/ui/color/brave_color_id.h @@ -39,7 +39,16 @@ E_CPONLY(kColorSearchConversionBannerTypeBackgroundBorderHovered) \ E_CPONLY(kColorSearchConversionBannerTypeBackgroundGradientFrom) \ E_CPONLY(kColorSearchConversionBannerTypeBackgroundGradientTo) \ - E_CPONLY(kColorSearchConversionBannerTypeDescText) + E_CPONLY(kColorSearchConversionBannerTypeDescText) \ + E_CPONLY(kColorSearchConversionButtonBorder) \ + E_CPONLY(kColorSearchConversionButtonBackground) \ + E_CPONLY(kColorSearchConversionButtonBackgroundHovered) \ + E_CPONLY(kColorSearchConversionButtonText) \ + E_CPONLY(kColorSearchConversionButtonCaratRight) \ + E_CPONLY(kColorSearchConversionButtonCloseButton) \ + E_CPONLY(kColorSearchConversionButtonCloseButtonHovered) \ + E_CPONLY(kColorSearchConversionButtonShadow1) \ + E_CPONLY(kColorSearchConversionButtonShadow2) #define BRAVE_SIDEBAR_COLOR_IDS \ E_CPONLY(kColorSidebarAddBubbleBackground) \ diff --git a/browser/ui/color/brave_color_mixer.cc b/browser/ui/color/brave_color_mixer.cc index dd0ad324deff..68cd73c94d0e 100644 --- a/browser/ui/color/brave_color_mixer.cc +++ b/browser/ui/color/brave_color_mixer.cc @@ -312,6 +312,26 @@ void AddBraveColorMixerForAllThemes(ui::ColorProvider* provider, mixer[kColorToolbarButtonActivated] = {SkColorSetRGB(0x7C, 0x91, 0xFF)}; mixer[kColorSidebarButtonPressed] = {kColorToolbarButtonActivated}; mixer[kColorToolbarContentAreaSeparator] = {kColorToolbar}; + + // Search conversion button in omnibox. + mixer[kColorSearchConversionButtonText] = {nala::kColorPrimary60}; + mixer[kColorSearchConversionButtonBorder] = {nala::kColorPrimary20}; + mixer[kColorSearchConversionButtonBackground] = {nala::kColorPrimary10}; + mixer[kColorSearchConversionButtonBackgroundHovered] = { + nala::kColorPrimary20}; + mixer[kColorSearchConversionButtonText] = {nala::kColorPrimary60}; + mixer[kColorSearchConversionButtonCloseButton] = { + ui::AlphaBlend({nala::kColorIconInteractive}, + {kColorSearchConversionButtonBackground}, 0.5 * 0xff)}; + mixer[kColorSearchConversionButtonCloseButtonHovered] = { + ui::AlphaBlend({nala::kColorIconInteractive}, + {kColorSearchConversionButtonBackground}, 0.7 * 0xff)}; + mixer[kColorSearchConversionButtonCaratRight] = { + kColorSearchConversionButtonCloseButton}; + mixer[kColorSearchConversionButtonShadow1] = { + SkColorSetA(SK_ColorBLACK, 0.05 * 255)}; + mixer[kColorSearchConversionButtonShadow2] = { + SkColorSetA(SK_ColorBLACK, 0.1 * 255)}; } } // namespace @@ -361,6 +381,7 @@ void AddBraveLightThemeColorMixer(ui::ColorProvider* provider, PickColorContrastingToToolbar(key, mixer, SkColorSetRGB(0x49, 0x50, 0x57), SkColorSetRGB(0xFF, 0xFF, 0xFF))}; mixer[kColorMenuItemSubText] = {SkColorSetRGB(0x86, 0x8E, 0x96)}; + // It's "Themeable/Blue/10" but leo/color.h doesn't have it. mixer[kColorSearchConversionBannerTypeBackground] = { SkColorSetRGB(0xEA, 0xF1, 0xFF)}; diff --git a/browser/ui/views/location_bar/brave_location_bar_view.cc b/browser/ui/views/location_bar/brave_location_bar_view.cc index 149130c6e1d7..ac214ed8c759 100644 --- a/browser/ui/views/location_bar/brave_location_bar_view.cc +++ b/browser/ui/views/location_bar/brave_location_bar_view.cc @@ -17,8 +17,11 @@ #include "brave/browser/ui/tabs/features.h" #include "brave/browser/ui/views/brave_actions/brave_actions_container.h" #include "brave/browser/ui/views/brave_news/brave_news_action_icon_view.h" +#include "brave/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.h" +#include "brave/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.h" #include "brave/browser/ui/views/playlist/playlist_action_icon_view.h" #include "brave/browser/ui/views/toolbar/brave_toolbar_view.h" +#include "brave/components/brave_search_conversion/features.h" #include "brave/components/commander/common/buildflags/buildflags.h" #include "brave/components/l10n/common/localization_util.h" #include "brave/grit/brave_theme_resources.h" @@ -125,6 +128,13 @@ void BraveLocationBarView::Init() { AddChildView(std::make_unique(browser_->profile())); #endif + if (base::FeatureList::IsEnabled( + brave_search_conversion::features::kOmniboxPromotionButton)) { + promotion_button_ = AddChildView(std::make_unique()); + promotion_controller_ = std::make_unique( + promotion_button_, omnibox_view_, browser()); + } + // brave action buttons brave_actions_ = AddChildView( std::make_unique(browser_, profile())); @@ -206,6 +216,13 @@ void BraveLocationBarView::OnChanged() { brave_news_action_icon_view_->Update(); } + if (promotion_controller_) { + const bool show_button = + promotion_controller_->ShouldShowSearchPromotionButton() && + !ShouldChipOverrideLocationIcon() && !ShouldShowKeywordBubble(); + promotion_controller_->Show(show_button); + } + // OnChanged calls Layout LocationBarView::OnChanged(); } @@ -234,6 +251,10 @@ std::vector BraveLocationBarView::GetLeftMostTrailingViews() { return views; } +views::View* BraveLocationBarView::GetSearchPromotionButton() const { + return promotion_button_; +} + void BraveLocationBarView::RefreshBackground() { LocationBarView::RefreshBackground(); diff --git a/browser/ui/views/location_bar/brave_location_bar_view.h b/browser/ui/views/location_bar/brave_location_bar_view.h index ab3fba36b650..0fb707e1fea1 100644 --- a/browser/ui/views/location_bar/brave_location_bar_view.h +++ b/browser/ui/views/location_bar/brave_location_bar_view.h @@ -19,6 +19,8 @@ class BraveActionsContainer; class BraveActionsContainerTest; +class PromotionButtonController; +class PromotionButtonView; class PlaylistActionIconView; class RewardsBrowserTest; class SkPath; @@ -70,6 +72,7 @@ class BraveLocationBarView : public LocationBarView { std::vector GetRightMostTrailingViews() override; // Views that locates at left side of upstream's trailing views. std::vector GetLeftMostTrailingViews() override; + views::View* GetSearchPromotionButton() const override; void RefreshBackground() override; void OnOmniboxBlurred() override; @@ -113,6 +116,8 @@ class BraveLocationBarView : public LocationBarView { std::unique_ptr shadow_; raw_ptr brave_actions_ = nullptr; raw_ptr brave_news_action_icon_view_ = nullptr; + std::unique_ptr promotion_controller_; + raw_ptr promotion_button_ = nullptr; #if BUILDFLAG(ENABLE_TOR) raw_ptr onion_location_view_ = nullptr; #endif diff --git a/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.cc b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.cc new file mode 100644 index 000000000000..d82a8c6f759c --- /dev/null +++ b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.cc @@ -0,0 +1,211 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.h" + +#include "base/functional/bind.h" +#include "brave/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.h" +#include "brave/components/ai_chat/core/common/buildflags/buildflags.h" +#include "brave/components/brave_search_conversion/pref_names.h" +#include "brave/components/brave_search_conversion/utils.h" +#include "brave/components/omnibox/browser/leo_provider.h" +#include "brave/components/omnibox/browser/promotion_utils.h" +#include "brave/components/search_engines/brave_prepopulated_engines.h" +#include "chrome/browser/image_fetcher/image_fetcher_service_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_key.h" +#include "chrome/browser/search_engines/template_url_service_factory.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_navigator.h" +#include "chrome/browser/ui/browser_navigator_params.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/views/omnibox/omnibox_view_views.h" +#include "components/image_fetcher/core/image_fetcher.h" +#include "components/image_fetcher/core/image_fetcher_service.h" +#include "components/omnibox/browser/autocomplete_match.h" +#include "components/omnibox/browser/omnibox_edit_model.h" +#include "components/omnibox/browser/omnibox_view.h" +#include "components/search_engines/template_url_data_util.h" +#include "components/search_engines/template_url_service.h" +#include "ui/base/page_transition_types.h" +#include "url/gurl.h" + +namespace { + +constexpr char kImageFetcherUmaClientName[] = "SearchPromotionButtonFavicon"; + +constexpr net::NetworkTrafficAnnotationTag + kSearchPromotionButtonTrafficAnnotation = + net::DefineNetworkTrafficAnnotation("search_promotion", R"( + semantics { + sender: "PromotionButtonController" + description: + "Fetches favicon for current search provider" + trigger: + "When current search provider is changed" + data: "URL of the favicon image to be fetched." + destination: WEBSITE + } + policy { + cookies_allowed: NO + setting: "Disabled when the user dismissed." + })"); + +} // namespace + +PromotionButtonController::PromotionButtonController( + PromotionButtonView* button, + OmniboxViewViews* omnibox_view, + Browser* browser) + : button_(button), + omnibox_view_(omnibox_view), + prefs_(*browser->profile()->GetPrefs()), + browser_(*browser) { + CHECK(button_ && omnibox_view); + button_->SetDismissedCallback(base::BindOnce( + &PromotionButtonController::Dismissed, weak_factory_.GetWeakPtr())); + button_->SetMakeDefaultCallback(base::BindOnce( + &PromotionButtonController::SetDefaultAndLoadBraveSearchWithCurrentInput, + weak_factory_.GetWeakPtr())); + template_url_service_ = + TemplateURLServiceFactory::GetForProfile(browser_->profile()); + is_brave_search_default_ = IsBraveSearchDefault(); + view_observation_.AddObservation(button_); + view_observation_.AddObservation(omnibox_view_); + UpdateButtonUI(); + template_url_service_observation_.Observe(template_url_service_); +} + +PromotionButtonController::~PromotionButtonController() { + button_ = nullptr; + omnibox_view_ = nullptr; +} + +void PromotionButtonController::Show(bool show) { + button_->SetVisible(show); + + // For now, animation is triggered only once before chaning search provider. + // Showing animation whenever this button is visible is too much. + // TODO(simonhong): adjust how frequently this animation is used. + if (use_animation_ && show) { + button_->AnimateExpand(); + use_animation_ = false; + } +} + +bool PromotionButtonController::ShouldShowSearchPromotionButton() { + if (!button_ || !omnibox_view_) { + return false; + } + + if (is_brave_search_default_) { + return false; + } + + if (prefs_->GetBoolean(brave_search_conversion::prefs::kDismissed)) { + return false; + } + + if (!omnibox_view_->model()->PopupIsOpen() || + GURL(omnibox_view_->GetText()).is_valid()) { + return false; + } + + const AutocompleteMatch match = omnibox_view_->model()->CurrentMatch(nullptr); + return !IsBraveSearchPromotionMatch(match) && +#if BUILDFLAG(ENABLE_AI_CHAT) + !LeoProvider::IsMatchFromLeoProvider(match) && +#endif + AutocompleteMatch::IsSearchType(match.type); +} + +void PromotionButtonController::OnViewIsDeleting(views::View* observed_view) { + // If any observed view among butto or omnibox is destroying, + // this controller will not do anything after that. + view_observation_.RemoveAllObservations(); + button_ = nullptr; + omnibox_view_ = nullptr; +} + +void PromotionButtonController::OnTemplateURLServiceChanged() { + use_animation_ = true; + is_brave_search_default_ = IsBraveSearchDefault(); + if (is_brave_search_default_) { + return; + } + + UpdateButtonUI(); +} + +void PromotionButtonController::OnTemplateURLServiceShuttingDown() { + template_url_service_observation_.Reset(); +} + +void PromotionButtonController::SetDefaultAndLoadBraveSearchWithCurrentInput() { + CHECK(omnibox_view_); + + // Set brave search as default. + auto provider_data = TemplateURLDataFromPrepopulatedEngine( + TemplateURLPrepopulateData::brave_search); + TemplateURL template_url(*provider_data); + template_url_service_->SetUserSelectedDefaultSearchProvider(&template_url); + + // Load brave search with current input. + const auto url = + template_url_service_->GenerateSearchURLForDefaultSearchProvider( + omnibox_view_->GetText()); + + NavigateParams params(&*browser_, url, ui::PAGE_TRANSITION_TYPED); + params.disposition = WindowOpenDisposition::CURRENT_TAB; + Navigate(¶ms); +} + +void PromotionButtonController::Dismissed() { + button_->SetVisible(false); + brave_search_conversion::SetDismissed(&*prefs_); + + // After dismissed, we don't need to monitor search provider changes. + template_url_service_observation_.Reset(); +} + +void PromotionButtonController::UpdateButtonUI() { + if (!template_url_service_->loaded()) { + return; + } + + const auto* template_url = template_url_service_->GetDefaultSearchProvider(); + auto* service = ImageFetcherServiceFactory::GetForKey( + browser_->profile()->GetProfileKey()); + CHECK(service); + auto* fetcher = service->GetImageFetcher( + image_fetcher::ImageFetcherConfig::kDiskCacheOnly); + image_fetcher::ImageFetcherParams params( + kSearchPromotionButtonTrafficAnnotation, kImageFetcherUmaClientName); + fetcher->FetchImage( + template_url->favicon_url(), + base::BindOnce(&PromotionButtonController::OnGetFaviconImage, + weak_factory_.GetWeakPtr()), + params); +} + +bool PromotionButtonController::IsBraveSearchDefault() { + const auto* template_url = template_url_service_->GetDefaultSearchProvider(); + if (template_url->prepopulate_id() == + TemplateURLPrepopulateData::PREPOPULATED_ENGINE_ID_BRAVE || + template_url->prepopulate_id() == + TemplateURLPrepopulateData::PREPOPULATED_ENGINE_ID_BRAVE_TOR) { + return true; + } + + return false; +} + +void PromotionButtonController::OnGetFaviconImage( + const gfx::Image& image, + const image_fetcher::RequestMetadata& request_metadata) { + if (!image.IsEmpty()) { + button_->UpdateTargetProviderImage(image); + } +} diff --git a/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.h b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.h new file mode 100644 index 000000000000..daea08a30eea --- /dev/null +++ b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.h @@ -0,0 +1,71 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_BRAVE_SEARCH_CONVERSION_PROMOTION_BUTTON_CONTROLLER_H_ +#define BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_BRAVE_SEARCH_CONVERSION_PROMOTION_BUTTON_CONTROLLER_H_ + +#include "base/memory/raw_ptr.h" +#include "base/memory/raw_ref.h" +#include "base/memory/weak_ptr.h" +#include "base/scoped_multi_source_observation.h" +#include "base/scoped_observation.h" +#include "components/search_engines/template_url_service_observer.h" +#include "ui/views/view_observer.h" + +class Browser; +class OmniboxViewViews; +class PrefService; +class PromotionButtonView; +class TemplateURLService; + +namespace gfx { +class Image; +} // namespace gfx + +namespace image_fetcher { +struct RequestMetadata; +} // namespace image_fetcher + +class PromotionButtonController : public views::ViewObserver, + public TemplateURLServiceObserver { + public: + PromotionButtonController(PromotionButtonView* button, + OmniboxViewViews* omnibox_view, + Browser* browser); + ~PromotionButtonController() override; + + bool ShouldShowSearchPromotionButton(); + void Show(bool show); + + private: + // views::ViewObserver overrides: + void OnViewIsDeleting(views::View* observed_view) override; + + // TemplateURLServiceObserver overrides: + void OnTemplateURLServiceChanged() override; + void OnTemplateURLServiceShuttingDown() override; + bool IsBraveSearchDefault(); + void SetDefaultAndLoadBraveSearchWithCurrentInput(); + void Dismissed(); + void UpdateButtonUI(); + void OnGetFaviconImage( + const gfx::Image& image, + const image_fetcher::RequestMetadata& request_metadata); + + bool use_animation_ = false; + bool is_brave_search_default_ = false; + raw_ptr button_ = nullptr; + raw_ptr omnibox_view_ = nullptr; + raw_ptr template_url_service_ = nullptr; + raw_ref prefs_; + raw_ref browser_; + base::ScopedMultiSourceObservation + view_observation_{this}; + base::ScopedObservation + template_url_service_observation_{this}; + base::WeakPtrFactory weak_factory_{this}; +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_BRAVE_SEARCH_CONVERSION_PROMOTION_BUTTON_CONTROLLER_H_ diff --git a/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.cc b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.cc new file mode 100644 index 000000000000..684c9d3369c0 --- /dev/null +++ b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.cc @@ -0,0 +1,269 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.h" + +#include + +#include "base/functional/bind.h" +#include "base/functional/callback.h" +#include "brave/browser/ui/color/brave_color_id.h" +#include "brave/components/vector_icons/vector_icons.h" +#include "brave/grit/brave_generated_resources.h" +#include "brave/grit/brave_theme_resources.h" +#include "chrome/browser/ui/layout_constants.h" +#include "ui/base/cursor/mojom/cursor_type.mojom.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/resource/resource_bundle.h" +#include "ui/gfx/animation/slide_animation.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_util.h" +#include "ui/views/background.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/painter.h" + +using views::BoxLayout; + +namespace { +constexpr int kIconSize = 16; + +// Subclass for using different cursor type. +class CustomImageView : public views::ImageView { + METADATA_HEADER(CustomImageView, views::ImageView) + + public: + using views::ImageView::ImageView; + + // views::ImageView overrides: + ui::Cursor GetCursor(const ui::MouseEvent& event) override { + return ui::mojom::CursorType::kHand; + } +}; + +BEGIN_METADATA(CustomImageView) +END_METADATA + +class CustomImageButton : public views::ImageButton { + METADATA_HEADER(CustomImageButton, views::ImageButton) + + public: + using views::ImageButton::ImageButton; + + // views::ImageButton overrides: + ui::Cursor GetCursor(const ui::MouseEvent& event) override { + return ui::mojom::CursorType::kHand; + } +}; + +BEGIN_METADATA(CustomImageButton) +END_METADATA + +base::TimeDelta GetAnimationDuration(base::TimeDelta duration) { + return gfx::Animation::ShouldRenderRichAnimation() ? duration + : base::TimeDelta(); +} + +} // namespace + +PromotionButtonView::PromotionButtonView() { + // Hovering on close button should not make this as normal state. + SetNotifyEnterExitOnChild(true); + + // Unretained here is safe as this callback is used by itself. + SetCallback(base::BindOnce(&PromotionButtonView::OnButtonPressed, + base::Unretained(this))); + SetLayoutManager( + std::make_unique(BoxLayout::Orientation::kHorizontal, + gfx::Insets::VH(0, 6), + /*between_child_spacing*/ 4)) + ->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kCenter); + SetTooltipText( + l10n_util::GetStringUTF16(IDS_BRAVE_SEARCH_CONVERSION_BUTTON_TOOLTIP)); + AddChildViews(); + animation_ = std::make_unique(this); + animation_->SetSlideDuration(GetAnimationDuration(base::Milliseconds(250))); + Update(); +} + +PromotionButtonView::~PromotionButtonView() = default; + +void PromotionButtonView::UpdateTargetProviderImage(const gfx::Image& image) { + target_provider_image_->SetImage( + gfx::ResizedImage(image, {kIconSize, kIconSize}).AsImageSkia()); +} + +void PromotionButtonView::AnimateExpand() { + animation_->Reset(); + animation_->Show(); +} + +void PromotionButtonView::SetDismissedCallback(base::OnceClosure callback) { + dismissed_callback_ = std::move(callback); +} + +void PromotionButtonView::SetMakeDefaultCallback(base::OnceClosure callback) { + make_default_callback_ = std::move(callback); +} + +void PromotionButtonView::StateChanged(views::Button::ButtonState old_state) { + Update(); +} + +void PromotionButtonView::OnThemeChanged() { + Button::OnThemeChanged(); + + SetupShadow(); + Update(); +} + +gfx::Size PromotionButtonView::CalculatePreferredSize( + const views::SizeBounds& available_size) const { + if (!animation_->is_animating()) { + return Button::CalculatePreferredSize(available_size); + } + + const auto size = GetLayoutManager()->GetPreferredSize(this); + const int target_width = size.width() * animation_->GetCurrentValue(); + return {target_width, size.height()}; +} + +ui::Cursor PromotionButtonView::GetCursor(const ui::MouseEvent& event) { + return ui::mojom::CursorType::kHand; +} + +void PromotionButtonView::AnimationProgressed(const gfx::Animation* animation) { + if (animation != animation_.get()) { + Button::AnimationProgressed(animation); + return; + } + + PreferredSizeChanged(); +} + +float PromotionButtonView::GetCornerRadius() const { + return GetLayoutConstant(LOCATION_BAR_CHILD_CORNER_RADIUS); +} + +void PromotionButtonView::UpdateBackgroundAndBorders() { + auto* cp = GetColorProvider(); + if (!cp) { + return; + } + + const auto bg_color = + cp->GetColor(GetState() == views::Button::STATE_NORMAL + ? kColorSearchConversionButtonBackground + : kColorSearchConversionButtonBackgroundHovered); + const auto stroke_color = cp->GetColor(kColorSearchConversionButtonBorder); + SetBackground(views::CreateBackgroundFromPainter( + views::Painter::CreateRoundRectWith1PxBorderPainter( + bg_color, stroke_color, GetCornerRadius(), SkBlendMode::kSrcOver, + true /* antialias */, true /* should_border_scale */))); +} + +void PromotionButtonView::AddChildViews() { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + target_provider_image_ = AddChildView(std::make_unique()); + target_provider_image_->SetPreferredSize(gfx::Size{kIconSize, kIconSize}); + + AddChildView(std::make_unique(ui::ImageModel::FromVectorIcon( + kLeoCaratLastIcon, kColorSearchConversionButtonCaratRight, 14))); + AddChildView(std::make_unique( + ui::ImageModel::FromImageSkia(*rb.GetImageSkiaNamed( + IDR_BRAVE_SEARCH_CONVERSION_BUTTON_BRAVE_SEARCH_ICON)))); + + auto title_font_list = + views::Label::GetDefaultFontList() + .DeriveWithWeight(gfx::Font::Weight::SEMIBOLD) + .DeriveWithStyle(gfx::Font::NORMAL) + .DeriveWithHeightUpperBound(18) + .DeriveWithSizeDelta( + 12 - views::Label::GetDefaultFontList().GetFontSize()); + views::Label::CustomFont custom_font{title_font_list}; + auto* button_label = AddChildView(std::make_unique( + l10n_util::GetStringUTF16(IDS_BRAVE_SEARCH_CONVERSION_BUTTON_LABEL), + custom_font)); + button_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT); + button_label->SetVerticalAlignment(gfx::VerticalAlignment::ALIGN_MIDDLE); + button_label->SetEnabledColorId(kColorSearchConversionButtonText); + button_label->SetBackgroundColor(SK_ColorTRANSPARENT); + + auto set_image = [](views::ImageButton* close_button, + views::Button::ButtonState state, int color_id) { + close_button->SetImageModel(state, ui::ImageModel::FromVectorIcon( + kLeoCloseCircleIcon, color_id, 16)); + }; + auto* close_button = AddChildView(std::make_unique()); + + // Unretained here is safe as |close_button| is the child of this class. + close_button->SetCallback(base::BindOnce(&PromotionButtonView::OnClosePressed, + base::Unretained(this))); + set_image(close_button, views::Button::STATE_NORMAL, + kColorSearchConversionButtonCloseButton); + set_image(close_button, views::Button::STATE_HOVERED, + kColorSearchConversionButtonCloseButtonHovered); + close_button->SetTooltipText(l10n_util::GetStringUTF16( + IDS_BRAVE_SEARCH_CONVERSION_CLOSE_BUTTON_TOOLTIP)); +} + +void PromotionButtonView::Update() { + UpdateBackgroundAndBorders(); + UpdateShadow(); +} + +void PromotionButtonView::SetupShadow() { + auto* cp = GetColorProvider(); + if (!cp) { + return; + } + + ViewShadow::ShadowParameters shadow_config1{ + .offset_x = 0, + .offset_y = 1, + .blur_radius = 0, + .shadow_color = cp->GetColor(kColorSearchConversionButtonShadow1)}; + + const int radius = GetCornerRadius(); + ViewShadow::ShadowParameters shadow_config2{ + .offset_x = 0, + .offset_y = 1, + .blur_radius = radius, + .shadow_color = cp->GetColor(kColorSearchConversionButtonShadow2)}; + + shadow1_ = std::make_unique(this, radius, shadow_config1); + shadow2_ = std::make_unique(this, radius, shadow_config2); +} + +void PromotionButtonView::UpdateShadow() { + if (!shadow1_ || !shadow2_) { + return; + } + + const bool is_hovered = GetState() == views::Button::STATE_HOVERED; + shadow1_->SetVisible(is_hovered); + shadow2_->SetVisible(is_hovered); +} + +void PromotionButtonView::OnButtonPressed() { + if (make_default_callback_) { + std::move(make_default_callback_).Run(); + } +} + +void PromotionButtonView::OnClosePressed() { + if (dismissed_callback_) { + std::move(dismissed_callback_).Run(); + } +} + +BEGIN_METADATA(PromotionButtonView) +END_METADATA diff --git a/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.h b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.h new file mode 100644 index 000000000000..1e2d145400b4 --- /dev/null +++ b/browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_BRAVE_SEARCH_CONVERSION_PROMOTION_BUTTON_VIEW_H_ +#define BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_BRAVE_SEARCH_CONVERSION_PROMOTION_BUTTON_VIEW_H_ + +#include + +#include "base/functional/callback_forward.h" +#include "base/memory/raw_ptr.h" +#include "brave/browser/ui/views/view_shadow.h" +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/views/controls/button/button.h" + +class ViewShadow; + +namespace gfx { +class Image; +class SlideAnimation; +} // namespace gfx + +namespace views { +class ImageView; +} // namespace views + +class PromotionButtonView : public views::Button { + METADATA_HEADER(PromotionButtonView, views::Button) + + public: + PromotionButtonView(); + ~PromotionButtonView() override; + + void SetDismissedCallback(base::OnceClosure callback); + void SetMakeDefaultCallback(base::OnceClosure callback); + void UpdateTargetProviderImage(const gfx::Image& image); + void AnimateExpand(); + + private: + // views::Button overrides: + void StateChanged(views::Button::ButtonState old_state) override; + void OnThemeChanged() override; + gfx::Size CalculatePreferredSize( + const views::SizeBounds& available_size) const override; + ui::Cursor GetCursor(const ui::MouseEvent& event) override; + void AnimationProgressed(const gfx::Animation* animation) override; + + void Update(); + + float GetCornerRadius() const; + void UpdateBackgroundAndBorders(); + void AddChildViews(); + void SetupShadow(); + void UpdateShadow(); + + void OnButtonPressed(); + void OnClosePressed(); + + raw_ptr target_provider_image_ = nullptr; + std::unique_ptr shadow1_; + std::unique_ptr shadow2_; + std::unique_ptr animation_; + base::OnceClosure dismissed_callback_; + base::OnceClosure make_default_callback_; +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_BRAVE_SEARCH_CONVERSION_PROMOTION_BUTTON_VIEW_H_ diff --git a/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.cc index 83b7b6073b55..df90ad90d673 100644 --- a/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.cc +++ b/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.cc @@ -36,6 +36,15 @@ add_trailing_decoration(item, /*intra_item_padding=*/0); \ } +#define BRAVE_LAYOUT_HANDLE_SEARCH_PROMOTION_BUTTON_VISIBILITY \ + } \ + else if (GetSearchPromotionButton() && \ + GetSearchPromotionButton()->GetVisible()) { \ + leading_decorations.AddDecoration(vertical_padding, location_height, \ + false, kLeadingDecorationMaxFraction, \ + /*intra_item_padding=*/0, 0, \ + GetSearchPromotionButton()); + #define OmniboxViewViews BraveOmniboxViewViews #define ChromeOmniboxClient BraveOmniboxClientImpl #define PageActionIconContainerView BravePageActionIconContainerView @@ -52,10 +61,15 @@ #undef PageActionIconContainerView #undef ChromeOmniboxClient #undef OmniboxViewViews +#undef BRAVE_LAYOUT_HANDLE_SEARCH_PROMOTION_BUTTON_VISIBILITY #undef BRAVE_LAYOUT_LEFT_MOST_TRAILING_DECORATIONS #undef BRAVE_LAYOUT_RIGHT_MOST_TRAILING_DECORATIONS #undef BRAVE_LAYOUT_LEADING_DECORATIONS +views::View* LocationBarView::GetSearchPromotionButton() const { + return nullptr; +} + std::vector LocationBarView::GetRightMostTrailingViews() { return std::vector(); } diff --git a/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.h b/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.h index 1264c5b2db2e..b83d079d39b8 100644 --- a/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.h +++ b/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view.h @@ -11,6 +11,7 @@ friend class BraveLocationBarView; \ \ public: \ + virtual views::View* GetSearchPromotionButton() const; \ virtual std::vector GetRightMostTrailingViews(); \ virtual std::vector GetLeftMostTrailingViews(); diff --git a/components/brave_search_conversion/features.cc b/components/brave_search_conversion/features.cc index 4505627adb98..da7443bb5a91 100644 --- a/components/brave_search_conversion/features.cc +++ b/components/brave_search_conversion/features.cc @@ -20,6 +20,10 @@ BASE_FEATURE(kOmniboxDDGBanner, "BraveSearchOmniboxDDGBanner", base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kOmniboxPromotionButton, + "BraveSearchPromotionOmniboxButton", + base::FEATURE_DISABLED_BY_DEFAULT); + // Brave search promotion at NTP. BASE_FEATURE(kNTP, "BraveSearchNTP", base::FEATURE_DISABLED_BY_DEFAULT); diff --git a/components/brave_search_conversion/features.h b/components/brave_search_conversion/features.h index 75ace97c0ef6..8b447e7155d8 100644 --- a/components/brave_search_conversion/features.h +++ b/components/brave_search_conversion/features.h @@ -20,6 +20,7 @@ BASE_DECLARE_FEATURE(kOmniboxBanner); extern const base::FeatureParam kBannerType; BASE_DECLARE_FEATURE(kOmniboxDDGBanner); +BASE_DECLARE_FEATURE(kOmniboxPromotionButton); BASE_DECLARE_FEATURE(kNTP); diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn index 7d8a7b945e29..48f81788b8c4 100644 --- a/components/vector_icons/BUILD.gn +++ b/components/vector_icons/BUILD.gn @@ -27,12 +27,14 @@ aggregate_vector_icons("brave_components_vector_icons") { "leo_browser_extensions_remove.icon", "leo_browser_sidebar_right.icon", "leo_browser_split_view_unsplit.icon", + "leo_carat_last.icon", "leo_carat_right.icon", "leo_check_circle_filled.icon", "leo_check_circle_outline.icon", "leo_chrome_cast.icon", "leo_clipboard.icon", "leo_close.icon", + "leo_close_circle.icon", "leo_code.icon", "leo_copy.icon", "leo_copy_plain_text.icon", diff --git a/patches/chrome-browser-ui-views-location_bar-location_bar_view.cc.patch b/patches/chrome-browser-ui-views-location_bar-location_bar_view.cc.patch index 00218bb3821f..64f4c36eadf3 100644 --- a/patches/chrome-browser-ui-views-location_bar-location_bar_view.cc.patch +++ b/patches/chrome-browser-ui-views-location_bar-location_bar_view.cc.patch @@ -1,5 +1,5 @@ diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc -index 61d32b558b7c49fa69989219b660bf2adf8fb90c..238b7d421119e21b15e95a86c789f5a2fed209c2 100644 +index 61d32b558b7c49fa69989219b660bf2adf8fb90c..25f77e4d8bf906b786f4da5efe91b6c1c3c8cac6 100644 --- a/chrome/browser/ui/views/location_bar/location_bar_view.cc +++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc @@ -715,6 +715,7 @@ void LocationBarView::Layout(PassKey) { @@ -10,7 +10,15 @@ index 61d32b558b7c49fa69989219b660bf2adf8fb90c..238b7d421119e21b15e95a86c789f5a2 if (should_indent) { icon_left += icon_indent; text_left += text_indent; -@@ -802,6 +803,7 @@ void LocationBarView::Layout(PassKey) { +@@ -778,6 +779,7 @@ void LocationBarView::Layout(PassKey) { + } + selected_keyword_view_->SetCustomImage(image); + } ++ BRAVE_LAYOUT_HANDLE_SEARCH_PROMOTION_BUTTON_VISIBILITY + } else if (location_icon_view_->GetShowText() && + !ShouldChipOverrideLocationIcon()) { + location_icon_view_->SetVisible(true); +@@ -802,6 +804,7 @@ void LocationBarView::Layout(PassKey) { } }; @@ -18,7 +26,7 @@ index 61d32b558b7c49fa69989219b660bf2adf8fb90c..238b7d421119e21b15e95a86c789f5a2 add_trailing_decoration(page_action_icon_container_, /*intra_item_padding=*/0); for (ContentSettingImageView* view : base::Reversed(content_setting_views_)) { -@@ -815,6 +817,7 @@ void LocationBarView::Layout(PassKey) { +@@ -815,6 +818,7 @@ void LocationBarView::Layout(PassKey) { } add_trailing_decoration(clear_all_button_, /*intra_item_padding=*/0);