Add photo tile view
This commit is contained in:
@@ -0,0 +1,91 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user