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:
parent
d1d57c132c
commit
7287c1eb5c
@ -36,14 +36,14 @@ app := application.New(application.Options{
|
|||||||
Set a badge on the application tile/dock icon:
|
Set a badge on the application tile/dock icon:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Set a default badge
|
||||||
|
badgeService.SetBadge("")
|
||||||
|
|
||||||
// Set a numeric badge
|
// Set a numeric badge
|
||||||
badgeService.SetBadge("3")
|
badgeService.SetBadge("3")
|
||||||
|
|
||||||
// Set a text badge
|
// Set a text badge
|
||||||
badgeService.SetBadge("New")
|
badgeService.SetBadge("New")
|
||||||
|
|
||||||
// Set a symbol badge
|
|
||||||
badgeService.SetBadge("●")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Removing a Badge
|
### Removing a Badge
|
||||||
@ -52,11 +52,6 @@ Remove the badge from the application icon:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
badgeService.RemoveBadge()
|
badgeService.RemoveBadge()
|
||||||
|
|
||||||
// or
|
|
||||||
|
|
||||||
// Set an empty string
|
|
||||||
badgeService.SetBadge("")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Platform Considerations
|
## Platform Considerations
|
||||||
@ -67,9 +62,10 @@ badgeService.SetBadge("")
|
|||||||
On macOS, badges:
|
On macOS, badges:
|
||||||
|
|
||||||
- Are displayed directly on the dock icon
|
- Are displayed directly on the dock icon
|
||||||
- Support both text and numeric values
|
- Support text values
|
||||||
- Automatically handle dark/light mode appearance
|
- Automatically handle dark/light mode appearance
|
||||||
- Use the standard macOS dock badge styling
|
- Use the standard macOS dock badge styling
|
||||||
|
- Automatically handle label overflow
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
@ -78,10 +74,10 @@ badgeService.SetBadge("")
|
|||||||
On Windows, badges:
|
On Windows, badges:
|
||||||
|
|
||||||
- Are displayed as an overlay icon in the taskbar
|
- Are displayed as an overlay icon in the taskbar
|
||||||
- Currently implemented as a red circle with a white center
|
- Support text values
|
||||||
- Do not currently support displaying text or numbers
|
|
||||||
- Adapt to Windows theme settings
|
- Adapt to Windows theme settings
|
||||||
- Require the application to have a window
|
- Require the application to have a window
|
||||||
|
- Does not handle label overflow
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ require (
|
|||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
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/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // 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
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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/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 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
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 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
|
@ -45,6 +45,8 @@ func (d *darwinBadge) SetBadge(label string) error {
|
|||||||
if label != "" {
|
if label != "" {
|
||||||
cLabel = C.CString(label)
|
cLabel = C.CString(label)
|
||||||
defer C.free(unsafe.Pointer(cLabel))
|
defer C.free(unsafe.Pointer(cLabel))
|
||||||
|
} else {
|
||||||
|
cLabel = C.CString("●") // Default badge character
|
||||||
}
|
}
|
||||||
C.setBadge(cLabel)
|
C.setBadge(cLabel)
|
||||||
return nil
|
return nil
|
||||||
|
@ -8,11 +8,17 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/flopp/go-findfont"
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
"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 (
|
var (
|
||||||
@ -102,7 +108,9 @@ func (t *ITaskbarList3) SetOverlayIcon(hwnd syscall.Handle, hIcon syscall.Handle
|
|||||||
}
|
}
|
||||||
|
|
||||||
type windowsBadge struct {
|
type windowsBadge struct {
|
||||||
taskbar *ITaskbarList3
|
taskbar *ITaskbarList3
|
||||||
|
badgeImg *image.RGBA
|
||||||
|
badgeSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Service {
|
func New() *Service {
|
||||||
@ -150,13 +158,19 @@ func (w *windowsBadge) SetBadge(label string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if label == "" {
|
w.createBadge()
|
||||||
return w.taskbar.SetOverlayIcon(syscall.Handle(hwnd), 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
hicon, err := createBadgeIcon()
|
var hicon w32.HICON
|
||||||
if err != nil {
|
if label == "" {
|
||||||
return err
|
hicon, err = w.createBadgeIcon()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hicon, err = w.createBadgeIconWithText(label)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer w32.DestroyIcon(hicon)
|
defer w32.DestroyIcon(hicon)
|
||||||
|
|
||||||
@ -186,17 +200,99 @@ func (w *windowsBadge) RemoveBadge() error {
|
|||||||
return w.taskbar.SetOverlayIcon(syscall.Handle(hwnd), 0, nil)
|
return w.taskbar.SetOverlayIcon(syscall.Handle(hwnd), 0, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBadgeIcon() (w32.HICON, error) {
|
func (w *windowsBadge) createBadgeIcon() (w32.HICON, error) {
|
||||||
const size = 32
|
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}
|
red := color.RGBA{255, 0, 0, 255}
|
||||||
radius := size / 2
|
radius := w.badgeSize / 2
|
||||||
centerX, centerY := radius, radius
|
centerX, centerY := radius, radius
|
||||||
|
|
||||||
for y := 0; y < size; y++ {
|
for y := 0; y < w.badgeSize; y++ {
|
||||||
for x := 0; x < size; x++ {
|
for x := 0; x < w.badgeSize; x++ {
|
||||||
dx := float64(x - centerX)
|
dx := float64(x - centerX)
|
||||||
dy := float64(y - centerY)
|
dy := float64(y - centerY)
|
||||||
|
|
||||||
@ -206,25 +302,5 @@ func createBadgeIcon() (w32.HICON, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
white := color.RGBA{255, 255, 255, 255}
|
w.badgeImg = img
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user