diff --git a/filenames.gni b/filenames.gni index 28ce13d725..186d7d8152 100644 --- a/filenames.gni +++ b/filenames.gni @@ -48,8 +48,8 @@ filenames = { "shell/browser/ui/views/opaque_frame_view.h", "shell/browser/ui/views/caption_button_placeholder_container.cc", "shell/browser/ui/views/caption_button_placeholder_container.h", - "shell/browser/ui/views/client_frame_view_linux.cc", - "shell/browser/ui/views/client_frame_view_linux.h", + "shell/browser/ui/views/native_frame_view_linux.cc", + "shell/browser/ui/views/native_frame_view_linux.h", "shell/browser/ui/views/linux_frame_layout.cc", "shell/browser/ui/views/linux_frame_layout.h", "shell/common/application_info_linux.cc", @@ -440,8 +440,6 @@ filenames = { "shell/browser/microtasks_runner.h", "shell/browser/native_window.cc", "shell/browser/native_window.h", - "shell/browser/native_window_features.cc", - "shell/browser/native_window_features.h", "shell/browser/native_window_observer.h", "shell/browser/net/asar/asar_file_validator.cc", "shell/browser/net/asar/asar_file_validator.h", diff --git a/shell/browser/native_window.cc b/shell/browser/native_window.cc index 25d39035b4..7aab7963b3 100644 --- a/shell/browser/native_window.cc +++ b/shell/browser/native_window.cc @@ -16,7 +16,6 @@ #include "shell/browser/background_throttling_source.h" #include "shell/browser/browser.h" #include "shell/browser/draggable_region_provider.h" -#include "shell/browser/native_window_features.h" #include "shell/browser/ui/drag_util.h" #include "shell/browser/window_list.h" #include "shell/common/color_util.h" @@ -29,6 +28,7 @@ #if !BUILDFLAG(IS_MAC) #include "shell/browser/ui/views/frameless_view.h" +#include "ui/views/view_utils.h" #endif #if defined(USE_OZONE) @@ -649,11 +649,13 @@ int NativeWindow::NonClientHitTest(const gfx::Point& point) { #if !BUILDFLAG(IS_MAC) // We need to ensure we account for resizing borders on Windows and Linux. if ((!has_frame() || has_client_frame()) && IsResizable()) { - auto* frame = - static_cast(widget()->non_client_view()->frame_view()); - int border_hit = frame->ResizingBorderHitTest(point); - if (border_hit != HTNOWHERE) - return border_hit; + auto* frame = views::AsViewClass( + widget()->non_client_view()->frame_view()); + if (frame) { + int border_hit = frame->ResizingBorderHitTest(point); + if (border_hit != HTNOWHERE) + return border_hit; + } } #endif @@ -780,7 +782,6 @@ bool NativeWindow::PlatformHasClientFrame() { // Ozone X11 likes to prefer custom frames, // but we don't need them unless on Wayland. static const bool has_client_frame = - base::FeatureList::IsEnabled(features::kWaylandWindowDecorations) && !ui::OzonePlatform::GetInstance() ->GetPlatformRuntimeProperties() .supports_server_side_window_decorations; diff --git a/shell/browser/native_window_features.cc b/shell/browser/native_window_features.cc deleted file mode 100644 index caacff9617..0000000000 --- a/shell/browser/native_window_features.cc +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2022 Slack Technologies, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/browser/native_window_features.h" - -namespace features { -BASE_FEATURE(kWaylandWindowDecorations, - "WaylandWindowDecorations", - base::FEATURE_ENABLED_BY_DEFAULT); -} diff --git a/shell/browser/native_window_features.h b/shell/browser/native_window_features.h deleted file mode 100644 index 661ed98e92..0000000000 --- a/shell/browser/native_window_features.h +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2022 Slack Technologies, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_ -#define ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_ - -#include "base/feature_list.h" - -namespace features { -extern const base::Feature kWaylandWindowDecorations; -} - -#endif // ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_ diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index fef6661ae7..ccb7c93029 100644 --- a/shell/browser/native_window_views.cc +++ b/shell/browser/native_window_views.cc @@ -64,12 +64,15 @@ #include "shell/browser/linux/unity_service.h" #include "shell/browser/linux/x11_util.h" #include "shell/browser/ui/electron_desktop_window_tree_host_linux.h" -#include "shell/browser/ui/views/client_frame_view_linux.h" #include "shell/browser/ui/views/linux_frame_layout.h" #include "shell/browser/ui/views/native_frame_view.h" +#include "shell/browser/ui/views/native_frame_view_linux.h" #include "shell/browser/ui/views/opaque_frame_view.h" #include "shell/common/platform_util.h" +#include "ui/linux/linux_ui.h" +#include "ui/linux/linux_ui_factory.h" #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" +#include "ui/views/window/frame_view_linux.h" #include "ui/views/window/native_frame_view.h" #if BUILDFLAG(SUPPORTS_OZONE_X11) @@ -508,9 +511,10 @@ void NativeWindowViews::SetTitleBarOverlay( // If anything was updated, ensure the overlay is repainted. if (updated) { - auto* frame_view = - static_cast(widget()->non_client_view()->frame_view()); - frame_view->InvalidateCaptionButtons(); + auto* fv = widget()->non_client_view()->frame_view(); + if (auto* frameless = views::AsViewClass(fv)) { + frameless->InvalidateCaptionButtons(); + } } } @@ -1283,9 +1287,11 @@ void NativeWindowViews::SetBackgroundColor(SkColor background_color) { SkColor compositor_color = background_color; #if BUILDFLAG(IS_LINUX) // Widget background needs to stay transparent for CSD shadow regions. + auto* fvl = GetFrameViewLinux(); LinuxFrameLayout* frame_layout = GetLinuxFrameLayout(); const bool uses_csd = - frame_layout && frame_layout->SupportsClientFrameShadow(); + (fvl && fvl->ShouldDrawRestoredFrameShadow()) || + (frame_layout && frame_layout->SupportsClientFrameShadow()); if (transparent() || uses_csd) compositor_color = SK_ColorTRANSPARENT; #endif @@ -1510,9 +1516,13 @@ gfx::Insets NativeWindowViews::GetRestoredFrameBorderInsets() const { if (!frame_view) return gfx::Insets(); - if (auto* frameless = views::AsViewClass(frame_view)) { + if (auto* frameless = views::AsViewClass(frame_view)) return frameless->RestoredFrameBorderInsets(); - } + +#if BUILDFLAG(IS_LINUX) + if (auto* fvl = views::AsViewClass(frame_view)) + return fvl->GetRestoredFrameBorderInsets(); +#endif return gfx::Insets(); } @@ -1925,8 +1935,21 @@ std::unique_ptr NativeWindowViews::CreateFrameView( if (!has_frame()) return std::make_unique(this, widget); - if (has_client_frame()) - return std::make_unique(this, widget); + if (has_client_frame()) { + auto* linux_ui_theme = ui::LinuxUiTheme::GetForProfile(nullptr); + auto getter = base::BindRepeating( + [](ui::LinuxUiTheme* theme, bool tiled, + bool maximized) -> ui::WindowFrameProvider* { + return theme->GetWindowFrameProvider(ui::FrameType::kDefault, + /*solid_frame=*/false, tiled, + maximized); + }, + base::Unretained(linux_ui_theme)); + auto nav_button_provider = + linux_ui_theme->CreateNavButtonProvider(ui::FrameType::kDefault); + return std::make_unique( + this, widget, std::move(nav_button_provider), std::move(getter)); + } return std::make_unique(this, widget); #endif @@ -1940,6 +1963,13 @@ LinuxFrameLayout* NativeWindowViews::GetLinuxFrameLayout() { auto* view = views::AsViewClass(ncv->frame_view()); return view ? view->GetLinuxFrameLayout() : nullptr; } + +views::FrameViewLinux* NativeWindowViews::GetFrameViewLinux() const { + auto* ncv = widget()->non_client_view(); + if (!ncv) + return nullptr; + return views::AsViewClass(ncv->frame_view()); +} #endif void NativeWindowViews::OnWidgetMove() { diff --git a/shell/browser/native_window_views.h b/shell/browser/native_window_views.h index eaeefd92c0..ba816acefd 100644 --- a/shell/browser/native_window_views.h +++ b/shell/browser/native_window_views.h @@ -30,10 +30,16 @@ namespace gin { class Arguments; } // namespace gin +#if BUILDFLAG(IS_LINUX) +namespace views { +class FrameViewLinux; +} // namespace views +#endif + namespace electron { #if BUILDFLAG(IS_LINUX) -class ClientFrameViewLinux; +class NativeFrameViewLinux; class GlobalMenuBarX11; class LinuxFrameLayout; #endif @@ -202,6 +208,7 @@ class NativeWindowViews : public NativeWindow, #if BUILDFLAG(IS_LINUX) LinuxFrameLayout* GetLinuxFrameLayout(); + views::FrameViewLinux* GetFrameViewLinux() const; #endif private: diff --git a/shell/browser/ui/electron_desktop_window_tree_host_linux.cc b/shell/browser/ui/electron_desktop_window_tree_host_linux.cc index 9ef0894da9..cc76f25709 100644 --- a/shell/browser/ui/electron_desktop_window_tree_host_linux.cc +++ b/shell/browser/ui/electron_desktop_window_tree_host_linux.cc @@ -10,19 +10,15 @@ #include -#include "base/feature_list.h" #include "base/i18n/rtl.h" #include "shell/browser/api/electron_api_web_contents.h" #include "shell/browser/linux/x11_util.h" -#include "shell/browser/native_window_features.h" #include "shell/browser/native_window_views.h" #include "shell/browser/ui/views/linux_frame_layout.h" -#include "third_party/skia/include/core/SkRegion.h" #include "ui/aura/window_delegate.h" #include "ui/base/hit_test.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/rect.h" -#include "ui/gfx/geometry/skia_conversions.h" #include "ui/linux/linux_ui.h" #include "ui/ozone/public/ozone_platform.h" #include "ui/platform_window/extensions/wayland_extension.h" @@ -30,6 +26,8 @@ #include "ui/platform_window/platform_window_init_properties.h" #include "ui/views/widget/desktop_aura/desktop_window_tree_host.h" #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h" +#include "ui/views/window/frame_view_linux.h" +#include "ui/views/window/frame_view_utils_linux.h" namespace electron { @@ -50,6 +48,12 @@ bool ElectronDesktopWindowTreeHostLinux::SupportsClientFrameShadow() const { void ElectronDesktopWindowTreeHostLinux::OnWidgetInitDone() { views::DesktopWindowTreeHostLinux::OnWidgetInitDone(); + + // SetSupportsClientFrameShadow must happen after widget init when + // platform_window is available. + if (auto* fvl = native_window_view_->GetFrameViewLinux()) + fvl->SetSupportsClientFrameShadow(SupportsClientFrameShadow()); + UpdateFrameHints(); } @@ -78,19 +82,22 @@ void ElectronDesktopWindowTreeHostLinux::Show( DesktopWindowTreeHostLinux::SetWindowIcons(saved_window_icon_, {}); } +gfx::Insets ElectronDesktopWindowTreeHostLinux::GetRestoredFrameBorderInsets() + const { + if (auto* fvl = native_window_view_->GetFrameViewLinux()) + return fvl->GetRestoredFrameBorderInsets(); + + auto* const layout = native_window_view_->GetLinuxFrameLayout(); + return layout ? layout->RestoredFrameBorderInsets() : gfx::Insets(); +} + gfx::Insets ElectronDesktopWindowTreeHostLinux::CalculateInsetsInDIP( ui::PlatformWindowState window_state) const { // If we are not showing frame, the insets should be zero. - if (!IsShowingFrame(window_state)) { + if (!IsShowingFrame(window_state)) return gfx::Insets(); - } - auto* const layout = native_window_view_->GetLinuxFrameLayout(); - if (!layout) - return {}; - - gfx::Insets insets = layout->RestoredFrameBorderInsets(); - return insets; + return GetRestoredFrameBorderInsets(); } // Electron treats min/max constraints as the logical window size, but Chromium @@ -101,11 +108,8 @@ std::optional ElectronDesktopWindowTreeHostLinux::GetMinimumSizeForWindow() const { auto min_size = views::DesktopWindowTreeHostLinux::GetMinimumSizeForWindow(); if (min_size.has_value()) { - auto* const layout = native_window_view_->GetLinuxFrameLayout(); - if (layout) { - gfx::Insets insets = layout->RestoredFrameBorderInsets(); - min_size->Enlarge(insets.width(), insets.height()); - } + gfx::Insets insets = GetRestoredFrameBorderInsets(); + min_size->Enlarge(insets.width(), insets.height()); } return min_size; } @@ -114,15 +118,12 @@ std::optional ElectronDesktopWindowTreeHostLinux::GetMaximumSizeForWindow() const { auto max_size = views::DesktopWindowTreeHostLinux::GetMaximumSizeForWindow(); if (max_size.has_value()) { - auto* const layout = native_window_view_->GetLinuxFrameLayout(); - if (layout) { - gfx::Insets insets = layout->RestoredFrameBorderInsets(); - // 0 means no constraint, so don't inflate. - if (max_size->width() > 0) - max_size->set_width(max_size->width() + insets.width()); - if (max_size->height() > 0) - max_size->set_height(max_size->height() + insets.height()); - } + gfx::Insets insets = GetRestoredFrameBorderInsets(); + // 0 means no constraint, so don't inflate. + if (max_size->width() > 0) + max_size->set_width(max_size->width() + insets.width()); + if (max_size->height() > 0) + max_size->set_height(max_size->height() + insets.height()); } return max_size; } @@ -151,15 +152,17 @@ void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged( void ElectronDesktopWindowTreeHostLinux::OnWindowTiledStateChanged( ui::WindowTiledEdges new_tiled_edges) { - if (auto* layout = native_window_view_->GetLinuxFrameLayout()) { - // GNOME on Ubuntu reports all edges as tiled - // even if the window is only half-tiled so do not trust individual edge - // values. - bool maximized = native_window_view_->IsMaximized(); - bool tiled = new_tiled_edges.top || new_tiled_edges.left || - new_tiled_edges.bottom || new_tiled_edges.right; - layout->set_tiled(tiled && !maximized); - } + // GNOME on Ubuntu reports all edges as tiled even if the window is only + // half-tiled, so do not trust individual edge values. + bool maximized = native_window_view_->IsMaximized(); + bool tiled = new_tiled_edges.top || new_tiled_edges.left || + new_tiled_edges.bottom || new_tiled_edges.right; + bool is_tiled = tiled && !maximized; + + if (auto* fvl = native_window_view_->GetFrameViewLinux()) + fvl->SetTiled(is_tiled); + else if (auto* layout = native_window_view_->GetLinuxFrameLayout()) + layout->set_tiled(is_tiled); UpdateFrameHints(); ScheduleRelayout(); if (GetWidget()->non_client_view()) { @@ -213,95 +216,61 @@ void ElectronDesktopWindowTreeHostLinux::OnDeviceScaleFactorChanged() { } void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() { - if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations)) { - auto* const layout = native_window_view_->GetLinuxFrameLayout(); - if (!layout) - return; - - ui::PlatformWindow* window = platform_window(); - auto window_state = window->GetPlatformWindowState(); - float scale = device_scale_factor(); - const gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size(); - - if (SupportsClientFrameShadow()) { - auto insets = CalculateInsetsInDIP(window_state); - if (insets.IsEmpty()) { - window->SetInputRegion(std::nullopt); - } else { - gfx::Rect input_bounds(widget_size); - input_bounds.Inset(insets - layout->GetInputInsets()); - input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale); - window->SetInputRegion( - std::optional>({input_bounds})); - } + if (native_window_view_->GetFrameViewLinux()) { + views::DesktopWindowTreeHostLinux::UpdateFrameHints(); + // Clear the opaque region for translucent windows. + if (native_window_view_->IsTranslucent() && + views::Widget::IsWindowCompositingSupported()) { + platform_window()->SetOpaqueRegion(std::vector{}); } - - if (ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported()) { - // Set the opaque region. - std::vector opaque_region; - if (native_window_view_->IsTranslucent()) { - // Leave opaque_region empty. - } else if (IsShowingFrame(window_state)) { - // The opaque region is a list of rectangles that contain only fully - // opaque pixels of the window. We need to convert the clipping - // rounded-rect into this format. - SkRRect rrect = layout->GetRoundedWindowBounds(); - gfx::RectF rectf(layout->GetWindowBounds()); - rectf.Scale(scale); - // It is acceptable to omit some pixels that are opaque, but the region - // must not include any translucent pixels. Therefore, we must - // conservatively scale to the enclosed rectangle. - gfx::Rect rect = gfx::ToEnclosedRect(rectf); - - // Create the initial region from the clipping rectangle without rounded - // corners. - SkRegion region(gfx::RectToSkIRect(rect)); - - // Now subtract out the small rectangles that cover the corners. - struct { - SkRRect::Corner corner; - bool left; - bool upper; - } kCorners[] = { - {SkRRect::kUpperLeft_Corner, true, true}, - {SkRRect::kUpperRight_Corner, false, true}, - {SkRRect::kLowerLeft_Corner, true, false}, - {SkRRect::kLowerRight_Corner, false, false}, - }; - for (const auto& corner : kCorners) { - auto radii = rrect.radii(corner.corner); - auto rx = std::ceil(scale * radii.x()); - auto ry = std::ceil(scale * radii.y()); - auto corner_rect = SkIRect::MakeXYWH( - corner.left ? rect.x() : rect.right() - rx, - corner.upper ? rect.y() : rect.bottom() - ry, rx, ry); - region.op(corner_rect, SkRegion::kDifference_Op); - } - - auto translucent_top_area_rect = SkIRect::MakeXYWH( - rect.x(), rect.y(), rect.width(), - std::ceil(layout->GetTranslucentTopAreaHeight() * scale - - rect.y())); - region.op(translucent_top_area_rect, SkRegion::kDifference_Op); - - // Convert the region to a list of rectangles. - for (SkRegion::Iterator i(region); !i.done(); i.next()) { - opaque_region.push_back(gfx::SkIRectToRect(i.rect())); - } - } else { - // The entire window except for the translucent top is opaque. - gfx::Rect opaque_region_dip(widget_size); - gfx::Insets insets; - insets.set_top(layout->GetTranslucentTopAreaHeight()); - opaque_region_dip.Inset(insets); - opaque_region.push_back( - gfx::ScaleToEnclosingRect(opaque_region_dip, scale)); - } - window->SetOpaqueRegion(opaque_region); - } - SizeConstraintsChanged(); + return; } + + auto* layout = native_window_view_->GetLinuxFrameLayout(); + if (!layout) + return; + + ui::PlatformWindow* window = platform_window(); + auto window_state = window->GetPlatformWindowState(); + float scale = device_scale_factor(); + const gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size(); + + if (SupportsClientFrameShadow()) { + auto insets = CalculateInsetsInDIP(window_state); + if (insets.IsEmpty()) { + window->SetInputRegion(std::nullopt); + } else { + gfx::Rect input_bounds(widget_size); + input_bounds.Inset(insets - layout->GetInputInsets()); + input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale); + window->SetInputRegion( + std::optional>({input_bounds})); + } + } + + if (ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported()) { + // Set the opaque region. + std::vector opaque_region; + if (native_window_view_->IsTranslucent()) { + // Leave opaque_region empty. + } else if (IsShowingFrame(window_state)) { + opaque_region = views::GetRestoredOpaqueRegion( + layout->GetRoundedWindowBounds(), scale, + layout->GetTranslucentTopAreaHeight()); + } else { + // The entire window except for the translucent top is opaque. + gfx::Rect opaque_region_dip(widget_size); + gfx::Insets insets; + insets.set_top(layout->GetTranslucentTopAreaHeight()); + opaque_region_dip.Inset(insets); + opaque_region.push_back( + gfx::ScaleToEnclosingRect(opaque_region_dip, scale)); + } + window->SetOpaqueRegion(opaque_region); + } + + SizeConstraintsChanged(); } void ElectronDesktopWindowTreeHostLinux::DispatchEvent(ui::Event* event) { diff --git a/shell/browser/ui/electron_desktop_window_tree_host_linux.h b/shell/browser/ui/electron_desktop_window_tree_host_linux.h index ff359b6e71..f63124d9ec 100644 --- a/shell/browser/ui/electron_desktop_window_tree_host_linux.h +++ b/shell/browser/ui/electron_desktop_window_tree_host_linux.h @@ -16,7 +16,6 @@ #include "ui/gfx/image/image_skia.h" #include "ui/linux/device_scale_factor_observer.h" #include "ui/linux/linux_ui.h" -#include "ui/native_theme/native_theme_observer.h" #include "ui/platform_window/platform_window.h" #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h" @@ -78,12 +77,13 @@ class ElectronDesktopWindowTreeHostLinux bool IsShowingFrame(ui::PlatformWindowState window_state) const; + // Returns restored frame border insets regardless of current widget state. + gfx::Insets GetRestoredFrameBorderInsets() const; + gfx::ImageSkia saved_window_icon_; raw_ptr native_window_view_; // weak ref - base::ScopedObservation - theme_observation_{this}; base::ScopedObservation scale_observation_{this}; ui::PlatformWindowState window_state_ = ui::PlatformWindowState::kUnknown; diff --git a/shell/browser/ui/views/client_frame_view_linux.cc b/shell/browser/ui/views/client_frame_view_linux.cc deleted file mode 100644 index 19b061f767..0000000000 --- a/shell/browser/ui/views/client_frame_view_linux.cc +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright (c) 2021 Ryan Gonzalez. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/browser/ui/views/client_frame_view_linux.h" - -#include - -#include "base/strings/utf_string_conversions.h" -#include "cc/paint/paint_filter.h" -#include "cc/paint/paint_flags.h" -#include "shell/browser/native_window_views.h" -#include "shell/browser/ui/views/frameless_view.h" -#include "shell/browser/ui/views/linux_frame_layout.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/ui_base_types.h" -#include "ui/gfx/canvas.h" -#include "ui/gfx/font_list.h" -#include "ui/gfx/geometry/insets.h" -#include "ui/gfx/geometry/rect.h" -#include "ui/gfx/text_constants.h" -#include "ui/gtk/gtk_compat.h" // nogncheck -#include "ui/gtk/gtk_util.h" // nogncheck -#include "ui/linux/linux_ui.h" -#include "ui/linux/nav_button_provider.h" -#include "ui/native_theme/native_theme.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/style/typography.h" -#include "ui/views/widget/widget.h" -#include "ui/views/window/frame_buttons.h" -#include "ui/views/window/window_button_order_provider.h" - -namespace electron { - -namespace { - -ui::NavButtonProvider::ButtonState ButtonStateToNavButtonProviderState( - 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 - -ClientFrameViewLinux::ClientFrameViewLinux(NativeWindowViews* window, - views::Widget* frame) - : FramelessView{window, frame}, - theme_{ui::NativeTheme::GetInstanceForNativeUi()}, - nav_button_provider_( - ui::LinuxUiTheme::GetForProfile(nullptr)->CreateNavButtonProvider( - ui::FrameType::kDefault)), - nav_buttons_{ - NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kClose, - views::FrameButton::kClose, &views::Widget::Close, - IDS_APP_ACCNAME_CLOSE, HTCLOSE}, - NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kMaximize, - views::FrameButton::kMaximize, &views::Widget::Maximize, - IDS_APP_ACCNAME_MAXIMIZE, HTMAXBUTTON}, - NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kRestore, - views::FrameButton::kMaximize, &views::Widget::Restore, - IDS_APP_ACCNAME_RESTORE, HTMAXBUTTON}, - NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kMinimize, - views::FrameButton::kMinimize, &views::Widget::Minimize, - IDS_APP_ACCNAME_MINIMIZE, HTMINBUTTON}, - }, - trailing_frame_buttons_{views::FrameButton::kMinimize, - views::FrameButton::kMaximize, - views::FrameButton::kClose} { - for (auto& button : nav_buttons_) { - auto image_button = std::make_unique(); - image_button->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE); - image_button->GetViewAccessibility().SetName( - l10n_util::GetStringUTF16(button.accessibility_id)); - button.button = AddChildView(std::move(image_button)); - } - - auto title = std::make_unique(); - title->SetSubpixelRenderingEnabled(false); - title->SetAutoColorReadabilityEnabled(false); - title->SetHorizontalAlignment(gfx::ALIGN_CENTER); - title->SetVerticalAlignment(gfx::ALIGN_MIDDLE); - title->SetTextStyle(views::style::STYLE_TAB_ACTIVE); - title_ = AddChildView(std::move(title)); - - native_theme_observer_.Observe(theme_); - - if (auto* ui = ui::LinuxUi::instance()) { - ui->AddWindowButtonOrderObserver(this); - OnWindowButtonOrderingChange(); - } - linux_frame_layout_ = std::make_unique(window); - - // Unretained() is safe because the subscription is saved into an instance - // member and thus will be cancelled upon the instance's destruction. - paint_as_active_changed_subscription_ = - frame->RegisterPaintAsActiveChangedCallback(base::BindRepeating( - &ClientFrameViewLinux::PaintAsActiveChanged, base::Unretained(this))); - - UpdateWindowTitle(); - - for (auto& button : nav_buttons_) { - // Unretained() is safe because the buttons are added as children to, and - // thus owned by, this view. Thus, the buttons themselves will be destroyed - // when this view is destroyed, and the frame's life must never outlive the - // view. - button.button->SetCallback( - base::BindRepeating(button.callback, base::Unretained(frame))); - } - - UpdateThemeValues(); -} - -ClientFrameViewLinux::~ClientFrameViewLinux() { - if (auto* ui = ui::LinuxUi::instance()) - ui->RemoveWindowButtonOrderObserver(this); - theme_->RemoveObserver(this); -} - -gfx::Insets ClientFrameViewLinux::RestoredFrameBorderInsets() const { - return linux_frame_layout_->RestoredFrameBorderInsets(); -} - -LinuxFrameLayout* ClientFrameViewLinux::GetLinuxFrameLayout() const { - return linux_frame_layout_.get(); -} - -void ClientFrameViewLinux::OnNativeThemeUpdated( - ui::NativeTheme* observed_theme) { - UpdateThemeValues(); -} - -void ClientFrameViewLinux::OnWindowButtonOrderingChange() { - auto* provider = views::WindowButtonOrderProvider::GetInstance(); - leading_frame_buttons_ = provider->leading_buttons(); - trailing_frame_buttons_ = provider->trailing_buttons(); - - InvalidateLayout(); -} - -int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) { - return ResizingBorderHitTestImpl( - point, linux_frame_layout_->GetResizeBorderInsets()); -} - -gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const { - gfx::Rect client_bounds = bounds(); - if (!frame_->IsFullscreen()) { - client_bounds.Inset(linux_frame_layout_->FrameBorderInsets(false)); - client_bounds.Inset( - gfx::Insets::TLBR(GetTitlebarBounds().height(), 0, 0, 0)); - } - return client_bounds; -} - -gfx::Rect ClientFrameViewLinux::GetWindowBoundsForClientBounds( - const gfx::Rect& client_bounds) const { - gfx::Insets insets = bounds().InsetsFrom(GetBoundsForClientView()); - return gfx::Rect(std::max(0, client_bounds.x() - insets.left()), - std::max(0, client_bounds.y() - insets.top()), - client_bounds.width() + insets.width(), - client_bounds.height() + insets.height()); -} - -int ClientFrameViewLinux::NonClientHitTest(const gfx::Point& point) { - int component = ResizingBorderHitTest(point); - if (component != HTNOWHERE) { - return component; - } - - for (auto& button : nav_buttons_) { - if (button.button->GetVisible() && - button.button->GetMirroredBounds().Contains(point)) { - return button.hit_test_id; - } - } - - if (GetTitlebarBounds().Contains(point)) { - return HTCAPTION; - } - - return FramelessView::NonClientHitTest(point); -} - -void ClientFrameViewLinux::GetWindowMask(const gfx::Size& size, - SkPath* window_mask) { - // Nothing to do here, as transparency is used for decorations, not masks. -} - -void ClientFrameViewLinux::UpdateWindowTitle() { - title_->SetText(base::UTF8ToUTF16(window_->GetTitle())); -} - -void ClientFrameViewLinux::SizeConstraintsChanged() { - InvalidateLayout(); -} - -void ClientFrameViewLinux::Layout(PassKey) { - LayoutSuperclass(this); - - if (frame_->IsFullscreen()) { - // Just hide everything and return. - for (NavButton& button : nav_buttons_) { - button.button->SetVisible(false); - } - - title_->SetVisible(false); - return; - } - - UpdateButtonImages(); - LayoutButtons(); - - gfx::Rect title_bounds(GetTitlebarContentBounds()); - title_bounds.Inset(theme_values_.title_padding); - - title_->SetVisible(true); - title_->SetBoundsRect(title_bounds); -} - -void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) { - if (frame_->IsFullscreen()) { - return; - } - - if (frame_->IsMaximized()) { - // Some GTK themes (Breeze) still render shadow/border assets when - // maximized, and we don't need a border when maximized anyway. Chromium - // switches on this too: OpaqueBrowserFrameView::PaintMaximizedFrameBorder. - PaintMaximizedFrameBorder(canvas); - } else { - PaintRestoredFrameBorder(canvas); - } -} - -void ClientFrameViewLinux::PaintRestoredFrameBorder(gfx::Canvas* canvas) { - if (auto* frame_provider = linux_frame_layout_->GetFrameProvider()) { - frame_provider->PaintWindowFrame( - canvas, GetLocalBounds(), GetTitlebarBounds().bottom(), - ShouldPaintAsActive(), linux_frame_layout_->GetInputInsets()); - } -} - -void ClientFrameViewLinux::PaintMaximizedFrameBorder(gfx::Canvas* canvas) { - ui::NativeTheme::FrameTopAreaExtraParams frame_top_area; - frame_top_area.use_custom_frame = true; - frame_top_area.is_active = ShouldPaintAsActive(); - frame_top_area.default_background_color = SK_ColorTRANSPARENT; - ui::NativeTheme::ExtraParams params(frame_top_area); - GetNativeTheme()->Paint( - canvas->sk_canvas(), GetColorProvider(), ui::NativeTheme::kFrameTopArea, - ui::NativeTheme::kNormal, - gfx::Rect(0, 0, width(), GetTitlebarBounds().bottom()), params); -} - -void ClientFrameViewLinux::PaintAsActiveChanged() { - UpdateThemeValues(); -} - -void ClientFrameViewLinux::UpdateThemeValues() { - gtk::GtkCssContext window_context = - gtk::AppendCssNodeToStyleContext({}, "window.background.csd"); - gtk::GtkCssContext headerbar_context = gtk::AppendCssNodeToStyleContext( - window_context, "headerbar.default-decoration.titlebar"); - gtk::GtkCssContext title_context = - gtk::AppendCssNodeToStyleContext(headerbar_context, "label.title"); - // ShouldPaintAsActive asks the widget, so assume active if the widget is not - // set yet. - if (GetWidget() != nullptr && !ShouldPaintAsActive()) { - gtk_style_context_set_state(window_context, GTK_STATE_FLAG_BACKDROP); - gtk_style_context_set_state(headerbar_context, GTK_STATE_FLAG_BACKDROP); - gtk_style_context_set_state(title_context, GTK_STATE_FLAG_BACKDROP); - } - - theme_values_.window_border_radius = - linux_frame_layout_->GetTopCornerRadiusDip(); - - gtk::GtkStyleContextGet(headerbar_context, "min-height", - &theme_values_.titlebar_min_height, nullptr); - theme_values_.titlebar_padding = - gtk::GtkStyleContextGetPadding(headerbar_context); - - theme_values_.title_color = gtk::GtkStyleContextGetColor(title_context); - theme_values_.title_padding = gtk::GtkStyleContextGetPadding(title_context); - - title_->SetEnabledColor(theme_values_.title_color); - - InvalidateLayout(); - SchedulePaint(); -} - -ui::NavButtonProvider::FrameButtonDisplayType -ClientFrameViewLinux::GetButtonTypeToSkip() const { - return frame_->IsMaximized() - ? ui::NavButtonProvider::FrameButtonDisplayType::kMaximize - : ui::NavButtonProvider::FrameButtonDisplayType::kRestore; -} - -void ClientFrameViewLinux::UpdateButtonImages() { - int top_area_height = theme_values_.titlebar_min_height + - theme_values_.titlebar_padding.height(); - nav_button_provider_->RedrawImages(top_area_height, frame_->IsMaximized(), - ShouldPaintAsActive()); - - ui::NavButtonProvider::FrameButtonDisplayType skip_type = - GetButtonTypeToSkip(); - - for (NavButton& button : nav_buttons_) { - if (button.type == skip_type) { - continue; - } - - for (size_t state_id = 0; state_id < views::Button::STATE_COUNT; - state_id++) { - views::Button::ButtonState state = - static_cast(state_id); - button.button->SetImageModel( - state, ui::ImageModel::FromImageSkia(nav_button_provider_->GetImage( - button.type, ButtonStateToNavButtonProviderState(state)))); - } - } -} - -void ClientFrameViewLinux::LayoutButtons() { - for (NavButton& button : nav_buttons_) { - button.button->SetVisible(false); - } - - gfx::Rect remaining_content_bounds = GetTitlebarContentBounds(); - LayoutButtonsOnSide(ButtonSide::kLeading, &remaining_content_bounds); - LayoutButtonsOnSide(ButtonSide::kTrailing, &remaining_content_bounds); -} - -void ClientFrameViewLinux::LayoutButtonsOnSide( - ButtonSide side, - gfx::Rect* remaining_content_bounds) { - ui::NavButtonProvider::FrameButtonDisplayType skip_type = - GetButtonTypeToSkip(); - - std::vector frame_buttons; - - switch (side) { - case ButtonSide::kLeading: - frame_buttons = leading_frame_buttons_; - break; - case ButtonSide::kTrailing: - frame_buttons = trailing_frame_buttons_; - // We always lay buttons out going from the edge towards the center, but - // they are given to us as left-to-right, so reverse them. - std::ranges::reverse(frame_buttons); - break; - default: - NOTREACHED(); - } - - for (views::FrameButton frame_button : frame_buttons) { - auto button = - std::ranges::find_if(nav_buttons_, [&](const NavButton& test) { - return test.type != skip_type && test.frame_button == frame_button; - }); - CHECK(button != nav_buttons_.end()) - << "Failed to find frame button: " << static_cast(frame_button); - - if (button->type == skip_type) { - continue; - } - - button->button->SetVisible(true); - - // CSS min-size/height/width is not enough to determine the actual size of - // the buttons, so we sample the rendered image. See Chromium's - // BrowserFrameViewLinuxNative::MaybeUpdateCachedFrameButtonImages. - int button_width = - nav_button_provider_ - ->GetImage(button->type, - ui::NavButtonProvider::ButtonState::kNormal) - .width(); - int next_button_offset = - button_width + nav_button_provider_->GetInterNavButtonSpacing(); - - int x_position = 0; - gfx::Insets inset_after_placement; - - switch (side) { - case ButtonSide::kLeading: - x_position = remaining_content_bounds->x(); - inset_after_placement.set_left(next_button_offset); - break; - case ButtonSide::kTrailing: - x_position = remaining_content_bounds->right() - button_width; - inset_after_placement.set_right(next_button_offset); - break; - default: - NOTREACHED(); - } - - button->button->SetBounds(x_position, remaining_content_bounds->y(), - button_width, remaining_content_bounds->height()); - remaining_content_bounds->Inset(inset_after_placement); - } -} - -gfx::Rect ClientFrameViewLinux::GetTitlebarBounds() const { - if (frame_->IsFullscreen()) { - return {}; - } - - int font_height = gfx::FontList().GetHeight(); - int titlebar_height = - std::max(font_height, theme_values_.titlebar_min_height) + - GetTitlebarContentInsets().height(); - - gfx::Insets decoration_insets = linux_frame_layout_->FrameBorderInsets(false); - - // We add the inset height here, so the .Inset() that follows won't reduce it - // to be too small. - gfx::Rect titlebar(width(), titlebar_height + decoration_insets.height()); - titlebar.Inset(decoration_insets); - return titlebar; -} - -gfx::Insets ClientFrameViewLinux::GetTitlebarContentInsets() const { - return theme_values_.titlebar_padding + - nav_button_provider_->GetTopAreaSpacing(); -} - -gfx::Rect ClientFrameViewLinux::GetTitlebarContentBounds() const { - gfx::Rect titlebar(GetTitlebarBounds()); - titlebar.Inset(GetTitlebarContentInsets()); - return titlebar; -} -views::View* ClientFrameViewLinux::TargetForRect(views::View* root, - const gfx::Rect& rect) { - return views::FrameView::TargetForRect(root, rect); -} - -BEGIN_METADATA(ClientFrameViewLinux) END_METADATA - -} // namespace electron diff --git a/shell/browser/ui/views/client_frame_view_linux.h b/shell/browser/ui/views/client_frame_view_linux.h deleted file mode 100644 index 9feee46864..0000000000 --- a/shell/browser/ui/views/client_frame_view_linux.h +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2021 Ryan Gonzalez. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_CLIENT_FRAME_VIEW_LINUX_H_ -#define ELECTRON_SHELL_BROWSER_UI_VIEWS_CLIENT_FRAME_VIEW_LINUX_H_ - -#include -#include -#include - -#include "base/memory/raw_ptr.h" -#include "base/memory/raw_ptr_exclusion.h" -#include "base/scoped_observation.h" -#include "shell/browser/ui/views/frameless_view.h" -#include "shell/browser/ui/views/linux_frame_layout.h" -#include "ui/base/metadata/metadata_header_macros.h" -#include "ui/base/ui_base_types.h" -#include "ui/linux/linux_ui.h" -#include "ui/linux/nav_button_provider.h" -#include "ui/linux/window_button_order_observer.h" -#include "ui/native_theme/native_theme.h" -#include "ui/native_theme/native_theme_observer.h" -#include "ui/views/controls/button/image_button.h" -#include "ui/views/controls/label.h" -#include "ui/views/widget/widget.h" -#include "ui/views/window/frame_buttons.h" - -namespace electron { - -class NativeWindowViews; - -class ClientFrameViewLinux : public FramelessView, - private ui::NativeThemeObserver, - private ui::WindowButtonOrderObserver { - METADATA_HEADER(ClientFrameViewLinux, FramelessView) - - public: - ClientFrameViewLinux(NativeWindowViews* window, views::Widget* frame); - ~ClientFrameViewLinux() override; - - // FramelessView: - gfx::Insets RestoredFrameBorderInsets() const override; - LinuxFrameLayout* GetLinuxFrameLayout() const override; - - protected: - // ui::NativeThemeObserver: - void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override; - - // views::WindowButtonOrderObserver: - void OnWindowButtonOrderingChange() override; - - // Overridden from FramelessView: - int ResizingBorderHitTest(const gfx::Point& point) override; - - // Overridden from 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 UpdateWindowTitle() override; - void SizeConstraintsChanged() override; - - // Overridden from View: - void Layout(PassKey) override; - void OnPaint(gfx::Canvas* canvas) override; - - // Overridden from views::ViewTargeterDelegate - views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override; - - private: - static constexpr int kNavButtonCount = 4; - - struct NavButton { - ui::NavButtonProvider::FrameButtonDisplayType type; - views::FrameButton frame_button; - void (views::Widget::*callback)(); - int accessibility_id; - int hit_test_id; - raw_ptr button = {}; - }; - - struct ThemeValues { - float window_border_radius; - - int titlebar_min_height; - gfx::Insets titlebar_padding; - - SkColor title_color; - gfx::Insets title_padding; - }; - - void PaintAsActiveChanged(); - void PaintRestoredFrameBorder(gfx::Canvas* canvas); - void PaintMaximizedFrameBorder(gfx::Canvas* canvas); - - void UpdateThemeValues(); - - enum class ButtonSide { kLeading, kTrailing }; - - ui::NavButtonProvider::FrameButtonDisplayType GetButtonTypeToSkip() const; - void UpdateButtonImages(); - void LayoutButtons(); - void LayoutButtonsOnSide(ButtonSide side, - gfx::Rect* remaining_content_bounds); - - gfx::Rect GetTitlebarBounds() const; - gfx::Insets GetTitlebarContentInsets() const; - gfx::Rect GetTitlebarContentBounds() const; - - std::unique_ptr linux_frame_layout_; - - raw_ptr theme_; - ThemeValues theme_values_; - - raw_ptr title_; - - std::unique_ptr nav_button_provider_; - std::array nav_buttons_; - - std::vector leading_frame_buttons_; - std::vector trailing_frame_buttons_; - - base::ScopedObservation - native_theme_observer_{this}; - base::ScopedObservation - window_button_order_observer_{this}; - - base::CallbackListSubscription paint_as_active_changed_subscription_; -}; - -} // namespace electron - -#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_CLIENT_FRAME_VIEW_LINUX_H_ diff --git a/shell/browser/ui/views/native_frame_view_linux.cc b/shell/browser/ui/views/native_frame_view_linux.cc new file mode 100644 index 0000000000..1582452c6a --- /dev/null +++ b/shell/browser/ui/views/native_frame_view_linux.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2026 Athul Iddya. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/ui/views/native_frame_view_linux.h" + +#include + +#include "shell/browser/native_window_views.h" +#include "ui/base/hit_test.h" +#include "ui/base/metadata/metadata_impl_macros.h" + +namespace electron { + +NativeFrameViewLinux::NativeFrameViewLinux( + NativeWindowViews* window, + views::Widget* widget, + std::unique_ptr nav_button_provider, + views::NativeFrameViewLayoutLinux::FrameProviderGetter getter) + : views::NativeFrameViewLinux(widget, + std::move(nav_button_provider), + std::move(getter)), + window_(window) { + InitViews(); +} + +NativeFrameViewLinux::~NativeFrameViewLinux() = default; + +int NativeFrameViewLinux::NonClientHitTest(const gfx::Point& point) { + int result = views::NativeFrameViewLinux::NonClientHitTest(point); + if (result != HTCLIENT) + return result; + + int contents_hit_test = window_->NonClientHitTest(point); + if (contents_hit_test != HTNOWHERE) + return contents_hit_test; + + return HTCLIENT; +} + +gfx::Size NativeFrameViewLinux::GetMinimumSize() const { + return window_->GetMinimumSize(); +} + +gfx::Size NativeFrameViewLinux::GetMaximumSize() const { + return window_->GetMaximumSize(); +} + +BEGIN_METADATA(NativeFrameViewLinux) +END_METADATA + +} // namespace electron diff --git a/shell/browser/ui/views/native_frame_view_linux.h b/shell/browser/ui/views/native_frame_view_linux.h new file mode 100644 index 0000000000..aec15ef48c --- /dev/null +++ b/shell/browser/ui/views/native_frame_view_linux.h @@ -0,0 +1,44 @@ +// Copyright (c) 2026 Athul Iddya. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_LINUX_H_ +#define ELECTRON_SHELL_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_LINUX_H_ + +#include + +#include "base/memory/raw_ptr.h" +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/linux/nav_button_provider.h" +#include "ui/views/window/native_frame_view_layout_linux.h" +#include "ui/views/window/native_frame_view_linux.h" + +namespace electron { + +class NativeWindowViews; + +// Extends views::NativeFrameViewLinux for Electron integration, such as +// app-defined draggable regions and frame sizing. +class NativeFrameViewLinux : public views::NativeFrameViewLinux { + METADATA_HEADER(NativeFrameViewLinux, views::NativeFrameViewLinux) + + public: + NativeFrameViewLinux( + NativeWindowViews* window, + views::Widget* widget, + std::unique_ptr nav_button_provider, + views::NativeFrameViewLayoutLinux::FrameProviderGetter getter); + ~NativeFrameViewLinux() override; + + // views::FrameViewLinux: + int NonClientHitTest(const gfx::Point& point) override; + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; + + private: + raw_ptr window_ = nullptr; +}; + +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_LINUX_H_