5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 01:31:54 +08:00

add set custom badge and update docs/examples/readmes

This commit is contained in:
popaprozac 2025-04-26 23:49:07 -07:00
parent 51c0d1dcdc
commit 900da01ad7
14 changed files with 427 additions and 4 deletions

View File

@ -74,6 +74,30 @@ badgeService.SetBadge("3")
badgeService.SetBadge("New")
```
### Setting a Custom Badge
Set a badge on the application tile/dock icon with one-off options applied:
#### Go
```go
options := badge.Options{
BackgroundColour: color.RGBA{0, 255, 255, 255},
FontName: "arialb.ttf", // System font
FontSize: 16,
SmallFontSize: 10,
TextColour: color.RGBA{0, 0, 0, 255},
}
// Set a default badge
badgeService.SetCustomBadge("", options)
// Set a numeric badge
badgeService.SetCustomBadge("3", options)
// Set a text badge
badgeService.SetCustomBadge("New", options)
```
### Removing a Badge
Remove the badge from the application icon:

View File

@ -35,7 +35,7 @@ app := application.New(application.Options{
### Setting a Badge
Set a badge on the application tile/dock icon:
Set a badge on the application tile/dock icon with the global options applied:
#### Go
```go
@ -63,6 +63,54 @@ SetBadge("3")
SetBadge("New")
```
### Setting a Custom Badge
Set a badge on the application tile/dock icon with one-off options applied:
#### Go
```go
// Set a default badge
badgeService.SetCustomBadge("")
// Set a numeric badge
badgeService.SetCustomBadge("3")
// Set a text badge
badgeService.SetCustomBadge("New")
```
#### JS
```js
import {SetCustomBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
const options = {
BackgroundColour: RGBA.createFrom({
R: 0,
G: 255,
B: 255,
A: 255,
}),
FontName: "arialb.ttf", // System font
FontSize: 16,
SmallFontSize: 10,
TextColour: RGBA.createFrom({
R: 0,
G: 0,
B: 0,
A: 255,
}),
}
// Set a default badge
SetCustomBadge("", options)
// Set a numeric badge
SetCustomBadge("3", options)
// Set a text badge
SetCustomBadge("New", options)
```
### Removing a Badge
Remove the badge from the application icon:

View File

@ -5,3 +5,7 @@ import * as Service from "./service.js";
export {
Service
};
export {
Options
} from "./models.js";

View File

@ -0,0 +1,58 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as color$0 from "../../../../../../../image/color/models.js";
export class Options {
"TextColour": color$0.RGBA;
"BackgroundColour": color$0.RGBA;
"FontName": string;
"FontSize": number;
"SmallFontSize": number;
/** Creates a new Options instance. */
constructor($$source: Partial<Options> = {}) {
if (!("TextColour" in $$source)) {
this["TextColour"] = (new color$0.RGBA());
}
if (!("BackgroundColour" in $$source)) {
this["BackgroundColour"] = (new color$0.RGBA());
}
if (!("FontName" in $$source)) {
this["FontName"] = "";
}
if (!("FontSize" in $$source)) {
this["FontSize"] = 0;
}
if (!("SmallFontSize" in $$source)) {
this["SmallFontSize"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new Options instance from a string or object.
*/
static createFrom($$source: any = {}): Options {
const $$createField0_0 = $$createType0;
const $$createField1_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("TextColour" in $$parsedSource) {
$$parsedSource["TextColour"] = $$createField0_0($$parsedSource["TextColour"]);
}
if ("BackgroundColour" in $$parsedSource) {
$$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]);
}
return new Options($$parsedSource as Partial<Options>);
}
}
// Private type creation functions
const $$createType0 = color$0.RGBA.createFrom;

View File

@ -10,6 +10,10 @@
// @ts-ignore: Unused imports
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
/**
* RemoveBadge removes the badge label from the application icon.
*/
@ -23,3 +27,7 @@ export function RemoveBadge(): $CancellablePromise<void> {
export function SetBadge(label: string): $CancellablePromise<void> {
return $Call.ByID(3052354152, label);
}
export function SetCustomBadge(label: string, options: $models.Options): $CancellablePromise<void> {
return $Call.ByID(921166821, label, options);
}

View File

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
RGBA
} from "./models.js";

View File

@ -0,0 +1,46 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
/**
* RGBA represents a traditional 32-bit alpha-premultiplied color, having 8
* bits for each of red, green, blue and alpha.
*
* An alpha-premultiplied color component C has been scaled by alpha (A), so
* has valid values 0 <= C <= A.
*/
export class RGBA {
"R": number;
"G": number;
"B": number;
"A": number;
/** Creates a new RGBA instance. */
constructor($$source: Partial<RGBA> = {}) {
if (!("R" in $$source)) {
this["R"] = 0;
}
if (!("G" in $$source)) {
this["G"] = 0;
}
if (!("B" in $$source)) {
this["B"] = 0;
}
if (!("A" in $$source)) {
this["A"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new RGBA instance from a string or object.
*/
static createFrom($$source: any = {}): RGBA {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new RGBA($$parsedSource as Partial<RGBA>);
}
}

View File

@ -1,3 +1,6 @@
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
(function polyfill() {
const relList = document.createElement("link").relList;
if (relList && relList.supports && relList.supports("modulepreload")) {
@ -1329,12 +1332,67 @@ function RemoveBadge() {
function SetBadge(label) {
return ByID(3052354152, label);
}
function SetCustomBadge(label, options) {
return ByID(921166821, label, options);
}
class RGBA {
/** Creates a new RGBA instance. */
constructor($$source = {}) {
__publicField(this, "R");
__publicField(this, "G");
__publicField(this, "B");
__publicField(this, "A");
if (!("R" in $$source)) {
this["R"] = 0;
}
if (!("G" in $$source)) {
this["G"] = 0;
}
if (!("B" in $$source)) {
this["B"] = 0;
}
if (!("A" in $$source)) {
this["A"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new RGBA instance from a string or object.
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === "string" ? JSON.parse($$source) : $$source;
return new RGBA($$parsedSource);
}
}
const setCustomButton = document.getElementById("set-custom");
const setButton = document.getElementById("set");
const removeButton = document.getElementById("remove");
const setButtonUsingGo = document.getElementById("set-go");
const removeButtonUsingGo = document.getElementById("remove-go");
const labelElement = document.getElementById("label");
const timeElement = document.getElementById("time");
setCustomButton.addEventListener("click", () => {
console.log("click!");
let label = labelElement.value;
SetCustomBadge(label, {
BackgroundColour: RGBA.createFrom({
R: 0,
G: 255,
B: 255,
A: 255
}),
FontName: "arialb.ttf",
// System font
FontSize: 16,
SmallFontSize: 10,
TextColour: RGBA.createFrom({
R: 0,
G: 0,
B: 0,
A: 255
})
});
});
setButton.addEventListener("click", () => {
let label = labelElement.value;
SetBadge(label);

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/style.css"/>
<title>Wails App</title>
<script type="module" crossorigin src="/assets/index-edhLCYCH.js"></script>
<script type="module" crossorigin src="/assets/index-DHsC0KxN.js"></script>
</head>
<body>
<div class="container">
@ -23,6 +23,7 @@
<div class="card">
<div class="input-box" id="input">
<input class="input" id="label" type="text" autocomplete="off"/>
<button class="btn" id="set-custom">Set Custom</button>
<button class="btn" id="set">Set</button>
<button class="btn" id="remove">Remove</button>
<button class="btn" id="set-go">Set using Go</button>

View File

@ -22,6 +22,7 @@
<div class="card">
<div class="input-box" id="input">
<input class="input" id="label" type="text" autocomplete="off"/>
<button class="btn" id="set-custom">Set Custom</button>
<button class="btn" id="set">Set</button>
<button class="btn" id="remove">Remove</button>
<button class="btn" id="set-go">Set using Go</button>

View File

@ -1,6 +1,8 @@
import {Events} from "@wailsio/runtime";
import {SetBadge, RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
import {SetBadge, RemoveBadge, SetCustomBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
import { RGBA } from "../bindings/image/color/models";
const setCustomButton = document.getElementById('set-custom')! as HTMLButtonElement;
const setButton = document.getElementById('set')! as HTMLButtonElement;
const removeButton = document.getElementById('remove')! as HTMLButtonElement;
const setButtonUsingGo = document.getElementById('set-go')! as HTMLButtonElement;
@ -8,6 +10,28 @@ const removeButtonUsingGo = document.getElementById('remove-go')! as HTMLButtonE
const labelElement : HTMLInputElement = document.getElementById('label')! as HTMLInputElement;
const timeElement = document.getElementById('time')! as HTMLDivElement;
setCustomButton.addEventListener('click', () => {
console.log("click!")
let label = (labelElement as HTMLInputElement).value
SetCustomBadge(label, {
BackgroundColour: RGBA.createFrom({
R: 0,
G: 255,
B: 255,
A: 255,
}),
FontName: "arialb.ttf", // System font
FontSize: 16,
SmallFontSize: 10,
TextColour: RGBA.createFrom({
R: 0,
G: 0,
B: 0,
A: 255,
}),
});
})
setButton.addEventListener('click', () => {
let label = (labelElement as HTMLInputElement).value
SetBadge(label);

View File

@ -13,6 +13,7 @@ type platformBadge interface {
Shutdown() error
SetBadge(label string) error
SetCustomBadge(label string, options Options) error
RemoveBadge() error
}
@ -49,6 +50,10 @@ func (b *Service) SetBadge(label string) error {
return b.impl.SetBadge(label)
}
func (b *Service) SetCustomBadge(label string, options Options) error {
return b.impl.SetCustomBadge(label, options)
}
// RemoveBadge removes the badge label from the application icon.
func (b *Service) RemoveBadge() error {
return b.impl.RemoveBadge()

View File

@ -61,6 +61,12 @@ func (d *darwinBadge) SetBadge(label string) error {
return nil
}
// SetCustomBadge is not supported on macOS, SetBadge is called instead.
// (Windows-specific)
func (d *darwinBadge) SetCustomBadge(label string, options Options) error {
return d.SetBadge(label)
}
// RemoveBadge removes the badge label from the application icon.
func (d *darwinBadge) RemoveBadge() error {
C.setBadge(nil)

View File

@ -110,6 +110,63 @@ func (w *windowsBadge) SetBadge(label string) error {
return w.taskbar.SetOverlayIcon(hwnd, hicon, nil)
}
// SetCustomBadge sets the badge label on the application icon with one-off options.
func (w *windowsBadge) SetCustomBadge(label string, options Options) error {
if w.taskbar == nil {
return nil
}
app := application.Get()
if app == nil {
return nil
}
window := app.CurrentWindow()
if window == nil {
return nil
}
hwnd, err := window.NativeWindowHandle()
if err != nil {
return err
}
const badgeSize = 32
img := image.NewRGBA(image.Rect(0, 0, badgeSize, badgeSize))
backgroundColour := options.BackgroundColour
radius := badgeSize / 2
centerX, centerY := radius, radius
for y := 0; y < badgeSize; y++ {
for x := 0; x < badgeSize; x++ {
dx := float64(x - centerX)
dy := float64(y - centerY)
if dx*dx+dy*dy < float64(radius*radius) {
img.Set(x, y, backgroundColour)
}
}
}
var hicon w32.HICON
if label == "" {
hicon, err = createBadgeIcon(badgeSize, img, options)
if err != nil {
return err
}
} else {
hicon, err = createBadgeIconWithText(w, label, badgeSize, img, options)
if err != nil {
return err
}
}
defer w32.DestroyIcon(hicon)
return w.taskbar.SetOverlayIcon(hwnd, hicon, nil)
}
// RemoveBadge removes the badge label from the application icon.
func (w *windowsBadge) RemoveBadge() error {
if w.taskbar == nil {
@ -160,9 +217,33 @@ func (w *windowsBadge) createBadgeIcon() (w32.HICON, error) {
return hicon, err
}
func createBadgeIcon(badgeSize int, img *image.RGBA, options Options) (w32.HICON, error) {
radius := badgeSize / 2
centerX, centerY := radius, radius
innerRadius := badgeSize / 5
for y := 0; y < badgeSize; y++ {
for x := 0; x < badgeSize; x++ {
dx := float64(x - centerX)
dy := float64(y - centerY)
if dx*dx+dy*dy < float64(innerRadius*innerRadius) {
img.Set(x, y, options.TextColour)
}
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return 0, err
}
hicon, err := w32.CreateSmallHIconFromImage(buf.Bytes())
return hicon, err
}
// createBadgeIconWithText creates a badge icon with the specified text.
func (w *windowsBadge) createBadgeIconWithText(label string) (w32.HICON, error) {
fontPath := w.fontManager.FindFontOrDefault(w.options.FontName)
if fontPath == "" {
return w.createBadgeIcon()
@ -215,6 +296,59 @@ func (w *windowsBadge) createBadgeIconWithText(label string) (w32.HICON, error)
return w32.CreateSmallHIconFromImage(buf.Bytes())
}
func createBadgeIconWithText(w *windowsBadge, label string, badgeSize int, img *image.RGBA, options Options) (w32.HICON, error) {
fontPath := w.fontManager.FindFontOrDefault(options.FontName)
if fontPath == "" {
return createBadgeIcon(badgeSize, img, options)
}
fontBytes, err := os.ReadFile(fontPath)
if err != nil {
return createBadgeIcon(badgeSize, img, options)
}
ttf, err := opentype.Parse(fontBytes)
if err != nil {
return createBadgeIcon(badgeSize, img, options)
}
fontSize := float64(options.FontSize)
if len(label) > 1 {
fontSize = float64(options.SmallFontSize)
}
// Get DPI of the current screen
screen := w32.GetDesktopWindow()
dpi := w32.GetDpiForWindow(screen)
face, err := opentype.NewFace(ttf, &opentype.FaceOptions{
Size: fontSize,
DPI: float64(dpi),
Hinting: font.HintingFull,
})
if err != nil {
return createBadgeIcon(badgeSize, img, options)
}
defer face.Close()
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(options.TextColour),
Face: face,
}
textWidth := d.MeasureString(label).Ceil()
d.Dot = fixed.P((badgeSize-textWidth)/2, int(float64(badgeSize)/2+fontSize/2))
d.DrawString(label)
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return 0, err
}
return w32.CreateSmallHIconFromImage(buf.Bytes())
}
// createBadge creates a circular badge with the specified background color.
func (w *windowsBadge) createBadge() {
w.badgeSize = 32