fix: glitchy rendering and maximize behavior with different GTK themes (#50644)

fix: glitchy rendering and maximize behavior with different GTK themes (#50550)

* fix glitchy rendering with different gtk themes especially when maximizing

* use actual insets, not restored insets

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>
This commit is contained in:
trop[bot]
2026-04-04 15:05:09 -05:00
committed by GitHub
parent 1686aa37f2
commit 1f51bf66fb
5 changed files with 55 additions and 26 deletions

View File

@@ -163,7 +163,7 @@ int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) {
gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {
gfx::Rect client_bounds = bounds();
if (!frame_->IsFullscreen()) {
client_bounds.Inset(RestoredFrameBorderInsets());
client_bounds.Inset(linux_frame_layout_->FrameBorderInsets(false));
client_bounds.Inset(
gfx::Insets::TLBR(GetTitlebarBounds().height(), 0, 0, 0));
}
@@ -236,6 +236,21 @@ void ClientFrameViewLinux::Layout(PassKey) {
}
void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
if (frame_->IsFullscreen()) {
return;
}
if (frame_->IsMaximized()) {
// Some GTK themes (Breeze) still render shadow/border assets when
// maximized, and we don't need a border when maximized anyway. Chromium
// switches on this too: OpaqueBrowserFrameView::PaintMaximizedFrameBorder.
PaintMaximizedFrameBorder(canvas);
} else {
PaintRestoredFrameBorder(canvas);
}
}
void ClientFrameViewLinux::PaintRestoredFrameBorder(gfx::Canvas* canvas) {
if (auto* frame_provider = linux_frame_layout_->GetFrameProvider()) {
frame_provider->PaintWindowFrame(
canvas, GetLocalBounds(), GetTitlebarBounds().bottom(),
@@ -243,6 +258,18 @@ void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
}
}
void ClientFrameViewLinux::PaintMaximizedFrameBorder(gfx::Canvas* canvas) {
ui::NativeTheme::FrameTopAreaExtraParams frame_top_area;
frame_top_area.use_custom_frame = true;
frame_top_area.is_active = ShouldPaintAsActive();
frame_top_area.default_background_color = SK_ColorTRANSPARENT;
ui::NativeTheme::ExtraParams params(frame_top_area);
GetNativeTheme()->Paint(
canvas->sk_canvas(), GetColorProvider(), ui::NativeTheme::kFrameTopArea,
ui::NativeTheme::kNormal,
gfx::Rect(0, 0, width(), GetTitlebarBounds().bottom()), params);
}
void ClientFrameViewLinux::PaintAsActiveChanged() {
UpdateThemeValues();
}
@@ -251,23 +278,15 @@ void ClientFrameViewLinux::UpdateThemeValues() {
gtk::GtkCssContext window_context =
gtk::AppendCssNodeToStyleContext({}, "window.background.csd");
gtk::GtkCssContext headerbar_context = gtk::AppendCssNodeToStyleContext(
{}, "headerbar.default-decoration.titlebar");
window_context, "headerbar.default-decoration.titlebar");
gtk::GtkCssContext title_context =
gtk::AppendCssNodeToStyleContext(headerbar_context, "label.title");
gtk::GtkCssContext button_context = gtk::AppendCssNodeToStyleContext(
headerbar_context, "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 =
@@ -281,10 +300,6 @@ void ClientFrameViewLinux::UpdateThemeValues() {
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();
@@ -299,8 +314,9 @@ ClientFrameViewLinux::GetButtonTypeToSkip() const {
}
void ClientFrameViewLinux::UpdateButtonImages() {
nav_button_provider_->RedrawImages(theme_values_.button_min_size,
frame_->IsMaximized(),
int top_area_height = theme_values_.titlebar_min_height +
theme_values_.titlebar_padding.height();
nav_button_provider_->RedrawImages(top_area_height, frame_->IsMaximized(),
ShouldPaintAsActive());
ui::NavButtonProvider::FrameButtonDisplayType skip_type =
@@ -368,7 +384,14 @@ void ClientFrameViewLinux::LayoutButtonsOnSide(
button->button->SetVisible(true);
int button_width = theme_values_.button_min_size;
// CSS min-size/height/width is not enough to determine the actual size of
// the buttons, so we sample the rendered image. See Chromium's
// BrowserFrameViewLinuxNative::MaybeUpdateCachedFrameButtonImages.
int button_width =
nav_button_provider_
->GetImage(button->type,
ui::NavButtonProvider::ButtonState::kNormal)
.width();
int next_button_offset =
button_width + nav_button_provider_->GetInterNavButtonSpacing();
@@ -404,7 +427,7 @@ gfx::Rect ClientFrameViewLinux::GetTitlebarBounds() const {
std::max(font_height, theme_values_.titlebar_min_height) +
GetTitlebarContentInsets().height();
gfx::Insets decoration_insets = RestoredFrameBorderInsets();
gfx::Insets decoration_insets = linux_frame_layout_->FrameBorderInsets(false);
// We add the inset height here, so the .Inset() that follows won't reduce it
// to be too small.

View File

@@ -91,12 +91,11 @@ class ClientFrameViewLinux : public FramelessView,
SkColor title_color;
gfx::Insets title_padding;
int button_min_size;
gfx::Insets button_padding;
};
void PaintAsActiveChanged();
void PaintRestoredFrameBorder(gfx::Canvas* canvas);
void PaintMaximizedFrameBorder(gfx::Canvas* canvas);
void UpdateThemeValues();

View File

@@ -83,6 +83,12 @@ gfx::Insets LinuxFrameLayout::RestoredFrameBorderInsets() const {
return gfx::Insets();
}
gfx::Insets LinuxFrameLayout::FrameBorderInsets(bool restored) const {
return !restored && (window_->IsMaximized() || window_->IsFullscreen())
? gfx::Insets()
: RestoredFrameBorderInsets();
}
gfx::Insets LinuxFrameLayout::GetInputInsets() const {
return gfx::Insets(kResizeInsideBoundsSize);
}
@@ -106,7 +112,7 @@ void LinuxFrameLayout::set_tiled(bool tiled) {
gfx::Rect LinuxFrameLayout::GetWindowBounds() const {
gfx::Rect bounds = window_->widget()->GetWindowBoundsInScreen();
bounds.Inset(RestoredFrameBorderInsets());
bounds.Inset(FrameBorderInsets(false));
return bounds;
}

View File

@@ -44,6 +44,9 @@ class LinuxFrameLayout {
CSDStyle csd_style);
// Insets from the transparent widget border to the opaque part of the window.
// Returns empty insets when maximized or fullscreen unless |restored| is
// true. Matches Chromium's OpaqueBrowserFrameViewLayout::FrameBorderInsets.
gfx::Insets FrameBorderInsets(bool restored) const;
virtual gfx::Insets RestoredFrameBorderInsets() const;
// Insets for parts of the surface that should be counted for user input.
virtual gfx::Insets GetInputInsets() const;

View File

@@ -204,7 +204,7 @@ void OpaqueFrameView::OnPaint(gfx::Canvas* canvas) {
return;
const bool active = ShouldPaintAsActive();
const gfx::Insets border = RestoredFrameBorderInsets();
const gfx::Insets border = FrameBorderInsets(false);
const bool showing_shadow = linux_frame_layout_->IsShowingShadow();
gfx::RectF bounds_dip(GetLocalBounds());
if (showing_shadow) {
@@ -341,9 +341,7 @@ views::Button* OpaqueFrameView::CreateButton(
}
gfx::Insets OpaqueFrameView::FrameBorderInsets(bool restored) const {
return !restored && IsFrameCondensed()
? gfx::Insets()
: linux_frame_layout_->RestoredFrameBorderInsets();
return linux_frame_layout_->FrameBorderInsets(restored);
}
int OpaqueFrameView::FrameTopBorderThickness(bool restored) const {