diff --git a/src/logic/Application.swift b/src/logic/Application.swift index 50b246d5c..eb57d375e 100644 --- a/src/logic/Application.swift +++ b/src/logic/Application.swift @@ -64,9 +64,9 @@ class Application: NSObject { func addAndObserveWindows() { if runningApplication.isFinishedLaunching && runningApplication.activationPolicy != .prohibited && axUiElement == nil { - axUiElement = AXUIElementCreateApplication(runningApplication.processIdentifier) - AXObserverCreate(runningApplication.processIdentifier, axObserverCallback, &axObserver) - debugPrint("Adding app", runningApplication.processIdentifier, runningApplication.bundleIdentifier ?? "nil") + axUiElement = AXUIElementCreateApplication(pid) + AXObserverCreate(pid, axObserverCallback, &axObserver) + debugPrint("Adding app", pid, runningApplication.bundleIdentifier ?? "nil") observeEvents() } } diff --git a/src/logic/Preferences.swift b/src/logic/Preferences.swift index 65ef5e346..ee953645f 100644 --- a/src/logic/Preferences.swift +++ b/src/logic/Preferences.swift @@ -60,6 +60,7 @@ class Preferences { "shortcutStyle": "0", "hideAppBadges": "false", "hideWindowlessApps": "false", + "hideThumbnails": "false", ] // constant values @@ -69,7 +70,6 @@ class Preferences { static var windowPadding: CGFloat { 18 } static var interCellPadding: CGFloat { 5 } static var intraCellPadding: CGFloat { 5 } - static var fontIconSize: CGFloat { 20 } // persisted values static var maxScreenUsage: CGFloat { defaults.cgfloat("maxScreenUsage") / CGFloat(100) } @@ -100,6 +100,7 @@ class Preferences { static var hideStatusIcons: Bool { defaults.bool("hideStatusIcons") } static var hideAppBadges: Bool { defaults.bool("hideAppBadges") } static var hideWindowlessApps: Bool { defaults.bool("hideWindowlessApps") } + static var hideThumbnails: Bool { defaults.bool("hideThumbnails") } 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 448f1f979..acf0f3699 100644 --- a/src/logic/Window.swift +++ b/src/logic/Window.swift @@ -152,7 +152,7 @@ class Window { } func focus() { - if application.runningApplication.processIdentifier == ProcessInfo.processInfo.processIdentifier { + if application.pid == ProcessInfo.processInfo.processIdentifier { App.app.showSecondaryWindow(App.app.window(withWindowNumber: Int(cgWindowId))) } else if isWindowlessApp { if let bundleID = application.runningApplication.bundleIdentifier { diff --git a/src/logic/Windows.swift b/src/logic/Windows.swift index d4636614a..081074336 100644 --- a/src/logic/Windows.swift +++ b/src/logic/Windows.swift @@ -87,6 +87,7 @@ class Windows { } static func refreshFirstFewThumbnailsSync() { + if Preferences.hideThumbnails { return } list.filter { $0.shouldShowTheUser } .prefix(criticalFirstThumbnails) .forEachAsync { window in window.refreshThumbnail() } @@ -94,6 +95,7 @@ class Windows { static func refreshThumbnailsAsync(_ screen: NSScreen, _ currentIndex: Int = criticalFirstThumbnails) { guard App.app.appIsBeingUsed else { return } + if Preferences.hideThumbnails { return } BackgroundWork.mainQueueConcurrentWorkQueue.async { if currentIndex < list.count { let window = list[currentIndex] diff --git a/src/ui/main-window/ThumbnailFontIconView.swift b/src/ui/main-window/ThumbnailFontIconView.swift index 862553c0c..f00dff3f7 100644 --- a/src/ui/main-window/ThumbnailFontIconView.swift +++ b/src/ui/main-window/ThumbnailFontIconView.swift @@ -27,11 +27,11 @@ class FontIcon: BaseLabel { // 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, _ shadow: NSShadow? = ThumbnailView.makeShadow(.darkGray)) { - // This helps SF symbols display vertically centered and not clipped at the bottom - self.init(size, 3, shadow: shadow) + convenience init(_ symbol: Symbols, _ size: CGFloat = Preferences.fontHeight, _ color: NSColor = .white, _ shadow: NSShadow? = ThumbnailView.makeShadow(.darkGray)) { + self.init(size, shadow) string = symbol.rawValue - font = NSFont(name: "SF Pro Text", size: size) + // This helps SF symbols display vertically centered and not clipped at the top + font = NSFont(name: "SF Pro Text", size: (size * 0.85).rounded()) textColor = color // This helps SF symbols not be clipped on the right widthAnchor.constraint(equalToConstant: size * 1.15).isActive = true @@ -62,15 +62,13 @@ class ThumbnailFontIconView: ThumbnailTitleView { } class ThumbnailFilledFontIconView: NSView { - convenience init(_ thumbnailFontIconView: ThumbnailFontIconView, _ backgroundColor: NSColor, _ offset: Bool = false) { + convenience init(_ thumbnailFontIconView: ThumbnailFontIconView, _ backgroundColor: NSColor) { self.init(frame: .zero) translatesAutoresizingMaskIntoConstraints = false - let backgroundView = ThumbnailFontIconView(.filledCircled, thumbnailFontIconView.font!.pointSize - (offset ? 2 : 0), backgroundColor) + let backgroundView = ThumbnailFontIconView(.filledCircled, 14 - 2, backgroundColor, nil) addSubview(backgroundView) addSubview(thumbnailFontIconView, positioned: .above, relativeTo: nil) - if offset { - backgroundView.leftAnchor.constraint(equalTo: backgroundView.superview!.leftAnchor, constant: 1).isActive = true - } + backgroundView.frame.origin = CGPoint(x: backgroundView.frame.origin.x + 1, y: backgroundView.frame.origin.y + 1) fit(thumbnailFontIconView.fittingSize.width, thumbnailFontIconView.fittingSize.height) } } diff --git a/src/ui/main-window/ThumbnailTitleView.swift b/src/ui/main-window/ThumbnailTitleView.swift index 9e74692ac..58d990335 100644 --- a/src/ui/main-window/ThumbnailTitleView.swift +++ b/src/ui/main-window/ThumbnailTitleView.swift @@ -1,9 +1,7 @@ import Cocoa class ThumbnailTitleView: BaseLabel { - var magicOffset = CGFloat(0) - - convenience init(_ size: CGFloat, _ magicOffset: CGFloat = 0, shadow: NSShadow? = ThumbnailView.makeShadow(.darkGray)) { + convenience init(_ size: CGFloat, _ shadow: NSShadow? = ThumbnailView.makeShadow(.darkGray)) { let textStorage = NSTextStorage() let layoutManager = NSLayoutManager() textStorage.addLayoutManager(layoutManager) @@ -13,18 +11,17 @@ class ThumbnailTitleView: BaseLabel { layoutManager.addTextContainer(textContainer) self.init(NSRect.zero, textContainer) font = Preferences.font - self.magicOffset = magicOffset textColor = Preferences.fontColor self.shadow = shadow defaultParagraphStyle = makeParagraphStyle(size) - heightAnchor.constraint(equalToConstant: size + magicOffset).isActive = true + heightAnchor.constraint(equalToConstant: size).isActive = true } private func makeParagraphStyle(_ size: CGFloat) -> NSMutableParagraphStyle { let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle paragraphStyle.lineBreakMode = getTruncationMode() - paragraphStyle.maximumLineHeight = size + magicOffset - paragraphStyle.minimumLineHeight = size + magicOffset + paragraphStyle.maximumLineHeight = size + paragraphStyle.minimumLineHeight = size paragraphStyle.allowsDefaultTighteningForTruncation = false return paragraphStyle } diff --git a/src/ui/main-window/ThumbnailView.swift b/src/ui/main-window/ThumbnailView.swift index 6c4ace619..4d72e497b 100644 --- a/src/ui/main-window/ThumbnailView.swift +++ b/src/ui/main-window/ThumbnailView.swift @@ -9,7 +9,7 @@ class ThumbnailView: NSStackView { var minimizedIcon = ThumbnailFontIconView(.circledMinusSign) var hiddenIcon = ThumbnailFontIconView(.circledSlashSign) var spaceIcon = ThumbnailFontIconView(.circledNumber0) - 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 dockLabelIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledNumber0, 14, NSColor(srgbRed: 1, green: 0.30, blue: 0.25, alpha: 1), nil), NSColor.white) var closeIcon = WindowControlView("close", 16) var minimizeIcon = WindowControlView("minimize", 16) var maximizeIcon = WindowControlView("fullscreen", 16) @@ -36,12 +36,10 @@ class ThumbnailView: NSStackView { layer!.borderWidth = Preferences.cellBorderWidth edgeInsets = NSEdgeInsets(top: Preferences.intraCellPadding, left: Preferences.intraCellPadding, bottom: Preferences.intraCellPadding, right: Preferences.intraCellPadding) orientation = .vertical - spacing = Preferences.intraCellPadding let shadow = ThumbnailView.makeShadow(.gray) thumbnail.shadow = shadow appIcon.shadow = shadow hStackView = NSStackView(views: [appIcon, label, hiddenIcon, fullscreenIcon, minimizedIcon, spaceIcon]) - hStackView.spacing = Preferences.intraCellPadding setViews([hStackView, thumbnail], in: .leading) addWindowControls() addDockLabelIcon() @@ -70,7 +68,7 @@ class ThumbnailView: NSStackView { if let shouldShowWindowControls = shouldShowWindowControls_ { self.shouldShowWindowControls = shouldShowWindowControls } - let shouldShow = shouldShowWindowControls && isHighlighted && !Preferences.hideColoredCircles && !window_!.isWindowlessApp + let shouldShow = shouldShowWindowControls && isHighlighted && !Preferences.hideColoredCircles && !window_!.isWindowlessApp && !Preferences.hideThumbnails if isShowingWindowControls != shouldShow { isShowingWindowControls = shouldShow [closeIcon, minimizeIcon, maximizeIcon].forEach { $0.isHidden = !shouldShow } @@ -101,14 +99,19 @@ class ThumbnailView: NSStackView { func updateRecycledCellWithNewContent(_ element: Window, _ index: Int, _ newHeight: CGFloat, _ screen: NSScreen) { window_ = element - thumbnail.image = element.thumbnail - if let image = thumbnail.image { - image.size = element.thumbnailFullSize! + assignIfDifferent(&thumbnail.isHidden, Preferences.hideThumbnails) + if !Preferences.hideThumbnails { + thumbnail.image = element.thumbnail + if let image = thumbnail.image { + image.size = element.thumbnailFullSize! + } + let (thumbnailWidth, thumbnailHeight) = ThumbnailView.thumbnailSize(element.thumbnail, screen) + let thumbnailSize = NSSize(width: thumbnailWidth.rounded(), height: thumbnailHeight.rounded()) + thumbnail.image?.size = thumbnailSize + thumbnail.frame.size = thumbnailSize } - let (thumbnailWidth, thumbnailHeight) = ThumbnailView.thumbnailSize(element.thumbnail, screen) - let thumbnailSize = NSSize(width: thumbnailWidth.rounded(), height: thumbnailHeight.rounded()) - thumbnail.image?.size = thumbnailSize - thumbnail.frame.size = thumbnailSize + assignIfDifferent(&spacing, Preferences.hideThumbnails ? 0 : Preferences.intraCellPadding) + assignIfDifferent(&hStackView.spacing, Preferences.fontHeight == 0 ? 0 : Preferences.intraCellPadding) if appIcon.image != element.icon { appIcon.image = element.icon let appIconSize = NSSize(width: Preferences.iconSize, height: Preferences.iconSize) @@ -142,11 +145,11 @@ class ThumbnailView: NSStackView { } dockLabelIcon.setFrameOrigin(NSPoint(x: appIcon.frame.maxX - dockLabelIcon.fittingSize.width - 1, y: appIcon.frame.maxY - dockLabelIcon.fittingSize.height + 4)) } - assignIfDifferent(&frame.size.width, max(thumbnail.frame.size.width + Preferences.intraCellPadding * 2, ThumbnailView.widthMin(screen))) + assignIfDifferent(&frame.size.width, max((Preferences.hideThumbnails ? hStackView.fittingSize.width : thumbnail.frame.size.width) + Preferences.intraCellPadding * 2, ThumbnailView.widthMin(screen))) assignIfDifferent(&frame.size.height, newHeight) - let fontIconWidth = CGFloat([fullscreenIcon, minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.intraCellPadding) + let fontIconWidth = CGFloat([fullscreenIcon, minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontHeight + Preferences.intraCellPadding) assignIfDifferent(&label.textContainer!.size.width, frame.width - Preferences.iconSize - Preferences.intraCellPadding * 3 - fontIconWidth) - assignIfDifferent(&windowlessIcon.isHidden, !element.isWindowlessApp) + assignIfDifferent(&windowlessIcon.isHidden, !element.isWindowlessApp || Preferences.hideThumbnails) if element.isWindowlessApp { let maxWidth = (ThumbnailView.widthMin(screen) - Preferences.intraCellPadding * 2).rounded() let maxHeight = ((ThumbnailView.height(screen) - hStackView.fittingSize.height) - Preferences.intraCellPadding * 2).rounded() diff --git a/src/ui/main-window/ThumbnailsView.swift b/src/ui/main-window/ThumbnailsView.swift index 221255e99..fe5b1d652 100644 --- a/src/ui/main-window/ThumbnailsView.swift +++ b/src/ui/main-window/ThumbnailsView.swift @@ -51,8 +51,15 @@ class ThumbnailsView: NSVisualEffectView { } } + func rowHeight(_ screen: NSScreen) -> CGFloat { + if Preferences.hideThumbnails { + return max(Preferences.iconSize, Preferences.fontHeight + 3) + Preferences.intraCellPadding * 2 + } + return ThumbnailView.height(screen).rounded(.down) + } + private func layoutThumbnailViews(_ screen: NSScreen, _ widthMax: CGFloat) -> (CGFloat, CGFloat)? { - let height = ThumbnailView.height(screen).rounded(.down) + let height = rowHeight(screen) let isLeftToRight = App.shared.userInterfaceLayoutDirection == .leftToRight let startingX = isLeftToRight ? Preferences.interCellPadding : widthMax - Preferences.interCellPadding var currentX = startingX diff --git a/src/ui/preferences-window/tabs/AppearanceTab.swift b/src/ui/preferences-window/tabs/AppearanceTab.swift index 8d8f49d17..32d8ca803 100644 --- a/src/ui/preferences-window/tabs/AppearanceTab.swift +++ b/src/ui/preferences-window/tabs/AppearanceTab.swift @@ -9,7 +9,7 @@ class AppearanceTab { LabelAndControl.makeLabelWithSlider(NSLocalizedString("Rows of windows:", comment: ""), "rowsCount", 1, 20, 20, true), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Min windows per row:", comment: ""), "minCellsPerRow", 1, 20, 20, true), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Max windows per row:", comment: ""), "maxCellsPerRow", 1, 40, 20, true), - LabelAndControl.makeLabelWithSlider(NSLocalizedString("Window app icon size:", comment: ""), "iconSize", 0, 64, 11, false, "px"), + LabelAndControl.makeLabelWithSlider(NSLocalizedString("Window app icon size:", comment: ""), "iconSize", 0, 128, 11, false, "px"), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Window title font size:", comment: ""), "fontHeight", 0, 64, 11, false, "px"), LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Window title truncation:", comment: ""), "titleTruncation", TitleTruncationPreference.allCases), LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show on:", comment: ""), "showOnScreen", ShowOnScreenPreference.allCases), @@ -21,6 +21,7 @@ class AppearanceTab { LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide colored circles on mouse hover:", comment: ""), "hideColoredCircles"), LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide app badges:", comment: ""), "hideAppBadges"), LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide apps with no open window:", comment: ""), "hideWindowlessApps"), + LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide window thumbnails:", comment: ""), "hideThumbnails"), ]) grid.column(at: 0).xPlacement = .trailing grid.rowAlignment = .lastBaseline