// Package gui provides the Fyne desktop app: a settings window and a system // tray that start/stop the flinger engine. // // The engine itself lives in internal/flinger and is GUI-free, so the tray // can control streaming without any UI dependency. This package wires the two // together: config persistence, the settings form, the tray menu and the // tray status icon. package gui import ( "bytes" "image" "image/color" "image/png" ) // traySize is the icon size in pixels. Tray hosts typically request 22px; // we render larger and let the host scale down for crispness. const traySize = 64 // newTrayResource renders a rounded square with a status dot into a // fyne.Resource that SetSystemTrayIcon accepts. func newTrayResource(bg, dot color.NRGBA) *staticResource { img := image.NewRGBA(image.Rect(0, 0, traySize, traySize)) // Rounded-square background. radius := 14 for y := 0; y < traySize; y++ { for x := 0; x < traySize; x++ { if inRoundedRect(x, y, radius) { img.Set(x, y, bg) } } } // Center status dot. cx, cy, r := traySize/2, traySize/2, 16 for y := cy - r; y <= cy+r; y++ { for x := cx - r; x <= cx+r; x++ { if (x-cx)*(x-cx)+(y-cy)*(y-cy) <= r*r { img.Set(x, y, dot) } } } var buf bytes.Buffer _ = png.Encode(&buf, img) return &staticResource{name: "teleportfling-tray", data: buf.Bytes()} } // inRoundedRect reports whether (x,y) falls inside the rounded square. func inRoundedRect(x, y, radius int) bool { const m = traySize - 1 switch { case x >= radius && x <= m-radius: return true case y >= radius && y <= m-radius: return true } // Corner: within radius of the nearest corner centre. var cx, cy int if x < radius { cx = radius } else { cx = m - radius } if y < radius { cy = radius } else { cy = m - radius } dx, dy := x-cx, y-cy return dx*dx+dy*dy <= radius*radius } // staticResource is a simple in-memory fyne.Resource. type staticResource struct { name string data []byte } func (r *staticResource) Name() string { return r.name } func (r *staticResource) Content() []byte { return r.data }