Files
Git-Manager-Gui/src/features/trayPopup.js
T
2026-10-22 09:13:21 +00:00

141 lines
4.5 KiB
JavaScript

// Eigenes Menüfenster für das Tray-Symbol.
// Das native Electron-Menü zeichnet unter Windows keine Hover-Markierung; dieses kleine Fenster
// (renderer/tray-menu.html) übernimmt Aussehen und Markierung selbst.
const { BrowserWindow, screen, nativeTheme, ipcMain } = require('electron');
const WIDTH = 286; // Fensterbreite inkl. 8 px Rand für den Schatten
const DEFAULT_HEIGHT = 320;
const MARGIN = 8;
function clamp(value, min, max) {
return Math.max(min, Math.min(value, Math.max(min, max)));
}
// Position des Menüs: am Mauszeiger, innerhalb des sichtbaren Bereichs. Liegt der Zeiger in der
// Taskleiste (unter/über dem Arbeitsbereich), sitzt das Menü direkt daneben.
function computePosition(point, workArea, width, height) {
const x = clamp(point.x - Math.round(width / 2), workArea.x + MARGIN, workArea.x + workArea.width - width - MARGIN);
let y;
if (point.y >= workArea.y + workArea.height) y = workArea.y + workArea.height - height - MARGIN;
else if (point.y <= workArea.y) y = workArea.y + MARGIN;
else y = clamp(point.y - Math.round(height / 2), workArea.y + MARGIN, workArea.y + workArea.height - height - MARGIN);
return { x, y };
}
function createTrayPopup({ htmlPath, preloadPath, getModel, onAction }) {
let win = null;
let height = DEFAULT_HEIGHT;
let shownAt = 0;
let handlersRegistered = false;
const own = (event) => win && !win.isDestroyed() && event.sender === win.webContents;
function registerHandlers() {
if (handlersRegistered) return;
handlersRegistered = true;
ipcMain.on('tray-menu:action', (event, id) => {
if (!own(event)) return;
hide();
try { onAction(String(id)); } catch (e) { console.warn('[Tray] Aktion fehlgeschlagen:', e.message); }
});
ipcMain.on('tray-menu:close', (event) => { if (own(event)) hide(); });
ipcMain.on('tray-menu:resize', (event, contentHeight) => {
if (!own(event)) return;
const h = Math.round(Number(contentHeight));
if (!(h > 40 && h < 900)) return;
height = h;
if (win.isVisible()) {
const { x, y } = computePosition(screen.getCursorScreenPoint(), screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workArea, WIDTH, height);
win.setBounds({ x, y, width: WIDTH, height });
} else {
win.setContentSize(WIDTH, height);
}
});
}
function ensureWindow() {
if (win && !win.isDestroyed()) return win;
registerHandlers();
win = new BrowserWindow({
width: WIDTH,
height,
show: false,
frame: false,
transparent: true,
resizable: false,
movable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
skipTaskbar: true,
alwaysOnTop: true,
focusable: true,
hasShadow: false,
webPreferences: {
preload: preloadPath,
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
spellcheck: false
}
});
win.setAlwaysOnTop(true, 'pop-up-menu');
win.on('blur', () => { if (Date.now() - shownAt > 250) hide(); });
win.on('closed', () => { win = null; });
win.loadFile(htmlPath);
return win;
}
function push() {
if (!win || win.isDestroyed()) return;
win.webContents.send('tray-menu-state', { items: getModel(), dark: nativeTheme.shouldUseDarkColors });
}
function show() {
const w = ensureWindow();
const send = () => {
push();
const point = screen.getCursorScreenPoint();
const { x, y } = computePosition(point, screen.getDisplayNearestPoint(point).workArea, WIDTH, height);
w.setBounds({ x, y, width: WIDTH, height });
shownAt = Date.now();
w.show();
w.focus();
};
if (w.webContents.isLoading()) w.webContents.once('did-finish-load', send);
else send();
}
function hide() {
if (win && !win.isDestroyed() && win.isVisible()) win.hide();
}
function refresh() {
if (win && !win.isDestroyed() && win.isVisible()) push();
}
// Lädt das versteckte Fenster im Voraus. Der erste Start des Renderers dauert mehrere Sekunden;
// ohne Vorladen würde der erste Rechtsklick auf das Tray-Symbol entsprechend hängen.
function warmUp() {
ensureWindow();
}
function destroy() {
if (win && !win.isDestroyed()) win.destroy();
win = null;
}
return {
show,
warmUp,
hide,
refresh,
destroy,
isVisible: () => !!win && !win.isDestroyed() && win.isVisible(),
getWindow: () => (win && !win.isDestroyed() ? win : null)
};
}
module.exports = { createTrayPopup, computePosition };