diff --git a/resources/l10n/Localizable.strings b/resources/l10n/Localizable.strings index 4cb9c51c..c0182d7d 100644 --- a/resources/l10n/Localizable.strings +++ b/resources/l10n/Localizable.strings @@ -121,6 +121,9 @@ /* No comment provided by engineer. */ "Ask whether to send crash reports" = "Ask whether to send crash reports"; +/* No comment provided by engineer. */ +"Auto" = "Auto"; + /* No comment provided by engineer. */ "Auto-install updates periodically" = "Auto-install updates periodically"; diff --git a/resources/l10n/en.lproj/Localizable.strings b/resources/l10n/en.lproj/Localizable.strings index a681ae57..d859a2b5 100644 --- a/resources/l10n/en.lproj/Localizable.strings +++ b/resources/l10n/en.lproj/Localizable.strings @@ -26,6 +26,7 @@ "AltTab will show if the window is currently minimized or fullscreen with a status icon." = "AltTab will show if the window is currently minimized or fullscreen with a status icon."; "Always" = "Always"; "Always send crash reports" = "Always send crash reports"; +"Auto" = "Auto"; "and press" = "and press"; "and press:" = "and press:"; "Animations" = "Animations"; diff --git a/src/logic/Appearance.swift b/src/logic/Appearance.swift index 8ae6822a..fdec38e5 100644 --- a/src/logic/Appearance.swift +++ b/src/logic/Appearance.swift @@ -2,6 +2,7 @@ import Cocoa class Appearance { // size + static var resolvedSize = AppearanceSizePreference.medium static var hideThumbnails = Bool(false) static var windowPadding = CGFloat(1000) static var windowCornerRadius = CGFloat(1000) @@ -54,16 +55,29 @@ class Appearance { private static func updateSize() { let isHorizontalScreen = NSScreen.preferred.isHorizontal() maxWidthOnScreen = AppearanceTestable.comfortableWidth(NSScreen.preferred.physicalSize().map { $0.width }) - if currentStyle == .appIcons { - appIconsSize() - } else if currentStyle == .titles { - titlesSize(isHorizontalScreen) - } else { - thumbnailsSize(isHorizontalScreen) - } + let sizeToApply: AppearanceSizePreference = currentSize == .auto ? .large : currentSize + resolvedSize = sizeToApply + applyConcreteSize(sizeToApply, isHorizontalScreen) updateFont() } + static func applySize(_ size: AppearanceSizePreference) { + let isHorizontalScreen = NSScreen.preferred.isHorizontal() + resolvedSize = size + applyConcreteSize(size, isHorizontalScreen) + updateFont() + } + + private static func applyConcreteSize(_ size: AppearanceSizePreference, _ isHorizontalScreen: Bool) { + if currentStyle == .appIcons { + appIconsSize(size) + } else if currentStyle == .titles { + titlesSize(isHorizontalScreen, size) + } else { + thumbnailsSize(isHorizontalScreen, size) + } + } + private static func updateTheme() { highlightBorderWidth = currentStyle == .titles ? 2 : 3 if currentTheme == .dark { @@ -79,7 +93,7 @@ class Appearance { } } - private static func thumbnailsSize(_ isHorizontalScreen: Bool) { + private static func thumbnailsSize(_ isHorizontalScreen: Bool, _ size: AppearanceSizePreference) { hideThumbnails = false windowPadding = 18 windowCornerRadius = 23 @@ -90,7 +104,7 @@ class Appearance { windowCornerRadius = 43 cellCornerRadius = 18 } - switch currentSize { + switch size { case .small: rowsCount = isHorizontalScreen ? 5 : 8 iconSize = 16 @@ -99,7 +113,7 @@ class Appearance { rowsCount = isHorizontalScreen ? 4 : 7 iconSize = 26 fontHeight = 14 - case .large: + case .large, .auto: rowsCount = isHorizontalScreen ? 3 : 6 iconSize = 28 fontHeight = 16 @@ -108,7 +122,7 @@ class Appearance { (windowMinWidthInRow, windowMaxWidthInRow) = AppearanceTestable.goodValuesForThumbnailsWidthMinMax(tilesPanelRatio, rowsCount) } - private static func appIconsSize() { + private static func appIconsSize(_ size: AppearanceSizePreference) { hideThumbnails = true windowPadding = 25 windowCornerRadius = 23 @@ -120,7 +134,7 @@ class Appearance { windowMinWidthInRow = 0.04 windowMaxWidthInRow = 0.3 rowsCount = 1 - switch currentSize { + switch size { case .small: iconSize = 70 fontHeight = 13 @@ -135,7 +149,7 @@ class Appearance { windowCornerRadius = 55 cellCornerRadius = 35 } - case .large: + case .large, .auto: windowPadding = 28 iconSize = 150 fontHeight = 16 @@ -146,7 +160,7 @@ class Appearance { } } - private static func titlesSize(_ isHorizontalScreen: Bool) { + private static func titlesSize(_ isHorizontalScreen: Bool, _ size: AppearanceSizePreference) { hideThumbnails = true windowPadding = 18 windowCornerRadius = 23 @@ -155,14 +169,14 @@ class Appearance { windowMinWidthInRow = 0.6 windowMaxWidthInRow = 0.9 rowsCount = 1 - switch currentSize { + switch size { case .small: iconSize = 18 fontHeight = 13 case .medium: iconSize = 24 fontHeight = 14 - case .large: + case .large, .auto: iconSize = 30 fontHeight = 16 } diff --git a/src/logic/MacroPreferences.swift b/src/logic/MacroPreferences.swift index c82c0eb8..56c3b8e2 100644 --- a/src/logic/MacroPreferences.swift +++ b/src/logic/MacroPreferences.swift @@ -424,12 +424,14 @@ enum AppearanceSizePreference: CaseIterable, SfSymbolMacroPreference { case small case medium case large + case auto var localizedString: LocalizedString { switch self { case .small: return NSLocalizedString("Small", comment: "") case .medium: return NSLocalizedString("Medium", comment: "") case .large: return NSLocalizedString("Large", comment: "") + case .auto: return NSLocalizedString("Auto", comment: "") } } @@ -438,6 +440,7 @@ enum AppearanceSizePreference: CaseIterable, SfSymbolMacroPreference { case .small: return "moonphase.waning.gibbous.inverse" case .medium: return "moonphase.last.quarter.inverse" case .large: return "moonphase.waning.crescent.inverse" + case .auto: return "sparkles" } } } diff --git a/src/logic/Preferences.swift b/src/logic/Preferences.swift index 0cc2f1c1..38b0cbfc 100644 --- a/src/logic/Preferences.swift +++ b/src/logic/Preferences.swift @@ -40,7 +40,7 @@ class Preferences { "hideColoredCircles": "false", "windowDisplayDelay": "100", "appearanceStyle": AppearanceStylePreference.thumbnails.indexAsString, - "appearanceSize": AppearanceSizePreference.medium.indexAsString, + "appearanceSize": AppearanceSizePreference.auto.indexAsString, "appearanceTheme": AppearanceThemePreference.system.indexAsString, "theme": ThemePreference.macOs.indexAsString, "showOnScreen": ShowOnScreenPreference.active.indexAsString, diff --git a/src/ui/generic-components/WindowlessAppIndicator.swift b/src/ui/generic-components/WindowlessAppIndicator.swift index d582cb2c..8a4a746f 100644 --- a/src/ui/generic-components/WindowlessAppIndicator.swift +++ b/src/ui/generic-components/WindowlessAppIndicator.swift @@ -43,12 +43,12 @@ class WindowlessAppIndicator: NSView { static func getAppearanceParameter() -> AppearanceParameter { if Preferences.appearanceStyle == .thumbnails || Preferences.appearanceStyle == .appIcons { - if Preferences.appearanceSize == .large { + if Appearance.resolvedSize == .large { return AppearanceParameter(width: 12, height: 5, cornerRadius: 2) } return AppearanceParameter(width: 10, height: 5, cornerRadius: 2) } - if Preferences.appearanceSize == .large { + if Appearance.resolvedSize == .large { return AppearanceParameter(width: 8, height: 3, cornerRadius: 1) } return AppearanceParameter(width: 6, height: 3, cornerRadius: 1) diff --git a/src/ui/main-window/TileView.swift b/src/ui/main-window/TileView.swift index 0f326b0f..f9cd57a3 100644 --- a/src/ui/main-window/TileView.swift +++ b/src/ui/main-window/TileView.swift @@ -204,7 +204,7 @@ class TileView: FlippedView { private func updateAppIconsLabelFrame() { let viewWidth = frame.width let labelWidth = fullTitleWidth - let padding = (Preferences.appearanceSize == .small ? 0 : ( Preferences.appearanceSize == .medium ? 1 : 2)) * Appearance.intraCellPadding + let padding = (Appearance.resolvedSize == .small ? 0 : (Appearance.resolvedSize == .medium ? 1 : 2)) * Appearance.intraCellPadding let maxAllowedLabelWidth = getMaxAllowedLabelWidth() let sidesToOffset: CGFloat = (isFirstInRow ? 1 : 0) + (isLastInRow ? 1 : 0) let paddingForOffset = sidesToOffset * padding @@ -500,7 +500,7 @@ class TileView: FlippedView { private func updateDockLabelIconPosition() { let iconSize = max(appIcon.frame.width, appIcon.frame.height) - let offset = (iconSize * (Preferences.appearanceStyle == .appIcons && Preferences.appearanceSize == .large ? 0.03 : 0.05)).rounded() + let offset = (iconSize * (Preferences.appearanceStyle == .appIcons && Appearance.resolvedSize == .large ? 0.03 : 0.05)).rounded() let badgeTopRightX = appIcon.frame.maxX + offset let badgeTopRightY = appIcon.frame.minY - offset assignIfDifferent(&dockLabelIcon.frame.origin.x, badgeTopRightX - dockLabelIcon.frame.width) diff --git a/src/ui/main-window/TilesView.swift b/src/ui/main-window/TilesView.swift index 9ae2b5dd..d5e2c618 100644 --- a/src/ui/main-window/TilesView.swift +++ b/src/ui/main-window/TilesView.swift @@ -363,6 +363,10 @@ class TilesView: NSObject { func updateItemsAndLayout(_ preservedScrollOrigin: CGPoint?) { let widthMax = TilesPanel.maxThumbnailsWidth().rounded() + if Preferences.appearanceSize == .auto { + resolveAutoSize(widthMax) + Self.updateCachedSizes() + } if let (maxX, maxY, labelHeight, rowSignature) = layoutTileViews(widthMax) { layoutParentViews(maxX, widthMax, maxY, labelHeight) if Preferences.alignThumbnails == .center { @@ -401,6 +405,48 @@ class TilesView: NSObject { scrollView.reflectScrolledClipView(scrollView.contentView) } + private func resolveAutoSize(_ widthMax: CGFloat) { + let searchReservedHeight: CGFloat = searchMode == .off ? 0 : searchBarHeight() + 10 + let heightMax = max(0, TilesPanel.maxThumbnailsHeight() - searchReservedHeight) + for size in [AppearanceSizePreference.large, .medium, .small] { + Appearance.applySize(size) + Self.updateCachedSizes() + let maxY = dryRunLayoutTileViews(widthMax) + if size == .small || maxY <= heightMax { return } + } + } + + private func dryRunLayoutTileViews(_ widthMax: CGFloat) -> CGFloat { + let labelHeight = Self.layoutCache.labelHeight + let height = TileView.height(labelHeight) + let isLeftToRight = App.shared.userInterfaceLayoutDirection == .leftToRight + let startingX = isLeftToRight ? Appearance.interCellPadding : widthMax - Appearance.interCellPadding + var currentX = startingX + var currentY = Appearance.interCellPadding + var maxY = currentY + height + Appearance.interCellPadding + var index = 0 + while index < TilesView.recycledViews.count { + guard App.app.appIsBeingUsed else { return maxY } + defer { index += 1 } + let view = TilesView.recycledViews[index] + guard index < Windows.list.count else { break } + let window = Windows.list[index] + guard Windows.shouldDisplay(window) else { view.frame = .zero; continue } + view.updateRecycledCellWithNewContent(window, index, height) + let width = view.frame.size.width + let projectedX = projectedWidth(currentX, width).rounded(.down) + if needNewLine(projectedX, widthMax) { + currentX = startingX + currentY = (currentY + height + Appearance.interCellPadding).rounded(.down) + currentX = projectedWidth(currentX, width).rounded(.down) + maxY = max(currentY + height + Appearance.interCellPadding, maxY) + } else { + currentX = projectedX + } + } + return maxY + } + private func layoutTileViews(_ widthMax: CGFloat) -> (CGFloat, CGFloat, CGFloat, [Int])? { let labelHeight = Self.layoutCache.labelHeight let height = TileView.height(labelHeight)