fix(mac): show disconnected sessions + sleeping eyes

main
Peter Steinberger 2025-12-22 21:13:33 +01:00
parent 9d47b15575
commit 469c8a1a4b
2 changed files with 59 additions and 19 deletions

View File

@ -137,7 +137,7 @@ struct CritterStatusLabel: View {
} }
if self.isSleeping { if self.isSleeping {
return Image(nsImage: CritterIconRenderer.makeIcon(blink: 1, badge: nil)) return Image(nsImage: CritterIconRenderer.makeIcon(blink: 1, eyesClosedLines: true, badge: nil))
} }
return Image(nsImage: CritterIconRenderer.makeIcon( return Image(nsImage: CritterIconRenderer.makeIcon(
@ -268,6 +268,7 @@ enum CritterIconRenderer {
earWiggle: CGFloat = 0, earWiggle: CGFloat = 0,
earScale: CGFloat = 1, earScale: CGFloat = 1,
earHoles: Bool = false, earHoles: Bool = false,
eyesClosedLines: Bool = false,
badge: Badge? = nil) -> NSImage badge: Badge? = nil) -> NSImage
{ {
// Force a 36×36px backing store (2× for the 18pt logical canvas) so the menu bar icon stays crisp on Retina. // Force a 36×36px backing store (2× for the 18pt logical canvas) so the menu bar icon stays crisp on Retina.
@ -333,9 +334,7 @@ enum CritterIconRenderer {
let legLift = snapY(legH * 0.35 * legWiggle) let legLift = snapY(legH * 0.35 * legWiggle)
let legYBase = snapY(bodyY - legH + h * 0.05) let legYBase = snapY(bodyY - legH + h * 0.05)
let eyeOpen = max(0.05, 1 - blink)
let eyeW = snapX(bodyW * 0.2) let eyeW = snapX(bodyW * 0.2)
let eyeH = snapY(bodyH * 0.26 * eyeOpen)
let eyeY = snapY(bodyY + bodyH * 0.56) let eyeY = snapY(bodyY + bodyH * 0.56)
let eyeOffset = snapX(bodyW * 0.24) let eyeOffset = snapX(bodyW * 0.24)
@ -405,20 +404,50 @@ enum CritterIconRenderer {
transform: nil)) transform: nil))
} }
let left = CGMutablePath() if eyesClosedLines {
left.move(to: CGPoint(x: snapX(leftCenter.x - eyeW / 2), y: snapY(leftCenter.y - eyeH))) let lineW = snapX(eyeW * 0.95)
left.addLine(to: CGPoint(x: snapX(leftCenter.x + eyeW / 2), y: snapY(leftCenter.y))) let lineH = snapY(max(stepY * 2, bodyH * 0.06))
left.addLine(to: CGPoint(x: snapX(leftCenter.x - eyeW / 2), y: snapY(leftCenter.y + eyeH))) let corner = snapX(lineH * 0.6)
left.closeSubpath() let leftRect = CGRect(
x: snapX(leftCenter.x - lineW / 2),
y: snapY(leftCenter.y - lineH / 2),
width: lineW,
height: lineH)
let rightRect = CGRect(
x: snapX(rightCenter.x - lineW / 2),
y: snapY(rightCenter.y - lineH / 2),
width: lineW,
height: lineH)
context.cgContext.addPath(CGPath(
roundedRect: leftRect,
cornerWidth: corner,
cornerHeight: corner,
transform: nil))
context.cgContext.addPath(CGPath(
roundedRect: rightRect,
cornerWidth: corner,
cornerHeight: corner,
transform: nil))
} else {
let eyeOpen = max(0.05, 1 - blink)
let eyeH = snapY(bodyH * 0.26 * eyeOpen)
let right = CGMutablePath() let left = CGMutablePath()
right.move(to: CGPoint(x: snapX(rightCenter.x + eyeW / 2), y: snapY(rightCenter.y - eyeH))) left.move(to: CGPoint(x: snapX(leftCenter.x - eyeW / 2), y: snapY(leftCenter.y - eyeH)))
right.addLine(to: CGPoint(x: snapX(rightCenter.x - eyeW / 2), y: snapY(rightCenter.y))) left.addLine(to: CGPoint(x: snapX(leftCenter.x + eyeW / 2), y: snapY(leftCenter.y)))
right.addLine(to: CGPoint(x: snapX(rightCenter.x + eyeW / 2), y: snapY(rightCenter.y + eyeH))) left.addLine(to: CGPoint(x: snapX(leftCenter.x - eyeW / 2), y: snapY(leftCenter.y + eyeH)))
right.closeSubpath() left.closeSubpath()
let right = CGMutablePath()
right.move(to: CGPoint(x: snapX(rightCenter.x + eyeW / 2), y: snapY(rightCenter.y - eyeH)))
right.addLine(to: CGPoint(x: snapX(rightCenter.x - eyeW / 2), y: snapY(rightCenter.y)))
right.addLine(to: CGPoint(x: snapX(rightCenter.x + eyeW / 2), y: snapY(rightCenter.y + eyeH)))
right.closeSubpath()
context.cgContext.addPath(left)
context.cgContext.addPath(right)
}
context.cgContext.addPath(left)
context.cgContext.addPath(right)
context.cgContext.fillPath() context.cgContext.fillPath()
context.cgContext.restoreGState() context.cgContext.restoreGState()

View File

@ -411,6 +411,16 @@ struct MenuContent: View {
self.sessionLoading = true self.sessionLoading = true
self.sessionErrorText = nil self.sessionErrorText = nil
if case .connected = self.controlChannel.state {
// ok
} else {
self.sessionStorePath = nil
self.sessionMenu = []
self.sessionErrorText = "No connection to gateway"
self.sessionLoading = false
return
}
do { do {
let snapshot = try await SessionLoader.loadSnapshot(limit: 32) let snapshot = try await SessionLoader.loadSnapshot(limit: 32)
self.sessionStorePath = snapshot.storePath self.sessionStorePath = snapshot.storePath
@ -426,7 +436,8 @@ struct MenuContent: View {
return (lhs.updatedAt ?? .distantPast) > (rhs.updatedAt ?? .distantPast) return (lhs.updatedAt ?? .distantPast) > (rhs.updatedAt ?? .distantPast)
} }
} catch { } catch {
// Keep the previous snapshot (if any) so the menu doesn't go empty while the gateway is flaky. self.sessionStorePath = nil
self.sessionMenu = []
self.sessionErrorText = self.compactSessionError(error) self.sessionErrorText = self.compactSessionError(error)
} }
@ -437,12 +448,12 @@ struct MenuContent: View {
if let loadError = error as? SessionLoadError { if let loadError = error as? SessionLoadError {
switch loadError { switch loadError {
case .gatewayUnavailable: case .gatewayUnavailable:
return "Sessions unavailable — gateway unreachable" return "No connection to gateway"
case .decodeFailed: case .decodeFailed:
return "Sessions unavailable — invalid payload" return "Sessions unavailable"
} }
} }
return "Sessions unavailable" return "No connection to gateway"
} }
private func open(tab: SettingsTab) { private func open(tab: SettingsTab) {