Bluetooth HUD Animations

This commit is contained in:
Hariharan Mudaliar 2026-02-04 23:48:48 +05:30
commit 2453ca89ff
No known key found for this signature in database
GPG key ID: 025583EB0BE5640E
12 changed files with 366 additions and 6 deletions

View file

@ -21,8 +21,65 @@
*/
import SwiftUI
import AppKit
import AVFoundation
import Defaults
// MARK: - Inline HUD looping .mov icon
private final class LoopingPlayerController {
let player: AVQueuePlayer
private var looper: AVPlayerLooper?
init(url: URL) {
let item = AVPlayerItem(url: url)
self.player = AVQueuePlayer()
self.player.isMuted = true
self.player.actionAtItemEnd = .none
self.looper = AVPlayerLooper(player: self.player, templateItem: item)
self.player.play()
}
deinit {
player.pause()
looper = nil
}
}
private struct LoopingVideoIcon: NSViewRepresentable {
let url: URL
let size: CGSize
func makeNSView(context: Context) -> NSView {
let view = NSView(frame: NSRect(origin: .zero, size: size))
view.wantsLayer = true
let layer = AVPlayerLayer()
layer.videoGravity = .resizeAspect
layer.frame = view.bounds
view.layer?.addSublayer(layer)
context.coordinator.attach(layer: layer, url: url)
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
// No-op; the animation loops via AVPlayerLooper.
}
func makeCoordinator() -> Coordinator { Coordinator() }
final class Coordinator {
private var controller: LoopingPlayerController?
func attach(layer: AVPlayerLayer, url: URL) {
controller = LoopingPlayerController(url: url)
layer.player = controller?.player
}
}
}
struct InlineHUD: View {
@EnvironmentObject var vm: DynamicIslandViewModel
@Binding var type: SneakContentType
@ -39,6 +96,7 @@ struct InlineHUD: View {
@Default(.useCircularBluetoothBatteryIndicator) var useCircularBluetoothBatteryIndicator
@Default(.showBluetoothBatteryPercentageText) var showBluetoothBatteryPercentageText
@Default(.showBluetoothDeviceNameMarquee) var showBluetoothDeviceNameMarquee
@Default(.useBluetoothHUD3DIcon) var useBluetoothHUD3DIcon
@Default(.enableMinimalisticUI) var enableMinimalisticUI
@Default(.showCapsLockLabel) var showCapsLockLabel
@Default(.capsLockIndicatorTintMode) var capsLockTintMode
@ -173,10 +231,17 @@ struct InlineHUD: View {
.contentTransition(.interpolate)
.frame(width: 20, height: 15, alignment: .center)
case .bluetoothAudio:
Image(systemName: icon.isEmpty ? "bluetooth" : icon)
.symbolRenderingMode(.hierarchical)
.contentTransition(.interpolate)
.frame(width: 20, height: 15, alignment: .center)
if useBluetoothHUD3DIcon,
let deviceType = bluetoothManager.lastConnectedDevice?.deviceType,
let url = Bundle.main.url(forResource: deviceType.inlineHUDAnimationBaseName, withExtension: "mov") {
LoopingVideoIcon(url: url, size: CGSize(width: 20, height: 20))
.frame(width: 20, height: 20, alignment: .center)
} else {
Image(systemName: icon.isEmpty ? "bluetooth" : icon)
.symbolRenderingMode(.hierarchical)
.contentTransition(.interpolate)
.frame(width: 20, height: 15, alignment: .center)
}
case .capsLock:
Image(systemName: "capslock.fill")
.symbolRenderingMode(.hierarchical)

View file

@ -631,6 +631,7 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .devices, title: "Use circular battery indicator", keywords: ["battery", "circular"], highlightID: SettingsTab.devices.highlightID(for: "Use circular battery indicator")),
SettingsSearchEntry(tab: .devices, title: "Show battery percentage text in HUD", keywords: ["battery text"], highlightID: SettingsTab.devices.highlightID(for: "Show battery percentage text in HUD")),
SettingsSearchEntry(tab: .devices, title: "Scroll device name in HUD", keywords: ["marquee", "device name"], highlightID: SettingsTab.devices.highlightID(for: "Scroll device name in HUD")),
SettingsSearchEntry(tab: .devices, title: "Use 3D Bluetooth HUD icon", keywords: ["bluetooth", "3d", "animation", "mov"], highlightID: SettingsTab.devices.highlightID(for: "Use 3D Bluetooth HUD icon")),
SettingsSearchEntry(tab: .devices, title: "Color-coded battery display", keywords: ["color", "battery"], highlightID: SettingsTab.devices.highlightID(for: "Color-coded battery display")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Color-coded volume display", keywords: ["volume", "color"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Color-coded volume display")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Smooth color transitions", keywords: ["gradient", "smooth"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Smooth color transitions")),
@ -1783,6 +1784,8 @@ private struct DevicesSettingsView: View {
.settingsHighlight(id: highlightID("Show battery percentage text in HUD"))
Defaults.Toggle("Scroll device name in HUD", key: .showBluetoothDeviceNameMarquee)
.settingsHighlight(id: highlightID("Scroll device name in HUD"))
Defaults.Toggle("Use 3D Bluetooth HUD icon", key: .useBluetoothHUD3DIcon)
.settingsHighlight(id: highlightID("Use 3D Bluetooth HUD icon"))
} header: {
Text("Bluetooth Audio Devices")
} footer: {