Files
wehub-resource-sync 869b84f27c
Build / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

58 lines
2.0 KiB
Swift

import AppKit
/// Thin wrapper around `NSHapticFeedbackManager` so call sites don't have to
/// reach into AppKit. Only Force Touch trackpads produce a click; on other
/// hardware the calls are no-ops, which is the documented behavior.
///
/// We deliberately use the system performer rather than spinning up a custom
/// haptic engine — the standard patterns (alignment / levelChange / generic)
/// already match what users expect from Apple's own apps.
enum Haptics {
/// Light tick — use for transient UI feedback like advancing a page or
/// flipping a toggle. Cheapest of the three.
static func tap() {
perform(.alignment)
}
/// Stronger affirmation — use when a user-visible state change crosses a
/// boundary (Full Disk Access granted, scan completed, clean finished).
static func success() {
perform(.levelChange)
}
/// Generic feedback — fallback when nothing else fits.
static func generic() {
perform(.generic)
}
/// Defaults key for the user-facing "Play sound effects" toggle
/// (Settings → General). Defaults to on.
static let soundEffectsKey = "PureMac.SoundEffects"
private static var soundEnabled: Bool {
UserDefaults.standard.object(forKey: soundEffectsKey) as? Bool ?? true
}
/// Completion beat: haptic + the system "Glass" chime in one call so the
/// two always land together. The sound intentionally still plays under
/// Reduce Motion — there it stands in for the suppressed confetti.
static func successWithSound() {
success()
if soundEnabled {
NSSound(named: "Glass")?.play()
}
}
/// Failure beat: haptic + the system "Basso" thud.
static func errorWithSound() {
generic()
if soundEnabled {
NSSound(named: "Basso")?.play()
}
}
private static func perform(_ pattern: NSHapticFeedbackManager.FeedbackPattern) {
NSHapticFeedbackManager.defaultPerformer.perform(pattern, performanceTime: .now)
}
}