5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-12 23:19:29 +08:00

render label on windows and sensible defaults

This commit is contained in:
popaprozac 2025-04-24 20:23:28 -07:00
parent d1d57c132c
commit 7287c1eb5c
5 changed files with 122 additions and 45 deletions

View File

@ -36,14 +36,14 @@ app := application.New(application.Options{
Set a badge on the application tile/dock icon:
```go
// Set a default badge
badgeService.SetBadge("")
// Set a numeric badge
badgeService.SetBadge("3")
// Set a text badge
badgeService.SetBadge("New")
// Set a symbol badge
badgeService.SetBadge("●")
```
### Removing a Badge
@ -52,11 +52,6 @@ Remove the badge from the application icon:
```go
badgeService.RemoveBadge()
// or
// Set an empty string
badgeService.SetBadge("")
```
## Platform Considerations
@ -67,9 +62,10 @@ badgeService.SetBadge("")
On macOS, badges:
- Are displayed directly on the dock icon
- Support both text and numeric values
- Support text values
- Automatically handle dark/light mode appearance
- Use the standard macOS dock badge styling
- Automatically handle label overflow
</TabItem>
@ -78,10 +74,10 @@ badgeService.SetBadge("")
On Windows, badges:
- Are displayed as an overlay icon in the taskbar
- Currently implemented as a red circle with a white center
- Do not currently support displaying text or numbers
- Support text values
- Adapt to Windows theme settings
- Require the application to have a window
- Does not handle label overflow
</TabItem>

View File

@ -49,6 +49,7 @@ require (
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/flopp/go-findfont v0.1.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
)

View File

@ -117,6 +117,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU=
github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=

View File

@ -45,6 +45,8 @@ func (d *darwinBadge) SetBadge(label string) error {
if label != "" {
cLabel = C.CString(label)
defer C.free(unsafe.Pointer(cLabel))
} else {
cLabel = C.CString("●") // Default badge character
}
C.setBadge(cLabel)
return nil

View File

@ -8,11 +8,17 @@ import (
"image"
"image/color"
"image/png"
"os"
"strings"
"syscall"
"unsafe"
"github.com/flopp/go-findfont"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/w32"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/math/fixed"
)
var (
@ -102,7 +108,9 @@ func (t *ITaskbarList3) SetOverlayIcon(hwnd syscall.Handle, hIcon syscall.Handle
}
type windowsBadge struct {
taskbar *ITaskbarList3
taskbar *ITaskbarList3
badgeImg *image.RGBA
badgeSize int
}
func New() *Service {
@ -150,13 +158,19 @@ func (w *windowsBadge) SetBadge(label string) error {
return err
}
if label == "" {
return w.taskbar.SetOverlayIcon(syscall.Handle(hwnd), 0, nil)
}
w.createBadge()
hicon, err := createBadgeIcon()
if err != nil {
return err
var hicon w32.HICON
if label == "" {
hicon, err = w.createBadgeIcon()
if err != nil {
return err
}
} else {
hicon, err = w.createBadgeIconWithText(label)
if err != nil {
return err
}
}
defer w32.DestroyIcon(hicon)
@ -186,17 +200,99 @@ func (w *windowsBadge) RemoveBadge() error {
return w.taskbar.SetOverlayIcon(syscall.Handle(hwnd), 0, nil)
}
func createBadgeIcon() (w32.HICON, error) {
const size = 32
func (w *windowsBadge) createBadgeIcon() (w32.HICON, error) {
radius := w.badgeSize / 2
centerX, centerY := radius, radius
white := color.RGBA{255, 255, 255, 255}
innerRadius := w.badgeSize / 5
img := image.NewRGBA(image.Rect(0, 0, size, size))
for y := 0; y < w.badgeSize; y++ {
for x := 0; x < w.badgeSize; x++ {
dx := float64(x - centerX)
dy := float64(y - centerY)
if dx*dx+dy*dy < float64(innerRadius*innerRadius) {
w.badgeImg.Set(x, y, white)
}
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, w.badgeImg); err != nil {
return 0, err
}
hicon, err := w32.CreateSmallHIconFromImage(buf.Bytes())
return hicon, err
}
func (w *windowsBadge) createBadgeIconWithText(label string) (w32.HICON, error) {
fontPath := ""
for _, path := range findfont.List() {
if strings.Contains(strings.ToLower(path), "segoeuib.ttf") || // Segoe UI Bold
strings.Contains(strings.ToLower(path), "arialbd.ttf") {
fontPath = path
break
}
}
if fontPath == "" {
return w.createBadgeIcon()
}
fontBytes, err := os.ReadFile(fontPath)
if err != nil {
return 0, err
}
ttf, err := opentype.Parse(fontBytes)
if err != nil {
return 0, err
}
fontSize := 18.0
if len(label) > 1 {
fontSize = 14.0
}
face, err := opentype.NewFace(ttf, &opentype.FaceOptions{
Size: fontSize,
DPI: 96,
Hinting: font.HintingFull,
})
if err != nil {
return 0, err
}
defer face.Close()
d := &font.Drawer{
Dst: w.badgeImg,
Src: image.NewUniform(color.White),
Face: face,
}
textWidth := d.MeasureString(label).Ceil()
d.Dot = fixed.P((w.badgeSize-textWidth)/2, int(float64(w.badgeSize)/2+fontSize/2))
d.DrawString(label)
var buf bytes.Buffer
if err := png.Encode(&buf, w.badgeImg); err != nil {
return 0, err
}
return w32.CreateSmallHIconFromImage(buf.Bytes())
}
func (w *windowsBadge) createBadge() {
w.badgeSize = 32
img := image.NewRGBA(image.Rect(0, 0, w.badgeSize, w.badgeSize))
red := color.RGBA{255, 0, 0, 255}
radius := size / 2
radius := w.badgeSize / 2
centerX, centerY := radius, radius
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
for y := 0; y < w.badgeSize; y++ {
for x := 0; x < w.badgeSize; x++ {
dx := float64(x - centerX)
dy := float64(y - centerY)
@ -206,25 +302,5 @@ func createBadgeIcon() (w32.HICON, error) {
}
}
white := color.RGBA{255, 255, 255, 255}
innerRadius := size / 5
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
dx := float64(x - centerX)
dy := float64(y - centerY)
if dx*dx+dy*dy < float64(innerRadius*innerRadius) {
img.Set(x, y, white)
}
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return 0, err
}
hicon, err := w32.CreateSmallHIconFromImage(buf.Bytes())
return hicon, err
w.badgeImg = img
}