Icon: add ear holes on voice wake

This commit is contained in:
Peter Steinberger
2025-12-07 16:15:40 +01:00
parent 73133b61fb
commit 3387c135ad
2 changed files with 46 additions and 15 deletions

View File

@@ -192,7 +192,8 @@ private struct CritterStatusLabel: View {
blink: self.blinkAmount,
legWiggle: max(self.legWiggle, self.isWorking ? 0.6 : 0),
earWiggle: self.earWiggle,
earScale: self.earBoostActive ? 1.9 : 1.0))
earScale: self.earBoostActive ? 1.9 : 1.0,
earHoles: self.earBoostActive))
.frame(width: 18, height: 16)
.rotationEffect(.degrees(self.wiggleAngle), anchor: .center)
.offset(x: self.wiggleOffset)
@@ -342,7 +343,8 @@ enum CritterIconRenderer {
blink: CGFloat,
legWiggle: CGFloat = 0,
earWiggle: CGFloat = 0,
earScale: CGFloat = 1) -> NSImage
earScale: CGFloat = 1,
earHoles: Bool = false) -> NSImage
{
let image = NSImage(size: size)
image.lockFocus()
@@ -362,6 +364,16 @@ enum CritterIconRenderer {
let earW = w * 0.22
let earH = bodyH * 0.66 * earScale * (1 - 0.08 * abs(earWiggle))
let earCorner = earW * 0.24
let leftEarRect = CGRect(
x: bodyX - earW * 0.55 + earWiggle,
y: bodyY + bodyH * 0.08 + earWiggle * 0.4,
width: earW,
height: earH)
let rightEarRect = CGRect(
x: bodyX + bodyW - earW * 0.45 - earWiggle,
y: bodyY + bodyH * 0.08 - earWiggle * 0.4,
width: earW,
height: earH)
let legW = w * 0.11
let legH = h * 0.26
@@ -385,20 +397,12 @@ enum CritterIconRenderer {
cornerHeight: bodyCorner,
transform: nil))
ctx.addPath(CGPath(
roundedRect: CGRect(
x: bodyX - earW * 0.55 + earWiggle,
y: bodyY + bodyH * 0.08 + earWiggle * 0.4,
width: earW,
height: earH),
roundedRect: leftEarRect,
cornerWidth: earCorner,
cornerHeight: earCorner,
transform: nil))
ctx.addPath(CGPath(
roundedRect: CGRect(
x: bodyX + bodyW - earW * 0.45 - earWiggle,
y: bodyY + bodyH * 0.08 - earWiggle * 0.4,
width: earW,
height: earH),
roundedRect: rightEarRect,
cornerWidth: earCorner,
cornerHeight: earCorner,
transform: nil))
@@ -416,6 +420,33 @@ enum CritterIconRenderer {
let leftCenter = CGPoint(x: w / 2 - eyeOffset, y: eyeY)
let rightCenter = CGPoint(x: w / 2 + eyeOffset, y: eyeY)
if earHoles || earScale > 1.05 {
let holeW = earW * 0.6
let holeH = earH * 0.46
let holeCorner = holeW * 0.34
let leftHoleRect = CGRect(
x: leftEarRect.midX - holeW / 2,
y: leftEarRect.midY - holeH / 2 + earH * 0.04,
width: holeW,
height: holeH)
let rightHoleRect = CGRect(
x: rightEarRect.midX - holeW / 2,
y: rightEarRect.midY - holeH / 2 + earH * 0.04,
width: holeW,
height: holeH)
ctx.addPath(CGPath(
roundedRect: leftHoleRect,
cornerWidth: holeCorner,
cornerHeight: holeCorner,
transform: nil))
ctx.addPath(CGPath(
roundedRect: rightHoleRect,
cornerWidth: holeCorner,
cornerHeight: holeCorner,
transform: nil))
}
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))

View File

@@ -4,7 +4,7 @@ Author: steipete · Updated: 2025-12-06 · Scope: macOS app (`apps/macos`)
- **Idle:** Normal icon animation (blink, occasional wiggle).
- **Paused:** Status item uses `appearsDisabled`; no motion.
- **Voice trigger (big ears):** Voice wake detector calls `AppState.triggerVoiceEars()``earBoostActive=true` for ~5s. Ears scale up (1.9x) then auto-reset. Only fired from the in-app voice pipeline.
- **Voice trigger (big ears):** Voice wake detector calls `AppState.triggerVoiceEars()``earBoostActive=true` for ~5s. Ears scale up (1.9x), get circular ear holes for readability, then auto-reset. Only fired from the in-app voice pipeline.
- **Working (agent running):** `AppState.isWorking=true` drives a “tail/leg scurry” micro-motion: faster leg wiggle and slight offset while work is in-flight. Currently toggled around WebChat agent runs; add the same toggle around other long tasks when you wire them.
Wiring points
@@ -12,8 +12,8 @@ Wiring points
- Agent activity: set `AppStateStore.shared.setWorking(true/false)` around work spans (already done in WebChat agent call). Keep spans short and reset in `defer` blocks to avoid stuck animations.
Shapes & sizes
- Base icon drawn in `CritterIconRenderer.makeIcon(blink:legWiggle:earWiggle:earScale:)`.
- Ear scale defaults to `1.0`; voice boost sets `earScale=1.9` without changing overall frame (18×16pt template image).
- Base icon drawn in `CritterIconRenderer.makeIcon(blink:legWiggle:earWiggle:earScale:earHoles:)`.
- Ear scale defaults to `1.0`; voice boost sets `earScale=1.9` and toggles `earHoles=true` without changing overall frame (18×16pt template image).
- Scurry uses leg wiggle up to ~1.0 with a small horizontal jiggle; its additive to any existing idle wiggle.
Behavioral notes