mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge pull request #1483 from peteschaffner/improve-titlebar-tabs-background-color
Improve titlebar unselected tabs background color
This commit is contained in:
@ -70,6 +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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -132,6 +133,9 @@
|
||||
AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalToolbar.swift; sourceTree = "<group>"; };
|
||||
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = "<group>"; };
|
||||
C1F26EA62B738B9900404083 /* NSView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSView+Extension.swift"; sourceTree = "<group>"; };
|
||||
C1F26EE72B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TerminalWindowButtonsBackdropOverlayLayer.h; sourceTree = "<group>"; };
|
||||
C1F26EE82B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TerminalWindowButtonsBackdropOverlayLayer.m; sourceTree = "<group>"; };
|
||||
C1F26EEA2B76CC2400404083 /* ghostty-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ghostty-bridging-header.h"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -237,6 +241,7 @@
|
||||
A5FEB2FF2ABB69450068369E /* main.swift */,
|
||||
A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */,
|
||||
857F63802A5E64F200CA4815 /* MainMenu.xib */,
|
||||
C1F26EEA2B76CC2400404083 /* ghostty-bridging-header.h */,
|
||||
);
|
||||
path = macOS;
|
||||
sourceTree = "<group>";
|
||||
@ -288,6 +293,8 @@
|
||||
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */,
|
||||
A596309D2AEE1D6C00D64628 /* TerminalView.swift */,
|
||||
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */,
|
||||
C1F26EE72B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.h */,
|
||||
C1F26EE82B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m */,
|
||||
AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */,
|
||||
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
|
||||
);
|
||||
@ -409,6 +416,7 @@
|
||||
TargetAttributes = {
|
||||
A5B30530299BEAAA0047F10C = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
LastSwiftMigration = 1510;
|
||||
};
|
||||
A5D4499C2B53AE7B000F5B83 = {
|
||||
CreatedOnToolsVersion = 15.2;
|
||||
@ -480,6 +488,7 @@
|
||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
||||
C1F26EE92B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m in Sources */,
|
||||
A59630972AEE163600D64628 /* HostingWindow.swift in Sources */,
|
||||
A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */,
|
||||
A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */,
|
||||
@ -590,6 +599,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GhosttyReleaseLocal.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -616,6 +626,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = ReleaseLocal;
|
||||
@ -739,6 +750,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GhosttyDebug.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -764,6 +776,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
@ -774,6 +788,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Ghostty.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -800,6 +815,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
|
3
macos/Sources/App/macOS/ghostty-bridging-header.h
Normal file
3
macos/Sources/App/macOS/ghostty-bridging-header.h
Normal file
@ -0,0 +1,3 @@
|
||||
// C imports here are exposed to Swift.
|
||||
|
||||
#import "TerminalWindowButtonsBackdropOverlayLayer.h"
|
@ -27,10 +27,11 @@ class TerminalWindow: NSWindow {
|
||||
}
|
||||
}
|
||||
|
||||
private var windowButtonsBackdrop: NSView? = nil
|
||||
private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil
|
||||
private var windowDragHandle: WindowDragView? = nil
|
||||
private var storedTitlebarBackgroundColor: CGColor? = nil
|
||||
|
||||
private var newTabButtonImage: NSImage? = nil
|
||||
|
||||
// The tab bar controller ID from macOS
|
||||
static private let TabBarController = NSUserInterfaceItemIdentifier("_tabBarController")
|
||||
|
||||
@ -55,9 +56,21 @@ class TerminalWindow: NSWindow {
|
||||
|
||||
/// This is called by titlebarTabs changing so that we can setup the rest of our window
|
||||
private func changedTitlebarTabs(to newValue: Bool) {
|
||||
self.titlebarAppearsTransparent = newValue
|
||||
|
||||
if (newValue) {
|
||||
// By hiding the visual effect view, we allow the window's (or titlebar's in this case)
|
||||
// background color to show through. If we were to set `titlebarAppearsTransparent` to true
|
||||
// the selected tab would look fine, but the unselected ones and new tab button backgrounds
|
||||
// would be an opaque color. When the titlebar isn't transparent, however, the system applies
|
||||
// a compositing effect to the unselected tab backgrounds, which makes them blend with the
|
||||
// titlebar's/window's background.
|
||||
if let titlebarContainer = contentView?.superview?.subviews.first(where: {
|
||||
$0.className == "NSTitlebarContainerView"
|
||||
}), let effectView = titlebarContainer.descendants(withClassName: "NSVisualEffectView").first {
|
||||
effectView.isHidden = true
|
||||
}
|
||||
|
||||
self.titlebarSeparatorStyle = .none
|
||||
|
||||
// We use the toolbar to anchor our tab bar positions in the titlebar,
|
||||
// so we make sure it's the right size/position, and exists.
|
||||
self.toolbarStyle = .unifiedCompact
|
||||
@ -68,7 +81,11 @@ class TerminalWindow: NSWindow {
|
||||
// Set a custom background on the titlebar - this is required for when
|
||||
// titlebar tabs is used in conjunction with a transparent background.
|
||||
self.restoreTitlebarBackground()
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
// 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.
|
||||
DispatchQueue.main.async {
|
||||
@ -184,7 +201,60 @@ class TerminalWindow: NSWindow {
|
||||
self.markHierarchyForLayout(accessoryView)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func update() {
|
||||
super.update()
|
||||
|
||||
// This is called when we open, close, switch, and reorder tabs, at which point we determine if the
|
||||
// first tab in the tab bar is selected. If it is, we make the `windowButtonsBackdrop` color the same
|
||||
// as that of the active tab (i.e. the titlebar's background color), otherwise we make it the same
|
||||
// color as the background of unselected tabs.
|
||||
if let index = windowController?.window?.tabbedWindows?.firstIndex(of: self) {
|
||||
windowButtonsBackdrop?.isHighlighted = index == 0
|
||||
}
|
||||
|
||||
// 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 }
|
||||
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 }
|
||||
|
||||
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)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
newTabButtonImage = newImage
|
||||
}
|
||||
|
||||
newTabButtonImageView.image = newTabButtonImage
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -192,32 +262,22 @@ class TerminalWindow: NSWindow {
|
||||
view.isHidden = false
|
||||
titlebarView.addSubview(view)
|
||||
view.leftAnchor.constraint(equalTo: toolbarView.leftAnchor).isActive = true
|
||||
view.rightAnchor.constraint(equalTo: toolbarView.leftAnchor, constant: 80).isActive = true
|
||||
view.rightAnchor.constraint(equalTo: toolbarView.leftAnchor, constant: 78).isActive = true
|
||||
view.topAnchor.constraint(equalTo: toolbarView.topAnchor).isActive = true
|
||||
view.heightAnchor.constraint(equalTo: toolbarView.heightAnchor).isActive = true
|
||||
return
|
||||
}
|
||||
|
||||
let view = NSView()
|
||||
let view = WindowButtonsBackdropView(backgroundColor: storedTitlebarBackgroundColor ?? NSColor.windowBackgroundColor.cgColor)
|
||||
view.identifier = NSUserInterfaceItemIdentifier("_windowButtonsBackdrop")
|
||||
titlebarView.addSubview(view)
|
||||
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.leftAnchor.constraint(equalTo: toolbarView.leftAnchor).isActive = true
|
||||
view.rightAnchor.constraint(equalTo: toolbarView.leftAnchor, constant: 80).isActive = true
|
||||
view.rightAnchor.constraint(equalTo: toolbarView.leftAnchor, constant: 78).isActive = true
|
||||
view.topAnchor.constraint(equalTo: toolbarView.topAnchor).isActive = true
|
||||
view.heightAnchor.constraint(equalTo: toolbarView.heightAnchor).isActive = true
|
||||
view.wantsLayer = true
|
||||
|
||||
// This is jank but this makes the background color for light themes on the button
|
||||
// backdrop look MUCH better. I couldn't figure out a perfect color to use that works
|
||||
// for both so we just check the appearance.
|
||||
if effectiveAppearance.name == .aqua {
|
||||
view.layer?.backgroundColor = CGColor(genericGrayGamma2_2Gray: 0.95, alpha: 1)
|
||||
} else {
|
||||
view.layer?.backgroundColor = CGColor(genericGrayGamma2_2Gray: 0.0, alpha: 0.45)
|
||||
}
|
||||
|
||||
|
||||
windowButtonsBackdrop = view
|
||||
}
|
||||
|
||||
@ -287,3 +347,39 @@ fileprivate class WindowDragView: NSView {
|
||||
addCursorRect(bounds, cursor: .openHand)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 isLightTheme: Bool
|
||||
|
||||
var isHighlighted: Bool = true {
|
||||
didSet {
|
||||
if isLightTheme {
|
||||
overlayLayer.isHidden = isHighlighted
|
||||
layer?.backgroundColor = .clear
|
||||
} else {
|
||||
overlayLayer.isHidden = true
|
||||
layer?.backgroundColor = isHighlighted ? .clear : CGColor(genericGrayGamma2_2Gray: 0.0, alpha: 0.45)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
init(backgroundColor: CGColor) {
|
||||
self.isLightTheme = NSColor(cgColor: backgroundColor)!.isLightColor
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
wantsLayer = true
|
||||
|
||||
overlayLayer.frame = layer!.bounds
|
||||
overlayLayer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
|
||||
overlayLayer.backgroundColor = CGColor(genericGrayGamma2_2Gray: 0.95, alpha: 1)
|
||||
|
||||
layer?.addSublayer(overlayLayer)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@interface TerminalWindowButtonsBackdropOverlayLayer: CALayer
|
||||
@end
|
@ -0,0 +1,9 @@
|
||||
#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
|
@ -1,6 +1,19 @@
|
||||
import AppKit
|
||||
|
||||
extension NSView {
|
||||
/// Recursively finds and returns the first descendant view that has the given class name.
|
||||
func firstDescendant(withClassName name: String) -> NSView? {
|
||||
for subview in subviews {
|
||||
if String(describing: type(of: subview)) == name {
|
||||
return subview
|
||||
} else if let found = subview.firstDescendant(withClassName: name) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Recursively finds and returns descendant views that have the given class name.
|
||||
func descendants(withClassName name: String) -> [NSView] {
|
||||
var result = [NSView]()
|
||||
|
Reference in New Issue
Block a user