From fb628341d1000e354833d56feca05850513e2a26 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Thu, 13 Aug 2020 21:18:26 +0900 Subject: [PATCH] feat: show notification badges on top of app icons (closes #523) --- resources/SF-Pro-Text-Regular.otf | Bin 10028 -> 13688 bytes resources/l10n/Localizable.strings | 3 ++ resources/l10n/ar.lproj/Localizable.strings | 3 ++ resources/l10n/de.lproj/Localizable.strings | 3 ++ resources/l10n/en.lproj/Localizable.strings | 3 ++ resources/l10n/es.lproj/Localizable.strings | 3 ++ resources/l10n/fi.lproj/Localizable.strings | 3 ++ resources/l10n/fr.lproj/Localizable.strings | 3 ++ resources/l10n/hu.lproj/Localizable.strings | 3 ++ resources/l10n/id.lproj/Localizable.strings | 3 ++ resources/l10n/it.lproj/Localizable.strings | 3 ++ resources/l10n/ja.lproj/Localizable.strings | 3 ++ resources/l10n/ko.lproj/Localizable.strings | 3 ++ resources/l10n/lb.lproj/Localizable.strings | 3 ++ resources/l10n/nl.lproj/Localizable.strings | 3 ++ resources/l10n/pt.lproj/Localizable.strings | 3 ++ resources/l10n/ru.lproj/Localizable.strings | 3 ++ resources/l10n/tr.lproj/Localizable.strings | 3 ++ .../l10n/zh-CN.lproj/Localizable.strings | 3 ++ .../l10n/zh-TW.lproj/Localizable.strings | 3 ++ scripts/subset_fonticon.sh | 2 +- src/api-wrappers/AXUIElement.swift | 8 ++++ src/api-wrappers/PrivateApis.swift | 3 +- src/logic/Application.swift | 1 + src/logic/Applications.swift | 15 ++++++ src/logic/Preferences.swift | 2 + src/logic/Window.swift | 7 ++- src/logic/events/AccessibilityEvents.swift | 6 +-- src/ui/App.swift | 2 + .../main-window/ThumbnailFontIconView.swift | 44 ++++++++++-------- src/ui/main-window/ThumbnailView.swift | 29 +++++++++--- .../tabs/AppearanceTab.swift | 1 + 32 files changed, 141 insertions(+), 36 deletions(-) diff --git a/resources/SF-Pro-Text-Regular.otf b/resources/SF-Pro-Text-Regular.otf index ba724c23654814dab17bc41e1db62e5ef5322743..010702f500093a5331bec617ee04d62c5af3c6aa 100644 GIT binary patch delta 10060 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 EKS5#DVgLXD delta 5848 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 I=Z3%W|L!-NKL7v# diff --git a/resources/l10n/Localizable.strings b/resources/l10n/Localizable.strings index fe4e3830..8e745a4c 100644 --- a/resources/l10n/Localizable.strings +++ b/resources/l10n/Localizable.strings @@ -127,6 +127,9 @@ /* No comment provided by engineer. */ "Hidden" = "Hidden"; +/* No comment provided by engineer. */ +"Hide app badges:" = "Hide app badges:"; + /* No comment provided by engineer. */ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/ar.lproj/Localizable.strings b/resources/l10n/ar.lproj/Localizable.strings index b8814d1c..c7360744 100644 --- a/resources/l10n/ar.lproj/Localizable.strings +++ b/resources/l10n/ar.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "مخفي"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/de.lproj/Localizable.strings b/resources/l10n/de.lproj/Localizable.strings index 98bf2bf8..fa489bca 100644 --- a/resources/l10n/de.lproj/Localizable.strings +++ b/resources/l10n/de.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Versteckt"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Bunte Kreise beim Markieren mit Maus verbergen:"; diff --git a/resources/l10n/en.lproj/Localizable.strings b/resources/l10n/en.lproj/Localizable.strings index 5d218bb3..da845f56 100644 --- a/resources/l10n/en.lproj/Localizable.strings +++ b/resources/l10n/en.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Hidden"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/es.lproj/Localizable.strings b/resources/l10n/es.lproj/Localizable.strings index 83df2495..3df3cc1a 100644 --- a/resources/l10n/es.lproj/Localizable.strings +++ b/resources/l10n/es.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Ocultas"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/fi.lproj/Localizable.strings b/resources/l10n/fi.lproj/Localizable.strings index 6d574600..4167f76d 100644 --- a/resources/l10n/fi.lproj/Localizable.strings +++ b/resources/l10n/fi.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Piilossa"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/fr.lproj/Localizable.strings b/resources/l10n/fr.lproj/Localizable.strings index 21fe68cf..c021431d 100644 --- a/resources/l10n/fr.lproj/Localizable.strings +++ b/resources/l10n/fr.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Cachées"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/hu.lproj/Localizable.strings b/resources/l10n/hu.lproj/Localizable.strings index cfef894d..cc31ce76 100644 --- a/resources/l10n/hu.lproj/Localizable.strings +++ b/resources/l10n/hu.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Rejtett"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/id.lproj/Localizable.strings b/resources/l10n/id.lproj/Localizable.strings index 99f171d6..20404d9d 100644 --- a/resources/l10n/id.lproj/Localizable.strings +++ b/resources/l10n/id.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Tersembunyi"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/it.lproj/Localizable.strings b/resources/l10n/it.lproj/Localizable.strings index 939de267..c847bf1f 100644 --- a/resources/l10n/it.lproj/Localizable.strings +++ b/resources/l10n/it.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Nascosto"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/ja.lproj/Localizable.strings b/resources/l10n/ja.lproj/Localizable.strings index 7a2842f7..f0a149fa 100644 --- a/resources/l10n/ja.lproj/Localizable.strings +++ b/resources/l10n/ja.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "ウィンドウをしまう"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/ko.lproj/Localizable.strings b/resources/l10n/ko.lproj/Localizable.strings index ec37057c..89d6f2b0 100644 --- a/resources/l10n/ko.lproj/Localizable.strings +++ b/resources/l10n/ko.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "숨겨진 화면"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "마우스를 올릴 때 색칠된 원들 숨기기:"; diff --git a/resources/l10n/lb.lproj/Localizable.strings b/resources/l10n/lb.lproj/Localizable.strings index cace0bda..25515041 100644 --- a/resources/l10n/lb.lproj/Localizable.strings +++ b/resources/l10n/lb.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Verstoppen"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/nl.lproj/Localizable.strings b/resources/l10n/nl.lproj/Localizable.strings index db54861e..388ae313 100644 --- a/resources/l10n/nl.lproj/Localizable.strings +++ b/resources/l10n/nl.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Verborgen"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/pt.lproj/Localizable.strings b/resources/l10n/pt.lproj/Localizable.strings index df4f4bfd..323e953c 100644 --- a/resources/l10n/pt.lproj/Localizable.strings +++ b/resources/l10n/pt.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Escondido"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Esconder círculos coloridos onde o rato passar:"; diff --git a/resources/l10n/ru.lproj/Localizable.strings b/resources/l10n/ru.lproj/Localizable.strings index e61e9555..3e631bf0 100644 --- a/resources/l10n/ru.lproj/Localizable.strings +++ b/resources/l10n/ru.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Скрытые"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Скрыть цветные круги при наведении мышью:"; diff --git a/resources/l10n/tr.lproj/Localizable.strings b/resources/l10n/tr.lproj/Localizable.strings index 42223845..2b51bb77 100644 --- a/resources/l10n/tr.lproj/Localizable.strings +++ b/resources/l10n/tr.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "Gizli"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/zh-CN.lproj/Localizable.strings b/resources/l10n/zh-CN.lproj/Localizable.strings index a174e24a..176d76b5 100644 --- a/resources/l10n/zh-CN.lproj/Localizable.strings +++ b/resources/l10n/zh-CN.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "隐藏"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "鼠标悬停时隐藏彩色圆圈:"; diff --git a/resources/l10n/zh-TW.lproj/Localizable.strings b/resources/l10n/zh-TW.lproj/Localizable.strings index 9a9fecc2..8be6efd8 100644 --- a/resources/l10n/zh-TW.lproj/Localizable.strings +++ b/resources/l10n/zh-TW.lproj/Localizable.strings @@ -128,6 +128,9 @@ /*No comment provided by engineer.*/ "Hidden" = "隱藏"; +/*No comment provided by engineer.*/ +"Hide app badges:" = "Hide app badges:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/scripts/subset_fonticon.sh b/scripts/subset_fonticon.sh index e2fb67ad..42c5b168 100755 --- a/scripts/subset_fonticon.sh +++ b/scripts/subset_fonticon.sh @@ -4,4 +4,4 @@ set -exu "$(pipenv --venv)"/bin/pyftsubset resources/SF-Pro-Text-Regular-Full.otf \ --output-file=resources/SF-Pro-Text-Regular.otf \ - --text="􀀁􀁑􀁏􀁍􀁎􀁌􀕧􀕬􀀸􀀺􀀼􀀾􀁀􀁂􀁄􀑱􀁆􀁈􀁊􀑳􀓵􀓶􀓷􀓸􀓹􀓺􀓻􀓼􀓽􀓾􀓿􀔀􀔁􀔂􀔃􀔄􀔅􀔆􀔇􀔈􀔉􀘠􀚗􀚙􀚛􀚝􀚟􀚡􀚣􀚥􀚧􀚩􀚫􀚭􀚯􀚱􀚳􀚵􀚷􀚹􀚻" + --text="􀀁􀁑􀁏􀁍􀁎􀁌􀕧􀕬􀀸􀀺􀀼􀀾􀁀􀁂􀁄􀑱􀁆􀁈􀁊􀑳􀓵􀓶􀓷􀓸􀓹􀓺􀓻􀓼􀓽􀓾􀓿􀔀􀔁􀔂􀔃􀔄􀔅􀔆􀔇􀔈􀔉􀀹􀀻􀀽􀀿􀁁􀘘􀁃􀁅􀑲􀁇􀁉􀁋􀑴􀔔􀔕􀔖􀔗􀔘􀔙􀔚􀔛􀔜􀔝􀔞􀔟􀔠􀔡􀔢􀔣􀔤􀔥􀔦􀔧􀔨􀕭" diff --git a/src/api-wrappers/AXUIElement.swift b/src/api-wrappers/AXUIElement.swift index 72061d46..a4b3eba8 100644 --- a/src/api-wrappers/AXUIElement.swift +++ b/src/api-wrappers/AXUIElement.swift @@ -116,6 +116,10 @@ extension AXUIElement { return try attribute(kAXParentAttribute, AXUIElement.self) } + func children() throws -> [AXUIElement]? { + return try attribute(kAXChildrenAttribute, [AXUIElement].self) + } + func windows() throws -> [AXUIElement]? { return try attribute(kAXWindowsAttribute, [AXUIElement].self) } @@ -140,6 +144,10 @@ extension AXUIElement { return try attribute(kAXSubroleAttribute, String.self) } + func appIsRunning() throws -> Bool? { + return try attribute(kAXIsApplicationRunningAttribute, Bool.self) + } + func closeButton() throws -> AXUIElement? { return try attribute(kAXCloseButtonAttribute, AXUIElement.self) } diff --git a/src/api-wrappers/PrivateApis.swift b/src/api-wrappers/PrivateApis.swift index 90319f2c..3d69ac60 100644 --- a/src/api-wrappers/PrivateApis.swift +++ b/src/api-wrappers/PrivateApis.swift @@ -121,8 +121,9 @@ func CGSGetWindowLevel(_ cid: CGSConnectionID, _ wid: CGWindowID, _ level: inout @_silgen_name("SLSRequestScreenCaptureAccess") @discardableResult func SLSRequestScreenCaptureAccess() -> UInt8 -// for some reason, this attribute is missing from AXAttributeConstants +// for some reason, these attributes are missing from AXAttributeConstants let kAXFullscreenAttribute = "AXFullScreen" +let kAXStatusLabelAttribute = "AXStatusLabel" // enables/disables a symbolic hotkeys. These are system shortcuts such as command+tab or Spotlight // it is possible to find all the existing hotkey IDs by using CGSGetSymbolicHotKeyValue on the first few hundred numbers diff --git a/src/logic/Application.swift b/src/logic/Application.swift index beb98a6d..ae36083f 100644 --- a/src/logic/Application.swift +++ b/src/logic/Application.swift @@ -7,6 +7,7 @@ class Application: NSObject { var axUiElement: AXUIElement? var axObserver: AXObserver? var isReallyFinishedLaunching = false + var dockLabel: String? static func notifications(_ app: NSRunningApplication) -> [String] { var n = [ diff --git a/src/logic/Applications.swift b/src/logic/Applications.swift index 5cfa6036..469a8c9e 100644 --- a/src/logic/Applications.swift +++ b/src/logic/Applications.swift @@ -73,6 +73,21 @@ class Applications { App.app.refreshOpenUi() } + static func refreshBadges() { + retryAxCallUntilTimeout { + let axDock = AXUIElementCreateApplication(Applications.list.first { $0.runningApplication.bundleIdentifier == "com.apple.dock" }!.runningApplication.processIdentifier) + let axList = try axDock.children()!.first { try $0.role() == "AXList" }! + let axAppDockItem = try axList.children()!.filter { try $0.subrole() == "AXApplicationDockItem" && ($0.appIsRunning() ?? false) } + try Applications.list.forEach { app in + if app.runningApplication.activationPolicy == .regular, + let bundleId = app.runningApplication.bundleIdentifier, + let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId) { + app.dockLabel = try axAppDockItem.first { try $0.attribute(kAXURLAttribute, URL.self) == url }?.attribute(kAXStatusLabelAttribute, String.self) + } + } + } + } + private static func isActualApplication(_ app: NSRunningApplication) -> Bool { return (app.activationPolicy != .prohibited || isNotXpc(app)) && notAltTab(app) } diff --git a/src/logic/Preferences.swift b/src/logic/Preferences.swift index 2fbf7d76..9ef46d2e 100644 --- a/src/logic/Preferences.swift +++ b/src/logic/Preferences.swift @@ -58,6 +58,7 @@ class Preferences { "minCellsPerRow": defaultsDependingOnScreenRatio_["minCellsPerRow"]!, "maxCellsPerRow": defaultsDependingOnScreenRatio_["maxCellsPerRow"]!, "shortcutStyle": "0", + "hideAppBadges": "false", ] // constant values @@ -96,6 +97,7 @@ class Preferences { static var fadeOutAnimation: Bool { defaults.bool("fadeOutAnimation") } static var hideSpaceNumberLabels: Bool { defaults.bool("hideSpaceNumberLabels") } static var hideStatusIcons: Bool { defaults.bool("hideStatusIcons") } + static var hideAppBadges: Bool { defaults.bool("hideAppBadges") } static var startAtLogin: Bool { defaults.bool("startAtLogin") } static var dontShowBlacklist: [String] { blacklistStringToArray(defaults.string("dontShowBlacklist")) } static var disableShortcutsBlacklist: [String] { blacklistStringToArray(defaults.string("disableShortcutsBlacklist")) } diff --git a/src/logic/Window.swift b/src/logic/Window.swift index d6244720..1b9e4f69 100644 --- a/src/logic/Window.swift +++ b/src/logic/Window.swift @@ -4,10 +4,11 @@ class Window { var cgWindowId: CGWindowID var title: String! var thumbnail: NSImage? - var icon: NSImage? + var icon: NSImage? { get { application.runningApplication.icon } } var shouldShowTheUser = true var isTabbed: Bool = false - var isHidden: Bool + var isHidden: Bool { get { application.runningApplication.isHidden } } + var dockLabel: Int? { get { application.dockLabel.flatMap { Int($0) } } } var isFullscreen: Bool var isMinimized: Bool var isOnAllSpaces: Bool @@ -35,8 +36,6 @@ class Window { self.cgWindowId = wid self.spaceId = Spaces.currentSpaceId self.spaceIndex = Spaces.currentSpaceIndex - self.icon = application.runningApplication.icon - self.isHidden = application.runningApplication.isHidden self.isFullscreen = isFullscreen self.isMinimized = isMinimized self.isOnAllSpaces = false diff --git a/src/logic/events/AccessibilityEvents.swift b/src/logic/events/AccessibilityEvents.swift index 95c1906a..68c2f817 100644 --- a/src/logic/events/AccessibilityEvents.swift +++ b/src/logic/events/AccessibilityEvents.swift @@ -86,11 +86,7 @@ private func applicationHiddenOrShown(_ element: AXUIElement, _ pid: pid_t, _ ty DispatchQueue.main.async { let windows = Windows.list.filter { // for AXUIElement of apps, CFEqual or == don't work; looks like a Cocoa bug - let isFromApp = $0.application.runningApplication.processIdentifier == pid - if isFromApp { - $0.isHidden = type == kAXApplicationHiddenNotification - } - return isFromApp + return $0.application.runningApplication.processIdentifier == pid } App.app.refreshOpenUi(windows) } diff --git a/src/ui/App.swift b/src/ui/App.swift index f9a6b00e..a684d8d5 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -270,6 +270,8 @@ class App: AppCenterApplication, NSApplicationDelegate { guard appIsBeingUsed else { return } Windows.refreshAllThumbnails() guard appIsBeingUsed else { return } + Applications.refreshBadges() + guard appIsBeingUsed else { return } refreshOpenUi() guard appIsBeingUsed else { return } thumbnailsPanel.show() diff --git a/src/ui/main-window/ThumbnailFontIconView.swift b/src/ui/main-window/ThumbnailFontIconView.swift index 44c88573..a45f4072 100644 --- a/src/ui/main-window/ThumbnailFontIconView.swift +++ b/src/ui/main-window/ThumbnailFontIconView.swift @@ -7,22 +7,21 @@ enum Symbols: String { case circledNumber0 = "􀀸" case circledNumber10 = "􀓵" case circledStar = "􀕬" + case filledCircledStar = "􀕭" case filledCircled = "􀀁" case filledCircledMultiplySign = "􀁑" case filledCircledMinusSign = "􀁏" case filledCircledPlusSign = "􀁍" + case filledCircledNumber0 = "􀀹" + case filledCircledNumber10 = "􀔔" } // Font icon using SF Symbols from the SF Pro font from Apple // see https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/ class ThumbnailFontIconView: ThumbnailTitleView { - convenience init(_ symbol: Symbols, _ size: CGFloat = Preferences.fontIconSize, _ color: NSColor = .white, _ isBackground: Bool = false) { + convenience init(_ symbol: Symbols, _ size: CGFloat = Preferences.fontIconSize, _ color: NSColor = .white, _ shadow: NSShadow? = ThumbnailView.makeShadow(.darkGray)) { // This helps SF symbols display vertically centered and not clipped at the bottom - if isBackground { - self.init(size, 3, shadow: nil) - } else { - self.init(size, 3) - } + self.init(size, 3, shadow: shadow) string = symbol.rawValue font = NSFont(name: "SF Pro Text", size: size) textColor = color @@ -31,32 +30,39 @@ class ThumbnailFontIconView: ThumbnailTitleView { } // number should be in the interval [0-50] - func setNumber(_ number: UInt32) { - let (baseCharacter, offset) = baseCharacterAndOffset(number) - assignIfDifferent(&string, String(UnicodeScalar(baseCharacter.unicodeScalars.first!.value + offset)!)) + func setNumber(_ number: Int, _ filled: Bool) { + let (baseCharacter, offset) = baseCharacterAndOffset(number, filled) + assignIfDifferent(&string, String(UnicodeScalar(Int(baseCharacter.unicodeScalars.first!.value) + offset)!)) + } + + private func baseCharacterAndOffset(_ number: Int, _ filled: Bool) -> (String, Int) { + if number <= 9 { + // numbers alternate between empty and full circles; we skip the full circles + return ((filled ? Symbols.filledCircledNumber0 : Symbols.circledNumber0).rawValue, number * 2) + } else { + return ((filled ? Symbols.filledCircledNumber10 : Symbols.circledNumber10).rawValue, number - 10) + } } func setStar() { assignIfDifferent(&string, Symbols.circledStar.rawValue) } - private func baseCharacterAndOffset(_ number: UInt32) -> (String, UInt32) { - if number <= 9 { - // numbers alternate between empty and full circles; we skip the full circles - return (Symbols.circledNumber0.rawValue, number * UInt32(2)) - } else { - return (Symbols.circledNumber10.rawValue, number - 10) - } + func setFilledStar() { + assignIfDifferent(&string, Symbols.filledCircledStar.rawValue) } } class ThumbnailFilledFontIconView: NSView { - convenience init(_ thumbnailFontIconView: ThumbnailFontIconView, _ backgroundColor: NSColor) { + convenience init(_ thumbnailFontIconView: ThumbnailFontIconView, _ backgroundColor: NSColor, _ offset: Bool = false) { self.init(frame: .zero) translatesAutoresizingMaskIntoConstraints = false - let backgroundView = ThumbnailFontIconView(.filledCircled, thumbnailFontIconView.font!.pointSize, backgroundColor, true) + let backgroundView = ThumbnailFontIconView(.filledCircled, thumbnailFontIconView.font!.pointSize - (offset ? 2 : 0), backgroundColor) addSubview(backgroundView) addSubview(thumbnailFontIconView, positioned: .above, relativeTo: nil) - fit(backgroundView.fittingSize.width, backgroundView.fittingSize.height) + if offset { + backgroundView.leftAnchor.constraint(equalTo: backgroundView.superview!.leftAnchor, constant: 1).isActive = true + } + fit(thumbnailFontIconView.fittingSize.width, thumbnailFontIconView.fittingSize.height) } } diff --git a/src/ui/main-window/ThumbnailView.swift b/src/ui/main-window/ThumbnailView.swift index 73c54c1d..bfd8d070 100644 --- a/src/ui/main-window/ThumbnailView.swift +++ b/src/ui/main-window/ThumbnailView.swift @@ -9,9 +9,10 @@ class ThumbnailView: NSStackView { var minimizedIcon = ThumbnailFontIconView(.circledMinusSign) var hiddenIcon = ThumbnailFontIconView(.circledSlashSign) var spaceIcon = ThumbnailFontIconView(.circledNumber0) - var closeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMultiplySign, Preferences.fontIconSize, NSColor(srgbRed: 1, green: 0.35, blue: 0.32, alpha: 1)), NSColor(srgbRed: 0.64, green: 0.03, blue: 0.02, alpha: 1)) - var minimizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMinusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.91, green: 0.75, blue: 0.16, alpha: 1)), NSColor(srgbRed: 0.71, green: 0.55, blue: 0.09, alpha: 1)) - var maximizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledPlusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.32, green: 0.76, blue: 0.17, alpha: 1)), NSColor(srgbRed: 0.04, green: 0.39, blue: 0.02, alpha: 1)) + var dockLabelIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledNumber0, (Preferences.fontIconSize * 0.7).rounded(), NSColor(srgbRed: 1, green: 0.30, blue: 0.25, alpha: 1), nil), NSColor.white, true) + var closeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMultiplySign, Preferences.fontIconSize, NSColor(srgbRed: 1, green: 0.35, blue: 0.32, alpha: 1), nil), NSColor(srgbRed: 0.64, green: 0.03, blue: 0.02, alpha: 1)) + var minimizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMinusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.91, green: 0.75, blue: 0.16, alpha: 1), nil), NSColor(srgbRed: 0.71, green: 0.55, blue: 0.09, alpha: 1)) + var maximizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledPlusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.32, green: 0.76, blue: 0.17, alpha: 1), nil), NSColor(srgbRed: 0.04, green: 0.39, blue: 0.02, alpha: 1)) var hStackView: NSStackView! var mouseUpCallback: (() -> Void)! var mouseMovedCallback: (() -> Void)! @@ -42,9 +43,16 @@ class ThumbnailView: NSStackView { hStackView.spacing = Preferences.intraCellPadding setViews([hStackView, thumbnail], in: .leading) addWindowControls() + addDockLabelIcon() } - func addWindowControls() { + private func addDockLabelIcon() { + appIcon.addSubview(dockLabelIcon, positioned: .above, relativeTo: nil) + dockLabelIcon.topAnchor.constraint(equalTo: appIcon.topAnchor, constant: -4).isActive = true + dockLabelIcon.rightAnchor.constraint(equalTo: appIcon.rightAnchor, constant: 1).isActive = true + } + + private func addWindowControls() { thumbnail.addSubview(closeIcon, positioned: .above, relativeTo: nil) thumbnail.addSubview(minimizeIcon, positioned: .above, relativeTo: nil) thumbnail.addSubview(maximizeIcon, positioned: .above, relativeTo: nil) @@ -117,10 +125,19 @@ class ThumbnailView: NSStackView { assignIfDifferent(&minimizedIcon.isHidden, !element.isMinimized || Preferences.hideStatusIcons) assignIfDifferent(&spaceIcon.isHidden, Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels) if !spaceIcon.isHidden { - if element.isOnAllSpaces { + if element.spaceIndex > 30 || element.isOnAllSpaces { spaceIcon.setStar() } else { - spaceIcon.setNumber(UInt32(element.spaceIndex)) + spaceIcon.setNumber(element.spaceIndex, false) + } + } + assignIfDifferent(&dockLabelIcon.isHidden, element.dockLabel == nil || Preferences.hideAppBadges || Preferences.iconSize == 0) + if !dockLabelIcon.isHidden, let dockLabel = element.dockLabel { + let view = dockLabelIcon.subviews[1] as! ThumbnailFontIconView + if dockLabel > 30 { + view.setFilledStar() + } else { + view.setNumber(dockLabel, true) } } assignIfDifferent(&frame.size.width, max(thumbnail.frame.size.width + Preferences.intraCellPadding * 2, ThumbnailView.widthMin(screen))) diff --git a/src/ui/preferences-window/tabs/AppearanceTab.swift b/src/ui/preferences-window/tabs/AppearanceTab.swift index 4e064c10..a91ae4aa 100644 --- a/src/ui/preferences-window/tabs/AppearanceTab.swift +++ b/src/ui/preferences-window/tabs/AppearanceTab.swift @@ -24,6 +24,7 @@ class AppearanceTab: NSViewController, PreferencePane { LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide status icons:", comment: ""), "hideStatusIcons"), LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Show standard tabs as windows:", comment: ""), "showTabsAsWindows"), LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide colored circles on mouse hover:", comment: ""), "hideColoredCircles"), + LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide app badges:", comment: ""), "hideAppBadges"), ]) grid.column(at: 0).xPlacement = .trailing grid.rowAlignment = .lastBaseline