Ariver
2026-07-01 2ba1056c56435f8d98110eca18dba90dc344de66
privatevoice.src/internal/overlay/overlay_windows.go
@@ -49,10 +49,15 @@
// Colors (ARGB)
const (
   clrCapsuleBG = 0xD91C1C1E
   clrBorder    = 0x20FFFFFF
   clrText      = 0xFFB4B4B4
   clrCapsuleBG = 0xA6101318
   clrCapsuleHi = 0x18FFFFFF
   clrBorder    = 0x2EFFFFFF
   clrText      = 0xFFE7EDF5
   clrTextMuted = 0xFFB6C3D2
   clrBlue      = 0xFF007AFF
   clrCyan      = 0xFF2ED3FF
   clrViolet    = 0xFF7A6CFF
   clrPink      = 0xFFFF5EB8
   clrGreen     = 0xFF34C759
   clrRed       = 0xFFFF3B30
   clrOrange    = 0xFFFF9500
@@ -60,15 +65,15 @@
// Dimensions (logical px)
const (
   capW   = 170
   capH   = 48
   capR   = 24
   barW   = 5
   barGap = 4
   barR   = 2.5
   nBars  = 5
   txtSz  = 12.0
   txtGap = 12
   capW   = 420
   capH   = 92
   capR   = 46
   barW   = 7
   barGap = 6
   barR   = 3.5
   nBars  = 11
   txtSz  = 17.0
   txtGap = 20
)
// ---- Win32 structs ----
@@ -230,13 +235,13 @@
func statusColor(s Status) uint32 {
   switch s {
   case StatusLoading:
      return clrBlue
      return clrCyan
   case StatusReady, StatusDone:
      return clrGreen
   case StatusRecording, StatusError:
   case StatusRecording, StatusFreetalking:
      return clrCyan
   case StatusError:
      return clrRed
   case StatusFreetalking:
      return clrBlue
   case StatusProcessing, StatusCancelled, StatusNoVoice, StatusNoContent:
      return clrOrange
   default:
@@ -288,7 +293,7 @@
      done:     make(chan struct{}),
   }
   for i := range o.barHeights {
      o.barHeights[i] = 14
      o.barHeights[i] = 16
   }
   go o.run()
   return o
@@ -472,8 +477,8 @@
            text = cmd.text
            barClr = statusColor(cmd.status)
         case vol := <-o.volCh:
            copy(volumes[0:4], volumes[1:5])
            volumes[4] = vol
            copy(volumes[0:nBars-1], volumes[1:nBars])
            volumes[nBars-1] = vol
         case pos := <-o.posCh:
            o.posX, o.posY = pos[0], pos[1]
            uSetWindowPos.Call(hwnd, 0, uintptr(o.posX), uintptr(o.posY), 0, 0,
@@ -532,7 +537,7 @@
      o.animTime += 0.033
      o.animate(status, volumes)
      o.renderFrame(memDC, hwnd, screenDC, font, strFmt, barClr, text, alpha)
      o.renderFrame(memDC, hwnd, screenDC, font, strFmt, status, barClr, text, alpha)
   }
}
@@ -544,12 +549,14 @@
   switch status {
   case StatusLoading:
      for i := 0; i < nBars; i++ {
         pulse := math.Sin(t*2+float64(i)*0.3)*0.5 + 0.5
         o.barHeights[i] = (10 + pulse*20) * s
         pulse := math.Sin(t*2.4+float64(i)*0.42)*0.5 + 0.5
         o.barHeights[i] = (12 + pulse*28) * s
      }
   case StatusReady, StatusDone:
      for i := 0; i < nBars; i++ {
         o.barHeights[i] = o.barHeights[i]*0.85 + 14*s*0.15
         pulse := math.Sin(t*1.4+float64(i)*0.25)*0.5 + 0.5
         target := (15 + pulse*7) * s
         o.barHeights[i] = o.barHeights[i]*0.78 + target*0.22
      }
   case StatusRecording, StatusFreetalking:
      hasReal := false
@@ -562,29 +569,29 @@
      for i := 0; i < nBars; i++ {
         var target float64
         if hasReal {
            target = (8 + volumes[i]*32) * s
            target = (10 + volumes[i]*44) * s
         } else {
            wave := math.Sin(t*4+float64(i)*0.8)*0.5 + 0.5
            rnd := math.Sin(t*7+float64(i)*1.5) * 0.3
            target = (10 + (wave+rnd)*25) * s
            wave := math.Sin(t*4.6+float64(i)*0.55)*0.5 + 0.5
            rnd := math.Sin(t*7.5+float64(i)*1.1) * 0.2
            target = (12 + (wave+rnd)*30) * s
         }
         o.barHeights[i] = o.barHeights[i]*0.6 + target*0.4
         o.barHeights[i] = o.barHeights[i]*0.52 + target*0.48
      }
   case StatusProcessing:
      for i := 0; i < nBars; i++ {
         wave := math.Sin(t*3+float64(i)*0.6)*0.5 + 0.5
         o.barHeights[i] = (12 + wave*18) * s
         wave := math.Sin(t*3.2+float64(i)*0.5)*0.5 + 0.5
         o.barHeights[i] = (14 + wave*22) * s
      }
   default:
      for i := 0; i < nBars; i++ {
         o.barHeights[i] = o.barHeights[i]*0.85 + 14*s*0.15
         o.barHeights[i] = o.barHeights[i]*0.8 + 16*s*0.2
      }
   }
}
// ---- Rendering ----
func (o *winOverlay) renderFrame(memDC, hwnd, screenDC, font, strFmt uintptr, barClr uint32, text string, alpha byte) {
func (o *winOverlay) renderFrame(memDC, hwnd, screenDC, font, strFmt uintptr, status Status, barClr uint32, text string, alpha byte) {
   var gfx uintptr
   gpFromHDC.Call(memDC, uintptr(unsafe.Pointer(&gfx)))
   if gfx == 0 {
@@ -596,11 +603,21 @@
   gpSetTextHint.Call(gfx, textHintAntiAlias)
   gpClear.Call(gfx, 0) // transparent
   // Capsule background
   fillRoundRect(gfx, 0, 0, float64(o.sW), float64(o.sH), o.sCapR, clrCapsuleBG)
   capsuleW := float64(o.sW)
   capsuleH := float64(o.sH)
   capsuleR := o.sCapR
   edgeInset := 1.5 * o.scale
   contentInsetX := 18.0 * o.scale
   contentInsetY := 14.0 * o.scale
   contentH := capsuleH - contentInsetY*2
   // Colored halo + lighter translucent body.
   fillRoundRect(gfx, 0, 0, capsuleW, capsuleH, capsuleR, scaleAlpha(barClr, 0.16))
   fillRoundRect(gfx, edgeInset, edgeInset, capsuleW-edgeInset*2, capsuleH-edgeInset*2, maxf(capsuleR-edgeInset, 1), clrCapsuleBG)
   fillRoundRect(gfx, contentInsetX, contentInsetY, capsuleW-contentInsetX*2, contentH*0.58, maxf(contentH*0.29, 8*o.scale), clrCapsuleHi)
   // Inner border
   strokeRoundRect(gfx, 0.5, 0.5, float64(o.sW)-1, float64(o.sH)-1, o.sCapR-0.5, clrBorder, 1)
   strokeRoundRect(gfx, 0.5, 0.5, capsuleW-1, capsuleH-1, maxf(capsuleR-0.5, 1), clrBorder, float32(maxf(1.0*o.scale, 1)))
   // Layout: bars + optional text, centered
   barsW := float32(float64(nBars)*o.sBarW + float64(nBars-1)*o.sBarGap)
@@ -608,30 +625,66 @@
   if text != "" && font != 0 {
      textW = measureText(gfx, font, strFmt, text)
   }
   maxTextW := float32(capsuleW-contentInsetX*2) - barsW - float32(o.sTxtGap)
   if maxTextW < 0 {
      maxTextW = 0
   }
   if textW > maxTextW {
      textW = maxTextW
   }
   totalW := barsW
   if textW > 0 {
      totalW += float32(o.sTxtGap) + textW
   }
   startX := (float32(o.sW) - totalW) / 2
   // Bar bed glow
   barBedX := float64(startX) - 12*o.scale
   barBedW := float64(barsW) + 24*o.scale
   fillRoundRect(
      gfx,
      barBedX,
      contentInsetY+4*o.scale,
      barBedW,
      contentH-8*o.scale,
      maxf((contentH-8*o.scale)/2, 6*o.scale),
      scaleAlpha(barClr, 0.10),
   )
   // Bars
   for i := 0; i < nBars; i++ {
      bx := startX + float32(float64(i)*(o.sBarW+o.sBarGap))
      bh := float32(o.barHeights[i])
      if bh < float32(6*o.scale) {
         bh = float32(6 * o.scale)
      if bh < float32(8*o.scale) {
         bh = float32(8 * o.scale)
      }
      if bh > float32(40*o.scale) {
         bh = float32(40 * o.scale)
      if bh > float32(56*o.scale) {
         bh = float32(56 * o.scale)
      }
      by := (float32(o.sH) - bh) / 2
      fillRoundRect(gfx, float64(bx), float64(by), o.sBarW, float64(bh), o.sBarR, barClr)
      barColor := barFillColor(status, i, o.animTime, barClr)
      glowH := float64(bh) + 18*o.scale
      glowY := float64(by) - 9*o.scale
      fillRoundRect(
         gfx,
         float64(bx)-o.sBarW*0.4,
         glowY,
         o.sBarW*1.8,
         glowH,
         o.sBarR*1.8,
         scaleAlpha(barColor, 0.18),
      )
      fillRoundRect(gfx, float64(bx), float64(by), o.sBarW, float64(bh), o.sBarR, barColor)
   }
   // Text
   if text != "" && font != 0 && strFmt != 0 {
      tx := startX + barsW + float32(o.sTxtGap)
      drawText(gfx, font, strFmt, text, tx, 0, textW+4, float32(o.sH), clrText)
      textColor := uint32(clrText)
      if status == StatusReady || status == StatusDone {
         textColor = uint32(clrTextMuted)
      }
      drawText(gfx, font, strFmt, text, tx, 0, textW+4, float32(o.sH), textColor)
   }
   // UpdateLayeredWindow
@@ -642,8 +695,84 @@
      memDC, uintptr(unsafe.Pointer(&ptSrc)), 0, uintptr(unsafe.Pointer(&blend)), ulwAlpha)
}
func barFillColor(status Status, index int, t float64, base uint32) uint32 {
   progress := 0.0
   if nBars > 1 {
      progress = float64(index) / float64(nBars-1)
   }
   switch status {
   case StatusRecording, StatusFreetalking:
      return mixColor(spectralColor(progress+t*0.08), base, 0.18)
   case StatusLoading, StatusProcessing:
      return mixColor(spectralColor(progress+t*0.16), base, 0.32)
   case StatusReady, StatusDone:
      return mixColor(spectralColor(progress+t*0.03), clrGreen, 0.70)
   case StatusError:
      return mixColor(clrRed, clrPink, progress*0.35)
   case StatusCancelled, StatusNoVoice, StatusNoContent:
      return mixColor(clrOrange, clrPink, progress*0.22)
   default:
      return base
   }
}
// ---- GDI+ drawing helpers ----
func clamp01(v float64) float64 {
   if v < 0 {
      return 0
   }
   if v > 1 {
      return 1
   }
   return v
}
func maxf(a, b float64) float64 {
   if a > b {
      return a
   }
   return b
}
func argb(a, r, g, b byte) uint32 {
   return uint32(a)<<24 | uint32(r)<<16 | uint32(g)<<8 | uint32(b)
}
func colorChannels(c uint32) (byte, byte, byte, byte) {
   return byte(c >> 24), byte(c >> 16), byte(c >> 8), byte(c)
}
func mixColor(a, b uint32, t float64) uint32 {
   t = clamp01(t)
   aa, ar, ag, ab := colorChannels(a)
   ba, br, bg, bb := colorChannels(b)
   mix := func(x, y byte) byte {
      return byte(math.Round(float64(x) + (float64(y)-float64(x))*t))
   }
   return argb(mix(aa, ba), mix(ar, br), mix(ag, bg), mix(ab, bb))
}
func scaleAlpha(c uint32, factor float64) uint32 {
   factor = clamp01(factor)
   a, r, g, b := colorChannels(c)
   return argb(byte(math.Round(float64(a)*factor)), r, g, b)
}
func spectralColor(progress float64) uint32 {
   progress = progress - math.Floor(progress)
   switch {
   case progress < 0.25:
      return mixColor(clrCyan, clrBlue, progress/0.25)
   case progress < 0.55:
      return mixColor(clrBlue, clrViolet, (progress-0.25)/0.30)
   case progress < 0.80:
      return mixColor(clrViolet, clrPink, (progress-0.55)/0.25)
   default:
      return mixColor(clrPink, 0xFFFF8AA8, (progress-0.80)/0.20)
   }
}
func makeRoundRectPath(x, y, w, h, r float64) uintptr {
   var path uintptr
   gpCreatePath.Call(fillModeAlternate, uintptr(unsafe.Pointer(&path)))