Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions Sources/MacDuo/StatusItemController.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppKit
import Combine
import SwiftUI

/// The menu bar item and the settings popover.
Expand All @@ -9,7 +10,8 @@ final class StatusItemController: NSObject, NSPopoverDelegate {
private let popover = NSPopover()
private let preferences: Preferences
private let controller: LidController
private var titleTimer: Timer?
private var showsAngleSubscription: AnyCancellable?
private var angleSubscription: AnyCancellable?
private var barWindowMoved: NSObjectProtocol?

init(controller: LidController, preferences: Preferences) {
Expand Down Expand Up @@ -43,17 +45,17 @@ final class StatusItemController: NSObject, NSPopoverDelegate {
hostingController.sizingOptions = [.preferredContentSize]
popover.contentViewController = hostingController

let timer = Timer(timeInterval: 0.25, repeats: true) { [weak self] _ in
MainActor.assumeIsolated { self?.refreshTitle() }
}
RunLoop.main.add(timer, forMode: .common)
titleTimer = timer
refreshTitle()
showsAngleSubscription = preferences.$showsAngleInMenuBar
.removeDuplicates()
.sink { [weak self] showsAngle in
self?.setShowsAngle(showsAngle)
}
watchBarWindow()
}

deinit {
titleTimer?.invalidate()
showsAngleSubscription?.cancel()
angleSubscription?.cancel()
if let barWindowMoved {
NotificationCenter.default.removeObserver(barWindowMoved)
}
Expand Down Expand Up @@ -99,12 +101,28 @@ final class StatusItemController: NSObject, NSPopoverDelegate {
popover.show(relativeTo: .zero, of: button, preferredEdge: .minY)
}

private func refreshTitle() {
guard let button = statusItem.button else { return }
if preferences.showsAngleInMenuBar {
button.title = String(format: " %.0f°", controller.currentAngle)
} else if !button.title.isEmpty {
button.title = ""
private func setShowsAngle(_ showsAngle: Bool) {
angleSubscription?.cancel()
angleSubscription = nil

guard showsAngle else {
statusItem.button?.title = ""
return
}
// Throttle delivers its first value asynchronously, so show it now;
// the 250 ms window preserves the former timer's 4 Hz ceiling.
statusItem.button?.title = Self.angleTitle(controller.currentAngle)
angleSubscription = controller.$currentAngle
.map(Self.angleTitle)
.removeDuplicates()
.throttle(for: .milliseconds(250), scheduler: RunLoop.main, latest: true)
.sink { [weak self] title in
guard self?.statusItem.button?.title != title else { return }
self?.statusItem.button?.title = title
}
}

private static func angleTitle(_ angle: Double) -> String {
String(format: " %.0f°", angle)
}
}