feat: new look for tahoe and liquid glass (closes #4658)

This commit is contained in:
lwouis committed 2025-11-09 09:40:06 +01:00
1 parent 4aec2aa6da
commit fab9d7c0ab
12 files changed
+130 -230

No files matched your search

+3 -21
View File
@@ -203,27 +203,9 @@ extension NSTextField {
}
extension NSImage {
func cgImage(maxSize: NSSize) -> CGImage {
// some images like NSRunningApp.icon are from icns. They hosts multiple representations and it's hard to know the highest resolution
// by setting a maxSize, the returned CGImage will be the biggest it can under that maxSize
let ctx = CGContext(
data: nil,
width: Int(maxSize.width),
height: Int(maxSize.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
)!
NSGraphicsContext.current = NSGraphicsContext(cgContext: ctx, flipped: false)
self.draw(
in: CGRect(origin: .zero, size: maxSize),
from: .zero,
operation: .copy,
fraction: 1.0
)
NSGraphicsContext.current = nil
return ctx.makeImage()!
func appIconFixedSize(_ size: NSSize) -> CGImage? {
var rect = NSRect(origin: .zero, size: size)
return cgImage(forProposedRect: &rect, context: nil, hints: nil)
}
// NSImage(named) caches/reuses NSImage objects; we force separate instances of images by using copy()
+51 -135
View File
@@ -3,16 +3,17 @@ import Cocoa
class Appearance {
// size
static var hideThumbnails = Bool(false)
static var windowPadding = CGFloat(18)
static var windowCornerRadius = CGFloat(23)
static var cellCornerRadius = CGFloat(10)
static var edgeInsetsSize = CGFloat(5)
static var maxWidthOnScreen = CGFloat(0.8)
static var rowsCount = CGFloat(0)
static var iconSize = CGFloat(0)
static var fontHeight = CGFloat(0)
static var windowMinWidthInRow = CGFloat(0)
static var windowMaxWidthInRow = CGFloat(0)
static var windowPadding = CGFloat(1000)
static var windowCornerRadius = CGFloat(1000)
static var cellCornerRadius = CGFloat(1000)
static var edgeInsetsSize = CGFloat(1000)
static var maxWidthOnScreen = CGFloat(1000)
static var rowsCount = CGFloat(1000)
static var iconSize = CGFloat(1000)
static var fontHeight = CGFloat(1000)
static var windowMinWidthInRow = CGFloat(1000)
static var windowMaxWidthInRow = CGFloat(1000)
// size: constants
static let maxHeightOnScreen = CGFloat(0.8)
static let interCellPadding = CGFloat(1)
@@ -20,40 +21,36 @@ class Appearance {
static let appIconLabelSpacing = CGFloat(2)
// theme
static var fontColor = NSColor.white
static var indicatedIconShadowColor: NSColor? = .darkGray
static var titleShadowColor: NSColor? = .darkGray
static var highlightMaterial = NSVisualEffectView.Material.selection
static var material = NSVisualEffectView.Material.dark
static var imageShadowColor: NSColor? = .gray // for icon, thumbnail and windowless images
static var highlightFocusedAlphaValue = 1.0
static var highlightHoveredAlphaValue = 0.8
static var highlightFocusedBackgroundColor = NSColor.black.withAlphaComponent(0.5)
static var highlightHoveredBackgroundColor = NSColor.black.withAlphaComponent(0.3)
static var highlightFocusedBorderColor = NSColor.clear
static var highlightHoveredBorderColor = NSColor.clear
static var highlightBorderShadowColor = NSColor.clear
static var highlightBorderWidth = CGFloat(0)
static var enablePanelShadow = false
static var fontColor = NSColor.red
static var imagesShadowColor = NSColor.red // for icon, thumbnail and windowless images
static var material = NSVisualEffectView.Material.ultraDark
static var highlightBorderWidth = CGFloat(3)
// theme: constants
static let enablePanelShadow = true
static let highlightFocusedBackgroundColor = NSColor.systemAccentColor.withAlphaComponent(0.2)
static let highlightHoveredBackgroundColor = NSColor.systemAccentColor.withAlphaComponent(0.1)
static let highlightFocusedBorderColor = NSColor.systemAccentColor
static let highlightHoveredBorderColor = NSColor.systemAccentColor.withAlphaComponent(0.7)
// derived
static var font: NSFont {
if #available(macOS 26.0, *) {
return NSFont.systemFont(ofSize: fontHeight, weight: .medium)
return NSFont.systemFont(ofSize: fontHeight, weight: .semibold)
}
return NSFont.systemFont(ofSize: fontHeight)
}
private static var currentStyle: AppearanceStylePreference { Preferences.appearanceStyle }
private static var currentSize: AppearanceSizePreference { Preferences.appearanceSize }
private static var currentTheme: AppearanceThemePreference {
static var currentTheme: AppearanceThemePreference {
if Preferences.appearanceTheme == .system {
return NSAppearance.current.getThemeName()
} else {
return Preferences.appearanceTheme
}
}
private static var currentVisibility: AppearanceVisibilityPreference { Preferences.appearanceVisibility }
static func update() {
updateSize()
@@ -78,10 +75,11 @@ class Appearance {
} else {
lightTheme()
}
// for Liquid Glass, we don't want a shadow around the panel
if #available(macOS 26.0, *), currentStyle == .appIcons && LiquidGlassEffectView.canUsePrivateLiquidGlassLook() {
enablePanelShadow = false
}
highlightBorderWidth = currentStyle == .titles ? 2 : 3
// // for Liquid Glass, we don't want a shadow around the panel
// if #available(macOS 26.0, *), currentStyle == .appIcons && LiquidGlassEffectView.canUsePrivateLiquidGlassLook() {
// enablePanelShadow = false
// }
}
private static func thumbnailsSize(_ isHorizontalScreen: Bool) {
@@ -90,7 +88,6 @@ class Appearance {
windowCornerRadius = 23
cellCornerRadius = 10
edgeInsetsSize = 12
maxWidthOnScreen = 0.8
if #available(macOS 26.0, *) {
windowPadding = 28
windowCornerRadius = 43
@@ -99,23 +96,19 @@ class Appearance {
switch currentSize {
case .small:
rowsCount = isHorizontalScreen ? 5 : 8
iconSize = 20
fontHeight = 12
iconSize = 16
fontHeight = 13
case .medium:
rowsCount = isHorizontalScreen ? 4 : 7
iconSize = 30
fontHeight = 13
iconSize = 26
fontHeight = 14
case .large:
rowsCount = isHorizontalScreen ? 3 : 6
iconSize = 32
iconSize = 28
fontHeight = 16
}
let thumbnailsPanelRatio = (NSScreen.preferred.frame.width * maxWidthOnScreen) / (NSScreen.preferred.frame.height * maxHeightOnScreen)
(windowMinWidthInRow, windowMaxWidthInRow) = AppearanceTestable.goodValuesForThumbnailsWidthMinMax(thumbnailsPanelRatio, rowsCount)
if currentVisibility == .highest {
edgeInsetsSize = 10
cellCornerRadius = 12
}
}
private static func appIconsSize() {
@@ -127,32 +120,31 @@ class Appearance {
if #available(macOS 26.0, *) {
edgeInsetsSize = 6
}
maxWidthOnScreen = 0.8
windowMinWidthInRow = 0.04
windowMaxWidthInRow = 0.3
rowsCount = 1
switch currentSize {
case .small:
iconSize = 88
iconSize = 70
fontHeight = 13
if #available(macOS 26.0, *) {
windowCornerRadius = 50
cellCornerRadius = 28
cellCornerRadius = 24
}
case .medium:
iconSize = 128
fontHeight = 15
iconSize = 110
fontHeight = 14
if #available(macOS 26.0, *) {
windowCornerRadius = 55
cellCornerRadius = 38
cellCornerRadius = 35
}
case .large:
windowPadding = 28
iconSize = 168
fontHeight = 17
iconSize = 150
fontHeight = 16
if #available(macOS 26.0, *) {
windowCornerRadius = 75
cellCornerRadius = 48
cellCornerRadius = 45
}
}
}
@@ -169,102 +161,26 @@ class Appearance {
rowsCount = 1
switch currentSize {
case .small:
iconSize = 20
iconSize = 18
fontHeight = 13
case .medium:
iconSize = 26
iconSize = 24
fontHeight = 14
case .large:
iconSize = 32
iconSize = 22
fontHeight = 16
}
}
private static func lightTheme() {
fontColor = .black.withAlphaComponent(0.8)
indicatedIconShadowColor = nil
titleShadowColor = nil
highlightMaterial = .mediumLight
imageShadowColor = .lightGray.withAlphaComponent(0.4)
switch currentVisibility {
case .normal:
material = .light
highlightFocusedBackgroundColor = .lightGray.withAlphaComponent(0.7)
highlightHoveredBackgroundColor = .lightGray.withAlphaComponent(0.5)
enablePanelShadow = false
highlightFocusedAlphaValue = 1.0
highlightHoveredAlphaValue = 0.8
highlightFocusedBorderColor = NSColor.clear
highlightHoveredBorderColor = NSColor.clear
highlightBorderShadowColor = NSColor.clear
highlightBorderWidth = 0
case .high:
material = .mediumLight
highlightFocusedBackgroundColor = .lightGray.withAlphaComponent(0.7)
highlightHoveredBackgroundColor = .lightGray.withAlphaComponent(0.5)
enablePanelShadow = true
highlightFocusedAlphaValue = 1.0
highlightHoveredAlphaValue = 0.8
highlightFocusedBorderColor = .lightGray.withAlphaComponent(0.9)
highlightHoveredBorderColor = .lightGray.withAlphaComponent(0.8)
highlightBorderShadowColor = .black.withAlphaComponent(0.5)
highlightBorderWidth = 1
case .highest:
material = .mediumLight
highlightFocusedBackgroundColor = .lightGray.withAlphaComponent(0.4)
highlightHoveredBackgroundColor = .lightGray.withAlphaComponent(0.3)
enablePanelShadow = true
highlightFocusedAlphaValue = 0.4
highlightHoveredAlphaValue = 0.2
highlightFocusedBorderColor = NSColor.systemAccentColor
highlightHoveredBorderColor = NSColor.systemAccentColor.withAlphaComponent(0.8)
highlightBorderShadowColor = .black.withAlphaComponent(0.5)
highlightBorderWidth = currentStyle == .titles ? 2 : 4
}
imagesShadowColor = .gray.withAlphaComponent(0.8)
material = .mediumLight
}
private static func darkTheme() {
fontColor = .white.withAlphaComponent(0.9)
indicatedIconShadowColor = .darkGray
titleShadowColor = .darkGray
highlightMaterial = .ultraDark
switch currentVisibility {
case .normal:
material = .dark
imageShadowColor = .gray.withAlphaComponent(0.8)
highlightFocusedBackgroundColor = .black.withAlphaComponent(0.6)
highlightHoveredBackgroundColor = .black.withAlphaComponent(0.5)
enablePanelShadow = false
highlightFocusedAlphaValue = 1.0
highlightHoveredAlphaValue = 0.8
highlightFocusedBorderColor = NSColor.clear
highlightHoveredBorderColor = NSColor.clear
highlightBorderShadowColor = NSColor.clear
highlightBorderWidth = 0
case .high:
material = .ultraDark
imageShadowColor = .gray.withAlphaComponent(0.4)
highlightFocusedBackgroundColor = .gray.withAlphaComponent(0.6)
highlightHoveredBackgroundColor = .gray.withAlphaComponent(0.4)
enablePanelShadow = true
highlightFocusedAlphaValue = 1.0
highlightHoveredAlphaValue = 0.8
highlightFocusedBorderColor = .gray.withAlphaComponent(0.8)
highlightHoveredBorderColor = .gray.withAlphaComponent(0.7)
highlightBorderShadowColor = .white.withAlphaComponent(0.5)
highlightBorderWidth = 1
case .highest:
material = .ultraDark
imageShadowColor = .gray.withAlphaComponent(0.4)
highlightFocusedBackgroundColor = .black.withAlphaComponent(0.4)
highlightHoveredBackgroundColor = .black.withAlphaComponent(0.2)
enablePanelShadow = true
highlightFocusedAlphaValue = 0.4
highlightHoveredAlphaValue = 0.2
highlightFocusedBorderColor = NSColor.systemAccentColor
highlightHoveredBorderColor = NSColor.systemAccentColor.withAlphaComponent(0.8)
highlightBorderShadowColor = .white.withAlphaComponent(0.5)
highlightBorderWidth = currentStyle == .titles ? 2 : 4
}
fontColor = .white.withAlphaComponent(0.85)
imagesShadowColor = .gray.withAlphaComponent(0.8)
material = .dark
}
}
+4 -4
View File
@@ -33,13 +33,13 @@ class Application: NSObject {
static func appIconWithoutPadding(_ icon: NSImage?) -> CGImage? {
guard let icon else { return nil }
// we can render the icon quite big (e.g. windowless app icon), so we store it high-res
let iconSize = CGFloat(1024)
let iconWidth = CGFloat(1024)
// NSRunningApplication.icon returns icons with padding; we remove it manually
let paddingToRemove = CGFloat(84)
let croppedSize = iconSize - paddingToRemove * 2
let croppedSize = iconWidth - paddingToRemove * 2
return icon
.cgImage(maxSize: NSSize(width: iconSize, height: iconSize))
.cropping(to: CGRect(x: paddingToRemove, y: paddingToRemove, width: croppedSize, height: croppedSize).integral)!
.appIconFixedSize(NSSize(width: iconWidth, height: iconWidth))?
.cropping(to: CGRect(x: paddingToRemove, y: paddingToRemove, width: croppedSize, height: croppedSize).integral)
}
init(_ runningApplication: NSRunningApplication) {
-22
View File
@@ -484,28 +484,6 @@ enum AppearanceThemePreference: CaseIterable, SfSymbolMacroPreference {
}
}
enum AppearanceVisibilityPreference: CaseIterable, SfSymbolMacroPreference {
case normal
case high
case highest
var localizedString: LocalizedString {
switch self {
case .normal: return NSLocalizedString("Normal", comment: "")
case .high: return NSLocalizedString("High", comment: "")
case .highest: return NSLocalizedString("Highest", comment: "")
}
}
var symbolName: String {
switch self {
case .normal: return "eye"
case .high: return "eyeglasses"
case .highest: return "binoculars.fill"
}
}
}
enum UpdatePolicyPreference: CaseIterable, MacroPreference {
case manual
case autoCheck
-2
View File
@@ -50,7 +50,6 @@ class Preferences {
"appearanceStyle": AppearanceStylePreference.thumbnails.indexAsString,
"appearanceSize": AppearanceSizePreference.medium.indexAsString,
"appearanceTheme": AppearanceThemePreference.system.indexAsString,
"appearanceVisibility": AppearanceVisibilityPreference.normal.indexAsString,
"theme": ThemePreference.macOs.indexAsString,
"showOnScreen": ShowOnScreenPreference.active.indexAsString,
"titleTruncation": TitleTruncationPreference.end.indexAsString,
@@ -129,7 +128,6 @@ class Preferences {
static var appearanceStyle: AppearanceStylePreference { CachedUserDefaults.macroPref("appearanceStyle", AppearanceStylePreference.allCases) }
static var appearanceSize: AppearanceSizePreference { CachedUserDefaults.macroPref("appearanceSize", AppearanceSizePreference.allCases) }
static var appearanceTheme: AppearanceThemePreference { CachedUserDefaults.macroPref("appearanceTheme", AppearanceThemePreference.allCases) }
static var appearanceVisibility: AppearanceVisibilityPreference { CachedUserDefaults.macroPref("appearanceVisibility", AppearanceVisibilityPreference.allCases) }
// periphery:ignore
static var theme: ThemePreference { ThemePreference.macOs/*CachedUserDefaults.macroPref("theme", ThemePreference.allCases)*/ }
static var showOnScreen: ShowOnScreenPreference { CachedUserDefaults.macroPref("showOnScreen", ShowOnScreenPreference.allCases) }
@@ -25,11 +25,9 @@ class ThumbnailFontIconView: ThumbnailTitleView {
}()
var initialAttributedString: NSMutableAttributedString!
convenience init(symbol: Symbols, tooltip: String? = nil, size: CGFloat = Appearance.fontHeight,
color: NSColor = Appearance.fontColor,
shadow: NSShadow? = ThumbnailView.makeShadow(Appearance.indicatedIconShadowColor)) {
convenience init(symbol: Symbols, tooltip: String? = nil, size: CGFloat = Appearance.fontHeight, color: NSColor = Appearance.fontColor) {
// This helps SF symbols display vertically centered and not clipped at the top
self.init(shadow: shadow, font: NSFont(name: "SF Pro Text", size: (size * 0.85).rounded())!)
self.init(font: NSFont(name: "SF Pro Text", size: (size * 0.85).rounded())!)
initialAttributedString = NSMutableAttributedString(string: symbol.rawValue, attributes: [.paragraphStyle: ThumbnailFontIconView.paragraphStyle])
attributedStringValue = initialAttributedString
textColor = color
@@ -72,7 +70,7 @@ class ThumbnailFilledFontIconView: NSView {
convenience init(_ thumbnailFontIconView: ThumbnailFontIconView, backgroundColor: NSColor, size: CGFloat) {
self.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
let backgroundView = ThumbnailFontIconView(symbol: .filledCircled, size: size - 4, color: backgroundColor, shadow: nil)
let backgroundView = ThumbnailFontIconView(symbol: .filledCircled, size: size - 3, color: backgroundColor)
addSubview(backgroundView)
addSubview(thumbnailFontIconView, positioned: .above, relativeTo: nil)
backgroundView.centerXAnchor.constraint(equalTo: thumbnailFontIconView.centerXAnchor).isActive = true
+1 -2
View File
@@ -1,11 +1,10 @@
import Cocoa
class ThumbnailTitleView: NSTextField {
convenience init(shadow: NSShadow?, font: NSFont) {
convenience init(font: NSFont) {
self.init(labelWithString: "")
self.font = font
textColor = Appearance.fontColor
self.shadow = shadow
allowsDefaultTighteningForTruncation = false
translatesAutoresizingMaskIntoConstraints = false
}
+41 -22
View File
@@ -7,13 +7,13 @@ class ThumbnailView: FlippedView {
var windowlessIcon = LightImageView()
let thumbnailContainer = FlippedView()
var appIcon = LightImageView()
var label = ThumbnailTitleView(shadow: ThumbnailView.makeShadow(Appearance.titleShadowColor), font: Appearance.font)
var label = ThumbnailTitleView(font: Appearance.font)
var fullscreenIcon = ThumbnailFontIconView(symbol: .circledPlusSign, tooltip: NSLocalizedString("Window is fullscreen", comment: ""))
var minimizedIcon = ThumbnailFontIconView(symbol: .circledMinusSign, tooltip: NSLocalizedString("Window is minimized", comment: ""))
var hiddenIcon = ThumbnailFontIconView(symbol: .circledSlashSign, tooltip: NSLocalizedString("App is hidden", comment: ""))
var spaceIcon = ThumbnailFontIconView(symbol: .circledNumber0)
var dockLabelIcon = ThumbnailFilledFontIconView(
ThumbnailFontIconView(symbol: .filledCircledNumber0, size: dockLabelLabelSize(), color: NSColor(srgbRed: 1, green: 0.30, blue: 0.25, alpha: 1), shadow: nil),
ThumbnailFontIconView(symbol: .filledCircledNumber0, size: dockLabelLabelSize(), color: NSColor(srgbRed: 1, green: 0.30, blue: 0.25, alpha: 1)),
backgroundColor: NSColor.white, size: dockLabelLabelSize())
var quitIcon = TrafficLightButton(.quit, NSLocalizedString("Quit app", comment: ""))
var closeIcon = TrafficLightButton(.close, NSLocalizedString("Close window", comment: ""))
@@ -134,7 +134,6 @@ class ThumbnailView: FlippedView {
let isHovered = indexInRecycledViews == Windows.hoveredWindowIndex
setBackground(isFocused: isFocused, isHovered: isHovered)
setBorder(isFocused: isFocused, isHovered: isHovered)
setShadow(isFocused: isFocused, isHovered: isHovered)
if Preferences.appearanceStyle == .appIcons {
label.isHidden = !(isFocused || isHovered)
updateAppIconsLabel(isFocused: isFocused, isHovered: isHovered)
@@ -181,12 +180,13 @@ class ThumbnailView: FlippedView {
}
private func setupSharedSubviews() {
let shadow = ThumbnailView.makeShadow(Appearance.imageShadowColor)
let shadow = ThumbnailView.makeShadow(Appearance.imagesShadowColor)
thumbnailContainer.wantsLayer = true
thumbnailContainer.layer!.masksToBounds = false // let thumbnail shadows show
thumbnail.shadow = shadow
windowlessIcon.shadow = shadow
appIcon.shadow = shadow
dockLabelIcon.shadow = shadow
windowlessIcon.translatesAutoresizingMaskIntoConstraints = false
windowlessIcon.toolTip = ThumbnailView.noOpenWindowToolTip
appIcon.translatesAutoresizingMaskIntoConstraints = false
@@ -250,15 +250,6 @@ class ThumbnailView: FlippedView {
}
}
private func setShadow(isFocused: Bool, isHovered: Bool) {
if (isFocused || isHovered) && Appearance.highlightBorderShadowColor != .clear {
vStackView.layer!.shadowColor = Appearance.highlightBorderShadowColor.cgColor
vStackView.layer!.shadowOpacity = 0.25
vStackView.layer!.shadowOffset = .zero
vStackView.layer!.shadowRadius = 1
}
}
private func updateAppIconsLabel(isFocused: Bool, isHovered: Bool) {
let focusedView = ThumbnailsView.recycledViews[Windows.focusedWindowIndex]
var hoveredView: ThumbnailView? = nil
@@ -285,19 +276,22 @@ class ThumbnailView: FlippedView {
let availableRightWidth = view.isLastInRow ? 0 : CGFloat(view.numberOfViewsInRow - 1 - view.indexInRow) * viewWidth
let totalWidth = availableLeftWidth + availableRightWidth + viewWidth
let maxLabelWidth = min(totalWidth, maxAllowedWidth)
return maxLabelWidth
return maxLabelWidth - Appearance.intraCellPadding * 4
}
private func updateAppIconsLabelFrame(_ view: ThumbnailView) {
let viewWidth = view.frame.width
let labelWidth = view.label.cell!.cellSize.width
let labelWidth = view.label.fittingSize.width
let maxAllowedLabelWidth = getMaxAllowedLabelWidth(view)
let effectiveLabelWidth = max(min(labelWidth, maxAllowedLabelWidth), viewWidth)
var leftOffset = CGFloat(0)
var effectiveLabelWidth = max(min(labelWidth, maxAllowedLabelWidth), viewWidth)
var leftOffset = -Appearance.intraCellPadding * 2
if view.isFirstInRow && view.isLastInRow {
leftOffset = 0
leftOffset = -Appearance.intraCellPadding * 2
effectiveLabelWidth -= Appearance.intraCellPadding * 4
} else if view.isLastInRow {
leftOffset = max(0, effectiveLabelWidth - viewWidth)
leftOffset = max(0, effectiveLabelWidth - viewWidth + Appearance.intraCellPadding * 2)
} else if !view.isFirstInRow && !view.isLastInRow {
let halfNeededOffset = max(0, (effectiveLabelWidth - viewWidth) / 2)
let availableLeftWidth = view.isFirstInRow ? 0 : CGFloat(view.indexInRow) * viewWidth
@@ -445,11 +439,36 @@ class ThumbnailView: FlippedView {
windowlessAppIndicator.frame.origin.y = windowlessAppIndicator.superview!.frame.height - windowlessAppIndicator.frame.height + 5
}
// we set dockLabelIcon origin, without checking if .isHidden
// This is because its updated async. We needed it positioned correctly always
let iconSize = ThumbnailView.iconSize()
dockLabelIcon.frame.origin.x = ((iconSize.width * 0.8) - (dockLabelIcon.fittingSize.width / 2)).rounded()
dockLabelIcon.frame.origin.y = ((iconSize.height * 0.8) - (dockLabelIcon.fittingSize.height / 2)).rounded()
- (Preferences.appearanceSize == .small ? 2 : 0)
// This is because its updated async. We need it positioned correctly always
let (offsetX, offsetY) = dockLabelOffset()
dockLabelIcon.frame.origin.x = appIcon.frame.maxX - (dockLabelIcon.fittingSize.width * offsetX).rounded()
dockLabelIcon.frame.origin.y = appIcon.frame.maxY - (dockLabelIcon.fittingSize.height * offsetY).rounded()
}
/// positioning the dock label is messy because it's an NSTextField so it's visual size doesn't match what we can through APIs
// TODO: remove this; find a better way
private func dockLabelOffset() -> (CGFloat, CGFloat) {
var offsetX = 0.6
if Preferences.appearanceStyle == .appIcons {
if Preferences.appearanceSize == .small {
offsetX = 0.82
} else if Preferences.appearanceSize == .medium {
offsetX = 0.86
} else { // .large
offsetX = 0.92
}
}
var offsetY = offsetX
if Preferences.appearanceStyle == .appIcons {
if Preferences.appearanceSize == .small {
offsetY = 0.88
} else if Preferences.appearanceSize == .medium {
offsetY = 0.90
} else { // .large
offsetY = 0.92
}
}
return (offsetX, offsetY)
}
private func indicatorsSpace() -> CGFloat {
+7 -2
View File
@@ -10,7 +10,6 @@ class ThumbnailsPanel: NSPanel {
isFloatingPanel = true
animationBehavior = .none
hidesOnDeactivate = false
hasShadow = Appearance.enablePanelShadow
titleVisibility = .hidden
backgroundColor = .clear
contentView! = thumbnailsView.contentView
@@ -23,6 +22,12 @@ class ThumbnailsPanel: NSPanel {
setAccessibilitySubrole(.unknown)
// for VoiceOver
setAccessibilityLabel(App.name)
updateAppearance()
}
func updateAppearance() {
hasShadow = Appearance.enablePanelShadow
appearance = NSAppearance(named: Appearance.currentTheme == .dark ? .vibrantDark : .vibrantLight)
}
override func orderOut(_ sender: Any?) {
@@ -37,7 +42,7 @@ class ThumbnailsPanel: NSPanel {
}
func show() {
hasShadow = Appearance.enablePanelShadow
updateAppearance()
alphaValue = 1
makeKeyAndOrderFront(nil)
MouseEvents.toggle(true)
@@ -8,10 +8,14 @@ class LiquidGlassEffectView: NSGlassEffectView, EffectView {
return method != nil
}
convenience init(_: Int?) {
convenience init(_ clear: Bool) {
self.init()
style = .clear
safeSetVariant(3)
if clear {
style = .clear
safeSetVariant(3)
} else {
style = .regular
}
updateAppearance()
wantsLayer = true
// without this, there are weird shadows around the corners
@@ -70,16 +74,19 @@ protocol EffectView: NSView {
}
func makeAppropriateEffectView() -> EffectView {
if #available(macOS 26.0, *), Preferences.appearanceStyle == .appIcons, LiquidGlassEffectView.canUsePrivateLiquidGlassLook() {
Logger.error("liquid")
return LiquidGlassEffectView(nil)
} else {
if #available(macOS 26.0, *), Preferences.appearanceStyle == .appIcons {
if #available(macOS 26.0, *) {
if Preferences.appearanceStyle == .appIcons {
if LiquidGlassEffectView.canUsePrivateLiquidGlassLook() {
Logger.debug("Using LiquidGlassEffectView(true)")
return LiquidGlassEffectView(true)
}
let os = ProcessInfo.processInfo.operatingSystemVersion
let version = "\(os.majorVersion).\(os.minorVersion).\(os.patchVersion)"
Logger.error("Private API set_variant is no longer available. macOS version: \(version))")
}
Logger.error("frosted")
return FrostedGlassEffectView(nil)
Logger.debug("Using LiquidGlassEffectView(false)")
return LiquidGlassEffectView(false)
}
Logger.debug("Using FrostedGlassEffectView(nil)")
return FrostedGlassEffectView(nil)
}
@@ -133,7 +133,7 @@ class LabelAndControl: NSObject {
onClick: EventClosure? = nil,
onMouseEntered: EventClosure? = nil,
onMouseExited: EventClosure? = nil) -> ClickHoverImageView {
let imageView = ThumbnailFontIconView(symbol: .circledInfo, size: size, color: .labelColor, shadow: nil)
let imageView = ThumbnailFontIconView(symbol: .circledInfo, size: size, color: .labelColor)
let view = ClickHoverImageView(infoCircle: imageView)
view.onClick = onClick
view.onMouseEntered = onMouseEntered
@@ -225,7 +225,7 @@ class LabelAndControl: NSObject {
}
}
static func makeSegmentedControl(_ rawName: String, _ macroPreferences: [MacroPreference], extraAction: ActionClosure? = nil, segmentWidth: CGFloat = -1) -> NSSegmentedControl {
static func makeSegmentedControl(_ rawName: String, _ macroPreferences: [MacroPreference], segmentWidth: CGFloat = -1, extraAction: ActionClosure? = nil) -> NSSegmentedControl {
let button = NSSegmentedControl(labels: macroPreferences.map {
$0.localizedString
}, trackingMode: .selectOne, target: nil, action: nil)
@@ -313,7 +313,7 @@ class LabelAndControl: NSObject {
}
// some preferences require re-creating some components
if (!(senderControl is NSSlider) || (NSEvent.pressedMouseButtons & (1 << 0)) == 0) &&
(["appearanceStyle", "appearanceSize", "appearanceTheme", "appearanceVisibility", "showOnScreen", "showAppsOrWindows"].contains { (pref: String) -> Bool in
(["appearanceStyle", "appearanceSize", "appearanceTheme", "showOnScreen", "showAppsOrWindows"].contains { (pref: String) -> Bool in
pref == senderControl.identifier!.rawValue
}) {
(App.shared as! App).resetPreferencesDependentComponents()
@@ -397,8 +397,6 @@ class AppearanceTab: NSObject {
rightViews: [LabelAndControl.makeSegmentedControl("appearanceSize", AppearanceSizePreference.allCases, segmentWidth: 100)])
table.addRow(leftText: NSLocalizedString("Theme", comment: ""),
rightViews: [LabelAndControl.makeSegmentedControl("appearanceTheme", AppearanceThemePreference.allCases, segmentWidth: 100)])
table.addRow(leftText: NSLocalizedString("Visibility", comment: ""),
rightViews: [LabelAndControl.makeSegmentedControl("appearanceVisibility", AppearanceVisibilityPreference.allCases, segmentWidth: 100)])
table.addRow(rightViews: customizeStyleButton)
table.fit()
return table