mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #1501 from peteschaffner/improve-new-tab-button-image
Improve new tab button icon color with window transparency
This commit is contained in:
@ -70,7 +70,7 @@
|
|||||||
C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; };
|
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 */; };
|
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 */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@ -133,8 +133,8 @@
|
|||||||
AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalToolbar.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantLayer.h; sourceTree = "<group>"; };
|
||||||
C1F26EE82B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TerminalWindowButtonsBackdropOverlayLayer.m; sourceTree = "<group>"; };
|
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantLayer.m; sourceTree = "<group>"; };
|
||||||
C1F26EEA2B76CC2400404083 /* ghostty-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ghostty-bridging-header.h"; 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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -202,6 +202,8 @@
|
|||||||
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */,
|
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */,
|
||||||
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */,
|
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */,
|
||||||
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
||||||
|
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
|
||||||
|
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
|
||||||
A5CEAFDA29B8005900646FDA /* SplitView */,
|
A5CEAFDA29B8005900646FDA /* SplitView */,
|
||||||
);
|
);
|
||||||
path = Helpers;
|
path = Helpers;
|
||||||
@ -293,8 +295,6 @@
|
|||||||
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */,
|
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */,
|
||||||
A596309D2AEE1D6C00D64628 /* TerminalView.swift */,
|
A596309D2AEE1D6C00D64628 /* TerminalView.swift */,
|
||||||
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */,
|
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */,
|
||||||
C1F26EE72B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.h */,
|
|
||||||
C1F26EE82B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m */,
|
|
||||||
AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */,
|
AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */,
|
||||||
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
|
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
|
||||||
);
|
);
|
||||||
@ -488,7 +488,7 @@
|
|||||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||||
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
||||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
||||||
C1F26EE92B76CBFC00404083 /* TerminalWindowButtonsBackdropOverlayLayer.m in Sources */,
|
C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */,
|
||||||
A59630972AEE163600D64628 /* HostingWindow.swift in Sources */,
|
A59630972AEE163600D64628 /* HostingWindow.swift in Sources */,
|
||||||
A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */,
|
A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */,
|
||||||
A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */,
|
A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */,
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
// C imports here are exposed to Swift.
|
// C imports here are exposed to Swift.
|
||||||
|
|
||||||
#import "TerminalWindowButtonsBackdropOverlayLayer.h"
|
#import "VibrantLayer.h"
|
||||||
|
@ -16,8 +16,14 @@ class TerminalWindow: NSWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
super.becomeKey()
|
super.becomeKey()
|
||||||
|
updateNewTabButtonOpacity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func resignKey() {
|
||||||
|
super.resignKey()
|
||||||
|
updateNewTabButtonOpacity()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Titlebar Tabs
|
// MARK: - Titlebar Tabs
|
||||||
|
|
||||||
// Used by the window controller to enable/disable titlebar tabs.
|
// Used by the window controller to enable/disable titlebar tabs.
|
||||||
@ -30,7 +36,7 @@ class TerminalWindow: NSWindow {
|
|||||||
private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil
|
private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil
|
||||||
private var windowDragHandle: WindowDragView? = nil
|
private var windowDragHandle: WindowDragView? = nil
|
||||||
private var storedTitlebarBackgroundColor: CGColor? = nil
|
private var storedTitlebarBackgroundColor: CGColor? = nil
|
||||||
private var newTabButtonImage: NSImage? = nil
|
private var newTabButtonImageLayer: VibrantLayer? = nil
|
||||||
|
|
||||||
// The tab bar controller ID from macOS
|
// The tab bar controller ID from macOS
|
||||||
static private let TabBarController = NSUserInterfaceItemIdentifier("_tabBarController")
|
static private let TabBarController = NSUserInterfaceItemIdentifier("_tabBarController")
|
||||||
@ -84,7 +90,7 @@ class TerminalWindow: NSWindow {
|
|||||||
|
|
||||||
// Reset the new tab button image so that we are sure to generate a fresh
|
// Reset the new tab button image so that we are sure to generate a fresh
|
||||||
// one, tinted appropriately for the given theme.
|
// 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
|
// 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.
|
// the window from hiding the tab bar when we get down to a single tab.
|
||||||
@ -215,9 +221,47 @@ class TerminalWindow: NSWindow {
|
|||||||
|
|
||||||
// Color the new tab button's image to match the color of the tab title/keyboard shortcut labels,
|
// 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.
|
// just as it does in the stock tab bar.
|
||||||
//
|
updateNewTabButtonOpacity()
|
||||||
// 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 }
|
||||||
|
guard let newTabButtonImage = newTabButtonImageView.image else { return }
|
||||||
|
guard let storedTitlebarBackgroundColor, let isLightTheme = NSColor(cgColor: storedTitlebarBackgroundColor)?.isLightColor else { return }
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
// slightly, so we need to reset it to make sure our new image doesn't shift quickly.
|
||||||
|
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: {
|
guard let titlebarContainer = contentView?.superview?.subviews.first(where: {
|
||||||
$0.className == "NSTitlebarContainerView"
|
$0.className == "NSTitlebarContainerView"
|
||||||
}) else { return }
|
}) else { return }
|
||||||
@ -226,33 +270,7 @@ class TerminalWindow: NSWindow {
|
|||||||
$0 as? NSImageView != nil
|
$0 as? NSImageView != nil
|
||||||
}) as? NSImageView else { return }
|
}) as? NSImageView else { return }
|
||||||
|
|
||||||
if newTabButtonImage == nil {
|
newTabButtonImageView.alphaValue = isKeyWindow ? 1 : 0.5
|
||||||
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) {
|
private func addWindowButtonsBackdrop(titlebarView: NSView, toolbarView: NSView) {
|
||||||
@ -350,7 +368,7 @@ fileprivate class WindowDragView: NSView {
|
|||||||
|
|
||||||
// A view that matches the color of selected and unselected tabs in the adjacent tab bar.
|
// A view that matches the color of selected and unselected tabs in the adjacent tab bar.
|
||||||
fileprivate class WindowButtonsBackdropView: NSView {
|
fileprivate class WindowButtonsBackdropView: NSView {
|
||||||
private let overlayLayer = TerminalWindowButtonsBackdropOverlayLayer()
|
private let overlayLayer = VibrantLayer()
|
||||||
private let isLightTheme: Bool
|
private let isLightTheme: Bool
|
||||||
|
|
||||||
var isHighlighted: Bool = true {
|
var isHighlighted: Bool = true {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
#import <QuartzCore/QuartzCore.h>
|
|
||||||
|
|
||||||
@interface TerminalWindowButtonsBackdropOverlayLayer: CALayer
|
|
||||||
@end
|
|
@ -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
|
|
16
macos/Sources/Helpers/VibrantLayer.h
Normal file
16
macos/Sources/Helpers/VibrantLayer.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#import <QuartzCore/QuartzCore.h>
|
||||||
|
|
||||||
|
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
|
27
macos/Sources/Helpers/VibrantLayer.m
Normal file
27
macos/Sources/Helpers/VibrantLayer.m
Normal file
@ -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
|
Reference in New Issue
Block a user