feat(mac): show relay attention badge without dimming paused state
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user