From 3b6bac012179853446543df4444de0a3debe5ead Mon Sep 17 00:00:00 2001 From: Pete Schaffner Date: Sun, 11 Feb 2024 00:53:18 +0100 Subject: [PATCH 1/4] Make special blending mode class more generic This allows us to specify what kind of context we'd like our vibrant layer to exist in (light or dark). --- macos/Ghostty.xcodeproj/project.pbxproj | 12 ++++----- .../App/macOS/ghostty-bridging-header.h | 2 +- .../Features/Terminal/TerminalWindow.swift | 2 +- ...erminalWindowButtonsBackdropOverlayLayer.h | 4 --- ...erminalWindowButtonsBackdropOverlayLayer.m | 9 ------- macos/Sources/Helpers/VibrantLayer.h | 16 +++++++++++ macos/Sources/Helpers/VibrantLayer.m | 27 +++++++++++++++++++ 7 files changed, 51 insertions(+), 21 deletions(-) delete mode 100644 macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.h delete mode 100644 macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.m create mode 100644 macos/Sources/Helpers/VibrantLayer.h create mode 100644 macos/Sources/Helpers/VibrantLayer.m diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index afa4c2fa9..cf8f7bbbf 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -70,7 +70,7 @@ C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; }; C159E89D2B69A2EF00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; }; C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EA62B738B9900404083 /* NSView+Extension.swift */; }; - C1F26EE92B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EE82B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m */; }; + C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EE82B76CBFC00404083 /* VibrantLayer.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -133,8 +133,8 @@ AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalToolbar.swift; sourceTree = ""; }; C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = ""; }; C1F26EA62B738B9900404083 /* NSView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSView+Extension.swift"; sourceTree = ""; }; - C1F26EE72B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TerminalWindowButtonsBackdropOverlayLayer.h; sourceTree = ""; }; - C1F26EE82B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TerminalWindowButtonsBackdropOverlayLayer.m; sourceTree = ""; }; + C1F26EE72B76CBFC00404083 /* VibrantLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantLayer.h; sourceTree = ""; }; + C1F26EE82B76CBFC00404083 /* VibrantLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantLayer.m; sourceTree = ""; }; C1F26EEA2B76CC2400404083 /* ghostty-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ghostty-bridging-header.h"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -202,6 +202,8 @@ A59FB5D02AE0DEA7009128F3 /* MetalView.swift */, C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */, + C1F26EE72B76CBFC00404083 /* VibrantLayer.h */, + C1F26EE82B76CBFC00404083 /* VibrantLayer.m */, A5CEAFDA29B8005900646FDA /* SplitView */, ); path = Helpers; @@ -293,8 +295,6 @@ A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */, A596309D2AEE1D6C00D64628 /* TerminalView.swift */, A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */, - C1F26EE72B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.h */, - C1F26EE82B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m */, AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */, A535B9D9299C569B0017E2E4 /* ErrorView.swift */, ); @@ -488,7 +488,7 @@ A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */, A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */, A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */, - C1F26EE92B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m in Sources */, + C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */, A59630972AEE163600D64628 /* HostingWindow.swift in Sources */, A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */, A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */, diff --git a/macos/Sources/App/macOS/ghostty-bridging-header.h b/macos/Sources/App/macOS/ghostty-bridging-header.h index 6d2ccfaf3..fc654ad3f 100644 --- a/macos/Sources/App/macOS/ghostty-bridging-header.h +++ b/macos/Sources/App/macOS/ghostty-bridging-header.h @@ -1,3 +1,3 @@ // C imports here are exposed to Swift. -#import "TerminalWindowButtonsBackdropOverlayLayer.h" +#import "VibrantLayer.h" diff --git a/macos/Sources/Features/Terminal/TerminalWindow.swift b/macos/Sources/Features/Terminal/TerminalWindow.swift index 5c9077833..b6d0bbc02 100644 --- a/macos/Sources/Features/Terminal/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/TerminalWindow.swift @@ -350,7 +350,7 @@ fileprivate class WindowDragView: NSView { // A view that matches the color of selected and unselected tabs in the adjacent tab bar. fileprivate class WindowButtonsBackdropView: NSView { - private let overlayLayer = TerminalWindowButtonsBackdropOverlayLayer() + private let overlayLayer = VibrantLayer() private let isLightTheme: Bool var isHighlighted: Bool = true { diff --git a/macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.h b/macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.h deleted file mode 100644 index 2f5d0169a..000000000 --- a/macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface TerminalWindowButtonsBackdropOverlayLayer: CALayer -@end diff --git a/macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.m b/macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.m deleted file mode 100644 index 9ce1250ec..000000000 --- a/macos/Sources/Features/Terminal/TerminalWindowButtonsBackdropOverlayLayer.m +++ /dev/null @@ -1,9 +0,0 @@ -#import "TerminalWindowButtonsBackdropOverlayLayer.h" - -@implementation TerminalWindowButtonsBackdropOverlayLayer - -// A private compositing filter ("plus darker") that is used in titlebar -// tab bars to create the effect of recessed, unselected tabs. -- (id)compositingFilter { return @"plusD"; } - -@end diff --git a/macos/Sources/Helpers/VibrantLayer.h b/macos/Sources/Helpers/VibrantLayer.h new file mode 100644 index 000000000..b4af07cb9 --- /dev/null +++ b/macos/Sources/Helpers/VibrantLayer.h @@ -0,0 +1,16 @@ +#import + +typedef NS_ENUM(NSUInteger, VibrantLayerType) { + VibrantLayerTypeLight, + VibrantLayerTypeDark +}; + +// This layer can be used to recreate the "vibrant" appearance you see of +// views placed inside `NSVisualEffectView`s. When a light NSAppearance is +// active, we will use the private "plus darker" blend mode. For dark +// appearances we use "plus lighter". +@interface VibrantLayer : CALayer + +- (id)initForAppearance:(VibrantLayerType)type; + +@end diff --git a/macos/Sources/Helpers/VibrantLayer.m b/macos/Sources/Helpers/VibrantLayer.m new file mode 100644 index 000000000..edcf6aa0c --- /dev/null +++ b/macos/Sources/Helpers/VibrantLayer.m @@ -0,0 +1,27 @@ +#import "VibrantLayer.h" + +@interface VibrantLayer() + +@property (nonatomic) VibrantLayerType type; + +@end + +@implementation VibrantLayer + +- (id)initForAppearance:(VibrantLayerType)type { + self = [super init]; + if (self) { + _type = type; + } + return self; +} + +- (id)compositingFilter { + if (self.type == VibrantLayerTypeLight) { + return @"plusD"; + } else { + return @"plusL"; + } +} + +@end From 1bcec0d49fdbe4682157961636f4c9fdd3ad2ca4 Mon Sep 17 00:00:00 2001 From: Pete Schaffner Date: Sun, 11 Feb 2024 00:55:33 +0100 Subject: [PATCH 2/4] Make new tab button images vibrant This makes them blend better with the background in windows with transparency. --- .../Features/Terminal/TerminalWindow.swift | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalWindow.swift b/macos/Sources/Features/Terminal/TerminalWindow.swift index b6d0bbc02..395a1bd8d 100644 --- a/macos/Sources/Features/Terminal/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/TerminalWindow.swift @@ -30,7 +30,7 @@ class TerminalWindow: NSWindow { private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil private var windowDragHandle: WindowDragView? = nil private var storedTitlebarBackgroundColor: CGColor? = nil - private var newTabButtonImage: NSImage? = nil + private var newTabButtonImageLayer: VibrantLayer? = nil // The tab bar controller ID from macOS static private let TabBarController = NSUserInterfaceItemIdentifier("_tabBarController") @@ -84,7 +84,7 @@ class TerminalWindow: NSWindow { // Reset the new tab button image so that we are sure to generate a fresh // one, tinted appropriately for the given theme. - self.newTabButtonImage = nil + self.newTabButtonImageLayer = nil // We have to wait before setting the titleVisibility or else it prevents // the window from hiding the tab bar when we get down to a single tab. @@ -215,9 +215,6 @@ class TerminalWindow: NSWindow { // Color the new tab button's image to match the color of the tab title/keyboard shortcut labels, // just as it does in the stock tab bar. - // - // One issue I haven't been able to fix is that their tint is made grey when the window isn't key, - // which doesn't look great and is made worse by the fact that the tab label colors don't change. guard let titlebarContainer = contentView?.superview?.subviews.first(where: { $0.className == "NSTitlebarContainerView" }) else { return } @@ -225,34 +222,31 @@ class TerminalWindow: NSWindow { guard let newTabButtonImageView: NSImageView = newTabButton.subviews.first(where: { $0 as? NSImageView != nil }) as? NSImageView else { return } + guard let newTabButtonImage = newTabButtonImageView.image else { return } + guard let storedTitlebarBackgroundColor, let isLightTheme = NSColor(cgColor: storedTitlebarBackgroundColor)?.isLightColor else { return } - if newTabButtonImage == nil { - guard let image = newTabButtonImageView.image, - let storedTitlebarBackgroundColor, - let titlebarBackgroundColor = NSColor(cgColor: storedTitlebarBackgroundColor) else { return } - - let isLightTheme = titlebarBackgroundColor.isLightColor - - let newImage = NSImage(size: image.size, flipped: false) { rect in - NSGraphicsContext.saveGraphicsState() - - titlebarBackgroundColor.darken(by: isLightTheme ? 0.1 : 0.5).setFill() - rect.fill() - - NSColor.secondaryLabelColor.setFill() - rect.fill(using: titlebarBackgroundColor.isLightColor ? .plusDarker : .plusLighter) - - NSGraphicsContext.restoreGraphicsState() - - image.draw(in: rect, from: .zero, operation: .destinationAtop, fraction: 1.0) - + if newTabButtonImageLayer == nil { + let fillColor: NSColor = isLightTheme ? .black.withAlphaComponent(0.85) : .white.withAlphaComponent(0.85) + let newImage = NSImage(size: newTabButtonImage.size, flipped: false) { rect in + newTabButtonImage.draw(in: rect) + fillColor.setFill() + rect.fill(using: .sourceAtop) return true } + let imageLayer = VibrantLayer(forAppearance: isLightTheme ? .light : .dark)! + imageLayer.frame = NSRect(origin: NSPoint(x: newTabButton.bounds.midX - newTabButtonImage.size.width/2, y: newTabButton.bounds.midY - newTabButtonImage.size.height/2), size: newTabButtonImage.size) + imageLayer.contentsGravity = .resizeAspect + imageLayer.contents = newImage + imageLayer.opacity = 0.5 - newTabButtonImage = newImage + newTabButtonImageLayer = imageLayer } - newTabButtonImageView.image = newTabButtonImage + newTabButtonImageView.layer?.addSublayer(newTabButtonImageLayer!) + newTabButtonImageView.image = nil + // When we nil out the original image, the image view's frame resizes and repositions + // slightly, so we need to reset it to make sure our new image doesn't shift quickly. + newTabButtonImageView.frame = newTabButton.bounds } private func addWindowButtonsBackdrop(titlebarView: NSView, toolbarView: NSView) { From 595c1e222b87124c15bc2cd595787b8b632ec76e Mon Sep 17 00:00:00 2001 From: Pete Schaffner Date: Sun, 11 Feb 2024 13:52:11 +0100 Subject: [PATCH 3/4] Remove vibrant layer before re-adding it --- macos/Sources/Features/Terminal/TerminalWindow.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/macos/Sources/Features/Terminal/TerminalWindow.swift b/macos/Sources/Features/Terminal/TerminalWindow.swift index 395a1bd8d..b4194c113 100644 --- a/macos/Sources/Features/Terminal/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/TerminalWindow.swift @@ -242,6 +242,7 @@ class TerminalWindow: NSWindow { newTabButtonImageLayer = imageLayer } + newTabButtonImageView.layer?.sublayers?.first(where: { $0.className == "VibrantLayer" })?.removeFromSuperlayer() newTabButtonImageView.layer?.addSublayer(newTabButtonImageLayer!) newTabButtonImageView.image = nil // When we nil out the original image, the image view's frame resizes and repositions From aad302f2361a8503d82692b2d50a82b4cdd81955 Mon Sep 17 00:00:00 2001 From: Pete Schaffner Date: Sun, 11 Feb 2024 14:24:35 +0100 Subject: [PATCH 4/4] Make new tab icon respond to window's key status --- .../Features/Terminal/TerminalWindow.swift | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/macos/Sources/Features/Terminal/TerminalWindow.swift b/macos/Sources/Features/Terminal/TerminalWindow.swift index b4194c113..44864d511 100644 --- a/macos/Sources/Features/Terminal/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/TerminalWindow.swift @@ -16,8 +16,14 @@ class TerminalWindow: NSWindow { } super.becomeKey() + updateNewTabButtonOpacity() } - + + override func resignKey() { + super.resignKey() + updateNewTabButtonOpacity() + } + // MARK: - Titlebar Tabs // Used by the window controller to enable/disable titlebar tabs. @@ -215,6 +221,8 @@ class TerminalWindow: NSWindow { // Color the new tab button's image to match the color of the tab title/keyboard shortcut labels, // just as it does in the stock tab bar. + updateNewTabButtonOpacity() + guard let titlebarContainer = contentView?.superview?.subviews.first(where: { $0.className == "NSTitlebarContainerView" }) else { return } @@ -250,6 +258,21 @@ class TerminalWindow: NSWindow { newTabButtonImageView.frame = newTabButton.bounds } + // Since we are coloring the new tab button's image, it doesn't respond to the + // window's key status changes in terms of becoming less prominent visually, + // so we need to do it manually. + private func updateNewTabButtonOpacity() { + guard let titlebarContainer = contentView?.superview?.subviews.first(where: { + $0.className == "NSTitlebarContainerView" + }) else { return } + guard let newTabButton: NSButton = titlebarContainer.firstDescendant(withClassName: "NSTabBarNewTabButton") as? NSButton else { return } + guard let newTabButtonImageView: NSImageView = newTabButton.subviews.first(where: { + $0 as? NSImageView != nil + }) as? NSImageView else { return } + + newTabButtonImageView.alphaValue = isKeyWindow ? 1 : 0.5 + } + private func addWindowButtonsBackdrop(titlebarView: NSView, toolbarView: NSView) { // If we already made the view, just make sure it's unhidden and correctly placed as a subview. if let view = windowButtonsBackdrop {