feat(mac): show relay attention badge without dimming paused state

This commit is contained in:
Peter Steinberger
2025-12-06 23:54:56 +01:00
parent c9f5edbc1d
commit ff36375581

View File

@@ -592,9 +592,9 @@ enum ShellRunner {
struct ClawdisApp: App { struct ClawdisApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate @NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate
@StateObject private var state: AppState @StateObject private var state: AppState
@StateObject private var relayManager = RelayProcessManager.shared
@State private var statusItem: NSStatusItem? @State private var statusItem: NSStatusItem?
@State private var isMenuPresented = false @State private var isMenuPresented = false
private let relayManager = RelayProcessManager.shared
init() { init() {
_state = StateObject(wrappedValue: AppStateStore.shared) _state = StateObject(wrappedValue: AppStateStore.shared)
@@ -605,7 +605,8 @@ struct ClawdisApp: App {
CritterStatusLabel( CritterStatusLabel(
isPaused: self.state.isPaused, isPaused: self.state.isPaused,
isWorking: self.state.isWorking, isWorking: self.state.isWorking,
earBoostActive: self.state.earBoostActive) earBoostActive: self.state.earBoostActive,
relayStatus: self.relayManager.status)
} }
.menuBarExtraStyle(.menu) .menuBarExtraStyle(.menu)
.menuBarExtraAccess(isPresented: self.$isMenuPresented) { item in .menuBarExtraAccess(isPresented: self.$isMenuPresented) { item in
@@ -708,6 +709,7 @@ private struct CritterStatusLabel: View {
var isPaused: Bool var isPaused: Bool
var isWorking: Bool var isWorking: Bool
var earBoostActive: Bool var earBoostActive: Bool
var relayStatus: RelayProcessManager.Status
@State private var blinkAmount: CGFloat = 0 @State private var blinkAmount: CGFloat = 0
@State private var nextBlink = Date().addingTimeInterval(Double.random(in: 3.5...8.5)) @State private var nextBlink = Date().addingTimeInterval(Double.random(in: 3.5...8.5))
@@ -721,45 +723,54 @@ private struct CritterStatusLabel: View {
private let ticker = Timer.publish(every: 0.35, on: .main, in: .common).autoconnect() private let ticker = Timer.publish(every: 0.35, on: .main, in: .common).autoconnect()
var body: some View { var body: some View {
Group { ZStack(alignment: .bottomTrailing) {
if self.isPaused { Group {
Image(nsImage: CritterIconRenderer.makeIcon(blink: 0)) if self.isPaused {
.frame(width: 18, height: 16) Image(nsImage: CritterIconRenderer.makeIcon(blink: 0))
} else { .frame(width: 18, height: 16)
Image(nsImage: CritterIconRenderer.makeIcon( } else {
blink: self.blinkAmount, Image(nsImage: CritterIconRenderer.makeIcon(
legWiggle: max(self.legWiggle, self.isWorking ? 0.6 : 0), blink: self.blinkAmount,
earWiggle: self.earWiggle, legWiggle: max(self.legWiggle, self.isWorking ? 0.6 : 0),
earScale: self.earBoostActive ? 1.9 : 1.0)) earWiggle: self.earWiggle,
.frame(width: 18, height: 16) earScale: self.earBoostActive ? 1.9 : 1.0))
.rotationEffect(.degrees(self.wiggleAngle), anchor: .center) .frame(width: 18, height: 16)
.offset(x: self.wiggleOffset) .rotationEffect(.degrees(self.wiggleAngle), anchor: .center)
.onReceive(self.ticker) { now in .offset(x: self.wiggleOffset)
if now >= self.nextBlink { .onReceive(self.ticker) { now in
self.blink() if now >= self.nextBlink {
self.nextBlink = now.addingTimeInterval(Double.random(in: 3.5...8.5)) self.blink()
} self.nextBlink = now.addingTimeInterval(Double.random(in: 3.5...8.5))
}
if now >= self.nextWiggle { if now >= self.nextWiggle {
self.wiggle() self.wiggle()
self.nextWiggle = now.addingTimeInterval(Double.random(in: 6.5...14)) self.nextWiggle = now.addingTimeInterval(Double.random(in: 6.5...14))
} }
if now >= self.nextLegWiggle { if now >= self.nextLegWiggle {
self.wiggleLegs() self.wiggleLegs()
self.nextLegWiggle = now.addingTimeInterval(Double.random(in: 5.0...11.0)) self.nextLegWiggle = now.addingTimeInterval(Double.random(in: 5.0...11.0))
} }
if now >= self.nextEarWiggle { if now >= self.nextEarWiggle {
self.wiggleEars() self.wiggleEars()
self.nextEarWiggle = now.addingTimeInterval(Double.random(in: 7.0...14.0)) self.nextEarWiggle = now.addingTimeInterval(Double.random(in: 7.0...14.0))
} }
if self.isWorking { if self.isWorking {
self.scurry() self.scurry()
}
} }
} .onChange(of: self.isPaused) { _, _ in self.resetMotion() }
.onChange(of: self.isPaused) { _, _ in self.resetMotion() } }
}
if self.relayNeedsAttention {
Circle()
.fill(self.relayBadgeColor)
.frame(width: 8, height: 8)
.offset(x: 4, y: 4)
} }
} }
} }
@@ -827,6 +838,23 @@ private struct CritterStatusLabel: View {
withAnimation(.interpolatingSpring(stiffness: 260, damping: 19)) { self.earWiggle = 0 } withAnimation(.interpolatingSpring(stiffness: 260, damping: 19)) { self.earWiggle = 0 }
} }
} }
private var relayNeedsAttention: Bool {
switch self.relayStatus {
case .failed, .stopped:
return !self.isPaused
case .starting, .restarting, .running:
return false
}
}
private var relayBadgeColor: Color {
switch self.relayStatus {
case .failed: return .red
case .stopped: return .orange
default: return .clear
}
}
} }
enum CritterIconRenderer { enum CritterIconRenderer {