chore: open settings from menu and restart packaged app
This commit is contained in:
@@ -374,8 +374,6 @@ struct ClawdisApp: App {
|
|||||||
Button("Test Notification") {
|
Button("Test Notification") {
|
||||||
Task { _ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil) }
|
Task { _ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil) }
|
||||||
}
|
}
|
||||||
Divider()
|
|
||||||
Button("Permissions…") { PermissionsSheetController.shared.show(state: state) }
|
|
||||||
Button("Quit") { NSApplication.shared.terminate(nil) }
|
Button("Quit") { NSApplication.shared.terminate(nil) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,11 +389,11 @@ 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 {
|
||||||
CritterGlyph(blinkAmount: blinkAmount)
|
Image(nsImage: CritterIconRenderer.makeIcon(blink: blinkAmount))
|
||||||
.frame(width: 16, height: 16)
|
.renderingMode(.template)
|
||||||
|
.frame(width: 18, height: 16)
|
||||||
.rotationEffect(.degrees(wiggleAngle), anchor: .center)
|
.rotationEffect(.degrees(wiggleAngle), anchor: .center)
|
||||||
.offset(x: wiggleOffset)
|
.offset(x: wiggleOffset)
|
||||||
.foregroundStyle(isPaused ? .secondary : .primary)
|
|
||||||
.onReceive(ticker) { now in
|
.onReceive(ticker) { now in
|
||||||
guard !isPaused else {
|
guard !isPaused else {
|
||||||
resetMotion()
|
resetMotion()
|
||||||
@@ -451,80 +449,83 @@ private struct CritterStatusLabel: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CritterGlyph: View {
|
enum CritterIconRenderer {
|
||||||
var blinkAmount: CGFloat
|
private static let size = NSSize(width: 18, height: 16)
|
||||||
|
|
||||||
var body: some View {
|
static func makeIcon(blink: CGFloat) -> NSImage {
|
||||||
GeometryReader { geo in
|
let image = NSImage(size: size)
|
||||||
let w = geo.size.width
|
image.lockFocus()
|
||||||
let h = geo.size.height
|
defer { image.unlockFocus() }
|
||||||
|
|
||||||
let bodyWidth = w * 0.8
|
guard let ctx = NSGraphicsContext.current?.cgContext else { return image }
|
||||||
let bodyHeight = h * 0.56
|
|
||||||
let bodyCorner = w * 0.08
|
|
||||||
let bodyY = h * 0.18
|
|
||||||
|
|
||||||
let earWidth = w * 0.22
|
let w = size.width
|
||||||
let earHeight = bodyHeight * 0.7
|
let h = size.height
|
||||||
let earCorner = earWidth * 0.22
|
|
||||||
|
|
||||||
let legWidth = w * 0.12
|
let bodyW = w * 0.78
|
||||||
let legHeight = h * 0.28
|
let bodyH = h * 0.56
|
||||||
let legSpacing = w * 0.08
|
let bodyX = (w - bodyW) / 2
|
||||||
let legStartX = (w - (4 * legWidth + 3 * legSpacing)) / 2
|
let bodyY = h * 0.22
|
||||||
let legY = bodyY + bodyHeight - legHeight * 0.18
|
let bodyCorner = w * 0.09
|
||||||
|
|
||||||
let eyeOpen = max(0.08, 1 - blinkAmount)
|
let earW = w * 0.22
|
||||||
let eyeWidth = bodyWidth * 0.18
|
let earH = bodyH * 0.74
|
||||||
let eyeHeight = bodyHeight * 0.24 * eyeOpen
|
let earCorner = earW * 0.24
|
||||||
let eyeY = bodyY + bodyHeight * 0.45
|
|
||||||
let eyeOffset = bodyWidth * 0.22
|
|
||||||
|
|
||||||
ZStack {
|
let legW = w * 0.12
|
||||||
RoundedRectangle(cornerRadius: bodyCorner, style: .continuous)
|
let legH = h * 0.24
|
||||||
.frame(width: bodyWidth, height: bodyHeight)
|
let legSpacing = w * 0.08
|
||||||
.offset(y: bodyY)
|
let legsWidth = 4 * legW + 3 * legSpacing
|
||||||
|
let legStartX = (w - legsWidth) / 2
|
||||||
|
let legY = bodyY + bodyH - legH * 0.05
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: earCorner, style: .continuous)
|
let eyeOpen = max(0.05, 1 - blink)
|
||||||
.frame(width: earWidth, height: earHeight)
|
let eyeW = bodyW * 0.2
|
||||||
.offset(x: -bodyWidth * 0.5 + earWidth * 0.35, y: bodyY + bodyHeight * 0.02)
|
let eyeH = bodyH * 0.26 * eyeOpen
|
||||||
|
let eyeY = bodyY + bodyH * 0.55
|
||||||
|
let eyeOffset = bodyW * 0.24
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: earCorner, style: .continuous)
|
ctx.setFillColor(NSColor.labelColor.cgColor)
|
||||||
.frame(width: earWidth, height: earHeight)
|
|
||||||
.offset(x: bodyWidth * 0.5 - earWidth * 0.35, y: bodyY + bodyHeight * 0.02)
|
|
||||||
|
|
||||||
Path { path in
|
// Body
|
||||||
for i in 0 ..< 4 {
|
ctx.addPath(CGPath(roundedRect: CGRect(x: bodyX, y: bodyY, width: bodyW, height: bodyH), cornerWidth: bodyCorner, cornerHeight: bodyCorner, transform: nil))
|
||||||
let x = legStartX + CGFloat(i) * (legWidth + legSpacing)
|
// Ears
|
||||||
path.addRoundedRect(
|
ctx.addPath(CGPath(roundedRect: CGRect(x: bodyX - earW * 0.55, y: bodyY + bodyH * 0.08, width: earW, height: earH), cornerWidth: earCorner, cornerHeight: earCorner, transform: nil))
|
||||||
in: CGRect(x: x, y: legY, width: legWidth, height: legHeight),
|
ctx.addPath(CGPath(roundedRect: CGRect(x: bodyX + bodyW - earW * 0.45, y: bodyY + bodyH * 0.08, width: earW, height: earH), cornerWidth: earCorner, cornerHeight: earCorner, transform: nil))
|
||||||
cornerSize: CGSize(width: legWidth * 0.36, height: legWidth * 0.36)
|
// Legs
|
||||||
)
|
for i in 0 ..< 4 {
|
||||||
}
|
let x = legStartX + CGFloat(i) * (legW + legSpacing)
|
||||||
}
|
let rect = CGRect(x: x, y: legY, width: legW, height: legH)
|
||||||
.fill()
|
ctx.addPath(CGPath(roundedRect: rect, cornerWidth: legW * 0.34, cornerHeight: legW * 0.34, transform: nil))
|
||||||
|
|
||||||
ZStack {
|
|
||||||
Path { path in
|
|
||||||
let centerX = w / 2 - eyeOffset
|
|
||||||
path.move(to: CGPoint(x: centerX - eyeWidth / 2, y: eyeY - eyeHeight))
|
|
||||||
path.addLine(to: CGPoint(x: centerX + eyeWidth / 2, y: eyeY))
|
|
||||||
path.addLine(to: CGPoint(x: centerX - eyeWidth / 2, y: eyeY + eyeHeight))
|
|
||||||
path.closeSubpath()
|
|
||||||
}
|
|
||||||
Path { path in
|
|
||||||
let centerX = w / 2 + eyeOffset
|
|
||||||
path.move(to: CGPoint(x: centerX + eyeWidth / 2, y: eyeY - eyeHeight))
|
|
||||||
path.addLine(to: CGPoint(x: centerX - eyeWidth / 2, y: eyeY))
|
|
||||||
path.addLine(to: CGPoint(x: centerX + eyeWidth / 2, y: eyeY + eyeHeight))
|
|
||||||
path.closeSubpath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.compositingGroup()
|
|
||||||
.blendMode(.destinationOut)
|
|
||||||
}
|
|
||||||
.compositingGroup()
|
|
||||||
}
|
}
|
||||||
|
ctx.fillPath()
|
||||||
|
|
||||||
|
// Eyes punched out
|
||||||
|
ctx.saveGState()
|
||||||
|
ctx.setBlendMode(.clear)
|
||||||
|
|
||||||
|
let leftCenter = CGPoint(x: w / 2 - eyeOffset, y: eyeY)
|
||||||
|
let rightCenter = CGPoint(x: w / 2 + eyeOffset, y: eyeY)
|
||||||
|
|
||||||
|
let left = CGMutablePath()
|
||||||
|
left.move(to: CGPoint(x: leftCenter.x - eyeW / 2, y: leftCenter.y - eyeH))
|
||||||
|
left.addLine(to: CGPoint(x: leftCenter.x + eyeW / 2, y: leftCenter.y))
|
||||||
|
left.addLine(to: CGPoint(x: leftCenter.x - eyeW / 2, y: leftCenter.y + eyeH))
|
||||||
|
left.closeSubpath()
|
||||||
|
|
||||||
|
let right = CGMutablePath()
|
||||||
|
right.move(to: CGPoint(x: rightCenter.x + eyeW / 2, y: rightCenter.y - eyeH))
|
||||||
|
right.addLine(to: CGPoint(x: rightCenter.x - eyeW / 2, y: rightCenter.y))
|
||||||
|
right.addLine(to: CGPoint(x: rightCenter.x + eyeW / 2, y: rightCenter.y + eyeH))
|
||||||
|
right.closeSubpath()
|
||||||
|
|
||||||
|
ctx.addPath(left)
|
||||||
|
ctx.addPath(right)
|
||||||
|
ctx.fillPath()
|
||||||
|
ctx.restoreGState()
|
||||||
|
|
||||||
|
image.isTemplate = true
|
||||||
|
return image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user