feat: add tray streaming status and app icon
- 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
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// 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 }
|
||||
Reference in New Issue
Block a user