diff --git a/Example/Example/Model/Creator.swift b/Example/Example/Model/Creator.swift index 8680865..3d93eb9 100644 --- a/Example/Example/Model/Creator.swift +++ b/Example/Example/Model/Creator.swift @@ -74,9 +74,8 @@ class Creator: CollectionViewStreamingCollection { return } let index = Int.random(in: 0..: NSCollectionViewItem { + + static var identifier: NSUserInterfaceItemIdentifier { + return NSUserInterfaceItemIdentifier(rawValue: "CollectionViewCell") + } + + private var hostingView: NSHostingView>? + private var id: ID? + private var content: Content? + private var parentHasFocus: Bool = false + private var parentIsKey: Bool = false + + override var isSelected: Bool { + didSet { + updateState() + } + } + + override var highlightState: NSCollectionViewItem.HighlightState { + didSet { + updateState() + } + } + + override func loadView() { + view = NSView() + } + + func configure(_ content: Content?, id: ID?, parentHasFocus: Bool, parentIsKey: Bool) { + self.content = content + self.id = id + self.parentHasFocus = parentHasFocus + self.parentIsKey = parentIsKey + host() + } + + private func updateState() { + host() + } + + private func host() { + + guard content != nil || hostingView != nil else { + return + } + + let selectionColor = parentHasFocus && parentIsKey + ? Color(nsColor: .selectedContentBackgroundColor) + : Color(nsColor: .unemphasizedSelectedContentBackgroundColor) + let rootView = CollectionViewCellContent(id: id, + content: content, + isSelected: isSelected, + highlightState: .init(highlightState), + selectionColor: selectionColor) + + guard let hostingView else { + let hostingView = NSHostingView(rootView: rootView) + hostingView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingView) + NSLayoutConstraint.activate([ + hostingView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingView.topAnchor.constraint(equalTo: view.topAnchor), + hostingView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + self.hostingView = hostingView + return + } + hostingView.rootView = rootView + } + + override func prepareForReuse() { + super.prepareForReuse() + configure(nil, id: nil, parentHasFocus: false, parentIsKey: false) + } + +} + +#endif diff --git a/Sources/SelectableCollectionView/Views/CollectionViewCellContent.swift b/Sources/SelectableCollectionView/Views/CollectionViewCellContent.swift new file mode 100644 index 0000000..f77dd1a --- /dev/null +++ b/Sources/SelectableCollectionView/Views/CollectionViewCellContent.swift @@ -0,0 +1,45 @@ +// Copyright (c) 2022-2026 Jason Morley +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if os(macOS) + +import SwiftUI + +struct CollectionViewCellContent: View { + + let id: ID? + let content: Content? + let isSelected: Bool + let highlightState: CollectionViewItemHighlightState + let selectionColor: Color + + var body: some View { + if let content, let id { + content + .environment(\.isSelected, isSelected) + .environment(\.highlightState, highlightState) + .environment(\.selectionColor, selectionColor) + .id(id) + } + } + +} + +#endif diff --git a/Sources/SelectableCollectionView/Views/CollectionViewContainer.swift b/Sources/SelectableCollectionView/Views/CollectionViewContainer.swift index 31851c5..cd41729 100644 --- a/Sources/SelectableCollectionView/Views/CollectionViewContainer.swift +++ b/Sources/SelectableCollectionView/Views/CollectionViewContainer.swift @@ -24,8 +24,6 @@ import Carbon import Combine import SwiftUI -import SelectableCollectionViewMacResources - public protocol CollectionViewContainerDelegate: NSObject { associatedtype Element: Identifiable @@ -67,7 +65,7 @@ public class CollectionViewContainer typealias DataSource = NSCollectionViewDiffableDataSource - typealias Cell = ShortcutItemView + typealias Cell = CollectionViewCell private let scrollView: CustomScrollView private let collectionView: InteractiveCollectionView @@ -90,17 +88,26 @@ public class CollectionViewContainer? - private var content: AnyView? - var element: Any? - - override var isSelected: Bool { - didSet { - updateState() - } - } - - override var highlightState: NSCollectionViewItem.HighlightState { - didSet { - updateState() - } - } - - func updateState() { - guard let content = content else { - return - } - host(content) - } - - private var parentHasFocus: Bool = false - private var parentIsKey: Bool = false - - override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nil, bundle: Resources.bundle) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func host(_ content: AnyView) { - - let modifiedContent = AnyView(content - .environment(\.isSelected, isSelected) - .environment(\.highlightState, .init(highlightState)) - .environment(\.selectionColor, parentHasFocus && parentIsKey ? Color(nsColor: .selectedContentBackgroundColor) : Color(nsColor: .unemphasizedSelectedContentBackgroundColor))) - if let hostingView = hostingView { - hostingView.rootView = modifiedContent - } else { - let newHostingView = NSHostingView(rootView: modifiedContent) - newHostingView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(newHostingView) - setupConstraints(for: newHostingView) - self.hostingView = newHostingView - } - } - -#warning("TODO: Not sure if this is necessary") - override func prepareForReuse() { - super.prepareForReuse() - configure(AnyView(EmptyView()), parentHasFocus: false, parentIsKey: false) - } - -#warning("TODO: Called by the data source") -#warning("TODO: This should take an item") - func configure(_ content: AnyView, parentHasFocus: Bool, parentIsKey: Bool) { - self.content = content - self.parentHasFocus = parentHasFocus - self.parentIsKey = parentIsKey - host(content) - } - - func setupConstraints(for view: NSView) { - NSLayoutConstraint.activate([ - view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), - view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), - view.topAnchor.constraint(equalTo: self.view.topAnchor), - view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), - ]) - } - -} - -#endif