From cad4c850a8bdc94894c5d20b3831a52c8d4c9edc Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Fri, 30 Aug 2024 22:39:33 +0900 Subject: [PATCH] Added search promotion button in omnibox fix https://github.com/brave/brave-browser/issues/40776 --- app/brave_generated_resources.grd | 7 + app/theme/brave_theme_resources.grd | 3 +- ...ch_conversion_button_brave_search_icon.png | Bin 0 -> 793 bytes ...ch_conversion_button_brave_search_icon.png | Bin 0 -> 1764 bytes browser/ui/BUILD.gn | 4 + browser/ui/color/brave_color_id.h | 11 +- browser/ui/color/brave_color_mixer.cc | 21 ++ .../location_bar/brave_location_bar_view.cc | 21 ++ .../location_bar/brave_location_bar_view.h | 5 + .../promotion_button_controller.cc | 211 ++++++++++++++ .../promotion_button_controller.h | 71 +++++ .../promotion_button_view.cc | 269 ++++++++++++++++++ .../promotion_button_view.h | 68 +++++ .../views/location_bar/location_bar_view.cc | 14 + .../ui/views/location_bar/location_bar_view.h | 1 + .../brave_search_conversion/features.cc | 4 + components/brave_search_conversion/features.h | 1 + components/vector_icons/BUILD.gn | 2 + ...ws-location_bar-location_bar_view.cc.patch | 14 +- 19 files changed, 722 insertions(+), 5 deletions(-) create mode 100644 app/theme/default_100_percent/brave/brave_search_conversion_button_brave_search_icon.png create mode 100644 app/theme/default_200_percent/brave/brave_search_conversion_button_brave_search_icon.png create mode 100644 browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.cc create mode 100644 browser/ui/views/location_bar/brave_search_conversion/promotion_button_controller.h create mode 100644 browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.cc create mode 100644 browser/ui/views/location_bar/brave_search_conversion/promotion_button_view.h 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 0000000000000000000000000000000000000000..63b0667d2ea29b2fee2cf096bae33d30582751f2 GIT binary patch literal 793 zcmV+!1LpjRP)9e%5)}ouL_vieQcwh88zn*!f;c^3hd&TsK?yN5 zp+UNcB0@?OM+gvAP6P>J``$9|&RD=nr+0qdot>SX75w)U=Bqg|{TYav7R9)`Qy>s% zV2xL6E>}B$$ICFWTuqzfT7ha=Rog1kg0=fh4`I9v0cHe}gJ|7$4{ObOJsbkW$;a#l zE2Y&&#m4SZfS*`F-vC_({IVf>s1+~WBRjLgn^1e-^A5x~09V;1UkJBn8=Z$42CtKM zfouwZEwD_9zZ;F0i`jFZw`Z9`wj-68LE&z7+*rG3ZIo|xmTn+o; zTfir_{=BX@f?vG4uxgCAVXes515tAX+wM0@oh6z@k%qH&^F~3m9o0SCUEbl}zZZ4dV&q_ft>(2pyjziam8C(uw%bJ_3S=__jBS&6eQD0r1vWoJtGcx82Y~*+E>yh{-yGr8HtM z22Qm)trO}rK|Zu|IKj%5^~5XJr<6iE&jf#rr-ErJ8W=^?at?DAPK0!VGIo=c@8p5o zd8dKIcLvRY-D6i;BemFx#rgYnAq#6v-g#ZG&9{*{J_`CT%<1T+;bo($~$x%OJi&!(OcK7r@=qVB%ZC3Ny(YSKw6PC zTk!g6PtBMeEE6lQx?Y%Aayu~u7y^-^M7GM$49RB)>)hetbJ1GPg@ySAE|u5dhPHnL X{7*Ml$$Wnf00000NkvXXu0mjfm26p= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0ada49e32326801e08f1213102452d1c97143d01 GIT binary patch literal 1764 zcmVW2shB2ru|RCLh77N% zaoIH;qveA|2mc3vmDpUzBVaDX$cnU3NsR;7o!3cYx)^R6Oc_doBrY8}wC~0L1i(sd zTz5vg=@w(IF}tH^uBxx3-rUgc0Rbl;wnnBM*OgMjp??g=u{NV#UjX4 z28)ffvkLukuuHVzN&9pO*c5f_NPGN+`1pTskQLfJbHTwsGLB11!i5yd@3XkcdeaW&UT;!fmp4t9-A^ z)t{X3os-v!2K4Giw9s&`TzMBQ!6T(~80?+icc5d%u zJS21Pi?99RcyrH*nQsh1=0YB#1-~nTuNg4nKUsgGF?n!rH3FblJ>vfU*35%qf`f8V zJ`{IZ9n%0|TISx7wflT>Yh-;>c(yCtDS-?OKaiPTBT;L;B4dsP2=R$IC46EE_{)hc zSzNd|-5OhytWKE3YT~#!BkQOZOxq~R~wk=VEsoo}YcwlXgO4L+ukr z&jWhY?Hu4SxLSPJ z5KVaFgtzH=xpu!h(Bj0#ey>1r=m&I1nMbp3kasi%a;GT;o03|vejsf3w!E6uc#@VG zJ2o=5ZIJZrAS_Z;S*nauQhLAO(dHV`oS*<~UJeQd>md1Ed&UzWO0uv+zJ`CblmpJ3 zA}pK>o!&Qr&8!9b5ff1w`CY@Yf+r};!itESrf9E)H=u%lpatSn0pVhUFq?-)cv`BW zuF1E7zF&elqv*qQv`h=M5_B|4NHNj^AwHc1aZK!bOzg@S^-Q8b?3HV%)Za?wQ;&z; z{H~GlDD$x331}kGY*{XzHXSRcm#FC_iQDyQy+QwAkiLmC&J7i_)}Mkqugu-%pj1TT z7@4+e#N>q2@&O)N=QtwA!m}#4P^vT!JG59Y7{3?Ky6T`Ov0000(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);