83 lines
2.7 KiB
Swift
83 lines
2.7 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import AppKit
|
|
import ListViewKit
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
final class HostingListRow: ListRowView {
|
|
private let hostingView = NSHostingView(rootView: AnyView(EmptyView()))
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
hostingView.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(hostingView)
|
|
NSLayoutConstraint.activate([
|
|
hostingView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
hostingView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
hostingView.topAnchor.constraint(equalTo: topAnchor),
|
|
hostingView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
])
|
|
}
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
hostingView.rootView = AnyView(EmptyView())
|
|
}
|
|
|
|
func configure(rootView: AnyView) {
|
|
hostingView.rootView = rootView
|
|
}
|
|
}
|
|
|
|
/// Shared renderer and diff state for SwiftUI-backed ListView coordinators.
|
|
/// List-specific setup, selection and scrolling stay in their owning adapter.
|
|
@MainActor
|
|
final class HostingListContent<Item>
|
|
where
|
|
Item: Identifiable & Hashable & SendableMetatype, Item.ID: Hashable
|
|
{
|
|
typealias Renderer = @MainActor (Item) -> AnyView
|
|
|
|
private var renderer: Renderer?
|
|
private var locale = Locale.current
|
|
private var colorScheme = ColorScheme.light
|
|
private var items: [Item] = []
|
|
|
|
@discardableResult
|
|
func update(
|
|
_ listView: ListView<Item>, items newItems: [Item], renderer: @escaping Renderer,
|
|
locale newLocale: Locale, colorScheme newColorScheme: ColorScheme,
|
|
animated: Bool
|
|
) -> Bool {
|
|
self.renderer = renderer
|
|
let environmentChanged = locale != newLocale || colorScheme != newColorScheme
|
|
let identitiesChanged = items.map(\.id) != newItems.map(\.id)
|
|
locale = newLocale
|
|
colorScheme = newColorScheme
|
|
if !identitiesChanged {
|
|
for (old, new) in zip(items, newItems) where old != new { listView.update(new) }
|
|
if environmentChanged { listView.reloadData() }
|
|
} else {
|
|
listView.apply(newItems, animated: animated && !items.isEmpty)
|
|
}
|
|
items = newItems
|
|
return identitiesChanged
|
|
}
|
|
|
|
func configure(_ row: HostingListRow, item: Item) {
|
|
guard let root = rootView(for: item) else { return }
|
|
row.configure(rootView: root)
|
|
}
|
|
|
|
func rootView(for item: Item) -> AnyView? {
|
|
guard let renderer else { return nil }
|
|
return AnyView(
|
|
renderer(item)
|
|
.id(item.id)
|
|
.environment(\.locale, locale)
|
|
.environment(\.colorScheme, colorScheme))
|
|
}
|
|
}
|