/** * Größe und Lage des Hauptfensters – reine Logik, ohne Electron. * * **Wozu.** Das Fenster startete bis 0.22.0 immer mit derselben Größe an der * Systemposition. Wer maximiert arbeitet oder es schmal neben eine * Bildschirmlupe oder ein zweites Programm stellt, richtete das bei jedem * Start neu ein — und das sind gerade die Nutzer, für die diese Anwendung * gebaut ist. * * **Warum die Prüfung hier liegt und nicht bei der Fenstererzeugung.** An ihr * hängt der einzige gefährliche Fall: eine gespeicherte Lage auf einem * Bildschirm, den es nicht mehr gibt. Das Fenster ginge dann außerhalb jeder * sichtbaren Fläche auf — vorhanden, aber unerreichbar, und ohne Fenster gibt * es auch keinen Weg, die Einstellung zurückzusetzen. Als reine Funktion * lässt sich das prüfen, ohne einen zweiten Monitor abzustecken. */ /** Fenstergröße und -lage, wie sie gespeichert wird. */ export interface Fensterlage { readonly breite: number; readonly hoehe: number; readonly x?: number; readonly y?: number; readonly maximiert?: boolean; } /** Ein Rechteck einer verfügbaren Bildschirmfläche. */ export interface Flaeche { readonly x: number; readonly y: number; readonly width: number; readonly height: number; } /** Vorgabe, wenn nichts gespeichert ist oder das Gespeicherte nicht taugt. */ export const FENSTER_VORGABE = Object.freeze({ breite: 1180, hoehe: 820 }); /** Untergrenzen; dieselben wie `minWidth`/`minHeight` am Fenster. */ export const FENSTER_MINDESTBREITE = 360; export const FENSTER_MINDESTHOEHE = 560; /** So viel vom Fenster muss auf einer Fläche liegen, damit es greifbar ist. */ const SICHTBAR_MINDESTENS = 80; function zahl(wert: unknown): number | null { return typeof wert === 'number' && Number.isFinite(wert) ? Math.round(wert) : null; } /** * Nimmt eine gespeicherte Lage entgegen und gibt zurück, was davon taugt. * * `null` heißt: nichts Brauchbares, es gilt die Vorgabe. */ export function fensterlageBereinigen(wert: unknown): Fensterlage | null { if (typeof wert !== 'object' || wert === null) { return null; } const roh = wert as Record; const breite = zahl(roh['breite']); const hoehe = zahl(roh['hoehe']); if (breite === null || hoehe === null) { return null; } const x = zahl(roh['x']); const y = zahl(roh['y']); return { /* Nach unten gekappt, nicht nach oben: Ein zu großes Fenster schiebt das Betriebssystem selbst zurecht, ein zu kleines wäre unbedienbar. */ breite: Math.max(FENSTER_MINDESTBREITE, breite), hoehe: Math.max(FENSTER_MINDESTHOEHE, hoehe), ...(x === null ? {} : { x }), ...(y === null ? {} : { y }), ...(roh['maximiert'] === true ? { maximiert: true } : {}), }; } /** * Ob die gespeicherte Lage auf einer der vorhandenen Flächen liegt. * * Verlangt wird kein vollständiges Enthaltensein: Ein Fenster, das ein Stück * über den Rand ragt, ist gewollt und alltäglich. Verlangt wird, dass ein * greifbarer Teil sichtbar bleibt — sonst ließe es sich nicht einmal mehr * anfassen, um es zurückzuholen. */ export function lageIstErreichbar(lage: Fensterlage, flaechen: readonly Flaeche[]): boolean { const { x, y } = lage; if (x === undefined || y === undefined) { /* Ohne Position entscheidet das Betriebssystem – das ist immer gültig. */ return true; } return flaechen.some((flaeche) => { const ueberlappungX = Math.min(x + lage.breite, flaeche.x + flaeche.width) - Math.max(x, flaeche.x); const ueberlappungY = Math.min(y + lage.hoehe, flaeche.y + flaeche.height) - Math.max(y, flaeche.y); return ueberlappungX >= SICHTBAR_MINDESTENS && ueberlappungY >= SICHTBAR_MINDESTENS; }); } /** * Die Lage, mit der das Fenster aufgehen soll. * * Fällt auf die Vorgabe zurück, wenn nichts gespeichert ist oder die * gespeicherte Lage auf keiner vorhandenen Fläche mehr liegt. */ export function startlage(gespeichert: unknown, flaechen: readonly Flaeche[]): Fensterlage { const lage = fensterlageBereinigen(gespeichert); if (lage === null || !lageIstErreichbar(lage, flaechen)) { return { ...FENSTER_VORGABE }; } return lage; }