// Startanimation des Hauptfensters (abschaltbar unter Einstellungen → Darstellung). // // Ablauf: Ein randloses, durchsichtiges Fenster liegt genau dort, wo das Programm erscheinen wird, // und zeigt das sich drehende Logo samt Schriftzug. Ist die Oberfläche geladen, wird das noch // unsichtbare Hauptfenster abfotografiert und dieses Bild im Startfenster von der Mitte nach außen // aufgezogen. Am Ende übernimmt das echte Fenster an exakt derselben Stelle, so wirkt es, als baue // sich das Programm auf. // // Das Startfenster wird VOR dem Hauptfenster angelegt (begin), damit das Logo so früh wie möglich // erscheint. Danach lädt das Hauptfenster, und play() übernimmt den Rest. const { BrowserWindow } = require('electron'); const MIN_SPIN_MS = 1600; // so lange dreht sich das Logo mindestens const MAX_WAIT_MS = 12000; // spätestens dann wird das Hauptfenster gezeigt const MAX_GROW_MS = 2500; // der Aufbau darf nie länger dauern, sonst bliebe das Fenster unsichtbar const MAX_PAINT_WAIT_MS = 1500; const IGNORE_MOUSE_DELAY_MS = 600; // Mausereignisse erst nach dem ersten Zeichnen ignorieren, sonst wird das Fenster weiß const wait = (ms) => new Promise((r) => setTimeout(r, ms)); // Wie lange noch gewartet werden muss, damit das Logo mindestens minMs lang zu sehen war. function remainingMs(shownAt, now, minMs) { if (!shownAt) return 0; return Math.max(0, minMs - (now - shownAt)); } // Position und Größe des Hauptfensters, zentriert im Arbeitsbereich. Wird für Start- und Hauptfenster // gleich berechnet, damit beide exakt übereinander liegen. function centeredBounds(workArea, width, height) { const w = Math.min(width, workArea.width); const h = Math.min(height, workArea.height); return { x: Math.round(workArea.x + (workArea.width - w) / 2), y: Math.round(workArea.y + (workArea.height - h) / 2), width: w, height: h }; } // Legt das Startfenster an und zeigt es, sobald es geladen ist. // Liefert { window, shownAt }: shownAt ist ein Promise auf den Zeitpunkt, an dem das Logo sichtbar wurde. function begin({ htmlPath, bounds }) { const window = new BrowserWindow({ x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height, frame: false, transparent: true, resizable: false, movable: false, minimizable: false, maximizable: false, fullscreenable: false, skipTaskbar: true, hasShadow: false, focusable: false, show: false, backgroundColor: '#00000000', title: 'Git Manager', webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true, spellcheck: false } }); const shownAt = window.loadFile(htmlPath).then(() => { if (window.isDestroyed()) return 0; window.showInactive(); // Das Startfenster soll keine Mausklicks schlucken. WICHTIG: Der Aufruf muss NACH dem Zeigen und etwas // verzögert erfolgen. Davor aufgerufen wird das durchsichtige Fenster unter Windows weiß (getestet). setTimeout(() => { if (!window.isDestroyed()) window.setIgnoreMouseEvents(true); }, IGNORE_MOUSE_DELAY_MS); return Date.now(); }).catch(() => 0); return { window, shownAt }; } // Wartet, bis die Oberfläche gezeichnet ist (Liste da oder nach höchstens MAX_PAINT_WAIT_MS), // sonst wüchse ein halbleeres Fenster auf. async function waitForPaintedUi(webContents) { const check = (expression) => webContents.executeJavaScript(expression, true).catch(() => true); const until = Date.now() + MAX_PAINT_WAIT_MS; while (Date.now() < until) { if (await check("document.readyState === 'complete' && (document.querySelectorAll('#explorerGrid > *').length > 0)")) break; await wait(100); } await wait(200); } /** * Spielt die Startanimation und zeigt am Ende das Hauptfenster. * @param {BrowserWindow} mainWin Hauptfenster, noch nicht angezeigt, an denselben Bounds wie das Startfenster * @param {Promise} uiReady wird erfüllt, sobald die Oberfläche geladen ist * @param {{window: BrowserWindow, shownAt: Promise}} startHandle Ergebnis von begin() */ async function play(mainWin, uiReady, startHandle) { const start = startHandle.window; const showMain = () => { if (mainWin.isDestroyed()) return; mainWin.setOpacity(1); mainWin.setIgnoreMouseEvents(false); if (!mainWin.isVisible()) mainWin.show(); mainWin.focus(); }; try { const shownAt = await startHandle.shownAt; await Promise.race([uiReady, wait(MAX_WAIT_MS)]); await wait(remainingMs(shownAt, Date.now(), MIN_SPIN_MS)); if (mainWin.isDestroyed()) return; // Unsichtbar zeigen, damit Chromium die Oberfläche zeichnet, dann abfotografieren mainWin.setOpacity(0); mainWin.setIgnoreMouseEvents(true); // unsichtbar: Klicks gehen nicht versehentlich hinein mainWin.show(); await waitForPaintedUi(mainWin.webContents); if (mainWin.isDestroyed()) return; const image = await mainWin.webContents.capturePage(); if (!image.isEmpty() && !start.isDestroyed()) { await Promise.race([start.webContents.executeJavaScript(`aufbauen(${JSON.stringify(image.toDataURL())})`), wait(MAX_GROW_MS)]); } } catch (_) { // Die Animation ist nur Zierde: im Fehlerfall einfach das Hauptfenster zeigen } finally { showMain(); setTimeout(() => { if (!start.isDestroyed()) start.destroy(); }, 80); } } module.exports = { begin, play, remainingMs, centeredBounds, MIN_SPIN_MS };