diff --git a/BUILD.gn b/BUILD.gn index 2f04156a0..c7f09bccc 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1042,12 +1042,16 @@ source_set("libcef_static") { "libcef/browser/osr/browser_platform_delegate_osr_linux.h", "libcef/browser/printing/print_dialog_linux.cc", "libcef/browser/printing/print_dialog_linux.h", + "libcef/browser/views/linux_frame_view.cc", + "libcef/browser/views/linux_frame_view.h", "libcef/common/util_linux.h", "libcef/common/util_linux.cc", ] deps += [ "//third_party/fontconfig", + "//ui/linux:linux_ui", + "//ui/linux:linux_ui_factory", ] if (ozone_platform_x11) { diff --git a/libcef/browser/chrome/views/chrome_browser_widget.cc b/libcef/browser/chrome/views/chrome_browser_widget.cc index 9e15d2dc6..8e50f213d 100644 --- a/libcef/browser/chrome/views/chrome_browser_widget.cc +++ b/libcef/browser/chrome/views/chrome_browser_widget.cc @@ -15,6 +15,11 @@ #include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/views/frame/browser_view.h" +#if BUILDFLAG(IS_LINUX) +#include "cef/libcef/browser/views/linux_frame_view.h" +#include "ui/views/view_utils.h" +#endif + #if BUILDFLAG(IS_MAC) #include "cef/libcef/browser/views/native_widget_mac.h" #include "cef/libcef/browser/views/view_util.h" @@ -245,6 +250,18 @@ void ChromeBrowserWidget::OnNativeWidgetDestroyed() { } } +gfx::Insets ChromeBrowserWidget::GetCustomInsetsInDIP() const { +#if BUILDFLAG(IS_LINUX) + if (non_client_view() && non_client_view()->frame_view()) { + if (auto* linux_frame = views::AsViewClass( + non_client_view()->frame_view())) { + return linux_frame->GetFrameBorderInsets(); + } + } +#endif + return Widget::GetCustomInsetsInDIP(); +} + void ChromeBrowserWidget::OnNativeThemeUpdated( ui::NativeTheme* observed_theme) { // TODO: Reduce the frequency of this callback on Windows/Linux. diff --git a/libcef/browser/chrome/views/chrome_browser_widget.h b/libcef/browser/chrome/views/chrome_browser_widget.h index efc86f441..2bf80f8ed 100644 --- a/libcef/browser/chrome/views/chrome_browser_widget.h +++ b/libcef/browser/chrome/views/chrome_browser_widget.h @@ -148,6 +148,9 @@ class ChromeBrowserWidget : public BrowserWidget, // NativeWidgetDelegate methods: void OnNativeWidgetDestroyed() override; + // views::Widget methods: + gfx::Insets GetCustomInsetsInDIP() const override; + // ui::NativeThemeObserver methods: void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override; ui::ColorProviderKey GetColorProviderKey() const override; diff --git a/libcef/browser/views/linux_frame_view.cc b/libcef/browser/views/linux_frame_view.cc new file mode 100644 index 000000000..4cef12a68 --- /dev/null +++ b/libcef/browser/views/linux_frame_view.cc @@ -0,0 +1,522 @@ +// Copyright 2025 The Chromium Embedded Framework Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +#include "cef/libcef/browser/views/linux_frame_view.h" + +#include +#include + +#include "base/containers/adapters.h" +#include "ui/base/hit_test.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/color/color_id.h" +#include "ui/color/color_provider.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font_list.h" +#include "ui/linux/linux_ui.h" +#include "ui/strings/grit/ui_strings.h" +#include "ui/views/accessibility/view_accessibility.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/resources/grit/views_resources.h" +#include "ui/views/style/typography_provider.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/window/window_button_order_provider.h" + +namespace { + +constexpr int kResizeAreaCornerSize = 16; +constexpr int kTitleCaptionSpacing = 5; +constexpr int kTitleIconOffsetX = 4; +constexpr int kIconLeftSpacing = 2; +constexpr int kCaptionButtonHeightWithPadding = 19; + +void LayoutButton(views::ImageButton* button, const gfx::Rect& bounds) { + button->SetVisible(true); + button->SetImageVerticalAlignment(views::ImageButton::ALIGN_BOTTOM); + button->SetBoundsRect(bounds); +} + +ui::NavButtonProvider::ButtonState ToNavButtonState( + views::Button::ButtonState state) { + switch (state) { + case views::Button::STATE_NORMAL: + return ui::NavButtonProvider::ButtonState::kNormal; + case views::Button::STATE_HOVERED: + return ui::NavButtonProvider::ButtonState::kHovered; + case views::Button::STATE_PRESSED: + return ui::NavButtonProvider::ButtonState::kPressed; + case views::Button::STATE_DISABLED: + return ui::NavButtonProvider::ButtonState::kDisabled; + case views::Button::STATE_COUNT: + default: + NOTREACHED(); + } +} + +} // namespace + +bool LinuxFrameView::NavButtonCacheParams::operator==( + const NavButtonCacheParams& other) const { + return top_area_height == other.top_area_height && + maximized == other.maximized && active == other.active; +} + +LinuxFrameView::LinuxFrameView(views::Widget* widget, + base::WeakPtr view, + ui::LinuxUiTheme* linux_ui_theme) + : widget_(widget), + view_(std::move(view)), + linux_ui_theme_(linux_ui_theme), + nav_button_provider_(linux_ui_theme->CreateNavButtonProvider()) { + if (!nav_button_provider_) { + // Fall back to Chromium's built-in caption button images. + close_button_ = InitWindowCaptionButton( + base::BindRepeating(&views::Widget::CloseWithReason, + base::Unretained(widget_), + views::Widget::ClosedReason::kCloseButtonClicked), + IDS_APP_ACCNAME_CLOSE, IDR_CLOSE, IDR_CLOSE_H, IDR_CLOSE_P); + minimize_button_ = InitWindowCaptionButton( + base::BindRepeating(&views::Widget::Minimize, + base::Unretained(widget_)), + IDS_APP_ACCNAME_MINIMIZE, IDR_MINIMIZE, IDR_MINIMIZE_H, IDR_MINIMIZE_P); + maximize_button_ = InitWindowCaptionButton( + base::BindRepeating(&views::Widget::Maximize, + base::Unretained(widget_)), + IDS_APP_ACCNAME_MAXIMIZE, IDR_MAXIMIZE, IDR_MAXIMIZE_H, IDR_MAXIMIZE_P); + restore_button_ = InitWindowCaptionButton( + base::BindRepeating(&views::Widget::Restore, + base::Unretained(widget_)), + IDS_APP_ACCNAME_RESTORE, IDR_RESTORE, IDR_RESTORE_H, IDR_RESTORE_P); + } else { + // GTK-themed nav buttons. Create ImageButton children that we'll + // populate with images from the NavButtonProvider. + close_button_ = AddChildView(std::make_unique( + base::BindRepeating(&views::Widget::CloseWithReason, + base::Unretained(widget_), + views::Widget::ClosedReason::kCloseButtonClicked))); + close_button_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); + close_button_->GetViewAccessibility().SetName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); + + minimize_button_ = AddChildView(std::make_unique( + base::BindRepeating(&views::Widget::Minimize, + base::Unretained(widget_)))); + minimize_button_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); + minimize_button_->GetViewAccessibility().SetName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE)); + + maximize_button_ = AddChildView(std::make_unique( + base::BindRepeating(&views::Widget::Maximize, + base::Unretained(widget_)))); + maximize_button_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); + maximize_button_->GetViewAccessibility().SetName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE)); + + restore_button_ = AddChildView(std::make_unique( + base::BindRepeating(&views::Widget::Restore, + base::Unretained(widget_)))); + restore_button_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); + restore_button_->GetViewAccessibility().SetName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE)); + } + + paint_as_active_subscription_ = + widget_->RegisterPaintAsActiveChangedCallback(base::BindRepeating( + &LinuxFrameView::SchedulePaint, base::Unretained(this))); +} + +LinuxFrameView::~LinuxFrameView() = default; + +gfx::Rect LinuxFrameView::GetBoundsForClientView() const { + return client_view_bounds_; +} + +gfx::Rect LinuxFrameView::GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const { + gfx::Insets insets = GetFrameBorderInsets(); + int top_height = GetTopAreaHeight(); + return gfx::Rect(client_bounds.x() - insets.left(), + client_bounds.y() - top_height, + client_bounds.width() + insets.left() + insets.right(), + client_bounds.height() + top_height + insets.bottom()); +} + +int LinuxFrameView::NonClientHitTest(const gfx::Point& point) { + if (!bounds().Contains(point)) { + return HTNOWHERE; + } + + int frame_component = widget_->client_view()->NonClientHitTest(point); + if (frame_component != HTNOWHERE) { + return frame_component; + } + + if (close_button_ && close_button_->GetVisible() && + close_button_->GetMirroredBounds().Contains(point)) { + return HTCLOSE; + } + if (restore_button_ && restore_button_->GetVisible() && + restore_button_->GetMirroredBounds().Contains(point)) { + return HTMAXBUTTON; + } + if (maximize_button_ && maximize_button_->GetVisible() && + maximize_button_->GetMirroredBounds().Contains(point)) { + return HTMAXBUTTON; + } + if (minimize_button_ && minimize_button_->GetVisible() && + minimize_button_->GetMirroredBounds().Contains(point)) { + return HTMINBUTTON; + } + + // Test for draggable region. + if (view_) { + SkRegion* draggable_region = view_->draggable_region(); + if (draggable_region && draggable_region->contains(point.x(), point.y())) { + return HTCAPTION; + } + } + + gfx::Insets resize_border = GetFrameBorderInsets(); + int window_component = GetHTComponentForFrame( + point, resize_border, kResizeAreaCornerSize, kResizeAreaCornerSize, + widget_->widget_delegate()->CanResize()); + return (window_component == HTNOWHERE) ? HTCAPTION : window_component; +} + +void LinuxFrameView::GetWindowMask(const gfx::Size& size, + SkPath* window_mask) {} + +void LinuxFrameView::ResetWindowControls() { + if (restore_button_) { + restore_button_->SetState(views::Button::STATE_NORMAL); + } + if (minimize_button_) { + minimize_button_->SetState(views::Button::STATE_NORMAL); + } + if (maximize_button_) { + maximize_button_->SetState(views::Button::STATE_NORMAL); + } +} + +void LinuxFrameView::UpdateWindowIcon() {} + +void LinuxFrameView::UpdateWindowTitle() { + if (widget_->widget_delegate()->ShouldShowWindowTitle() && + maximum_title_bar_x_ > -1) { + LayoutTitleBar(); + SchedulePaintInRect(title_bounds_); + } +} + +void LinuxFrameView::SizeConstraintsChanged() { + ResetWindowControls(); + LayoutWindowControls(); +} + +void LinuxFrameView::OnPaint(gfx::Canvas* canvas) { + if (widget_->IsFullscreen()) { + return; + } + + PaintFrameBorder(canvas); + + // Paint title text. + views::WidgetDelegate* delegate = widget_->widget_delegate(); + if (delegate && delegate->ShouldShowWindowTitle()) { + gfx::Rect rect = title_bounds_; + rect.set_x(GetMirroredXForRect(title_bounds_)); + canvas->DrawStringRect( + delegate->GetWindowTitle(), + views::TypographyProvider::Get().GetWindowTitleFontList(), + GetColorProvider()->GetColor(ui::kColorCustomFrameCaptionForeground), + rect); + } +} + +void LinuxFrameView::Layout(views::View::PassKey) { + if (nav_button_provider_) { + MaybeUpdateCachedNavButtonImages(); + } + + if (!widget_->IsFullscreen()) { + LayoutWindowControls(); + LayoutTitleBar(); + } + + LayoutClientView(); + LayoutSuperclass(this); +} + +gfx::Size LinuxFrameView::CalculatePreferredSize( + const views::SizeBounds& available_size) const { + return widget_->non_client_view() + ->GetWindowBoundsForClientBounds( + gfx::Rect(widget_->client_view()->GetPreferredSize(available_size))) + .size(); +} + +gfx::Size LinuxFrameView::GetMinimumSize() const { + return widget_->non_client_view() + ->GetWindowBoundsForClientBounds( + gfx::Rect(widget_->client_view()->GetMinimumSize())) + .size(); +} + +gfx::Size LinuxFrameView::GetMaximumSize() const { + gfx::Size max_size = widget_->client_view()->GetMaximumSize(); + gfx::Size converted_size = + widget_->non_client_view() + ->GetWindowBoundsForClientBounds(gfx::Rect(max_size)) + .size(); + return gfx::Size(max_size.width() == 0 ? 0 : converted_size.width(), + max_size.height() == 0 ? 0 : converted_size.height()); +} + +void LinuxFrameView::OnThemeChanged() { + views::FrameView::OnThemeChanged(); + nav_button_cache_.reset(); + SchedulePaint(); +} + +ui::WindowFrameProvider* LinuxFrameView::GetFrameProvider() const { + bool tiled = false; + bool maximized = widget_->IsMaximized(); + // solid_frame=true means no shadow (tiled/maximized windows). + bool solid_frame = maximized || tiled; + return linux_ui_theme_->GetWindowFrameProvider(solid_frame, tiled, maximized); +} + +gfx::Insets LinuxFrameView::GetFrameBorderInsets() const { + if (widget_->IsMaximized() || widget_->IsFullscreen()) { + return gfx::Insets(); + } + auto* provider = GetFrameProvider(); + if (provider) { + return provider->GetFrameThicknessDip(); + } + return gfx::Insets(4); +} + +int LinuxFrameView::GetTopAreaHeight() const { + if (widget_->IsFullscreen()) { + return 0; + } + gfx::Insets insets = GetFrameBorderInsets(); + return std::max(insets.top(), + insets.top() + kCaptionButtonHeightWithPadding); +} + +void LinuxFrameView::LayoutWindowControls() { + minimum_title_bar_x_ = 0; + maximum_title_bar_x_ = width(); + + if (bounds().IsEmpty()) { + return; + } + + gfx::Insets insets = GetFrameBorderInsets(); + int caption_y = insets.top(); + int next_button_x = insets.left(); + + bool is_maximized = widget_->IsMaximized(); + bool is_restored = !is_maximized && !widget_->IsMinimized(); + views::ImageButton* invisible_button = + is_restored ? restore_button_.get() : maximize_button_.get(); + if (invisible_button) { + invisible_button->SetVisible(false); + } + + views::WindowButtonOrderProvider* button_order = + views::WindowButtonOrderProvider::GetInstance(); + const std::vector& leading_buttons = + button_order->leading_buttons(); + const std::vector& trailing_buttons = + button_order->trailing_buttons(); + + for (auto frame_button : leading_buttons) { + views::ImageButton* button = GetImageButton(frame_button); + if (!button) { + continue; + } + gfx::Rect target_bounds(gfx::Point(next_button_x, caption_y), + button->GetPreferredSize({})); + LayoutButton(button, target_bounds); + next_button_x += button->width(); + minimum_title_bar_x_ = std::min(width(), next_button_x); + } + + next_button_x = width() - insets.right(); + for (auto frame_button : base::Reversed(trailing_buttons)) { + views::ImageButton* button = GetImageButton(frame_button); + if (!button) { + continue; + } + gfx::Rect target_bounds(gfx::Point(next_button_x, caption_y), + button->GetPreferredSize({})); + target_bounds.Offset(-target_bounds.width(), 0); + LayoutButton(button, target_bounds); + next_button_x = button->x(); + maximum_title_bar_x_ = std::max(minimum_title_bar_x_, next_button_x); + } +} + +void LinuxFrameView::LayoutTitleBar() { + if (!widget_->widget_delegate()->ShouldShowWindowTitle()) { + return; + } + + gfx::Insets insets = GetFrameBorderInsets(); + int title_x = insets.left() + kIconLeftSpacing + minimum_title_bar_x_ + + kTitleIconOffsetX; + int title_height = + views::TypographyProvider::Get().GetWindowTitleFontList().GetHeight(); + int top_height = GetTopAreaHeight(); + int title_y = insets.top() + (top_height - insets.top() - title_height) / 2; + + title_bounds_.SetRect( + title_x, title_y, + std::max(0, maximum_title_bar_x_ - kTitleCaptionSpacing - title_x), + title_height); +} + +void LinuxFrameView::LayoutClientView() { + if (widget_->IsFullscreen()) { + client_view_bounds_ = bounds(); + return; + } + + gfx::Insets insets = GetFrameBorderInsets(); + int top_height = GetTopAreaHeight(); + client_view_bounds_.SetRect( + insets.left(), top_height, + std::max(0, width() - insets.left() - insets.right()), + std::max(0, height() - top_height - insets.bottom())); +} + +void LinuxFrameView::PaintFrameBorder(gfx::Canvas* canvas) { + auto* provider = GetFrameProvider(); + if (!provider) { + return; + } + + gfx::Insets input_insets; + if (widget_->IsMaximized()) { + input_insets = gfx::Insets(); + } + + provider->PaintWindowFrame(canvas, GetLocalBounds(), GetTopAreaHeight(), + ShouldPaintAsActive(), input_insets); +} + +views::ImageButton* LinuxFrameView::InitWindowCaptionButton( + views::Button::PressedCallback callback, + int accessibility_string_id, + int normal_image_id, + int hot_image_id, + int pushed_image_id) { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + views::ImageButton* button = + AddChildView(std::make_unique(std::move(callback))); + button->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); + button->GetViewAccessibility().SetName( + l10n_util::GetStringUTF16(accessibility_string_id)); + button->SetImageModel( + views::Button::STATE_NORMAL, + ui::ImageModel::FromImage(rb.GetImageNamed(normal_image_id))); + button->SetImageModel( + views::Button::STATE_HOVERED, + ui::ImageModel::FromImage(rb.GetImageNamed(hot_image_id))); + button->SetImageModel( + views::Button::STATE_PRESSED, + ui::ImageModel::FromImage(rb.GetImageNamed(pushed_image_id))); + return button; +} + +views::ImageButton* LinuxFrameView::GetImageButton( + views::FrameButton frame_button) { + views::ImageButton* button = nullptr; + switch (frame_button) { + case views::FrameButton::kMinimize: { + button = minimize_button_; + bool should_show = widget_->widget_delegate()->CanMinimize(); + if (button) { + button->SetVisible(should_show); + } + return should_show ? button : nullptr; + } + case views::FrameButton::kMaximize: { + bool is_restored = !widget_->IsMaximized() && !widget_->IsMinimized(); + button = is_restored ? maximize_button_.get() : restore_button_.get(); + bool should_show = widget_->widget_delegate()->CanMaximize(); + if (button) { + button->SetVisible(should_show); + } + return should_show ? button : nullptr; + } + case views::FrameButton::kClose: { + return close_button_; + } + } + return nullptr; +} + +void LinuxFrameView::MaybeUpdateCachedNavButtonImages() { + if (!nav_button_provider_) { + return; + } + + gfx::Insets insets = GetFrameBorderInsets(); + NavButtonCacheParams params{GetTopAreaHeight() - insets.top(), + widget_->IsMaximized(), ShouldPaintAsActive()}; + if (nav_button_cache_ == params) { + return; + } + nav_button_cache_ = params; + + nav_button_provider_->RedrawImages(params.top_area_height, params.maximized, + params.active); + + for (auto type : { + ui::NavButtonProvider::FrameButtonDisplayType::kMinimize, + params.maximized + ? ui::NavButtonProvider::FrameButtonDisplayType::kRestore + : ui::NavButtonProvider::FrameButtonDisplayType::kMaximize, + ui::NavButtonProvider::FrameButtonDisplayType::kClose, + }) { + views::Button* button = GetButtonFromDisplayType(type); + if (!button) { + continue; + } + for (size_t state = 0; state < views::Button::STATE_COUNT; state++) { + auto button_state = static_cast(state); + static_cast(button)->SetImageModel( + button_state, + ui::ImageModel::FromImageSkia(nav_button_provider_->GetImage( + type, ToNavButtonState(button_state)))); + } + } +} + +views::Button* LinuxFrameView::GetButtonFromDisplayType( + ui::NavButtonProvider::FrameButtonDisplayType type) { + switch (type) { + case ui::NavButtonProvider::FrameButtonDisplayType::kMinimize: + return minimize_button_; + case ui::NavButtonProvider::FrameButtonDisplayType::kMaximize: + return maximize_button_; + case ui::NavButtonProvider::FrameButtonDisplayType::kRestore: + return restore_button_; + case ui::NavButtonProvider::FrameButtonDisplayType::kClose: + return close_button_; + default: + NOTREACHED(); + } +} + +BEGIN_METADATA(LinuxFrameView) +END_METADATA diff --git a/libcef/browser/views/linux_frame_view.h b/libcef/browser/views/linux_frame_view.h new file mode 100644 index 000000000..0775209e1 --- /dev/null +++ b/libcef/browser/views/linux_frame_view.h @@ -0,0 +1,113 @@ +// Copyright 2025 The Chromium Embedded Framework Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +#ifndef CEF_LIBCEF_BROWSER_VIEWS_LINUX_FRAME_VIEW_H_ +#define CEF_LIBCEF_BROWSER_VIEWS_LINUX_FRAME_VIEW_H_ + +#include +#include + +#include "base/memory/raw_ptr.h" +#include "cef/libcef/browser/views/window_view.h" +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/linux/nav_button_provider.h" +#include "ui/linux/window_frame_provider.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/window/frame_buttons.h" +#include "ui/views/window/frame_view.h" + +namespace ui { +class LinuxUiTheme; +} + +// A frame view for CEF windows on Linux that uses the GTK toolkit's +// WindowFrameProvider for native-looking client-side decorations. +// This replaces the default blue header bar (DefaultFrameView) when +// the compositor does not support server-side decorations. +class LinuxFrameView : public views::FrameView { + METADATA_HEADER(LinuxFrameView, views::FrameView) + + public: + LinuxFrameView(views::Widget* widget, + base::WeakPtr view, + ui::LinuxUiTheme* linux_ui_theme); + + LinuxFrameView(const LinuxFrameView&) = delete; + LinuxFrameView& operator=(const LinuxFrameView&) = delete; + + ~LinuxFrameView() override; + + // views::FrameView: + gfx::Rect GetBoundsForClientView() const override; + gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const override; + int NonClientHitTest(const gfx::Point& point) override; + void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override; + void ResetWindowControls() override; + void UpdateWindowIcon() override; + void UpdateWindowTitle() override; + void SizeConstraintsChanged() override; + + // views::View: + void OnPaint(gfx::Canvas* canvas) override; + void Layout(views::View::PassKey) override; + gfx::Size CalculatePreferredSize( + const views::SizeBounds& available_size) const override; + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; + void OnThemeChanged() override; + + gfx::Insets GetFrameBorderInsets() const; + + private: + ui::WindowFrameProvider* GetFrameProvider() const; + int GetTopAreaHeight() const; + + void LayoutWindowControls(); + void LayoutTitleBar(); + void LayoutClientView(); + + void PaintFrameBorder(gfx::Canvas* canvas); + + views::ImageButton* InitWindowCaptionButton( + views::Button::PressedCallback callback, + int accessibility_string_id, + int normal_image_id, + int hot_image_id, + int pushed_image_id); + + views::ImageButton* GetImageButton(views::FrameButton frame_button); + + void MaybeUpdateCachedNavButtonImages(); + views::Button* GetButtonFromDisplayType( + ui::NavButtonProvider::FrameButtonDisplayType type); + + raw_ptr widget_; + base::WeakPtr view_; + raw_ptr linux_ui_theme_; + std::unique_ptr nav_button_provider_; + + gfx::Rect client_view_bounds_; + gfx::Rect title_bounds_; + + raw_ptr minimize_button_ = nullptr; + raw_ptr maximize_button_ = nullptr; + raw_ptr restore_button_ = nullptr; + raw_ptr close_button_ = nullptr; + + int minimum_title_bar_x_ = 0; + int maximum_title_bar_x_ = -1; + + struct NavButtonCacheParams { + bool operator==(const NavButtonCacheParams& other) const; + int top_area_height; + bool maximized; + bool active; + }; + std::optional nav_button_cache_; + + base::CallbackListSubscription paint_as_active_subscription_; +}; + +#endif // CEF_LIBCEF_BROWSER_VIEWS_LINUX_FRAME_VIEW_H_ diff --git a/libcef/browser/views/widget_impl.cc b/libcef/browser/views/widget_impl.cc index a4a2e03cf..1dac62c9e 100644 --- a/libcef/browser/views/widget_impl.cc +++ b/libcef/browser/views/widget_impl.cc @@ -11,7 +11,9 @@ #include "chrome/browser/themes/theme_service_factory.h" #if BUILDFLAG(IS_LINUX) +#include "cef/libcef/browser/views/linux_frame_view.h" #include "ui/linux/linux_ui.h" +#include "ui/views/view_utils.h" #endif CefWidgetImpl::CefWidgetImpl(CefWindowView* window_view) @@ -123,6 +125,18 @@ void CefWidgetImpl::OnNativeWidgetDestroyed() { views::Widget::OnNativeWidgetDestroyed(); } +gfx::Insets CefWidgetImpl::GetCustomInsetsInDIP() const { +#if BUILDFLAG(IS_LINUX) + if (non_client_view() && non_client_view()->frame_view()) { + if (auto* linux_frame = views::AsViewClass( + non_client_view()->frame_view())) { + return linux_frame->GetFrameBorderInsets(); + } + } +#endif + return Widget::GetCustomInsetsInDIP(); +} + void CefWidgetImpl::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) { // TODO: Reduce the frequency of this callback on Windows/Linux. // See https://issues.chromium.org/issues/40280130#comment7 diff --git a/libcef/browser/views/widget_impl.h b/libcef/browser/views/widget_impl.h index 5bbd9438b..164fe8423 100644 --- a/libcef/browser/views/widget_impl.h +++ b/libcef/browser/views/widget_impl.h @@ -66,6 +66,9 @@ class CefWidgetImpl : public views::Widget, // NativeWidgetDelegate methods: void OnNativeWidgetDestroyed() override; + // views::Widget methods: + gfx::Insets GetCustomInsetsInDIP() const override; + // ui::NativeThemeObserver methods: void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override; ui::ColorProviderKey GetColorProviderKey() const override; diff --git a/libcef/browser/views/window_view.cc b/libcef/browser/views/window_view.cc index 47ff9bf78..f489cd315 100644 --- a/libcef/browser/views/window_view.cc +++ b/libcef/browser/views/window_view.cc @@ -21,6 +21,12 @@ #include "cef/libcef/browser/image_impl.h" #include "cef/libcef/browser/views/widget.h" #include "cef/libcef/browser/views/window_impl.h" + +#if BUILDFLAG(IS_LINUX) +#include "cef/libcef/browser/views/linux_frame_view.h" +#include "ui/linux/linux_ui.h" +#include "ui/linux/linux_ui_factory.h" +#endif #include "ui/base/hit_test.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/base/mojom/ui_base_types.mojom-shared.h" @@ -539,6 +545,13 @@ void CefWindowView::CreateWidget(gfx::AcceleratedWidget parent_widget) { params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque; } else { params.type = views::Widget::InitParams::TYPE_WINDOW; +#if BUILDFLAG(IS_LINUX) + if (auto* linux_ui_theme = ui::GetDefaultLinuxUiTheme()) { + if (linux_ui_theme->GetWindowFrameProvider(false, false, false)) { + params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; + } + } +#endif } if (cef_delegate()) { @@ -811,8 +824,19 @@ std::unique_ptr CefWindowView::CreateFrameView( weak_ptr_factory_.GetWeakPtr()); } - // Use Chromium provided CustomFrameView. In case if we would like to - // customize the frame, provide own implementation. +#if BUILDFLAG(IS_LINUX) + // When the compositor does not support server-side decorations (e.g. + // GNOME/Mutter on Wayland), use GTK-themed client-side decorations + // instead of falling back to Chromium's DefaultFrameView. + if (auto* linux_ui_theme = ui::GetDefaultLinuxUiTheme()) { + if (linux_ui_theme->GetWindowFrameProvider( + /*solid_frame=*/false, /*tiled=*/false, /*maximized=*/false)) { + return std::make_unique( + widget, weak_ptr_factory_.GetWeakPtr(), linux_ui_theme); + } + } +#endif + return nullptr; }