Files
photochk/Sources/Photochk/Views/PhotoTileView.swift
T
2026-07-27 15:42:42 +00:00

92 lines
3.8 KiB
Swift

import AppKit
import SwiftUI
struct PhotoTileView: View {
let member: SimilarityGroup.Member
let isRepresentative: Bool
let isMarked: Bool
let canMark: Bool
let onToggle: () -> Void
private let thumbnailSize: CGFloat = 168
var body: some View {
VStack(alignment: .leading, spacing: 6) {
ZStack(alignment: .topLeading) {
ThumbnailView(url: member.id, size: thumbnailSize)
.overlay {
RoundedRectangle(cornerRadius: 6, style: .continuous)
.strokeBorder(borderColor, lineWidth: isMarked ? 3 : 1)
}
.opacity(isMarked ? 0.55 : 1)
Toggle(isOn: Binding(get: { isMarked }, set: { _ in onToggle() })) {
EmptyView()
}
.toggleStyle(.checkbox)
.labelsHidden()
.disabled(!canMark)
// 明るい写真の上でもチェックボックスが埋もれないように下敷きを敷く
.padding(4)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 5, style: .continuous))
.padding(6)
.help(canMark ? "ゴミ箱へ移動する対象にする" : "グループから最低1枚は残す必要があります")
if isRepresentative {
Text("残す候補")
.font(.caption2.weight(.semibold))
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(Color.accentColor, in: Capsule())
.foregroundStyle(.white)
.padding(6)
.frame(maxWidth: thumbnailSize, alignment: .topTrailing)
}
}
Text(member.asset.fileName)
.font(.callout)
.lineLimit(1)
.truncationMode(.middle)
VStack(alignment: .leading, spacing: 1) {
Text("\(member.asset.dimensionText)・\(member.asset.fileSizeText)")
Text(member.asset.captureDateText)
if isRepresentative {
Text("この写真が基準")
} else if let tag = member.exactSetTag {
// 代表とは別だが、同じ印どうしは中身が完全に一致している
Text("\(member.relation.distanceText)・同一 \(tag)")
.foregroundStyle(.orange)
} else {
Text(member.relation.distanceText)
.foregroundStyle(member.relation.isExact ? .orange : .secondary)
}
}
.font(.caption)
.foregroundStyle(.secondary)
}
.frame(width: thumbnailSize)
.contentShape(.rect)
// クリックは選択に一本化する。ダブルクリックを併置すると単クリックと取り合いになり、
// チェックが二度切り替わってしまうため、プレビューは右クリックメニューから開く。
.onTapGesture { if canMark { onToggle() } }
.contextMenu {
Button("プレビューで開く") { NSWorkspace.shared.open(member.id) }
Button("Finder で表示") {
NSWorkspace.shared.activateFileViewerSelecting([member.id])
}
Button("パスをコピー") {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(member.id.path, forType: .string)
}
}
}
private var borderColor: Color {
if isMarked { return .red }
return isRepresentative ? .accentColor : Color.primary.opacity(0.12)
}
}