feat: new look for tahoe and liquid glass (closes #4658)
This commit is contained in:
1 parent
4aec2aa6da
commit
fab9d7c0ab
12 files changed
+130
-230
No files matched your search
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user