133091a32e
首次启动: - 配置文件不存在时弹欢迎对话框:选「启用密码保护」或「稍后再说」 - 选启用直接进 setPasswordDialog,OK 时 store.SetPassword 触发首次保存 - io/fs.ErrNotExist 检测新分支 - modalCard 抽出 modalShell 内核以便 welcomeDialog 自定义 footer 真窗口图标: - tools/gen_ico 从 PNG 生成多分辨率 .ico(7 尺寸:16~256) - 提交 rsrc_windows.syso(rsrc 通过 goproxy.cn 生成),go build 自动链入 - tray_windows.go 改用嵌入资源 ID=1,失败 fallback IDI_APPLICATION - README 更新完整的图标生成流程
145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
// gen_ico produces a multi-resolution .ico file from a single PNG
|
|
// source. The output embeds each size as a PNG payload (Vista+ .ico
|
|
// format) so the result stays small and crisp.
|
|
//
|
|
// Usage:
|
|
//
|
|
// go run ./tools/gen_ico [src.png] [dst.ico]
|
|
//
|
|
// Defaults:
|
|
//
|
|
// src = internal/ui/assets/app_icon.png
|
|
// dst = internal/ui/assets/app_icon.ico
|
|
//
|
|
// After generating the .ico, embed it into the Windows binary via:
|
|
//
|
|
// go run github.com/akavel/rsrc@latest \
|
|
// -ico internal/ui/assets/app_icon.ico \
|
|
// -o internal/ui/assets/rsrc_windows.syso
|
|
//
|
|
// rsrc_windows.syso is auto-linked by `go build` on Windows.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"io"
|
|
"os"
|
|
|
|
"golang.org/x/image/draw"
|
|
)
|
|
|
|
// icoSizes are the embedded resolutions. Windows picks the largest size
|
|
// that fits the target use (title bar = 16, Alt-Tab = 32, Explorer = up
|
|
// to 256) and scales down. Skipping sizes Windows can't read anyway
|
|
// keeps the file under 200 KB.
|
|
var icoSizes = []int{16, 24, 32, 48, 64, 128, 256}
|
|
|
|
// icoEntry holds one image's dimensions and its encoded PNG bytes.
|
|
type icoEntry struct {
|
|
w, h int
|
|
png []byte
|
|
}
|
|
|
|
func main() {
|
|
srcPath := "internal/ui/assets/app_icon.png"
|
|
dstPath := "internal/ui/assets/app_icon.ico"
|
|
if len(os.Args) > 1 {
|
|
srcPath = os.Args[1]
|
|
}
|
|
if len(os.Args) > 2 {
|
|
dstPath = os.Args[2]
|
|
}
|
|
if err := run(srcPath, dstPath); err != nil {
|
|
fmt.Fprintln(os.Stderr, "gen_ico:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run(srcPath, dstPath string) error {
|
|
srcF, err := os.Open(srcPath)
|
|
if err != nil {
|
|
return fmt.Errorf("open src: %w", err)
|
|
}
|
|
defer srcF.Close()
|
|
src, _, err := image.Decode(srcF)
|
|
if err != nil {
|
|
return fmt.Errorf("decode src: %w", err)
|
|
}
|
|
|
|
var entries []icoEntry
|
|
for _, size := range icoSizes {
|
|
dst := image.NewNRGBA(image.Rect(0, 0, size, size))
|
|
// CatmullRom gives sharper results at small sizes than the
|
|
// default nearest-neighbour.
|
|
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
|
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, dst); err != nil {
|
|
return fmt.Errorf("encode %dpx: %w", size, err)
|
|
}
|
|
entries = append(entries, icoEntry{w: size, h: size, png: buf.Bytes()})
|
|
}
|
|
|
|
out, err := os.Create(dstPath)
|
|
if err != nil {
|
|
return fmt.Errorf("create dst: %w", err)
|
|
}
|
|
defer out.Close()
|
|
return writeICO(out, entries)
|
|
}
|
|
|
|
// writeICO serialises a Vista+ PNG-based .ico file: ICONDIR + one
|
|
// ICONDIRENTRY per image + the PNG bytes concatenated.
|
|
func writeICO(w io.Writer, entries []icoEntry) error {
|
|
if _, err := w.Write([]byte{
|
|
0, 0, // reserved
|
|
1, 0, // type = 1 (icon)
|
|
byte(len(entries)), 0, // count
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
const dirHdrLen = 6
|
|
const dirEntryLen = 16
|
|
headerLen := dirHdrLen + dirEntryLen*len(entries)
|
|
|
|
offset := headerLen
|
|
for _, e := range entries {
|
|
wByte := byte(e.w)
|
|
if e.w >= 256 {
|
|
// 0 means 256 in the .ico directory.
|
|
wByte = 0
|
|
}
|
|
hByte := byte(e.h)
|
|
if e.h >= 256 {
|
|
hByte = 0
|
|
}
|
|
size := uint32(len(e.png))
|
|
|
|
entry := make([]byte, dirEntryLen)
|
|
entry[0] = wByte
|
|
entry[1] = hByte
|
|
entry[2] = 0 // color palette
|
|
entry[3] = 0 // reserved
|
|
binary.LittleEndian.PutUint16(entry[4:], 1) // planes
|
|
binary.LittleEndian.PutUint16(entry[6:], 32) // bit count
|
|
binary.LittleEndian.PutUint32(entry[8:], size)
|
|
binary.LittleEndian.PutUint32(entry[12:], uint32(offset))
|
|
if _, err := w.Write(entry); err != nil {
|
|
return err
|
|
}
|
|
offset += int(size)
|
|
}
|
|
|
|
for _, e := range entries {
|
|
if _, err := w.Write(e.png); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|