From 0c1d146556e91b9889ebe13ecb0853b8f143ddf5 Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Tue, 19 May 2026 13:13:51 +0900 Subject: [PATCH] [Sidebar V2] Make brave's panel header as `SidePanel`'s header (#36472) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar v2 attaches a Brave-styled panel header at the SidePanel level instead of embedding it inside the content view. ## Changes - New BraveSidePanelHeader (view) + BraveSidePanelHeaderController (delegate) under brave/browser/ui/views/side_panel/. Visual spec (title, optional launch button + separator, close button, 60px height, Nala colors, Leo icons) mirrors V1's inline headers in BraveReadLaterSidePanelView/BraveBookmarksSidePanelView. - BraveSidePanelCoordinator::PopulateSidePanel attaches the header via upstream SidePanel::AddHeaderView only for entries ShouldShowBraveHeader allows (today: kReadingList, kBookmarks). - SidePanel chromium_src override now wraps upstream AddHeaderView / RemoveHeaderView so each call also re-runs Brave's UpdateBorder(). UpdateBorder() reserves a top inset equal to the header's preferred height when a header is attached, in both the rounded-border and no-border states. - brave_side_panel_header_controller is split into its own :side_panel_impl source_set (depending on :side_panel) to keep dep chains clean. ## Test plan - New SidebarBrowserTest.SidebarV2BraveHeaderTest test case - Manual: run with enable_sidebar_v2=true, open reading list and bookmarks — header should be visible --- .../ui/browser_window/internal/sources.gni | 6 +- browser/ui/config.gni | 1 + browser/ui/sidebar/sidebar_browsertest.cc | 36 +++-- browser/ui/views/side_panel/BUILD.gn | 26 ++++ .../brave_bookmarks_side_panel_view.cc | 3 + .../brave_read_later_side_panel_view.cc | 3 + .../brave_side_panel_coordinator.cc | 25 ++++ .../side_panel/brave_side_panel_coordinator.h | 6 + .../side_panel/brave_side_panel_header.cc | 77 +++++++++++ .../side_panel/brave_side_panel_header.h | 48 +++++++ .../brave_side_panel_header_controller.cc | 124 ++++++++++++++++++ .../brave_side_panel_header_controller.h | 48 +++++++ .../internal/browser_window_features.cc | 13 +- .../reading_list_side_panel_coordinator.cc | 5 + .../browser/ui/views/side_panel/side_panel.cc | 29 ++-- .../browser/ui/views/side_panel/side_panel.h | 17 +-- 16 files changed, 434 insertions(+), 33 deletions(-) create mode 100644 browser/ui/views/side_panel/brave_side_panel_header.cc create mode 100644 browser/ui/views/side_panel/brave_side_panel_header.h create mode 100644 browser/ui/views/side_panel/brave_side_panel_header_controller.cc create mode 100644 browser/ui/views/side_panel/brave_side_panel_header_controller.h diff --git a/browser/ui/browser_window/internal/sources.gni b/browser/ui/browser_window/internal/sources.gni index eb2fc4f5ae3..83bca2030fe 100644 --- a/browser/ui/browser_window/internal/sources.gni +++ b/browser/ui/browser_window/internal/sources.gni @@ -3,5 +3,7 @@ # 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/. -brave_browser_window_internal_deps = - [ "//brave/browser/ui/browser_window/internal" ] +brave_browser_window_internal_deps = [ + "//brave/browser/ui/browser_window/internal", + "//brave/browser/ui/sidebar/buildflags", +] diff --git a/browser/ui/config.gni b/browser/ui/config.gni index 8a33df2f63c..63c92266691 100644 --- a/browser/ui/config.gni +++ b/browser/ui/config.gni @@ -56,6 +56,7 @@ if (toolkit_views) { "//brave/browser/ui/views/frame/vertical_tabs", "//brave/browser/ui/views/location_bar", "//brave/browser/ui/views/page_info", + "//brave/browser/ui/views/side_panel:side_panel_impl", ] if (enable_ai_chat) { diff --git a/browser/ui/sidebar/sidebar_browsertest.cc b/browser/ui/sidebar/sidebar_browsertest.cc index 695b5590455..baafe12a0ed 100644 --- a/browser/ui/sidebar/sidebar_browsertest.cc +++ b/browser/ui/sidebar/sidebar_browsertest.cc @@ -96,6 +96,7 @@ #endif #if BUILDFLAG(ENABLE_SIDEBAR_V2) +#include "brave/browser/ui/views/side_panel/brave_side_panel_header.h" #include "brave/browser/ui/views/side_panel/brave_side_panel_resize_area.h" #endif @@ -2150,30 +2151,41 @@ IN_PROC_BROWSER_TEST_F(SidebarBrowserTest, SidebarV2ActiveItemStateSync) { [&]() { return !model()->active_index().has_value(); })); } -// Verify that the upstream SidePanelHeader is never added when a sidebar panel -// is opened in V2, so Brave can render its own header. -IN_PROC_BROWSER_TEST_F(SidebarBrowserTest, SidebarV2NoUpstreamHeaderTest) { +// Verify the Brave-styled side panel header is attached for reading list and +// bookmarks and absent for other entries. +IN_PROC_BROWSER_TEST_F(SidebarBrowserTest, SidebarV2BraveHeaderTest) { auto* panel_ui = browser()->GetFeatures().side_panel_ui(); auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser()); auto* side_panel = browser_view->contents_height_side_panel(); side_panel->DisableAnimationsForTesting(); - panel_ui->Toggle(); - ASSERT_TRUE(base::test::RunUntil([&]() { return side_panel->GetVisible(); })); + // Reading list: Brave header attached. + panel_ui->Show(SidePanelEntryId::kReadingList); + ASSERT_TRUE(base::test::RunUntil([&]() { + return panel_ui->IsSidePanelEntryShowing( + SidePanelEntry::Key(SidePanelEntryId::kReadingList)); + })); + EXPECT_NE(nullptr, side_panel->GetHeaderView()) + << "BraveSidePanelHeader should be attached for the reading list panel"; - EXPECT_EQ(nullptr, side_panel->GetHeaderView()) - << "Upstream SidePanelHeader should not be present after V2 panel open"; + // Bookmarks: Brave header attached. + panel_ui->Show(SidePanelEntryId::kBookmarks); + ASSERT_TRUE(base::test::RunUntil([&]() { + return panel_ui->IsSidePanelEntryShowing( + SidePanelEntry::Key(SidePanelEntryId::kBookmarks)); + })); + EXPECT_NE(nullptr, side_panel->GetHeaderView()) + << "BraveSidePanelHeader should be attached for the bookmarks panel"; - // Also verify CustomizeChrome panel does not get an upstream header. + // CustomizeChrome: no Brave header (the previous one must be cleared when + // switching to an entry that doesn't request a Brave header). panel_ui->Show(SidePanelEntryId::kCustomizeChrome); ASSERT_TRUE(base::test::RunUntil([&]() { return panel_ui->IsSidePanelEntryShowing( SidePanelEntry::Key(SidePanelEntryId::kCustomizeChrome)); })); - - EXPECT_EQ(nullptr, side_panel->GetHeaderView()) - << "Upstream SidePanelHeader should not be present for CustomizeChrome " - "panel"; + EXPECT_EQ(nullptr, side_panel->GetHeaderView()) + << "BraveSidePanelHeader should not be attached for CustomizeChrome"; } // Verify that the resize area is positioned correctly for both border states. diff --git a/browser/ui/views/side_panel/BUILD.gn b/browser/ui/views/side_panel/BUILD.gn index f356e843918..c1a59d60406 100644 --- a/browser/ui/views/side_panel/BUILD.gn +++ b/browser/ui/views/side_panel/BUILD.gn @@ -23,8 +23,34 @@ source_set("side_panel") { if (enable_sidebar_v2) { sources += [ + "brave_side_panel_header.cc", + "brave_side_panel_header.h", + "brave_side_panel_header_controller.h", "brave_side_panel_resize_area.cc", "brave_side_panel_resize_area.h", ] } } + +source_set("side_panel_impl") { + sources = [] + deps = [] + + if (enable_sidebar_v2) { + sources += [ "brave_side_panel_header_controller.cc" ] + + deps += [ + ":side_panel", + "//base", + "//brave/app:brave_generated_resources_grit_grit", + "//brave/components/vector_icons", + "//chrome/app:generated_resources", + "//chrome/browser/ui/browser_window", + "//chrome/browser/ui/color:mixers", + "//chrome/browser/ui/side_panel:side_panel_views_dependent", + "//chrome/common", + "//ui/base", + "//ui/views", + ] + } +} diff --git a/browser/ui/views/side_panel/brave_bookmarks_side_panel_view.cc b/browser/ui/views/side_panel/brave_bookmarks_side_panel_view.cc index f93173b1cf1..0254ef40249 100644 --- a/browser/ui/views/side_panel/brave_bookmarks_side_panel_view.cc +++ b/browser/ui/views/side_panel/brave_bookmarks_side_panel_view.cc @@ -10,6 +10,7 @@ #include "base/check_op.h" #include "base/memory/raw_ref.h" #include "brave/browser/ui/color/brave_color_id.h" +#include "brave/browser/ui/sidebar/features.h" #include "brave/components/vector_icons/vector_icons.h" #include "brave/grit/brave_generated_resources.h" #include "brave/ui/color/nala/nala_color_id.h" @@ -141,6 +142,8 @@ END_METADATA BraveBookmarksSidePanelView::BraveBookmarksSidePanelView( SidePanelEntryScope& scope) { + CHECK(!base::FeatureList::IsEnabled(sidebar::features::kSidebarV2)); + CHECK_EQ(SidePanelEntryScope::ScopeType::kBrowser, scope.get_scope_type()); SetLayoutManager(std::make_unique()) ->SetOrientation(views::LayoutOrientation::kVertical); diff --git a/browser/ui/views/side_panel/brave_read_later_side_panel_view.cc b/browser/ui/views/side_panel/brave_read_later_side_panel_view.cc index 0a0fe658f3e..7578678b0c3 100644 --- a/browser/ui/views/side_panel/brave_read_later_side_panel_view.cc +++ b/browser/ui/views/side_panel/brave_read_later_side_panel_view.cc @@ -9,6 +9,7 @@ #include "base/functional/bind.h" #include "brave/browser/ui/color/brave_color_id.h" +#include "brave/browser/ui/sidebar/features.h" #include "brave/components/vector_icons/vector_icons.h" #include "brave/grit/brave_generated_resources.h" #include "brave/ui/color/nala/nala_color_id.h" @@ -112,6 +113,8 @@ BraveReadLaterSidePanelView::BraveReadLaterSidePanelView( TabStripModel* tab_strip_model, SidePanelEntryScope& scope, base::RepeatingClosure close_cb) { + CHECK(!base::FeatureList::IsEnabled(sidebar::features::kSidebarV2)); + SetLayoutManager(std::make_unique()) ->SetOrientation(views::LayoutOrientation::kVertical); AddChildView(std::make_unique(scope)); diff --git a/browser/ui/views/side_panel/brave_side_panel_coordinator.cc b/browser/ui/views/side_panel/brave_side_panel_coordinator.cc index 9e0ff81d35f..0c49c66b35a 100644 --- a/browser/ui/views/side_panel/brave_side_panel_coordinator.cc +++ b/browser/ui/views/side_panel/brave_side_panel_coordinator.cc @@ -26,6 +26,12 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/side_panel/side_panel_entry.h" +#include "chrome/browser/ui/views/side_panel/side_panel.h" + +#if BUILDFLAG(ENABLE_SIDEBAR_V2) +#include "brave/browser/ui/views/side_panel/brave_side_panel_header.h" +#include "brave/browser/ui/views/side_panel/brave_side_panel_header_controller.h" +#endif namespace { @@ -202,8 +208,27 @@ void BraveSidePanelCoordinator::PopulateSidePanel( SidePanelCoordinator::PopulateSidePanel(supress_animations, unique_key, std::move(open_trigger), entry, std::move(content_view)); + +#if BUILDFLAG(ENABLE_SIDEBAR_V2) + if (ShouldShowBraveHeader(entry)) { + auto* side_panel = GetSidePanelFor(entry->type()); + CHECK(side_panel); + side_panel->AddHeaderView(std::make_unique( + std::make_unique( + *browser_view_->browser(), entry))); + } +#endif } +#if BUILDFLAG(ENABLE_SIDEBAR_V2) +bool BraveSidePanelCoordinator::ShouldShowBraveHeader( + SidePanelEntry* entry) const { + const SidePanelEntry::Id id = entry->key().id(); + return id == SidePanelEntry::Id::kReadingList || + id == SidePanelEntry::Id::kBookmarks; +} +#endif + BraveBrowserView* BraveSidePanelCoordinator::GetBraveBrowserView() { return static_cast(browser_view_); } diff --git a/browser/ui/views/side_panel/brave_side_panel_coordinator.h b/browser/ui/views/side_panel/brave_side_panel_coordinator.h index 432e4bc495b..1efa19df502 100644 --- a/browser/ui/views/side_panel/brave_side_panel_coordinator.h +++ b/browser/ui/views/side_panel/brave_side_panel_coordinator.h @@ -9,6 +9,7 @@ #include #include +#include "brave/browser/ui/sidebar/buildflags/buildflags.h" #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h" #include "chrome/browser/ui/views/side_panel/side_panel_helper.h" @@ -52,6 +53,11 @@ class BraveSidePanelCoordinator : public SidePanelCoordinator { std::optional GetLastActiveEntryKey() const; void UpdateToolbarButtonHighlight(bool side_panel_visible); BraveBrowserView* GetBraveBrowserView(); + +#if BUILDFLAG(ENABLE_SIDEBAR_V2) + // Returns true when we want to show brave panel header view for |entry|. + bool ShouldShowBraveHeader(SidePanelEntry* entry) const; +#endif }; #endif // BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_BRAVE_SIDE_PANEL_COORDINATOR_H_ diff --git a/browser/ui/views/side_panel/brave_side_panel_header.cc b/browser/ui/views/side_panel/brave_side_panel_header.cc new file mode 100644 index 00000000000..f7fd83afc9e --- /dev/null +++ b/browser/ui/views/side_panel/brave_side_panel_header.cc @@ -0,0 +1,77 @@ +/* Copyright (c) 2026 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/side_panel/brave_side_panel_header.h" + +#include + +#include "base/check.h" +#include "brave/ui/color/nala/nala_color_id.h" +#include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/views/background.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/separator.h" +#include "ui/views/layout/flex_layout.h" +#include "ui/views/layout/flex_layout_types.h" +#include "ui/views/layout/layout_types.h" +#include "ui/views/view_class_properties.h" + +namespace { + +constexpr int kHeaderInteriorMargin = 16; +constexpr int kHeaderButtonSize = 20; +constexpr int kHeaderHeight = 60; +constexpr int kSeparatorHorizontalSpacing = 12; + +} // namespace + +BraveSidePanelHeader::BraveSidePanelHeader(std::unique_ptr delegate) + : delegate_(std::move(delegate)) { + CHECK(delegate_); + + SetBackground(views::CreateSolidBackground(nala::kColorPageBackground)); + SetLayoutManager(std::make_unique()) + ->SetOrientation(views::LayoutOrientation::kHorizontal) + .SetInteriorMargin(gfx::Insets(kHeaderInteriorMargin)) + .SetMainAxisAlignment(views::LayoutAlignment::kStart) + .SetCrossAxisAlignment(views::LayoutAlignment::kCenter); + + AddChildView(delegate_->CreatePanelTitle()); + + AddChildView(std::make_unique()) + ->SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kUnbounded) + .WithOrder(2)); + + if (auto launch_button = delegate_->CreateLaunchButton()) { + AddChildView(std::move(launch_button)); + auto* separator = AddChildView(std::make_unique()); + separator->SetColorId(nala::kColorDividerSubtle); + separator->SetPreferredLength(kHeaderButtonSize); + separator->SetProperty(views::kMarginsKey, + gfx::Insets::VH(0, kSeparatorHorizontalSpacing)); + } + + AddChildView(delegate_->CreateCloseButton()); +} + +BraveSidePanelHeader::~BraveSidePanelHeader() = default; + +void BraveSidePanelHeader::Layout(PassKey) { + LayoutSuperclass(this); + + // Need to set bounds as parent view(SidePanel) uses FillLayout. + const gfx::Rect contents_bounds = parent()->GetContentsBounds(); + SetBoundsRect(gfx::Rect(contents_bounds.x(), + contents_bounds.y() - kHeaderHeight, + contents_bounds.width(), kHeaderHeight)); +} + +BEGIN_METADATA(BraveSidePanelHeader) +END_METADATA diff --git a/browser/ui/views/side_panel/brave_side_panel_header.h b/browser/ui/views/side_panel/brave_side_panel_header.h new file mode 100644 index 00000000000..c24d651ee88 --- /dev/null +++ b/browser/ui/views/side_panel/brave_side_panel_header.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2026 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_SIDE_PANEL_BRAVE_SIDE_PANEL_HEADER_H_ +#define BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_BRAVE_SIDE_PANEL_HEADER_H_ + +#include + +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/views/view.h" + +namespace views { +class ImageButton; +class Label; +} // namespace views + +// Header view shown on top of the side panel border in sidebar v2. The visual +// spec mirrors the v1 inline headers in BraveReadLaterSidePanelView and +// BraveBookmarksSidePanelView; per-entry pieces are produced by a Delegate. +class BraveSidePanelHeader : public views::View { + METADATA_HEADER(BraveSidePanelHeader, views::View) + + public: + class Delegate { + public: + virtual ~Delegate() = default; + virtual std::unique_ptr CreatePanelTitle() = 0; + + // Return nullptr when the entry has no launch action (e.g. reading list). + virtual std::unique_ptr CreateLaunchButton() = 0; + virtual std::unique_ptr CreateCloseButton() = 0; + }; + + explicit BraveSidePanelHeader(std::unique_ptr delegate); + BraveSidePanelHeader(const BraveSidePanelHeader&) = delete; + BraveSidePanelHeader& operator=(const BraveSidePanelHeader&) = delete; + ~BraveSidePanelHeader() override; + + // views::View: + void Layout(PassKey) override; + + private: + std::unique_ptr delegate_; +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_BRAVE_SIDE_PANEL_HEADER_H_ diff --git a/browser/ui/views/side_panel/brave_side_panel_header_controller.cc b/browser/ui/views/side_panel/brave_side_panel_header_controller.cc new file mode 100644 index 00000000000..fcc3ec5ede7 --- /dev/null +++ b/browser/ui/views/side_panel/brave_side_panel_header_controller.cc @@ -0,0 +1,124 @@ +/* Copyright (c) 2026 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/side_panel/brave_side_panel_header_controller.h" + +#include +#include + +#include "base/functional/bind.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 "chrome/browser/ui/browser_window/public/browser_window_features.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/side_panel/side_panel_ui.h" +#include "chrome/browser/ui/singleton_tabs.h" +#include "chrome/common/webui_url_constants.h" +#include "chrome/grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/image_model.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_list.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/label.h" + +namespace { + +constexpr int kHeaderTitleFontSize = 16; +constexpr int kHeaderButtonSize = 20; + +std::unique_ptr CreateHeaderImageButton( + views::Button::PressedCallback callback, + const gfx::VectorIcon& icon, + int tooltip_id) { + auto button = std::make_unique(std::move(callback)); + button->SetTooltipText(l10n_util::GetStringUTF16(tooltip_id)); + button->SetImageModel( + views::Button::STATE_NORMAL, + ui::ImageModel::FromVectorIcon(icon, kColorSidebarPanelHeaderButton, + kHeaderButtonSize)); + button->SetImageModel( + views::Button::STATE_HOVERED, + ui::ImageModel::FromVectorIcon( + icon, kColorSidebarPanelHeaderButtonHovered, kHeaderButtonSize)); + return button; +} + +// Per-panel header titles. Mirrors V1's hardcoded labels in +// BraveReadLaterSidePanelView / BraveBookmarksSidePanelView; we deliberately +// don't read from the entry's action item because that text differs from the +// label V1 uses in the header. +std::u16string GetEntryTitle(SidePanelEntry::Id id) { + int message_id = 0; + switch (id) { + case SidePanelEntry::Id::kReadingList: + message_id = IDS_SIDEBAR_READING_LIST_PANEL_HEADER_TITLE; + break; + case SidePanelEntry::Id::kBookmarks: + message_id = IDS_BOOKMARK_MANAGER_TITLE; + break; + default: + return std::u16string(); + } + return l10n_util::GetStringUTF16(message_id); +} + +} // namespace + +BraveSidePanelHeaderController::BraveSidePanelHeaderController( + BrowserWindowInterface& browser_window, + SidePanelEntry* entry) + : browser_window_(browser_window), entry_(entry->GetWeakPtr()) {} + +BraveSidePanelHeaderController::~BraveSidePanelHeaderController() = default; + +std::unique_ptr +BraveSidePanelHeaderController::CreatePanelTitle() { + CHECK(entry_); + auto label = + std::make_unique(GetEntryTitle(entry_->key().id())); + const int size_delta = + kHeaderTitleFontSize - views::Label::GetDefaultFontList().GetFontSize(); + label->SetFontList(views::Label::GetDefaultFontList() + .DeriveWithSizeDelta(size_delta) + .DeriveWithWeight(gfx::Font::Weight::SEMIBOLD)); + label->SetEnabledColor(kColorSidebarPanelHeaderTitle); + label->SetAutoColorReadabilityEnabled(false); + return label; +} + +std::unique_ptr +BraveSidePanelHeaderController::CreateLaunchButton() { + CHECK(entry_); + if (entry_->key().id() == SidePanelEntry::Id::kBookmarks) { + return CreateHeaderImageButton( + base::BindRepeating( + &BraveSidePanelHeaderController::OnLaunchButtonPressed, + weak_factory_.GetWeakPtr(), GURL(chrome::kChromeUIBookmarksURL)), + kLeoLaunchIcon, + IDS_SIDEBAR_READING_LIST_PANEL_HEADER_BOOKMARKS_BUTTON_TOOLTIP); + } + + return nullptr; +} + +std::unique_ptr +BraveSidePanelHeaderController::CreateCloseButton() { + return CreateHeaderImageButton( + base::BindRepeating(&BraveSidePanelHeaderController::OnCloseButtonPressed, + weak_factory_.GetWeakPtr()), + kLeoCloseIcon, IDS_SIDEBAR_PANEL_CLOSE_BUTTON_TOOLTIP); +} + +void BraveSidePanelHeaderController::OnLaunchButtonPressed(const GURL& url) { + ShowSingletonTab(base::to_address(browser_window_), url); +} + +void BraveSidePanelHeaderController::OnCloseButtonPressed() { + if (auto* side_panel_ui = browser_window_->GetFeatures().side_panel_ui()) { + side_panel_ui->Close(SidePanelEntry::PanelType::kContent); + } +} diff --git a/browser/ui/views/side_panel/brave_side_panel_header_controller.h b/browser/ui/views/side_panel/brave_side_panel_header_controller.h new file mode 100644 index 00000000000..4fb0a9887cd --- /dev/null +++ b/browser/ui/views/side_panel/brave_side_panel_header_controller.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2026 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_SIDE_PANEL_BRAVE_SIDE_PANEL_HEADER_CONTROLLER_H_ +#define BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_BRAVE_SIDE_PANEL_HEADER_CONTROLLER_H_ + +#include + +#include "base/memory/raw_ref.h" +#include "base/memory/weak_ptr.h" +#include "brave/browser/ui/views/side_panel/brave_side_panel_header.h" +#include "chrome/browser/ui/side_panel/side_panel_entry.h" +#include "url/gurl.h" + +class BrowserWindowInterface; + +namespace views { +class ImageButton; +class Label; +} // namespace views + +class BraveSidePanelHeaderController : public BraveSidePanelHeader::Delegate { + public: + BraveSidePanelHeaderController(BrowserWindowInterface& browser_window, + SidePanelEntry* entry); + BraveSidePanelHeaderController(const BraveSidePanelHeaderController&) = + delete; + BraveSidePanelHeaderController& operator=( + const BraveSidePanelHeaderController&) = delete; + ~BraveSidePanelHeaderController() override; + + // BraveSidePanelHeader::Delegate: + std::unique_ptr CreatePanelTitle() override; + std::unique_ptr CreateLaunchButton() override; + std::unique_ptr CreateCloseButton() override; + + private: + void OnLaunchButtonPressed(const GURL& url); + void OnCloseButtonPressed(); + + const raw_ref browser_window_; + base::WeakPtr entry_; + base::WeakPtrFactory weak_factory_{this}; +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_BRAVE_SIDE_PANEL_HEADER_CONTROLLER_H_ diff --git a/chromium_src/chrome/browser/ui/browser_window/internal/browser_window_features.cc b/chromium_src/chrome/browser/ui/browser_window/internal/browser_window_features.cc index b3c6b97fc35..3799c05c4a0 100644 --- a/chromium_src/chrome/browser/ui/browser_window/internal/browser_window_features.cc +++ b/chromium_src/chrome/browser/ui/browser_window/internal/browser_window_features.cc @@ -8,6 +8,7 @@ #include "brave/browser/ui/brave_browser_actions.h" #include "brave/browser/ui/brave_browser_command_controller.h" #include "brave/browser/ui/brave_browser_content_setting_bubble_model_delegate.h" +#include "brave/browser/ui/sidebar/buildflags/buildflags.h" #include "brave/browser/ui/toolbar/brave_location_bar_model_delegate.h" #include "brave/browser/ui/views/side_panel/bookmarks/brave_bookmarks_side_panel_coordinator.h" #include "brave/browser/ui/views/side_panel/brave_side_panel_coordinator.h" @@ -25,18 +26,24 @@ BraveBrowserContentSettingBubbleModelDelegate #define BrowserWindowFeatures BrowserWindowFeatures_ChromiumImpl #define SidePanelCoordinator BraveSidePanelCoordinator -#define BookmarksSidePanelCoordinator BraveBookmarksSidePanelCoordinator #define BrowserActions BraveBrowserActions #define BrowserCommandController BraveBrowserCommandController +#if !BUILDFLAG(ENABLE_SIDEBAR_V2) +#define BookmarksSidePanelCoordinator BraveBookmarksSidePanelCoordinator +#endif + #include +#if !BUILDFLAG(ENABLE_SIDEBAR_V2) #undef BookmarksSidePanelCoordinator +#endif + +#undef BrowserCommandController +#undef BrowserActions #undef SidePanelCoordinator #undef BrowserWindowFeatures #undef BrowserContentSettingBubbleModelDelegate -#undef BrowserActions -#undef BrowserCommandController const SidePanelUI* BrowserWindowFeatures_ChromiumImpl::side_panel_ui() const { return const_cast(this)->side_panel_ui(); diff --git a/chromium_src/chrome/browser/ui/views/side_panel/reading_list/reading_list_side_panel_coordinator.cc b/chromium_src/chrome/browser/ui/views/side_panel/reading_list/reading_list_side_panel_coordinator.cc index e63869f66ee..5802784e5db 100644 --- a/chromium_src/chrome/browser/ui/views/side_panel/reading_list/reading_list_side_panel_coordinator.cc +++ b/chromium_src/chrome/browser/ui/views/side_panel/reading_list/reading_list_side_panel_coordinator.cc @@ -3,11 +3,16 @@ // 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/sidebar/buildflags/buildflags.h" #include "brave/browser/ui/views/side_panel/brave_read_later_side_panel_view.h" #include "chrome/browser/ui/views/side_panel/reading_list/read_later_side_panel_web_view.h" +#if !BUILDFLAG(ENABLE_SIDEBAR_V2) #define ReadLaterSidePanelWebView BraveReadLaterSidePanelView +#endif #include +#if !BUILDFLAG(ENABLE_SIDEBAR_V2) #undef ReadLaterSidePanelWebView +#endif diff --git a/chromium_src/chrome/browser/ui/views/side_panel/side_panel.cc b/chromium_src/chrome/browser/ui/views/side_panel/side_panel.cc index 692ebd773ad..276937992b9 100644 --- a/chromium_src/chrome/browser/ui/views/side_panel/side_panel.cc +++ b/chromium_src/chrome/browser/ui/views/side_panel/side_panel.cc @@ -13,11 +13,10 @@ // .cc pulls it in. #include "chrome/browser/ui/views/side_panel/side_panel.h" -// Rename the upstream RemoveHeaderView implementation so we can provide -// our own method. Upstream RemoveHeaderView() resets Border always but we -// want to preserve the no-border state. As we don't set any header to all -// panels, making it empty would not add any side-effect. -#define RemoveHeaderView RemoveHeaderView_UnUsed +// Rename the upstream Add/RemoveHeaderView implementation so we can provide +// a thin wrapper that reapplies border state. +#define AddHeaderView AddHeaderView_ChromiumImpl +#define RemoveHeaderView RemoveHeaderView_ChromiumImpl // Rename the upstream Open/Close implementation so we can provide a thin // wrapper that reapplies border state after UpdateVisibility() runs. @@ -31,6 +30,7 @@ #undef Close #undef Open #undef RemoveHeaderView +#undef AddHeaderView #endif // BUILDFLAG(ENABLE_SIDEBAR_V2) @@ -79,19 +79,32 @@ void SidePanel::UpdateBorder() { return; } + // When a Brave header is attached, reserve top inset for it so the header + // paints over the border strip without overlapping content. + const int header_top_inset = + header_view_ ? header_view_->GetPreferredSize().height() : 0; + if (rounded_border_enabled_) { // Upstream GetBorderInsets() has a negative top to overlap the toolbar; // Brave doesn't need that overlap. - SetBorder(views::CreateEmptyBorder(GetBorderInsets().set_top(0))); + SetBorder( + views::CreateEmptyBorder(GetBorderInsets().set_top(header_top_inset))); } else { - SetBorder(nullptr); + SetBorder( + views::CreateEmptyBorder(gfx::Insets::TLBR(header_top_inset, 0, 0, 0))); } border_view_->SetVisible(rounded_border_enabled_); } +void SidePanel::AddHeaderView(std::unique_ptr view) { + AddHeaderView_ChromiumImpl(std::move(view)); + UpdateBorder(); +} + void SidePanel::RemoveHeaderView() { - // See above method overriding's comment why it's empty. + RemoveHeaderView_ChromiumImpl(); + UpdateBorder(); } #endif // BUILDFLAG(ENABLE_SIDEBAR_V2) diff --git a/chromium_src/chrome/browser/ui/views/side_panel/side_panel.h b/chromium_src/chrome/browser/ui/views/side_panel/side_panel.h index ee506a603fa..e0c1e165224 100644 --- a/chromium_src/chrome/browser/ui/views/side_panel/side_panel.h +++ b/chromium_src/chrome/browser/ui/views/side_panel/side_panel.h @@ -22,14 +22,15 @@ // Override Open/Close to apply our border style whenever open/close panel. // Override VisibilityChanged() is not sufficient as it's not called when // opening another panel while other panel is opened. -#define GetContentParentView(...) \ - GetContentParentView(__VA_ARGS__); \ - void SetResizeArea(std::unique_ptr resize_area); \ - void SetRoundedBorderEnabled(bool enabled); \ - void UpdateBorder(); \ - void Open_ChromiumImpl(bool animated); \ - void Close_ChromiumImpl(bool animated); \ - void RemoveHeaderView_UnUsed() +#define GetContentParentView(...) \ + GetContentParentView(__VA_ARGS__); \ + void SetResizeArea(std::unique_ptr resize_area); \ + void SetRoundedBorderEnabled(bool enabled); \ + void UpdateBorder(); \ + void Open_ChromiumImpl(bool animated); \ + void Close_ChromiumImpl(bool animated); \ + void AddHeaderView_ChromiumImpl(std::unique_ptr view); \ + void RemoveHeaderView_ChromiumImpl() #define did_resize_ \ did_resize_ = false; \