Some checks failed
Release / build (push) Failing after 21s
Replace Node.js + Python codebase with three Go binaries: - claude-statusline: CLI status bar for Claude Code - claude-fetcher: standalone cron job for API usage - claude-widget: system tray icon (fyne-io/systray + fogleman/gg) All CGO-free for trivial cross-compilation. Add nfpm .deb packaging with autostart and cron. CI pipeline produces Linux + Windows binaries, .deb, .tar.gz, and .zip release assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
package renderer
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"math"
|
|
|
|
"github.com/fogleman/gg"
|
|
)
|
|
|
|
const iconSize = 256
|
|
|
|
// Claude orange for the starburst logo.
|
|
var claudeOrange = color.RGBA{224, 123, 83, 255}
|
|
|
|
// arcColors maps usage percentage thresholds to colors.
|
|
var arcColors = []struct {
|
|
threshold int
|
|
color color.RGBA
|
|
}{
|
|
{10, color.RGBA{76, 175, 80, 255}}, // green
|
|
{20, color.RGBA{67, 160, 71, 255}}, // darker green
|
|
{30, color.RGBA{124, 179, 66, 255}}, // light green
|
|
{40, color.RGBA{192, 202, 51, 255}}, // lime
|
|
{50, color.RGBA{253, 216, 53, 255}}, // yellow
|
|
{60, color.RGBA{255, 193, 7, 255}}, // amber
|
|
{70, color.RGBA{255, 179, 0, 255}}, // darker amber
|
|
{80, color.RGBA{255, 152, 0, 255}}, // orange
|
|
{90, color.RGBA{255, 87, 34, 255}}, // deep orange
|
|
{100, color.RGBA{244, 67, 54, 255}}, // red
|
|
}
|
|
|
|
func getArcColor(pct int) color.RGBA {
|
|
for _, ac := range arcColors {
|
|
if pct <= ac.threshold {
|
|
return ac.color
|
|
}
|
|
}
|
|
return arcColors[len(arcColors)-1].color
|
|
}
|
|
|
|
// drawStarburst draws the 8-petal Claude logo.
|
|
func drawStarburst(dc *gg.Context) {
|
|
cx := float64(iconSize) / 2
|
|
cy := float64(iconSize) / 2
|
|
petalLen := float64(iconSize) * 0.38
|
|
petalWidth := float64(iconSize) * 0.10
|
|
centerRadius := float64(iconSize) * 0.04
|
|
|
|
dc.SetColor(claudeOrange)
|
|
|
|
for i := 0; i < 8; i++ {
|
|
angle := float64(i) * (2 * math.Pi / 8)
|
|
|
|
// Tip of the petal
|
|
tipX := cx + petalLen*math.Cos(angle)
|
|
tipY := cy + petalLen*math.Sin(angle)
|
|
|
|
// Base points (perpendicular to angle)
|
|
perpAngle := angle + math.Pi/2
|
|
baseX1 := cx + petalWidth*math.Cos(perpAngle)
|
|
baseY1 := cy + petalWidth*math.Sin(perpAngle)
|
|
baseX2 := cx - petalWidth*math.Cos(perpAngle)
|
|
baseY2 := cy - petalWidth*math.Sin(perpAngle)
|
|
|
|
// Inner point (slightly behind center for petal shape)
|
|
innerX := cx - petalWidth*0.5*math.Cos(angle)
|
|
innerY := cy - petalWidth*0.5*math.Sin(angle)
|
|
|
|
dc.MoveTo(innerX, innerY)
|
|
dc.LineTo(baseX1, baseY1)
|
|
dc.LineTo(tipX, tipY)
|
|
dc.LineTo(baseX2, baseY2)
|
|
dc.ClosePath()
|
|
dc.Fill()
|
|
}
|
|
|
|
// Center dot
|
|
dc.DrawCircle(cx, cy, centerRadius)
|
|
dc.Fill()
|
|
}
|
|
|
|
// drawArc draws a circular progress arc.
|
|
func drawArc(dc *gg.Context, pct int) {
|
|
if pct <= 0 {
|
|
return
|
|
}
|
|
if pct > 100 {
|
|
pct = 100
|
|
}
|
|
|
|
cx := float64(iconSize) / 2
|
|
cy := float64(iconSize) / 2
|
|
radius := float64(iconSize)/2 - 14 // inset from edge
|
|
arcWidth := 28.0
|
|
|
|
startAngle := -math.Pi / 2 // 12 o'clock
|
|
endAngle := startAngle + (float64(pct)/100)*2*math.Pi
|
|
|
|
dc.SetColor(getArcColor(pct))
|
|
dc.SetLineWidth(arcWidth)
|
|
dc.SetLineCap(gg.LineCapButt)
|
|
dc.DrawArc(cx, cy, radius, startAngle, endAngle)
|
|
dc.Stroke()
|
|
}
|
|
|
|
// RenderIcon generates a 256x256 PNG icon with starburst and usage arc.
|
|
func RenderIcon(pct int) image.Image {
|
|
dc := gg.NewContext(iconSize, iconSize)
|
|
drawStarburst(dc)
|
|
drawArc(dc, pct)
|
|
return dc.Image()
|
|
}
|
|
|
|
// RenderIconPNG generates the icon as PNG bytes (for systray).
|
|
func RenderIconPNG(pct int) ([]byte, error) {
|
|
img := RenderIcon(pct)
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|