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…
Reference in a new issue