mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 11:43:20 +08:00
add set custom badge and update docs/examples/readmes
This commit is contained in:
parent
51c0d1dcdc
commit
900da01ad7
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -5,3 +5,7 @@ import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export {
|
||||
Options
|
||||
} from "./models.js";
|
||||
|
@ -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;
|
@ -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);
|
||||
}
|
||||
|
@ -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";
|
@ -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>);
|
||||
}
|
||||
}
|
@ -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);
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user