diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index 52609f34..a4c4ba90 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -70,7 +70,7 @@ 5F21C9E42C6E94700091F72F /* CustomizeStyleSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21C9E32C6E94700091F72F /* CustomizeStyleSheet.swift */; }; 5F21C9E62C6E94920091F72F /* AnimationsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21C9E52C6E94920091F72F /* AnimationsSheet.swift */; }; 5F21C9E82C6E96A00091F72F /* SheetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21C9E72C6E96A00091F72F /* SheetWindow.swift */; }; - 5F4753072CEE5A2E002C6C5E /* AppearanceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8D57C86AF492D43A576D /* AppearanceUtils.swift */; }; + 5F4753072CEE5A2E002C6C5E /* AppearanceTestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8D57C86AF492D43A576D /* AppearanceTestable.swift */; }; 5F706EC02BA448B1004D542D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5F706EBE2BA448B1004D542D /* Localizable.strings */; }; 5F706EC32BA448D8004D542D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5F706EC12BA448D8004D542D /* InfoPlist.strings */; }; 76D02BB22BFE7C9E0056008D /* Pods_alt_tab_macos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */; }; @@ -86,7 +86,7 @@ BF0C81A79128CF877FE895CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8A48BBB3BF649864DB08 /* InfoPlist.strings */; }; BF0C81B64A1205F066AC796A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C80404D3455D496C02F29 /* Localizable.strings */; }; BF0C81D836A8A4CA393B135F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C82E55127D38ABF969CA0 /* InfoPlist.strings */; }; - BF0C81FA62D810342F59291A /* AppearanceUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C84933936C6FE676CD33C /* AppearanceUtilsTests.swift */; }; + BF0C81FA62D810342F59291A /* AppearanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C84933936C6FE676CD33C /* AppearanceTests.swift */; }; BF0C8203D0EFEDEED0F908E8 /* ScreensEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C822D3BEDAB1F1C9F775E /* ScreensEvents.swift */; }; BF0C820C6E89C2D90FA30A4B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8F67ABD271F26FA0EDEF /* Localizable.strings */; }; BF0C8224CFD1ED511F3F0063 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8D94228D90FE99D4A3C8 /* Localizable.strings */; }; @@ -116,7 +116,7 @@ BF0C865E3C3F3B353A7A7CBC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C86FFF1C801761B7CE363 /* Localizable.strings */; }; BF0C86BB79E417163E813179 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C80851C26EDB3DB98556C /* InfoPlist.strings */; }; BF0C87732A6E660899D13BFD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C886B1D6E707E9E6BE46C /* InfoPlist.strings */; }; - BF0C8846AFEE2FFF93FAB36F /* AppearanceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8D57C86AF492D43A576D /* AppearanceUtils.swift */; }; + BF0C8846AFEE2FFF93FAB36F /* AppearanceTestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8D57C86AF492D43A576D /* AppearanceTestable.swift */; }; BF0C884D88B74F05B8BBF956 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C88A25E1ED37E5F80AC91 /* Localizable.strings */; }; BF0C8879F979414A87AB2958 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C89348E07FC05870FEE62 /* InfoPlist.strings */; }; BF0C88C371B5550E7D23E4F1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8BF5835F97923BF3E8DA /* InfoPlist.strings */; }; @@ -331,6 +331,7 @@ 5F706EC22BA448D8004D542D /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = is; path = InfoPlist.strings; sourceTree = ""; }; 672F94799CC7C90282AEB3EA /* Pods-alt-tab-macos.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-alt-tab-macos.debug.xcconfig"; path = "Target Support Files/Pods-alt-tab-macos/Pods-alt-tab-macos.debug.xcconfig"; sourceTree = ""; }; AA974BA829B7D84C0099A29E /* PreviewPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewPanel.swift; sourceTree = ""; }; + BF0C8070A1BBA8B425C8EF34 /* IOKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IOKit.swift; sourceTree = ""; }; BF0C809C9DE36EA1317816FB /* nn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nn; path = Localizable.strings; sourceTree = ""; }; BF0C80A05C11C8ADC366EC4A /* lb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lb; path = InfoPlist.strings; sourceTree = ""; }; BF0C8113492C0FFCB4454BC9 /* sl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sl; path = Localizable.strings; sourceTree = ""; }; @@ -362,7 +363,7 @@ BF0C84674BAE2B4D7871B86C /* et */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = et; path = Localizable.strings; sourceTree = ""; }; BF0C847A108ABC72723ABBA2 /* hi */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hi; path = InfoPlist.strings; sourceTree = ""; }; BF0C848A4DDFF5D118CA9CAF /* ca */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ca; path = Localizable.strings; sourceTree = ""; }; - BF0C84933936C6FE676CD33C /* AppearanceUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceUtilsTests.swift; sourceTree = ""; }; + BF0C84933936C6FE676CD33C /* AppearanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceTests.swift; sourceTree = ""; }; BF0C84B9B7A41E4A4534E519 /* app-icons-style-full.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "app-icons-style-full.jpg"; sourceTree = ""; }; BF0C84E11784B3B0900D0963 /* app.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = app.icns; sourceTree = ""; }; BF0C850C46CCD8B978168CCD /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = Localizable.strings; sourceTree = ""; }; @@ -423,7 +424,7 @@ BF0C8CE7BBC3B9B0CEC008FF /* vi */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = vi; path = Localizable.strings; sourceTree = ""; }; BF0C8D3333653ACAEF730DD8 /* menubar-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-2@2x.png"; sourceTree = ""; }; BF0C8D52C3D33B0669ECF996 /* ro */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ro; path = InfoPlist.strings; sourceTree = ""; }; - BF0C8D57C86AF492D43A576D /* AppearanceUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceUtils.swift; sourceTree = ""; }; + BF0C8D57C86AF492D43A576D /* AppearanceTestable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceTestable.swift; sourceTree = ""; }; BF0C8D606B53D315F2D9927B /* KeyRepeatTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyRepeatTimer.swift; sourceTree = ""; }; BF0C8D7DE89E594FEE6DA692 /* ta */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ta; path = Localizable.strings; sourceTree = ""; }; BF0C8D87C20B7308B16498B6 /* et */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = et; path = InfoPlist.strings; sourceTree = ""; }; @@ -436,6 +437,7 @@ BF0C8E54658B4DAEA0528200 /* menubar-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-3@2x.png"; sourceTree = ""; }; BF0C8E5D2BD5F7F93DF77DF5 /* sk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sk; path = InfoPlist.strings; sourceTree = ""; }; BF0C8E7B087DCC97A454C0AE /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = InfoPlist.strings; sourceTree = ""; }; + BF0C8EAF02309B9C489E65F4 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; BF0C8EC400A560FDF99F8C35 /* el */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = el; path = Localizable.strings; sourceTree = ""; }; BF0C8EE17F3ABD7A6D44BFFE /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = Localizable.strings; sourceTree = ""; }; BF0C8FC083A2443F3EAF5DB7 /* preferences-blacklist.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "preferences-blacklist.jpg"; sourceTree = ""; }; @@ -854,6 +856,15 @@ path = sl.lproj; sourceTree = ""; }; + BF0C827B752F58F79EDB6B6C /* experimentations */ = { + isa = PBXGroup; + children = ( + BF0C8070A1BBA8B425C8EF34 /* IOKit.swift */, + BF0C8EAF02309B9C489E65F4 /* README.md */, + ); + path = experimentations; + sourceTree = ""; + }; BF0C82D4A643500E81DB2965 /* ga.lproj */ = { isa = PBXGroup; children = ( @@ -902,7 +913,7 @@ BF0C8719351B41D268CE8B70 /* unit-tests */ = { isa = PBXGroup; children = ( - BF0C84933936C6FE676CD33C /* AppearanceUtilsTests.swift */, + BF0C84933936C6FE676CD33C /* AppearanceTests.swift */, ); path = "unit-tests"; sourceTree = ""; @@ -1112,7 +1123,7 @@ BF0C8BA452332236D972C60E /* ATShortcut.swift */, 1C961AC3633CD0C62CCE050E /* Appearance.swift */, BF0C8D606B53D315F2D9927B /* KeyRepeatTimer.swift */, - BF0C8D57C86AF492D43A576D /* AppearanceUtils.swift */, + BF0C8D57C86AF492D43A576D /* AppearanceTestable.swift */, ); path = logic; sourceTree = ""; @@ -1376,6 +1387,7 @@ D04BA70746DEEC3D30B43F81 /* main.swift */, D04BA0D80B24E72B9B981A1D /* logic */, D04BA229883E5D540D00232D /* ui */, + BF0C827B752F58F79EDB6B6C /* experimentations */, ); path = src; sourceTree = ""; @@ -2124,8 +2136,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - BF0C81FA62D810342F59291A /* AppearanceUtilsTests.swift in Sources */, - 5F4753072CEE5A2E002C6C5E /* AppearanceUtils.swift in Sources */, + BF0C81FA62D810342F59291A /* AppearanceTests.swift in Sources */, + 5F4753072CEE5A2E002C6C5E /* AppearanceTestable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2210,7 +2222,7 @@ BF0C896B5D08B7DA8F2024AC /* KeyRepeatTimer.swift in Sources */, BF0C8249228867A3188E13F3 /* SystemAppearanceEvents.swift in Sources */, BF0C827A169F0DE86C8F12AD /* SystemScrollerStyleEvents.swift in Sources */, - BF0C8846AFEE2FFF93FAB36F /* AppearanceUtils.swift in Sources */, + BF0C8846AFEE2FFF93FAB36F /* AppearanceTestable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/logic/Appearance.swift b/src/logic/Appearance.swift index 1f0a5e12..53c37b48 100644 --- a/src/logic/Appearance.swift +++ b/src/logic/Appearance.swift @@ -56,13 +56,13 @@ class Appearance { private static func updateSize() { let screen = NSScreen.preferred() let isHorizontalScreen = screen.isHorizontal() - maxWidthOnScreen = AppearanceUtils.comfortableWidth(screen.physicalSize()!.width) + maxWidthOnScreen = AppearanceTestable.comfortableWidth(screen.physicalSize().map { $0.width }) if currentStyle == .appIcons { appIconsSize() } else if currentStyle == .titles { titlesSize(isHorizontalScreen) } else { - thumbnailsSize(isHorizontalScreen) + thumbnailsSize(isHorizontalScreen, screen) } } @@ -74,7 +74,7 @@ class Appearance { } } - private static func thumbnailsSize(_ isHorizontalScreen: Bool) { + private static func thumbnailsSize(_ isHorizontalScreen: Bool, _ screen: NSScreen) { hideThumbnails = false windowPadding = 18 cellCornerRadius = 10 @@ -96,7 +96,8 @@ class Appearance { iconSize = 32 fontHeight = 16 } - (windowMinWidthInRow, windowMaxWidthInRow) = goodValuesForThumbnailsWidthMinMax() + let thumbnailsPanelRatio = (screen.frame.width * maxWidthOnScreen) / (screen.frame.height * maxHeightOnScreen) + (windowMinWidthInRow, windowMaxWidthInRow) = AppearanceTestable.goodValuesForThumbnailsWidthMinMax(thumbnailsPanelRatio, rowsCount) if currentVisibility == .highest { edgeInsetsSize = 10 cellCornerRadius = 12 @@ -241,22 +242,4 @@ class Appearance { highlightBorderWidth = currentStyle == .titles ? 2 : 4 } } - - // calculate windowMinWidthInRow and windowMaxWidthInRow such that: - // * fullscreen windows fill their tile vertically - // * narrow windows have enough width that a few words can be read from their title - private static func goodValuesForThumbnailsWidthMinMax() -> (CGFloat, CGFloat) { - let aspectRatio = NSScreen.preferred().ratio() - let minRatio: CGFloat - let maxRatio: CGFloat - if aspectRatio >= 1 { - minRatio = 0.7 / (aspectRatio * rowsCount) - maxRatio = 1.5 / (aspectRatio * rowsCount) - } else { - minRatio = 1.3 / rowsCount - maxRatio = 2.1 / rowsCount - } - // Make sure the values are clamped between some reasonable bounds - return (max(0.09, minRatio), min(0.3, maxRatio)) - } } diff --git a/src/logic/AppearanceTestable.swift b/src/logic/AppearanceTestable.swift new file mode 100644 index 00000000..6cd422f4 --- /dev/null +++ b/src/logic/AppearanceTestable.swift @@ -0,0 +1,35 @@ +import Foundation + +class AppearanceTestable { + /// How wide should the ThumbnailsPanel be, for comfortable viewing? + /// * a comfortable field-of-view is 50-60 degrees + /// * people sit at various distances from the screen. We can't know how far they sit + /// * most people will seat far enough so that they can view the whole width of the screen + /// * some people use wide-screen or TV monitors. Those people tend to be too close to the screen, since they need to use keyboard and mouse on their desk + /// Let's use this heuristic: let's assume that people can view 60cm comfortably. Bigger screens can only show parts of AltTab + /// Let's clamp at 90% like Windows 11 + /// Let's clamp at 45% (value for the biggest, 60" screens) + static func comfortableWidth(_ physicalWidth: Double?) -> Double { + if let physicalWidth = physicalWidth { + return min(0.9, max(0.45, 600.0 / physicalWidth)) + } + return 0.9 + } + + // calculate windowMinWidthInRow and windowMaxWidthInRow such that: + // * fullscreen windows fill their tile vertically + // * narrow windows have enough width that a few words can be read from their title + static func goodValuesForThumbnailsWidthMinMax(_ aspectRatio: CGFloat, _ rowsCount: CGFloat) -> (CGFloat, CGFloat) { + let minRatio: CGFloat + let maxRatio: CGFloat + if aspectRatio >= 1 { + minRatio = 0.7 / (aspectRatio * rowsCount) + maxRatio = 1.5 / (aspectRatio * rowsCount) + } else { + minRatio = 1.3 / rowsCount + maxRatio = 2.1 / rowsCount + } + // Make sure the values are clamped between some reasonable bounds + return (max(0.09, minRatio), min(0.30, maxRatio)) + } +} diff --git a/src/logic/AppearanceUtils.swift b/src/logic/AppearanceUtils.swift deleted file mode 100644 index f2e7338c..00000000 --- a/src/logic/AppearanceUtils.swift +++ /dev/null @@ -1,12 +0,0 @@ -class AppearanceUtils { - /// How wide should the ThumbnailsPanel be, for comfortable viewing? - /// * a comfortable field-of-view is 50-60 degrees - /// * people sit at various distances from the screen. We can't know how far they sit - /// * most people will seat far enough so that they can view the whole width of the screen - /// * some people use wide-screen or TV monitors. Those people tend to be too close to the screen, since they need to use keyboard and mouse on their desk - /// Let's use this heuristic: let's assume that people can view 60cm comfortably. Bigger screens can only show parts of AltTab - /// Let's clamp at 90% like Windows 11 - static func comfortableWidth(_ physicalWidth: Double) -> Double { - return min(0.9, 600.0 / physicalWidth) - } -} diff --git a/src/logic/NSScreen.swift b/src/logic/NSScreen.swift index 7df0867c..c519c1c0 100644 --- a/src/logic/NSScreen.swift +++ b/src/logic/NSScreen.swift @@ -29,7 +29,16 @@ extension NSScreen { } func physicalSize() -> CGSize? { - return number().map { CGDisplayScreenSize($0) } + if let number = number() { + let size = CGDisplayScreenSize(number) + // CGDisplayScreenSize docs says it can return "zero" + if size.width > 0 && size.height > 0 && + // CGDisplayScreenSize may return wrong values; we compare physical and logical ratios to reject + abs(ratio() - (size.width / size.height)) < 0.2 { + return size + } + } + return nil } static func preferred() -> NSScreen { @@ -44,6 +53,8 @@ extension NSScreen { } } + + // NSScreen.main docs are incorrect. It stopped returning the screen with the key window in macOS 10.9 // see https://stackoverflow.com/a/56268826/2249756 diff --git a/unit-tests/AppearanceTests.swift b/unit-tests/AppearanceTests.swift new file mode 100644 index 00000000..7073edf1 --- /dev/null +++ b/unit-tests/AppearanceTests.swift @@ -0,0 +1,54 @@ +import XCTest + +final class AppearanceTests: XCTestCase { + let screens: [(String, (CGFloat, CGFloat), (CGFloat, CGFloat), (CGFloat, CGFloat), [(Int, CGFloat, CGFloat)])] = [ + // screen model, (widthInPixels, heightInPixels), (physicalWidthInMM, physicalHeightInMM), (expectedWidthForHorizontal, expectedWidthForVertical), (rowCount, expectedMinWidth, expectedMaxWidth) + ("11\" Laptop: MacBook Air 11\": HD", (1366, 768), (255.7, 178.6), (0.90, 0.90), [(3, 0.12, 0.25), (4, 0.09, 0.19), (5, 0.09, 0.15)]), + ("13\" Laptop: MacBook Air 13\": WXGA+", (1440, 900), (304.1, 197.8), (0.90, 0.90), [(3, 0.13, 0.28), (4, 0.10, 0.21), (5, 0.09, 0.17)]), + ("14\" Laptop: MacBook Pro 14\": 3K", (3024, 1964), (311.0, 221.1), (0.90, 0.90), [(3, 0.13, 0.29), (4, 0.10, 0.22), (5, 0.09, 0.17)]), + ("15\" Laptop: MacBook Pro 15\": QXGA", (2880, 1800), (344.4, 233.0), (0.90, 0.90), [(3, 0.13, 0.28), (4, 0.10, 0.21), (5, 0.09, 0.17)]), + ("16\" Laptop: MacBook Pro 16\": 3.5K", (3456, 2234), (358.4, 245.9), (0.90, 0.90), [(3, 0.13, 0.29), (4, 0.10, 0.22), (5, 0.09, 0.17)]), + ("19\" Monitor: Apple Studio Display 19\": HD", (1440, 900), (403.0, 236.0), (0.90, 0.90), [(3, 0.13, 0.28), (4, 0.10, 0.21), (5, 0.09, 0.17)]), + ("20\" Monitor: Apple Cinema Display 20\": WSXGA+", (1680, 1050), (440.0, 268.0), (0.90, 0.90), [(3, 0.13, 0.28), (4, 0.10, 0.21), (5, 0.09, 0.17)]), + ("21\" Monitor: LG 21:9 UltraWide: UWHD", (2560, 1080), (470.0, 290.0), (0.90, 0.90), [(3, 0.09, 0.19), (4, 0.09, 0.14), (5, 0.09, 0.11)]), + ("22\" Monitor: ASUS 22\" Full HD: Full HD", (1920, 1080), (485.0, 290.0), (0.90, 0.90), [(3, 0.12, 0.25), (4, 0.09, 0.19), (5, 0.09, 0.15)]), + ("24\" Monitor: Dell P2419H: Full HD", (1920, 1080), (531.3, 298.6), (0.90, 0.90), [(3, 0.12, 0.25), (4, 0.09, 0.19), (5, 0.09, 0.15)]), + ("27\" Monitor: LG 27UK850-W: 4K", (3840, 2160), (596.8, 336.4), (0.90, 0.90), [(3, 0.12, 0.25), (4, 0.09, 0.19), (5, 0.09, 0.15)]), + ("30\" Monitor: BenQ PD3200U: 4K", (3840, 2160), (657.5, 376.3), (0.90, 0.90), [(3, 0.12, 0.25), (4, 0.09, 0.19), (5, 0.09, 0.15)]), + ("32\" Monitor: BenQ EW3270U: 4K", (3840, 2160), (711.5, 398.9), (0.84, 0.90), [(3, 0.12, 0.27), (4, 0.09, 0.20), (5, 0.09, 0.16)]), + ("34\" UltraWide Monitor: LG 34UC79G-B: UWHD", (2560, 1080), (798.5, 336.5), (0.75, 0.90), [(3, 0.10, 0.22), (4, 0.09, 0.17), (5, 0.09, 0.14)]), + ("34\" UltraWide Monitor: LG 34WN80C-B: UWQHD", (3440, 1440), (799.8, 334.8), (0.75, 0.90), [(3, 0.10, 0.22), (4, 0.09, 0.17), (5, 0.09, 0.13)]), + ("32\" TV: Samsung UE32T5300: Full HD", (1920, 1080), (715.0, 406.0), (0.83, 0.90), [(3, 0.13, 0.27), (4, 0.09, 0.20), (5, 0.09, 0.16)]), + ("40\" TV: Samsung Q60B: 4K", (3840, 2160), (889.0, 510.0), (0.67, 0.90), [(3, 0.16, 0.30), (4, 0.12, 0.25), (5, 0.09, 0.20)]), + ("43\" TV: LG 43UN7300: 4K", (3840, 2160), (956.0, 551.0), (0.62, 0.90), [(3, 0.17, 0.30), (4, 0.13, 0.27), (5, 0.10, 0.22)]), + ("50\" TV: Samsung TU8000: 4K", (3840, 2160), (1110.0, 630.0), (0.54, 0.90), [(3, 0.19, 0.30), (4, 0.15, 0.30), (5, 0.12, 0.25)]), + ("55\" TV: LG OLED55CXPUA: 4K", (3840, 2160), (1210.0, 715.0), (0.49, 0.83), [(3, 0.21, 0.30), (4, 0.16, 0.30), (5, 0.13, 0.28)]), + ("60\" TV: Vizio 60-inch 4K: 4K", (3840, 2160), (1320.0, 750.0), (0.45, 0.80), [(3, 0.23, 0.30), (4, 0.17, 0.30), (5, 0.14, 0.30)]), + ] + + // TODO add 6, 7, 8 rowsCount and reuse vertical screens data from bellow + func testGoodValuesForThumbnailsWidthMinMax() throws { + var actual: (CGFloat, CGFloat) + for (model, (pixelWidth, pixelHeight), _, (expectedHorizontal, _), expectedArray) in screens { + for (rowCount, expectedMin, expectedMax) in expectedArray { + actual = AppearanceTestable.goodValuesForThumbnailsWidthMinMax((pixelWidth * expectedHorizontal) / (pixelHeight * 0.8), CGFloat(rowCount)) + XCTAssertEqual(actual.0, expectedMin, accuracy: 0.01, model) + XCTAssertEqual(actual.1, expectedMax, accuracy: 0.01, model) + } + } + } + + + func testComfortableWidth() throws { + var actual: Double + for (model, _, (physicalWidth, physicalHeight), (expectedHorizontal, expectedVertical), _) in screens { + // screen used horizontally + actual = AppearanceTestable.comfortableWidth(physicalWidth) + XCTAssertEqual(actual, expectedHorizontal, accuracy: 0.01, model) + // screen used vertically + actual = AppearanceTestable.comfortableWidth(physicalHeight) + XCTAssertEqual(actual, expectedVertical, accuracy: 0.01, model) + } + + } +} diff --git a/unit-tests/AppearanceUtilsTests.swift b/unit-tests/AppearanceUtilsTests.swift deleted file mode 100644 index 73c6e893..00000000 --- a/unit-tests/AppearanceUtilsTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -import XCTest - -final class AppearanceUtilsTests: XCTestCase { - func testComfortableWidth() throws { - [ - // screen model, width, height, expected width % - ("11\" Laptop: MacBook Air 11\"", 255.7, 178.6, 0.9), - ("13\" Laptop: MacBook Air 13\"", 304.1, 197.8, 0.9), - ("14\" Laptop: MacBook Pro 14\"", 311.0, 221.1, 0.9), - ("15\" Laptop: MacBook Pro 15\"", 344.4, 233.0, 0.9), - ("16\" Laptop: MacBook Pro 16\"", 358.4, 245.9, 0.9), - ("19\" Monitor: Apple Studio Display 19\"", 403, 236, 0.9), - ("20\" Monitor: Apple Cinema Display 20\"", 440, 268, 0.9), - ("21\" Monitor: LG 21:9 UltraWide", 470, 290, 0.9), - ("22\" Monitor: ASUS 22\" Full HD", 485, 290, 0.9), - ("24\" Monitor: Dell P2419H", 531.3, 298.6, 0.9), - ("27\" Monitor: LG 27UK850-W", 596.8, 336.4, 0.9), - ("30\" Monitor: BenQ PD3200U", 657.5, 376.3, 0.9), - ("32\" Monitor: BenQ EW3270U", 711.5, 398.9, 0.8432888264230499), - ("34\" UltraWide Monitor: LG 34UC79G-B", 798.5, 336.5, 0.7514088916718847), - ("32\" TV: Samsung UE32T5300", 715, 406, 0.8391608391608392), - ("40\" TV: Samsung Q60B", 889, 510, 0.6749156355455568), - ("43\" TV: LG 43UN7300", 956, 551, 0.6276150627615062), - ("50\" TV: Samsung TU8000", 1110, 630, 0.5405405405405406), - ("55\" TV: LG OLED55CXPUA", 1210, 715, 0.49586776859504134), - ("60\" TV: Vizio 60-inch 4K", 1320, 750, 0.45454545454545453), - ].forEach { screen in - let percent = AppearanceUtils.comfortableWidth(screen.1) - XCTAssertEqual(percent, screen.3) - } - } -}