tray: a click on the bird flaps it — the wing beats twice and the tail flicks up once, 16 frames over about half a second (build/icon/gen.go tray-flap bends the outline about the tail's root and the shoulder, eased, so it stays one line); and the usage beside it no longer sits hard against it, a thin space between (Image #13: 图标与用量之间没有任何的间距)
No files matched your search
@@ -41,6 +41,7 @@ app: build
|
||||
|
||||
icons:
|
||||
@go run build/icon/gen.go tray internal/gui/tray.png
|
||||
@go run build/icon/gen.go tray-flap internal/gui/trayflap
|
||||
@go run build/icon/gen.go app 64 internal/gui/icon.png
|
||||
@go run build/icon/gen.go app 1024 internal/gui/icon-1024.png
|
||||
@rm -rf build/darwin/magpie.iconset && mkdir -p build/darwin/magpie.iconset
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// internal/gui/assets/index.html.
|
||||
//
|
||||
// go run build/icon/gen.go tray internal/gui/tray.png # 44px black template icon (macOS menu bar)
|
||||
// go run build/icon/gen.go tray-flap internal/gui/trayflap # its frames when clicked (a size after the folder for a bigger look)
|
||||
// go run build/icon/gen.go app 64 internal/gui/icon.png # app icon at a given size
|
||||
// make icons # regenerates all of them plus magpie.icns
|
||||
package main
|
||||
@@ -38,6 +39,19 @@ func main() {
|
||||
switch {
|
||||
case len(os.Args) == 3 && os.Args[1] == "tray":
|
||||
img = tray()
|
||||
case len(os.Args) == 3 && os.Args[1] == "tray-flap":
|
||||
if err := trayFlap(os.Args[2], 44); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
case len(os.Args) == 4 && os.Args[1] == "tray-flap":
|
||||
n, _ := strconv.Atoi(os.Args[3])
|
||||
if err := trayFlap(os.Args[2], n); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
case len(os.Args) == 4 && os.Args[1] == "glyph":
|
||||
n, _ := strconv.Atoi(os.Args[2])
|
||||
img = glyph(n)
|
||||
@@ -71,6 +85,86 @@ func tray() image.Image {
|
||||
return img
|
||||
}
|
||||
|
||||
// flapFrames is how many pictures one flap of the tray bird is drawn in.
|
||||
const flapFrames = 16
|
||||
|
||||
// trayFlap writes the frames the tray icon plays when it is clicked,
|
||||
// flap-01.png to flap-16.png: the wing beats twice and the tail flicks up
|
||||
// once, both easing out of and back into the still bird. The drawing is
|
||||
// bent, not cut: every point of the outline near the tail turns about the
|
||||
// tail's root, and every point near the wing about the shoulder, by less
|
||||
// the nearer it is to the pivot, so the outline stays whole.
|
||||
func trayFlap(dir string, S int) error {
|
||||
base := parse(smallSVG)
|
||||
for f := 1; f <= flapFrames; f++ {
|
||||
t := float64(f) / (flapFrames + 1)
|
||||
env := math.Sin(math.Pi * t) // 0 at both ends
|
||||
tail := 14 * env * math.Sin(math.Pi*t)
|
||||
wing := 8 * env * math.Sin(4*math.Pi*t)
|
||||
if wing < 0 {
|
||||
wing /= 2 // a beat is mostly up; down only a little, into the belly
|
||||
}
|
||||
rings := make([][][2]float64, len(base))
|
||||
for i, r := range base {
|
||||
rings[i] = make([][2]float64, len(r))
|
||||
for j, p := range r {
|
||||
// Tail and wing meet in a notch of the outline (ring 0): above
|
||||
// it the outline bends with the tail, below it with the wing,
|
||||
// eased across it so it stays one line. The holes, the
|
||||
// shoulder and the belly, move only where they meet the wing.
|
||||
up, down := 0.0, smooth((p[1]-25)/3)
|
||||
if i == 0 {
|
||||
up = 1 - smooth((p[1]-24)/2.5)
|
||||
}
|
||||
p = bend(p, [2]float64{12, 24}, [2]float64{2, 6}, 5, tail*up)
|
||||
p = bend(p, [2]float64{29, 19.5}, [2]float64{8, 30}, 3.5, wing*down)
|
||||
rings[i][j] = p
|
||||
}
|
||||
}
|
||||
k := float64(S) / 44
|
||||
cov := coverage(rings, S, 8, k, 0, 0)
|
||||
img := image.NewNRGBA(image.Rect(0, 0, S, S))
|
||||
for i, a := range cov {
|
||||
img.SetNRGBA(i%S, i/S, color.NRGBA{0, 0, 0, uint8(a*255 + 0.5)})
|
||||
}
|
||||
out, err := os.Create(fmt.Sprintf("%s/flap-%02d.png", dir, f))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = png.Encode(out, img)
|
||||
out.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bend turns p about pivot by deg degrees (clockwise on screen, which
|
||||
// raises what points left), by all of it at tip's end of the axis from pivot
|
||||
// to tip and by none at the pivot or behind it, fading too with p's distance
|
||||
// from that axis past width.
|
||||
func bend(p, pivot, tip [2]float64, width, deg float64) [2]float64 {
|
||||
ax, ay := tip[0]-pivot[0], tip[1]-pivot[1]
|
||||
l := math.Hypot(ax, ay)
|
||||
dx, dy := p[0]-pivot[0], p[1]-pivot[1]
|
||||
along := (dx*ax + dy*ay) / (l * l)
|
||||
across := math.Abs(dx*ay-dy*ax) / l
|
||||
w := smooth(along/0.45) * (1 - smooth((across-width)/width))
|
||||
if w <= 0 {
|
||||
return p
|
||||
}
|
||||
a := deg * w * math.Pi / 180
|
||||
s, c := math.Sin(a), math.Cos(a)
|
||||
return [2]float64{pivot[0] + dx*c - dy*s, pivot[1] + dx*s + dy*c}
|
||||
}
|
||||
|
||||
// smooth eases 0..1 (clamped) in and out.
|
||||
func smooth(x float64) float64 {
|
||||
x = math.Max(0, math.Min(1, x))
|
||||
return x * x * (3 - 2*x)
|
||||
}
|
||||
|
||||
// glyph is the bird alone, black on clear, as big on an n-pixel canvas as
|
||||
// app draws it on its tile: the layer of build/darwin/AppIcon.icon, which
|
||||
// macOS 26 lays on a tile of its own, light or dark (#117).
|
||||
|
||||
@@ -4,8 +4,9 @@ package gui
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -26,6 +27,12 @@ import (
|
||||
//go:embed tray.png
|
||||
var trayIcon []byte // black glyph, tinted by the macOS menu bar
|
||||
|
||||
// trayFlap is the bird beating its wing and flicking its tail, played on
|
||||
// the tray icon when it is clicked (build/icon/gen.go tray-flap).
|
||||
//
|
||||
//go:embed trayflap/*.png
|
||||
var trayFlap embed.FS
|
||||
|
||||
//go:embed icon.png
|
||||
var appIcon []byte // coloured, for other trays
|
||||
|
||||
@@ -56,6 +63,9 @@ type host struct {
|
||||
panel *application.WebviewWindow
|
||||
main *application.WebviewWindow
|
||||
tray *application.SystemTray
|
||||
// flapping is set while the tray bird plays its flap, so a second
|
||||
// click in it doesn't start another over it
|
||||
flapping atomic.Bool
|
||||
|
||||
panelHeight int
|
||||
glides atomic.Int64 // the newest panel glide; older ones stop
|
||||
@@ -302,6 +312,9 @@ func Run(version string, showMain bool, link string) error {
|
||||
// the quick panel by the icon, or the main window if the user would
|
||||
// rather (Settings → Tray icon)
|
||||
h.tray.OnClick(func() {
|
||||
if runtime.GOOS == "darwin" {
|
||||
go h.flap()
|
||||
}
|
||||
if settings.Load().Tray == "window" {
|
||||
h.ShowMain("")
|
||||
return
|
||||
@@ -375,3 +388,22 @@ func abs(n int) int {
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// flap plays trayFlap on the tray icon, about half a second, and ends on the
|
||||
// still bird.
|
||||
func (h *host) flap() {
|
||||
if !h.flapping.CompareAndSwap(false, true) {
|
||||
return
|
||||
}
|
||||
defer h.flapping.Store(false)
|
||||
names, _ := fs.Glob(trayFlap, "trayflap/*.png")
|
||||
for _, n := range names {
|
||||
b, err := trayFlap.ReadFile(n)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
h.tray.SetTemplateIcon(b)
|
||||
time.Sleep(28 * time.Millisecond)
|
||||
}
|
||||
h.tray.SetTemplateIcon(trayIcon)
|
||||
}
|
||||
|
After Width: | Height: | Size: 829 B |
|
After Width: | Height: | Size: 854 B |
|
After Width: | Height: | Size: 851 B |
|
After Width: | Height: | Size: 830 B |
|
After Width: | Height: | Size: 833 B |
|
After Width: | Height: | Size: 853 B |
|
After Width: | Height: | Size: 870 B |
|
After Width: | Height: | Size: 835 B |
|
After Width: | Height: | Size: 854 B |
|
After Width: | Height: | Size: 859 B |
|
After Width: | Height: | Size: 843 B |
|
After Width: | Height: | Size: 837 B |
|
After Width: | Height: | Size: 833 B |
|
After Width: | Height: | Size: 845 B |
|
After Width: | Height: | Size: 835 B |
|
After Width: | Height: | Size: 830 B |
@@ -4,6 +4,7 @@ package gui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/yetone/magpie/internal/provider"
|
||||
@@ -35,6 +36,10 @@ func (h *host) watchTrayUsage() {
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
if label != "" && runtime.GOOS == "darwin" {
|
||||
// the menu bar sets the text hard against the icon
|
||||
label = "\u2009" + label
|
||||
}
|
||||
if label+"\x00"+tip != shown {
|
||||
shown = label + "\x00" + tip
|
||||
h.tray.SetLabel(label)
|
||||
|
||||