Dynamic generation of task bar overlay icons in Electron
David Rickard
Posted on December 29, 2022
Electron exposes the ability to show an overlay icon on the taskbar. It's incredibly flexible: allowing you to show any image you want there. But in my case, I just wanted a circle with a number in it to represent the number of unread items.
Pre-generating a bunch of images to bundle with the app didn't seem very appealing, so I settled on a dynamic generation approach:
1) Create an in-memory <canvas>
element in the renderer code.
2) Draw the circle and the number in the center of it.
3) Call canvas.toDataURL()
and pass this string to the main process via IPC.
4) Create a NativeImage
from the data URI.
5) Call BrowserWindow.setOverlayIcon
and pass in the NativeImage
.
We do the generation in the render process because it has access to Canvas APIs for free.
Here's the code to generate the Data URI:
export function renderTaskBarOverlay(count: number): string {
if (count > 99) {
count = 99;
}
const canvas: HTMLCanvasElement = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Could not get context');
}
const textY = count > 9 ? 24 : 25;
const fontSize = count > 9 ? 20 : 24;
ctx.beginPath();
ctx.arc(16, 16, 16, 0, 2 * Math.PI, false);
ctx.fillStyle = '#007A5D';
ctx.fill();
ctx.font = `${fontSize}px sans-serif`;
ctx.textAlign = 'center';
ctx.fillStyle = 'white';
ctx.fillText(count.toString(), 16, textY);
return canvas.toDataURL(); // Defaults to PNG
}
The Electron docs say that this is a 16x16 icon, but I think that must be device-independent pixels they are using because 32x32 looked a lot better on my 4k monitor.
Once it gets to the main process, you can handle it:
export interface TaskBarOverlay {
imageDataUri: string;
description: string;
}
ipcMain.on('setOverlay', (event: Event, overlay: TaskBarOverlay | null) => {
if (overlay) {
const image = nativeImage.createFromDataURL(overlay.imageDataUri);
mainWindow.setOverlayIcon(image, overlay.description);
} else {
mainWindow.setOverlayIcon(null, '');
}
});
mainWindow.on('closed', () => {
ipcMain.removeHandler('setOverlay');
});
And that's it. I was a bit surprised how little code was needed for this.
Posted on December 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.