- tray icon and tooltip now reflect engine state: grey when stopped, green + live frame/drop counters in the tooltip while streaming - generate a proper app icon (assets/, via cmd/teleportfling-icon) and embed it so the window and tray carry the icon without runtime files - add a .desktop entry template for launching from an app menu - run Fyne UI updates (status label, tray icon/tooltip) on the main thread via fyne.Do to satisfy the threading model
116 lines
2.6 KiB
Go
116 lines
2.6 KiB
Go
// Command teleportfling-icon regenerates the application icon assets in
|
|
// assets/ from scratch. It draws a rounded green square with two lighter
|
|
// "fling" dots suggesting motion.
|
|
//
|
|
// Usage:
|
|
//
|
|
// teleportfling-icon
|
|
//
|
|
// The generated PNGs are committed to the repo and embedded into the binary
|
|
// via the assets package; this tool exists so the icon can be tweaked and
|
|
// regenerated without a design tool.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"os"
|
|
)
|
|
|
|
// sizes are the icon sizes to emit. Hosts scale from these; 512 is used for
|
|
// the app icon, 256 and 64 for smaller contexts.
|
|
var sizes = []int{512, 256, 64}
|
|
|
|
// outputDir is where the PNGs are written (the repo assets directory).
|
|
const outputDir = "assets"
|
|
|
|
// inRoundedRect reports whether (x,y) lies inside a square with rounded
|
|
// corners of the given radius.
|
|
func inRoundedRect(x, y, radius, size int) bool {
|
|
m := size - 1
|
|
switch {
|
|
case x >= radius && x <= m-radius:
|
|
return true
|
|
case y >= radius && y <= m-radius:
|
|
return true
|
|
}
|
|
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
|
|
}
|
|
|
|
// render draws the icon at the given size into a PNG.
|
|
func render(size int, bg, dot color.NRGBA) []byte {
|
|
img := image.NewNRGBA(image.Rect(0, 0, size, size))
|
|
radius := size / 5
|
|
for y := 0; y < size; y++ {
|
|
for x := 0; x < size; x++ {
|
|
if inRoundedRect(x, y, radius, size) {
|
|
img.SetNRGBA(x, y, bg)
|
|
}
|
|
}
|
|
}
|
|
// Two dots suggesting a flung stream, offset diagonally.
|
|
for i, off := range []int{3, 9} {
|
|
cx, cy, r := size/2, size/2+off*size/32, size/8-i
|
|
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.SetNRGBA(x, y, dot)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var buf bytes.Buffer
|
|
_ = png.Encode(&buf, img)
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func main() {
|
|
bg := color.NRGBA{R: 46, G: 125, B: 50, A: 255} // teleport green
|
|
dot := color.NRGBA{R: 150, G: 220, B: 140, A: 255}
|
|
|
|
//nolint:gosec // writing generated PNG assets into the repo, not secrets
|
|
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for _, size := range sizes {
|
|
name := "teleportfling"
|
|
if size != 512 {
|
|
name += "-" + itoa(size)
|
|
}
|
|
path := outputDir + "/" + name + ".png"
|
|
//nolint:gosec // generated PNG assets, world-readable for installers
|
|
if err := os.WriteFile(path, render(size, bg, dot), 0o644); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func itoa(v int) string {
|
|
if v == 0 {
|
|
return "0"
|
|
}
|
|
var buf [10]byte
|
|
i := len(buf)
|
|
for v > 0 {
|
|
i--
|
|
buf[i] = byte('0' + v%10)
|
|
v /= 10
|
|
}
|
|
return string(buf[i:])
|
|
}
|