From 3a7a91e18745d7d2a1861493411b459f2410e5b1 Mon Sep 17 00:00:00 2001 From: LudoO Date: Tue, 4 Feb 2025 16:31:24 +0100 Subject: [PATCH] feat: add tray window (based on electron-tray-window) --- src/main/MainRunner.ts | 101 +++++++++++++++------------ src/main/tray.ts | 150 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 44 deletions(-) create mode 100644 src/main/tray.ts diff --git a/src/main/MainRunner.ts b/src/main/MainRunner.ts index 98fffc7..803b03d 100644 --- a/src/main/MainRunner.ts +++ b/src/main/MainRunner.ts @@ -1,6 +1,21 @@ -import { app, BrowserWindow, Menu, RenderProcessGoneDetails, Tray } from 'electron' +import { app, BrowserWindow, RenderProcessGoneDetails, BrowserWindowConstructorOptions } from 'electron' import Constants from './utils/Constants' import IPCs from './IPCs' +import { createTray, setWindowAutoHide, showWindow } from './tray.ts' + +const options = { + tray: true, + trayWindow: true, + width: Constants.IS_DEV_ENV ? 1500 : 1200, + height: 650, + trayOptions: { + menu: false, + tooltip: 'Vutron App', + margin: {x:0, y:0}, + width: Constants.IS_DEV_ENV ? 1500 : 1200, + height: 650, + } +} const exitApp = (mainWindow: BrowserWindow): void => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -10,15 +25,35 @@ const exitApp = (mainWindow: BrowserWindow): void => { app.exit() } -export const createMainWindow = async (mainWindow: BrowserWindow): Promise => { - mainWindow = new BrowserWindow({ +export const createMainWindow = async (): Promise => { + let opt: BrowserWindowConstructorOptions = { title: Constants.APP_NAME, show: false, - width: Constants.IS_DEV_ENV ? 1500 : 1200, - height: 650, + width: options.width, + height: options.height, useContentSize: true, - webPreferences: Constants.DEFAULT_WEB_PREFERENCES - }) + webPreferences: Constants.DEFAULT_WEB_PREFERENCES, + frame: !options.trayWindow, + } + if (options.trayWindow){ + opt = { + ...opt, + width: options.trayOptions.width , + height: options.trayOptions.height, + maxWidth: options.trayOptions.width, + maxHeight: options.trayOptions.height, + show: false, + frame: false, + fullscreenable: false, + resizable: false, + transparent: true, + alwaysOnTop: true, + webPreferences: { + backgroundThrottling: false + } + } + } + const mainWindow = new BrowserWindow(opt) mainWindow.setMenu(null) @@ -33,12 +68,21 @@ export const createMainWindow = async (mainWindow: BrowserWindow): Promise { - mainWindow.setAlwaysOnTop(true) - mainWindow.show() - mainWindow.focus() - mainWindow.setAlwaysOnTop(false) - }) + if (options.tray) { + createTray(mainWindow, options.trayOptions); + } + + if (options.trayWindow) { + setWindowAutoHide(mainWindow); + // showWindow(mainWindow) + }else{ + mainWindow.once('ready-to-show', (): void => { + mainWindow.setAlwaysOnTop(true) + mainWindow.show() + mainWindow.focus() + mainWindow.setAlwaysOnTop(false) + }) + } // Initialize IPC Communication IPCs.initialize() @@ -49,8 +93,6 @@ export const createMainWindow = async (mainWindow: BrowserWindow): Promise { - mainWindow.show(); - } - }, - { - label: 'Hide App', - click: () => { - mainWindow.hide(); - } - }, - { - label: 'Exit', - click: () => { - app.quit(); - } - } - ]); - tray.on('double-click', function (event) { - mainWindow.show(); - }); - tray.setToolTip('Vutron App'); - tray.setContextMenu(contextMenu); -} diff --git a/src/main/tray.ts b/src/main/tray.ts new file mode 100644 index 0000000..6b6d3b8 --- /dev/null +++ b/src/main/tray.ts @@ -0,0 +1,150 @@ +import { app, Menu, Tray, BaseWindow, BrowserWindow } from 'electron' +import Constants from './utils/Constants.ts' +const electron = require("electron"); + +// defaults +// const width = 800; +// const height = 500; +// const DEFAULT_MARGIN = {x: 0, y: 0} ; + +let tray; +let trayOptions; +const DEFAULT_OPTIONS = { + menu: false, + tooltip: 'Vutron App', + margin: {x:0, y:0}, + width: Constants.IS_DEV_ENV ? 1500 : 1200, + height: 650, +} + +export function createTray(window: BaseWindow, options) { + trayOptions = options || DEFAULT_OPTIONS; + tray = new Tray('buildAssets/icons/icon16.png'); + + tray.on('double-click', function (event) { + toggleWindow(window) + }); + tray.on('right-click', function (event) { + toggleWindow(window) + }); + tray.on('click', function (event) { + toggleWindow(window) + }); + tray.setToolTip(trayOptions.tooltip); + if (trayOptions.menu) { + const contextMenu = Menu.buildFromTemplate([ + { + label: 'Show App', + click: () => { + showWindow(window) + } + }, + { + label: 'Hide App', + click: () => { + window.hide() + } + }, + { + label: 'Exit', + click: () => { + app.quit() + } + } + ]) + // tray icon only with classic window + tray.setContextMenu(contextMenu) + } else { + // no menu for tray window + window.setMenu(null) + tray.setContextMenu(null) + } + // align at startup + alignWindow(window) + return tray +} + +export function setWindowAutoHide(window: BrowserWindow) { + window.hide(); + window.on("blur", () => { + // dont close if devtools + if (!window.webContents.isDevToolsOpened()) { + window.hide(); + // ipcMain.emit("tray-window-hidden", { window: window, tray: tray }); + } + }); +} + +export function toggleWindow(window: BaseWindow) { + if (window.isVisible()) { + window.hide(); + // ipcMain.emit("tray-window-hidden", { window: window, tray: tray }); + return; + } + + showWindow(window); + // ipcMain.emit("tray-window-visible", { window: window, tray: tray }); +} + +export function showWindow(window: BaseWindow) { + window.show() + alignWindow(window) +} + +export function alignWindow(window: BaseWindow) { + const position = calculateWindowPosition(); + const b = window.getBounds() + window.setBounds({ + width: b.width, + height: b.height, + x: position.x, + y: position.y + }); +} + +function calculateWindowPosition() { + const margin = trayOptions.margin; + const b = trayOptions + + const screenBounds = electron.screen.getPrimaryDisplay().size; + const trayBounds = tray.getBounds(); + // where is the icon on the screen? + let trayPos = 4; // 1:top-left 2:top-right 3:bottom-left 4.bottom-right + trayPos = trayBounds.y > screenBounds.height / 2 ? trayPos : trayPos / 2; + trayPos = trayBounds.x > screenBounds.width / 2 ? trayPos : trayPos - 1; + let x = 0; + let y = 0 ; + + // calculate the new window position + switch (trayPos) { + case 1: // for TOP - LEFT + x = Math.floor(trayBounds.x + margin.x + trayBounds.width / 2); + y = Math.floor(trayBounds.y + margin.y + trayBounds.height / 2); + break; + + case 2: // for TOP - RIGHT + x = Math.floor( + trayBounds.x - b.width - margin.x + trayBounds.width / 2 + ); + y = Math.floor(trayBounds.y + margin.y + trayBounds.height / 2); + break; + + case 3: // for BOTTOM - LEFT + x = Math.floor(trayBounds.x + margin.x + trayBounds.width / 2); + y = Math.floor( + trayBounds.y - b.height - margin.y + trayBounds.height / 2 + ); + break; + + case 4: // for BOTTOM - RIGHT + x = Math.floor( + trayBounds.x - b.width - margin.x + trayBounds.width / 2 + ); + y = Math.floor( + trayBounds.y - b.height - margin.y + trayBounds.height / 2 + ); + break; + } + + return { x, y }; +}