fix: Add support for Wayland window decorations (#29618)
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com> Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
This commit is contained in:
		
					parent
					
						
							
								cabad35383
							
						
					
				
			
			
				commit
				
					
						7caa88c46f
					
				
			
		
					 12 changed files with 914 additions and 16 deletions
				
			
		|  | @ -32,10 +32,13 @@ filenames = { | |||
|     "shell/browser/notifications/linux/notification_presenter_linux.cc", | ||||
|     "shell/browser/notifications/linux/notification_presenter_linux.h", | ||||
|     "shell/browser/relauncher_linux.cc", | ||||
|     "shell/browser/ui/electron_desktop_window_tree_host_linux.cc", | ||||
|     "shell/browser/ui/file_dialog_gtk.cc", | ||||
|     "shell/browser/ui/message_box_gtk.cc", | ||||
|     "shell/browser/ui/tray_icon_gtk.cc", | ||||
|     "shell/browser/ui/tray_icon_gtk.h", | ||||
|     "shell/browser/ui/views/client_frame_view_linux.cc", | ||||
|     "shell/browser/ui/views/client_frame_view_linux.h", | ||||
|     "shell/common/application_info_linux.cc", | ||||
|     "shell/common/language_util_linux.cc", | ||||
|     "shell/common/node_bindings_linux.cc", | ||||
|  | @ -413,6 +416,8 @@ filenames = { | |||
|     "shell/browser/native_browser_view.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", | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include "base/values.h" | ||||
| #include "content/public/browser/web_contents_user_data.h" | ||||
| #include "shell/browser/browser.h" | ||||
| #include "shell/browser/native_window_features.h" | ||||
| #include "shell/browser/window_list.h" | ||||
| #include "shell/common/color_util.h" | ||||
| #include "shell/common/gin_helper/dictionary.h" | ||||
|  | @ -25,6 +26,11 @@ | |||
| #include "ui/display/win/screen_win.h" | ||||
| #endif | ||||
| 
 | ||||
| #if defined(USE_OZONE) || defined(USE_X11) | ||||
| #include "ui/base/ui_base_features.h" | ||||
| #include "ui/ozone/public/ozone_platform.h" | ||||
| #endif | ||||
| 
 | ||||
| namespace gin { | ||||
| 
 | ||||
| template <> | ||||
|  | @ -108,6 +114,17 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options, | |||
|   if (parent) | ||||
|     options.Get("modal", &is_modal_); | ||||
| 
 | ||||
| #if defined(USE_OZONE) | ||||
|   // Ozone X11 likes to prefer custom frames, but we don't need them unless
 | ||||
|   // on Wayland.
 | ||||
|   if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations) && | ||||
|       !ui::OzonePlatform::GetInstance() | ||||
|            ->GetPlatformRuntimeProperties() | ||||
|            .supports_server_side_window_decorations) { | ||||
|     has_client_frame_ = true; | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
|   WindowList::AddWindow(this); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -332,6 +332,7 @@ class NativeWindow : public base::SupportsUserData, | |||
|   bool has_frame() const { return has_frame_; } | ||||
|   void set_has_frame(bool has_frame) { has_frame_ = has_frame; } | ||||
| 
 | ||||
|   bool has_client_frame() const { return has_client_frame_; } | ||||
|   bool transparent() const { return transparent_; } | ||||
|   bool enable_larger_than_screen() const { return enable_larger_than_screen_; } | ||||
| 
 | ||||
|  | @ -381,6 +382,11 @@ class NativeWindow : public base::SupportsUserData, | |||
|   // Whether window has standard frame.
 | ||||
|   bool has_frame_ = true; | ||||
| 
 | ||||
|   // Whether window has standard frame, but it's drawn by Electron (the client
 | ||||
|   // application) instead of the OS. Currently only has meaning on Linux for
 | ||||
|   // Wayland hosts.
 | ||||
|   bool has_client_frame_ = false; | ||||
| 
 | ||||
|   // Whether window is transparent.
 | ||||
|   bool transparent_ = false; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								shell/browser/native_window_features.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								shell/browser/native_window_features.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| // 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 { | ||||
| const base::Feature kWaylandWindowDecorations{ | ||||
|     "WaylandWindowDecorations", base::FEATURE_DISABLED_BY_DEFAULT}; | ||||
| } | ||||
							
								
								
									
										14
									
								
								shell/browser/native_window_features.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								shell/browser/native_window_features.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| // 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_
 | ||||
|  | @ -19,6 +19,7 @@ | |||
| #include "content/public/browser/desktop_media_id.h" | ||||
| #include "shell/browser/api/electron_api_web_contents.h" | ||||
| #include "shell/browser/native_browser_view_views.h" | ||||
| #include "shell/browser/native_window_features.h" | ||||
| #include "shell/browser/ui/drag_util.h" | ||||
| #include "shell/browser/ui/inspectable_web_contents.h" | ||||
| #include "shell/browser/ui/inspectable_web_contents_view.h" | ||||
|  | @ -47,9 +48,11 @@ | |||
| #include "base/strings/string_util.h" | ||||
| #include "shell/browser/browser.h" | ||||
| #include "shell/browser/linux/unity_service.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/frameless_view.h" | ||||
| #include "shell/browser/ui/views/native_frame_view.h" | ||||
| #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h" | ||||
| #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" | ||||
| #include "ui/views/window/native_frame_view.h" | ||||
| 
 | ||||
| #if defined(USE_X11) | ||||
|  | @ -78,7 +81,6 @@ | |||
| #include "ui/display/screen.h" | ||||
| #include "ui/display/win/screen_win.h" | ||||
| #include "ui/gfx/color_utils.h" | ||||
| #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" | ||||
| #endif | ||||
| 
 | ||||
| namespace electron { | ||||
|  | @ -227,9 +229,10 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, | |||
|   params.bounds = bounds; | ||||
|   params.delegate = this; | ||||
|   params.type = views::Widget::InitParams::TYPE_WINDOW; | ||||
|   params.remove_standard_frame = !has_frame(); | ||||
|   params.remove_standard_frame = !has_frame() || has_client_frame(); | ||||
| 
 | ||||
|   if (transparent()) | ||||
|   // If a client frame, we need to draw our own shadows.
 | ||||
|   if (transparent() || has_client_frame()) | ||||
|     params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; | ||||
| 
 | ||||
|   // The given window is most likely not rectangular since it uses
 | ||||
|  | @ -253,6 +256,13 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, | |||
|   // Set WM_CLASS.
 | ||||
|   params.wm_class_name = base::ToLowerASCII(name); | ||||
|   params.wm_class_class = name; | ||||
| 
 | ||||
|   if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations)) { | ||||
|     auto* native_widget = new views::DesktopNativeWidgetAura(widget()); | ||||
|     params.native_widget = native_widget; | ||||
|     params.desktop_window_tree_host = | ||||
|         new ElectronDesktopWindowTreeHostLinux(this, native_widget); | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
|   widget()->Init(std::move(params)); | ||||
|  | @ -337,7 +347,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, | |||
|   ::SetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE, ex_style); | ||||
| #endif | ||||
| 
 | ||||
|   if (has_frame()) { | ||||
|   if (has_frame() && !has_client_frame()) { | ||||
|     // TODO(zcbenz): This was used to force using native frame on Windows 2003,
 | ||||
|     // we should check whether setting it in InitParams can work.
 | ||||
|     widget()->set_frame_type(views::Widget::FrameType::kForceNative); | ||||
|  | @ -1553,7 +1563,7 @@ bool NativeWindowViews::ShouldDescendIntoChildForEventHandling( | |||
|     return false; | ||||
| 
 | ||||
|   // And the events on border for dragging resizable frameless window.
 | ||||
|   if (!has_frame() && resizable_) { | ||||
|   if ((!has_frame() || has_client_frame()) && resizable_) { | ||||
|     auto* frame = | ||||
|         static_cast<FramelessView*>(widget()->non_client_view()->frame_view()); | ||||
|     return frame->ResizingBorderHitTest(location) == HTNOWHERE; | ||||
|  | @ -1573,10 +1583,12 @@ NativeWindowViews::CreateNonClientFrameView(views::Widget* widget) { | |||
|   frame_view->Init(this, widget); | ||||
|   return frame_view; | ||||
| #else | ||||
|   if (has_frame()) { | ||||
|   if (has_frame() && !has_client_frame()) { | ||||
|     return std::make_unique<NativeFrameView>(this, widget); | ||||
|   } else { | ||||
|     auto frame_view = std::make_unique<FramelessView>(); | ||||
|     auto frame_view = has_frame() && has_client_frame() | ||||
|                           ? std::make_unique<ClientFrameViewLinux>() | ||||
|                           : std::make_unique<FramelessView>(); | ||||
|     frame_view->Init(this, widget); | ||||
|     return frame_view; | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										149
									
								
								shell/browser/ui/electron_desktop_window_tree_host_linux.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								shell/browser/ui/electron_desktop_window_tree_host_linux.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,149 @@ | |||
| // Copyright (c) 2021 Ryan Gonzalez.
 | ||||
| // Use of this source code is governed by the MIT license that can be
 | ||||
| // found in the LICENSE file.
 | ||||
| // Portions of this file are sourced from
 | ||||
| // chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc,
 | ||||
| // Copyright (c) 2019 The Chromium Authors,
 | ||||
| // which is governed by a BSD-style license
 | ||||
| 
 | ||||
| #include "shell/browser/ui/electron_desktop_window_tree_host_linux.h" | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "base/i18n/rtl.h" | ||||
| #include "shell/browser/ui/views/client_frame_view_linux.h" | ||||
| #include "ui/gfx/geometry/rect.h" | ||||
| #include "ui/gfx/geometry/skia_conversions.h" | ||||
| #include "ui/platform_window/platform_window.h" | ||||
| #include "ui/views/linux_ui/linux_ui.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/non_client_view.h" | ||||
| 
 | ||||
| namespace electron { | ||||
| 
 | ||||
| ElectronDesktopWindowTreeHostLinux::ElectronDesktopWindowTreeHostLinux( | ||||
|     NativeWindowViews* native_window_view, | ||||
|     views::DesktopNativeWidgetAura* desktop_native_widget_aura) | ||||
|     : views::DesktopWindowTreeHostLinux(native_window_view->widget(), | ||||
|                                         desktop_native_widget_aura), | ||||
|       native_window_view_(native_window_view) {} | ||||
| 
 | ||||
| ElectronDesktopWindowTreeHostLinux::~ElectronDesktopWindowTreeHostLinux() = | ||||
|     default; | ||||
| 
 | ||||
| bool ElectronDesktopWindowTreeHostLinux::SupportsClientFrameShadow() const { | ||||
|   return platform_window()->CanSetDecorationInsets() && | ||||
|          platform_window()->IsTranslucentWindowOpacitySupported(); | ||||
| } | ||||
| 
 | ||||
| void ElectronDesktopWindowTreeHostLinux::OnWidgetInitDone() { | ||||
|   views::DesktopWindowTreeHostLinux::OnWidgetInitDone(); | ||||
|   UpdateFrameHints(); | ||||
| } | ||||
| 
 | ||||
| void ElectronDesktopWindowTreeHostLinux::OnBoundsChanged( | ||||
|     const BoundsChange& change) { | ||||
|   views::DesktopWindowTreeHostLinux::OnBoundsChanged(change); | ||||
|   UpdateFrameHints(); | ||||
| } | ||||
| 
 | ||||
| void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged( | ||||
|     ui::PlatformWindowState old_state, | ||||
|     ui::PlatformWindowState new_state) { | ||||
|   views::DesktopWindowTreeHostLinux::OnWindowStateChanged(old_state, new_state); | ||||
|   UpdateFrameHints(); | ||||
| } | ||||
| 
 | ||||
| void ElectronDesktopWindowTreeHostLinux::OnNativeThemeUpdated( | ||||
|     ui::NativeTheme* observed_theme) { | ||||
|   UpdateFrameHints(); | ||||
| } | ||||
| 
 | ||||
| void ElectronDesktopWindowTreeHostLinux::OnDeviceScaleFactorChanged() { | ||||
|   UpdateFrameHints(); | ||||
| } | ||||
| 
 | ||||
| void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() { | ||||
|   if (SupportsClientFrameShadow() && native_window_view_->has_frame() && | ||||
|       native_window_view_->has_client_frame()) { | ||||
|     UpdateClientDecorationHints(static_cast<ClientFrameViewLinux*>( | ||||
|         native_window_view_->widget()->non_client_view()->frame_view())); | ||||
|   } | ||||
| 
 | ||||
|   SizeConstraintsChanged(); | ||||
| } | ||||
| 
 | ||||
| void ElectronDesktopWindowTreeHostLinux::UpdateClientDecorationHints( | ||||
|     ClientFrameViewLinux* view) { | ||||
|   ui::PlatformWindow* window = platform_window(); | ||||
|   bool showing_frame = !native_window_view_->IsFullscreen(); | ||||
|   float scale = device_scale_factor(); | ||||
| 
 | ||||
|   bool should_set_opaque_region = window->IsTranslucentWindowOpacitySupported(); | ||||
| 
 | ||||
|   gfx::Insets insets; | ||||
|   gfx::Insets input_insets; | ||||
|   if (showing_frame) { | ||||
|     insets = view->GetBorderDecorationInsets(); | ||||
|     if (base::i18n::IsRTL()) { | ||||
|       insets.Set(insets.top(), insets.right(), insets.bottom(), insets.left()); | ||||
|     } | ||||
| 
 | ||||
|     input_insets = view->GetInputInsets(); | ||||
|   } | ||||
| 
 | ||||
|   gfx::Insets scaled_insets = gfx::ScaleToCeiledInsets(insets, scale); | ||||
|   window->SetDecorationInsets(&scaled_insets); | ||||
| 
 | ||||
|   gfx::Rect input_bounds(view->GetWidget()->GetWindowBoundsInScreen().size()); | ||||
|   input_bounds.Inset(insets + input_insets); | ||||
|   gfx::Rect scaled_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale); | ||||
|   window->SetInputRegion(&scaled_bounds); | ||||
| 
 | ||||
|   if (should_set_opaque_region) { | ||||
|     // 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 = view->GetRoundedWindowContentBounds(); | ||||
|     gfx::RectF rectf(view->GetWindowContentBounds()); | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     // Convert the region to a list of rectangles.
 | ||||
|     std::vector<gfx::Rect> opaque_region; | ||||
|     for (SkRegion::Iterator i(region); !i.done(); i.next()) | ||||
|       opaque_region.push_back(gfx::SkIRectToRect(i.rect())); | ||||
|     window->SetOpaqueRegion(&opaque_region); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }  // namespace electron
 | ||||
							
								
								
									
										71
									
								
								shell/browser/ui/electron_desktop_window_tree_host_linux.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								shell/browser/ui/electron_desktop_window_tree_host_linux.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| // Copyright (c) 2021 Ryan Gonzalez.
 | ||||
| // Use of this source code is governed by the MIT license that can be
 | ||||
| // found in the LICENSE file.
 | ||||
| // Portions of this file are sourced from
 | ||||
| // chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h,
 | ||||
| // Copyright (c) 2019 The Chromium Authors,
 | ||||
| // which is governed by a BSD-style license
 | ||||
| 
 | ||||
| #ifndef ELECTRON_SHELL_BROWSER_UI_ELECTRON_DESKTOP_WINDOW_TREE_HOST_LINUX_H_ | ||||
| #define ELECTRON_SHELL_BROWSER_UI_ELECTRON_DESKTOP_WINDOW_TREE_HOST_LINUX_H_ | ||||
| 
 | ||||
| #include "base/scoped_observation.h" | ||||
| #include "shell/browser/native_window_views.h" | ||||
| #include "shell/browser/ui/views/client_frame_view_linux.h" | ||||
| #include "ui/native_theme/native_theme_observer.h" | ||||
| #include "ui/views/linux_ui/device_scale_factor_observer.h" | ||||
| #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h" | ||||
| 
 | ||||
| namespace electron { | ||||
| 
 | ||||
| class ElectronDesktopWindowTreeHostLinux | ||||
|     : public views::DesktopWindowTreeHostLinux, | ||||
|       public ui::NativeThemeObserver, | ||||
|       public views::DeviceScaleFactorObserver { | ||||
|  public: | ||||
|   ElectronDesktopWindowTreeHostLinux( | ||||
|       NativeWindowViews* native_window_view, | ||||
|       views::DesktopNativeWidgetAura* desktop_native_widget_aura); | ||||
|   ~ElectronDesktopWindowTreeHostLinux() override; | ||||
| 
 | ||||
|   // disable copy
 | ||||
|   ElectronDesktopWindowTreeHostLinux( | ||||
|       const ElectronDesktopWindowTreeHostLinux&) = delete; | ||||
|   ElectronDesktopWindowTreeHostLinux& operator=( | ||||
|       const ElectronDesktopWindowTreeHostLinux&) = delete; | ||||
| 
 | ||||
|   bool SupportsClientFrameShadow() const; | ||||
| 
 | ||||
|  protected: | ||||
|   // views::DesktopWindowTreeHostLinuxImpl:
 | ||||
|   void OnWidgetInitDone() override; | ||||
| 
 | ||||
|   // ui::PlatformWindowDelegate
 | ||||
|   void OnBoundsChanged(const BoundsChange& change) override; | ||||
|   void OnWindowStateChanged(ui::PlatformWindowState old_state, | ||||
|                             ui::PlatformWindowState new_state) override; | ||||
| 
 | ||||
|   // ui::NativeThemeObserver:
 | ||||
|   void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override; | ||||
| 
 | ||||
|   // views::OnDeviceScaleFactorChanged:
 | ||||
|   void OnDeviceScaleFactorChanged() override; | ||||
| 
 | ||||
|  private: | ||||
|   void UpdateFrameHints(); | ||||
|   void UpdateClientDecorationHints(ClientFrameViewLinux* view); | ||||
| 
 | ||||
|   NativeWindowViews* native_window_view_;  // weak ref
 | ||||
| 
 | ||||
|   base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver> | ||||
|       theme_observation_{this}; | ||||
|   base::ScopedObservation<views::LinuxUI, | ||||
|                           views::DeviceScaleFactorObserver, | ||||
|                           &views::LinuxUI::AddDeviceScaleFactorObserver, | ||||
|                           &views::LinuxUI::RemoveDeviceScaleFactorObserver> | ||||
|       scale_observation_{this}; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace electron
 | ||||
| 
 | ||||
| #endif  // ELECTRON_SHELL_BROWSER_UI_ELECTRON_DESKTOP_WINDOW_TREE_HOST_LINUX_H_
 | ||||
							
								
								
									
										463
									
								
								shell/browser/ui/views/client_frame_view_linux.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										463
									
								
								shell/browser/ui/views/client_frame_view_linux.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,463 @@ | |||
| // 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 <algorithm> | ||||
| 
 | ||||
| #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/electron_desktop_window_tree_host_linux.h" | ||||
| #include "shell/browser/ui/views/frameless_view.h" | ||||
| #include "ui/base/hit_test.h" | ||||
| #include "ui/base/l10n/l10n_util.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/geometry/skia_conversions.h" | ||||
| #include "ui/gfx/skia_util.h" | ||||
| #include "ui/gfx/text_constants.h" | ||||
| #include "ui/gtk/gtk_compat.h"  // nogncheck
 | ||||
| #include "ui/gtk/gtk_util.h" | ||||
| #include "ui/native_theme/native_theme.h" | ||||
| #include "ui/strings/grit/ui_strings.h" | ||||
| #include "ui/views/controls/button/image_button.h" | ||||
| #include "ui/views/linux_ui/linux_ui.h" | ||||
| #include "ui/views/linux_ui/nav_button_provider.h" | ||||
| #include "ui/views/style/typography.h" | ||||
| #include "ui/views/widget/widget.h" | ||||
| #include "ui/views/window/frame_buttons.h" | ||||
| 
 | ||||
| namespace electron { | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| // These values should be the same as Chromium uses.
 | ||||
| constexpr int kResizeOutsideBorderSize = 10; | ||||
| constexpr int kResizeInsideBoundsSize = 5; | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| // static
 | ||||
| const char ClientFrameViewLinux::kViewClassName[] = "ClientFrameView"; | ||||
| 
 | ||||
| ClientFrameViewLinux::ClientFrameViewLinux() | ||||
|     : theme_(ui::NativeTheme::GetInstanceForNativeUi()), | ||||
|       nav_button_provider_( | ||||
|           views::LinuxUI::instance()->CreateNavButtonProvider()), | ||||
|       nav_buttons_{ | ||||
|           NavButton{views::NavButtonProvider::FrameButtonDisplayType::kClose, | ||||
|                     views::FrameButton::kClose, &views::Widget::Close, | ||||
|                     IDS_APP_ACCNAME_CLOSE, HTCLOSE}, | ||||
|           NavButton{views::NavButtonProvider::FrameButtonDisplayType::kMaximize, | ||||
|                     views::FrameButton::kMaximize, &views::Widget::Maximize, | ||||
|                     IDS_APP_ACCNAME_MAXIMIZE, HTMAXBUTTON}, | ||||
|           NavButton{views::NavButtonProvider::FrameButtonDisplayType::kRestore, | ||||
|                     views::FrameButton::kMaximize, &views::Widget::Restore, | ||||
|                     IDS_APP_ACCNAME_RESTORE, HTMAXBUTTON}, | ||||
|           NavButton{views::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_) { | ||||
|     button.button = new views::ImageButton(); | ||||
|     button.button->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE); | ||||
|     button.button->SetAccessibleName( | ||||
|         l10n_util::GetStringUTF16(button.accessibility_id)); | ||||
|     AddChildView(button.button); | ||||
|   } | ||||
| 
 | ||||
|   title_ = new views::Label(); | ||||
|   title_->SetSubpixelRenderingEnabled(false); | ||||
|   title_->SetAutoColorReadabilityEnabled(false); | ||||
|   title_->SetHorizontalAlignment(gfx::ALIGN_CENTER); | ||||
|   title_->SetVerticalAlignment(gfx::ALIGN_MIDDLE); | ||||
|   title_->SetTextStyle(views::style::STYLE_TAB_ACTIVE); | ||||
|   AddChildView(title_); | ||||
| 
 | ||||
|   native_theme_observer_.Observe(theme_); | ||||
|   window_button_order_observer_.Observe(views::LinuxUI::instance()); | ||||
| } | ||||
| 
 | ||||
| ClientFrameViewLinux::~ClientFrameViewLinux() { | ||||
|   views::LinuxUI::instance()->RemoveWindowButtonOrderObserver(this); | ||||
|   theme_->RemoveObserver(this); | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::Init(NativeWindowViews* window, | ||||
|                                 views::Widget* frame) { | ||||
|   FramelessView::Init(window, frame); | ||||
| 
 | ||||
|   // 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))); | ||||
| 
 | ||||
|   auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>( | ||||
|       ElectronDesktopWindowTreeHostLinux::GetHostForWidget( | ||||
|           window->GetAcceleratedWidget())); | ||||
|   host_supports_client_frame_shadow_ = tree_host->SupportsClientFrameShadow(); | ||||
| 
 | ||||
|   frame_provider_ = views::LinuxUI::instance()->GetWindowFrameProvider( | ||||
|       !host_supports_client_frame_shadow_); | ||||
| 
 | ||||
|   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(); | ||||
| } | ||||
| 
 | ||||
| gfx::Insets ClientFrameViewLinux::GetBorderDecorationInsets() const { | ||||
|   return frame_provider_->GetFrameThicknessDip(); | ||||
| } | ||||
| 
 | ||||
| gfx::Insets ClientFrameViewLinux::GetInputInsets() const { | ||||
|   return gfx::Insets( | ||||
|       host_supports_client_frame_shadow_ ? -kResizeOutsideBorderSize : 0); | ||||
| } | ||||
| 
 | ||||
| gfx::Rect ClientFrameViewLinux::GetWindowContentBounds() const { | ||||
|   gfx::Rect content_bounds = bounds(); | ||||
|   content_bounds.Inset(GetBorderDecorationInsets()); | ||||
|   return content_bounds; | ||||
| } | ||||
| 
 | ||||
| SkRRect ClientFrameViewLinux::GetRoundedWindowContentBounds() const { | ||||
|   SkRect rect = gfx::RectToSkRect(GetWindowContentBounds()); | ||||
|   SkRRect rrect; | ||||
| 
 | ||||
|   if (!frame_->IsMaximized()) { | ||||
|     SkPoint round_point{theme_values_.window_border_radius, | ||||
|                         theme_values_.window_border_radius}; | ||||
|     SkPoint radii[] = {round_point, round_point, {}, {}}; | ||||
|     rrect.setRectRadii(rect, radii); | ||||
|   } else { | ||||
|     rrect.setRect(rect); | ||||
|   } | ||||
| 
 | ||||
|   return rrect; | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::OnNativeThemeUpdated( | ||||
|     ui::NativeTheme* observed_theme) { | ||||
|   UpdateThemeValues(); | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::OnWindowButtonOrderingChange( | ||||
|     const std::vector<views::FrameButton>& leading_buttons, | ||||
|     const std::vector<views::FrameButton>& trailing_buttons) { | ||||
|   leading_frame_buttons_ = leading_buttons; | ||||
|   trailing_frame_buttons_ = trailing_buttons; | ||||
| 
 | ||||
|   InvalidateLayout(); | ||||
| } | ||||
| 
 | ||||
| int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) { | ||||
|   return ResizingBorderHitTestImpl( | ||||
|       point, | ||||
|       GetBorderDecorationInsets() + gfx::Insets(kResizeInsideBoundsSize)); | ||||
| } | ||||
| 
 | ||||
| gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const { | ||||
|   gfx::Rect client_bounds = bounds(); | ||||
|   if (!frame_->IsFullscreen()) { | ||||
|     client_bounds.Inset(GetBorderDecorationInsets()); | ||||
|     client_bounds.Inset(0, GetTitlebarBounds().height(), 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(); | ||||
| } | ||||
| 
 | ||||
| gfx::Size ClientFrameViewLinux::CalculatePreferredSize() const { | ||||
|   return SizeWithDecorations(FramelessView::CalculatePreferredSize()); | ||||
| } | ||||
| 
 | ||||
| gfx::Size ClientFrameViewLinux::GetMinimumSize() const { | ||||
|   return SizeWithDecorations(FramelessView::GetMinimumSize()); | ||||
| } | ||||
| 
 | ||||
| gfx::Size ClientFrameViewLinux::GetMaximumSize() const { | ||||
|   return SizeWithDecorations(FramelessView::GetMaximumSize()); | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::Layout() { | ||||
|   FramelessView::Layout(); | ||||
| 
 | ||||
|   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_->SetBounds(title_bounds.x(), title_bounds.y(), title_bounds.width(), | ||||
|                     title_bounds.height()); | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) { | ||||
|   if (!frame_->IsFullscreen()) { | ||||
|     frame_provider_->PaintWindowFrame(canvas, GetLocalBounds(), | ||||
|                                       GetTitlebarBounds().bottom(), | ||||
|                                       ShouldPaintAsActive()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const char* ClientFrameViewLinux::GetClassName() const { | ||||
|   return kViewClassName; | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::PaintAsActiveChanged() { | ||||
|   UpdateThemeValues(); | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::UpdateThemeValues() { | ||||
|   gtk::GtkCssContext window_context = | ||||
|       gtk::AppendCssNodeToStyleContext({}, "GtkWindow#window.background.csd"); | ||||
|   gtk::GtkCssContext headerbar_context = gtk::AppendCssNodeToStyleContext( | ||||
|       {}, "GtkHeaderBar#headerbar.default-decoration.titlebar"); | ||||
|   gtk::GtkCssContext title_context = gtk::AppendCssNodeToStyleContext( | ||||
|       headerbar_context, "GtkLabel#label.title"); | ||||
|   gtk::GtkCssContext button_context = gtk::AppendCssNodeToStyleContext( | ||||
|       headerbar_context, "GtkButton#button.image-button"); | ||||
| 
 | ||||
|   gtk_style_context_set_parent(headerbar_context, window_context); | ||||
|   gtk_style_context_set_parent(title_context, headerbar_context); | ||||
|   gtk_style_context_set_parent(button_context, headerbar_context); | ||||
| 
 | ||||
|   // 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); | ||||
|     gtk_style_context_set_state(button_context, GTK_STATE_FLAG_BACKDROP); | ||||
|   } | ||||
| 
 | ||||
|   theme_values_.window_border_radius = frame_provider_->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); | ||||
| 
 | ||||
|   gtk::GtkStyleContextGet(button_context, "min-height", | ||||
|                           &theme_values_.button_min_size, nullptr); | ||||
|   theme_values_.button_padding = gtk::GtkStyleContextGetPadding(button_context); | ||||
| 
 | ||||
|   title_->SetEnabledColor(theme_values_.title_color); | ||||
| 
 | ||||
|   InvalidateLayout(); | ||||
|   SchedulePaint(); | ||||
| } | ||||
| 
 | ||||
| views::NavButtonProvider::FrameButtonDisplayType | ||||
| ClientFrameViewLinux::GetButtonTypeToSkip() const { | ||||
|   return frame_->IsMaximized() | ||||
|              ? views::NavButtonProvider::FrameButtonDisplayType::kMaximize | ||||
|              : views::NavButtonProvider::FrameButtonDisplayType::kRestore; | ||||
| } | ||||
| 
 | ||||
| void ClientFrameViewLinux::UpdateButtonImages() { | ||||
|   nav_button_provider_->RedrawImages(theme_values_.button_min_size, | ||||
|                                      frame_->IsMaximized(), | ||||
|                                      ShouldPaintAsActive()); | ||||
| 
 | ||||
|   views::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<views::Button::ButtonState>(state_id); | ||||
|       button.button->SetImage( | ||||
|           state, nav_button_provider_->GetImage(button.type, 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) { | ||||
|   views::NavButtonProvider::FrameButtonDisplayType skip_type = | ||||
|       GetButtonTypeToSkip(); | ||||
| 
 | ||||
|   std::vector<views::FrameButton> 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::reverse(frame_buttons.begin(), frame_buttons.end()); | ||||
|       break; | ||||
|     default: | ||||
|       NOTREACHED(); | ||||
|   } | ||||
| 
 | ||||
|   for (views::FrameButton frame_button : frame_buttons) { | ||||
|     auto* button = std::find_if( | ||||
|         nav_buttons_.begin(), nav_buttons_.end(), [&](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<int>(frame_button); | ||||
| 
 | ||||
|     if (button->type == skip_type) { | ||||
|       continue; | ||||
|     } | ||||
| 
 | ||||
|     button->button->SetVisible(true); | ||||
| 
 | ||||
|     int button_width = theme_values_.button_min_size; | ||||
|     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 gfx::Rect(); | ||||
|   } | ||||
| 
 | ||||
|   int font_height = gfx::FontList().GetHeight(); | ||||
|   int titlebar_height = | ||||
|       std::max(font_height, theme_values_.titlebar_min_height) + | ||||
|       GetTitlebarContentInsets().height(); | ||||
| 
 | ||||
|   gfx::Insets decoration_insets = GetBorderDecorationInsets(); | ||||
| 
 | ||||
|   // 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; | ||||
| } | ||||
| 
 | ||||
| gfx::Size ClientFrameViewLinux::SizeWithDecorations(gfx::Size size) const { | ||||
|   gfx::Insets decoration_insets = GetBorderDecorationInsets(); | ||||
| 
 | ||||
|   size.Enlarge(0, GetTitlebarBounds().height()); | ||||
|   size.Enlarge(decoration_insets.width(), decoration_insets.height()); | ||||
|   return size; | ||||
| } | ||||
| 
 | ||||
| }  // namespace electron
 | ||||
							
								
								
									
										143
									
								
								shell/browser/ui/views/client_frame_view_linux.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								shell/browser/ui/views/client_frame_view_linux.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,143 @@ | |||
| // 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 <array> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "base/scoped_observation.h" | ||||
| #include "shell/browser/ui/views/frameless_view.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/linux_ui/linux_ui.h" | ||||
| #include "ui/views/linux_ui/nav_button_provider.h" | ||||
| #include "ui/views/linux_ui/window_button_order_observer.h" | ||||
| #include "ui/views/linux_ui/window_frame_provider.h" | ||||
| #include "ui/views/widget/widget.h" | ||||
| #include "ui/views/window/frame_buttons.h" | ||||
| 
 | ||||
| namespace electron { | ||||
| 
 | ||||
| class ClientFrameViewLinux : public FramelessView, | ||||
|                              public ui::NativeThemeObserver, | ||||
|                              public views::WindowButtonOrderObserver { | ||||
|  public: | ||||
|   static const char kViewClassName[]; | ||||
|   ClientFrameViewLinux(); | ||||
|   ~ClientFrameViewLinux() override; | ||||
| 
 | ||||
|   void Init(NativeWindowViews* window, views::Widget* frame) override; | ||||
| 
 | ||||
|   // These are here for ElectronDesktopWindowTreeHostLinux to use.
 | ||||
|   gfx::Insets GetBorderDecorationInsets() const; | ||||
|   gfx::Insets GetInputInsets() const; | ||||
|   gfx::Rect GetWindowContentBounds() const; | ||||
|   SkRRect GetRoundedWindowContentBounds() const; | ||||
| 
 | ||||
|  protected: | ||||
|   // ui::NativeThemeObserver:
 | ||||
|   void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override; | ||||
| 
 | ||||
|   // views::WindowButtonOrderObserver:
 | ||||
|   void OnWindowButtonOrderingChange( | ||||
|       const std::vector<views::FrameButton>& leading_buttons, | ||||
|       const std::vector<views::FrameButton>& trailing_buttons) override; | ||||
| 
 | ||||
|   // Overriden from FramelessView:
 | ||||
|   int ResizingBorderHitTest(const gfx::Point& point) override; | ||||
| 
 | ||||
|   // Overriden from views::NonClientFrameView:
 | ||||
|   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:
 | ||||
|   gfx::Size CalculatePreferredSize() const override; | ||||
|   gfx::Size GetMinimumSize() const override; | ||||
|   gfx::Size GetMaximumSize() const override; | ||||
|   void Layout() override; | ||||
|   void OnPaint(gfx::Canvas* canvas) override; | ||||
|   const char* GetClassName() const override; | ||||
| 
 | ||||
|  private: | ||||
|   static constexpr int kNavButtonCount = 4; | ||||
| 
 | ||||
|   struct NavButton { | ||||
|     views::NavButtonProvider::FrameButtonDisplayType type; | ||||
|     views::FrameButton frame_button; | ||||
|     void (views::Widget::*callback)(); | ||||
|     int accessibility_id; | ||||
|     int hit_test_id; | ||||
|     views::ImageButton* button{nullptr}; | ||||
|   }; | ||||
| 
 | ||||
|   struct ThemeValues { | ||||
|     float window_border_radius; | ||||
| 
 | ||||
|     int titlebar_min_height; | ||||
|     gfx::Insets titlebar_padding; | ||||
| 
 | ||||
|     SkColor title_color; | ||||
|     gfx::Insets title_padding; | ||||
| 
 | ||||
|     int button_min_size; | ||||
|     gfx::Insets button_padding; | ||||
|   }; | ||||
| 
 | ||||
|   void PaintAsActiveChanged(); | ||||
| 
 | ||||
|   void UpdateThemeValues(); | ||||
| 
 | ||||
|   enum class ButtonSide { kLeading, kTrailing }; | ||||
| 
 | ||||
|   views::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; | ||||
| 
 | ||||
|   gfx::Size SizeWithDecorations(gfx::Size size) const; | ||||
| 
 | ||||
|   ui::NativeTheme* theme_; | ||||
|   ThemeValues theme_values_; | ||||
| 
 | ||||
|   views::Label* title_; | ||||
| 
 | ||||
|   std::unique_ptr<views::NavButtonProvider> nav_button_provider_; | ||||
|   std::array<NavButton, kNavButtonCount> nav_buttons_; | ||||
| 
 | ||||
|   std::vector<views::FrameButton> leading_frame_buttons_; | ||||
|   std::vector<views::FrameButton> trailing_frame_buttons_; | ||||
| 
 | ||||
|   bool host_supports_client_frame_shadow_ = false; | ||||
| 
 | ||||
|   views::WindowFrameProvider* frame_provider_; | ||||
| 
 | ||||
|   base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver> | ||||
|       native_theme_observer_{this}; | ||||
|   base::ScopedObservation<views::LinuxUI, | ||||
|                           views::WindowButtonOrderObserver, | ||||
|                           &views::LinuxUI::AddWindowButtonOrderObserver, | ||||
|                           &views::LinuxUI::RemoveWindowButtonOrderObserver> | ||||
|       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_
 | ||||
|  | @ -33,7 +33,11 @@ void FramelessView::Init(NativeWindowViews* window, views::Widget* frame) { | |||
| } | ||||
| 
 | ||||
| int FramelessView::ResizingBorderHitTest(const gfx::Point& point) { | ||||
|   // Check the frame first, as we allow a small area overlapping the contents
 | ||||
|   return ResizingBorderHitTestImpl(point, gfx::Insets(kResizeInsideBoundsSize)); | ||||
| } | ||||
| 
 | ||||
| int FramelessView::ResizingBorderHitTestImpl(const gfx::Point& point, | ||||
|                                              const gfx::Insets& resize_border) { | ||||
|   // to be used for resize handles.
 | ||||
|   bool can_ever_resize = frame_->widget_delegate() | ||||
|                              ? frame_->widget_delegate()->CanResize() | ||||
|  | @ -47,12 +51,11 @@ int FramelessView::ResizingBorderHitTest(const gfx::Point& point) { | |||
| 
 | ||||
|   // Don't allow overlapping resize handles when the window is maximized or
 | ||||
|   // fullscreen, as it can't be resized in those states.
 | ||||
|   int resize_border = frame_->IsMaximized() || frame_->IsFullscreen() | ||||
|                           ? 0 | ||||
|                           : kResizeInsideBoundsSize; | ||||
|   return GetHTComponentForFrame(point, gfx::Insets(resize_border), | ||||
|                                 kResizeAreaCornerSize, kResizeAreaCornerSize, | ||||
|                                 can_ever_resize); | ||||
|   bool allow_overlapping_handles = | ||||
|       !frame_->IsMaximized() && !frame_->IsFullscreen(); | ||||
|   return GetHTComponentForFrame( | ||||
|       point, allow_overlapping_handles ? resize_border : gfx::Insets(), | ||||
|       kResizeAreaCornerSize, kResizeAreaCornerSize, can_ever_resize); | ||||
| } | ||||
| 
 | ||||
| gfx::Rect FramelessView::GetBoundsForClientView() const { | ||||
|  |  | |||
|  | @ -28,9 +28,14 @@ class FramelessView : public views::NonClientFrameView { | |||
|   virtual void Init(NativeWindowViews* window, views::Widget* frame); | ||||
| 
 | ||||
|   // Returns whether the |point| is on frameless window's resizing border.
 | ||||
|   int ResizingBorderHitTest(const gfx::Point& point); | ||||
|   virtual int ResizingBorderHitTest(const gfx::Point& point); | ||||
| 
 | ||||
|  protected: | ||||
|   // Helper function for subclasses to implement ResizingBorderHitTest with a
 | ||||
|   // custom resize inset.
 | ||||
|   int ResizingBorderHitTestImpl(const gfx::Point& point, | ||||
|                                 const gfx::Insets& resize_border); | ||||
| 
 | ||||
|   // views::NonClientFrameView:
 | ||||
|   gfx::Rect GetBoundsForClientView() const override; | ||||
|   gfx::Rect GetWindowBoundsForClientBounds( | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ryan Gonzalez
				Ryan Gonzalez