From 33a8368a6855f71c650d3964c6b02026dd17a8a7 Mon Sep 17 00:00:00 2001 From: Pete Schaffner Date: Thu, 8 Feb 2024 10:14:15 +0100 Subject: [PATCH] Make unselected tabs blend better with background color This enables the standard effect created by a sytem tab bar, which ensures unselected tabs blend with the window's/titlebar's background color. This also ensures the `windowButtonsBackdrop` view matches the color of the adjacent tab (be it selected or not). --- .../Features/Terminal/TerminalWindow.swift | 93 +++++++++++++++---- 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalWindow.swift b/macos/Sources/Features/Terminal/TerminalWindow.swift index 457b3bd08..c0b90afbd 100644 --- a/macos/Sources/Features/Terminal/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/TerminalWindow.swift @@ -27,7 +27,7 @@ class TerminalWindow: NSWindow { } } - private var windowButtonsBackdrop: NSView? = nil + private var windowButtonsBackdrop: WindowButtonsBackdropView? = nil private var windowDragHandle: WindowDragView? = nil private var storedTitlebarBackgroundColor: CGColor? = nil @@ -51,9 +51,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 @@ -180,7 +192,20 @@ class TerminalWindow: NSWindow { self.markHierarchyForLayout(accessoryView) } } - + + // 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. + override func update() { + super.update() + if let index = windowController?.window?.tabbedWindows?.firstIndex(of: self) { + let firstTabIsSelected = index == 0 + + windowButtonsBackdrop?.isHighlighted = firstTabIsSelected + } + } + 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 { @@ -188,32 +213,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 } @@ -283,3 +298,45 @@ 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 = PlusDarkerBlendingModeLayer() + private let backgroundColor: CGColor + private let isLightTheme: Bool + + var isHighlighted: Bool = true { + didSet { + if isLightTheme { + overlayLayer.isHidden = isHighlighted + layer?.backgroundColor = backgroundColor + } else { + overlayLayer.isHidden = true + layer?.backgroundColor = isHighlighted ? backgroundColor : CGColor(genericGrayGamma2_2Gray: 0.0, alpha: 0.45) + } + + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + init(backgroundColor: CGColor) { + self.backgroundColor = backgroundColor + 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) + + isHighlighted = true + } +}