import { isTauriRuntime } from './globalShortcut'; // Pushes the current aggregate unread count to main, which updates the // Tray tooltip and (on Windows) the taskbar overlay icon. We render the // badge here in the renderer because main has no Canvas2D — painting a // red bubble with the count, encoding to PNG, and handing the buffer // to main keeps the implementation free of a native canvas dependency. // // No-op outside the Electron runtime (e.g. browser dev preview) so no // guards needed at call-sites. export async function updateTrayUnread(count: number): Promise { if (!isTauriRuntime()) return; const n = Math.max(0, Math.floor(count)); const badgeDataUrl = n > 0 ? renderBadgePng(n) : null; try { await window.electronAPI.setTrayUnread(n, badgeDataUrl); } catch (err: unknown) { console.warn('updateTrayUnread failed', err); } } // Discord-style red bubble with white count. // // Source is rendered at 64×64 so Windows' high-quality downsample to the // 16×16 taskbar overlay slot retains crisp edges (4× supersampling). // Earlier 32×32 + 2px white ring blurred badly: the ring became a half // pixel at the target size, and the bigger font fell into AA mush. Now // no outer ring (Discord doesn't use one either) and a full-bleed circle. function renderBadgePng(count: number): string | null { if (typeof document === 'undefined') return null; const size = 64; const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); if (!ctx) return null; ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; // Solid red bubble, full bleed. const center = size / 2; ctx.beginPath(); ctx.arc(center, center, center, 0, Math.PI * 2); ctx.fillStyle = '#ef4444'; ctx.fill(); // Count label — Discord parity: cap at 99 with a "+" once we cross it. // Sizes are tuned per glyph count so each variant fills the bubble // without clipping when Windows downsamples to 16×16. const label = count > 99 ? '99+' : String(count); const fontPx = label.length >= 3 ? 30 : label.length === 2 ? 40 : 48; ctx.fillStyle = '#fff'; // Segoe UI is the Windows system font; explicit weight 800 keeps the // glyph chunky after downsampling. Fallbacks cover macOS/Linux. ctx.font = `800 ${fontPx}px "Segoe UI", system-ui, -apple-system, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // +2 vertical nudge: most system fonts render numerals visually high // relative to the baseline mid-point; the offset re-centres them. ctx.fillText(label, center, center + 2); return canvas.toDataURL('image/png'); }