From e542cd8378dbc90d764d13a48bbe6c09f8fb488e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 Sep 2025 21:24:00 +0530 Subject: [PATCH] macOS: Fix a big where the color of a transparent titlebar was off when running in the release build versus the build from source. Also fix using a transparent titlebar causing the background opacity to be darkened. There were two issues. 1) Setting window background color to a non-zero opacity causes darkening (essentially there were two layers of blending) 2) The titlebar background view could end up in the wrong position because it was a child of the content view rather than its super view Fix both issues setting the window background to clear color and moving the background view into the super view while making sure it is positioned correctly using explicit constraints. Phew. --- docs/changelog.rst | 7 +++++++ glfw/cocoa_window.m | 42 +++++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index dd0b5a945..2d70dc99c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -134,6 +134,13 @@ consumption to do the same tasks. Detailed list of changes ------------------------------------- +0.43.1 [future] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- macOS: Fix a big where the color of a transparent titlebar was off when + running in the release build versus the build from source. Also fix using a + transparent titlebar causing the background opacity to be darkened. + 0.43.0 [2025-09-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index b484be022..395b5125c 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -3280,19 +3280,20 @@ title_bar_and_tool_bar_height(NSWindow *window) { static void clear_title_bar_background_views(NSWindow *window) { #define tag @"kitty-for-transparent-titlebar" - NSView *contentView = window.contentView; - if (contentView) { - for (NSView *subview in [contentView viewsWithIdentifier:tag]) [subview removeFromSuperview]; + NSView *contentView = window.contentView, *titlebarContainer = contentView ? contentView.superview : nil; + if (titlebarContainer) { + for (NSView *subview in [titlebarContainer viewsWithIdentifier:tag]) [subview removeFromSuperview]; } } static void set_title_bar_background(NSWindow *window, NSColor *backgroundColor) { + // add an extra view that just renders the background color under the transparent titlebar NSView *contentView = window.contentView, *titlebarContainer = contentView ? contentView.superview : nil; if (!titlebarContainer) return; - for (NSView *subview in [contentView viewsWithIdentifier:tag]) [subview removeFromSuperview]; + contentView.identifier = @"kitty-content-view"; + for (NSView *subview in [titlebarContainer viewsWithIdentifier:tag]) [subview removeFromSuperview]; if (!backgroundColor) return; - const CGFloat height = title_bar_and_tool_bar_height(window); debug_rendering("titlebar_height used for translucent titlebar view: %f\n", height); NSView *bgView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, titlebarContainer.bounds.size.width, height)]; @@ -3300,7 +3301,9 @@ set_title_bar_background(NSWindow *window, NSColor *backgroundColor) { bgView.wantsLayer = YES; bgView.layer.backgroundColor = backgroundColor.CGColor; bgView.identifier = tag; - [contentView addSubview:bgView]; + // position the background view above the content view but below the titlebar view + [titlebarContainer addSubview:bgView positioned:NSWindowAbove relativeTo:contentView]; + // for (NSView *subview in titlebarContainer.subviews) NSLog(@"sv: %@", subview.identifier); [NSLayoutConstraint activateConstraints:@[ // Pin to the top of the content view. [bgView.topAnchor constraintEqualToAnchor:titlebarContainer.topAnchor], @@ -3320,18 +3323,20 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us if (window->ns.layer_shell.is_active) return; const bool is_transparent = _glfwPlatformFramebufferTransparent(window); if (!is_transparent) { background_opacity = 1.0; background_blur = 0; } - NSColor *background = nil; + NSColor *window_background = [NSColor windowBackgroundColor]; + if (background_opacity < 1.0) { + // use a clear color (fully transparent) so that the final color is just the color from the surface. + // prevent blurring of shadows at window corners with desktop background by setting a low alpha background + window_background = background_blur > 0 ? [NSColor colorWithWhite: 0 alpha: 0.001f] : [NSColor clearColor]; + } NSAppearance *appearance = nil; bool titlebar_transparent = false; + NSColor *titlebar_color = nil; const NSWindowStyleMask current_style_mask = [window->ns.object styleMask]; const bool in_fullscreen = ((current_style_mask & NSWindowStyleMaskFullScreen) != 0) || window->ns.in_traditional_fullscreen; NSAppearance *light_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight] : [NSAppearance appearanceNamed:NSAppearanceNameAqua]; NSAppearance *dark_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark] : [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; if (use_system_color) { - if (is_transparent) { - // prevent blurring of shadows at window corners with desktop background by setting a low alpha background - background = background_blur > 0 ? [NSColor colorWithWhite: 0 alpha: 0.001f] : [NSColor clearColor]; - } else background = [NSColor windowBackgroundColor]; switch (system_color) { case 1: appearance = light_appearance; break; @@ -3339,17 +3344,15 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us appearance = dark_appearance; break; } } else { - // set a background color and make the title bar transparent so the background color is visible double red = ((color >> 16) & 0xFF) / 255.0; double green = ((color >> 8) & 0xFF) / 255.0; double blue = (color & 0xFF) / 255.0; double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue; - CGFloat alpha = background_opacity < 1.0 ? background_opacity : 1.0; - background = [NSColor colorWithSRGBRed:red green:green blue:blue alpha:alpha]; appearance = luma < 0.5 ? dark_appearance : light_appearance; + titlebar_color = [NSColor colorWithSRGBRed:red green:green blue:blue alpha:background_opacity]; titlebar_transparent = true; } - [window->ns.object setBackgroundColor:background]; + [window->ns.object setBackgroundColor:window_background]; [window->ns.object setAppearance:appearance]; _glfwPlatformSetWindowBlur(window, background_blur); bool has_shadow = false; @@ -3395,12 +3398,13 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us debug( "Window Chrome state:\n\tbackground: %s\n\tappearance: %s color_space: %s\n\t" "blur: %d has_shadow: %d resizable: %d decorations: %s (%d)\n\t" - "titlebar_transparent: %d title_visibility: %d hidden: %d buttons_hidden: %d" + "titlebar_transparent: %d titlebar_color: %s title_visibility: %d hidden: %d buttons_hidden: %d" "\n", - background ? [background.description UTF8String] : "", + window_background ? [window_background.description UTF8String] : "", appearance ? [appearance.name UTF8String] : "", cs ? (cs.localizedName ? [cs.localizedName UTF8String] : [cs.description UTF8String]) : "", background_blur, has_shadow, resizable, decorations_desc, window->decorated, titlebar_transparent, + titlebar_color ? [titlebar_color.description UTF8String] : "", show_text_in_titlebar, window->ns.titlebar_hidden, hide_titlebar_buttons ); [window->ns.object setColorSpace:cs]; @@ -3416,8 +3420,8 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us } else { [window->ns.object setStyleMask:window->ns.pre_full_screen_style_mask | fsmask]; } - if (background_opacity < 1.0 && !window->ns.titlebar_hidden && window->decorated) { - set_title_bar_background(window->ns.object, [background colorUsingColorSpace:(cs ? cs : [NSColorSpace deviceRGBColorSpace])]); + if (background_opacity < 1.0 && !window->ns.titlebar_hidden && window->decorated && titlebar_color != nil) { + set_title_bar_background(window->ns.object, titlebar_color); } else clear_title_bar_background_views(window->ns.object); // HACK: Changing the style mask can cause the first responder to be cleared [window->ns.object makeFirstResponder:window->ns.view];