From eed03531af0a03be164422fab5ecc96bbc7ebf28 Mon Sep 17 00:00:00 2001 From: "louis.pontoise" <> Date: Mon, 10 Feb 2020 13:56:22 +0900 Subject: [PATCH] feat: drag-and-drop files on the ui (closes #74) --- alt-tab-macos.xcodeproj/project.pbxproj | 4 + .../CollectionViewFlowLayout.swift | 2 +- .../ui/main-window/CollectionViewItem.swift | 130 +------------- .../main-window/CollectionViewItemTitle.swift | 2 +- .../main-window/CollectionViewItemView.swift | 170 ++++++++++++++++++ .../ui/main-window/ThumbnailsPanel.swift | 12 +- 6 files changed, 187 insertions(+), 133 deletions(-) create mode 100644 alt-tab-macos/ui/main-window/CollectionViewItemView.swift diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index e64a22e2..e9f86cc2 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ D04BAD5A6B2F9EEE6FD4185F /* CollectionViewItemTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA4BABBA0312E0EDBA647 /* CollectionViewItemTitle.swift */; }; D04BAD8346A6A32C9749E0B3 /* TabViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA293C53EC5CE00D11E02 /* TabViewItem.swift */; }; D04BAE369A14C3126A1606FE /* HelperExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8F1AA48A323EE5638DC /* HelperExtensions.swift */; }; + D04BAE4CE37C303DDD0347B8 /* CollectionViewItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE073DD0B0D65CD4CBB6 /* CollectionViewItemView.swift */; }; D04BAEAB8AB048FF2B16B131 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BAE5FA03065C5D23C0C2C /* Localizable.strings */; }; D04BAEF78503D7A2CEFB9E9E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA44C837F3A67403B9DB /* main.swift */; }; D04BAF3B6F75E50E9AA3E1D2 /* LabelAndControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA3D65E7CA78D699EDAB0 /* LabelAndControl.swift */; }; @@ -133,6 +134,7 @@ D04BADBAFB42AE72DBE1E59E /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = InfoPlist.strings; sourceTree = ""; }; D04BADBCA16C1D448D34F473 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = InfoPlist.strings; sourceTree = ""; }; D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + D04BAE073DD0B0D65CD4CBB6 /* CollectionViewItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewItemView.swift; sourceTree = ""; }; D04BAE1243C9B4BE3ED1B524 /* 7 windows - 2 lines - extra wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - extra wide window.jpg"; sourceTree = ""; }; D04BAE23C37E0F3B07EEE7B1 /* AboutTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutTab.swift; sourceTree = ""; }; D04BAE80772D25834E440975 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; @@ -442,6 +444,7 @@ D04BAF40D5E54AD1044B3FF7 /* ThumbnailsPanel.swift */, D04BA4BABBA0312E0EDBA647 /* CollectionViewItemTitle.swift */, D04BAC0416F29ADE7BC5A544 /* CollectionViewItemFontIcon.swift */, + D04BAE073DD0B0D65CD4CBB6 /* CollectionViewItemView.swift */, ); path = "main-window"; sourceTree = ""; @@ -558,6 +561,7 @@ D04BA6D9DA2A8BCD93347F0E /* CollectionViewItemFontIcon.swift in Sources */, D04BA9EE5D34A2789DCB0EE2 /* Sysctl.swift in Sources */, D04BAC4F69FE9563BC1C5E9C /* DebugProfile.swift in Sources */, + D04BAE4CE37C303DDD0347B8 /* CollectionViewItemView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/alt-tab-macos/ui/main-window/CollectionViewFlowLayout.swift b/alt-tab-macos/ui/main-window/CollectionViewFlowLayout.swift index d78e6147..92868e43 100644 --- a/alt-tab-macos/ui/main-window/CollectionViewFlowLayout.swift +++ b/alt-tab-macos/ui/main-window/CollectionViewFlowLayout.swift @@ -17,7 +17,7 @@ class CollectionViewFlowLayout: NSCollectionViewFlowLayout { var widestRow = CGFloat(0) var totalHeight = CGFloat(0) for (index, attribute) in attributes.enumerated() { - let isNewRow = abs(attribute.frame.origin.y - currentRowY) > CollectionViewItem.height(currentScreen!) + let isNewRow = abs(attribute.frame.origin.y - currentRowY) > CollectionViewItemView.height(currentScreen!) if isNewRow { currentRowWidth -= Preferences.interCellPadding widestRow = max(widestRow, currentRowWidth) diff --git a/alt-tab-macos/ui/main-window/CollectionViewItem.swift b/alt-tab-macos/ui/main-window/CollectionViewItem.swift index d9ee5108..67f59671 100644 --- a/alt-tab-macos/ui/main-window/CollectionViewItem.swift +++ b/alt-tab-macos/ui/main-window/CollectionViewItem.swift @@ -1,35 +1,12 @@ import Cocoa import WebKit -typealias MouseDownCallback = (Window) -> Void -typealias MouseMovedCallback = (CollectionViewItem) -> Void - class CollectionViewItem: NSCollectionViewItem { - var thumbnail = NSImageView() - var appIcon = NSImageView() - var label = CellTitle(Preferences.fontHeight) - var minimizedIcon = FontIcon(FontIcon.sfSymbolCircledMinusSign, Preferences.fontIconSize, .white) - var hiddenIcon = FontIcon(FontIcon.sfSymbolCircledDotSign, Preferences.fontIconSize, .white) - var spaceIcon = FontIcon(FontIcon.sfSymbolCircledNumber0, Preferences.fontIconSize, .white) - var window: Window? - var mouseDownCallback: MouseDownCallback? - var mouseMovedCallback: MouseMovedCallback? + var view_: CollectionViewItemView { view as! CollectionViewItemView } override func loadView() { - let hStackView = makeHStackView() - let vStackView = makeVStackView(hStackView) - let shadow = CollectionViewItem.makeShadow(.gray) - thumbnail.shadow = shadow - appIcon.shadow = shadow - view = vStackView - } - - override func mouseMoved(with event: NSEvent) { - mouseMovedCallback!(self) - } - - override func mouseDown(with theEvent: NSEvent) { - mouseDownCallback!(window!) + view = CollectionViewItemView() + view.wantsLayer = true } override var isSelected: Bool { @@ -38,105 +15,4 @@ class CollectionViewItem: NSCollectionViewItem { view.layer!.borderColor = isSelected ? Preferences.highlightBorderColor.cgColor : .clear } } - - func updateRecycledCellWithNewContent(_ element: Window, _ mouseDownCallback: @escaping MouseDownCallback, _ mouseMovedCallback: @escaping MouseMovedCallback, _ screen: NSScreen) { - window = element - thumbnail.image = element.thumbnail - let (thumbnailWidth, thumbnailHeight) = CollectionViewItem.thumbnailSize(element.thumbnail, screen) - let thumbnailSize = NSSize(width: thumbnailWidth.rounded(), height: thumbnailHeight.rounded()) - thumbnail.image?.size = thumbnailSize - thumbnail.frame.size = thumbnailSize - appIcon.image = element.icon - let appIconSize = NSSize(width: Preferences.iconSize, height: Preferences.iconSize) - appIcon.image?.size = appIconSize - appIcon.frame.size = appIconSize - label.string = element.title - // workaround: setting string on NSTextView change the font (most likely a Cocoa bug) - label.font = Preferences.font - hiddenIcon.isHidden = !window!.isHidden - minimizedIcon.isHidden = !window!.isMinimized - spaceIcon.isHidden = element.spaceIndex == nil || Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels - if !spaceIcon.isHidden { - if element.isOnAllSpaces { - spaceIcon.setStar() - } else { - spaceIcon.setNumber(UInt32(element.spaceIndex!)) - } - } - let fontIconWidth = CGFloat([minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.intraCellPadding) - label.textContainer!.size.width = view.frame.width - Preferences.iconSize - Preferences.intraCellPadding * 3 - fontIconWidth - view.subviews.first!.frame.size = view.frame.size - self.mouseDownCallback = mouseDownCallback - self.mouseMovedCallback = mouseMovedCallback - if view.trackingAreas.count > 0 { - view.removeTrackingArea(view.trackingAreas[0]) - } - view.addTrackingArea(NSTrackingArea(rect: view.bounds, options: [.mouseMoved, .activeAlways], owner: self, userInfo: nil)) - } - - static func makeShadow(_ color: NSColor) -> NSShadow { - let shadow = NSShadow() - shadow.shadowColor = color - shadow.shadowOffset = .zero - shadow.shadowBlurRadius = 1 - return shadow - } - - private func makeHStackView() -> NSStackView { - let hStackView = NSStackView() - hStackView.spacing = Preferences.intraCellPadding - hStackView.setViews([appIcon, label, hiddenIcon, minimizedIcon, spaceIcon], in: .leading) - return hStackView - } - - private func makeVStackView(_ hStackView: NSStackView) -> NSStackView { - let vStackView = NSStackView() - vStackView.wantsLayer = true - vStackView.layer!.backgroundColor = .clear - vStackView.layer!.cornerRadius = Preferences.cellCornerRadius - vStackView.layer!.borderWidth = Preferences.cellBorderWidth - vStackView.layer!.borderColor = .clear - vStackView.edgeInsets = NSEdgeInsets(top: Preferences.intraCellPadding, left: Preferences.intraCellPadding, bottom: Preferences.intraCellPadding, right: Preferences.intraCellPadding) - vStackView.orientation = .vertical - vStackView.spacing = Preferences.intraCellPadding - vStackView.setViews([hStackView, thumbnail], in: .leading) - return vStackView - } - - static func downscaleFactor() -> CGFloat { - let nCellsBeforePotentialOverflow = Preferences.minRows * Preferences.minCellsPerRow - guard CGFloat(Windows.list.count) > nCellsBeforePotentialOverflow else { return 1 } - // TODO: replace this buggy heuristic with a correct implementation of downscaling - return nCellsBeforePotentialOverflow / (nCellsBeforePotentialOverflow + (sqrt(CGFloat(Windows.list.count) - nCellsBeforePotentialOverflow) * 2)) - } - - static func widthMax(_ screen: NSScreen) -> CGFloat { - return (ThumbnailsPanel.widthMax(screen) / Preferences.minCellsPerRow - Preferences.interCellPadding) * CollectionViewItem.downscaleFactor() - } - - static func widthMin(_ screen: NSScreen) -> CGFloat { - return (ThumbnailsPanel.widthMax(screen) / Preferences.maxCellsPerRow - Preferences.interCellPadding) * CollectionViewItem.downscaleFactor() - } - - static func height(_ screen: NSScreen) -> CGFloat { - return (ThumbnailsPanel.heightMax(screen) / Preferences.minRows - Preferences.interCellPadding) * CollectionViewItem.downscaleFactor() - } - - static func width(_ image: NSImage?, _ screen: NSScreen) -> CGFloat { - return max(thumbnailSize(image, screen).0 + Preferences.intraCellPadding * 2, CollectionViewItem.widthMin(screen)) - } - - static func thumbnailSize(_ image: NSImage?, _ screen: NSScreen) -> (CGFloat, CGFloat) { - guard let image = image else { return (0, 0) } - let thumbnailHeightMax = CollectionViewItem.height(screen) - Preferences.intraCellPadding * 3 - Preferences.iconSize - let thumbnailWidthMax = CollectionViewItem.widthMax(screen) - Preferences.intraCellPadding * 2 - let thumbnailHeight = min(image.size.height, thumbnailHeightMax) - let thumbnailWidth = min(image.size.width, thumbnailWidthMax) - let imageRatio = image.size.width / image.size.height - let thumbnailRatio = thumbnailWidth / thumbnailHeight - if thumbnailRatio > imageRatio { - return (image.size.width * thumbnailHeight / image.size.height, thumbnailHeight) - } - return (thumbnailWidth, image.size.height * thumbnailWidth / image.size.width) - } } diff --git a/alt-tab-macos/ui/main-window/CollectionViewItemTitle.swift b/alt-tab-macos/ui/main-window/CollectionViewItemTitle.swift index 18337082..63c63159 100644 --- a/alt-tab-macos/ui/main-window/CollectionViewItemTitle.swift +++ b/alt-tab-macos/ui/main-window/CollectionViewItemTitle.swift @@ -14,7 +14,7 @@ class CellTitle: BaseLabel { self.init(NSRect.zero, textContainer) self.magicOffset = magicOffset textColor = Preferences.fontColor - shadow = CollectionViewItem.makeShadow(.darkGray) + shadow = CollectionViewItemView.makeShadow(.darkGray) defaultParagraphStyle = makeParagraphStyle(size) heightAnchor.constraint(equalToConstant: size + magicOffset).isActive = true } diff --git a/alt-tab-macos/ui/main-window/CollectionViewItemView.swift b/alt-tab-macos/ui/main-window/CollectionViewItemView.swift new file mode 100644 index 00000000..ca35842c --- /dev/null +++ b/alt-tab-macos/ui/main-window/CollectionViewItemView.swift @@ -0,0 +1,170 @@ +import Cocoa + +class CollectionViewItemView: NSView { + var window_: Window? + var thumbnail = NSImageView() + var appIcon = NSImageView() + var label = CellTitle(Preferences.fontHeight) + var minimizedIcon = FontIcon(FontIcon.sfSymbolCircledMinusSign, Preferences.fontIconSize, .white) + var hiddenIcon = FontIcon(FontIcon.sfSymbolCircledDotSign, Preferences.fontIconSize, .white) + var spaceIcon = FontIcon(FontIcon.sfSymbolCircledNumber0, Preferences.fontIconSize, .white) + var mouseDownCallback: MouseDownCallback! + var mouseMovedCallback: MouseMovedCallback! + var dragAndDropTimer: Timer? + + convenience init() { + self.init(frame: .zero) + let hStackView = makeHStackView() + let vStackView = makeVStackView(hStackView) + let shadow = CollectionViewItemView.makeShadow(.gray) + thumbnail.shadow = shadow + appIcon.shadow = shadow + observeDragAndDrop() + subviews.append(vStackView) + } + + private func observeDragAndDrop() { + // NSImageView instances are registered to drag-and-drop by default + thumbnail.unregisterDraggedTypes() + appIcon.unregisterDraggedTypes() + // we only handle URLs (i.e. not text, image, or other draggable things) + registerForDraggedTypes([NSPasteboard.PasteboardType(kUTTypeURL as String)]) + } + + override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation { + mouseMovedCallback() + return .link + } + + override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { + dragAndDropTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false, block: { _ in + self.mouseDownCallback() + }) + return .link + } + + override func draggingExited(_ sender: NSDraggingInfo?) { + dragAndDropTimer?.invalidate() + dragAndDropTimer = nil + } + + override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { + let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self]) as! [URL] + let appUrl = window_!.application.runningApplication.bundleURL! + let open = try? NSWorkspace.shared.open(urls, withApplicationAt: appUrl, options: [], configuration: [:]) + (App.shared as! App).hideUi() + return open != nil + } + + override func mouseMoved(with event: NSEvent) { + mouseMovedCallback() + } + + override func mouseDown(with theEvent: NSEvent) { + mouseDownCallback() + } + + func updateRecycledCellWithNewContent(_ element: Window, _ mouseDownCallback: @escaping MouseDownCallback, _ mouseMovedCallback: @escaping MouseMovedCallback, _ screen: NSScreen) { + window_ = element + thumbnail.image = element.thumbnail + let (thumbnailWidth, thumbnailHeight) = CollectionViewItemView.thumbnailSize(element.thumbnail, screen) + let thumbnailSize = NSSize(width: thumbnailWidth.rounded(), height: thumbnailHeight.rounded()) + thumbnail.image?.size = thumbnailSize + thumbnail.frame.size = thumbnailSize + appIcon.image = element.icon + let appIconSize = NSSize(width: Preferences.iconSize, height: Preferences.iconSize) + appIcon.image?.size = appIconSize + appIcon.frame.size = appIconSize + label.string = element.title + // workaround: setting string on NSTextView change the font (most likely a Cocoa bug) + label.font = Preferences.font + hiddenIcon.isHidden = !window_!.isHidden + minimizedIcon.isHidden = !window_!.isMinimized + spaceIcon.isHidden = element.spaceIndex == nil || Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels + if !spaceIcon.isHidden { + if element.isOnAllSpaces { + spaceIcon.setStar() + } else { + spaceIcon.setNumber(UInt32(element.spaceIndex!)) + } + } + let fontIconWidth = CGFloat([minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.intraCellPadding) + label.textContainer!.size.width = frame.width - Preferences.iconSize - Preferences.intraCellPadding * 3 - fontIconWidth + subviews.first!.frame.size = frame.size + self.mouseDownCallback = mouseDownCallback + self.mouseMovedCallback = mouseMovedCallback + if trackingAreas.count > 0 { + removeTrackingArea(trackingAreas[0]) + } + addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseMoved, .activeAlways], owner: self, userInfo: nil)) + } + + static func makeShadow(_ color: NSColor) -> NSShadow { + let shadow = NSShadow() + shadow.shadowColor = color + shadow.shadowOffset = .zero + shadow.shadowBlurRadius = 1 + return shadow + } + + static func downscaleFactor() -> CGFloat { + let nCellsBeforePotentialOverflow = Preferences.minRows * Preferences.minCellsPerRow + guard CGFloat(Windows.list.count) > nCellsBeforePotentialOverflow else { return 1 } + // TODO: replace this buggy heuristic with a correct implementation of downscaling + return nCellsBeforePotentialOverflow / (nCellsBeforePotentialOverflow + (sqrt(CGFloat(Windows.list.count) - nCellsBeforePotentialOverflow) * 2)) + } + + static func widthMax(_ screen: NSScreen) -> CGFloat { + return (ThumbnailsPanel.widthMax(screen) / Preferences.minCellsPerRow - Preferences.interCellPadding) * CollectionViewItemView.downscaleFactor() + } + + static func widthMin(_ screen: NSScreen) -> CGFloat { + return (ThumbnailsPanel.widthMax(screen) / Preferences.maxCellsPerRow - Preferences.interCellPadding) * CollectionViewItemView.downscaleFactor() + } + + static func height(_ screen: NSScreen) -> CGFloat { + return (ThumbnailsPanel.heightMax(screen) / Preferences.minRows - Preferences.interCellPadding) * CollectionViewItemView.downscaleFactor() + } + + static func width(_ image: NSImage?, _ screen: NSScreen) -> CGFloat { + return max(thumbnailSize(image, screen).0 + Preferences.intraCellPadding * 2, CollectionViewItemView.widthMin(screen)) + } + + static func thumbnailSize(_ image: NSImage?, _ screen: NSScreen) -> (CGFloat, CGFloat) { + guard let image = image else { return (0, 0) } + let thumbnailHeightMax = CollectionViewItemView.height(screen) - Preferences.intraCellPadding * 3 - Preferences.iconSize + let thumbnailWidthMax = CollectionViewItemView.widthMax(screen) - Preferences.intraCellPadding * 2 + let thumbnailHeight = min(image.size.height, thumbnailHeightMax) + let thumbnailWidth = min(image.size.width, thumbnailWidthMax) + let imageRatio = image.size.width / image.size.height + let thumbnailRatio = thumbnailWidth / thumbnailHeight + if thumbnailRatio > imageRatio { + return (image.size.width * thumbnailHeight / image.size.height, thumbnailHeight) + } + return (thumbnailWidth, image.size.height * thumbnailWidth / image.size.width) + } + + private func makeHStackView() -> NSStackView { + let hStackView = NSStackView() + hStackView.spacing = Preferences.intraCellPadding + hStackView.setViews([appIcon, label, hiddenIcon, minimizedIcon, spaceIcon], in: .leading) + return hStackView + } + + private func makeVStackView(_ hStackView: NSStackView) -> NSStackView { + let vStackView = NSStackView() + vStackView.wantsLayer = true + vStackView.layer!.backgroundColor = .clear + vStackView.layer!.cornerRadius = Preferences.cellCornerRadius + vStackView.layer!.borderWidth = Preferences.cellBorderWidth + vStackView.layer!.borderColor = .clear + vStackView.edgeInsets = NSEdgeInsets(top: Preferences.intraCellPadding, left: Preferences.intraCellPadding, bottom: Preferences.intraCellPadding, right: Preferences.intraCellPadding) + vStackView.orientation = .vertical + vStackView.spacing = Preferences.intraCellPadding + vStackView.setViews([hStackView, thumbnail], in: .leading) + return vStackView + } +} + +typealias MouseDownCallback = () -> Void +typealias MouseMovedCallback = () -> Void diff --git a/alt-tab-macos/ui/main-window/ThumbnailsPanel.swift b/alt-tab-macos/ui/main-window/ThumbnailsPanel.swift index b48a339f..56b380ec 100644 --- a/alt-tab-macos/ui/main-window/ThumbnailsPanel.swift +++ b/alt-tab-macos/ui/main-window/ThumbnailsPanel.swift @@ -21,8 +21,9 @@ class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDele backgroundView = ThumbnailsPanel.makeBackgroundView() backgroundView.addSubview(collectionView) contentView!.addSubview(backgroundView) - // highest level possible; this allows the app to go on top of context menus - level = .screenSaver + // 2nd highest level possible; this allows the app to go on top of context menus + // highest level is .screenSaver but makes drag and drop on top the main window impossible + level = .popUpMenu // helps filter out this window from the thumbnails setAccessibilitySubrole(.unknown) } @@ -65,13 +66,16 @@ class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDele func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let item = collectionView.makeItem(withIdentifier: cellId, for: indexPath) as! CollectionViewItem - item.updateRecycledCellWithNewContent(Windows.list[indexPath.item], app!.focusSelectedWindow, app!.thumbnailsPanel!.highlightCell, currentScreen!) + item.view_.updateRecycledCellWithNewContent(Windows.list[indexPath.item], + { self.app!.focusSelectedWindow(item.view_.window_) }, + { self.app!.thumbnailsPanel!.highlightCell(item) }, + currentScreen!) return item } func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { guard indexPath.item < Windows.list.count else { return .zero } - return NSSize(width: CollectionViewItem.width(Windows.list[indexPath.item].thumbnail, currentScreen!).rounded(), height: CollectionViewItem.height(currentScreen!).rounded()) + return NSSize(width: CollectionViewItemView.width(Windows.list[indexPath.item].thumbnail, currentScreen!).rounded(), height: CollectionViewItemView.height(currentScreen!).rounded()) } func highlightCell() {