From 2b341e5e5b26f595da35b9676b954748186c3999 Mon Sep 17 00:00:00 2001 From: Athul Iddya Date: Tue, 28 Apr 2026 09:29:25 -0700 Subject: [PATCH] refactor: replace ClientFrameViewLinux with NativeFrameViewLinux (#51160) Replace ClientFrameViewLinux with electron::NativeFrameViewLinux, a thin wrapper over views::NativeFrameViewLinux. The wrapper provides Electron integration, such as draggable region support in NonClientHitTest, and adapting to Electron's sizing conventions. ElectronDesktopWindowTreeHostLinux and NativeWindowViews now use FrameViewLinux to query frame geometry and update window states in addition to LinuxFrameLayout. Assisted-By: Claude Opus 4.6, Claude Code --- filenames.gni | 6 +- shell/browser/native_window.cc | 15 +- shell/browser/native_window_features.cc | 11 - shell/browser/native_window_features.h | 14 - shell/browser/native_window_views.cc | 48 +- shell/browser/native_window_views.h | 9 +- ...electron_desktop_window_tree_host_linux.cc | 211 ++++---- .../electron_desktop_window_tree_host_linux.h | 6 +- .../ui/views/client_frame_view_linux.cc | 455 ------------------ .../ui/views/client_frame_view_linux.h | 135 ------ .../ui/views/native_frame_view_linux.cc | 52 ++ .../ui/views/native_frame_view_linux.h | 44 ++ 12 files changed, 246 insertions(+), 760 deletions(-) delete mode 100644 shell/browser/native_window_features.cc delete mode 100644 shell/browser/native_window_features.h delete mode 100644 shell/browser/ui/views/client_frame_view_linux.cc delete mode 100644 shell/browser/ui/views/client_frame_view_linux.h create mode 100644 shell/browser/ui/views/native_frame_view_linux.cc create mode 100644 shell/browser/ui/views/native_frame_view_linux.h 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_